+```
+
+Include `build/srcset.min.js` in your page. Then, you'll have a `srcset` object with the following API :
-
+ - `srcset.update()` -- update all images in the page
+ - `srcset.imgs.get(Assumes that an ES5 platform where, if {@code WeakMap} is
+ * already present, then it conforms to the anticipated ES6
+ * specification. To run this file on an ES5 or almost ES5
+ * implementation where the {@code WeakMap} specification does not
+ * quite conform, run repairES5.js first.
+ *
+ *
Even though WeakMapModule is not global, the linter thinks it + * is, which is why it is in the overrides list below. + * + *
NOTE: Before using this WeakMap emulation in a non-SES + * environment, see the note below about hiddenRecord. + * + * @author Mark S. Miller + * @requires crypto, ArrayBuffer, Uint8Array, navigator, console + * @overrides WeakMap, ses, Proxy + * @overrides WeakMapModule + */ -(function(exports) { +/** + * This {@code WeakMap} emulation is observably equivalent to the + * ES-Harmony WeakMap, but with leakier garbage collection properties. + * + *
As with true WeakMaps, in this emulation, a key does not + * retain maps indexed by that key and (crucially) a map does not + * retain the keys it indexes. A map by itself also does not retain + * the values associated with that map. + * + *
However, the values associated with a key in some map are + * retained so long as that key is retained and those associations are + * not overridden. For example, when used to support membranes, all + * values exported from a given membrane will live for the lifetime + * they would have had in the absence of an interposed membrane. Even + * when the membrane is revoked, all objects that would have been + * reachable in the absence of revocation will still be reachable, as + * far as the GC can tell, even though they will no longer be relevant + * to ongoing computation. + * + *
The API implemented here is approximately the API as implemented + * in FF6.0a1 and agreed to by MarkM, Andreas Gal, and Dave Herman, + * rather than the offially approved proposal page. TODO(erights): + * upgrade the ecmascript WeakMap proposal page to explain this API + * change and present to EcmaScript committee for their approval. + * + *
The first difference between the emulation here and that in + * FF6.0a1 is the presence of non enumerable {@code get___, has___, + * set___, and delete___} methods on WeakMap instances to represent + * what would be the hidden internal properties of a primitive + * implementation. Whereas the FF6.0a1 WeakMap.prototype methods + * require their {@code this} to be a genuine WeakMap instance (i.e., + * an object of {@code [[Class]]} "WeakMap}), since there is nothing + * unforgeable about the pseudo-internal method names used here, + * nothing prevents these emulated prototype methods from being + * applied to non-WeakMaps with pseudo-internal methods of the same + * names. + * + *
Another difference is that our emulated {@code + * WeakMap.prototype} is not itself a WeakMap. A problem with the + * current FF6.0a1 API is that WeakMap.prototype is itself a WeakMap + * providing ambient mutability and an ambient communications + * channel. Thus, if a WeakMap is already present and has this + * problem, repairES5.js wraps it in a safe wrappper in order to + * prevent access to this channel. (See + * PATCH_MUTABLE_FROZEN_WEAKMAP_PROTO in repairES5.js). + */ - function isSrcsetImplemented() { - var img = new Image(); - return 'srcset' in img; +/** + * If this is a full secureable ES5 platform and the ES-Harmony {@code WeakMap} is + * absent, install an approximate emulation. + * + *
If WeakMap is present but cannot store some objects, use our approximate + * emulation as a wrapper. + * + *
If this is almost a secureable ES5 platform, then WeakMap.js + * should be run after repairES5.js. + * + *
See {@code WeakMap} for documentation of the garbage collection
+ * properties of this WeakMap emulation.
+ */
+(function WeakMapModule() {
+ "use strict";
+
+ if (typeof ses !== 'undefined' && ses.ok && !ses.ok()) {
+ // already too broken, so give up
+ return;
}
- function main() {
- // If the browser supports @srcset natively, don't do any polyfill.
- if (isSrcsetImplemented()) {
- return;
+ /**
+ * In some cases (current Firefox), we must make a choice betweeen a
+ * WeakMap which is capable of using all varieties of host objects as
+ * keys and one which is capable of safely using proxies as keys. See
+ * comments below about HostWeakMap and DoubleWeakMap for details.
+ *
+ * This function (which is a global, not exposed to guests) marks a
+ * WeakMap as permitted to do what is necessary to index all host
+ * objects, at the cost of making it unsafe for proxies.
+ *
+ * Do not apply this function to anything which is not a genuine
+ * fresh WeakMap.
+ */
+ function weakMapPermitHostObjects(map) {
+ // identity of function used as a secret -- good enough and cheap
+ if (map.permitHostObjects___) {
+ map.permitHostObjects___(weakMapPermitHostObjects);
}
+ }
+ if (typeof ses !== 'undefined') {
+ ses.weakMapPermitHostObjects = weakMapPermitHostObjects;
+ }
- // Get the user agent's capabilities (viewport width, viewport height, dPR).
- var viewportInfo = new ViewportInfo();
- viewportInfo.compute();
- // Go through all images on the page.
- var images = document.querySelectorAll('img');
- // If they have srcset attributes, apply JS to handle that correctly.
- for (var i = 0; i < images.length; i++) {
- var img = images[i];
- // Parse the srcset from the image element.
- var srcset = img.getAttribute('srcset');
- if (srcset) {
- var srcsetInfo = new SrcsetInfo({src: img.src,
- srcset: srcset});
- // Go through all the candidates, pick the best one that matches.
- var imageInfo = viewportInfo.getBestImage(srcsetInfo);
- // TODO: consider using -webkit-image-set instead (if available).
- // Replace the with this image.
- img.src = imageInfo.src;
- // If there's no set size, then we scale the image if necessary
- // (e.g. x != 1)
- if (!(img.width || img.height || img.style.height || img.style.width)) {
- img.style.webkitTransform = 'scale(' + (1/imageInfo.x) + ')';
- img.style.webkitTransformOrigin = '0 0';
- }
+ // IE 11 has no Proxy but has a broken WeakMap such that we need to patch
+ // it using DoubleWeakMap; this flag tells DoubleWeakMap so.
+ var doubleWeakMapCheckSilentFailure = false;
+
+ // Check if there is already a good-enough WeakMap implementation, and if so
+ // exit without replacing it.
+ if (typeof WeakMap === 'function') {
+ var HostWeakMap = WeakMap;
+ // There is a WeakMap -- is it good enough?
+ if (typeof navigator !== 'undefined' &&
+ /Firefox/.test(navigator.userAgent)) {
+ // We're now *assuming not*, because as of this writing (2013-05-06)
+ // Firefox's WeakMaps have a miscellany of objects they won't accept, and
+ // we don't want to make an exhaustive list, and testing for just one
+ // will be a problem if that one is fixed alone (as they did for Event).
+
+ // If there is a platform that we *can* reliably test on, here's how to
+ // do it:
+ // var problematic = ... ;
+ // var testHostMap = new HostWeakMap();
+ // try {
+ // testHostMap.set(problematic, 1); // Firefox 20 will throw here
+ // if (testHostMap.get(problematic) === 1) {
+ // return;
+ // }
+ // } catch (e) {}
+
+ } else {
+ // IE 11 bug: WeakMaps silently fail to store frozen objects.
+ var testMap = new HostWeakMap();
+ var testObject = Object.freeze({});
+ testMap.set(testObject, 1);
+ if (testMap.get(testObject) !== 1) {
+ doubleWeakMapCheckSilentFailure = true;
+ // Fall through to installing our WeakMap.
+ } else {
+ module.exports = WeakMap;
+ return;
+ }
+ }
+ }
+
+ var hop = Object.prototype.hasOwnProperty;
+ var gopn = Object.getOwnPropertyNames;
+ var defProp = Object.defineProperty;
+ var isExtensible = Object.isExtensible;
+
+ /**
+ * Security depends on HIDDEN_NAME being both unguessable and
+ * undiscoverable by untrusted code.
+ *
+ *
Given the known weaknesses of Math.random() on existing + * browsers, it does not generate unguessability we can be confident + * of. + * + *
It is the monkey patching logic in this file that is intended + * to ensure undiscoverability. The basic idea is that there are + * three fundamental means of discovering properties of an object: + * The for/in loop, Object.keys(), and Object.getOwnPropertyNames(), + * as well as some proposed ES6 extensions that appear on our + * whitelist. The first two only discover enumerable properties, and + * we only use HIDDEN_NAME to name a non-enumerable property, so the + * only remaining threat should be getOwnPropertyNames and some + * proposed ES6 extensions that appear on our whitelist. We monkey + * patch them to remove HIDDEN_NAME from the list of properties they + * returns. + * + *
TODO(erights): On a platform with built-in Proxies, proxies + * could be used to trap and thereby discover the HIDDEN_NAME, so we + * need to monkey patch Proxy.create, Proxy.createFunction, etc, in + * order to wrap the provided handler with the real handler which + * filters out all traps using HIDDEN_NAME. + * + *
TODO(erights): Revisit Mike Stay's suggestion that we use an + * encapsulated function at a not-necessarily-secret name, which + * uses the Stiegler shared-state rights amplification pattern to + * reveal the associated value only to the WeakMap in which this key + * is associated with that value. Since only the key retains the + * function, the function can also remember the key without causing + * leakage of the key, so this doesn't violate our general gc + * goals. In addition, because the name need not be a guarded + * secret, we could efficiently handle cross-frame frozen keys. + */ + var HIDDEN_NAME_PREFIX = 'weakmap:'; + var HIDDEN_NAME = HIDDEN_NAME_PREFIX + 'ident:' + Math.random() + '___'; + + if (typeof crypto !== 'undefined' && + typeof crypto.getRandomValues === 'function' && + typeof ArrayBuffer === 'function' && + typeof Uint8Array === 'function') { + var ab = new ArrayBuffer(25); + var u8s = new Uint8Array(ab); + crypto.getRandomValues(u8s); + HIDDEN_NAME = HIDDEN_NAME_PREFIX + 'rand:' + + Array.prototype.map.call(u8s, function(u8) { + return (u8 % 36).toString(36); + }).join('') + '___'; + } + + function isNotHiddenName(name) { + return !( + name.substr(0, HIDDEN_NAME_PREFIX.length) == HIDDEN_NAME_PREFIX && + name.substr(name.length - 3) === '___'); + } + + /** + * Monkey patch getOwnPropertyNames to avoid revealing the + * HIDDEN_NAME. + * + *
The ES5.1 spec requires each name to appear only once, but as + * of this writing, this requirement is controversial for ES6, so we + * made this code robust against this case. If the resulting extra + * search turns out to be expensive, we can probably relax this once + * ES6 is adequately supported on all major browsers, iff no browser + * versions we support at that time have relaxed this constraint + * without providing built-in ES6 WeakMaps. + */ + defProp(Object, 'getOwnPropertyNames', { + value: function fakeGetOwnPropertyNames(obj) { + return gopn(obj).filter(isNotHiddenName); + } + }); + + /** + * getPropertyNames is not in ES5 but it is proposed for ES6 and + * does appear in our whitelist, so we need to clean it too. + */ + if ('getPropertyNames' in Object) { + var originalGetPropertyNames = Object.getPropertyNames; + defProp(Object, 'getPropertyNames', { + value: function fakeGetPropertyNames(obj) { + return originalGetPropertyNames(obj).filter(isNotHiddenName); + } + }); + } + + /** + *
To treat objects as identity-keys with reasonable efficiency + * on ES5 by itself (i.e., without any object-keyed collections), we + * need to add a hidden property to such key objects when we + * can. This raises several issues: + *
The monkey patched versions throw a TypeError if their + * argument is not an object, so it should only be done to functions + * that should throw a TypeError anyway if their argument is not an + * object. + */ + (function(){ + var oldFreeze = Object.freeze; + defProp(Object, 'freeze', { + value: function identifyingFreeze(obj) { + getHiddenRecord(obj); + return oldFreeze(obj); + } + }); + var oldSeal = Object.seal; + defProp(Object, 'seal', { + value: function identifyingSeal(obj) { + getHiddenRecord(obj); + return oldSeal(obj); } + }); + var oldPreventExtensions = Object.preventExtensions; + defProp(Object, 'preventExtensions', { + value: function identifyingPreventExtensions(obj) { + getHiddenRecord(obj); + return oldPreventExtensions(obj); + } + }); + })(); + + function constFunc(func) { + func.prototype = null; + return Object.freeze(func); + } + + var calledAsFunctionWarningDone = false; + function calledAsFunctionWarning() { + // Future ES6 WeakMap is currently (2013-09-10) expected to reject WeakMap() + // but we used to permit it and do it ourselves, so warn only. + if (!calledAsFunctionWarningDone && typeof console !== 'undefined') { + calledAsFunctionWarningDone = true; + console.warn('WeakMap should be invoked as new WeakMap(), not ' + + 'WeakMap(). This will be an error in the future.'); } } - // Small cross browser document ready. - var readyTimer = setInterval(function () { - if (document.readyState === "complete") { - main(); - clearInterval(readyTimer); + var nextId = 0; + + var OurWeakMap = function() { + if (!(this instanceof OurWeakMap)) { // approximate test for new ...() + calledAsFunctionWarning(); } - }, 10); -})(window); + // We are currently (12/25/2012) never encountering any prematurely + // non-extensible keys. + var keys = []; // brute force for prematurely non-extensible keys. + var values = []; // brute force for corresponding values. + var id = nextId++; + + function get___(key, opt_default) { + var index; + var hiddenRecord = getHiddenRecord(key); + if (hiddenRecord) { + return id in hiddenRecord ? hiddenRecord[id] : opt_default; + } else { + index = keys.indexOf(key); + return index >= 0 ? values[index] : opt_default; + } + } + + function has___(key) { + var hiddenRecord = getHiddenRecord(key); + if (hiddenRecord) { + return id in hiddenRecord; + } else { + return keys.indexOf(key) >= 0; + } + } + + function set___(key, value) { + var index; + var hiddenRecord = getHiddenRecord(key); + if (hiddenRecord) { + hiddenRecord[id] = value; + } else { + index = keys.indexOf(key); + if (index >= 0) { + values[index] = value; + } else { + // Since some browsers preemptively terminate slow turns but + // then continue computing with presumably corrupted heap + // state, we here defensively get keys.length first and then + // use it to update both the values and keys arrays, keeping + // them in sync. + index = keys.length; + values[index] = value; + // If we crash here, values will be one longer than keys. + keys[index] = key; + } + } + return this; + } + + function delete___(key) { + var hiddenRecord = getHiddenRecord(key); + var index, lastIndex; + if (hiddenRecord) { + return id in hiddenRecord && delete hiddenRecord[id]; + } else { + index = keys.indexOf(key); + if (index < 0) { + return false; + } + // Since some browsers preemptively terminate slow turns but + // then continue computing with potentially corrupted heap + // state, we here defensively get keys.length first and then use + // it to update both the keys and the values array, keeping + // them in sync. We update the two with an order of assignments, + // such that any prefix of these assignments will preserve the + // key/value correspondence, either before or after the delete. + // Note that this needs to work correctly when index === lastIndex. + lastIndex = keys.length - 1; + keys[index] = void 0; + // If we crash here, there's a void 0 in the keys array, but + // no operation will cause a "keys.indexOf(void 0)", since + // getHiddenRecord(void 0) will always throw an error first. + values[index] = values[lastIndex]; + // If we crash here, values[index] cannot be found here, + // because keys[index] is void 0. + keys[index] = keys[lastIndex]; + // If index === lastIndex and we crash here, then keys[index] + // is still void 0, since the aliasing killed the previous key. + keys.length = lastIndex; + // If we crash here, keys will be one shorter than values. + values.length = lastIndex; + return true; + } + } + + return Object.create(OurWeakMap.prototype, { + get___: { value: constFunc(get___) }, + has___: { value: constFunc(has___) }, + set___: { value: constFunc(set___) }, + delete___: { value: constFunc(delete___) } + }); + }; + + OurWeakMap.prototype = Object.create(Object.prototype, { + get: { + /** + * Return the value most recently associated with key, or + * opt_default if none. + */ + value: function get(key, opt_default) { + return this.get___(key, opt_default); + }, + writable: true, + configurable: true + }, + + has: { + /** + * Is there a value associated with key in this WeakMap? + */ + value: function has(key) { + return this.has___(key); + }, + writable: true, + configurable: true + }, + + set: { + /** + * Associate value with key in this WeakMap, overwriting any + * previous association if present. + */ + value: function set(key, value) { + return this.set___(key, value); + }, + writable: true, + configurable: true + }, + + 'delete': { + /** + * Remove any association for key in this WeakMap, returning + * whether there was one. + * + *
Note that the boolean return here does not work like the
+ * {@code delete} operator. The {@code delete} operator returns
+ * whether the deletion succeeds at bringing about a state in
+ * which the deleted property is absent. The {@code delete}
+ * operator therefore returns true if the property was already
+ * absent, whereas this {@code delete} method returns false if
+ * the association was already absent.
+ */
+ value: function remove(key) {
+ return this.delete___(key);
+ },
+ writable: true,
+ configurable: true
+ }
+ });
+
+ if (typeof HostWeakMap === 'function') {
+ (function() {
+ // If we got here, then the platform has a WeakMap but we are concerned
+ // that it may refuse to store some key types. Therefore, make a map
+ // implementation which makes use of both as possible.
+
+ // In this mode we are always using double maps, so we are not proxy-safe.
+ // This combination does not occur in any known browser, but we had best
+ // be safe.
+ if (doubleWeakMapCheckSilentFailure && typeof Proxy !== 'undefined') {
+ Proxy = undefined;
+ }
+
+ function DoubleWeakMap() {
+ if (!(this instanceof OurWeakMap)) { // approximate test for new ...()
+ calledAsFunctionWarning();
+ }
+
+ // Preferable, truly weak map.
+ var hmap = new HostWeakMap();
+
+ // Our hidden-property-based pseudo-weak-map. Lazily initialized in the
+ // 'set' implementation; thus we can avoid performing extra lookups if
+ // we know all entries actually stored are entered in 'hmap'.
+ var omap = undefined;
+
+ // Hidden-property maps are not compatible with proxies because proxies
+ // can observe the hidden name and either accidentally expose it or fail
+ // to allow the hidden property to be set. Therefore, we do not allow
+ // arbitrary WeakMaps to switch to using hidden properties, but only
+ // those which need the ability, and unprivileged code is not allowed
+ // to set the flag.
+ //
+ // (Except in doubleWeakMapCheckSilentFailure mode in which case we
+ // disable proxies.)
+ var enableSwitching = false;
+
+ function dget(key, opt_default) {
+ if (omap) {
+ return hmap.has(key) ? hmap.get(key)
+ : omap.get___(key, opt_default);
+ } else {
+ return hmap.get(key, opt_default);
+ }
+ }
+
+ function dhas(key) {
+ return hmap.has(key) || (omap ? omap.has___(key) : false);
+ }
+
+ var dset;
+ if (doubleWeakMapCheckSilentFailure) {
+ dset = function(key, value) {
+ hmap.set(key, value);
+ if (!hmap.has(key)) {
+ if (!omap) { omap = new OurWeakMap(); }
+ omap.set(key, value);
+ }
+ return this;
+ };
+ } else {
+ dset = function(key, value) {
+ if (enableSwitching) {
+ try {
+ hmap.set(key, value);
+ } catch (e) {
+ if (!omap) { omap = new OurWeakMap(); }
+ omap.set___(key, value);
+ }
+ } else {
+ hmap.set(key, value);
+ }
+ return this;
+ };
+ }
+
+ function ddelete(key) {
+ var result = !!hmap['delete'](key);
+ if (omap) { return omap.delete___(key) || result; }
+ return result;
+ }
+
+ return Object.create(OurWeakMap.prototype, {
+ get___: { value: constFunc(dget) },
+ has___: { value: constFunc(dhas) },
+ set___: { value: constFunc(dset) },
+ delete___: { value: constFunc(ddelete) },
+ permitHostObjects___: { value: constFunc(function(token) {
+ if (token === weakMapPermitHostObjects) {
+ enableSwitching = true;
+ } else {
+ throw new Error('bogus call to permitHostObjects___');
+ }
+ })}
+ });
+ }
+ DoubleWeakMap.prototype = OurWeakMap.prototype;
+ module.exports = DoubleWeakMap;
+
+ // define .constructor to hide OurWeakMap ctor
+ Object.defineProperty(WeakMap.prototype, 'constructor', {
+ value: WeakMap,
+ enumerable: false, // as default .constructor is
+ configurable: true,
+ writable: true
+ });
+ })();
+ } else {
+ // There is no host WeakMap, so we must use the emulation.
+
+ // Emulated WeakMaps are incompatible with native proxies (because proxies
+ // can observe the hidden name), so we must disable Proxy usage (in
+ // ArrayLike and Domado, currently).
+ if (typeof Proxy !== 'undefined') {
+ Proxy = undefined;
+ }
+
+ module.exports = OurWeakMap;
+ }
+})();
+
+},{}]},{},[2]);
diff --git a/build/srcset.min.js b/build/srcset.min.js
index 010fe61..bdbc1fe 100644
--- a/build/srcset.min.js
+++ b/build/srcset.min.js
@@ -1 +1 @@
-var Query=function(t){"use strict";var e=function(t){var e,r,n,i,s=[];if(t===void 0||null===t||""===t)return s;for(0===t.indexOf("?")&&(t=t.substring(1)),r=(""+t).split(/[&;]/),e=0;r.length>e;e++)n=r[e],i=n.split("="),s.push([i[0],i[1]]);return s},r=e(t),n=function(){var t,e,n="";for(t=0;r.length>t;t++)e=r[t],n.length>0&&(n+="&"),n+=e.join("=");return n.length>0?"?"+n:n},i=function(t){return t=decodeURIComponent(t),t=t.replace("+"," ")},s=function(t){var e,n;for(n=0;r.length>n;n++)if(e=r[n],i(t)===i(e[0]))return e[1]},o=function(t){var e,n,s=[];for(e=0;r.length>e;e++)n=r[e],i(t)===i(n[0])&&s.push(n[1]);return s},a=function(t,e){var n,s,o,a,u=[];for(n=0;r.length>n;n++)s=r[n],o=i(s[0])===i(t),a=i(s[1])===i(e),(1===arguments.length&&!o||2===arguments.length&&!o&&!a)&&u.push(s);return r=u,this},u=function(t,e,n){return 3===arguments.length&&-1!==n?(n=Math.min(n,r.length),r.splice(n,0,[t,e])):arguments.length>0&&r.push([t,e]),this},h=function(t,e,n){var s,o,h=-1;if(3===arguments.length){for(s=0;r.length>s;s++)if(o=r[s],i(o[0])===i(t)&&decodeURIComponent(o[1])===i(n)){h=s;break}a(t,n).addParam(t,e,h)}else{for(s=0;r.length>s;s++)if(o=r[s],i(o[0])===i(t)){h=s;break}a(t),u(t,e,h)}return this};return{getParamValue:s,getParamValues:o,deleteParam:a,addParam:u,replaceParam:h,toString:n}},Uri=function(t){"use strict";var e=!1,r=function(t){for(var r={strict:/^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,loose:/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/},n=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],i={name:"queryKey",parser:/(?:^|&)([^&=]*)=?([^&]*)/g},s=r[e?"strict":"loose"].exec(t),o={},a=14;a--;)o[n[a]]=s[a]||"";return o[i.name]={},o[n[12]].replace(i.parser,function(t,e,r){e&&(o[i.name][e]=r)}),o},n=r(t||""),i=new Query(n.query),s=function(t){return t!==void 0&&(n.protocol=t),n.protocol},o=null,a=function(t){return t!==void 0&&(o=t),null===o?-1!==n.source.indexOf("//"):o},u=function(t){return t!==void 0&&(n.userInfo=t),n.userInfo},h=function(t){return t!==void 0&&(n.host=t),n.host},c=function(t){return t!==void 0&&(n.port=t),n.port},f=function(t){return t!==void 0&&(n.path=t),n.path},l=function(t){return t!==void 0&&(i=new Query(t)),i},d=function(t){return t!==void 0&&(n.anchor=t),n.anchor},g=function(t){return s(t),this},p=function(t){return a(t),this},m=function(t){return u(t),this},v=function(t){return h(t),this},w=function(t){return c(t),this},x=function(t){return f(t),this},y=function(t){return l(t),this},I=function(t){return d(t),this},C=function(t){return l().getParamValue(t)},P=function(t){return l().getParamValues(t)},_=function(t,e){return 2===arguments.length?l().deleteParam(t,e):l().deleteParam(t),this},V=function(t,e,r){return 3===arguments.length?l().addParam(t,e,r):l().addParam(t,e),this},B=function(t,e,r){return 3===arguments.length?l().replaceParam(t,e,r):l().replaceParam(t,e),this},O=function(){var t="",e=function(t){return null!==t&&""!==t};return e(s())?(t+=s(),s().indexOf(":")!==s().length-1&&(t+=":"),t+="//"):a()&&e(h())&&(t+="//"),e(u())&&e(h())&&(t+=u(),u().indexOf("@")!==u().length-1&&(t+="@")),e(h())&&(t+=h(),e(c())&&(t+=":"+c())),e(f())?t+=f():e(h())&&(e(""+l())||e(d()))&&(t+="/"),e(""+l())&&(0!==(""+l()).indexOf("?")&&(t+="?"),t+=""+l()),e(d())&&(0!==d().indexOf("#")&&(t+="#"),t+=d()),t},Q=function(){return new Uri(O())};return{protocol:s,hasAuthorityPrefix:a,userInfo:u,host:h,port:c,path:f,query:l,anchor:d,setProtocol:g,setHasAuthorityPrefix:p,setUserInfo:m,setHost:v,setPort:w,setPath:x,setQuery:y,setAnchor:I,getQueryParamValue:C,getQueryParamValues:P,deleteQueryParam:_,addQueryParam:V,replaceQueryParam:B,toString:O,clone:Q}},jsUri=Uri;(function(t){function e(t){this.imageCandidates=[],this.srcValue=t.src,this.srcsetValue=t.srcset,this.isValid=!0,this.error="",this._parse(this.srcsetValue),this.isValid||console.error("Error: "+this.error)}function r(t){this.src=t.src,this.w=t.w||1/0,this.h=t.h||1/0,this.x=t.x||1}var n=/^[0-9]+$/;e.prototype._parse=function(){for(var t,e,n=this.srcsetValue,i=0,s=[];""!==n;){for(;" "===n.charAt(0);)n=n.slice(1);if(i=n.indexOf(" "),-1!==i){if(t=n.slice(0,i),""===t)break;n=n.slice(i+1),i=n.indexOf(","),-1===i?(e=n,n=""):(e=n.slice(0,i),n=n.slice(i+1)),s.push({url:t,descriptors:e})}else s.push({url:n,descriptors:""}),n=""}for(var o=0,a=s.length;a>o;o++){var u=s[o],h=this._parseDescriptors(u.descriptors);this._addCandidate(new r({src:u.url,x:h.x,w:h.w,h:h.h}))}this.srcValue&&this._addCandidate(new r({src:this.srcValue}))},e.prototype._addCandidate=function(t){for(var e=0;this.imageCandidates.length>e;e++){var r=this.imageCandidates[e];if(r.x==t.x&&r.w==t.w&&r.h==t.h)return}this.imageCandidates.push(t)},e.prototype._parseDescriptors=function(t){for(var e=t.split(/\s/),r={},i=0;e.length>i;i++){var s=e[i];if(s.length>0){var o=s[s.length-1],a=s.substring(0,s.length-1),u=parseInt(a,10),h=parseFloat(a);a.match(n)&&"w"===o?r[o]=u:a.match(n)&&"h"==o?r[o]=u:isNaN(h)||"x"!=o?(this.error='Invalid srcset descriptor found in "'+s+'".',this.isValid=!1):r[o]=h}}return r},t.SrcsetInfo=e})(window),function(t){function e(){this.w=null,this.h=null,this.x=null}e.prototype.compute=function(){this.w=window.innerWidth||document.documentElement.clientWidth,this.h=window.innerHeight||document.documentElement.clientHeight,this.x=window.devicePixelRatio},e.prototype.setForTesting=function(t){this.w=t.w,this.h=t.h,this.x=t.x},e.prototype.getBestImage=function(t){var e=t.imageCandidates.slice(0),r=this._getBestCandidateIf(e,function(t,e){return t.w>e.w});this._removeCandidatesIf(e,function(t){return function(e){return e.w
+ 
with this image.
- img.src = imageInfo.src;
- // If there's no set size, then we scale the image if necessary
- // (e.g. x != 1)
- if (!(img.width || img.height || img.style.height || img.style.width)) {
- img.style.webkitTransform = 'scale(' + (1/imageInfo.x) + ')';
- img.style.webkitTransformOrigin = '0 0';
- }
- }
- }
- }
-
- // Small cross browser document ready.
- var readyTimer = setInterval(function () {
- if (document.readyState === "complete") {
- main();
- clearInterval(readyTimer);
- }
- }, 10);
-
-})(window);
diff --git a/js/srcset-info.js b/js/srcset-info.js
index 3c40e15..4fb585d 100644
--- a/js/srcset-info.js
+++ b/js/srcset-info.js
@@ -1,4 +1,4 @@
-(function(exports) {
+(function() {
var INT_REGEXP = /^[0-9]+$/;
function SrcsetInfo(options) {
@@ -145,6 +145,11 @@
this.x = options.x || 1;
}
- exports.SrcsetInfo = SrcsetInfo;
-})(window);
+ // Exports
+ this.SrcsetInfo = SrcsetInfo;
+ if (typeof module !== "undefined" && module !== null) {
+ module.exports = this.SrcsetInfo;
+ }
+
+})(this);
diff --git a/js/srcset.js b/js/srcset.js
new file mode 100644
index 0000000..c64aeb7
--- /dev/null
+++ b/js/srcset.js
@@ -0,0 +1,194 @@
+(function () {
+ //
+ // CustomEvent polyfill (https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill)
+ //
+
+ if ( typeof window.CustomEvent === "function" ) return false;
+
+ function CustomEvent ( event, params ) {
+ params = params || { bubbles: false, cancelable: false, detail: undefined };
+ var evt = document.createEvent( 'CustomEvent' );
+ evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
+ return evt;
+ }
+
+ CustomEvent.prototype = window.Event.prototype;
+
+ window.CustomEvent = CustomEvent;
+})();
+
+(function () {
+ var ViewportInfo = this.ViewportInfo || require('./viewport-info');
+ var SrcsetInfo = this.SrcsetInfo || require('./srcset-info');
+ var WeakMap = require('weak-map');
+
+ var srcset = {};
+
+ var viewportInfo = new ViewportInfo();
+ srcset.viewportInfo = viewportInfo;
+ viewportInfo.compute();
+
+ var windowResizedAt = (new Date).getTime();
+ srcset.windowResizedAt = windowResizedAt;
+
+ // Picked from underscore.js
+ function debounce(func, wait, immediate) {
+ var timeout, args, context, timestamp, result;
+
+ var later = function() {
+ var last = new Date().getTime() - timestamp;
+
+ if (last < wait && last >= 0) {
+ timeout = setTimeout(later, wait - last);
+ } else {
+ timeout = null;
+ if (!immediate) {
+ result = func.apply(context, args);
+ if (!timeout) context = args = null;
+ }
+ }
+ };
+
+ return function() {
+ context = this;
+ args = arguments;
+ timestamp = new Date().getTime();
+ var callNow = immediate && !timeout;
+ if (!timeout) timeout = setTimeout(later, wait);
+ if (callNow) {
+ result = func.apply(context, args);
+ context = args = null;
+ }
+
+ return result;
+ };
+ }
+
+ // https://gist.github.com/stucox/5231211
+ var hasMO = (function () {
+ var prefixes = ['WebKit', 'Moz', 'O', 'Ms', ''];
+
+ for (var i=0; i < prefixes.length; i++) {
+ if ((prefixes[i] + 'MutationObserver') in window) {
+ return window[prefixes[i] + 'MutationObserver'];
+ }
+ }
+
+ return false;
+ }());
+
+ function SrcsetView(el) {
+ this.el = el;
+
+ this.srcsetInfo = new SrcsetInfo({
+ src: this.el.src,
+ srcset: this.el.dataset.srcset
+ });
+
+ //
+ // Observe data-srcset attributes mutations to keep this.srcsetInfo up-to-date (if available)
+ //
+
+ if (hasMO) {
+ this.mo = new MutationObserver(function (mutations) {
+ mutations.forEach(function (mutation) {
+ //console.log(mutation);
+
+ if (mutation.target === this.el && mutation.type === 'attributes') {
+ if (mutation.attributeName === 'src' || mutation.attributeName === 'data-srcset') {
+ this.update();
+ }
+ }
+ }.bind(this));
+ }.bind(this));
+
+ this.mo.observe(this.el, {attributes: true});
+ }
+ }
+ SrcsetView.prototype.update = function (options) {
+ options || (options = {});
+ /*options = $.extend({}, options, {
+ force: false
+ });*/
+
+ if (this.srcsetInfo.srcValue !== this.el.src) {
+ this.srcsetInfo.srcValue = this.el.src;
+ }
+
+ var srcsetchanged;
+ if (this.srcsetInfo.srcsetValue !== this.el.dataset.srcset) {
+ srcsetchanged = true;
+
+ this.srcsetInfo.imageCandidates = []; // reset imageCandidates
+ this.srcsetInfo.srcsetValue = this.el.dataset.srcset;
+ this.srcsetInfo._parse(this.srcsetInfo.srcsetValue);
+ if (!this.srcsetInfo.isValid) {
+ console.error('Error: ' + this.srcsetInfo.error);
+ }
+ }
+
+ var needUpdate = (!this.srcupdatedat || this.srcupdatedat < windowResizedAt);
+ if (!this.el.src || needUpdate || srcsetchanged || options.force) {
+
+ if (this.srcsetInfo) {
+ var bestImageInfo = viewportInfo.getBestImage(this.srcsetInfo);
+
+ var oldsrc = this.el.src;
+ var newsrc = bestImageInfo.src;
+
+ if (newsrc === oldsrc) return false; // same, no need to update
+
+ //console.log('updating src', this.el, oldsrc, newsrc);
+
+ //
+ // 'srcchanged' event
+ //
+
+ var srcchanged = new CustomEvent('srcchanged', {
+ detail: {
+ previous: oldsrc,
+ actual: newsrc
+ },
+ bubbles: true
+ });
+
+ this.el.src = newsrc;
+ // Dispatch 'srcchanged'
+ setTimeout(function () {
+ this.el.dispatchEvent(srcchanged);
+ }.bind(this), 0);
+ }
+
+ // Remember when updated to compare with window's resizeAt timestamp
+ this.srcupdatedat = (new Date).getTime();
+ }
+ };
+
+ var srcsetViews = new WeakMap();
+ srcset.imgs = srcsetViews;
+ function update() {
+ // update timestamp
+ windowResizedAt = (new Date).getTime();
+ viewportInfo.compute();
+
+ // Update every images
+ [].forEach.call(document.querySelectorAll('img[data-srcset]'), function (el) {
+ var srcsetview = srcsetViews.get(el);
+ if (!srcsetview) {
+ srcsetview = new SrcsetView(el);
+ srcsetViews.set(el, srcsetview);
+ }
+
+ srcsetview.update();
+ });
+ }
+ window.onresize = debounce(update, 200);
+ update();
+ srcset.update = update;
+
+ // Exports
+ this.srcset = srcset;
+ if (typeof module !== "undefined" && module !== null) {
+ module.exports = this.srcset;
+ }
+})(this);
diff --git a/js/viewport-info.js b/js/viewport-info.js
index 15fe42b..0afd071 100644
--- a/js/viewport-info.js
+++ b/js/viewport-info.js
@@ -1,4 +1,5 @@
-(function(exports) {
+(function() {
+ var SrcsetInfo = this.SrcsetInfo || require('./srcset-info');
function ViewportInfo() {
this.w = null;
@@ -129,6 +130,10 @@
return bestMatch;
};
- exports.ViewportInfo = ViewportInfo;
+ // Exports
+ this.ViewportInfo = ViewportInfo;
+ if (typeof module !== "undefined" && module !== null) {
+ module.exports = this.ViewportInfo;
+ }
-})(window);
+})(this);
diff --git a/package.json b/package.json
index d29cf53..e331cae 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,12 @@
{
"name": "srcset-polyfill",
"version": "0.1.0",
+ "main": "js/srcset.js",
"devDependencies": {
- "grunt": "~0.4.0",
- "grunt-contrib-jshint": "~0.1.1",
- "grunt-contrib-nodeunit": "~0.1.2",
- "grunt-contrib-concat": "~0.1.3",
- "grunt-contrib-uglify": "~0.1.2"
+ "browserify": "^11.0.1",
+ "uglify-js": "^2.4.24"
+ },
+ "dependencies": {
+ "weak-map": "^1.0.2"
}
}