]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | // here, the extra check for window['Ext'] is needed for use with cmd-test\r |
2 | // code injection. we need to make that this file will sync up with page global\r | |
3 | // scope to avoid duplicate Ext.Boot state. That check is after the initial Ext check\r | |
4 | // to allow the sandboxing template to inject an appropriate Ext var and prevent the\r | |
5 | // global detection.\r | |
6 | var Ext = Ext || window['Ext'] || {};\r | |
7 | \r | |
8 | \r | |
9 | //<editor-fold desc="Microloader">\r | |
10 | /**\r | |
11 | * @Class Ext.Microloader\r | |
12 | * @singleton\r | |
13 | */\r | |
14 | Ext.Microloader = Ext.Microloader || (function () {\r | |
15 | var Boot = Ext.Boot,\r | |
16 | //<debug>\r | |
17 | _debug = function (message) {\r | |
18 | //console.log(message);\r | |
19 | },\r | |
20 | //</debug>\r | |
21 | _warn = function (message) {\r | |
22 | console.log("[WARN] " + message);\r | |
23 | },\r | |
24 | _privatePrefix = '_sencha',\r | |
25 | _location = window.document.location,\r | |
26 | _documentUri = _location.protocol + '//' + _location.hostname + _location.pathname + _location.search,\r | |
27 | postProcessor;\r | |
28 | \r | |
29 | // Returns a unique key to a manifest file\r | |
30 | function getManifestStorageKey (url) {\r | |
31 | return _privatePrefix + '-' + _documentUri + url;\r | |
32 | }\r | |
33 | \r | |
34 | var _storage;\r | |
35 | try {\r | |
36 | _storage = window['localStorage'];\r | |
37 | } catch(ex) {\r | |
38 | // ignore\r | |
39 | }\r | |
40 | \r | |
41 | var _cache = window['applicationCache'],\r | |
42 | // Local Storage Controller\r | |
43 | LocalStorage = {\r | |
44 | clearAllPrivate: function() {\r | |
45 | if(_storage) {\r | |
46 | var i, key,\r | |
47 | removeKeys = [],\r | |
48 | ln = _storage.length;\r | |
49 | for (i = 0; i < ln; i++) {\r | |
50 | key = _storage.key(i);\r | |
51 | if (key.indexOf(_privatePrefix) === 0) {\r | |
52 | removeKeys.push(key);\r | |
53 | }\r | |
54 | }\r | |
55 | \r | |
56 | for(i in removeKeys) {\r | |
57 | //<debug>\r | |
58 | _debug("Removing "+ removeKeys[i] + " from Local Storage");\r | |
59 | //</debug>\r | |
60 | _storage.removeItem(removeKeys[i]);\r | |
61 | }\r | |
62 | }\r | |
63 | },\r | |
64 | /**\r | |
65 | * private\r | |
66 | */\r | |
67 | retrieveAsset: function (key) {\r | |
68 | try {\r | |
69 | return _storage.getItem(key);\r | |
70 | }\r | |
71 | catch (e) {\r | |
72 | // Private browsing mode\r | |
73 | return null;\r | |
74 | }\r | |
75 | },\r | |
76 | \r | |
77 | setAsset: function(key, content) {\r | |
78 | try {\r | |
79 | if (content === null || content == '') {\r | |
80 | _storage.removeItem(key);\r | |
81 | } else {\r | |
82 | _storage.setItem(key, content);\r | |
83 | }\r | |
84 | }\r | |
85 | catch (e) {\r | |
86 | if (_storage && e.code == e.QUOTA_EXCEEDED_ERR) {\r | |
87 | //<debug>\r | |
88 | _warn("LocalStorage Quota exceeded, cannot store " + key + " locally");\r | |
89 | //</debug>\r | |
90 | }\r | |
91 | }\r | |
92 | }\r | |
93 | };\r | |
94 | \r | |
95 | var Asset = function (cfg) {\r | |
96 | if (typeof cfg.assetConfig === 'string') {\r | |
97 | this.assetConfig = {\r | |
98 | path: cfg.assetConfig\r | |
99 | };\r | |
100 | } else {\r | |
101 | this.assetConfig = cfg.assetConfig;\r | |
102 | }\r | |
103 | \r | |
104 | this.type = cfg.type;\r | |
105 | this.key = cfg.manifestKey + '-' + this.assetConfig.path;\r | |
106 | \r | |
107 | if (cfg.loadFromCache) {\r | |
108 | this.loadFromCache();\r | |
109 | }\r | |
110 | };\r | |
111 | \r | |
112 | Asset.prototype = {\r | |
113 | shouldCache: function() {\r | |
114 | return _storage && this.assetConfig.update && this.assetConfig.hash && !this.assetConfig.remote;\r | |
115 | },\r | |
116 | \r | |
117 | is: function (asset) {\r | |
118 | return (!!asset && this.assetConfig && asset.assetConfig && (this.assetConfig.hash === asset.assetConfig.hash))\r | |
119 | },\r | |
120 | \r | |
121 | cache: function(content) {\r | |
122 | if (this.shouldCache()) {\r | |
123 | LocalStorage.setAsset(this.key, content || this.content);\r | |
124 | }\r | |
125 | },\r | |
126 | \r | |
127 | uncache: function() {\r | |
128 | LocalStorage.setAsset(this.key, null);\r | |
129 | },\r | |
130 | \r | |
131 | updateContent: function (content) {\r | |
132 | this.content = content;\r | |
133 | },\r | |
134 | \r | |
135 | getSize: function () {\r | |
136 | return this.content ? this.content.length : 0;\r | |
137 | },\r | |
138 | \r | |
139 | loadFromCache: function() {\r | |
140 | if (this.shouldCache()) {\r | |
141 | this.content = LocalStorage.retrieveAsset(this.key);\r | |
142 | }\r | |
143 | }\r | |
144 | };\r | |
145 | \r | |
146 | var Manifest = function (cfg) {\r | |
147 | if (typeof cfg.content === "string") {\r | |
148 | this.content = JSON.parse(cfg.content);\r | |
149 | } else {\r | |
150 | this.content = cfg.content;\r | |
151 | }\r | |
152 | this.assetMap = {};\r | |
153 | \r | |
154 | this.url = cfg.url;\r | |
155 | this.fromCache = !!cfg.cached;\r | |
156 | this.assetCache = !(cfg.assetCache === false);\r | |
157 | this.key = getManifestStorageKey(this.url);\r | |
158 | \r | |
159 | // Pull out select properties for repetitive use\r | |
160 | this.hash = this.content.hash;\r | |
161 | this.loadOrder = this.content.loadOrder;\r | |
162 | this.deltas = this.content.cache ? this.content.cache.deltas : null;\r | |
163 | this.cacheEnabled = this.content.cache ? this.content.cache.enable : false;\r | |
164 | \r | |
165 | this.loadOrderMap = (this.loadOrder) ? Boot.createLoadOrderMap(this.loadOrder) : null;\r | |
166 | \r | |
167 | // Convert all assets into Assets\r | |
168 | this.js = this.processAssets(this.content.js, 'js');\r | |
169 | this.css = this.processAssets(this.content.css, 'css');\r | |
170 | };\r | |
171 | \r | |
172 | Manifest.prototype = {\r | |
173 | processAsset: function(assetConfig, type) {\r | |
174 | var processedAsset = new Asset({\r | |
175 | manifestKey: this.key,\r | |
176 | assetConfig: assetConfig,\r | |
177 | type: type,\r | |
178 | loadFromCache: this.assetCache\r | |
179 | });\r | |
180 | this.assetMap[assetConfig.path] = processedAsset;\r | |
181 | return processedAsset;\r | |
182 | },\r | |
183 | \r | |
184 | processAssets: function(assets, type) {\r | |
185 | var results = [],\r | |
186 | ln = assets.length,\r | |
187 | i, assetConfig;\r | |
188 | \r | |
189 | for (i = 0; i < ln; i++) {\r | |
190 | assetConfig = assets[i];\r | |
191 | results.push(this.processAsset(assetConfig, type));\r | |
192 | }\r | |
193 | \r | |
194 | return results;\r | |
195 | },\r | |
196 | \r | |
197 | useAppCache: function() {\r | |
198 | return true;\r | |
199 | },\r | |
200 | \r | |
201 | // Concatenate all assets for easy access\r | |
202 | getAssets: function () {\r | |
203 | return this.css.concat(this.js);\r | |
204 | },\r | |
205 | \r | |
206 | getAsset: function (path) {\r | |
207 | return this.assetMap[path];\r | |
208 | },\r | |
209 | \r | |
210 | shouldCache: function() {\r | |
211 | return this.hash && this.cacheEnabled;\r | |
212 | },\r | |
213 | \r | |
214 | cache: function(content) {\r | |
215 | if (this.shouldCache()) {\r | |
216 | LocalStorage.setAsset(this.key, JSON.stringify(content || this.content));\r | |
217 | }\r | |
218 | //<debug>\r | |
219 | else {\r | |
220 | _debug("Manifest caching is disabled.");\r | |
221 | }\r | |
222 | //</debug>\r | |
223 | },\r | |
224 | \r | |
225 | is: function(manifest) {\r | |
226 | //<debug>\r | |
227 | _debug("Testing Manifest: " + this.hash + " VS " + manifest.hash);\r | |
228 | //</debug>\r | |
229 | return this.hash === manifest.hash;\r | |
230 | },\r | |
231 | \r | |
232 | // Clear the manifest from local storage\r | |
233 | uncache: function() {\r | |
234 | LocalStorage.setAsset(this.key, null);\r | |
235 | },\r | |
236 | \r | |
237 | exportContent: function() {\r | |
238 | return Boot.apply({\r | |
239 | loadOrderMap: this.loadOrderMap\r | |
240 | }, this.content);\r | |
241 | }\r | |
242 | };\r | |
243 | \r | |
244 | /**\r | |
245 | * Microloader\r | |
246 | * @type {Array}\r | |
247 | * @private\r | |
248 | */\r | |
249 | var _listeners = [],\r | |
250 | _loaded = false,\r | |
251 | Microloader = {\r | |
252 | init: function () {\r | |
253 | Ext.microloaded = true;\r | |
254 | if (Ext.beforeLoad) {\r | |
255 | postProcessor = Ext.beforeLoad(Ext.platformTags);\r | |
256 | }\r | |
257 | \r | |
258 | var readyHandler = Ext._beforereadyhandler;\r | |
259 | \r | |
260 | Ext._beforereadyhandler = function () {\r | |
261 | if (Ext.Boot !== Boot) {\r | |
262 | Ext.apply(Ext.Boot, Boot);\r | |
263 | Ext.Boot = Boot;\r | |
264 | }\r | |
265 | if (readyHandler) {\r | |
266 | readyHandler();\r | |
267 | }\r | |
268 | };\r | |
269 | },\r | |
270 | \r | |
271 | run: function() {\r | |
272 | Microloader.init();\r | |
273 | var manifest = Ext.manifest;\r | |
274 | \r | |
275 | if (typeof manifest === "string") {\r | |
276 | var extension = ".json",\r | |
277 | url = manifest.indexOf(extension) === manifest.length - extension.length\r | |
278 | ? manifest\r | |
279 | : manifest + ".json",\r | |
280 | key = getManifestStorageKey(url),\r | |
281 | content = LocalStorage.retrieveAsset(key);\r | |
282 | \r | |
283 | // Manifest found in local storage, use this for immediate boot except in PhantomJS environments for building.\r | |
284 | if (content) {\r | |
285 | //<debug>\r | |
286 | _debug("Manifest file, '" + url + "', was found in Local Storage");\r | |
287 | //</debug>\r | |
288 | manifest = new Manifest({\r | |
289 | url: url,\r | |
290 | content: content,\r | |
291 | cached: true\r | |
292 | });\r | |
293 | if (postProcessor) {\r | |
294 | postProcessor(manifest);\r | |
295 | }\r | |
296 | Microloader.load(manifest);\r | |
297 | \r | |
298 | \r | |
299 | // Manifest is not in local storage. Fetch it from the server\r | |
300 | } else {\r | |
301 | Boot.fetch(url, function (result) {\r | |
302 | //<debug>\r | |
303 | _debug("Manifest file was not found in Local Storage, loading: " + url);\r | |
304 | //</debug>\r | |
305 | manifest = new Manifest({\r | |
306 | url: url,\r | |
307 | content: result.content\r | |
308 | });\r | |
309 | \r | |
310 | manifest.cache();\r | |
311 | if (postProcessor) {\r | |
312 | postProcessor(manifest);\r | |
313 | }\r | |
314 | Microloader.load(manifest);\r | |
315 | });\r | |
316 | }\r | |
317 | \r | |
318 | // Embedded Manifest into JS file\r | |
319 | } else {\r | |
320 | //<debug>\r | |
321 | _debug("Manifest was embedded into application javascript file");\r | |
322 | //</debug>\r | |
323 | manifest = new Manifest({\r | |
324 | content: manifest,\r | |
325 | url: 'embedded'\r | |
326 | });\r | |
327 | Microloader.load(manifest);\r | |
328 | }\r | |
329 | },\r | |
330 | \r | |
331 | /**\r | |
332 | *\r | |
333 | * @param {Manifest} manifest\r | |
334 | */\r | |
335 | load: function (manifest) {\r | |
336 | Microloader.urls = [];\r | |
337 | Microloader.manifest = manifest;\r | |
338 | Ext.manifest = Microloader.manifest.exportContent();\r | |
339 | \r | |
340 | var assets = manifest.getAssets(),\r | |
341 | cachedAssets = [],\r | |
342 | asset, i, len, include, entry;\r | |
343 | \r | |
344 | for (len = assets.length, i = 0; i < len; i++) {\r | |
345 | asset = assets[i];\r | |
346 | include = true;\r | |
347 | if (asset.assetConfig.platform && !Boot.filterPlatform(asset.assetConfig.platform)) {\r | |
348 | include = false;\r | |
349 | }\r | |
350 | if (include) {\r | |
351 | // Asset is using the localStorage caching system\r | |
352 | if (manifest.shouldCache() && asset.shouldCache()) {\r | |
353 | // Asset already has content from localStorage, instantly seed that into boot\r | |
354 | if (asset.content) {\r | |
355 | //<debug>\r | |
356 | _debug("Asset: " + asset.assetConfig.path + " was found in local storage. No remote load for this file");\r | |
357 | //</debug>\r | |
358 | entry = Boot.registerContent(asset.assetConfig.path, asset.type, asset.content);\r | |
359 | if (entry.evaluated) {\r | |
360 | _warn("Asset: " + asset.assetConfig.path + " was evaluated prior to local storage being consulted.");\r | |
361 | }\r | |
362 | //load via AJAX and seed content into Boot\r | |
363 | } else {\r | |
364 | //<debug>\r | |
365 | _debug("Asset: " + asset.assetConfig.path + " was NOT found in local storage. Adding to load queue");\r | |
366 | //</debug>\r | |
367 | cachedAssets.push(asset);\r | |
368 | }\r | |
369 | }\r | |
370 | Microloader.urls.push(asset.assetConfig.path);\r | |
371 | }\r | |
372 | }\r | |
373 | \r | |
374 | // If any assets are using the caching system and do not have local versions load them first via AJAX\r | |
375 | if (cachedAssets.length > 0) {\r | |
376 | Microloader.remainingCachedAssets = cachedAssets.length;\r | |
377 | while (cachedAssets.length > 0) {\r | |
378 | asset = cachedAssets.pop();\r | |
379 | //<debug>\r | |
380 | _debug("Preloading/Fetching Cached Assets from: " + asset.assetConfig.path);\r | |
381 | //</debug>\r | |
382 | Boot.fetch(asset.assetConfig.path, (function(asset) {\r | |
383 | return function(result) {\r | |
384 | Microloader.onCachedAssetLoaded(asset, result);\r | |
385 | }\r | |
386 | })(asset));\r | |
387 | }\r | |
388 | } else {\r | |
389 | Microloader.onCachedAssetsReady();\r | |
390 | }\r | |
391 | },\r | |
392 | \r | |
393 | // Load the asset and seed its content into Boot to be evaluated in sequence\r | |
394 | onCachedAssetLoaded: function (asset, result) {\r | |
395 | var checksum;\r | |
396 | result = Microloader.parseResult(result);\r | |
397 | Microloader.remainingCachedAssets--;\r | |
398 | \r | |
399 | if (!result.error) {\r | |
400 | checksum = Microloader.checksum(asset.assetConfig.hash, result.content);\r | |
401 | if (!checksum) {\r | |
402 | _warn("Cached Asset '" + asset.assetConfig.path + "' has failed checksum. This asset will be uncached for future loading");\r | |
403 | \r | |
404 | // Un cache this asset so it is loaded next time\r | |
405 | asset.uncache();\r | |
406 | }\r | |
407 | \r | |
408 | //<debug>\r | |
409 | _debug("Checksum for Cached Asset: " + asset.assetConfig.path + " is " + checksum);\r | |
410 | //</debug>\r | |
411 | Boot.registerContent(asset.assetConfig.path, asset.type, result.content);\r | |
412 | asset.updateContent(result.content);\r | |
413 | asset.cache();\r | |
414 | } else {\r | |
415 | _warn("There was an error pre-loading the asset '" + asset.assetConfig.path + "'. This asset will be uncached for future loading");\r | |
416 | \r | |
417 | // Un cache this asset so it is loaded next time\r | |
418 | asset.uncache();\r | |
419 | } \r | |
420 | \r | |
421 | if (Microloader.remainingCachedAssets === 0) {\r | |
422 | Microloader.onCachedAssetsReady();\r | |
423 | }\r | |
424 | },\r | |
425 | \r | |
426 | onCachedAssetsReady: function(){\r | |
427 | Boot.load({\r | |
428 | url: Microloader.urls,\r | |
429 | loadOrder: Microloader.manifest.loadOrder,\r | |
430 | loadOrderMap: Microloader.manifest.loadOrderMap,\r | |
431 | sequential: true,\r | |
432 | success: Microloader.onAllAssetsReady,\r | |
433 | failure: Microloader.onAllAssetsReady\r | |
434 | });\r | |
435 | },\r | |
436 | \r | |
437 | onAllAssetsReady: function() {\r | |
438 | _loaded = true;\r | |
439 | Microloader.notify();\r | |
440 | \r | |
441 | if (navigator.onLine !== false) {\r | |
442 | //<debug>\r | |
443 | _debug("Application is online, checking for updates");\r | |
444 | //</debug>\r | |
445 | Microloader.checkAllUpdates();\r | |
446 | }\r | |
447 | else {\r | |
448 | //<debug>\r | |
449 | _debug("Application is offline, adding online listener to check for updates");\r | |
450 | //</debug>\r | |
451 | if(window['addEventListener']) {\r | |
452 | window.addEventListener('online', Microloader.checkAllUpdates, false);\r | |
453 | }\r | |
454 | }\r | |
455 | },\r | |
456 | \r | |
457 | onMicroloaderReady: function (listener) {\r | |
458 | if (_loaded) {\r | |
459 | listener();\r | |
460 | } else {\r | |
461 | _listeners.push(listener);\r | |
462 | }\r | |
463 | },\r | |
464 | \r | |
465 | /**\r | |
466 | * @private\r | |
467 | */\r | |
468 | notify: function () {\r | |
469 | //<debug>\r | |
470 | _debug("notifying microloader ready listeners.");\r | |
471 | //</debug>\r | |
472 | var listener;\r | |
473 | while((listener = _listeners.shift())) {\r | |
474 | listener();\r | |
475 | }\r | |
476 | },\r | |
477 | \r | |
478 | // Delta patches content\r | |
479 | patch: function (content, delta) {\r | |
480 | var output = [],\r | |
481 | chunk, i, ln;\r | |
482 | \r | |
483 | if (delta.length === 0) {\r | |
484 | return content;\r | |
485 | }\r | |
486 | \r | |
487 | for (i = 0,ln = delta.length; i < ln; i++) {\r | |
488 | chunk = delta[i];\r | |
489 | \r | |
490 | if (typeof chunk === 'number') {\r | |
491 | output.push(content.substring(chunk, chunk + delta[++i]));\r | |
492 | }\r | |
493 | else {\r | |
494 | output.push(chunk);\r | |
495 | }\r | |
496 | }\r | |
497 | \r | |
498 | return output.join('');\r | |
499 | },\r | |
500 | \r | |
501 | checkAllUpdates: function() {\r | |
502 | //<debug>\r | |
503 | _debug("Checking for All Updates");\r | |
504 | //</debug>\r | |
505 | if(window['removeEventListener']) {\r | |
506 | window.removeEventListener('online', Microloader.checkAllUpdates, false);\r | |
507 | }\r | |
508 | \r | |
509 | if(_cache) {\r | |
510 | Microloader.checkForAppCacheUpdate();\r | |
511 | }\r | |
512 | \r | |
513 | // Manifest came from a cached instance, check for updates\r | |
514 | if (Microloader.manifest.fromCache) {\r | |
515 | Microloader.checkForUpdates();\r | |
516 | }\r | |
517 | },\r | |
518 | \r | |
519 | checkForAppCacheUpdate: function() {\r | |
520 | //<debug>\r | |
521 | _debug("Checking App Cache status");\r | |
522 | //</debug>\r | |
523 | if (_cache.status === _cache.UPDATEREADY || _cache.status === _cache.OBSOLETE) {\r | |
524 | //<debug>\r | |
525 | _debug("App Cache is already in an updated");\r | |
526 | //</debug>\r | |
527 | Microloader.appCacheState = 'updated';\r | |
528 | } else if (_cache.status !== _cache.IDLE && _cache.status !== _cache.UNCACHED) {\r | |
529 | //<debug>\r | |
530 | _debug("App Cache is checking or downloading updates, adding listeners");\r | |
531 | //</debug>\r | |
532 | Microloader.appCacheState = 'checking';\r | |
533 | _cache.addEventListener('error', Microloader.onAppCacheError);\r | |
534 | _cache.addEventListener('noupdate', Microloader.onAppCacheNotUpdated);\r | |
535 | _cache.addEventListener('cached', Microloader.onAppCacheNotUpdated);\r | |
536 | _cache.addEventListener('updateready', Microloader.onAppCacheReady);\r | |
537 | _cache.addEventListener('obsolete', Microloader.onAppCacheObsolete);\r | |
538 | } else {\r | |
539 | //<debug>\r | |
540 | _debug("App Cache is current or uncached");\r | |
541 | //</debug>\r | |
542 | Microloader.appCacheState = 'current';\r | |
543 | }\r | |
544 | },\r | |
545 | \r | |
546 | checkForUpdates: function() {\r | |
547 | // Fetch the Latest Manifest from the server\r | |
548 | //<debug>\r | |
549 | _debug("Checking for updates at: " + Microloader.manifest.url);\r | |
550 | //</debug>\r | |
551 | Boot.fetch(Microloader.manifest.url, Microloader.onUpdatedManifestLoaded);\r | |
552 | },\r | |
553 | \r | |
554 | onAppCacheError: function(e) {\r | |
555 | _warn(e.message);\r | |
556 | \r | |
557 | Microloader.appCacheState = 'error';\r | |
558 | Microloader.notifyUpdateReady();\r | |
559 | },\r | |
560 | \r | |
561 | onAppCacheReady: function() {\r | |
562 | _cache.swapCache();\r | |
563 | Microloader.appCacheUpdated();\r | |
564 | },\r | |
565 | \r | |
566 | onAppCacheObsolete: function() {\r | |
567 | Microloader.appCacheUpdated();\r | |
568 | },\r | |
569 | \r | |
570 | appCacheUpdated: function() {\r | |
571 | //<debug>\r | |
572 | _debug("App Cache Updated");\r | |
573 | //</debug>\r | |
574 | Microloader.appCacheState = 'updated';\r | |
575 | Microloader.notifyUpdateReady();\r | |
576 | },\r | |
577 | \r | |
578 | onAppCacheNotUpdated: function() {\r | |
579 | //<debug>\r | |
580 | _debug("App Cache Not Updated Callback");\r | |
581 | //</debug>\r | |
582 | Microloader.appCacheState = 'current';\r | |
583 | Microloader.notifyUpdateReady();\r | |
584 | },\r | |
585 | \r | |
586 | onUpdatedManifestLoaded: function (result) {\r | |
587 | result = Microloader.parseResult(result);\r | |
588 | \r | |
589 | if (!result.error) {\r | |
590 | var currentAssets, newAssets, currentAsset, newAsset, prop,\r | |
591 | assets, deltas, deltaPath, include,\r | |
592 | updatingAssets = [],\r | |
593 | manifest = new Manifest({\r | |
594 | url: Microloader.manifest.url,\r | |
595 | content: result.content,\r | |
596 | assetCache: false\r | |
597 | });\r | |
598 | \r | |
599 | Microloader.remainingUpdatingAssets = 0;\r | |
600 | Microloader.updatedAssets = [];\r | |
601 | Microloader.removedAssets = [];\r | |
602 | Microloader.updatedManifest = null;\r | |
603 | Microloader.updatedAssetsReady = false;\r | |
604 | \r | |
605 | // If the updated manifest has turned off caching we need to clear out all local storage\r | |
606 | // and trigger a appupdate as all content is now uncached\r | |
607 | if (!manifest.shouldCache()) {\r | |
608 | //<debug>\r | |
609 | _debug("New Manifest has caching disabled, clearing out any private storage");\r | |
610 | //</debug>\r | |
611 | \r | |
612 | Microloader.updatedManifest = manifest;\r | |
613 | LocalStorage.clearAllPrivate();\r | |
614 | Microloader.onAllUpdatedAssetsReady();\r | |
615 | return;\r | |
616 | }\r | |
617 | \r | |
618 | // Manifest itself has changed\r | |
619 | if (!Microloader.manifest.is(manifest)) {\r | |
620 | Microloader.updatedManifest = manifest;\r | |
621 | \r | |
622 | currentAssets = Microloader.manifest.getAssets();\r | |
623 | newAssets = manifest.getAssets();\r | |
624 | \r | |
625 | // Look through new assets for assets that do not exist or assets that have different versions\r | |
626 | for (prop in newAssets) {\r | |
627 | newAsset = newAssets[prop];\r | |
628 | currentAsset = Microloader.manifest.getAsset(newAsset.assetConfig.path);\r | |
629 | include = !(newAsset.assetConfig.platform && !Boot.filterPlatform(newAsset.assetConfig.platform));\r | |
630 | \r | |
631 | if (include && (!currentAsset || (newAsset.shouldCache() && (!currentAsset.is(newAsset))))) {\r | |
632 | //<debug>\r | |
633 | _debug("New/Updated Version of Asset: " + newAsset.assetConfig.path + " was found in new manifest");\r | |
634 | //</debug>\r | |
635 | updatingAssets.push({_new: newAsset, _current: currentAsset});\r | |
636 | }\r | |
637 | }\r | |
638 | \r | |
639 | // Look through current assets for stale/old assets that have been removed\r | |
640 | for (prop in currentAssets) {\r | |
641 | currentAsset = currentAssets[prop];\r | |
642 | newAsset = manifest.getAsset(currentAsset.assetConfig.path);\r | |
643 | \r | |
644 | //New version of this asset has been filtered out\r | |
645 | include = !(newAsset.assetConfig.platform && !Boot.filterPlatform(newAsset.assetConfig.platform));\r | |
646 | \r | |
647 | if (!include || !newAsset || (currentAsset.shouldCache() && !newAsset.shouldCache())) {\r | |
648 | //<debug>\r | |
649 | _debug("Asset: " + currentAsset.assetConfig.path + " was not found in new manifest, has been filtered out or has been switched to not cache. Marked for removal");\r | |
650 | //</debug>\r | |
651 | Microloader.removedAssets.push(currentAsset);\r | |
652 | }\r | |
653 | }\r | |
654 | \r | |
655 | // Loop through all assets that need updating\r | |
656 | if (updatingAssets.length > 0) {\r | |
657 | Microloader.remainingUpdatingAssets = updatingAssets.length;\r | |
658 | while (updatingAssets.length > 0) {\r | |
659 | assets = updatingAssets.pop();\r | |
660 | newAsset = assets._new;\r | |
661 | currentAsset = assets._current;\r | |
662 | \r | |
663 | // Full Updates will simply download the file and replace its current content\r | |
664 | if (newAsset.assetConfig.update === "full" || !currentAsset) {\r | |
665 | \r | |
666 | //<debug>\r | |
667 | if (newAsset.assetConfig.update === "delta") {\r | |
668 | _debug("Delta updated asset found without current asset available: " + newAsset.assetConfig.path + " fetching full file");\r | |
669 | } else {\r | |
670 | _debug("Full update found for: " + newAsset.assetConfig.path + " fetching");\r | |
671 | }\r | |
672 | //</debug>\r | |
673 | \r | |
674 | // Load the asset and cache its its content into Boot to be evaluated in sequence\r | |
675 | Boot.fetch(newAsset.assetConfig.path, (function (asset) {\r | |
676 | return function (result) {\r | |
677 | Microloader.onFullAssetUpdateLoaded(asset, result)\r | |
678 | };\r | |
679 | }(newAsset))\r | |
680 | );\r | |
681 | \r | |
682 | // Delta updates will be given a delta patch\r | |
683 | } else if (newAsset.assetConfig.update === "delta") {\r | |
684 | deltas = manifest.deltas;\r | |
685 | deltaPath = deltas + "/" + newAsset.assetConfig.path + "/" + currentAsset.assetConfig.hash + ".json";\r | |
686 | // Fetch the Delta Patch and update the contents of the asset\r | |
687 | //<debug>\r | |
688 | _debug("Delta update found for: " + newAsset.assetConfig.path + " fetching");\r | |
689 | //</debug>\r | |
690 | Boot.fetch(deltaPath,\r | |
691 | (function (asset, oldAsset) {\r | |
692 | return function (result) {\r | |
693 | Microloader.onDeltaAssetUpdateLoaded(asset, oldAsset, result)\r | |
694 | };\r | |
695 | }(newAsset, currentAsset))\r | |
696 | );\r | |
697 | }\r | |
698 | }\r | |
699 | } else {\r | |
700 | //<debug>\r | |
701 | _debug("No Assets needed updating");\r | |
702 | //</debug>\r | |
703 | Microloader.onAllUpdatedAssetsReady();\r | |
704 | }\r | |
705 | } else {\r | |
706 | //<debug>\r | |
707 | _debug("Manifest files have matching hash's");\r | |
708 | //</debug>\r | |
709 | Microloader.onAllUpdatedAssetsReady();\r | |
710 | }\r | |
711 | } else {\r | |
712 | _warn("Error loading manifest file to check for updates");\r | |
713 | Microloader.onAllUpdatedAssetsReady();\r | |
714 | }\r | |
715 | },\r | |
716 | \r | |
717 | onFullAssetUpdateLoaded: function(asset, result) {\r | |
718 | var checksum;\r | |
719 | result = Microloader.parseResult(result);\r | |
720 | Microloader.remainingUpdatingAssets--;\r | |
721 | \r | |
722 | if (!result.error) {\r | |
723 | \r | |
724 | checksum = Microloader.checksum(asset.assetConfig.hash, result.content);\r | |
725 | //<debug>\r | |
726 | _debug("Checksum for Full asset: " + asset.assetConfig.path + " is " + checksum);\r | |
727 | //</debug>\r | |
728 | if (!checksum) {\r | |
729 | //<debug>\r | |
730 | _debug("Full Update Asset: " + asset.assetConfig.path + " has failed checksum. This asset will be uncached for future loading");\r | |
731 | //</debug>\r | |
732 | \r | |
733 | // uncache this asset as there is a new version somewhere that has not been loaded.\r | |
734 | asset.uncache();\r | |
735 | } else {\r | |
736 | asset.updateContent(result.content);\r | |
737 | Microloader.updatedAssets.push(asset);\r | |
738 | }\r | |
739 | } else {\r | |
740 | //<debug>\r | |
741 | _debug("Error loading file at" + asset.assetConfig.path + ". This asset will be uncached for future loading");\r | |
742 | //</debug>\r | |
743 | \r | |
744 | // uncache this asset as there is a new version somewhere that has not been loaded.\r | |
745 | asset.uncache();\r | |
746 | }\r | |
747 | \r | |
748 | if (Microloader.remainingUpdatingAssets === 0) {\r | |
749 | Microloader.onAllUpdatedAssetsReady();\r | |
750 | }\r | |
751 | },\r | |
752 | \r | |
753 | onDeltaAssetUpdateLoaded: function(asset, oldAsset, result) {\r | |
754 | var json, checksum, content;\r | |
755 | result = Microloader.parseResult(result);\r | |
756 | Microloader.remainingUpdatingAssets--;\r | |
757 | \r | |
758 | if (!result.error) {\r | |
759 | //<debug>\r | |
760 | _debug("Delta patch loaded successfully, patching content");\r | |
761 | //</debug>\r | |
762 | try {\r | |
763 | json = JSON.parse(result.content);\r | |
764 | content = Microloader.patch(oldAsset.content, json);\r | |
765 | checksum = Microloader.checksum(content, asset.assetConfig.hash);\r | |
766 | //<debug>\r | |
767 | _debug("Checksum for Delta Patched asset: " + asset.assetConfig.path + " is " + checksum);\r | |
768 | //</debug>\r | |
769 | if (!checksum) {\r | |
770 | //<debug>\r | |
771 | _debug("Delta Update Asset: " + asset.assetConfig.path + " has failed checksum. This asset will be uncached for future loading");\r | |
772 | //</debug>\r | |
773 | \r | |
774 | // uncache this asset as there is a new version somewhere that has not been loaded.\r | |
775 | asset.uncache();\r | |
776 | } else {\r | |
777 | asset.updateContent(content);\r | |
778 | Microloader.updatedAssets.push(asset);\r | |
779 | }\r | |
780 | } catch (e) {\r | |
781 | _warn("Error parsing delta patch for " + asset.assetConfig.path + " with hash " + oldAsset.assetConfig.hash + " . This asset will be uncached for future loading");\r | |
782 | // uncache this asset as there is a new version somewhere that has not been loaded.\r | |
783 | asset.uncache();\r | |
784 | }\r | |
785 | } else {\r | |
786 | _warn("Error loading delta patch for " + asset.assetConfig.path + " with hash " + oldAsset.assetConfig.hash + " . This asset will be uncached for future loading");\r | |
787 | \r | |
788 | // uncache this asset as there is a new version somewhere that has not been loaded.\r | |
789 | asset.uncache();\r | |
790 | }\r | |
791 | if (Microloader.remainingUpdatingAssets === 0) {\r | |
792 | Microloader.onAllUpdatedAssetsReady();\r | |
793 | }\r | |
794 | },\r | |
795 | \r | |
796 | //TODO: Make this all transaction based to allow for reverting if quota is exceeded\r | |
797 | onAllUpdatedAssetsReady: function() {\r | |
798 | var asset;\r | |
799 | Microloader.updatedAssetsReady = true;\r | |
800 | \r | |
801 | if (Microloader.updatedManifest) {\r | |
802 | while (Microloader.removedAssets.length > 0) {\r | |
803 | asset = Microloader.removedAssets.pop();\r | |
804 | //<debug>\r | |
805 | _debug("Asset: " + asset.assetConfig.path + " was removed, un-caching");\r | |
806 | //</debug>\r | |
807 | asset.uncache();\r | |
808 | }\r | |
809 | \r | |
810 | if (Microloader.updatedManifest) {\r | |
811 | //<debug>\r | |
812 | _debug("Manifest was updated, re-caching");\r | |
813 | //</debug>\r | |
814 | Microloader.updatedManifest.cache();\r | |
815 | }\r | |
816 | \r | |
817 | while (Microloader.updatedAssets.length > 0) {\r | |
818 | asset = Microloader.updatedAssets.pop();\r | |
819 | //<debug>\r | |
820 | _debug("Asset: " + asset.assetConfig.path + " was updated, re-caching");\r | |
821 | //</debug>\r | |
822 | asset.cache();\r | |
823 | }\r | |
824 | \r | |
825 | }\r | |
826 | \r | |
827 | Microloader.notifyUpdateReady();\r | |
828 | },\r | |
829 | \r | |
830 | notifyUpdateReady: function () {\r | |
831 | if (Microloader.appCacheState !== 'checking' && Microloader.updatedAssetsReady) {\r | |
832 | if (Microloader.appCacheState === 'updated' || Microloader.updatedManifest) {\r | |
833 | //<debug>\r | |
834 | _debug("There was an update here you will want to reload the app, trigger an event");\r | |
835 | //</debug>\r | |
836 | Microloader.appUpdate = {\r | |
837 | updated: true,\r | |
838 | app: Microloader.appCacheState === 'updated',\r | |
839 | manifest: Microloader.updatedManifest && Microloader.updatedManifest.exportContent()\r | |
840 | };\r | |
841 | \r | |
842 | Microloader.fireAppUpdate();\r | |
843 | }\r | |
844 | //<debug>\r | |
845 | else {\r | |
846 | _debug("AppCache and LocalStorage Cache are current, no updating needed");\r | |
847 | Microloader.appUpdate = {};\r | |
848 | }\r | |
849 | //</debug>\r | |
850 | }\r | |
851 | },\r | |
852 | \r | |
853 | fireAppUpdate: function() {\r | |
854 | if (Ext.GlobalEvents) {\r | |
855 | // We defer dispatching this event slightly in order to let the application finish loading\r | |
856 | // as we are still very early in the lifecycle\r | |
857 | Ext.defer(function() {\r | |
858 | Ext.GlobalEvents.fireEvent('appupdate', Microloader.appUpdate);\r | |
859 | }, 100);\r | |
860 | }\r | |
861 | },\r | |
862 | \r | |
863 | checksum: function(content, hash) {\r | |
864 | var passed = true,\r | |
865 | hashLn = hash.length,\r | |
866 | checksumType = content.substring(0, 1);\r | |
867 | \r | |
868 | if (checksumType == '/') {\r | |
869 | if (content.substring(2, hashLn + 2) !== hash) {\r | |
870 | passed = false;\r | |
871 | }\r | |
872 | } else if (checksumType == 'f') {\r | |
873 | if (content.substring(10, hashLn + 10) !== hash) {\r | |
874 | passed = false;\r | |
875 | }\r | |
876 | } else if (checksumType == '.') {\r | |
877 | if (content.substring(1, hashLn + 1) !== hash) {\r | |
878 | passed = false;\r | |
879 | }\r | |
880 | }\r | |
881 | return passed;\r | |
882 | },\r | |
883 | parseResult: function(result) {\r | |
884 | var rst = {};\r | |
885 | if ((result.exception || result.status === 0) && !Boot.env.phantom) {\r | |
886 | rst.error = true;\r | |
887 | } else if ((result.status >= 200 && result.status < 300) || result.status === 304\r | |
888 | || Boot.env.phantom\r | |
889 | || (result.status === 0 && result.content.length > 0)\r | |
890 | ) {\r | |
891 | rst.content = result.content;\r | |
892 | } else {\r | |
893 | rst.error = true;\r | |
894 | }\r | |
895 | return rst;\r | |
896 | }\r | |
897 | };\r | |
898 | \r | |
899 | return Microloader;\r | |
900 | }());\r | |
901 | \r | |
902 | //</editor-fold>\r | |
903 | \r | |
904 | /**\r | |
905 | * the current application manifest\r | |
906 | *\r | |
907 | *\r | |
908 | * {\r | |
909 | * name: 'name',\r | |
910 | * version: <checksum>,\r | |
911 | * debug: {\r | |
912 | * hooks: {\r | |
913 | * "*": true\r | |
914 | * }\r | |
915 | * },\r | |
916 | * localStorage: false,\r | |
917 | * mode: production,\r | |
918 | * js: [\r | |
919 | * ...\r | |
920 | * {\r | |
921 | * path: '../boo/baz.js',\r | |
922 | * version: <checksum>,\r | |
923 | * update: full | delta | <falsy>,\r | |
924 | * platform: ['phone', 'ios', 'android']\r | |
925 | * },\r | |
926 | * {\r | |
927 | * path: 'http://some.domain.com/api.js',\r | |
928 | * remote: true\r | |
929 | * },\r | |
930 | * ...\r | |
931 | * ],\r | |
932 | * css: [\r | |
933 | * ...\r | |
934 | * {\r | |
935 | * path: '../boo/baz.css',\r | |
936 | * version: <checksum>,\r | |
937 | * update: full | delta | <falsy>,\r | |
938 | * platform: ['phone', 'ios', 'android']\r | |
939 | * },\r | |
940 | * ...\r | |
941 | * ],\r | |
942 | * localStorage: false,\r | |
943 | * paths: {...},\r | |
944 | * loadOrder: [\r | |
945 | * ...\r | |
946 | * {\r | |
947 | * path: '../foo/bar.js",\r | |
948 | * idx: 158,\r | |
949 | * requires; [1,2,3,...,145,157],\r | |
950 | * uses: [182, 193]\r | |
951 | * },\r | |
952 | * ...\r | |
953 | * ],\r | |
954 | * classes: {\r | |
955 | * ...\r | |
956 | * 'Ext.panel.Panel': {\r | |
957 | * requires: [...],\r | |
958 | * uses: [...],\r | |
959 | * aliases: [...],\r | |
960 | * alternates: [...],\r | |
961 | * mixins: [...]\r | |
962 | * },\r | |
963 | * 'Ext.rtl.util.Renderable': {\r | |
964 | * requires: [...],\r | |
965 | * uses: [...],\r | |
966 | * aliases: [...],\r | |
967 | * alternates: [...],\r | |
968 | * mixins: [...]\r | |
969 | * override: 'Ext.util.Renderable'\r | |
970 | * },\r | |
971 | * ...\r | |
972 | * },\r | |
973 | * packages: {\r | |
974 | * ...\r | |
975 | * "sencha-core": {\r | |
976 | * version: '1.2.3.4',\r | |
977 | * requires: []\r | |
978 | * },\r | |
979 | * "ext": {\r | |
980 | * version: '5.0.0.0',\r | |
981 | * requires: ["sencha-core"]\r | |
982 | * }.\r | |
983 | * ...\r | |
984 | * }\r | |
985 | * }\r | |
986 | *\r | |
987 | *\r | |
988 | * @type {String/Object}\r | |
989 | */\r | |
990 | Ext.manifest = Ext.manifest || "bootstrap";\r | |
991 | \r | |
992 | Ext.Microloader.run(); |