├── .gitignore ├── LICENSE ├── README.md ├── dist ├── jsCache.js ├── jsCache.min.js ├── localStorage.min.js └── localStorage.swf └── src └── jsCache.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.*~ 3 | preview_github_readme* 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Morten Houmøller Nygaard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jsCache 2 | ======= 3 | 4 | jsCache is a javascript library that enables caching of javascripts, 5 | css-stylesheets and images using one of my earlier projects [localStorage 6 | Polyfill](https://github.com/mortzdk/localStorage) as the persistent caching 7 | unit. To obtain the content of the javascript, css, or image files, CORS AJAX 8 | and a canvas trick for images is used. This enables caching of both locally and 9 | cross-origin files, if the right HTTP header `Access-Control-Allow-Origin: *` 10 | is set at the cross-origin. If CORS is not available or something goes wrong 11 | with the caching, the library will automatically fallback to do a normal HTTP 12 | request for the files. 13 | 14 | jsCache is especially useful when serving your website for mobile phones as it 15 | will [speed up the loading of your website](http://www.stevesouders.com/blog/2011/03/28/storager-case-study-bing-google/) 16 | and hearby increase the user experience, by providing a better way to cache the 17 | files than the limited HTTP caching available on most mobile phones. jsCache 18 | also speeds up your site in an ordinary webbrowser as it saves HTTP requests 19 | and loads all the different files asynchronously if possible. The asynchronous 20 | loading make events such as when the DOM is ready and onload fire earlier and 21 | hearby make faster rendering of the page. 22 | 23 | Eventhough all files are attempted to be loaded asynchronously, you still 24 | have the possibility to load the files in some specific order. This is done 25 | using the `then` method, which will wait for any earlier loads to terminate 26 | before executing its callback function. Hearby you can create a hierarchy of 27 | scripts, styles and images and load them as you wish. This is really useful 28 | when you want to load libraries with dependencies, as you can ensure that one 29 | library actually is loaded before using it in another. 30 | 31 | #Cross-browser compability 32 | 33 | Because of the use of the localStorage polyfill, this library has compability 34 | for persistent storage in almost all browsers both new and old. More about this 35 | can be seen at in the [README](https://github.com/mortzdk/localStorage) of the 36 | localStorage polyfill. 37 | 38 | When it comes to the support of CORS, the first choice of implementation is the 39 | XMLHttpRequest version 2, which now adays is supported by every major browser. 40 | Moreover to support older versions of Internet Explorer, the XDomainRequest is 41 | also used. If none of these are available, the old version of XMLHttpRequest or 42 | the ActiveX versions of AJAX is used. These do not support CORS, so in case of 43 | such use, only the local files will be cached, while CORS files will be loaded 44 | as a normal asynchronous HTTP request. 45 | 46 |  Chrome has support of CORS 47 | from version 3 and support of XMLHttpRequest from version 1. 48 | 49 |  Firefox has support of CORS 50 | from version 3.5 and support of XMLHttpRequest from version 1. 51 | 52 |  Internet Explorer has support 53 | of CORS from version 10 using the XMLHttpRequest version 2 and furthermore 54 | support CORS from version 8 using XDomainRequest. In addition to this there is 55 | support for AJAX using either XMLHttpRequest or ActiveX from version 5. 56 | 57 |  Safari has support of CORS from 58 | version 4 and support XMLHttpRequest from version 1.2. 59 | 60 |  Opera has support of CORS from version 61 | 12 and support XMLHttpRequest from version 7.6. 62 | 63 | #Example Usage 64 | 65 | In this example, we use the `domReady` method to ensure that the DOM is ready 66 | to be manipulated before doing anything. Next we `detect` if the browser is IE 67 | 8 or lower. If so the callback function will be called, i.e. the function 68 | holding an alert is invoked. Next we `load` a lot of files. By having the files 69 | in the same load method, we state that the order of which the files are loaded 70 | are not important and accept that the files are loaded asynchronous. Finally 71 | the `then` method is used to ensure that the earlier loaded files are finished 72 | loading before executing the callback function. This way we ensure that jQuery 73 | is loaded before we use the library to get the firstChild of the element having 74 | the id "logo". In the callback function we load an image, append the attributes 75 | to the image and append the image before the firstChild of the element having 76 | the "logo" id. 77 | 78 | ```html 79 | 107 | ``` 108 | 109 | #jsCache Object Explanation 110 | 111 | The jsCache object is defined as follows: 112 | 113 |
114 | jsCache { 115 | unsigned long expires; 116 | unsigned long modified; 117 | unsigned long timeout; 118 | Object get(in DOMString key); 119 | jsCache load(in Object obj1, in Object obj 2, ...); 120 | jsCache then(in Function callback); 121 | jsCache remove(in DOMString key); 122 | jsCache clear(); 123 | jsCache detect(in DOMString condition, in Function callback); 124 | void domReady(in Function callback); 125 | }; 126 |127 | 128 | ### expires 129 | 130 | The `expires` variable determines when the cached files is to expire. The 131 | default value is 5 day i.e. `5*24*60*60*1000` milliseconds. If you wish to 132 | change this, you simply assign a new value to the variable, with a long value 133 | determining the time in milliseconds. 134 | 135 | ### modified 136 | 137 | The `modified` variable determines when any of the files was last modified. 138 | This can be helpful if you have modified some of your local files and wishes to 139 | serve these files instead of an old cached version of the file. Default this 140 | variable is zero, but this can be changed the same way as the expires variable. 141 | 142 | ### timeout 143 | 144 | The `timeout` variable determines when the AJAX call should timeout i.e. when 145 | you have waited too long for loading a file and should instead try to do it the 146 | normal way. This value is default 5 seconds i.e. `5000` milliseconds, but can 147 | be changed the same way as the expires variable. 148 | 149 | ### get(in DOMString key) 150 | 151 | The `get` method is used to get the cached data. This method should always be 152 | used instead of the get method of the localStorage object, as this method 153 | appends a string to the key. The method return a file Object containing 154 | information such as url, type, data, attributes etc. 155 | 156 | ### load(in Object obj1, in Object obj 2, ...) 157 | 158 | The `load` method is used to load files. The method takes an infite amount of 159 | special objects containing information about the files to be loaded. Such an 160 | object have the following structure: 161 | 162 |
163 | Object { 164 | DOMString url; 165 | DOMString type; 166 | boolean cache; 167 | DOMString detect; 168 | Object append; 169 | Object attr; 170 | DOMString format; 171 | } 172 |173 | 174 | The object has a lot of different variables available, but the only required 175 | variable is the `url` variable, which should hold the address of the file to be 176 | loaded. 177 | 178 | The `type` variable is optional. This variable can be used to statically 179 | determine the type of a file. When loading files, jsCache will dynamically 180 | try to determine the type of the file, but if this variable is set, the file 181 | will always be handled as the set value. 182 | 183 | The `cache` variable is optional. This variable should hold a boolean that 184 | determines whether to cache the file or not. If this variable is not set or is 185 | set to true, jsCache will cache the file. If the variable is set to false, no 186 | caching will be performed on the file. 187 | 188 | The `detect` variable is optional. This variable works the same way as the 189 | `detect` method of the jsCache object i.e. this variable should hold a HTML 190 | conditional comments value. If the condition is true, the file will be loaded. 191 | 192 | The `append` variable is optional. This variable should hold an object that you 193 | wish to insert the files before. 194 | 195 | The `attr` variable is optional. This variable holds an object of attributes. 196 | These attributes are appended to the file loaded. For example if you wish to 197 | give an image file a title or an alt attribute, you have to specify these in 198 | this object. See the example usage for more information. 199 | 200 | The `format` variable is optional. This is a special variable that currently 201 | only should be used when using a image wrapper to get the base64 value of the 202 | image. If such a wrapper is used, the format variable should be assigned the 203 | "base64". In PHP such a wrapper would look something like this: 204 | 205 | ```php 206 | 210 | ``` 211 | 212 | ### then(in Function callback); 213 | 214 | The `then` method is used to guarantee that files that has previously been 215 | served to the `load` method, is fully loaded before the callback function of 216 | this method is invoked. This is extremely helpful you are dependent on 217 | libraries or other files. 218 | 219 | ### remove(in DOMString key); 220 | 221 | The `remove` method is used to remove a specific file from the cache. This 222 | method should always be used instead of the get method of the localStorage 223 | object, as this method appends a string to the key. 224 | 225 | ### clear(); 226 | 227 | The `clear` method is used to remove all cached files from the cache. 228 | 229 | ### detect(in DOMString condition, in Function callback); 230 | 231 | The `detect` method is another extremely useful method when having to detect if 232 | the browser is Internet Explorer. Most of the time all other browsers than IE 233 | is rendering your website more or less consistently, but as always IE is a pain 234 | that needs special fixes to render your site properly. This method behave in 235 | the same way as the HTML conditional comments and the method should be handled 236 | a string such as `if IE` and a callback function to be called if the condition 237 | is true. 238 | 239 | ### domReady(in Function callback); 240 | 241 | The `domReady` method is used to wait for the DOM to be ready for manipulation. 242 | This is useful when having to manipulate with the DOM as any manipulation 243 | before this state is not ensured to be successful. It is recommended to always 244 | use this method. 245 | -------------------------------------------------------------------------------- /dist/jsCache.js: -------------------------------------------------------------------------------- 1 | /*jslint devel: true, white: true, browser: true*/ 2 | /*global XDomainRequest: true, ActiveXObject: true, FileReader: true, 3 | Blob: true*/ 4 | (function (w, d, string, object, func, text, css, script, style, javascript, 5 | img, application, stylesheet, link, cache, t, DOMContentLoaded, 6 | readystatechange, loaded, load, complete, get, contenttype, async, js, ifie, 7 | ifgt, iflt, on, msxml2, xmlhttp, error, canvas, twod, src, png, jpg, gif, 8 | svg, image, base64, ttrue, boolt, boolf, nil) { 9 | "use strict"; 10 | 11 | /** 12 | * Function that returns object, which can be used to determine the 13 | * filetype or ie version. 14 | */ 15 | function Dictionary() { 16 | var dict = {}, ie = (function () { 17 | var 18 | v = 3, 19 | div = d.createElement("div"), 20 | all = div.getElementsByTagName("i"); 21 | 22 | do { 23 | v += 1; 24 | div.innerHTML = ""; 26 | } while (all[0]); 27 | 28 | return v > 4 ? v : 0; 29 | }()); 30 | 31 | /** 32 | * Accepted javascript synonyms 33 | */ 34 | dict[application + "/" + javascript] = dict[text + "/" + javascript] = 35 | dict[application + "/x-" + javascript] = dict["." + js] = dict[js] = 36 | dict[javascript] = dict[script] = js; 37 | 38 | /** 39 | * Accepted css synonyms 40 | */ 41 | dict[text + "/" + css] = dict[css] = dict["." + css] = 42 | dict[stylesheet] = dict[style] = css; 43 | 44 | /** 45 | * Accepted pictures 46 | */ 47 | dict[png] = dict["." + png] = dict[image + "/" + png] = dict[jpg] = 48 | dict["." + jpg] = dict[image + "/" + jpg] = dict[image + "/" + gif] = 49 | dict[gif] = dict["." + gif] = dict[image + "/" +svg] = 50 | dict["." + svg] = dict[svg] = img; 51 | 52 | /** 53 | * IE versioning check 54 | */ 55 | dict[ifie] = (ie > 4); 56 | dict[iflt + " 9"] = (ie && ie < 9); 57 | dict[iflt + " 8"] = (ie && ie < 8); 58 | dict[iflt + " 7"] = (ie && ie < 7); 59 | dict[iflt + " 6"] = (ie && ie < 6); 60 | dict[ifgt + " 6"] = (ie > 6); 61 | dict[ifgt + " 7"] = (ie > 7); 62 | dict[ifgt + " 8"] = (ie > 8); 63 | dict[ifgt + " 9"] = (ie > 9); 64 | dict[ifie + " 9"] = (ie === 9); 65 | dict[ifie + " 8"] = (ie === 8); 66 | dict[ifie + " 7"] = (ie === 7); 67 | dict[ifie + " 6"] = (ie === 6); 68 | dict[ifie + " 5"] = (ie === 5); 69 | 70 | return dict; 71 | } 72 | 73 | /** 74 | * Queue.js 75 | * 76 | * A function to represent a queue 77 | * 78 | * Created by Stephen Morley - http://code.stephenmorley.org/ - and 79 | * released under the terms of the CC0 1.0 Universal legal code: 80 | * 81 | * http://creativecommons.org/publicdomain/zero/1.0/legalcode 82 | * 83 | */ 84 | function Queue(){ 85 | var 86 | a=[], 87 | b=0; 88 | 89 | this.getLength = function () { 90 | return(a.length-b); 91 | }; 92 | 93 | this.isEmpty = function () { 94 | return(a.length===0); 95 | }; 96 | 97 | this.enqueue = function (c) { 98 | a.push(c); 99 | }; 100 | 101 | this.dequeue = function () { 102 | var c; 103 | if (a.length === 0) { 104 | return undefined; 105 | } 106 | c = a[b]; 107 | b += 1; 108 | if ((b*2) >= a.length){ 109 | a = a.slice(b); 110 | b = 0; 111 | } 112 | return c; 113 | }; 114 | 115 | this.peek = function () { 116 | return(a.length>0?a[b]:undefined); 117 | }; 118 | } 119 | 120 | var 121 | head = d.getElementsByTagName("head")[0].lastChild, 122 | scriptArray = d.getElementsByTagName(script), 123 | scripts = scriptArray[scriptArray.length-1], 124 | queue = new Queue(), 125 | dict = new Dictionary(), 126 | determine = function (obj) { 127 | return dict[obj.type] || dict[obj.url.match(/\.[^.]+$/)[0]]; 128 | }, 129 | 130 | /** 131 | * Cross browser object, which has a lot of cross browser functionalities. 132 | */ 133 | cross = { 134 | /** 135 | * Function to determine if DOM is ready to be manipulated. 136 | */ 137 | domReady : function (cb) { 138 | var timer, change, ready, clear, done = boolf; 139 | 140 | if (typeof cb === func || typeof cb === object) { 141 | /* Clear timer */ 142 | clear = function () { 143 | clearInterval(timer); 144 | timer = nil; 145 | }; 146 | 147 | /* Called when DOM is ready. Will remove all listeners and 148 | callback initial function */ 149 | ready = function () { 150 | done = boolt; 151 | clear.call(this); 152 | cross.removeListener(d, DOMContentLoaded, change); 153 | cross.removeListener(d, readystatechange, change); 154 | cross.removeListener(w, load, change); 155 | cb.call(this); 156 | }; 157 | 158 | /* Checks if DOM is ready */ 159 | change = function (evt) { 160 | if (!done) { 161 | if (evt && evt.type === DOMContentLoaded) { 162 | ready.call(this); 163 | } else if (evt && evt.type === load) { 164 | ready.call(this); 165 | } else if (d.readyState) { 166 | if (d.readyState === loaded || 167 | d.readyState === complete) { 168 | ready.call(this); 169 | } else if (!!d.documentElement.doScroll) { 170 | try { 171 | d.documentElement.doScroll("left"); 172 | } catch (exc) { 173 | return; 174 | } 175 | ready.call(this); 176 | } 177 | } 178 | } else { 179 | clear.call(this); 180 | } 181 | }; 182 | 183 | /* Initialize event listeners and timers that will determine 184 | if DOM is ready */ 185 | cross.setListener(d, DOMContentLoaded, change); 186 | cross.setListener(d, readystatechange, change); 187 | cross.setListener(w, load, change); 188 | timer = setInterval(change, 1); 189 | } 190 | }, cors : function (time) { 191 | var xdr = nil, 192 | 193 | /** 194 | * Function that returns a XHR object and if XDomainRequest is 195 | * available, store such an object in the xdr variable. 196 | */ 197 | XHR = function () { 198 | var obj = nil; 199 | try { 200 | obj = new XMLHttpRequest(); 201 | if (obj.withCredentials) { 202 | return obj; 203 | } 204 | } catch (ignore){} 205 | try { 206 | xdr = new XDomainRequest(); 207 | } catch (ignore){} 208 | try { 209 | if (obj) { 210 | return obj; 211 | } 212 | } catch (ignore){} 213 | try { 214 | return new ActiveXObject(msxml2 + "." + xmlhttp + ".6.0"); 215 | } catch (ignore){} 216 | try { 217 | return new ActiveXObject(msxml2 + "." + xmlhttp + ".3.0"); 218 | } catch (ignore){} 219 | try { 220 | return new ActiveXObject("Microsoft." + xmlhttp); 221 | } catch (ignore){} 222 | 223 | return nil; 224 | }, 225 | 226 | /** 227 | * Function that does the actual request 228 | */ 229 | ajax = function (obj, success, err) { 230 | var url, timer, xhr = XHR.call(this), source = obj, 231 | 232 | /* Aborts the xhr request, clear timer and do error callback */ 233 | clear = function () { 234 | xhr.abort(); 235 | clearTimeout(timer); 236 | err.call(this, source); 237 | }, 238 | 239 | /* Function that is called when the XHR request was 240 | successfull */ 241 | succ = function (response, type) { 242 | clearTimeout(timer); 243 | success.call( 244 | this, 245 | source, 246 | response, 247 | type 248 | ); 249 | }; 250 | 251 | /* If we were not able to create xhr object, we do error 252 | callback */ 253 | if (!xhr) { 254 | err.call(this, source); 255 | return; 256 | } 257 | 258 | /* If URL has "//" as its protocol, we substitute with the 259 | protocol of the current site, to prevent IE 7 form failing */ 260 | if (source.url.match(/^(\/\/)/)) { 261 | if (w.location.protocol) { 262 | url = w.location.protocol + source.url; 263 | } else if (d.protocol) { 264 | url = d.protocol + source.url; 265 | } 266 | } else { 267 | url = source.url; 268 | } 269 | 270 | /* We try to use XDomainRequest request and fallback to XHR 271 | request, if XDomainRequest was not available */ 272 | try { 273 | /* Tests whether XDomainRequest is available and that the 274 | URL is external, in which case it is best to use 275 | XDomainRequest. Else we throw an error which basically 276 | enables us to use XHR instead */ 277 | if (xdr && source.url.match(/^(http|https|\/\/)/)) { 278 | xdr.open(get, url); 279 | xhr = xdr; 280 | } else { 281 | throw new URIError( 282 | "Local URI, use XMLHttpRequest or ActiveXObject" 283 | ); 284 | } 285 | } catch (e0) { 286 | /* Trying to use XHR. If that fails we do error callback */ 287 | try { 288 | xhr.open(get, url, boolt); 289 | } catch (e1) { 290 | err.call(this, source); 291 | return; 292 | } 293 | } 294 | 295 | /* If we try to load an image we use arraybuffer as 296 | responsetype to get the binary data */ 297 | if ( determine(source) === img && 298 | (!source.format || source.format !== base64) ) { 299 | try { 300 | xhr.responseType = "arraybuffer"; 301 | } catch (exc) { 302 | err.call(this, source); 303 | return; 304 | } 305 | } 306 | 307 | /* Sets timeout of XHR to the choosen timeout of jsCache */ 308 | try { 309 | xhr.timeout = time; 310 | } catch (ignore){} 311 | 312 | /* If XHR times out, we do error callback */ 313 | try { 314 | xhr.ontimeout = clear; 315 | } catch (ignore) {} 316 | 317 | /* Tests whether do make listeners for XDR or XHR */ 318 | if (xdr && source.url.match(/^(http|https|\/\/)/)) { 319 | xhr.onload = function () { 320 | succ.call( 321 | this, 322 | xhr.responseText, 323 | xhr.contentType 324 | ); 325 | }; 326 | xhr.onerror = clear; 327 | xhr.onprogress = function () {}; 328 | } else { 329 | xhr.onreadystatechange = function () { 330 | var blob, filereader; 331 | if (xhr.readyState === 4) { 332 | /* Test whether the HTTP status code is successfull 333 | or cache */ 334 | if ((xhr.status >= 200 && xhr.status < 300) || 335 | xhr.status === 304) { 336 | /* If we have successfully loaded and image, we 337 | need to run it through a FileReader. Else 338 | we can just callback our succ function */ 339 | if ( determine(source) === img && 340 | (!source.format || 341 | source.format !== base64) ) { 342 | try { 343 | filereader = new FileReader(); 344 | blob = new Blob( 345 | [xhr.response], 346 | {type: image + "/" + png} 347 | ); 348 | /* Chrome doesn't support 349 | addEventListener so no reason to 350 | use setAttribute from cross 351 | object */ 352 | filereader.onload = function (e) { 353 | succ.call( 354 | this, 355 | e.target.result, 356 | xhr.getResponseHeader( 357 | contenttype 358 | ) 359 | ); 360 | }; 361 | filereader.readAsDataURL(blob); 362 | } catch (ecx) { 363 | clear.call(this); 364 | } 365 | } else { 366 | succ.call( 367 | this, 368 | xhr.responseText, 369 | xhr.getResponseHeader(contenttype) 370 | ); 371 | } 372 | } else { 373 | clear.call(this); 374 | } 375 | } 376 | }; 377 | } 378 | /* Set secondary timeout function because of poor supported 379 | timeout event handlers in xhr */ 380 | timer = setTimeout(clear, time); 381 | 382 | /* Send request */ 383 | xhr.send(nil); 384 | }; 385 | 386 | return { 387 | request : function (obj, success, err) { 388 | ajax.call(this, obj, success, err); 389 | } 390 | }; 391 | }, removeListener : function (elm, act, cb) { 392 | if (elm.removeEventListener) { 393 | elm.removeEventListener(act, cb, boolf); 394 | } else if (elm.detachEvent) { 395 | elm.detachEvent(on+act, cb); 396 | } else { 397 | act = on+act; 398 | elm[act] = nil; 399 | } 400 | }, setListener : function (elm, act, cb) { 401 | if (elm.addEventListener) { 402 | elm.addEventListener(act, cb, boolf); 403 | } else if (elm.attachEvent) { 404 | elm.attachEvent(on+act, cb); 405 | } else { 406 | act = on+act; 407 | if (typeof (elm[act]) === func) { 408 | cb = (function (f1, f2) { 409 | f1.apply(this, arguments); 410 | f2.apply(this, arguments); 411 | }(cb, elm[act])); 412 | } 413 | elm[act] = cb; 414 | } 415 | }, setAttribute : function (elm, att, name) { 416 | if (elm.setAttribute) { 417 | elm.setAttribute(att, name); 418 | } else if (elm.attributes) { 419 | elm.attributes[att] = name; 420 | } else if (elm.createAttribute) { 421 | elm.createAttribute(att, name); 422 | } else { 423 | elm[att] = name; 424 | } 425 | }, isCanvasSupported : function () { 426 | var elm = d.createElement(canvas); 427 | return !!(elm.getContext && elm.getContext(twod)); 428 | } 429 | }, 430 | 431 | /** 432 | * jsCache function that creates the jsCache object 433 | */ 434 | jsCache = function () { 435 | var tmp, prefix = cache + "_", 436 | 437 | /** 438 | * Array holding the filenames that we are currently loading. 439 | */ 440 | loading = [], 441 | 442 | /** 443 | * Function to handle onload events of loaded files. What it basically 444 | * does is removing the filename matching the paramater key from the 445 | * array. 446 | */ 447 | onload = function (key) { 448 | var i; 449 | for (i = loading.length-1; i > -1; i -= 1) { 450 | if ( 451 | (typeof loading[i] === string && loading[i] === key) || 452 | (typeof loading[i] === object && loading[i].url === key) 453 | ) { 454 | loading.splice(i, 1); 455 | break; 456 | } 457 | } 458 | }, 459 | 460 | /** 461 | * Creates all attributes given by the attr object an appending them 462 | * to elm. If elm is not there, a string with attributes are returned 463 | */ 464 | makeAttributes = function (attr, elm) { 465 | var att, attributes = "", name; 466 | if (attr && typeof attr === object) { 467 | for (att in attr) { 468 | if (attr.hasOwnProperty(att)) { 469 | name = att; 470 | if (att === "className" || att === "clss" || 471 | att === "cls") { 472 | name = "class"; 473 | } 474 | if (elm) { 475 | cross.setAttribute(elm, name, attr[att]); 476 | } 477 | attributes += name + "='" + attr[att] + "' "; 478 | } 479 | } 480 | } 481 | return attributes; 482 | }, 483 | 484 | /** 485 | * Function to test whether cached data has expired 486 | */ 487 | expired = function (obj) { 488 | return (obj.expire < new Date().getTime() || 489 | w.jsCache.modified > obj.timestamp); 490 | }, 491 | 492 | /** 493 | * Function to check if the files has finished loading. 494 | */ 495 | ready = function(obj, url, cb) { 496 | /* If element has the readyState attribute we use the 497 | onreadystatechange event handler, but not in the case where 498 | it is Opera, as Opera handles onreadystatechange bad. Else we 499 | use the onload event handler. */ 500 | if (obj.readyState && !w.opera) { 501 | obj.onreadystatechange = function () { 502 | if (this.readyState === loaded || 503 | this.readyState === complete) { 504 | this.onreadystatechange = nil; 505 | cb.call(this, url); 506 | } 507 | }; 508 | } else { 509 | cross.setListener(obj, load, function () { 510 | cb.call(this, url); 511 | }); 512 | } 513 | }, 514 | 515 | /** 516 | * Function that loads scripts asynchronously from an URL or loads 517 | * script from the cache given by the obj.data attribute. 518 | */ 519 | loadScript = function (obj, cb) { 520 | var sc = nil, source = obj, attributes = ""; 521 | try { 522 | /* If attributes has been defined for the wished object, we 523 | append them to the script */ 524 | attributes = makeAttributes(source.attr); 525 | 526 | /* If script wasn't in the cache, we load it asynchronously */ 527 | if (!source.data) { 528 | attributes += async + "='" + ttrue + "' "; 529 | } 530 | 531 | sc = d.createElement( 532 | "<" + script + " " + t + "='" + text + "/" + javascript + 533 | "' " + attributes + src + "='" + 534 | source.url + "'>" 535 | ); 536 | } catch (e) { 537 | sc = d.createElement(script); 538 | cross.setAttribute(sc, t, text + "/" + javascript); 539 | 540 | /* If script wasn't in the cache, we load it asynchronously */ 541 | if (!source.data) { 542 | cross.setAttribute(sc, async, ttrue); 543 | cross.setAttribute(sc, src, source.url); 544 | } 545 | 546 | /* If attributes has been defined for the wished object, we 547 | append them to the script */ 548 | makeAttributes(source.attr, sc); 549 | } finally { 550 | if (sc) { 551 | 552 | /* If script wasn't in cache, we need event handlers to 553 | determine, when the script is loaded. Else we just apply 554 | the cached data to the script, which make it load 555 | immediately. */ 556 | if (!source.data) { 557 | ready(sc, source.url, cb); 558 | } else { 559 | try { 560 | sc.insertBefore(d.createTextNode(source.data), sc.firstChild); 561 | } catch (e) { 562 | sc.text = source.data; 563 | } 564 | } 565 | 566 | /* Appending the script to the DOM */ 567 | if (source.append && typeof source.append === object) { 568 | source.append.parentNode.insertBefore(sc, source.append); 569 | } else { 570 | scripts.parentNode.insertBefore(sc, scripts.nextSibling); 571 | } 572 | 573 | /* If script was cached we need to callback to remove the 574 | script from our loading array */ 575 | if (source.data) { 576 | cb.call(this, source.url); 577 | } 578 | } 579 | } 580 | }, 581 | 582 | /** 583 | * Function that loads stylesheet from URL or cache. 584 | */ 585 | loadStyle = function (obj, cb) { 586 | var st = nil, source = obj; 587 | 588 | /* If stylesheet was not in cache, we load it from URL. Else we 589 | load the stylesheet from cache, and create a style tag to hold 590 | the stylesheet */ 591 | if (!source.data) { 592 | try { 593 | /* IE way of loading stylesheet from url. As this is the 594 | only way to load and render stylesheets in IE, we have 595 | to accept that we cannot apply any additional attributes 596 | to the object, as the createStyleSheet function 597 | automatically appends the stylesheet to the DOM */ 598 | st = d.createStyleSheet(source.url); 599 | cb.call(this, source.url); 600 | return; 601 | } catch (e) { 602 | /* Creating link tag */ 603 | st = d.createElement(link); 604 | 605 | /* Append attributes to link */ 606 | cross.setAttribute(st, t, text + "/" + css); 607 | cross.setAttribute(st, "rel", stylesheet); 608 | cross.setAttribute(st, "href", source.url); 609 | cross.setAttribute(st, "media", "all"); 610 | 611 | /* If attributes has been defined for the wished object, we 612 | append them to the style */ 613 | makeAttributes(source.attr, st); 614 | } 615 | 616 | if (st) { 617 | /* If style wasn't in cache, we need event handlers to 618 | determine, when the style is loaded. */ 619 | ready(st, source.url, cb); 620 | 621 | /* Append the styles to the DOM */ 622 | if (source.append && typeof source.append === object) { 623 | source.append.parentNode.insertBefore(st, source.append); 624 | } else { 625 | head.parentNode.insertBefore(st, head); 626 | } 627 | } 628 | } else { 629 | /* Creating style tag */ 630 | st = d.createElement(style); 631 | 632 | /* Append type attribute to style */ 633 | cross.setAttribute(st, t, text + "/" + css); 634 | 635 | /* If attributes has been defined for the wished object, we 636 | append them to the style */ 637 | makeAttributes(source.attr, st); 638 | 639 | /* Append the styles to the DOM */ 640 | if (source.append && typeof source.append === object) { 641 | source.append.parentNode.insertBefore(st, source.append); 642 | } else { 643 | head.parentNode.insertBefore(st, head); 644 | } 645 | 646 | /* If IE we use cssText of the styleSheet object to render the 647 | css. Else we just insert the text as a textnode */ 648 | if (st.styleSheet) { 649 | st.styleSheet.cssText = source.data; 650 | } else { 651 | st.insertBefore(d.createTextNode(source.data), st.firstChild); 652 | } 653 | 654 | /* If script was cached we need to callback to remove the 655 | script from our loading array */ 656 | cb.call(this, source.url); 657 | } 658 | }, 659 | 660 | /** 661 | * Function that loads image from URL or cache. 662 | */ 663 | loadImg = function(obj, cb) { 664 | var elm = new Image(), timer, source = obj, done = boolf, 665 | 666 | /* Function called when the image has been loaded */ 667 | succ = function (elm) { 668 | var can, con; 669 | done = boolt; 670 | clearInterval(timer); 671 | timer = nil; 672 | if (source.data && elm.src !== source.data && 673 | cross.isCanvasSupported()) { 674 | can = d.createElement(canvas); 675 | con = can.getContext(twod); 676 | con.drawImage(elm, 0, 0); 677 | } 678 | cb.call(this, source.url); 679 | }; 680 | 681 | /* Onload event handler */ 682 | cross.setListener(elm, load, function () { 683 | if (!done) { 684 | succ.call(this, this); 685 | } 686 | }); 687 | 688 | /* Onerror event handler */ 689 | cross.setListener(elm, error, function () { 690 | if (!done) { 691 | if (source.data && source.url !== this.src) { 692 | cross.setAttribute(this, src, source.url); 693 | } else { 694 | succ.call(this, this); 695 | } 696 | } 697 | }); 698 | 699 | /* Setting source of the image */ 700 | if (!source.data) { 701 | cross.setAttribute(elm, src, source.url); 702 | } else { 703 | cross.setAttribute(elm, src, source.data); 704 | } 705 | 706 | /* If attributes has been defined for the wished object, we 707 | append them to the image */ 708 | makeAttributes(source.attr, elm); 709 | 710 | /* Append the image to the DOM */ 711 | if (source.append && typeof source.append === object) { 712 | source.append.parentNode.insertBefore(elm, source.append); 713 | } else { 714 | d.body.insertBefore(elm, d.body.lastChild.nextSibling); 715 | } 716 | 717 | /* Setting timer interval to check if image is loaded as well */ 718 | timer = setInterval(function () { 719 | if ( !elm.complete || 720 | (elm.naturalWidth && elm.naturalWidth === 0) ) { 721 | return; 722 | } 723 | 724 | if (!done) { 725 | succ.call(this, elm); 726 | } 727 | }, 10); 728 | }, 729 | 730 | /** 731 | * Function that adds data to cache 732 | */ 733 | add = function (obj) { 734 | var i, prefkey, key, item, jsonObj, append; 735 | if (obj.url && typeof obj.url === string && w.localStorage) { 736 | /* If everything goes well, we stringify our object and store 737 | it in cache. Else we look for data that has expired to 738 | remove it */ 739 | try { 740 | append = obj.append; 741 | try { 742 | delete obj.append; 743 | } catch (e) { 744 | obj.append = nil; 745 | } 746 | jsonObj = JSON.stringify(obj); 747 | w.localStorage.setItem( 748 | prefix + obj.url, 749 | jsonObj 750 | ); 751 | obj.append = append; 752 | } catch (exc) { 753 | /* localStorage polyfill are throwing exception names that 754 | contains the word "quota" or if IE "Error", when the 755 | storage is full */ 756 | if (exc.name.match(/quota/i) || exc.name === "Error") { 757 | /* Loop through all elements in cache an delete them 758 | if they are expired */ 759 | for (i = w.localStorage.length-1; i > -1; i -= 1) { 760 | prefkey = w.localStorage.key(i); 761 | 762 | if (prefkey) { 763 | key = prefkey.split(prefix)[1]; 764 | } 765 | 766 | if (key) { 767 | item = w.jsCache.get(key); 768 | if (!item || !(item.url === key) || expired(item)) { 769 | w.jsCache.remove(key); 770 | if (JSON.stringify(item).length > jsonObj.length) { 771 | add(obj); 772 | break; 773 | } 774 | } 775 | item = nil; 776 | } 777 | key = nil; 778 | prefkey = nil; 779 | } 780 | } 781 | } 782 | } 783 | }; 784 | 785 | /* If there isn't a native JSON object, we apply one */ 786 | if (!w.JSON) { 787 | tmp = "//cdnjs.cloudflare.com/ajax/libs/json2/20130526/json2.min.js"; 788 | loading.push(tmp); 789 | loadScript({url : tmp}, onload); 790 | } 791 | 792 | if (!w.localStorage) { 793 | /* Load SWFObject, to allow flash storage in our localStorage wrapper */ 794 | if (!w.swfobject) { 795 | tmp = "//ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"; 796 | loading.push(tmp); 797 | loadScript({url : tmp}, onload); 798 | } 799 | 800 | /* Load localStorage, to wrap the localStorage object */ 801 | tmp = "localStorage.min.js"; 802 | loading.push(tmp); 803 | loadScript({url : tmp}, onload); 804 | } 805 | 806 | return { 807 | /** 808 | * When should the cached data expire. 809 | * 810 | * Default: 5 day 811 | */ 812 | expires : (5 * 24 * 60 * 60 * 1000), 813 | 814 | /** 815 | * When should script loading timeout. 816 | * 817 | * Default: 5 seconds 818 | */ 819 | timeout : 5000, 820 | 821 | /** 822 | * If some of the cached files have been modified a timestamp 823 | * indicating when the modification was made, should be stored 824 | * in this variable, before any loading from the cache. 825 | * 826 | * Default: 0 827 | */ 828 | modified : 0, 829 | 830 | /** 831 | * Function that gets cached data. 832 | * 833 | * @key [string] (String associated with some data) 834 | */ 835 | get : function (key) { 836 | var obj = nil; 837 | if (key && typeof key === string && w.localStorage) { 838 | try { 839 | obj = JSON.parse(w.localStorage.getItem(prefix + key)); 840 | if (!obj || !(obj.url === key) || expired(obj)) { 841 | w.localStorage.removeItem(prefix + key); 842 | obj = nil; 843 | } 844 | } catch (ignore) {} 845 | } 846 | return obj; 847 | }, 848 | 849 | /** 850 | * Function that loads scripts and stylesheets 851 | * 852 | * @arguments [object, ..., object] (Object containing as minimum 853 | * a url property) 854 | */ 855 | load : function () { 856 | var i, source, obj, elm, cors = new cross.cors(this.timeout), 857 | /* Function to be called when we wish to load or insert script */ 858 | insert = function (obj) { 859 | var type = determine(obj); 860 | if (type) { 861 | if (type === js) { 862 | loadScript.call(this, obj, onload); 863 | } else if (type === css) { 864 | loadStyle.call(this, obj, onload); 865 | } else if (type === img) { 866 | loadImg.call(this, obj, onload); 867 | } 868 | } 869 | }; 870 | 871 | if (arguments.length > 0 && w.localStorage) { 872 | /* Loops through all arguments */ 873 | for (i = arguments.length-1; i > -1; i -= 1) { 874 | source = arguments[i]; 875 | /* If we have a valid argument */ 876 | if ( source && typeof source === object && source.url && 877 | ( !source.detect || dict[source.detect]) ) { 878 | loading.push(source.url); 879 | obj = this.get(source.url); 880 | /* If the argument was stored in cache we insert 881 | it directly */ 882 | if (obj) { 883 | if (source.append) { 884 | obj.append = source.append; 885 | } 886 | insert.call( 887 | this, 888 | obj 889 | ); 890 | /* If the cache argument is set to false, we do 891 | not cache it */ 892 | } else if (typeof source.cache === "boolean" && 893 | !source.cache) { 894 | insert.call( 895 | this, 896 | source 897 | ); 898 | /* Else we request the arguments data with CORS 899 | and insert it */ 900 | } else { 901 | try { 902 | /* If an image is requested, canvas is 903 | supported, and format isn't base64 */ 904 | if (determine(source) === img && 905 | cross.isCanvasSupported() && 906 | (!source.format || source.format !== base64)) { 907 | elm = new Image(); 908 | 909 | /* If crossOrigin attribute is 910 | supported on images and we have an 911 | external URL */ 912 | if (elm.crossOrigin && 913 | source.url.match(/^(http|https|\/\/)/) ) { 914 | elm.crossOrigin = "anonymous"; 915 | } 916 | 917 | /* Onload handler for image, that cache 918 | the image an applying it to the DOM 919 | by calling insert function */ 920 | cross.setListener(elm, load, function () { 921 | var can = d.createElement(canvas), 922 | con = can.getContext(twod), 923 | now = new Date().getTime(); 924 | 925 | can.width = this.width; 926 | can.height = this.height; 927 | 928 | con.drawImage( 929 | this, 930 | 0, 931 | 0, 932 | this.width, 933 | this.height 934 | ); 935 | 936 | source.data = can.toDataURL( 937 | image + "/" + png 938 | ); 939 | source.type = png; 940 | source.expire = now + w.jsCache.expires; 941 | source.timestamp = now; 942 | add(source); 943 | insert.call( 944 | this, 945 | source 946 | ); 947 | }); 948 | 949 | /* Applying source to image */ 950 | cross.setAttribute(elm, src, source.url); 951 | } else { 952 | /* If we do not have and image with the 953 | required conditions, we fallback to 954 | CORS */ 955 | throw { 956 | name : "CORSExpectedException", 957 | message : "CORS should be used" 958 | }; 959 | } 960 | } catch (e) { 961 | cors.request( 962 | source, 963 | /* If successfully loaded, we store it 964 | in cache */ 965 | function (obj, data, type) { 966 | var now = new Date().getTime(); 967 | obj.data = data; 968 | obj.type = obj.type || type; 969 | obj.expire = now + w.jsCache.expires; 970 | obj.timestamp = now; 971 | add(obj); 972 | insert.call( 973 | this, 974 | obj 975 | ); 976 | }, 977 | /* Else we just insert the argument 978 | asynchronously */ 979 | function (obj) { 980 | insert.call( 981 | this, 982 | obj 983 | ); 984 | } 985 | ); 986 | } 987 | } 988 | } 989 | } 990 | queue.dequeue(); 991 | } 992 | return this; 993 | }, 994 | 995 | /** 996 | * Function that waits for previous loaded files to be loaded 997 | * 998 | * @cb [function] (Function to call when previous files are loaded) 999 | */ 1000 | then : function (cb) { 1001 | var timer; 1002 | queue.enqueue(cb); 1003 | setTimeout(function () { 1004 | timer = setInterval(function () { 1005 | if (queue.peek() === cb && loading.length === 0) { 1006 | clearInterval(timer); 1007 | cb.call(this); 1008 | } 1009 | }, 10); 1010 | }, 1); 1011 | }, 1012 | 1013 | /** 1014 | * Remove cached data associated with the key 1015 | * 1016 | * @key [string] (String associated with some data) 1017 | */ 1018 | remove : function (key) { 1019 | if (key && typeof key === string && w.localStorage) { 1020 | try { 1021 | w.localStorage.removeItem(prefix + key); 1022 | } catch (ignore) {} 1023 | } 1024 | return this; 1025 | }, 1026 | 1027 | /** 1028 | * Delete the whole cache 1029 | */ 1030 | clear : function () { 1031 | var i, key, prefkey; 1032 | if (w.localStorage) { 1033 | try { 1034 | for (i = w.localStorage.length-1; i > -1; i -= 1) { 1035 | prefkey = w.localStorage.key(i); 1036 | 1037 | if (prefkey) { 1038 | key = prefkey.split(prefix)[1]; 1039 | } 1040 | 1041 | if (key) { 1042 | this.remove(key); 1043 | } 1044 | 1045 | prefkey = nil; 1046 | key = nil; 1047 | } 1048 | } catch (ignore) {} 1049 | } 1050 | return this; 1051 | }, 1052 | 1053 | /** 1054 | * Detect specific IE versions. 1055 | * 1056 | * @cond [string] (Should be the same as traditionally IE version comment detection) 1057 | * @cb [function] (Function to call if condition is hold) 1058 | */ 1059 | detect : function(cond, cb) { 1060 | if (dict[cond]) { 1061 | cb.call(this); 1062 | } 1063 | return this; 1064 | }, 1065 | 1066 | /** 1067 | * Detect if DOM is ready for manipulation 1068 | * 1069 | * @cb [function] (Function to call when DOM is ready) 1070 | */ 1071 | domReady : function (cb) { 1072 | cross.domReady(function () { 1073 | if (w.swfLoaded) { 1074 | w.swfLoaded(cb); 1075 | } else { 1076 | cb.call(this); 1077 | } 1078 | }); 1079 | } 1080 | }; 1081 | }; 1082 | 1083 | if (!w.jsCache) { 1084 | w.jsCache = new jsCache(); 1085 | } 1086 | 1087 | }(window, window.document, "string", "object", "function", "text", "css", 1088 | "script", "style", "javascript", "img", "application", "stylesheet", "link", 1089 | "jsCache", "type", "DOMContentLoaded", "readystatechange", "loaded", "load", 1090 | "complete", "GET", "Content-Type", "async", "js", "if IE", "if gt IE", 1091 | "if lt IE", "on", "Msxml2", "XMLHTTP", "error", "canvas", "2d", "src", "png", 1092 | "gif", "jpg", "svg", "image", "base64", "true", true, false, null)); 1093 | -------------------------------------------------------------------------------- /dist/jsCache.min.js: -------------------------------------------------------------------------------- 1 | (function(h,l,F,t,bc,w,u,G,bd,x,D,be,bf,br,bs,H,R,bg,bh,y,bi,bj,bk,bl,I,z,E,J,K,bm,S,bt,T,U,A,B,V,W,X,C,Y,bn,Z,L,o){"use strict";function bu(){var b={},f=(function(){var a=3,d=l.createElement("div"),c=d.getElementsByTagName("i");do{a+=1;d.innerHTML=""}while(c[0]);return a>4?a:0}());b[be+"/"+x]=b[w+"/"+x]=b[be+"/x-"+x]=b["."+I]=b[I]=b[x]=b[G]=I;b[w+"/"+u]=b[u]=b["."+u]=b[bf]=b[bd]=u;b[B]=b["."+B]=b[C+"/"+B]=b[V]=b["."+V]=b[C+"/"+V]=b[C+"/"+W]=b[W]=b["."+W]=b[C+"/"+X]=b["."+X]=b[X]=D;b[z]=(f>4);b[J+" 9"]=(f&&f<9);b[J+" 8"]=(f&&f<8);b[J+" 7"]=(f&&f<7);b[J+" 6"]=(f&&f<6);b[E+" 6"]=(f>6);b[E+" 7"]=(f>7);b[E+" 8"]=(f>8);b[E+" 9"]=(f>9);b[z+" 9"]=(f===9);b[z+" 8"]=(f===8);b[z+" 7"]=(f===7);b[z+" 6"]=(f===6);b[z+" 5"]=(f===5);return b}function bv(){var d=[],c=0;this.getLength=function(){return(d.length-c)};this.isEmpty=function(){return(d.length===0)};this.enqueue=function(a){d.push(a)};this.dequeue=function(){var a;if(d.length===0){return undefined}a=d[c];c+=1;if((c*2)>=d.length){d=d.slice(c);c=0}return a};this.peek=function(){return(d.length>0?d[c]:undefined)}}var M=l.getElementsByTagName("head")[0].lastChild,bo=l.getElementsByTagName(G),bp=bo[bo.length-1],ba=new bv(),N=new bu(),O=function(a){return N[a.type]||N[a.url.match(/\.[^.]+$/)[0]]},j={domReady:function(d){var c,b,f,g,n=L;if(typeof d===bc||typeof d===t){g=function(){clearInterval(c);c=o};f=function(){n=Z;g.call(this);j.removeListener(l,R,b);j.removeListener(l,bg,b);j.removeListener(h,y,b);d.call(this)};b=function(a){if(!n){if(a&&a.type===R){f.call(this)}else if(a&&a.type===y){f.call(this)}else if(l.readyState){if(l.readyState===bh||l.readyState===bi){f.call(this)}else if(!!l.documentElement.doScroll){try{l.documentElement.doScroll("left")}catch(exc){return}f.call(this)}}}else{g.call(this)}};j.setListener(l,R,b);j.setListener(l,bg,b);j.setListener(h,y,b);c=setInterval(b,1)}},cors:function(q){var r=o,v=function(){var a=o;try{a=new XMLHttpRequest();if(a.withCredentials){return a}}catch(ignore){}try{r=new XDomainRequest()}catch(ignore){}try{if(a){return a}}catch(ignore){}try{return new ActiveXObject(bm+"."+S+".6.0")}catch(ignore){}try{return new ActiveXObject(bm+"."+S+".3.0")}catch(ignore){}try{return new ActiveXObject("Microsoft."+S)}catch(ignore){}return o},P=function(b,f,g){var n,m,i=v.call(this),k=b,p=function(){i.abort();clearTimeout(m);g.call(this,k)},s=function(a,d){clearTimeout(m);f.call(this,k,a,d)};if(!i){g.call(this,k);return}if(k.url.match(/^(\/\/)/)){if(h.location.protocol){n=h.location.protocol+k.url}else if(l.protocol){n=l.protocol+k.url}}else{n=k.url}try{if(r&&k.url.match(/^(http|https|\/\/)/)){r.open(bj,n);i=r}else{throw new URIError("Local URI, use XMLHttpRequest or ActiveXObject");}}catch(e0){try{i.open(bj,n,Z)}catch(e1){g.call(this,k);return}}if(O(k)===D&&(!k.format||k.format!==Y)){try{i.responseType="arraybuffer"}catch(exc){g.call(this,k);return}}try{i.timeout=q}catch(ignore){}try{i.ontimeout=p}catch(ignore){}if(r&&k.url.match(/^(http|https|\/\/)/)){i.onload=function(){s.call(this,i.responseText,i.contentType)};i.onerror=p;i.onprogress=function(){}}else{i.onreadystatechange=function(){var d,c;if(i.readyState===4){if((i.status>=200&&i.status<300)||i.status===304){if(O(k)===D&&(!k.format||k.format!==Y)){try{c=new FileReader();d=new Blob([i.response],{type:C+"/"+B});c.onload=function(a){s.call(this,a.target.result,i.getResponseHeader(bk))};c.readAsDataURL(d)}catch(ecx){p.call(this)}}else{s.call(this,i.responseText,i.getResponseHeader(bk))}}else{p.call(this)}}}}m=setTimeout(p,q);i.send(o)};return{request:function(a,d,c){P.call(this,a,d,c)}}},removeListener:function(a,d,c){if(a.removeEventListener){a.removeEventListener(d,c,L)}else if(a.detachEvent){a.detachEvent(K+d,c)}else{d=K+d;a[d]=o}},setListener:function(c,b,f){if(c.addEventListener){c.addEventListener(b,f,L)}else if(c.attachEvent){c.attachEvent(K+b,f)}else{b=K+b;if(typeof(c[b])===bc){f=(function(a,d){a.apply(this,arguments);d.apply(this,arguments)}(f,c[b]))}c[b]=f}},setAttribute:function(a,d,c){if(a.setAttribute){a.setAttribute(d,c)}else if(a.attributes){a.attributes[d]=c}else if(a.createAttribute){a.createAttribute(d,c)}else{a[d]=c}},isCanvasSupported:function(){var a=l.createElement(T);return!!(a.getContext&&a.getContext(U))}},bw=function(){var p,s=bs+"_",q=[],r=function(a){var d;for(d=q.length-1;d>-1;d-=1){if((typeof q[d]===F&&q[d]===a)||(typeof q[d]===t&&q[d].url===a)){q.splice(d,1);break}}},v=function(a,d){var c,b="",f;if(a&&typeof a===t){for(c in a){if(a.hasOwnProperty(c)){f=c;if(c==="className"||c==="clss"||c==="cls"){f="class"}if(d){j.setAttribute(d,f,a[c])}b+=f+"='"+a[c]+"' "}}}return b},P=function(a){return(a.expire