├── .gitignore ├── LICENSE ├── README.md ├── build └── build.js ├── component.js ├── demo ├── images │ ├── sample-1.png │ ├── sample-2.png │ ├── sample-3.png │ ├── sample-4.png │ └── sample-5.png ├── index.html ├── placeholder.png ├── remote_images.json ├── scripts │ ├── app.js │ └── bootstrap.js └── style.css └── lib ├── RAL ├── CacheParser.js ├── FileManifest.js ├── FileSystem.js ├── Heap.js ├── Image.js ├── Loader.js ├── Namespace.js ├── NetworkMonitor.js ├── Queue.js ├── RemoteFile.js └── Sanitiser.js ├── ral.map └── ral.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Chrome Packaged Apps Resource Loader 2 | 3 | You can request external images using XMLHttpRequest and transform them into ObjectURLs. Then set the src attribute in the `` tag to each ObjectURL and it should work. 4 | 5 | Since this is a very common use case, we created this library to simplify it. Just drop the apps-resource-loader [ral.min.js](https://github.com/GoogleChrome/apps-resource-loader/blob/master/lib/ral.min.js) to your project and then: 6 | 7 | var remoteImage, 8 | container = document.querySelector('.imageContainer'), 9 | toLoad = { 'images': [ 10 | 'http://myserver.com/image1.png', 11 | 'http://myserver.com/image2.png' ] }; // list of image URLs 12 | 13 | toLoad.images.forEach(function(imageToLoad) { 14 | remoteImage = new RAL.RemoteImage(imageToLoad); 15 | container.appendChild(remoteImage.element); 16 | RAL.Queue.add(remoteImage); 17 | }); 18 | RAL.Queue.setMaxConnections(4); 19 | RAL.Queue.start(); 20 | 21 | Remember that you need permission in the `manifest.json` to all domains you will be XHR'ing to. If you don't know beforehand where those images will be hosted, you can ask permission for any url: 22 | 23 | permissions: [''], 24 | 25 | For other usages, please see the simple demo at: 26 | https://github.com/GoogleChrome/apps-resource-loader/tree/master/demo 27 | 28 | ## LICENSE 29 | 30 | Copyright 2013 Google Inc. All Rights Reserved. 31 | 32 | Licensed under the Apache License, Version 2.0 (the "License"); 33 | you may not use this file except in compliance with the License. 34 | You may obtain a copy of the License at 35 | 36 | http://www.apache.org/licenses/LICENSE-2.0 37 | 38 | Unless required by applicable law or agreed to in writing, software 39 | distributed under the License is distributed on an "AS IS" BASIS, 40 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 41 | See the License for the specific language governing permissions and 42 | limitations under the License. 43 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | /** npm install uglify-js **/ 2 | var UglifyJS = require("uglify-js"); 3 | var FS = require('fs'); 4 | 5 | var result = UglifyJS.minify ( 6 | 7 | // files 8 | [ 9 | "Namespace.js", 10 | "Heap.js", 11 | "Sanitiser.js", 12 | "CacheParser.js", 13 | "FileSystem.js", 14 | "FileManifest.js", 15 | "RemoteFile.js", 16 | "Image.js", 17 | "Loader.js", 18 | "NetworkMonitor.js", 19 | "Queue.js" 20 | ], 21 | 22 | // options 23 | { 24 | outSourceMap: "ral.map", 25 | sourceRoot: "/lib/RAL/" 26 | } 27 | ); 28 | 29 | result.code += "//# sourceMappingURL=ral.map"; 30 | 31 | FS.writeFileSync('../ral.min.js', result.code); 32 | FS.writeFileSync('../ral.map', result.map); 33 | -------------------------------------------------------------------------------- /component.js: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AppResourceLoader", 3 | "version": "0.0.1", 4 | "main": ["./lib/ral.min.js"] 5 | } 6 | -------------------------------------------------------------------------------- /demo/images/sample-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/apps-resource-loader/5e15c184eabcc902bacf84f68d654988578ac5c2/demo/images/sample-1.png -------------------------------------------------------------------------------- /demo/images/sample-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/apps-resource-loader/5e15c184eabcc902bacf84f68d654988578ac5c2/demo/images/sample-2.png -------------------------------------------------------------------------------- /demo/images/sample-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/apps-resource-loader/5e15c184eabcc902bacf84f68d654988578ac5c2/demo/images/sample-3.png -------------------------------------------------------------------------------- /demo/images/sample-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/apps-resource-loader/5e15c184eabcc902bacf84f68d654988578ac5c2/demo/images/sample-4.png -------------------------------------------------------------------------------- /demo/images/sample-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/apps-resource-loader/5e15c184eabcc902bacf84f68d654988578ac5c2/demo/images/sample-5.png -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Image Loader 6 | 7 | 8 | 9 | 10 |

Image caching

11 |
12 |

13 | TL;DR The interesting code is in scripts/bootstrap.js 14 |

15 | 16 |

17 | This page loads 5 images. Three are specified in a remote manifest.json file, 18 | one is added from the img tag in the page and the final one comes from 19 | an object created in JavaScript. These are the three ways to get images. 20 |

21 | 22 |

23 | Image 1 should never cache. That is, the headers that come back from the 24 | server should set no-cache for it. This triggers a warning, because we can't 25 | save the image for offline. 26 |

27 | 28 |

29 | All other images are given somewhere between 60 and 180 seconds as their 30 | max-age. You can see the images saved in the filesystem along with an additional file 31 | stored in the root which contains the details on all files we've saved. 32 |

33 | 34 |

35 | If the manifest or a file is removed, or a file has expired, the browser will request it 36 | again from the server. If it is not possible to retrieve a new copy of the 37 | file then the old one is served. 38 |

39 |
40 | 41 |
42 |
43 | 44 |
45 | 46 |
47 |

Warnings

48 | 49 |
50 | 51 | 52 | 53 |
54 | 55 | 56 | 57 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /demo/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/apps-resource-loader/5e15c184eabcc902bacf84f68d654988578ac5c2/demo/placeholder.png -------------------------------------------------------------------------------- /demo/remote_images.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [{ 3 | "src": "images/sample-1.png", 4 | "priority": 1 5 | },{ 6 | "src": "images/sample-2.png", 7 | "priority": 2, 8 | "placeholder": "placeholder.png", 9 | "width": 376, 10 | "height": 201 11 | },{ 12 | "src": "images/sample-3.png", 13 | "placeholder": "placeholder.png" 14 | }] 15 | } 16 | -------------------------------------------------------------------------------- /demo/scripts/app.js: -------------------------------------------------------------------------------- 1 | var count = 1; 2 | 3 | function onCacheError(evt) { 4 | 5 | var fileInfo = evt.data; 6 | var log = document.getElementById('log'); 7 | 8 | log.textContent += (count++) + ": " + evt.data.src + " is not cacheable:\n" + 9 | fileInfo.warnings.join("\n") + "\n\n"; 10 | 11 | // hack to scroll the warning pane 12 | log.scrollTop = 1000000; 13 | } 14 | 15 | function onClick(evt) { 16 | this.load(); 17 | } 18 | 19 | function showClear() { 20 | var clearButton = document.getElementById('clear'); 21 | clearButton.addEventListener('click', function() { 22 | RAL.FileSystem.removeDir('slowimageserver.appspot.com', function() { 23 | console.log("All clear"); 24 | }); 25 | }); 26 | } 27 | 28 | if(RAL.FileSystem.isReady()) { 29 | showClear(); 30 | } else { 31 | RAL.FileSystem.registerOnReady(showClear); 32 | } 33 | -------------------------------------------------------------------------------- /demo/scripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', function() { 2 | 3 | /** 4 | * Fires off a request to get a remote manifest file 5 | */ 6 | function init() { 7 | 8 | // get the local resources 9 | parseLocalImages(); 10 | 11 | // start by loading the remote asset list 12 | RAL.Loader.load("remote_images.json", 13 | 'text', // xhr response type 14 | onSuccessfulRMGet, 15 | onFailedRMGet); 16 | } 17 | 18 | function onSuccessfulRMGet(data) { 19 | var toLoad = JSON.parse(data); 20 | var imageToLoad = null; 21 | var remoteImage = null; 22 | var container = document.getElementById('mid'); 23 | 24 | for(var i = 0; i < toLoad.images.length; i++) { 25 | 26 | imageToLoad = toLoad.images[i]; 27 | 28 | // next add in an image directly from the script 29 | // with the highest queue priority 30 | remoteImage = new RAL.RemoteImage(imageToLoad); 31 | 32 | // attach the new image element to the DOM 33 | // (we could've passed in an image to the RemoteImage call 34 | // but since we didn't it created an image element) 35 | container.appendChild(remoteImage.element); 36 | 37 | // listen for the image being loaded 38 | // and spit out a console message 39 | remoteImage.addEventListener('loaded', 40 | onRemoteImageLoaded.bind(remoteImage)); 41 | 42 | // also listen for cache problems like 'no-cache' 43 | remoteImage.addEventListener('cacheerror', 44 | onCacheError); 45 | 46 | // add it to the queue 47 | RAL.Queue.add(remoteImage); 48 | } 49 | 50 | go(); 51 | } 52 | 53 | function onRemoteImageLoaded() { 54 | console.log("I have loaded the image from remote: " + this.src); 55 | } 56 | 57 | function onFailedRMGet() { 58 | console.warn("Could not load remote manifest"); 59 | 60 | // just go get what we have queued 61 | go(); 62 | } 63 | 64 | /** 65 | * Gets the images that are embedded in the page itself 66 | * and creates an additional image asset directly 67 | */ 68 | function parseLocalImages() { 69 | 70 | // grab all images in the page 71 | var images = document.querySelectorAll('img'); 72 | 73 | for (var i = 0; i < images.length; i++) { 74 | 75 | // wrap the image in a remote image 76 | // object and pass it to the queue 77 | // - set the image priority to the reverse 78 | // of the querySelectorAll order 79 | var remoteImage = new RAL.RemoteImage({ 80 | element: images[i], 81 | priority: i 82 | }); 83 | 84 | // declared in app.js - lets us know if we 85 | // hit cache errors, e.g. no-cache 86 | remoteImage.addEventListener('cacheerror', onCacheError); 87 | 88 | RAL.Queue.add(remoteImage); 89 | } 90 | 91 | // next add in an image (sample-5.png) directly from the script 92 | // with the highest queue priority 93 | var finalImage = new RAL.RemoteImage({ 94 | src: "images/sample-5.png", 95 | priority: RAL.Queue.getNextHighestPriority(), 96 | width: 376, 97 | height: 201 98 | }); 99 | 100 | // just listen for the image being loaded 101 | // and add it into the document 102 | finalImage.addEventListener('loaded', function() { 103 | var img = finalImage.element; 104 | document.getElementById('mid').appendChild(img); 105 | }); 106 | 107 | // add it to the queue 108 | RAL.Queue.add(finalImage); 109 | } 110 | 111 | function go() { 112 | 113 | // start the queue, maxed out 114 | // to 4 connections 115 | RAL.Queue.setMaxConnections(4); 116 | RAL.Queue.start(); 117 | } 118 | 119 | // wait for the file manifest to be loaded 120 | // before attempting anything 121 | if(RAL.FileManifest.isReady()) { 122 | init(); 123 | } else { 124 | RAL.FileManifest.registerOnReady(init); 125 | } 126 | 127 | }); 128 | -------------------------------------------------------------------------------- /demo/style.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | font-family: "Helvetica Neue", Helvetica, Arial; 4 | background: #E2E2E2; 5 | color: #555; 6 | text-shadow: 0 1px 1px rgba(255,255,255,0.4); 7 | line-height: 140%; 8 | } 9 | 10 | h1 { 11 | color: #333; 12 | text-shadow: 0 1px 1px rgba(255,255,255,0.6); 13 | padding: 15px 0 0 10px; 14 | } 15 | 16 | h2 { 17 | color: #333; 18 | text-shadow: 0 1px 1px rgba(255,255,255,0.6); 19 | padding: 10px 0 5px 0; 20 | margin: 0; 21 | font-size: 16px; 22 | } 23 | 24 | #container { 25 | position: relative; 26 | } 27 | 28 | #left { 29 | position: absolute; 30 | top: 80px; 31 | left: 20px; 32 | width: 370px; 33 | font-size: 13px; 34 | line-height: 125%; 35 | } 36 | 37 | #right { 38 | position: absolute; 39 | top: 200px; 40 | right: 30px; 41 | width: 80px; 42 | } 43 | 44 | #left a, #right a { 45 | border-radius: 40px; 46 | width: 80px; 47 | height: 80px; 48 | text-align: center; 49 | display: block; 50 | background: #333; 51 | text-indent: -30000px; 52 | } 53 | 54 | #left a:after, 55 | #right a:after { 56 | content:'←'; 57 | text-indent: 0; 58 | position: absolute; 59 | top: 0; 60 | left: 0; 61 | width: 80px; 62 | height: 80px; 63 | line-height: 70px; 64 | color: #FFF; 65 | text-shadow: 0 1px 1px rgba(0,0,0,0.3); 66 | font-size: 45px; 67 | } 68 | 69 | #right a:after { 70 | content:'→'; 71 | } 72 | 73 | #mid { 74 | margin: 0 0 0 400px; 75 | } 76 | 77 | .resource { 78 | background: #FFF; 79 | padding: 5px; 80 | border-radius: 3px; 81 | box-shadow: 0 1px 2px rgba(0,0,0,0.4); 82 | } 83 | 84 | #image-holder { 85 | min-height: 415px; 86 | margin-bottom: 30px; 87 | } 88 | 89 | #warnings { 90 | width: 360px; 91 | height: 170px; 92 | position: fixed; 93 | bottom: 20px; 94 | left: 20px; 95 | } 96 | 97 | textarea { 98 | width: 90%; 99 | height: 120px; 100 | background: rgba(255,255,255,0.5); 101 | color: #333; 102 | border: 1px solid gray; 103 | } 104 | 105 | #clear { 106 | margin-bottom: 20px; 107 | } 108 | -------------------------------------------------------------------------------- /lib/RAL/CacheParser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parses cache headers so that we can respect them. 3 | */ 4 | RAL.CacheParser = (function() { 5 | 6 | /** 7 | * Parses headers to determine whether there is 8 | * anything in there that we need to know about, e.g. 9 | * no-cache or no-store. 10 | * @param {string} headers The XHR headers to parse. 11 | * @see http://www.mnot.net/cache_docs/ 12 | * @returns {object} An object with the extracted expiry for the asset. 13 | */ 14 | function parse(headers) { 15 | 16 | var rMaxAge = /max\-age=(\d+)/gi; 17 | var rNoCache = /Cache-Control:.*?no\-cache/gi; 18 | var rNoStore = /Cache-Control:.*?no\-store/gi; 19 | var rMustRevalidate = /Cache-Control:.*?must\-revalidate/gi; 20 | var rExpiry = /Expires:\s(.*)/gi; 21 | 22 | var warnings = []; 23 | var expires = rMaxAge.exec(headers); 24 | var useBy = Date.now(); 25 | 26 | // check for no-store 27 | if(rNoStore.test(headers)) { 28 | warnings.push("Cache-Control: no-store is set"); 29 | } 30 | 31 | // check for no-cache 32 | if(rNoCache.test(headers)) { 33 | warnings.push("Cache-Control: no-cache is set"); 34 | } 35 | 36 | // check for must-revalidate 37 | if(rMustRevalidate.test(headers)) { 38 | warnings.push("Cache-Control: must-revalidate is set"); 39 | } 40 | 41 | // if no max-age is set check 42 | // for an Expires value 43 | if(expires !== null) { 44 | useBy = Date.now() + (expires[1] * 1000); 45 | } else { 46 | 47 | // attempt to use the Expires: header 48 | expires = rExpiry.exec(headers); 49 | 50 | // if that fails warn 51 | if(expires !== null) { 52 | useBy = Date.parse(expires[1]); 53 | } else { 54 | warnings.push("Cache-Control: max-age and Expires: headers are not set"); 55 | } 56 | } 57 | 58 | return { 59 | headers: headers, 60 | cacheable: (warnings.length === 0), 61 | useBy: useBy, 62 | warnings: warnings 63 | }; 64 | } 65 | 66 | return { 67 | parse: parse 68 | }; 69 | 70 | })(); 71 | -------------------------------------------------------------------------------- /lib/RAL/FileManifest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the internal log of all files that 3 | * have been cached for offline use. 4 | */ 5 | RAL.FileManifest = (function() { 6 | 7 | var manifest = null, 8 | ready = false, 9 | readyListeners = [], 10 | saving = false, 11 | hasUpdated = false; 12 | 13 | /** 14 | * Determines if the manifest is ready for use. 15 | * @returns {boolean} If the manifest is ready. 16 | */ 17 | function isReady() { 18 | return ready; 19 | } 20 | 21 | /** 22 | * Registers a listener for when the manifest is 23 | * ready for interaction. 24 | * @param {Function} listener The listener function. 25 | */ 26 | function registerOnReady(listener) { 27 | readyListeners.push(listener); 28 | } 29 | 30 | /** 31 | * Gets the file information from the manifest. 32 | * @param {string} src The source URL of the asset. 33 | * @param {Function} callback The callback function to 34 | * call with the asset details. 35 | */ 36 | function get(src, callback) { 37 | var cleanSrc = RAL.Sanitiser.cleanURL(src); 38 | var fileInfo = manifest[cleanSrc] || null; 39 | callback(fileInfo); 40 | } 41 | 42 | /** 43 | * Sets the file information in the manifest. 44 | * @param {string} src The source URL of the asset. 45 | * @param {object} info The information to store against the asset. 46 | * @param {Function} callback The callback function to 47 | * call once the asset details have been saved. 48 | */ 49 | function set(src, info, callback) { 50 | var cleanSrc = RAL.Sanitiser.cleanURL(src); 51 | manifest[cleanSrc] = info; 52 | save(callback); 53 | } 54 | 55 | /** 56 | * Resets the manifest of files. 57 | */ 58 | function reset() { 59 | manifest = {}; 60 | save(); 61 | } 62 | 63 | /** 64 | * Callback for when there is no manifest available. 65 | * @private 66 | */ 67 | function onManifestUnavailable() { 68 | onManifestLoaded("{}"); 69 | } 70 | 71 | /** 72 | * Callback for when there is no manifest has laded. Passes through the 73 | * registered ready callbacks and fires each in turn. 74 | * @param {string} The JSON representation of the manifest. 75 | * @private 76 | */ 77 | function onManifestLoaded(fileData) { 78 | 79 | ready = true; 80 | manifest = JSON.parse(fileData); 81 | 82 | if (readyListeners.length) { 83 | var listener = readyListeners.length; 84 | while(listener--) { 85 | readyListeners[listener](); 86 | } 87 | } 88 | } 89 | 90 | /** 91 | * Saves the manifest file. 92 | * @private 93 | */ 94 | function save(callback) { 95 | 96 | var blob = new Blob([ 97 | JSON.stringify(manifest) 98 | ], {type: 'application/json'}); 99 | 100 | // Called whether or not the file exists 101 | RAL.FileSystem.set("manifest.json", blob, function() { 102 | if(!!callback) { 103 | callback(); 104 | } 105 | }); 106 | } 107 | 108 | /** 109 | * Requests the manifest JSON file from the file system. 110 | */ 111 | function init() { 112 | RAL.FileSystem.getDataAsText("manifest.json", 113 | onManifestLoaded, 114 | onManifestUnavailable); 115 | } 116 | 117 | // check if the file system is good to go. If not, then 118 | // flag that we want to know when it is. 119 | if (RAL.FileSystem.isReady()) { 120 | init(); 121 | } else { 122 | RAL.FileSystem.registerOnReady(init); 123 | } 124 | 125 | return { 126 | isReady: isReady, 127 | registerOnReady: registerOnReady, 128 | get: get, 129 | set: set, 130 | reset: reset 131 | }; 132 | 133 | })(); 134 | -------------------------------------------------------------------------------- /lib/RAL/FileSystem.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FileSystem API wrapper. Makes extensive use of 3 | * the FileSystem code from Eric Bidelman. 4 | * 5 | * @see http://www.html5rocks.com/en/tutorials/file/filesystem/ 6 | */ 7 | RAL.FileSystem = (function() { 8 | 9 | var ready = false, 10 | readyListeners = [], 11 | root = null, 12 | callbacks = { 13 | 14 | /** 15 | * Generic error handler, simply warns the user 16 | */ 17 | onError: function(e) { 18 | var msg = ''; 19 | 20 | switch (e.code) { 21 | case FileError.QUOTA_EXCEEDED_ERR: 22 | msg = 'QUOTA_EXCEEDED_ERR'; 23 | break; 24 | case FileError.NOT_FOUND_ERR: 25 | msg = 'NOT_FOUND_ERR'; 26 | break; 27 | case FileError.SECURITY_ERR: 28 | msg = 'SECURITY_ERR'; 29 | break; 30 | case FileError.INVALID_MODIFICATION_ERR: 31 | msg = 'INVALID_MODIFICATION_ERR'; 32 | break; 33 | case FileError.INVALID_STATE_ERR: 34 | msg = 'INVALID_STATE_ERR'; 35 | break; 36 | default: 37 | msg = 'Unknown Error'; 38 | break; 39 | } 40 | 41 | console.error('Error: ' + msg, e); 42 | }, 43 | 44 | /** 45 | * Callback for once the file system has been fired up. 46 | * Informs any listeners waiting on it. 47 | */ 48 | onInitialised: function(fs) { 49 | root = fs.root; 50 | ready = true; 51 | 52 | if(readyListeners.length) { 53 | var listener = readyListeners.length; 54 | while(listener--) { 55 | readyListeners[listener](); 56 | } 57 | } 58 | } 59 | }; 60 | 61 | /** 62 | * Determines if the file system is ready for use. 63 | * @returns {boolean} If the file system is ready. 64 | */ 65 | function isReady() { 66 | return ready; 67 | } 68 | 69 | /** 70 | * Registers a listener for when the file system is 71 | * ready for interaction. 72 | * @param {Function} listener The listener function. 73 | */ 74 | function registerOnReady(listener) { 75 | readyListeners.push(listener); 76 | } 77 | 78 | /** 79 | * Gets the internal file system URL for a stored file 80 | * @param {string} filePath The original URL of the asset. 81 | * @param {Function} callbackSuccess Callback for successful path retrieval. 82 | * @param {Function} callbackFail Callback for failed path retrieval. 83 | */ 84 | function getPath(filePath, callbackSuccess, callbackFail) { 85 | if (ready) { 86 | 87 | filePath = RAL.Sanitiser.cleanURL(filePath); 88 | 89 | root.getFile(filePath, {}, function(fileEntry) { 90 | callbackSuccess(fileEntry.toURL()); 91 | }, callbackFail); 92 | } 93 | } 94 | 95 | /** 96 | * Gets the file data as text for a stored file 97 | * @param {string} filePath The original URL of the asset. 98 | * @param {Function} callbackSuccess Callback for successful path retrieval. 99 | * @param {Function} callbackFail Callback for failed path retrieval. 100 | */ 101 | function getDataAsText(filePath, callbackSuccess, callbackFail) { 102 | 103 | if (ready) { 104 | 105 | filePath = RAL.Sanitiser.cleanURL(filePath); 106 | 107 | root.getFile(filePath, {}, function(fileEntry) { 108 | 109 | fileEntry.file(function(file) { 110 | var reader = new FileReader(); 111 | reader.onloadend = function(evt) { 112 | callbackSuccess(this.result); 113 | }; 114 | 115 | reader.readAsText(file); 116 | }); 117 | 118 | }, callbackFail); 119 | } 120 | } 121 | 122 | /** 123 | * Puts the file data in the file system. 124 | * @param {string} filePath The original URL of the asset. 125 | * @param {Blob} fileData The file data blob to store. 126 | * @param {Function} callback Callback for file storage. 127 | */ 128 | function set(filePath, fileData, callback) { 129 | 130 | if(ready) { 131 | 132 | filePath = RAL.Sanitiser.cleanURL(filePath); 133 | 134 | var dirPath = filePath.split("/"); 135 | dirPath.pop(); 136 | 137 | // create the directories all the way 138 | // down to the path 139 | createDir(root, dirPath, function() { 140 | 141 | // now get a reference to our file, create it 142 | // if necessary 143 | root.getFile(filePath, {create: true}, function(fileEntry) { 144 | 145 | // create a writer on the file reference 146 | fileEntry.createWriter(function(fileWriter) { 147 | 148 | // catch on file ends 149 | fileWriter.onwriteend = function(e) { 150 | 151 | // update the writeend so when we have 152 | // truncated the file data we call the callback 153 | fileWriter.onwriteend = function(e) { 154 | callback(fileEntry.toURL()); 155 | }; 156 | 157 | // now truncate the file contents 158 | // for when we overwrite with a smaller file 159 | fileWriter.truncate(fileData.size); 160 | }; 161 | 162 | // warn on write fails but right now don't bail 163 | fileWriter.onerror = function(e) { 164 | console.warn('Write failed: ' + e.toString()); 165 | }; 166 | 167 | // start writing 168 | fileWriter.write(fileData); 169 | 170 | }, callbacks.onError); 171 | 172 | }, callbacks.onError); 173 | }); 174 | } 175 | } 176 | 177 | /** 178 | * Recursively creates the directories in a path. 179 | * @param {DirectoryEntry} rootDirEntry The base directory for this call. 180 | * @param {Array.} dirs The subdirectories in this path. 181 | * @param {Function} onCreated The callback function to use when all 182 | * directories have been created. 183 | */ 184 | function createDir(rootDirEntry, dirs, onCreated) { 185 | 186 | // remove any empty or dot dirs 187 | if(dirs[0] === '.' || dirs[0] === '') { 188 | dirs = dirs.slice(1); 189 | } 190 | 191 | // on empty call this done 192 | if(!dirs.length) { 193 | onCreated(); 194 | } else { 195 | 196 | // create the subdirectory and recursively call 197 | rootDirEntry.getDirectory(dirs[0], {create: true}, function(dirEntry) { 198 | if (dirs.length) { 199 | createDir(dirEntry, dirs.slice(1), onCreated); 200 | } 201 | }, callbacks.onError); 202 | } 203 | } 204 | 205 | /** 206 | * Removes a directory. 207 | * @param {string} path The directory to remove. 208 | * @param {Function} onRemoved The callback for successful deletion. 209 | * @param {Function} onCreated The callback for failed deletion. 210 | */ 211 | function removeDir(path, onRemoved, onError) { 212 | if(ready) { 213 | root.getDirectory(path, {}, function(dirEntry) { 214 | dirEntry.removeRecursively(onRemoved, callbacks.onError); 215 | }, onError || callbacks.onError); 216 | } 217 | } 218 | 219 | /** 220 | * Removes a file. 221 | * @param {string} path The file to remove. 222 | * @param {Function} onRemoved The callback for successful deletion. 223 | * @param {Function} onCreated The callback for failed deletion. 224 | */ 225 | function removeFile(path, onRemoved, onError) { 226 | if(ready) { 227 | root.getFile(path, {}, function(fileEntry) { 228 | fileEntry.remove(onRemoved, callbacks.onError); 229 | }, onError || callbacks.onError); 230 | } 231 | } 232 | 233 | /** 234 | * Initializes the file system. 235 | * @param {number} storageSize The storage size in MB. 236 | */ 237 | (function init(storageSize) { 238 | 239 | storageSize = storageSize || 10; 240 | 241 | window.requestFileSystem = window.requestFileSystem || 242 | window.webkitRequestFileSystem; 243 | 244 | window.resolveLocalFileSystemURL = window.resolveLocalFileSystemURL || 245 | window.webkitResolveLocalFileSystemURL; 246 | 247 | if(!!window.requestFileSystem) { 248 | window.requestFileSystem( 249 | window.TEMPORARY, 250 | storageSize * 1024 * 1024, 251 | callbacks.onInitialised, 252 | callbacks.onError); 253 | } else { 254 | 255 | } 256 | })(); 257 | 258 | return { 259 | isReady: isReady, 260 | registerOnReady: registerOnReady, 261 | getPath: getPath, 262 | getDataAsText: getDataAsText, 263 | set: set, 264 | removeFile: removeFile, 265 | removeDir: removeDir 266 | }; 267 | })(); 268 | -------------------------------------------------------------------------------- /lib/RAL/Heap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple max-heap for a priority queue. 3 | */ 4 | RAL.Heap = function() { 5 | this.items = []; 6 | }; 7 | 8 | RAL.Heap.prototype = { 9 | 10 | /** 11 | * Gets the next priority value based on the head's priority. 12 | */ 13 | getNextHighestPriority: function() { 14 | var priority = 1; 15 | if(this.items[0]) { 16 | priority = this.items[0].priority + 1; 17 | } 18 | return priority; 19 | }, 20 | 21 | /** 22 | * Provides the index of the parent. 23 | * 24 | * @param {number} index The start position. 25 | */ 26 | parentIndex: function(index) { 27 | return Math.floor(index * 0.5); 28 | }, 29 | 30 | /** 31 | * Provides the index of the left child. 32 | * 33 | * @param {number} index The start position. 34 | */ 35 | leftChildIndex: function(index) { 36 | return index * 2; 37 | }, 38 | 39 | /** 40 | * Provides the index of the right child. 41 | * 42 | * @param {number} index The start position. 43 | */ 44 | rightChildIndex: function(index) { 45 | return (index * 2) + 1; 46 | }, 47 | 48 | /** 49 | * Gets the value from a specific position 50 | * in the heap. 51 | * 52 | * @param {number} index The position of the element. 53 | */ 54 | get: function(index) { 55 | var value = null; 56 | if(index >= 1 && this.items[index - 1]) { 57 | value = this.items[index - 1]; 58 | } 59 | return value; 60 | }, 61 | 62 | /** 63 | * Sets a value in the heap. 64 | * 65 | * @param {number} index The position in the heap. 66 | * @param {number} value The value to set. 67 | */ 68 | set: function(index, value) { 69 | this.items[index - 1] = value; 70 | }, 71 | 72 | /** 73 | * Swaps two values in the heap. 74 | * 75 | * @param {number} indexA Index of the first item to be swapped. 76 | * @param {number} indexB Index of the second item to be swapped. 77 | */ 78 | swap: function(indexA, indexB) { 79 | var temp = this.get(indexA); 80 | this.set(indexA, this.get(indexB)); 81 | this.set(indexB, temp); 82 | }, 83 | 84 | /** 85 | * Sends a value up heap. The item is compared 86 | * to its parent item. If its value is greater 87 | * then it's swapped and the process is repeated. 88 | * 89 | * @param {number} startIndex The start position for the operation. 90 | */ 91 | upHeap: function(startIndex) { 92 | 93 | var startValue = null, 94 | parentValue = null, 95 | parentIndex = null, 96 | switched = false; 97 | 98 | do { 99 | switched = false; 100 | parentIndex = this.parentIndex(startIndex); 101 | startValue = this.get(startIndex); 102 | parentValue = this.get(parentIndex); 103 | switched = parentValue !== null && 104 | startValue.priority > parentValue.priority; 105 | 106 | if(switched) { 107 | this.swap(startIndex, parentIndex); 108 | startIndex = parentIndex; 109 | } 110 | 111 | } while(switched); 112 | }, 113 | 114 | /** 115 | * Sends a value down heap. The item is compared 116 | * to its two children item. If its value is less 117 | * then it's swapped with the highest value child 118 | * and the process is repeated. 119 | * 120 | * @param {number} startIndex The start position for the operation. 121 | */ 122 | downHeap: function(startIndex) { 123 | 124 | var startValue = null, 125 | leftChildValue = null, 126 | rightChildValue = null, 127 | leftChildIndex = null, 128 | rightChildIndex = null, 129 | switchValue = null, 130 | switched = false; 131 | 132 | do { 133 | 134 | switched = false; 135 | leftChildIndex = this.leftChildIndex(startIndex); 136 | rightChildIndex = this.rightChildIndex(startIndex); 137 | 138 | startValue = this.get(startIndex) && this.get(startIndex).priority; 139 | leftChildValue = this.get(leftChildIndex) && this.get(leftChildIndex).priority; 140 | rightChildValue = this.get(rightChildIndex) && this.get(rightChildIndex).priority; 141 | 142 | if(leftChildValue === null) { 143 | leftChildValue = Number.NEGATIVE_INFINITY; 144 | } 145 | if(rightChildValue === null) { 146 | rightChildValue = Number.NEGATIVE_INFINITY; 147 | } 148 | 149 | switchValue = Math.max(leftChildValue, rightChildValue); 150 | 151 | if(startValue < switchValue) { 152 | 153 | if(rightChildValue === switchValue) { 154 | this.swap(startIndex, rightChildIndex); 155 | startIndex = rightChildIndex; 156 | } else { 157 | this.swap(startIndex, leftChildIndex); 158 | startIndex = leftChildIndex; 159 | } 160 | 161 | switched = true; 162 | } 163 | 164 | } while(switched); 165 | 166 | }, 167 | 168 | /** 169 | * Adds a value to the heap. For now this is just 170 | * numbers but a comparator function could be used 171 | * for more complex comparisons. 172 | * 173 | * @param {number} value The value to be added to the heap. 174 | */ 175 | add: function(value) { 176 | this.items.push(value); 177 | this.upHeap(this.items.length); 178 | }, 179 | 180 | /** 181 | * Removes the head of the heap. 182 | */ 183 | remove: function() { 184 | var value = null; 185 | 186 | if(this.items.length) { 187 | // swap with the last child 188 | // in the heap 189 | this.swap(1, this.items.length); 190 | 191 | // grab the value and truncate 192 | // the item array 193 | value = this.get(this.items.length); 194 | this.items.length -= 1; 195 | 196 | // push the swapped item 197 | // down the heap to wherever it needs 198 | // to sit 199 | this.downHeap(1); 200 | } 201 | 202 | return value; 203 | } 204 | }; 205 | -------------------------------------------------------------------------------- /lib/RAL/Image.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a remote image. 3 | * @param {object} options The configuration options. 4 | */ 5 | RAL.RemoteImage = function(options) { 6 | 7 | // make sure to override the prototype 8 | // refs with the ones for this instance 9 | RAL.RemoteFile.call(this); 10 | 11 | options = options || {}; 12 | 13 | this.element = options.element || document.createElement('img'); 14 | this.src = this.element.dataset.src || options.src; 15 | this.width = this.element.width || options.width || null; 16 | this.height = this.element.height || options.height || null; 17 | this.placeholder = this.element.dataset.placeholder || null; 18 | this.priority = options.priority || 0; 19 | 20 | // attach on specific events for images 21 | this.addEventListener('remoteloadstart', this.showPlaceholder.bind(this)); 22 | this.addEventListener('loaded', this.showImage.bind(this)); 23 | 24 | if(typeof options.autoLoad !== "undefined") { 25 | this.autoLoad = options.autoLoad; 26 | } 27 | 28 | if(typeof options.ignoreCacheHeaders !== "undefined") { 29 | this.ignoreCacheHeaders = options.ignoreCacheHeaders; 30 | } 31 | 32 | // if there is a TTL use that instead of the default 33 | if(this.ignoreCacheHeaders && typeof this.timeToLive !== "undefined") { 34 | this.timeToLive = options.timeToLive; 35 | } 36 | 37 | if(this.autoLoad) { 38 | this.load(); 39 | } else { 40 | this.showPlaceholder(); 41 | } 42 | 43 | }; 44 | 45 | RAL.RemoteImage.prototype = new RAL.RemoteFile(); 46 | 47 | /** 48 | * Shows a placeholder image while we load in the main image 49 | */ 50 | RAL.RemoteImage.prototype.showPlaceholder = function() { 51 | 52 | if(this.placeholder !== null) { 53 | 54 | // add in transitions 55 | this.element.style['-webkit-transition'] = "background-image 0.5s ease-out"; 56 | this.showImage({data:this.placeholder}); 57 | } 58 | }; 59 | 60 | /** 61 | * Shows the image. 62 | * @param {event} evt The loaded event for the asset. 63 | */ 64 | RAL.RemoteImage.prototype.showImage = function(evt) { 65 | 66 | var imageSrc = evt.data; 67 | var image = new Image(); 68 | var revoke = (function(imageSrc) { 69 | this.wURL.revokeObjectURL(imageSrc); 70 | }).bind(this, imageSrc); 71 | 72 | var imageLoaded = function() { 73 | 74 | // infer the size from the image 75 | var width = this.width || image.naturalWidth; 76 | var height = this.height || image.naturalHeight; 77 | 78 | this.element.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; 79 | this.element.style.width = width + 'px'; 80 | this.element.style.height = height + 'px'; 81 | this.element.style.backgroundImage = 'url(' + imageSrc + ')'; 82 | this.element.style.backgroundSize = width + 'px ' + height + 'px'; 83 | 84 | // if it's a blob make sure we go ahead 85 | // and revoke it properly after a short timeout 86 | if(/blob:/.test(imageSrc)) { 87 | setTimeout(revoke, 100); 88 | } 89 | }; 90 | 91 | image.onload = imageLoaded.bind(this); 92 | image.src = imageSrc; 93 | }; 94 | -------------------------------------------------------------------------------- /lib/RAL/Loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Loads the remote files 3 | */ 4 | RAL.Loader = (function() { 5 | 6 | var callbacks = { 7 | 8 | /** 9 | * Callback for loaded files. 10 | * @param {string} source The remote file's URL. 11 | * @param {Function} callbackSuccess The callback for successful loading. 12 | * @param {Function} callbackError The callback for failed loading. 13 | * @param {ProgressEvent} xhrProgressEvent The XHR progress event. 14 | */ 15 | onLoad: function(source, callbackSuccess, callbackError, xhrProgressEvent) { 16 | 17 | // we have the file details 18 | // so now we need to wrap the file up, including 19 | // the caching information to return back 20 | var xhr = xhrProgressEvent.target; 21 | var fileData = xhr.response; 22 | var fileInfo = RAL.CacheParser.parse(xhr.getAllResponseHeaders()); 23 | 24 | if(xhr.readyState === 4) { 25 | if(xhr.status === 200) { 26 | callbackSuccess(fileData, fileInfo); 27 | } else { 28 | callbackError(xhrProgressEvent); 29 | } 30 | } 31 | }, 32 | 33 | /** 34 | * Generic callback for erroring loads. Simply passes the progres event 35 | * through to the assigned callback. 36 | * @param {Function} callback The callback for failed loading. 37 | * @param {ProgressEvent} xhrProgressEvent The XHR progress event. 38 | */ 39 | onError: function(callback, xhrProgressEvent) { 40 | callback(xhrProgressEvent); 41 | } 42 | }; 43 | 44 | /** 45 | * Aborts an in-flight XHR and reschedules it. 46 | * @param {XMLHttpRequest} xhr The XHR to abort. 47 | * @param {Function} callbackSuccess The callback for successful loading. 48 | * @param {Function} callbackError The callback for failed loading. 49 | * @param {ProgressEvent} xhrProgressEvent The XHR progress event. 50 | */ 51 | function abort(xhr, source, callbackSuccess, callbackFail) { 52 | 53 | // kill the current request 54 | xhr.abort(); 55 | 56 | // run it again, which will cause us to schedule up 57 | this.load(source, callbackSuccess, callbackFail); 58 | } 59 | 60 | /** 61 | * Aborts an in-flight XHR and reschedules it. 62 | * @param {XMLHttpRequest} xhr The XHR to abort. 63 | * @param {string} type The response type for the XHR, e.g. 'blob' 64 | * @param {Function} callbackSuccess The callback for successful loading. 65 | * @param {Function} callbackError The callback for failed loading. 66 | */ 67 | function load(source, type, callbackSuccess, callbackFail) { 68 | 69 | // check we're online, or schedule the load 70 | if(RAL.NetworkMonitor.isOnline()) { 71 | 72 | // attempt to load the file 73 | var xhr = new XMLHttpRequest(); 74 | 75 | xhr.onerror = callbacks.onError.bind(this, callbackFail); 76 | xhr.onload = callbacks.onLoad.bind(this, source, callbackSuccess, callbackFail); 77 | xhr.open('GET', source, true); 78 | xhr.responseType = type; 79 | xhr.send(); 80 | 81 | // register our interest in the connection 82 | // being cut. If that happens we will reschedule. 83 | RAL.NetworkMonitor.registerForOffline( 84 | abort.bind(this, 85 | xhr, 86 | source, 87 | callbackSuccess, 88 | callbackFail)); 89 | 90 | } else { 91 | 92 | // We are offline so register our interest in the 93 | // connection being restored. 94 | RAL.NetworkMonitor.registerForOnline( 95 | load.bind(this, 96 | source, 97 | callbackSuccess, 98 | callbackFail)); 99 | 100 | } 101 | } 102 | 103 | return { 104 | load: load 105 | }; 106 | 107 | })(); 108 | -------------------------------------------------------------------------------- /lib/RAL/Namespace.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace 3 | */ 4 | var RAL = {debug:false}; 5 | -------------------------------------------------------------------------------- /lib/RAL/NetworkMonitor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tracks the online / offline nature of the 3 | * browser so we can abort and reschedule any 4 | * in-flight requests. 5 | */ 6 | RAL.NetworkMonitor = (function() { 7 | 8 | var onlineListeners = []; 9 | var offlineListeners = []; 10 | 11 | /* Register for online events */ 12 | window.addEventListener("online", function() { 13 | 14 | // go through each listener, pop it 15 | // off and call it 16 | var listenerCount = onlineListeners.length, 17 | listener = null; 18 | while(listenerCount--) { 19 | listener = onlineListeners.pop(); 20 | listener(); 21 | } 22 | }); 23 | 24 | /* Register for offline events */ 25 | window.addEventListener("offline", function() { 26 | 27 | // go through each listener, pop it 28 | // off and call it 29 | var listenerCount = offlineListeners.length, 30 | listener = null; 31 | while(listenerCount--) { 32 | listener = offlineListeners.pop(); 33 | listener(); 34 | } 35 | }); 36 | 37 | /** 38 | * Appends a function for notification 39 | * when the browser comes back online. 40 | * @param callback The callback function for online notifications. 41 | */ 42 | function registerForOnline(callback) { 43 | onlineListeners.push(callback); 44 | } 45 | 46 | /** 47 | * Appends a function for notification 48 | * when the browser drops offline. 49 | * @param callback The callback function for offline notifications. 50 | */ 51 | function registerForOffline(callback) { 52 | offlineListeners.push(callback); 53 | } 54 | 55 | /** 56 | * Simple wrapper for whether the browser 57 | * is online or offline. 58 | * @returns {boolean} The online / offline state of the browser. 59 | */ 60 | function isOnline() { 61 | return window.navigator.onLine; 62 | } 63 | 64 | return { 65 | registerForOnline: registerForOnline, 66 | registerForOffline: registerForOffline, 67 | isOnline: isOnline 68 | }; 69 | 70 | })(); 71 | -------------------------------------------------------------------------------- /lib/RAL/Queue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the load queue for assets. 3 | */ 4 | RAL.Queue = (function() { 5 | 6 | var heap = new RAL.Heap(), 7 | connections = 0, 8 | maxConnections = 6, 9 | callbacks = { 10 | 11 | /** 12 | * Callback for when a file in the queue has been loaded 13 | */ 14 | onFileLoaded: function() { 15 | if(RAL.debug) { 16 | console.log("[Connections: " + connections + "] - File loaded"); 17 | } 18 | connections--; 19 | start(); 20 | } 21 | }; 22 | 23 | /** 24 | * Gets the queue's next priority value. 25 | */ 26 | function getNextHighestPriority() { 27 | return heap.getNextHighestPriority(); 28 | } 29 | 30 | /** 31 | * Sets the queue's maximum number of concurrent requests. 32 | * @param {number} newMaxConnections The maximum number of in-flight requests. 33 | */ 34 | function setMaxConnections(newMaxConnections) { 35 | maxConnections = newMaxConnections; 36 | } 37 | 38 | /** 39 | * Adds a file to the queue. 40 | * @param {RAL.REmoteFile} remoteFile The file to enqueue. 41 | * @param {boolean} startGetting Whether or not to try and get immediately. 42 | */ 43 | function add(remoteFile, startGetting) { 44 | 45 | // ensure we have a priority, and 46 | // go with a LIFO approach 47 | // - thx courage@ 48 | if(typeof remoteFile.priority === "undefined") { 49 | remoteFile.priority = heap.getNextHighestPriority(); 50 | } 51 | 52 | heap.add(remoteFile); 53 | 54 | if(startGetting) { 55 | start(); 56 | } 57 | } 58 | 59 | /** 60 | * Start requesting items from the queue. 61 | */ 62 | function start() { 63 | while(connections < maxConnections) { 64 | nextFile = heap.remove(); 65 | 66 | if(nextFile !== null) { 67 | nextFile.addEventListener('loaded', callbacks.onFileLoaded); 68 | nextFile.load(); 69 | if(RAL.debug) { 70 | console.log("[Connections: " + connections + "] - Loading " + nextFile.src); 71 | } 72 | connections++; 73 | } else { 74 | if(RAL.debug) { 75 | console.log("[Connections: " + connections + "] - No more images queued"); 76 | } 77 | break; 78 | } 79 | } 80 | } 81 | 82 | /** 83 | * Clears the queue. 84 | */ 85 | function clear() { 86 | heap.clear(); 87 | } 88 | 89 | return { 90 | getNextHighestPriority: getNextHighestPriority, 91 | setMaxConnections: setMaxConnections, 92 | add: add, 93 | clear: clear, 94 | start: start 95 | }; 96 | 97 | })(); 98 | -------------------------------------------------------------------------------- /lib/RAL/RemoteFile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Prototype for all remote files 3 | */ 4 | RAL.RemoteFile = function() {}; 5 | 6 | RAL.RemoteFile.prototype = { 7 | 8 | /** 9 | * The internal element used for dispatching events. 10 | * @type {Element} 11 | */ 12 | element: null, 13 | 14 | /** 15 | * The source URL of the remote file. 16 | * @type {string} 17 | */ 18 | src: null, 19 | 20 | /** 21 | * Whether or not this file should autoload. 22 | * @type {boolean} 23 | */ 24 | autoLoad: false, 25 | 26 | /** 27 | * Whether or not this file should ignore the server's cache headers. 28 | * @type {boolean} 29 | */ 30 | ignoreCacheHeaders: false, 31 | 32 | /** 33 | * The default TTL (in ms) should no expire header be provided. 34 | * @type {number} 35 | * @default 1209600000, 14 days 36 | */ 37 | timeToLive: 14 * 24 * 60 * 60 * 1000, 38 | 39 | /** 40 | * The file's priority in the queue. 41 | * @type {number} 42 | * @default 0 43 | */ 44 | priority: 0, 45 | 46 | /** 47 | * Whether or not the file has loaded. 48 | * @type {boolean} 49 | */ 50 | loaded: false, 51 | 52 | /** 53 | * A reference to the URL object. 54 | * @type {Function} 55 | * @private 56 | */ 57 | wURL: window.URL || window.webkitURL, 58 | 59 | callbacks: { 60 | 61 | /** 62 | * Callback for errors with caching this file, e.g. when the headers 63 | * from the server imply as such. 64 | * @param {object} fileInfo The file information including headers and 65 | * warnings from RAL.CacheParser 66 | */ 67 | onCacheError: function(fileInfo) { 68 | fileInfo.src = this.src; 69 | this.sendEvent('cacheerror', fileInfo); 70 | }, 71 | 72 | /** 73 | * Callback for when the remote file has loaded. 74 | * @param {Blob} fileData The file's data. 75 | * @param {object} fileInfo The file information including headers and 76 | * any warnings from RAL.CacheParser 77 | */ 78 | onRemoteFileLoaded: function(fileData, fileInfo) { 79 | 80 | // check the ignorance status 81 | if(this.ignoreCacheHeaders) { 82 | 83 | fileInfo.cacheable = true; 84 | fileInfo.useBy += this.timeToLive; 85 | } 86 | 87 | // check if the file can be cached and, if so, 88 | // go ahead and store it in the file system 89 | if(fileInfo.cacheable) { 90 | 91 | RAL.FileSystem.set(this.src, fileData, 92 | this.callbacks.onFileSystemSet.bind(this, fileInfo)); 93 | 94 | } else { 95 | 96 | var dataURL = this.wURL.createObjectURL(fileData); 97 | this.callbacks.onLocalFileLoaded.call(this, dataURL); 98 | this.callbacks.onCacheError.call(this, fileInfo); 99 | 100 | } 101 | 102 | this.sendEvent('remoteloaded', fileInfo); 103 | 104 | }, 105 | 106 | /** 107 | * Called when the remote file is unavailable. 108 | */ 109 | onRemoteFileUnavailable: function() { 110 | this.sendEvent('remoteunavailable'); 111 | }, 112 | 113 | /** 114 | * Called when the local file has been loaded. Since the 115 | * remote file will be stored as a local file, this should 116 | * always be fired, but it may be preceded by a 'remoteloaded' 117 | * event beforehand. 118 | * @param {string} filePath The local file system path of the file. 119 | */ 120 | onLocalFileLoaded: function(filePath) { 121 | this.loaded = true; 122 | this.sendEvent('loaded', filePath); 123 | }, 124 | 125 | /** 126 | * Called when the locally stored version of the file is 127 | * not available, e.g. after a file system purge. This 128 | * automatically requests the remote file again and shows 129 | * a placeholder. 130 | */ 131 | onLocalFileUnavailable: function() { 132 | this.showPlaceholder(); 133 | this.loadFromRemote(); 134 | this.sendEvent('localunavailable'); 135 | }, 136 | 137 | /** 138 | * Callback for once the file has been stored in the file system. Stores 139 | * the file in the global manifest. 140 | * @param {object} fileInfo The source URL and headers for the remote file. 141 | */ 142 | onFileSystemSet: function(fileInfo) { 143 | RAL.FileManifest.set(this.src, fileInfo, 144 | this.callbacks.onFileManifestSet.bind(this)); 145 | }, 146 | 147 | /** 148 | * Callback for once the file has been stored in the file manifest. 149 | */ 150 | onFileManifestSet: function() { 151 | // we stored the file, we should reattempt 152 | // the load operation 153 | this.load(); 154 | }, 155 | 156 | /** 157 | * Callback for once the file has been retrieved from the file manifest. 158 | * @param {object} fileInfo The file's information from the manifest. 159 | */ 160 | onFileManifestGet: function(fileInfo) { 161 | 162 | var time = Date.now(); 163 | 164 | // see whether we have the file 165 | if(fileInfo !== null) { 166 | 167 | // see if it is still in date or we are offline 168 | if(fileInfo.useBy > time || !RAL.NetworkMonitor.isOnline()) { 169 | 170 | // go and grab it 171 | RAL.FileSystem.getPath(this.src, 172 | this.callbacks.onLocalFileLoaded.bind(this), 173 | this.callbacks.onLocalFileUnavailable.bind(this)); 174 | 175 | } else { 176 | // it's out of date, so now we 177 | // need to get the remote file 178 | this.loadFromRemote(); 179 | } 180 | 181 | } else { 182 | // we do not have the file, go 183 | // and get it 184 | this.loadFromRemote(); 185 | } 186 | } 187 | }, 188 | 189 | /** 190 | * Helper function which uses the internal DOM element 191 | * to create and dispatch events for the remote file. 192 | * @see https://developer.mozilla.org/en-US/docs/DOM/document.createEvent 193 | * @param {string} evtName The event name. 194 | * @param {*} data The event data. 195 | */ 196 | sendEvent: function(evtName, data) { 197 | 198 | this.checkForElement(); 199 | 200 | var evt = document.createEvent("Event"); 201 | evt.initEvent(evtName, true, true); 202 | if(!!data) { 203 | evt.data = data; 204 | } 205 | this.element.dispatchEvent(evt); 206 | 207 | }, 208 | 209 | /** 210 | * Loads a file from a remote source. 211 | */ 212 | loadFromRemote: function() { 213 | 214 | RAL.Loader.load(this.src, 215 | 'blob', 216 | this.callbacks.onRemoteFileLoaded.bind(this), 217 | this.callbacks.onRemoteFileUnavailable.bind(this)); 218 | 219 | this.sendEvent('remoteloadstart'); 220 | }, 221 | 222 | /** 223 | * Attempts to load a file from the local file system. 224 | */ 225 | load: function() { 226 | // check the "manifest" to see if 227 | // we should already have this file 228 | RAL.FileManifest.get(this.src, this.callbacks.onFileManifestGet.bind(this)); 229 | 230 | }, 231 | 232 | /** 233 | * Checks for and creates an element for handling events. 234 | */ 235 | checkForElement: function() { 236 | if(!this.element) { 237 | // create a placeholder element 238 | // in lieu of having an actual one. Likely 239 | // to be the case where someone has created 240 | // a RemoteFile directly 241 | this.element = document.createElement('span'); 242 | } 243 | }, 244 | 245 | /** 246 | * Wrapper for event listening on the internal element. 247 | * @param {string} evtName The event name to listen for. 248 | * @param {Function} callback The event callback. 249 | * @param {boolean} useCapture Use capture for the event. 250 | */ 251 | addEventListener: function(evtName, callback, useCapture) { 252 | this.checkForElement(); 253 | this.element.addEventListener(evtName, callback, useCapture); 254 | } 255 | }; 256 | -------------------------------------------------------------------------------- /lib/RAL/Sanitiser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sanitises a file path. 3 | */ 4 | RAL.Sanitiser = (function() { 5 | 6 | /** 7 | * Cleans and removes the protocol from the URL. 8 | * @param {string} url The URL to clean. 9 | */ 10 | function cleanURL(url) { 11 | return url.replace(/.*?:\/\//, '', url); 12 | } 13 | 14 | return { 15 | cleanURL: cleanURL 16 | }; 17 | 18 | })(); 19 | -------------------------------------------------------------------------------- /lib/ral.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"ral.map","sources":["Namespace.js","Heap.js","Sanitiser.js","CacheParser.js","FileSystem.js","FileManifest.js","RemoteFile.js","Image.js","Loader.js","NetworkMonitor.js","Queue.js"],"names":["RAL","debug","Heap","this","items","prototype","getNextHighestPriority","priority","parentIndex","index","Math","floor","leftChildIndex","rightChildIndex","get","value","set","swap","indexA","indexB","temp","upHeap","startIndex","startValue","parentValue","switched","downHeap","leftChildValue","rightChildValue","switchValue","Number","NEGATIVE_INFINITY","max","add","push","length","remove","Sanitiser","cleanURL","url","replace","CacheParser","parse","headers","rMaxAge","rNoCache","rNoStore","rMustRevalidate","rExpiry","warnings","expires","exec","useBy","Date","now","test","cacheable","FileSystem","isReady","ready","registerOnReady","listener","readyListeners","getPath","filePath","callbackSuccess","callbackFail","root","getFile","fileEntry","toURL","getDataAsText","file","reader","FileReader","onloadend","result","readAsText","fileData","callback","dirPath","split","pop","createDir","create","createWriter","fileWriter","onwriteend","truncate","size","onerror","e","console","warn","toString","write","callbacks","onError","rootDirEntry","dirs","onCreated","slice","getDirectory","dirEntry","removeDir","path","onRemoved","removeRecursively","removeFile","msg","code","FileError","QUOTA_EXCEEDED_ERR","NOT_FOUND_ERR","SECURITY_ERR","INVALID_MODIFICATION_ERR","INVALID_STATE_ERR","error","onInitialised","fs","storageSize","window","requestFileSystem","webkitRequestFileSystem","resolveLocalFileSystemURL","webkitResolveLocalFileSystemURL","TEMPORARY","FileManifest","src","cleanSrc","fileInfo","manifest","info","save","reset","onManifestUnavailable","onManifestLoaded","JSON","blob","Blob","stringify","type","init","RemoteFile","element","autoLoad","ignoreCacheHeaders","timeToLive","loaded","wURL","URL","webkitURL","onCacheError","sendEvent","onRemoteFileLoaded","onFileSystemSet","bind","dataURL","createObjectURL","onLocalFileLoaded","call","onRemoteFileUnavailable","onLocalFileUnavailable","showPlaceholder","loadFromRemote","onFileManifestSet","load","onFileManifestGet","time","NetworkMonitor","isOnline","evtName","data","checkForElement","evt","document","createEvent","initEvent","dispatchEvent","Loader","createElement","addEventListener","useCapture","RemoteImage","options","dataset","width","height","placeholder","showImage","style","imageSrc","image","Image","revoke","revokeObjectURL","imageLoaded","naturalWidth","naturalHeight","backgroundImage","backgroundSize","setTimeout","onload","abort","xhr","source","XMLHttpRequest","responseType","onLoad","open","send","registerForOffline","registerForOnline","callbackError","xhrProgressEvent","target","response","getAllResponseHeaders","readyState","status","onlineListeners","offlineListeners","navigator","onLine","listenerCount","Queue","heap","setMaxConnections","newMaxConnections","maxConnections","remoteFile","startGetting","start","connections","nextFile","log","onFileLoaded","clear"],"mappings":"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","sourceRoot":"/lib/RAL/"} -------------------------------------------------------------------------------- /lib/ral.min.js: -------------------------------------------------------------------------------- 1 | var RAL={debug:!1};RAL.Heap=function(){this.items=[]},RAL.Heap.prototype={getNextHighestPriority:function(){var e=1;return this.items[0]&&(e=this.items[0].priority+1),e},parentIndex:function(e){return Math.floor(.5*e)},leftChildIndex:function(e){return 2*e},rightChildIndex:function(e){return 2*e+1},get:function(e){var t=null;return e>=1&&this.items[e-1]&&(t=this.items[e-1]),t},set:function(e,t){this.items[e-1]=t},swap:function(e,t){var n=this.get(e);this.set(e,this.get(t)),this.set(t,n)},upHeap:function(e){var t=null,n=null,i=null,o=!1;do o=!1,i=this.parentIndex(e),t=this.get(e),n=this.get(i),o=null!==n&&t.priority>n.priority,o&&(this.swap(e,i),e=i);while(o)},downHeap:function(e){var t=null,n=null,i=null,o=null,r=null,s=null,a=!1;do a=!1,o=this.leftChildIndex(e),r=this.rightChildIndex(e),t=this.get(e)&&this.get(e).priority,n=this.get(o)&&this.get(o).priority,i=this.get(r)&&this.get(r).priority,null===n&&(n=Number.NEGATIVE_INFINITY),null===i&&(i=Number.NEGATIVE_INFINITY),s=Math.max(n,i),s>t&&(i===s?(this.swap(e,r),e=r):(this.swap(e,o),e=o),a=!0);while(a)},add:function(e){this.items.push(e),this.upHeap(this.items.length)},remove:function(){var e=null;return this.items.length&&(this.swap(1,this.items.length),e=this.get(this.items.length),this.items.length-=1,this.downHeap(1)),e}},RAL.Sanitiser=function(){function e(e){return e.replace(/.*?:\/\//,"",e)}return{cleanURL:e}}(),RAL.CacheParser=function(){function e(e){var t=/max\-age=(\d+)/gi,n=/Cache-Control:.*?no\-cache/gi,i=/Cache-Control:.*?no\-store/gi,o=/Cache-Control:.*?must\-revalidate/gi,r=/Expires:\s(.*)/gi,s=[],a=t.exec(e),l=Date.now();return i.test(e)&&s.push("Cache-Control: no-store is set"),n.test(e)&&s.push("Cache-Control: no-cache is set"),o.test(e)&&s.push("Cache-Control: must-revalidate is set"),null!==a?l=Date.now()+1e3*a[1]:(a=r.exec(e),null!==a?l=Date.parse(a[1]):s.push("Cache-Control: max-age and Expires: headers are not set")),{headers:e,cacheable:0===s.length,useBy:l,warnings:s}}return{parse:e}}(),RAL.FileSystem=function(){function e(){return l}function t(e){c.push(e)}function n(e,t,n){l&&(e=RAL.Sanitiser.cleanURL(e),h.getFile(e,{},function(e){t(e.toURL())},n))}function i(e,t,n){l&&(e=RAL.Sanitiser.cleanURL(e),h.getFile(e,{},function(e){e.file(function(e){var n=new FileReader;n.onloadend=function(){t(this.result)},n.readAsText(e)})},n))}function o(e,t,n){if(l){e=RAL.Sanitiser.cleanURL(e);var i=e.split("/");i.pop(),r(h,i,function(){h.getFile(e,{create:!0},function(e){e.createWriter(function(i){i.onwriteend=function(){i.onwriteend=function(){n(e.toURL())},i.truncate(t.size)},i.onerror=function(e){console.warn("Write failed: "+e.toString())},i.write(t)},u.onError)},u.onError)})}}function r(e,t,n){("."===t[0]||""===t[0])&&(t=t.slice(1)),t.length?e.getDirectory(t[0],{create:!0},function(e){t.length&&r(e,t.slice(1),n)},u.onError):n()}function s(e,t,n){l&&h.getDirectory(e,{},function(e){e.removeRecursively(t,u.onError)},n||u.onError)}function a(e,t,n){l&&h.getFile(e,{},function(e){e.remove(t,u.onError)},n||u.onError)}var l=!1,c=[],h=null,u={onError:function(e){var t="";switch(e.code){case FileError.QUOTA_EXCEEDED_ERR:t="QUOTA_EXCEEDED_ERR";break;case FileError.NOT_FOUND_ERR:t="NOT_FOUND_ERR";break;case FileError.SECURITY_ERR:t="SECURITY_ERR";break;case FileError.INVALID_MODIFICATION_ERR:t="INVALID_MODIFICATION_ERR";break;case FileError.INVALID_STATE_ERR:t="INVALID_STATE_ERR";break;default:t="Unknown Error"}console.error("Error: "+t,e)},onInitialised:function(e){if(h=e.root,l=!0,c.length)for(var t=c.length;t--;)c[t]()}};return function(e){e=e||10,window.requestFileSystem=window.requestFileSystem||window.webkitRequestFileSystem,window.resolveLocalFileSystemURL=window.resolveLocalFileSystemURL||window.webkitResolveLocalFileSystemURL,window.requestFileSystem&&window.requestFileSystem(window.TEMPORARY,1024*1024*e,u.onInitialised,u.onError)}(),{isReady:e,registerOnReady:t,getPath:n,getDataAsText:i,set:o,removeFile:a,removeDir:s}}(),RAL.FileManifest=function(){function e(){return h}function t(e){u.push(e)}function n(e,t){var n=RAL.Sanitiser.cleanURL(e),i=c[n]||null;t(i)}function i(e,t,n){var i=RAL.Sanitiser.cleanURL(e);c[i]=t,a(n)}function o(){c={},a()}function r(){s("{}")}function s(e){if(h=!0,c=JSON.parse(e),u.length)for(var t=u.length;t--;)u[t]()}function a(e){var t=new Blob([JSON.stringify(c)],{type:"application/json"});RAL.FileSystem.set("manifest.json",t,function(){e&&e()})}function l(){RAL.FileSystem.getDataAsText("manifest.json",s,r)}var c=null,h=!1,u=[];return RAL.FileSystem.isReady()?l():RAL.FileSystem.registerOnReady(l),{isReady:e,registerOnReady:t,get:n,set:i,reset:o}}(),RAL.RemoteFile=function(){},RAL.RemoteFile.prototype={element:null,src:null,autoLoad:!1,ignoreCacheHeaders:!1,timeToLive:12096e5,priority:0,loaded:!1,wURL:window.URL||window.webkitURL,callbacks:{onCacheError:function(e){e.src=this.src,this.sendEvent("cacheerror",e)},onRemoteFileLoaded:function(e,t){if(this.ignoreCacheHeaders&&(t.cacheable=!0,t.useBy+=this.timeToLive),t.cacheable)RAL.FileSystem.set(this.src,e,this.callbacks.onFileSystemSet.bind(this,t));else{var n=this.wURL.createObjectURL(e);this.callbacks.onLocalFileLoaded.call(this,n),this.callbacks.onCacheError.call(this,t)}this.sendEvent("remoteloaded",t)},onRemoteFileUnavailable:function(){this.sendEvent("remoteunavailable")},onLocalFileLoaded:function(e){this.loaded=!0,this.sendEvent("loaded",e)},onLocalFileUnavailable:function(){this.showPlaceholder(),this.loadFromRemote(),this.sendEvent("localunavailable")},onFileSystemSet:function(e){RAL.FileManifest.set(this.src,e,this.callbacks.onFileManifestSet.bind(this))},onFileManifestSet:function(){this.load()},onFileManifestGet:function(e){var t=Date.now();null!==e?e.useBy>t||!RAL.NetworkMonitor.isOnline()?RAL.FileSystem.getPath(this.src,this.callbacks.onLocalFileLoaded.bind(this),this.callbacks.onLocalFileUnavailable.bind(this)):this.loadFromRemote():this.loadFromRemote()}},sendEvent:function(e,t){this.checkForElement();var n=document.createEvent("Event");n.initEvent(e,!0,!0),t&&(n.data=t),this.element.dispatchEvent(n)},loadFromRemote:function(){RAL.Loader.load(this.src,"blob",this.callbacks.onRemoteFileLoaded.bind(this),this.callbacks.onRemoteFileUnavailable.bind(this)),this.sendEvent("remoteloadstart")},load:function(){RAL.FileManifest.get(this.src,this.callbacks.onFileManifestGet.bind(this))},checkForElement:function(){this.element||(this.element=document.createElement("span"))},addEventListener:function(e,t,n){this.checkForElement(),this.element.addEventListener(e,t,n)}},RAL.RemoteImage=function(e){RAL.RemoteFile.call(this),e=e||{},this.element=e.element||document.createElement("img"),this.src=this.element.dataset.src||e.src,this.width=this.element.width||e.width||null,this.height=this.element.height||e.height||null,this.placeholder=this.element.dataset.placeholder||null,this.priority=e.priority||0,this.addEventListener("remoteloadstart",this.showPlaceholder.bind(this)),this.addEventListener("loaded",this.showImage.bind(this)),"undefined"!=typeof e.autoLoad&&(this.autoLoad=e.autoLoad),"undefined"!=typeof e.ignoreCacheHeaders&&(this.ignoreCacheHeaders=e.ignoreCacheHeaders),this.ignoreCacheHeaders&&"undefined"!=typeof this.timeToLive&&(this.timeToLive=e.timeToLive),this.autoLoad?this.load():this.showPlaceholder()},RAL.RemoteImage.prototype=new RAL.RemoteFile,RAL.RemoteImage.prototype.showPlaceholder=function(){null!==this.placeholder&&(this.element.style["-webkit-transition"]="background-image 0.5s ease-out",this.showImage({data:this.placeholder}))},RAL.RemoteImage.prototype.showImage=function(e){var t=e.data,n=new Image,i=function(e){this.wURL.revokeObjectURL(e)}.bind(this,t),o=function(){var e=this.width||n.naturalWidth,o=this.height||n.naturalHeight;this.element.src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",this.element.style.width=e+"px",this.element.style.height=o+"px",this.element.style.backgroundImage="url("+t+")",this.element.style.backgroundSize=e+"px "+o+"px",/blob:/.test(t)&&setTimeout(i,100)};n.onload=o.bind(this),n.src=t},RAL.Loader=function(){function e(e,t,n,i){e.abort(),this.load(t,n,i)}function t(i,o,r,s){if(RAL.NetworkMonitor.isOnline()){var a=new XMLHttpRequest;a.responseType=o,a.onerror=n.onError.bind(this,s),a.onload=n.onLoad.bind(this,i,r,s),a.open("GET",i,!0),a.send(),RAL.NetworkMonitor.registerForOffline(e.bind(this,a,i,r,s))}else RAL.NetworkMonitor.registerForOnline(t.bind(this,i,r,s))}var n={onLoad:function(e,t,n,i){var o=i.target,r=o.response,s=RAL.CacheParser.parse(o.getAllResponseHeaders());4===o.readyState&&(200===o.status?t(r,s):n(i))},onError:function(e,t){e(t)}};return{load:t}}(),RAL.NetworkMonitor=function(){function e(e){i.push(e)}function t(e){o.push(e)}function n(){return window.navigator.onLine}var i=[],o=[];return window.addEventListener("online",function(){for(var e=i.length,t=null;e--;)t=i.pop(),t()}),window.addEventListener("offline",function(){for(var e=o.length,t=null;e--;)t=o.pop(),t()}),{registerForOnline:e,registerForOffline:t,isOnline:n}}(),RAL.Queue=function(){function e(){return r.getNextHighestPriority()}function t(e){a=e}function n(e,t){"undefined"==typeof e.priority&&(e.priority=r.getNextHighestPriority()),r.add(e),t&&i()}function i(){for(;a>s;){if(nextFile=r.remove(),null===nextFile){RAL.debug&&console.log("[Connections: "+s+"] - No more images queued");break}nextFile.addEventListener("loaded",l.onFileLoaded),nextFile.load(),RAL.debug&&console.log("[Connections: "+s+"] - Loading "+nextFile.src),s++}}function o(){r.clear()}var r=new RAL.Heap,s=0,a=6,l={onFileLoaded:function(){RAL.debug&&console.log("[Connections: "+s+"] - File loaded"),s--,i()}};return{getNextHighestPriority:e,setMaxConnections:t,add:n,clear:o,start:i}}();//# sourceMappingURL=ral.map --------------------------------------------------------------------------------