]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | var Ext = Ext || {}; |
2 | Ext.manifest = Ext.manifest || "bootstrap.json"; | |
3 | // @tag core | |
4 | // @define Ext.Boot | |
5 | ||
6 | var Ext = Ext || {}; | |
7 | ||
8 | //<editor-fold desc="Boot"> | |
9 | /* | |
10 | * @class Ext.Boot | |
11 | * @singleton | |
12 | */ | |
13 | Ext.Boot = Ext.Boot || (function (emptyFn) { | |
14 | ||
15 | var doc = document, | |
16 | _emptyArray = [], | |
17 | _config = { | |
18 | /* | |
19 | * @cfg {Boolean} [disableCaching=true] | |
20 | * If `true` current timestamp is added to script URL's to prevent caching. | |
21 | * In debug builds, adding a "cache" or "disableCacheBuster" query parameter | |
22 | * to the page's URL will set this to `false`. | |
23 | */ | |
24 | disableCaching: (/[?&](?:cache|disableCacheBuster)\b/i.test(location.search) || | |
25 | !(/http[s]?\:/i.test(location.href)) || | |
26 | /(^|[ ;])ext-cache=1/.test(doc.cookie)) ? false : | |
27 | true, | |
28 | ||
29 | /* | |
30 | * @cfg {String} [disableCachingParam="_dc"] | |
31 | * The query parameter name for the cache buster's timestamp. | |
32 | */ | |
33 | disableCachingParam: '_dc', | |
34 | ||
35 | /* | |
36 | * @cfg {Boolean} loadDelay | |
37 | * Millisecond delay between asynchronous script injection (prevents stack | |
38 | * overflow on some user agents) 'false' disables delay but potentially | |
39 | * increases stack load. | |
40 | */ | |
41 | loadDelay: false, | |
42 | ||
43 | /* | |
44 | * @cfg {Boolean} preserveScripts | |
45 | * `false` to remove asynchronously loaded scripts, `true` to retain script | |
46 | * element for browser debugger compatibility and improved load performance. | |
47 | */ | |
48 | preserveScripts: true, | |
49 | ||
50 | /* | |
51 | * @cfg {String} [charset=UTF-8] | |
52 | * Optional charset to specify encoding of dynamic content. | |
53 | */ | |
54 | charset: 'UTF-8' | |
55 | }, | |
56 | ||
57 | _assetConfig= {}, | |
58 | ||
59 | cssRe = /\.css(?:\?|$)/i, | |
60 | resolverEl = doc.createElement('a'), | |
61 | isBrowser = typeof window !== 'undefined', | |
62 | _environment = { | |
63 | browser: isBrowser, | |
64 | node: !isBrowser && (typeof require === 'function'), | |
65 | phantom: (window && (window._phantom || window.callPhantom)) || /PhantomJS/.test(window.navigator.userAgent) | |
66 | }, | |
67 | _tags = (Ext.platformTags = {}), | |
68 | ||
69 | _apply = function (object, config, defaults) { | |
70 | if (defaults) { | |
71 | _apply(object, defaults); | |
72 | } | |
73 | if (object && config && typeof config === 'object') { | |
74 | for (var i in config) { | |
75 | object[i] = config[i]; | |
76 | } | |
77 | } | |
78 | return object; | |
79 | }, | |
80 | _merge = function() { | |
81 | var lowerCase = false, | |
82 | obj = Array.prototype.shift.call(arguments), | |
83 | index, i, len, value; | |
84 | ||
85 | if (typeof arguments[arguments.length - 1] === 'boolean') { | |
86 | lowerCase = Array.prototype.pop.call(arguments); | |
87 | } | |
88 | ||
89 | len = arguments.length; | |
90 | for (index = 0; index < len; index++) { | |
91 | value = arguments[index]; | |
92 | if (typeof value === 'object') { | |
93 | for (i in value) { | |
94 | obj[lowerCase ? i.toLowerCase() : i] = value[i]; | |
95 | } | |
96 | } | |
97 | } | |
98 | ||
99 | return obj; | |
100 | }, | |
101 | _getKeys = (typeof Object.keys == 'function') ? | |
102 | function(object){ | |
103 | if (!object) { | |
104 | return []; | |
105 | } | |
106 | return Object.keys(object); | |
107 | } : | |
108 | function(object) { | |
109 | var keys = [], | |
110 | property; | |
111 | ||
112 | for (property in object) { | |
113 | if (object.hasOwnProperty(property)) { | |
114 | keys.push(property); | |
115 | } | |
116 | } | |
117 | ||
118 | return keys; | |
119 | }, | |
120 | /* | |
121 | * The Boot loader class manages Request objects that contain one or | |
122 | * more individual urls that need to be loaded. Requests can be performed | |
123 | * synchronously or asynchronously, but will always evaluate urls in the | |
124 | * order specified on the request object. | |
125 | */ | |
126 | Boot = { | |
127 | loading: 0, | |
128 | loaded: 0, | |
129 | apply: _apply, | |
130 | env: _environment, | |
131 | config: _config, | |
132 | ||
133 | /** | |
134 | * @cfg {Object} assetConfig | |
135 | * A map (url->assetConfig) that contains information about assets loaded by the Microlaoder. | |
136 | */ | |
137 | assetConfig: _assetConfig, | |
138 | ||
139 | // Keyed by absolute URL this object holds "true" if that URL is already loaded | |
140 | // or an array of callbacks to call once it loads. | |
141 | scripts: { | |
142 | /* | |
143 | Entry objects | |
144 | ||
145 | 'http://foo.com/bar/baz/Thing.js': { | |
146 | done: true, | |
147 | el: scriptEl || linkEl, | |
148 | preserve: true, | |
149 | requests: [ request1, ... ] | |
150 | } | |
151 | */ | |
152 | }, | |
153 | ||
154 | /* | |
155 | * contains the current script name being loaded | |
156 | * (loadSync or sequential load only) | |
157 | */ | |
158 | currentFile: null, | |
159 | suspendedQueue: [], | |
160 | currentRequest: null, | |
161 | ||
162 | // when loadSync is called, need to cause subsequent load requests to also be loadSync, | |
163 | // eg, when Ext.require(...) is called | |
164 | syncMode: false, | |
165 | ||
166 | /* | |
167 | * simple helper method for debugging | |
168 | */ | |
169 | ||
170 | /* | |
171 | * enables / disables loading scripts via script / link elements rather | |
172 | * than using ajax / eval | |
173 | */ | |
174 | useElements: true, | |
175 | ||
176 | listeners: [], | |
177 | ||
178 | Request: Request, | |
179 | ||
180 | Entry: Entry, | |
181 | ||
182 | allowMultipleBrowsers: false, | |
183 | ||
184 | browserNames: { | |
185 | ie: 'IE', | |
186 | firefox: 'Firefox', | |
187 | safari: 'Safari', | |
188 | chrome: 'Chrome', | |
189 | opera: 'Opera', | |
190 | dolfin: 'Dolfin', | |
191 | edge: 'Edge', | |
192 | webosbrowser: 'webOSBrowser', | |
193 | chromeMobile: 'ChromeMobile', | |
194 | chromeiOS: 'ChromeiOS', | |
195 | silk: 'Silk', | |
196 | other: 'Other' | |
197 | }, | |
198 | ||
199 | osNames: { | |
200 | ios: 'iOS', | |
201 | android: 'Android', | |
202 | windowsPhone: 'WindowsPhone', | |
203 | webos: 'webOS', | |
204 | blackberry: 'BlackBerry', | |
205 | rimTablet: 'RIMTablet', | |
206 | mac: 'MacOS', | |
207 | win: 'Windows', | |
208 | tizen: 'Tizen', | |
209 | linux: 'Linux', | |
210 | bada: 'Bada', | |
211 | chromeOS: 'ChromeOS', | |
212 | other: 'Other' | |
213 | }, | |
214 | ||
215 | browserPrefixes: { | |
216 | ie: 'MSIE ', | |
217 | edge: 'Edge/', | |
218 | firefox: 'Firefox/', | |
219 | chrome: 'Chrome/', | |
220 | safari: 'Version/', | |
221 | opera: 'OPR/', | |
222 | dolfin: 'Dolfin/', | |
223 | webosbrowser: 'wOSBrowser/', | |
224 | chromeMobile: 'CrMo/', | |
225 | chromeiOS: 'CriOS/', | |
226 | silk: 'Silk/' | |
227 | }, | |
228 | ||
229 | // When a UA reports multiple browsers this list is used to prioritize the 'real' browser | |
230 | // lower index number will win | |
231 | browserPriority: [ | |
232 | 'edge', | |
233 | 'opera', | |
234 | 'dolfin', | |
235 | 'webosbrowser', | |
236 | 'silk', | |
237 | 'chromeiOS', | |
238 | 'chromeMobile', | |
239 | 'ie', | |
240 | 'firefox', | |
241 | 'safari', | |
242 | 'chrome' | |
243 | ], | |
244 | ||
245 | osPrefixes: { | |
246 | tizen: '(Tizen )', | |
247 | ios: 'i(?:Pad|Phone|Pod)(?:.*)CPU(?: iPhone)? OS ', | |
248 | android: '(Android |HTC_|Silk/)', // Some HTC devices ship with an OSX userAgent by default, | |
249 | // so we need to add a direct check for HTC_ | |
250 | windowsPhone: 'Windows Phone ', | |
251 | blackberry: '(?:BlackBerry|BB)(?:.*)Version\/', | |
252 | rimTablet: 'RIM Tablet OS ', | |
253 | webos: '(?:webOS|hpwOS)\/', | |
254 | bada: 'Bada\/', | |
255 | chromeOS: 'CrOS ' | |
256 | }, | |
257 | ||
258 | fallbackOSPrefixes: { | |
259 | windows: 'win', | |
260 | mac: 'mac', | |
261 | linux: 'linux' | |
262 | }, | |
263 | ||
264 | devicePrefixes: { | |
265 | iPhone: 'iPhone', | |
266 | iPod: 'iPod', | |
267 | iPad: 'iPad' | |
268 | }, | |
269 | ||
270 | maxIEVersion: 12, | |
271 | ||
272 | ||
273 | /** | |
274 | * The default function that detects various platforms and sets tags | |
275 | * in the platform map accordingly. Examples are iOS, android, tablet, etc. | |
276 | * @param tags the set of tags to populate | |
277 | */ | |
278 | detectPlatformTags: function () { | |
279 | var me = this, | |
280 | ua = navigator.userAgent, | |
281 | isMobile = /Mobile(\/|\s)/.test(ua), | |
282 | element = document.createElement('div'), | |
283 | isEventSupported = function (name, tag) { | |
284 | if (tag === undefined) { | |
285 | tag = window; | |
286 | } | |
287 | ||
288 | var eventName = 'on' + name.toLowerCase(), | |
289 | isSupported = (eventName in element); | |
290 | ||
291 | if (!isSupported) { | |
292 | if (element.setAttribute && element.removeAttribute) { | |
293 | element.setAttribute(eventName, ''); | |
294 | isSupported = typeof element[eventName] === 'function'; | |
295 | ||
296 | if (typeof element[eventName] !== 'undefined') { | |
297 | element[eventName] = undefined; | |
298 | } | |
299 | ||
300 | element.removeAttribute(eventName); | |
301 | } | |
302 | } | |
303 | ||
304 | return isSupported; | |
305 | }, | |
306 | ||
307 | // Browser Detection | |
308 | getBrowsers = function () { | |
309 | var browsers = {}, | |
310 | maxIEVersion, prefix, | |
311 | value, key, index, len, match, version, matched; | |
312 | ||
313 | // MS Edge browser (and possibly others) can report multiple browsers in the UserAgent | |
314 | // "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240" | |
315 | // we use this to prioritize the actual browser in this situation | |
316 | len = me.browserPriority.length; | |
317 | for (index = 0; index < len; index++) { | |
318 | key = me.browserPriority[index]; | |
319 | if (!matched) { | |
320 | value = me.browserPrefixes[key]; | |
321 | match = ua.match(new RegExp('(' + value + ')([\\w\\._]+)')); | |
322 | version = match && match.length > 1 ? parseInt(match[2]) : 0; | |
323 | if (version) { | |
324 | matched = true; | |
325 | } | |
326 | } else { | |
327 | version = 0; | |
328 | } | |
329 | browsers[key] = version; | |
330 | } | |
331 | ||
332 | //Deal with IE document mode | |
333 | if (browsers.ie) { | |
334 | var mode = document.documentMode; | |
335 | ||
336 | if (mode >= 8) { | |
337 | browsers.ie = mode; | |
338 | } | |
339 | } | |
340 | ||
341 | // Fancy IE greater than and less then quick tags | |
342 | version = browsers.ie || false; | |
343 | maxIEVersion = Math.max(version, me.maxIEVersion); | |
344 | ||
345 | for (index = 8; index <= maxIEVersion; ++index) { | |
346 | prefix = 'ie' + index; | |
347 | browsers[prefix + 'm'] = version ? version <= index : 0; | |
348 | browsers[prefix] = version ? version === index : 0; | |
349 | browsers[prefix + 'p'] = version ? version >= index : 0; | |
350 | } | |
351 | ||
352 | return browsers; | |
353 | }, | |
354 | ||
355 | //OS Detection | |
356 | getOperatingSystems = function () { | |
357 | var systems = {}, | |
358 | value, key, keys, index, len, match, matched, version, activeCount; | |
359 | ||
360 | keys = _getKeys(me.osPrefixes); | |
361 | len = keys.length; | |
362 | for (index = 0, activeCount = 0; index < len; index++) { | |
363 | key = keys[index]; | |
364 | value = me.osPrefixes[key]; | |
365 | match = ua.match(new RegExp('(' + value + ')([^\\s;]+)')); | |
366 | matched = match ? match[1] : null; | |
367 | ||
368 | // This is here because some HTC android devices show an OSX Snow Leopard userAgent by default. | |
369 | // And the Kindle Fire doesn't have any indicator of Android as the OS in its User Agent | |
370 | if (matched && (matched === 'HTC_' || matched === 'Silk/')) { | |
371 | version = 2.3; | |
372 | } else { | |
373 | version = match && match.length > 1 ? parseFloat(match[match.length - 1]) : 0; | |
374 | } | |
375 | ||
376 | if (version) { | |
377 | activeCount++; | |
378 | } | |
379 | systems[key] = version; | |
380 | } | |
381 | ||
382 | keys = _getKeys(me.fallbackOSPrefixes); | |
383 | ||
384 | // If no OS could be found we resort to the fallbacks, otherwise we just | |
385 | // falsify the fallbacks | |
386 | len = keys.length; | |
387 | for (index = 0; index < len; index++) { | |
388 | key = keys[index]; | |
389 | ||
390 | // No OS was detected from osPrefixes | |
391 | if (activeCount === 0) { | |
392 | value = me.fallbackOSPrefixes[key]; | |
393 | match = ua.toLowerCase().match(new RegExp(value)); | |
394 | systems[key] = match ? true : 0; | |
395 | } else { | |
396 | systems[key] = 0; | |
397 | } | |
398 | } | |
399 | ||
400 | return systems; | |
401 | }, | |
402 | ||
403 | // Device Detection | |
404 | getDevices = function () { | |
405 | var devices = {}, | |
406 | value, key, keys, index, len, match; | |
407 | ||
408 | keys = _getKeys(me.devicePrefixes); | |
409 | len = keys.length; | |
410 | for (index = 0; index < len; index++) { | |
411 | key = keys[index]; | |
412 | value = me.devicePrefixes[key]; | |
413 | match = ua.match(new RegExp(value)); | |
414 | devices[key] = match ? true : 0; | |
415 | } | |
416 | ||
417 | return devices; | |
418 | }, | |
419 | browsers = getBrowsers(), | |
420 | systems = getOperatingSystems(), | |
421 | devices = getDevices(), | |
422 | platformParams = Boot.loadPlatformsParam(); | |
423 | ||
424 | // We apply platformParams from the query here first to allow for forced user valued | |
425 | // to be used in calculation of generated tags | |
426 | _merge(_tags, browsers, systems, devices, platformParams, true); | |
427 | ||
428 | _tags.phone = (_tags.iphone || _tags.ipod) || | |
429 | (!_tags.silk && (_tags.android && (_tags.android < 3 || isMobile))) || | |
430 | (_tags.blackberry && isMobile) || | |
431 | (_tags.windowsphone); | |
432 | ||
433 | _tags.tablet = !_tags.phone && ( | |
434 | _tags.ipad || | |
435 | _tags.android || | |
436 | _tags.silk || | |
437 | _tags.rimtablet || | |
438 | (_tags.ie10 && /; Touch/.test(ua)) | |
439 | ); | |
440 | ||
441 | _tags.touch = | |
442 | // if the browser has touch events we can be reasonably sure the device has | |
443 | // a touch screen | |
444 | isEventSupported('touchend') || | |
445 | // browsers that use pointer event have maxTouchPoints > 0 if the | |
446 | // device supports touch input | |
447 | // http://www.w3.org/TR/pointerevents/#widl-Navigator-maxTouchPoints | |
448 | navigator.maxTouchPoints || | |
449 | // IE10 uses a vendor-prefixed maxTouchPoints property | |
450 | navigator.msMaxTouchPoints; | |
451 | ||
452 | _tags.desktop = !_tags.phone && !_tags.tablet; | |
453 | _tags.cordova = _tags.phonegap = !!(window.PhoneGap || window.Cordova || window.cordova); | |
454 | _tags.webview = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)(?!.*FBAN)/i.test(ua); | |
455 | _tags.androidstock = (_tags.android <= 4.3) && (_tags.safari || _tags.silk); | |
456 | ||
457 | // Re-apply any query params here to allow for user override of generated tags (desktop, touch, tablet, etc) | |
458 | _merge(_tags, platformParams, true); | |
459 | }, | |
460 | ||
461 | /** | |
462 | * Extracts user supplied platform tags from the "platformTags" query parameter | |
463 | * of the form: | |
464 | * | |
465 | * ?platformTags=name:state,name:state,... | |
466 | * | |
467 | * (each tag defaults to true when state is unspecified) | |
468 | * | |
469 | * Example: | |
470 | * ?platformTags=isTablet,isPhone:false,isDesktop:0,iOS:1,Safari:true, ... | |
471 | * | |
472 | * @returns {Object} the platform tags supplied by the query string | |
473 | */ | |
474 | loadPlatformsParam: function () { | |
475 | // Check if the ?platform parameter is set in the URL | |
476 | var paramsString = window.location.search.substr(1), | |
477 | paramsArray = paramsString.split("&"), | |
478 | params = {}, i, | |
479 | platforms = {}, | |
480 | tmpArray, tmplen, platform, name, enabled; | |
481 | ||
482 | for (i = 0; i < paramsArray.length; i++) { | |
483 | tmpArray = paramsArray[i].split("="); | |
484 | params[tmpArray[0]] = tmpArray[1]; | |
485 | } | |
486 | ||
487 | if (params.platformTags) { | |
488 | tmpArray = params.platformTags.split(","); | |
489 | for (tmplen = tmpArray.length, i = 0; i < tmplen; i++) { | |
490 | platform = tmpArray[i].split(":"); | |
491 | name = platform[0]; | |
492 | enabled=true; | |
493 | if (platform.length > 1) { | |
494 | enabled = platform[1]; | |
495 | if (enabled === 'false' || enabled === '0') { | |
496 | enabled = false; | |
497 | } | |
498 | } | |
499 | platforms[name] = enabled; | |
500 | } | |
501 | } | |
502 | return platforms; | |
503 | }, | |
504 | ||
505 | filterPlatform: function (platform, excludes) { | |
506 | platform = _emptyArray.concat(platform || _emptyArray); | |
507 | excludes = _emptyArray.concat(excludes || _emptyArray); | |
508 | ||
509 | var plen = platform.length, | |
510 | elen = excludes.length, | |
511 | include = (!plen && elen), // default true if only excludes specified | |
512 | i, tag; | |
513 | ||
514 | for (i = 0; i < plen && !include; i++) { | |
515 | tag = platform[i]; | |
516 | include = !!_tags[tag]; | |
517 | } | |
518 | ||
519 | for (i = 0; i < elen && include; i++) { | |
520 | tag = excludes[i]; | |
521 | include = !_tags[tag]; | |
522 | } | |
523 | ||
524 | return include; | |
525 | }, | |
526 | ||
527 | init: function () { | |
528 | var scriptEls = doc.getElementsByTagName('script'), | |
529 | len = scriptEls.length, | |
530 | re = /\/ext(\-[a-z\-]+)?\.js$/, | |
531 | entry, script, src, state, baseUrl, key, n, origin; | |
532 | ||
533 | // Since we are loading after other scripts, and we needed to gather them | |
534 | // anyway, we track them in _scripts so we don't have to ask for them all | |
535 | // repeatedly. | |
536 | for (n = 0; n < len; n++) { | |
537 | src = (script = scriptEls[n]).src; | |
538 | if (!src) { | |
539 | continue; | |
540 | } | |
541 | state = script.readyState || null; | |
542 | ||
543 | // If we find a script file called "ext-*.js", then the base path is that file's base path. | |
544 | if (!baseUrl) { | |
545 | if (re.test(src)) { | |
546 | Boot.hasReadyState = ("readyState" in script); | |
547 | Boot.hasAsync = ("async" in script) || !Boot.hasReadyState; | |
548 | baseUrl = src; | |
549 | } | |
550 | } | |
551 | ||
552 | if (!Boot.scripts[key = Boot.canonicalUrl(src)]) { | |
553 | entry = new Entry({ | |
554 | key: key, | |
555 | url: src, | |
556 | done: state === null || // non-IE | |
557 | state === 'loaded' || state === 'complete', // IE only | |
558 | el: script, | |
559 | prop: 'src' | |
560 | }); | |
561 | } | |
562 | } | |
563 | ||
564 | if (!baseUrl) { | |
565 | script = scriptEls[scriptEls.length - 1]; | |
566 | baseUrl = script.src; | |
567 | Boot.hasReadyState = ('readyState' in script); | |
568 | Boot.hasAsync = ("async" in script) || !Boot.hasReadyState; | |
569 | } | |
570 | ||
571 | Boot.baseUrl = baseUrl.substring(0, baseUrl.lastIndexOf('/') + 1); | |
572 | origin = window.location.origin || | |
573 | window.location.protocol + | |
574 | "//" + | |
575 | window.location.hostname + | |
576 | (window.location.port ? ':' + window.location.port: ''); | |
577 | Boot.origin = origin; | |
578 | ||
579 | Boot.detectPlatformTags(); | |
580 | Ext.filterPlatform = Boot.filterPlatform; | |
581 | }, | |
582 | ||
583 | /* | |
584 | * This method returns a canonical URL for the given URL. | |
585 | * | |
586 | * For example, the following all produce the same canonical URL (which is the | |
587 | * last one): | |
588 | * | |
589 | * http://foo.com/bar/baz/zoo/derp/../../goo/Thing.js?_dc=12345 | |
590 | * http://foo.com/bar/baz/zoo/derp/../../goo/Thing.js | |
591 | * http://foo.com/bar/baz/zoo/derp/../jazz/../../goo/Thing.js | |
592 | * http://foo.com/bar/baz/zoo/../goo/Thing.js | |
593 | * http://foo.com/bar/baz/goo/Thing.js | |
594 | * | |
595 | * @private | |
596 | */ | |
597 | canonicalUrl: function (url) { | |
598 | // @TODO - see if we need this fallback logic | |
599 | // http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue | |
600 | resolverEl.href = url; | |
601 | ||
602 | var ret = resolverEl.href, | |
603 | dc = _config.disableCachingParam, | |
604 | pos = dc ? ret.indexOf(dc + '=') : -1, | |
605 | c, end; | |
606 | ||
607 | // If we have a _dc query parameter we need to remove it from the canonical | |
608 | // URL. | |
609 | if (pos > 0 && ((c = ret.charAt(pos - 1)) === '?' || c === '&')) { | |
610 | end = ret.indexOf('&', pos); | |
611 | end = (end < 0) ? '' : ret.substring(end); | |
612 | if (end && c === '?') { | |
613 | ++pos; // keep the '?' | |
614 | end = end.substring(1); // remove the '&' | |
615 | } | |
616 | ret = ret.substring(0, pos - 1) + end; | |
617 | } | |
618 | ||
619 | return ret; | |
620 | }, | |
621 | ||
622 | /* | |
623 | * Get the config value corresponding to the specified name. If no name is given, will return the config object | |
624 | * @param {String} name The config property name | |
625 | * @return {Object} | |
626 | */ | |
627 | getConfig: function (name) { | |
628 | return name ? Boot.config[name] : Boot.config; | |
629 | }, | |
630 | ||
631 | /* | |
632 | * Set the configuration. | |
633 | * @param {Object} config The config object to override the default values. | |
634 | * @return {Ext.Boot} this | |
635 | */ | |
636 | setConfig: function (name, value) { | |
637 | if (typeof name === 'string') { | |
638 | Boot.config[name] = value; | |
639 | } else { | |
640 | for (var s in name) { | |
641 | Boot.setConfig(s, name[s]); | |
642 | } | |
643 | } | |
644 | return Boot; | |
645 | }, | |
646 | ||
647 | getHead: function () { | |
648 | return Boot.docHead || | |
649 | (Boot.docHead = doc.head || | |
650 | doc.getElementsByTagName('head')[0]); | |
651 | }, | |
652 | ||
653 | create: function (url, key, cfg) { | |
654 | var config = cfg || {}; | |
655 | config.url = url; | |
656 | config.key = key; | |
657 | return Boot.scripts[key] = new Entry(config); | |
658 | }, | |
659 | ||
660 | getEntry: function (url, cfg) { | |
661 | var key = Boot.canonicalUrl(url), | |
662 | entry = Boot.scripts[key]; | |
663 | if (!entry) { | |
664 | entry = Boot.create(url, key, cfg); | |
665 | } | |
666 | return entry; | |
667 | }, | |
668 | ||
669 | registerContent: function (url, type, content) { | |
670 | var cfg = { | |
671 | content: content, | |
672 | loaded: true, | |
673 | css: type === 'css' | |
674 | }; | |
675 | ||
676 | return Boot.getEntry(url, cfg); | |
677 | }, | |
678 | ||
679 | processRequest: function(request, sync) { | |
680 | request.loadEntries(sync); | |
681 | }, | |
682 | ||
683 | load: function (request) { | |
684 | var request = new Request(request); | |
685 | ||
686 | if (request.sync || Boot.syncMode) { | |
687 | return Boot.loadSync(request); | |
688 | } | |
689 | ||
690 | // If there is a request in progress, we must | |
691 | // queue this new request to be fired when the current request completes. | |
692 | if (Boot.currentRequest) { | |
693 | // trigger assignment of entries now to ensure that overlapping | |
694 | // entries with currently running requests will synchronize state | |
695 | // with this pending one as they complete | |
696 | request.getEntries(); | |
697 | Boot.suspendedQueue.push(request); | |
698 | } else { | |
699 | Boot.currentRequest = request; | |
700 | Boot.processRequest(request, false); | |
701 | } | |
702 | return Boot; | |
703 | }, | |
704 | ||
705 | loadSync: function (request) { | |
706 | var request = new Request(request); | |
707 | ||
708 | Boot.syncMode++; | |
709 | Boot.processRequest(request, true); | |
710 | Boot.syncMode--; | |
711 | return Boot; | |
712 | }, | |
713 | ||
714 | loadBasePrefix: function(request) { | |
715 | request = new Request(request); | |
716 | request.prependBaseUrl = true; | |
717 | return Boot.load(request); | |
718 | }, | |
719 | ||
720 | loadSyncBasePrefix: function(request) { | |
721 | request = new Request(request); | |
722 | request.prependBaseUrl = true; | |
723 | return Boot.loadSync(request); | |
724 | }, | |
725 | ||
726 | requestComplete: function(request) { | |
727 | var next; | |
728 | ||
729 | if (Boot.currentRequest === request) { | |
730 | Boot.currentRequest = null; | |
731 | while(Boot.suspendedQueue.length > 0) { | |
732 | next = Boot.suspendedQueue.shift(); | |
733 | if(!next.done) { | |
734 | Boot.load(next); | |
735 | break; | |
736 | } | |
737 | } | |
738 | } | |
739 | if (!Boot.currentRequest && Boot.suspendedQueue.length == 0) { | |
740 | Boot.fireListeners(); | |
741 | } | |
742 | }, | |
743 | ||
744 | isLoading: function () { | |
745 | return !Boot.currentRequest && Boot.suspendedQueue.length == 0; | |
746 | }, | |
747 | ||
748 | fireListeners: function () { | |
749 | var listener; | |
750 | while (Boot.isLoading() && (listener = Boot.listeners.shift())) { | |
751 | listener(); | |
752 | } | |
753 | }, | |
754 | ||
755 | onBootReady: function (listener) { | |
756 | if (!Boot.isLoading()) { | |
757 | listener(); | |
758 | } else { | |
759 | Boot.listeners.push(listener); | |
760 | } | |
761 | }, | |
762 | ||
763 | /* | |
764 | * this is a helper function used by Ext.Loader to flush out | |
765 | * 'uses' arrays for classes | |
766 | */ | |
767 | getPathsFromIndexes: function (indexMap, loadOrder) { | |
768 | return Request.prototype.getPathsFromIndexes(indexMap, loadOrder); | |
769 | }, | |
770 | ||
771 | createLoadOrderMap: function(loadOrder) { | |
772 | return Request.prototype.createLoadOrderMap(loadOrder); | |
773 | }, | |
774 | ||
775 | fetch: function(url, complete, scope, async) { | |
776 | async = (async === undefined) ? !!complete : async; | |
777 | ||
778 | var xhr = new XMLHttpRequest(), | |
779 | result, status, content, exception = false, | |
780 | readyStateChange = function () { | |
781 | if (xhr && xhr.readyState == 4) { | |
782 | status = (xhr.status === 1223) ? 204 : | |
783 | (xhr.status === 0 && ((self.location || {}).protocol === 'file:' || | |
784 | (self.location || {}).protocol === 'ionp:')) ? 200 : xhr.status; | |
785 | content = xhr.responseText; | |
786 | result = { | |
787 | content: content, | |
788 | status: status, | |
789 | exception: exception | |
790 | }; | |
791 | if (complete) { | |
792 | complete.call(scope, result); | |
793 | } | |
794 | xhr = null; | |
795 | } | |
796 | }; | |
797 | ||
798 | if (async) { | |
799 | xhr.onreadystatechange = readyStateChange; | |
800 | } | |
801 | ||
802 | try { | |
803 | xhr.open('GET', url, async); | |
804 | xhr.send(null); | |
805 | } catch (err) { | |
806 | exception = err; | |
807 | readyStateChange(); | |
808 | return result; | |
809 | } | |
810 | ||
811 | if (!async) { | |
812 | readyStateChange(); | |
813 | } | |
814 | ||
815 | return result; | |
816 | }, | |
817 | ||
818 | notifyAll: function(entry) { | |
819 | entry.notifyRequests(); | |
820 | } | |
821 | }; | |
822 | ||
823 | /* | |
824 | * The request class encapsulates a series of Entry objects | |
825 | * and provides notification around the completion of all Entries | |
826 | * in this request. | |
827 | */ | |
828 | function Request(cfg) { | |
829 | if(cfg.$isRequest) { | |
830 | return cfg; | |
831 | } | |
832 | ||
833 | var cfg = cfg.url ? cfg : {url: cfg}, | |
834 | url = cfg.url, | |
835 | urls = url.charAt ? [ url ] : url, | |
836 | charset = cfg.charset || Boot.config.charset; | |
837 | ||
838 | _apply(cfg, { | |
839 | urls: urls, | |
840 | charset: charset | |
841 | }); | |
842 | _apply(this, cfg); | |
843 | }; | |
844 | Request.prototype = { | |
845 | $isRequest: true, | |
846 | ||
847 | /* | |
848 | * @private | |
849 | * @param manifest | |
850 | * @returns {*} | |
851 | */ | |
852 | createLoadOrderMap: function (loadOrder) { | |
853 | var len = loadOrder.length, | |
854 | loadOrderMap = {}, | |
855 | i, element; | |
856 | ||
857 | for (i = 0; i < len; i++) { | |
858 | element = loadOrder[i]; | |
859 | loadOrderMap[element.path] = element; | |
860 | } | |
861 | ||
862 | return loadOrderMap; | |
863 | }, | |
864 | ||
865 | /* | |
866 | * @private | |
867 | * @param index | |
868 | * @param indexMap | |
869 | * @returns {{}} | |
870 | */ | |
871 | getLoadIndexes: function (index, indexMap, loadOrder, includeUses, skipLoaded) { | |
872 | var item = loadOrder[index], | |
873 | len, i, reqs, entry, stop, added, idx, ridx, url; | |
874 | ||
875 | if (indexMap[index]) { | |
876 | // prevent cycles | |
877 | return indexMap; | |
878 | } | |
879 | ||
880 | indexMap[index] = true; | |
881 | ||
882 | stop = false; | |
883 | while (!stop) { | |
884 | added = false; | |
885 | ||
886 | // iterate the requirements for each index and | |
887 | // accumulate in the index map | |
888 | for (idx in indexMap) { | |
889 | if (indexMap.hasOwnProperty(idx)) { | |
890 | item = loadOrder[idx]; | |
891 | if (!item) { | |
892 | continue; | |
893 | } | |
894 | url = this.prepareUrl(item.path); | |
895 | entry = Boot.getEntry(url); | |
896 | if (!skipLoaded || !entry || !entry.done) { | |
897 | reqs = item.requires; | |
898 | if (includeUses && item.uses) { | |
899 | reqs = reqs.concat(item.uses); | |
900 | } | |
901 | for (len = reqs.length, i = 0; i < len; i++) { | |
902 | ridx = reqs[i]; | |
903 | // if we find a requirement that wasn't | |
904 | // already in the index map, | |
905 | // set the added flag to indicate we need to | |
906 | // reprocess | |
907 | if (!indexMap[ridx]) { | |
908 | indexMap[ridx] = true; | |
909 | added = true; | |
910 | } | |
911 | } | |
912 | } | |
913 | } | |
914 | } | |
915 | ||
916 | // if we made a pass through the index map and didn't add anything | |
917 | // then we can stop | |
918 | if (!added) { | |
919 | stop = true; | |
920 | } | |
921 | } | |
922 | ||
923 | return indexMap; | |
924 | }, | |
925 | ||
926 | getPathsFromIndexes: function (indexMap, loadOrder) { | |
927 | var indexes = [], | |
928 | paths = [], | |
929 | index, len, i; | |
930 | ||
931 | for (index in indexMap) { | |
932 | if (indexMap.hasOwnProperty(index) && indexMap[index]) { | |
933 | indexes.push(index); | |
934 | } | |
935 | } | |
936 | ||
937 | indexes.sort(function (a, b) { | |
938 | return a - b; | |
939 | }); | |
940 | ||
941 | // convert indexes back into load paths | |
942 | for (len = indexes.length, i = 0; i < len; i++) { | |
943 | paths.push(loadOrder[indexes[i]].path); | |
944 | } | |
945 | ||
946 | return paths; | |
947 | }, | |
948 | ||
949 | expandUrl: function (url, indexMap, includeUses, skipLoaded) { | |
950 | if (typeof url == 'string') { | |
951 | url = [url]; | |
952 | } | |
953 | ||
954 | var me = this, | |
955 | loadOrder = me.loadOrder, | |
956 | loadOrderMap = me.loadOrderMap; | |
957 | ||
958 | if (loadOrder) { | |
959 | loadOrderMap = loadOrderMap || me.createLoadOrderMap(loadOrder); | |
960 | me.loadOrderMap = loadOrderMap; | |
961 | indexMap = indexMap || {}; | |
962 | var len = url.length, | |
963 | unmapped = [], | |
964 | i, item; | |
965 | ||
966 | for (i = 0; i < len; i++) { | |
967 | item = loadOrderMap[url[i]]; | |
968 | if (item) { | |
969 | me.getLoadIndexes(item.idx, indexMap, loadOrder, includeUses, skipLoaded); | |
970 | } else { | |
971 | unmapped.push(url[i]); | |
972 | } | |
973 | } | |
974 | ||
975 | ||
976 | return me.getPathsFromIndexes(indexMap, loadOrder).concat(unmapped); | |
977 | } | |
978 | return url; | |
979 | }, | |
980 | ||
981 | expandUrls: function (urls, includeUses) { | |
982 | if (typeof urls == "string") { | |
983 | urls = [urls]; | |
984 | } | |
985 | ||
986 | var expanded = [], | |
987 | expandMap = {}, | |
988 | tmpExpanded, | |
989 | len = urls.length, | |
990 | i, t, tlen, tUrl; | |
991 | ||
992 | for (i = 0; i < len; i++) { | |
993 | tmpExpanded = this.expandUrl(urls[i], {}, includeUses, true); | |
994 | for (t = 0, tlen = tmpExpanded.length; t < tlen; t++) { | |
995 | tUrl = tmpExpanded[t]; | |
996 | if (!expandMap[tUrl]) { | |
997 | expandMap[tUrl] = true; | |
998 | expanded.push(tUrl); | |
999 | } | |
1000 | } | |
1001 | } | |
1002 | ||
1003 | if (expanded.length == 0) { | |
1004 | expanded = urls; | |
1005 | } | |
1006 | ||
1007 | return expanded; | |
1008 | }, | |
1009 | ||
1010 | expandLoadOrder: function () { | |
1011 | var me = this, | |
1012 | urls = me.urls, | |
1013 | expanded; | |
1014 | ||
1015 | if (!me.expanded) { | |
1016 | expanded = this.expandUrls(urls, true); | |
1017 | me.expanded = true; | |
1018 | } else { | |
1019 | expanded = urls; | |
1020 | } | |
1021 | ||
1022 | me.urls = expanded; | |
1023 | ||
1024 | // if we added some urls to the request to honor the indicated | |
1025 | // load order, the request needs to be sequential | |
1026 | if (urls.length != expanded.length) { | |
1027 | me.sequential = true; | |
1028 | } | |
1029 | ||
1030 | return me; | |
1031 | }, | |
1032 | ||
1033 | getUrls: function () { | |
1034 | this.expandLoadOrder(); | |
1035 | return this.urls; | |
1036 | }, | |
1037 | ||
1038 | prepareUrl: function(url) { | |
1039 | if(this.prependBaseUrl) { | |
1040 | return Boot.baseUrl + url; | |
1041 | } | |
1042 | return url; | |
1043 | }, | |
1044 | ||
1045 | getEntries: function () { | |
1046 | var me = this, | |
1047 | entries = me.entries, | |
1048 | i, entry, urls, url; | |
1049 | if (!entries) { | |
1050 | entries = []; | |
1051 | urls = me.getUrls(); | |
1052 | for (i = 0; i < urls.length; i++) { | |
1053 | url = me.prepareUrl(urls[i]); | |
1054 | entry = Boot.getEntry(url, { | |
1055 | buster: me.buster, | |
1056 | charset: me.charset | |
1057 | }); | |
1058 | entry.requests.push(me); | |
1059 | entries.push(entry); | |
1060 | } | |
1061 | me.entries = entries; | |
1062 | } | |
1063 | return entries; | |
1064 | }, | |
1065 | ||
1066 | loadEntries: function(sync) { | |
1067 | var me = this, | |
1068 | entries = me.getEntries(), | |
1069 | len = entries.length, | |
1070 | start = me.loadStart || 0, | |
1071 | continueLoad, entry, i; | |
1072 | ||
1073 | if(sync !== undefined) { | |
1074 | me.sync = sync; | |
1075 | } | |
1076 | ||
1077 | me.loaded = me.loaded || 0; | |
1078 | me.loading = me.loading || len; | |
1079 | ||
1080 | for(i = start; i < len; i++) { | |
1081 | entry = entries[i]; | |
1082 | if(!entry.loaded) { | |
1083 | continueLoad = entries[i].load(me.sync); | |
1084 | } else { | |
1085 | continueLoad = true; | |
1086 | } | |
1087 | if(!continueLoad) { | |
1088 | me.loadStart = i; | |
1089 | entry.onDone(function(){ | |
1090 | me.loadEntries(sync); | |
1091 | }); | |
1092 | break; | |
1093 | } | |
1094 | } | |
1095 | me.processLoadedEntries(); | |
1096 | }, | |
1097 | ||
1098 | processLoadedEntries: function () { | |
1099 | var me = this, | |
1100 | entries = me.getEntries(), | |
1101 | len = entries.length, | |
1102 | start = me.startIndex || 0, | |
1103 | i, entry; | |
1104 | ||
1105 | if (!me.done) { | |
1106 | for (i = start; i < len; i++) { | |
1107 | entry = entries[i]; | |
1108 | ||
1109 | if (!entry.loaded) { | |
1110 | me.startIndex = i; | |
1111 | return; | |
1112 | } | |
1113 | ||
1114 | if (!entry.evaluated) { | |
1115 | entry.evaluate(); | |
1116 | } | |
1117 | ||
1118 | if (entry.error) { | |
1119 | me.error = true; | |
1120 | } | |
1121 | } | |
1122 | me.notify(); | |
1123 | } | |
1124 | }, | |
1125 | ||
1126 | notify: function () { | |
1127 | var me = this; | |
1128 | if (!me.done) { | |
1129 | var error = me.error, | |
1130 | fn = me[error ? 'failure' : 'success'], | |
1131 | delay = ('delay' in me) | |
1132 | ? me.delay | |
1133 | : (error ? 1 : Boot.config.chainDelay), | |
1134 | scope = me.scope || me; | |
1135 | me.done = true; | |
1136 | if (fn) { | |
1137 | if (delay === 0 || delay > 0) { | |
1138 | // Free the stack (and defer the next script) | |
1139 | setTimeout(function () { | |
1140 | fn.call(scope, me); | |
1141 | }, delay); | |
1142 | } else { | |
1143 | fn.call(scope, me); | |
1144 | } | |
1145 | } | |
1146 | me.fireListeners(); | |
1147 | Boot.requestComplete(me); | |
1148 | } | |
1149 | }, | |
1150 | ||
1151 | onDone: function(listener) { | |
1152 | var me = this, | |
1153 | listeners = me.listeners || (me.listeners = []); | |
1154 | if(me.done) { | |
1155 | listener(me); | |
1156 | } else { | |
1157 | listeners.push(listener); | |
1158 | } | |
1159 | }, | |
1160 | ||
1161 | fireListeners: function() { | |
1162 | var listeners = this.listeners, | |
1163 | listener; | |
1164 | if(listeners) { | |
1165 | while((listener = listeners.shift())) { | |
1166 | listener(this); | |
1167 | } | |
1168 | } | |
1169 | } | |
1170 | }; | |
1171 | ||
1172 | /* | |
1173 | * The Entry class is a token to manage the load and evaluation | |
1174 | * state of a particular url. It is used to notify all Requests | |
1175 | * interested in this url that the content is available. | |
1176 | */ | |
1177 | function Entry(cfg) { | |
1178 | if(cfg.$isEntry) { | |
1179 | return cfg; | |
1180 | } | |
1181 | ||
1182 | ||
1183 | var charset = cfg.charset || Boot.config.charset, | |
1184 | manifest = Ext.manifest, | |
1185 | loader = manifest && manifest.loader, | |
1186 | cache = (cfg.cache !== undefined) ? cfg.cache : (loader && loader.cache), | |
1187 | buster, busterParam; | |
1188 | ||
1189 | if (Boot.config.disableCaching) { | |
1190 | if (cache === undefined) { | |
1191 | cache = !Boot.config.disableCaching; | |
1192 | } | |
1193 | ||
1194 | if (cache === false) { | |
1195 | buster = +new Date(); | |
1196 | } else if (cache !== true) { | |
1197 | buster = cache; | |
1198 | } | |
1199 | ||
1200 | if (buster) { | |
1201 | busterParam = (loader && loader.cacheParam) || Boot.config.disableCachingParam; | |
1202 | buster = busterParam + "=" + buster; | |
1203 | } | |
1204 | } | |
1205 | ||
1206 | _apply(cfg, { | |
1207 | charset: charset, | |
1208 | buster: buster, | |
1209 | requests: [] | |
1210 | }); | |
1211 | _apply(this, cfg); | |
1212 | }; | |
1213 | Entry.prototype = { | |
1214 | $isEntry: true, | |
1215 | done: false, | |
1216 | evaluated: false, | |
1217 | loaded: false, | |
1218 | ||
1219 | isCrossDomain: function() { | |
1220 | var me = this; | |
1221 | if(me.crossDomain === undefined) { | |
1222 | me.crossDomain = (me.getLoadUrl().indexOf(Boot.origin) !== 0); | |
1223 | } | |
1224 | return me.crossDomain; | |
1225 | }, | |
1226 | ||
1227 | isCss: function () { | |
1228 | var me = this; | |
1229 | if (me.css === undefined) { | |
1230 | if (me.url) { | |
1231 | var assetConfig = Boot.assetConfig[me.url]; | |
1232 | me.css = assetConfig ? assetConfig.type === "css" : cssRe.test(me.url); | |
1233 | } else { | |
1234 | me.css = false; | |
1235 | } | |
1236 | } | |
1237 | return this.css; | |
1238 | }, | |
1239 | ||
1240 | getElement: function (tag) { | |
1241 | var me = this, | |
1242 | el = me.el; | |
1243 | if (!el) { | |
1244 | if (me.isCss()) { | |
1245 | tag = tag || "link"; | |
1246 | el = doc.createElement(tag); | |
1247 | if(tag == "link") { | |
1248 | el.rel = 'stylesheet'; | |
1249 | me.prop = 'href'; | |
1250 | } else { | |
1251 | me.prop="textContent"; | |
1252 | } | |
1253 | el.type = "text/css"; | |
1254 | } else { | |
1255 | tag = tag || "script"; | |
1256 | el = doc.createElement(tag); | |
1257 | el.type = 'text/javascript'; | |
1258 | me.prop = 'src'; | |
1259 | ||
1260 | if (me.charset) { | |
1261 | el.charset = me.charset; | |
1262 | } | |
1263 | ||
1264 | if (Boot.hasAsync) { | |
1265 | el.async = false; | |
1266 | } | |
1267 | } | |
1268 | me.el = el; | |
1269 | } | |
1270 | return el; | |
1271 | }, | |
1272 | ||
1273 | getLoadUrl: function () { | |
1274 | var me = this, | |
1275 | url = Boot.canonicalUrl(me.url); | |
1276 | if (!me.loadUrl) { | |
1277 | me.loadUrl = !!me.buster | |
1278 | ? (url + (url.indexOf('?') === -1 ? '?' : '&') + me.buster) | |
1279 | : url; | |
1280 | } | |
1281 | return me.loadUrl; | |
1282 | }, | |
1283 | ||
1284 | fetch: function (req) { | |
1285 | var url = this.getLoadUrl(), | |
1286 | async = !!req.async, | |
1287 | complete = req.complete; | |
1288 | ||
1289 | Boot.fetch(url, complete, this, async); | |
1290 | }, | |
1291 | ||
1292 | onContentLoaded: function (response) { | |
1293 | var me = this, | |
1294 | status = response.status, | |
1295 | content = response.content, | |
1296 | exception = response.exception, | |
1297 | url = this.getLoadUrl(); | |
1298 | me.loaded = true; | |
1299 | if ((exception || status === 0) && !_environment.phantom) { | |
1300 | me.error = | |
1301 | true; | |
1302 | me.evaluated = true; | |
1303 | } | |
1304 | else if ((status >= 200 && status < 300) || status === 304 | |
1305 | || _environment.phantom | |
1306 | || (status === 0 && content.length > 0) | |
1307 | ) { | |
1308 | me.content = content; | |
1309 | } | |
1310 | else { | |
1311 | me.error = | |
1312 | true; | |
1313 | me.evaluated = true; | |
1314 | } | |
1315 | }, | |
1316 | ||
1317 | createLoadElement: function(callback) { | |
1318 | var me = this, | |
1319 | el = me.getElement(), | |
1320 | readyStateChange = function(){ | |
1321 | if (this.readyState === 'loaded' || this.readyState === 'complete') { | |
1322 | if(callback) { | |
1323 | callback(); | |
1324 | } | |
1325 | } | |
1326 | }, | |
1327 | errorFn = function() { | |
1328 | me.error = true; | |
1329 | if(callback) { | |
1330 | callback(); | |
1331 | } | |
1332 | }; | |
1333 | me.preserve = true; | |
1334 | el.onerror = errorFn; | |
1335 | if(Boot.hasReadyState) { | |
1336 | el.onreadystatechange = readyStateChange; | |
1337 | } else { | |
1338 | el.onload = callback; | |
1339 | } | |
1340 | // IE starts loading here | |
1341 | el[me.prop] = me.getLoadUrl(); | |
1342 | }, | |
1343 | ||
1344 | onLoadElementReady: function() { | |
1345 | Boot.getHead().appendChild(this.getElement()); | |
1346 | this.evaluated = true; | |
1347 | }, | |
1348 | ||
1349 | inject: function (content, asset) { | |
1350 | var me = this, | |
1351 | head = Boot.getHead(), | |
1352 | url = me.url, | |
1353 | key = me.key, | |
1354 | base, el, ieMode, basePath; | |
1355 | ||
1356 | if (me.isCss()) { | |
1357 | me.preserve = true; | |
1358 | basePath = key.substring(0, key.lastIndexOf("/") + 1); | |
1359 | base = doc.createElement('base'); | |
1360 | base.href = basePath; | |
1361 | if(head.firstChild) { | |
1362 | head.insertBefore(base, head.firstChild); | |
1363 | } else { | |
1364 | head.appendChild(base); | |
1365 | } | |
1366 | // reset the href attribute to cuase IE to pick up the change | |
1367 | base.href = base.href; | |
1368 | ||
1369 | if (url) { | |
1370 | content += "\n/*# sourceURL=" + key + " */"; | |
1371 | } | |
1372 | ||
1373 | // create element after setting base | |
1374 | el = me.getElement("style"); | |
1375 | ||
1376 | ieMode = ('styleSheet' in el); | |
1377 | ||
1378 | head.appendChild(base); | |
1379 | if(ieMode) { | |
1380 | head.appendChild(el); | |
1381 | el.styleSheet.cssText = content; | |
1382 | } else { | |
1383 | el.textContent = content; | |
1384 | head.appendChild(el); | |
1385 | } | |
1386 | head.removeChild(base); | |
1387 | ||
1388 | } else { | |
1389 | // Debugger friendly, file names are still shown even though they're | |
1390 | // eval'ed code. Breakpoints work on both Firebug and Chrome's Web | |
1391 | // Inspector. | |
1392 | if (url) { | |
1393 | content += "\n//# sourceURL=" + key; | |
1394 | } | |
1395 | Ext.globalEval(content); | |
1396 | } | |
1397 | return me; | |
1398 | }, | |
1399 | ||
1400 | loadCrossDomain: function() { | |
1401 | var me = this, | |
1402 | complete = function(){ | |
1403 | me.loaded = me.evaluated = me.done = true; | |
1404 | me.notifyRequests(); | |
1405 | }; | |
1406 | me.createLoadElement(function(){ | |
1407 | complete(); | |
1408 | }); | |
1409 | me.evaluateLoadElement(); | |
1410 | // at this point, we need sequential evaluation, | |
1411 | // which means we can't advance the load until | |
1412 | // this entry has fully completed | |
1413 | return false; | |
1414 | }, | |
1415 | ||
1416 | loadElement: function() { | |
1417 | var me = this, | |
1418 | complete = function(){ | |
1419 | me.loaded = me.evaluated = me.done = true; | |
1420 | me.notifyRequests(); | |
1421 | }; | |
1422 | me.createLoadElement(function(){ | |
1423 | complete(); | |
1424 | }); | |
1425 | me.evaluateLoadElement(); | |
1426 | return true; | |
1427 | }, | |
1428 | ||
1429 | loadSync: function() { | |
1430 | var me = this; | |
1431 | me.fetch({ | |
1432 | async: false, | |
1433 | complete: function (response) { | |
1434 | me.onContentLoaded(response); | |
1435 | } | |
1436 | }); | |
1437 | me.evaluate(); | |
1438 | me.notifyRequests(); | |
1439 | }, | |
1440 | ||
1441 | load: function (sync) { | |
1442 | var me = this; | |
1443 | if (!me.loaded) { | |
1444 | if(me.loading) { | |
1445 | // if we're calling back through load and we're loading but haven't | |
1446 | // yet loaded, then we should be in a sequential, cross domain | |
1447 | // load scenario which means we can't continue the load on the | |
1448 | // request until this entry has fully evaluated, which will mean | |
1449 | // loaded = evaluated = done = true in one step. For css files, this | |
1450 | // will happen immediately upon <link> element creation / insertion, | |
1451 | // but <script> elements will set this upon load notification | |
1452 | return false; | |
1453 | } | |
1454 | me.loading = true; | |
1455 | ||
1456 | // for async modes, we have some options | |
1457 | if (!sync) { | |
1458 | // if cross domain, just inject the script tag and let the onload | |
1459 | // events drive the progression | |
1460 | if(me.isCrossDomain()) { | |
1461 | return me.loadCrossDomain(); | |
1462 | } | |
1463 | // for IE, use the readyStateChange allows us to load scripts in parallel | |
1464 | // but serialize the evaluation by appending the script node to the | |
1465 | // document | |
1466 | else if(!me.isCss() && Boot.hasReadyState) { | |
1467 | me.createLoadElement(function () { | |
1468 | me.loaded = true; | |
1469 | me.notifyRequests(); | |
1470 | }); | |
1471 | } | |
1472 | ||
1473 | else if(Boot.useElements && | |
1474 | // older webkit, phantomjs included, won't fire load for link elements | |
1475 | !(me.isCss() && _environment.phantom)) { | |
1476 | return me.loadElement(); | |
1477 | } | |
1478 | // for other browsers, just ajax the content down in parallel, and use | |
1479 | // globalEval to serialize evaluation | |
1480 | else { | |
1481 | me.fetch({ | |
1482 | async: !sync, | |
1483 | complete: function (response) { | |
1484 | me.onContentLoaded(response); | |
1485 | me.notifyRequests(); | |
1486 | } | |
1487 | }); | |
1488 | } | |
1489 | } | |
1490 | ||
1491 | // for sync mode in js, global eval FTW. IE won't honor the comment | |
1492 | // paths in the debugger, so eventually we need a sync mode for IE that | |
1493 | // uses the readyStateChange mechanism | |
1494 | else { | |
1495 | me.loadSync(); | |
1496 | } | |
1497 | } | |
1498 | // signal that the load process can continue | |
1499 | return true; | |
1500 | }, | |
1501 | ||
1502 | evaluateContent: function () { | |
1503 | this.inject(this.content); | |
1504 | this.content = null; | |
1505 | }, | |
1506 | ||
1507 | evaluateLoadElement: function() { | |
1508 | Boot.getHead().appendChild(this.getElement()); | |
1509 | }, | |
1510 | ||
1511 | evaluate: function () { | |
1512 | var me = this; | |
1513 | if(!me.evaluated) { | |
1514 | if(me.evaluating) { | |
1515 | return; | |
1516 | } | |
1517 | me.evaluating = true; | |
1518 | if(me.content !== undefined) { | |
1519 | me.evaluateContent(); | |
1520 | } else if(!me.error) { | |
1521 | me.evaluateLoadElement(); | |
1522 | } | |
1523 | me.evaluated = me.done = true; | |
1524 | me.cleanup(); | |
1525 | } | |
1526 | }, | |
1527 | ||
1528 | /* | |
1529 | * @private | |
1530 | */ | |
1531 | cleanup: function () { | |
1532 | var me = this, | |
1533 | el = me.el, | |
1534 | prop; | |
1535 | ||
1536 | if (!el) { | |
1537 | return; | |
1538 | } | |
1539 | ||
1540 | if (!me.preserve) { | |
1541 | me.el = null; | |
1542 | ||
1543 | el.parentNode.removeChild(el); // Remove, since its useless now | |
1544 | ||
1545 | for (prop in el) { | |
1546 | try { | |
1547 | if (prop !== me.prop) { | |
1548 | // If we set the src property to null IE | |
1549 | // will try and request a script at './null' | |
1550 | el[prop] = null; | |
1551 | } | |
1552 | delete el[prop]; // and prepare for GC | |
1553 | } catch (cleanEx) { | |
1554 | //ignore | |
1555 | } | |
1556 | } | |
1557 | } | |
1558 | ||
1559 | // Setting to null can cause exceptions if IE ever needs to call these | |
1560 | // again (like onreadystatechange). This emptyFn has nothing locked in | |
1561 | // closure scope so it is about as safe as null for memory leaks. | |
1562 | el.onload = el.onerror = el.onreadystatechange = emptyFn; | |
1563 | }, | |
1564 | ||
1565 | notifyRequests: function () { | |
1566 | var requests = this.requests, | |
1567 | len = requests.length, | |
1568 | i, request; | |
1569 | for (i = 0; i < len; i++) { | |
1570 | request = requests[i]; | |
1571 | request.processLoadedEntries(); | |
1572 | } | |
1573 | if(this.done) { | |
1574 | this.fireListeners(); | |
1575 | } | |
1576 | }, | |
1577 | ||
1578 | onDone: function(listener) { | |
1579 | var me = this, | |
1580 | listeners = me.listeners || (me.listeners = []); | |
1581 | if(me.done) { | |
1582 | listener(me); | |
1583 | } else { | |
1584 | listeners.push(listener); | |
1585 | } | |
1586 | }, | |
1587 | ||
1588 | fireListeners: function() { | |
1589 | var listeners = this.listeners, | |
1590 | listener; | |
1591 | if(listeners && listeners.length > 0) { | |
1592 | while((listener = listeners.shift())) { | |
1593 | listener(this); | |
1594 | } | |
1595 | } | |
1596 | } | |
1597 | }; | |
1598 | ||
1599 | /* | |
1600 | * Turns on or off the "cache buster" applied to dynamically loaded scripts. Normally | |
1601 | * dynamically loaded scripts have an extra query parameter appended to avoid stale | |
1602 | * cached scripts. This method can be used to disable this mechanism, and is primarily | |
1603 | * useful for testing. This is done using a cookie. | |
1604 | * @param {Boolean} disable True to disable the cache buster. | |
1605 | * @param {String} [path="/"] An optional path to scope the cookie. | |
1606 | */ | |
1607 | Ext.disableCacheBuster = function (disable, path) { | |
1608 | var date = new Date(); | |
1609 | date.setTime(date.getTime() + (disable ? 10 * 365 : -1) * 24 * 60 * 60 * 1000); | |
1610 | date = date.toGMTString(); | |
1611 | doc.cookie = 'ext-cache=1; expires=' + date + '; path=' + (path || '/'); | |
1612 | }; | |
1613 | ||
1614 | ||
1615 | Boot.init(); | |
1616 | return Boot; | |
1617 | ||
1618 | // NOTE: We run the eval at global scope to protect the body of the function and allow | |
1619 | // compressors to still process it. | |
1620 | }(function () { | |
1621 | }));//(eval("/*@cc_on!@*/!1")); | |
1622 | ||
1623 | /* | |
1624 | * This method evaluates the given code free of any local variable. This | |
1625 | * will be at global scope, in others it will be in a function. | |
1626 | * @parma {String} code The code to evaluate. | |
1627 | * @private | |
1628 | * @method | |
1629 | */ | |
1630 | Ext.globalEval = Ext.globalEval || (this.execScript | |
1631 | ? function (code) { execScript(code); } | |
1632 | : function ($$code) { eval.call(window, $$code); }); | |
1633 | ||
1634 | //<feature legacyBrowser> | |
1635 | /* | |
1636 | * Only IE8 & IE/Quirks lack Function.prototype.bind so we polyfill that here. | |
1637 | */ | |
1638 | if (!Function.prototype.bind) { | |
1639 | (function () { | |
1640 | var slice = Array.prototype.slice, | |
1641 | // To reduce overhead on call of the bound fn we have two flavors based on | |
1642 | // whether we have args to prepend or not: | |
1643 | bind = function (me) { | |
1644 | var args = slice.call(arguments, 1), | |
1645 | method = this; | |
1646 | ||
1647 | if (args.length) { | |
1648 | return function () { | |
1649 | var t = arguments; | |
1650 | // avoid the slice/concat if the caller does not supply args | |
1651 | return method.apply(me, t.length ? args.concat(slice.call(t)) : args); | |
1652 | }; | |
1653 | } | |
1654 | // this is the majority use case - just fn.bind(this) and no args | |
1655 | ||
1656 | args = null; | |
1657 | return function () { | |
1658 | return method.apply(me, arguments); | |
1659 | }; | |
1660 | }; | |
1661 | Function.prototype.bind = bind; | |
1662 | bind.$extjs = true; // to detect this polyfill if one want to improve it | |
1663 | }()); | |
1664 | } | |
1665 | //</feature> | |
1666 | ||
1667 | //</editor-fold> | |
1668 | ||
1669 | Ext.setResourcePath = function (poolName, path) { | |
1670 | var manifest = Ext.manifest || (Ext.manifest = {}), | |
1671 | paths = manifest.resources || (manifest.resources = {}); | |
1672 | ||
1673 | if (manifest) { | |
1674 | if (typeof poolName !== 'string') { | |
1675 | Ext.apply(paths, poolName); | |
1676 | } else { | |
1677 | paths[poolName] = path; | |
1678 | } | |
1679 | manifest.resources = paths; | |
1680 | } | |
1681 | }; | |
1682 | ||
1683 | Ext.getResourcePath = function (path, poolName, packageName) { | |
1684 | if (typeof path !== 'string') { | |
1685 | poolName = path.pool; | |
1686 | packageName = path.packageName; | |
1687 | path = path.path; | |
1688 | } | |
1689 | var manifest = Ext.manifest, | |
1690 | paths = manifest && manifest.resources, | |
1691 | poolPath = paths[poolName], | |
1692 | output = []; | |
1693 | ||
1694 | if (poolPath == null) { | |
1695 | poolPath = paths.path; | |
1696 | if (poolPath == null) { | |
1697 | poolPath = 'resources'; | |
1698 | } | |
1699 | } | |
1700 | ||
1701 | if (poolPath) { | |
1702 | output.push(poolPath); | |
1703 | } | |
1704 | ||
1705 | if (packageName) { | |
1706 | output.push(packageName); | |
1707 | } | |
1708 | ||
1709 | output.push(path); | |
1710 | return output.join('/'); | |
1711 | }; | |
1712 | // here, the extra check for window['Ext'] is needed for use with cmd-test | |
1713 | // code injection. we need to make that this file will sync up with page global | |
1714 | // scope to avoid duplicate Ext.Boot state. That check is after the initial Ext check | |
1715 | // to allow the sandboxing template to inject an appropriate Ext var and prevent the | |
1716 | // global detection. | |
1717 | var Ext = Ext || window['Ext'] || {}; | |
1718 | ||
1719 | ||
1720 | //<editor-fold desc="Microloader"> | |
1721 | /** | |
1722 | * @Class Ext.Microloader | |
1723 | * @singleton | |
1724 | */ | |
1725 | Ext.Microloader = Ext.Microloader || (function () { | |
1726 | var Boot = Ext.Boot, | |
1727 | _warn = function (message) { | |
1728 | console.log("[WARN] " + message); | |
1729 | }, | |
1730 | _privatePrefix = '_ext:' + location.pathname, | |
1731 | ||
1732 | /** | |
1733 | * The Following combination is used to create isolated local storage keys | |
1734 | * '_ext' is used to scope all the local storage keys that we internally by Ext | |
1735 | * 'location.pathname' is used to force each assets to cache by an absolute URL (/build/MyApp) (dev vs prod) | |
1736 | * 'url' is used to force each asset to cache relative to the page (app.json vs resources/app.css) | |
1737 | * 'profileId' is used to differentiate the builds of an application (neptune vs crisp) | |
1738 | * 'Microloader.appId' is unique to the application and will differentiate apps on the same host (dev mode running app watch against multiple apps) | |
1739 | */ | |
1740 | getStorageKey = function(url, profileId) { | |
1741 | return _privatePrefix + url + '-' + (profileId ? profileId + '-' : '') + Microloader.appId; | |
1742 | }, | |
1743 | postProcessor, _storage; | |
1744 | ||
1745 | try { | |
1746 | _storage = window['localStorage']; | |
1747 | } catch(ex) { | |
1748 | // ignore | |
1749 | } | |
1750 | ||
1751 | var _cache = window['applicationCache'], | |
1752 | // Local Storage Controller | |
1753 | LocalStorage = { | |
1754 | clearAllPrivate: function(manifest) { | |
1755 | if(_storage) { | |
1756 | ||
1757 | //Remove the entry for the manifest first | |
1758 | _storage.removeItem(manifest.key); | |
1759 | ||
1760 | var i, key, | |
1761 | removeKeys = [], | |
1762 | suffix = manifest.profile + '-' + Microloader.appId, | |
1763 | ln = _storage.length; | |
1764 | for (i = 0; i < ln; i++) { | |
1765 | key = _storage.key(i); | |
1766 | // If key starts with the private key and the suffix is present we can clear this entry | |
1767 | if (key.indexOf(_privatePrefix) === 0 && key.indexOf(suffix) !== -1) { | |
1768 | removeKeys.push(key); | |
1769 | } | |
1770 | } | |
1771 | ||
1772 | for(i in removeKeys) { | |
1773 | _storage.removeItem(removeKeys[i]); | |
1774 | } | |
1775 | } | |
1776 | }, | |
1777 | /** | |
1778 | * private | |
1779 | */ | |
1780 | retrieveAsset: function (key) { | |
1781 | try { | |
1782 | return _storage.getItem(key); | |
1783 | } | |
1784 | catch (e) { | |
1785 | // Private browsing mode | |
1786 | return null; | |
1787 | } | |
1788 | }, | |
1789 | ||
1790 | setAsset: function(key, content) { | |
1791 | try { | |
1792 | if (content === null || content == '') { | |
1793 | _storage.removeItem(key); | |
1794 | } else { | |
1795 | _storage.setItem(key, content); | |
1796 | } | |
1797 | } | |
1798 | catch (e) { | |
1799 | if (_storage && e.code == e.QUOTA_EXCEEDED_ERR) { | |
1800 | } | |
1801 | } | |
1802 | } | |
1803 | }; | |
1804 | ||
1805 | var Asset = function (cfg) { | |
1806 | if (typeof cfg.assetConfig === 'string') { | |
1807 | this.assetConfig = { | |
1808 | path: cfg.assetConfig | |
1809 | }; | |
1810 | } else { | |
1811 | this.assetConfig = cfg.assetConfig; | |
1812 | } | |
1813 | ||
1814 | this.type = cfg.type; | |
1815 | this.key = getStorageKey(this.assetConfig.path, cfg.manifest.profile); | |
1816 | ||
1817 | if (cfg.loadFromCache) { | |
1818 | this.loadFromCache(); | |
1819 | } | |
1820 | }; | |
1821 | ||
1822 | Asset.prototype = { | |
1823 | shouldCache: function() { | |
1824 | return _storage && this.assetConfig.update && this.assetConfig.hash && !this.assetConfig.remote; | |
1825 | }, | |
1826 | ||
1827 | is: function (asset) { | |
1828 | return (!!asset && this.assetConfig && asset.assetConfig && (this.assetConfig.hash === asset.assetConfig.hash)) | |
1829 | }, | |
1830 | ||
1831 | cache: function(content) { | |
1832 | if (this.shouldCache()) { | |
1833 | LocalStorage.setAsset(this.key, content || this.content); | |
1834 | } | |
1835 | }, | |
1836 | ||
1837 | uncache: function() { | |
1838 | LocalStorage.setAsset(this.key, null); | |
1839 | }, | |
1840 | ||
1841 | updateContent: function (content) { | |
1842 | this.content = content; | |
1843 | }, | |
1844 | ||
1845 | getSize: function () { | |
1846 | return this.content ? this.content.length : 0; | |
1847 | }, | |
1848 | ||
1849 | loadFromCache: function() { | |
1850 | if (this.shouldCache()) { | |
1851 | this.content = LocalStorage.retrieveAsset(this.key); | |
1852 | } | |
1853 | } | |
1854 | }; | |
1855 | ||
1856 | var Manifest = function (cfg) { | |
1857 | if (typeof cfg.content === "string") { | |
1858 | this.content = JSON.parse(cfg.content); | |
1859 | } else { | |
1860 | this.content = cfg.content; | |
1861 | } | |
1862 | this.assetMap = {}; | |
1863 | ||
1864 | this.url = cfg.url; | |
1865 | this.fromCache = !!cfg.cached; | |
1866 | this.assetCache = !(cfg.assetCache === false); | |
1867 | this.key = getStorageKey(this.url); | |
1868 | ||
1869 | // Pull out select properties for repetitive use | |
1870 | this.profile = this.content.profile; | |
1871 | this.hash = this.content.hash; | |
1872 | this.loadOrder = this.content.loadOrder; | |
1873 | this.deltas = this.content.cache ? this.content.cache.deltas : null; | |
1874 | this.cacheEnabled = this.content.cache ? this.content.cache.enable : false; | |
1875 | ||
1876 | this.loadOrderMap = (this.loadOrder) ? Boot.createLoadOrderMap(this.loadOrder) : null; | |
1877 | ||
1878 | var tags = this.content.tags, | |
1879 | platformTags = Ext.platformTags; | |
1880 | ||
1881 | if (tags) { | |
1882 | if (tags instanceof Array) { | |
1883 | for (var i = 0; i < tags.length; i++) { | |
1884 | platformTags[tags[i]] = true; | |
1885 | } | |
1886 | } else { | |
1887 | Boot.apply(platformTags, tags); | |
1888 | } | |
1889 | ||
1890 | // re-apply the query parameters, so that the params as specified | |
1891 | // in the url always has highest priority | |
1892 | Boot.apply(platformTags, Boot.loadPlatformsParam()); | |
1893 | } | |
1894 | ||
1895 | // Convert all assets into Assets | |
1896 | this.js = this.processAssets(this.content.js, 'js'); | |
1897 | this.css = this.processAssets(this.content.css, 'css'); | |
1898 | }; | |
1899 | ||
1900 | Manifest.prototype = { | |
1901 | processAsset: function(assetConfig, type) { | |
1902 | var processedAsset = new Asset({ | |
1903 | manifest: this, | |
1904 | assetConfig: assetConfig, | |
1905 | type: type, | |
1906 | loadFromCache: this.assetCache | |
1907 | }); | |
1908 | this.assetMap[assetConfig.path] = processedAsset; | |
1909 | return processedAsset; | |
1910 | }, | |
1911 | ||
1912 | processAssets: function(assets, type) { | |
1913 | var results = [], | |
1914 | ln = assets.length, | |
1915 | i, assetConfig; | |
1916 | ||
1917 | for (i = 0; i < ln; i++) { | |
1918 | assetConfig = assets[i]; | |
1919 | results.push(this.processAsset(assetConfig, type)); | |
1920 | } | |
1921 | ||
1922 | return results; | |
1923 | }, | |
1924 | ||
1925 | useAppCache: function() { | |
1926 | return true; | |
1927 | }, | |
1928 | ||
1929 | // Concatenate all assets for easy access | |
1930 | getAssets: function () { | |
1931 | return this.css.concat(this.js); | |
1932 | }, | |
1933 | ||
1934 | getAsset: function (path) { | |
1935 | return this.assetMap[path]; | |
1936 | }, | |
1937 | ||
1938 | shouldCache: function() { | |
1939 | return this.hash && this.cacheEnabled; | |
1940 | }, | |
1941 | ||
1942 | cache: function(content) { | |
1943 | if (this.shouldCache()) { | |
1944 | LocalStorage.setAsset(this.key, JSON.stringify(content || this.content)); | |
1945 | } | |
1946 | }, | |
1947 | ||
1948 | is: function(manifest) { | |
1949 | return this.hash === manifest.hash; | |
1950 | }, | |
1951 | ||
1952 | // Clear the manifest from local storage | |
1953 | uncache: function() { | |
1954 | LocalStorage.setAsset(this.key, null); | |
1955 | }, | |
1956 | ||
1957 | exportContent: function() { | |
1958 | return Boot.apply({ | |
1959 | loadOrderMap: this.loadOrderMap | |
1960 | }, this.content); | |
1961 | } | |
1962 | }; | |
1963 | ||
1964 | /** | |
1965 | * Microloader | |
1966 | * @type {Array} | |
1967 | * @private | |
1968 | */ | |
1969 | var _listeners = [], | |
1970 | _loaded = false, | |
1971 | Microloader = { | |
1972 | init: function () { | |
1973 | Ext.microloaded = true; | |
1974 | ||
1975 | // data-app is in the dev template for an application and is also | |
1976 | // injected into the app my CMD for production | |
1977 | // We use this to prefix localStorage cache to prevent collisions | |
1978 | var microloaderElement = document.getElementById('microloader'); | |
1979 | Microloader.appId = microloaderElement ? microloaderElement.getAttribute('data-app') : ''; | |
1980 | ||
1981 | if (Ext.beforeLoad) { | |
1982 | postProcessor = Ext.beforeLoad(Ext.platformTags); | |
1983 | } | |
1984 | ||
1985 | var readyHandler = Ext._beforereadyhandler; | |
1986 | ||
1987 | Ext._beforereadyhandler = function () { | |
1988 | if (Ext.Boot !== Boot) { | |
1989 | Ext.apply(Ext.Boot, Boot); | |
1990 | Ext.Boot = Boot; | |
1991 | } | |
1992 | if (readyHandler) { | |
1993 | readyHandler(); | |
1994 | } | |
1995 | }; | |
1996 | }, | |
1997 | ||
1998 | applyCacheBuster: function(url) { | |
1999 | var tstamp = new Date().getTime(), | |
2000 | sep = url.indexOf('?') === -1 ? '?' : '&'; | |
2001 | url = url + sep + "_dc=" + tstamp; | |
2002 | return url; | |
2003 | }, | |
2004 | ||
2005 | run: function() { | |
2006 | Microloader.init(); | |
2007 | var manifest = Ext.manifest; | |
2008 | ||
2009 | if (typeof manifest === "string") { | |
2010 | var extension = ".json", | |
2011 | url = manifest.indexOf(extension) === manifest.length - extension.length | |
2012 | ? manifest | |
2013 | : manifest + ".json", | |
2014 | key = getStorageKey(url), | |
2015 | content = LocalStorage.retrieveAsset(key); | |
2016 | ||
2017 | // Manifest found in local storage, use this for immediate boot except in PhantomJS environments for building. | |
2018 | if (content) { | |
2019 | manifest = new Manifest({ | |
2020 | url: url, | |
2021 | content: content, | |
2022 | cached: true | |
2023 | }); | |
2024 | if (postProcessor) { | |
2025 | postProcessor(manifest); | |
2026 | } | |
2027 | Microloader.load(manifest); | |
2028 | ||
2029 | ||
2030 | // Manifest is not in local storage. Fetch it from the server | |
2031 | } else { | |
2032 | Boot.fetch(Microloader.applyCacheBuster(url), function (result) { | |
2033 | manifest = new Manifest({ | |
2034 | url: url, | |
2035 | content: result.content | |
2036 | }); | |
2037 | ||
2038 | manifest.cache(); | |
2039 | if (postProcessor) { | |
2040 | postProcessor(manifest); | |
2041 | } | |
2042 | Microloader.load(manifest); | |
2043 | }); | |
2044 | } | |
2045 | ||
2046 | // Embedded Manifest into JS file | |
2047 | } else { | |
2048 | manifest = new Manifest({ | |
2049 | content: manifest | |
2050 | }); | |
2051 | Microloader.load(manifest); | |
2052 | } | |
2053 | }, | |
2054 | ||
2055 | /** | |
2056 | * | |
2057 | * @param {Manifest} manifest | |
2058 | */ | |
2059 | load: function (manifest) { | |
2060 | Microloader.urls = []; | |
2061 | Microloader.manifest = manifest; | |
2062 | Ext.manifest = Microloader.manifest.exportContent(); | |
2063 | ||
2064 | var assets = manifest.getAssets(), | |
2065 | cachedAssets = [], | |
2066 | asset, i, len, include, entry; | |
2067 | ||
2068 | for (len = assets.length, i = 0; i < len; i++) { | |
2069 | asset = assets[i]; | |
2070 | include = Microloader.filterAsset(asset); | |
2071 | if (include) { | |
2072 | // Asset is using the localStorage caching system | |
2073 | if (manifest.shouldCache() && asset.shouldCache()) { | |
2074 | // Asset already has content from localStorage, instantly seed that into boot | |
2075 | if (asset.content) { | |
2076 | entry = Boot.registerContent(asset.assetConfig.path, asset.type, asset.content); | |
2077 | if (entry.evaluated) { | |
2078 | _warn("Asset: " + asset.assetConfig.path + " was evaluated prior to local storage being consulted."); | |
2079 | } | |
2080 | //load via AJAX and seed content into Boot | |
2081 | } else { | |
2082 | cachedAssets.push(asset); | |
2083 | } | |
2084 | } | |
2085 | Microloader.urls.push(asset.assetConfig.path); | |
2086 | Boot.assetConfig[asset.assetConfig.path] = Boot.apply({type: asset.type}, asset.assetConfig); | |
2087 | } | |
2088 | } | |
2089 | ||
2090 | // If any assets are using the caching system and do not have local versions load them first via AJAX | |
2091 | if (cachedAssets.length > 0) { | |
2092 | Microloader.remainingCachedAssets = cachedAssets.length; | |
2093 | while (cachedAssets.length > 0) { | |
2094 | asset = cachedAssets.pop(); | |
2095 | Boot.fetch(asset.assetConfig.path, (function(asset) { | |
2096 | return function(result) { | |
2097 | Microloader.onCachedAssetLoaded(asset, result); | |
2098 | } | |
2099 | })(asset)); | |
2100 | } | |
2101 | } else { | |
2102 | Microloader.onCachedAssetsReady(); | |
2103 | } | |
2104 | }, | |
2105 | ||
2106 | // Load the asset and seed its content into Boot to be evaluated in sequence | |
2107 | onCachedAssetLoaded: function (asset, result) { | |
2108 | var checksum; | |
2109 | result = Microloader.parseResult(result); | |
2110 | Microloader.remainingCachedAssets--; | |
2111 | ||
2112 | if (!result.error) { | |
2113 | checksum = Microloader.checksum(result.content, asset.assetConfig.hash); | |
2114 | if (!checksum) { | |
2115 | _warn("Cached Asset '" + asset.assetConfig.path + "' has failed checksum. This asset will be uncached for future loading"); | |
2116 | ||
2117 | // Un cache this asset so it is loaded next time | |
2118 | asset.uncache(); | |
2119 | } | |
2120 | ||
2121 | Boot.registerContent(asset.assetConfig.path, asset.type, result.content); | |
2122 | asset.updateContent(result.content); | |
2123 | asset.cache(); | |
2124 | } else { | |
2125 | _warn("There was an error pre-loading the asset '" + asset.assetConfig.path + "'. This asset will be uncached for future loading"); | |
2126 | ||
2127 | // Un cache this asset so it is loaded next time | |
2128 | asset.uncache(); | |
2129 | } | |
2130 | ||
2131 | if (Microloader.remainingCachedAssets === 0) { | |
2132 | Microloader.onCachedAssetsReady(); | |
2133 | } | |
2134 | }, | |
2135 | ||
2136 | onCachedAssetsReady: function(){ | |
2137 | Boot.load({ | |
2138 | url: Microloader.urls, | |
2139 | loadOrder: Microloader.manifest.loadOrder, | |
2140 | loadOrderMap: Microloader.manifest.loadOrderMap, | |
2141 | sequential: true, | |
2142 | success: Microloader.onAllAssetsReady, | |
2143 | failure: Microloader.onAllAssetsReady | |
2144 | }); | |
2145 | }, | |
2146 | ||
2147 | onAllAssetsReady: function() { | |
2148 | _loaded = true; | |
2149 | Microloader.notify(); | |
2150 | ||
2151 | if (navigator.onLine !== false) { | |
2152 | Microloader.checkAllUpdates(); | |
2153 | } | |
2154 | else { | |
2155 | if(window['addEventListener']) { | |
2156 | window.addEventListener('online', Microloader.checkAllUpdates, false); | |
2157 | } | |
2158 | } | |
2159 | }, | |
2160 | ||
2161 | onMicroloaderReady: function (listener) { | |
2162 | if (_loaded) { | |
2163 | listener(); | |
2164 | } else { | |
2165 | _listeners.push(listener); | |
2166 | } | |
2167 | }, | |
2168 | ||
2169 | /** | |
2170 | * @private | |
2171 | */ | |
2172 | notify: function () { | |
2173 | var listener; | |
2174 | while((listener = _listeners.shift())) { | |
2175 | listener(); | |
2176 | } | |
2177 | }, | |
2178 | ||
2179 | // Delta patches content | |
2180 | patch: function (content, delta) { | |
2181 | var output = [], | |
2182 | chunk, i, ln; | |
2183 | ||
2184 | if (delta.length === 0) { | |
2185 | return content; | |
2186 | } | |
2187 | ||
2188 | for (i = 0,ln = delta.length; i < ln; i++) { | |
2189 | chunk = delta[i]; | |
2190 | ||
2191 | if (typeof chunk === 'number') { | |
2192 | output.push(content.substring(chunk, chunk + delta[++i])); | |
2193 | } | |
2194 | else { | |
2195 | output.push(chunk); | |
2196 | } | |
2197 | } | |
2198 | ||
2199 | return output.join(''); | |
2200 | }, | |
2201 | ||
2202 | checkAllUpdates: function() { | |
2203 | if(window['removeEventListener']) { | |
2204 | window.removeEventListener('online', Microloader.checkAllUpdates, false); | |
2205 | } | |
2206 | ||
2207 | if(_cache) { | |
2208 | Microloader.checkForAppCacheUpdate(); | |
2209 | } | |
2210 | ||
2211 | // Manifest came from a cached instance, check for updates | |
2212 | if (Microloader.manifest.fromCache) { | |
2213 | Microloader.checkForUpdates(); | |
2214 | } | |
2215 | }, | |
2216 | ||
2217 | checkForAppCacheUpdate: function() { | |
2218 | if (_cache.status === _cache.UPDATEREADY || _cache.status === _cache.OBSOLETE) { | |
2219 | Microloader.appCacheState = 'updated'; | |
2220 | } else if (_cache.status !== _cache.IDLE && _cache.status !== _cache.UNCACHED) { | |
2221 | Microloader.appCacheState = 'checking'; | |
2222 | _cache.addEventListener('error', Microloader.onAppCacheError); | |
2223 | _cache.addEventListener('noupdate', Microloader.onAppCacheNotUpdated); | |
2224 | _cache.addEventListener('cached', Microloader.onAppCacheNotUpdated); | |
2225 | _cache.addEventListener('updateready', Microloader.onAppCacheReady); | |
2226 | _cache.addEventListener('obsolete', Microloader.onAppCacheObsolete); | |
2227 | } else { | |
2228 | Microloader.appCacheState = 'current'; | |
2229 | } | |
2230 | }, | |
2231 | ||
2232 | checkForUpdates: function() { | |
2233 | // Fetch the Latest Manifest from the server | |
2234 | Boot.fetch(Microloader.applyCacheBuster(Microloader.manifest.url), Microloader.onUpdatedManifestLoaded); | |
2235 | }, | |
2236 | ||
2237 | onAppCacheError: function(e) { | |
2238 | _warn(e.message); | |
2239 | ||
2240 | Microloader.appCacheState = 'error'; | |
2241 | Microloader.notifyUpdateReady(); | |
2242 | }, | |
2243 | ||
2244 | onAppCacheReady: function() { | |
2245 | _cache.swapCache(); | |
2246 | Microloader.appCacheUpdated(); | |
2247 | }, | |
2248 | ||
2249 | onAppCacheObsolete: function() { | |
2250 | Microloader.appCacheUpdated(); | |
2251 | }, | |
2252 | ||
2253 | appCacheUpdated: function() { | |
2254 | Microloader.appCacheState = 'updated'; | |
2255 | Microloader.notifyUpdateReady(); | |
2256 | }, | |
2257 | ||
2258 | onAppCacheNotUpdated: function() { | |
2259 | Microloader.appCacheState = 'current'; | |
2260 | Microloader.notifyUpdateReady(); | |
2261 | }, | |
2262 | ||
2263 | ||
2264 | filterAsset: function(asset) { | |
2265 | var cfg = (asset && asset.assetConfig) || {}; | |
2266 | if(cfg.platform || cfg.exclude) { | |
2267 | return Boot.filterPlatform(cfg.platform, cfg.exclude); | |
2268 | } | |
2269 | return true; | |
2270 | }, | |
2271 | ||
2272 | onUpdatedManifestLoaded: function (result) { | |
2273 | result = Microloader.parseResult(result); | |
2274 | ||
2275 | if (!result.error) { | |
2276 | var currentAssets, newAssets, currentAsset, newAsset, prop, | |
2277 | assets, deltas, deltaPath, include, | |
2278 | updatingAssets = [], | |
2279 | manifest = new Manifest({ | |
2280 | url: Microloader.manifest.url, | |
2281 | content: result.content, | |
2282 | assetCache: false | |
2283 | }); | |
2284 | ||
2285 | Microloader.remainingUpdatingAssets = 0; | |
2286 | Microloader.updatedAssets = []; | |
2287 | Microloader.removedAssets = []; | |
2288 | Microloader.updatedManifest = null; | |
2289 | Microloader.updatedAssetsReady = false; | |
2290 | ||
2291 | // If the updated manifest has turned off caching we need to clear out all local storage | |
2292 | // and trigger a appupdate as all content is now uncached | |
2293 | if (!manifest.shouldCache()) { | |
2294 | ||
2295 | Microloader.updatedManifest = manifest; | |
2296 | LocalStorage.clearAllPrivate(manifest); | |
2297 | Microloader.onAllUpdatedAssetsReady(); | |
2298 | return; | |
2299 | } | |
2300 | ||
2301 | // Manifest itself has changed | |
2302 | if (!Microloader.manifest.is(manifest)) { | |
2303 | Microloader.updatedManifest = manifest; | |
2304 | ||
2305 | currentAssets = Microloader.manifest.getAssets(); | |
2306 | newAssets = manifest.getAssets(); | |
2307 | ||
2308 | // Look through new assets for assets that do not exist or assets that have different versions | |
2309 | for (prop in newAssets) { | |
2310 | newAsset = newAssets[prop]; | |
2311 | currentAsset = Microloader.manifest.getAsset(newAsset.assetConfig.path); | |
2312 | include = Microloader.filterAsset(newAsset); | |
2313 | ||
2314 | if (include && (!currentAsset || (newAsset.shouldCache() && (!currentAsset.is(newAsset))))) { | |
2315 | updatingAssets.push({_new: newAsset, _current: currentAsset}); | |
2316 | } | |
2317 | } | |
2318 | ||
2319 | // Look through current assets for stale/old assets that have been removed | |
2320 | for (prop in currentAssets) { | |
2321 | currentAsset = currentAssets[prop]; | |
2322 | newAsset = manifest.getAsset(currentAsset.assetConfig.path); | |
2323 | ||
2324 | //New version of this asset has been filtered out | |
2325 | include = !Microloader.filterAsset(newAsset); | |
2326 | ||
2327 | if (!include || !newAsset || (currentAsset.shouldCache() && !newAsset.shouldCache())) { | |
2328 | Microloader.removedAssets.push(currentAsset); | |
2329 | } | |
2330 | } | |
2331 | ||
2332 | // Loop through all assets that need updating | |
2333 | if (updatingAssets.length > 0) { | |
2334 | Microloader.remainingUpdatingAssets = updatingAssets.length; | |
2335 | while (updatingAssets.length > 0) { | |
2336 | assets = updatingAssets.pop(); | |
2337 | newAsset = assets._new; | |
2338 | currentAsset = assets._current; | |
2339 | ||
2340 | // Full Updates will simply download the file and replace its current content | |
2341 | if (newAsset.assetConfig.update === "full" || !currentAsset) { | |
2342 | ||
2343 | ||
2344 | // Load the asset and cache its its content into Boot to be evaluated in sequence | |
2345 | Boot.fetch(newAsset.assetConfig.path, (function (asset) { | |
2346 | return function (result) { | |
2347 | Microloader.onFullAssetUpdateLoaded(asset, result) | |
2348 | }; | |
2349 | }(newAsset)) | |
2350 | ); | |
2351 | ||
2352 | // Delta updates will be given a delta patch | |
2353 | } else if (newAsset.assetConfig.update === "delta") { | |
2354 | deltas = manifest.deltas; | |
2355 | deltaPath = deltas + "/" + newAsset.assetConfig.path + "/" + currentAsset.assetConfig.hash + ".json"; | |
2356 | // Fetch the Delta Patch and update the contents of the asset | |
2357 | Boot.fetch(deltaPath, | |
2358 | (function (asset, oldAsset) { | |
2359 | return function (result) { | |
2360 | Microloader.onDeltaAssetUpdateLoaded(asset, oldAsset, result) | |
2361 | }; | |
2362 | }(newAsset, currentAsset)) | |
2363 | ); | |
2364 | } | |
2365 | } | |
2366 | } else { | |
2367 | Microloader.onAllUpdatedAssetsReady(); | |
2368 | } | |
2369 | } else { | |
2370 | Microloader.onAllUpdatedAssetsReady(); | |
2371 | } | |
2372 | } else { | |
2373 | _warn("Error loading manifest file to check for updates"); | |
2374 | Microloader.onAllUpdatedAssetsReady(); | |
2375 | } | |
2376 | }, | |
2377 | ||
2378 | onFullAssetUpdateLoaded: function(asset, result) { | |
2379 | var checksum; | |
2380 | result = Microloader.parseResult(result); | |
2381 | Microloader.remainingUpdatingAssets--; | |
2382 | ||
2383 | if (!result.error) { | |
2384 | checksum = Microloader.checksum(result.content, asset.assetConfig.hash); | |
2385 | if (!checksum) { | |
2386 | ||
2387 | // uncache this asset as there is a new version somewhere that has not been loaded. | |
2388 | asset.uncache(); | |
2389 | } else { | |
2390 | asset.updateContent(result.content); | |
2391 | Microloader.updatedAssets.push(asset); | |
2392 | } | |
2393 | } else { | |
2394 | ||
2395 | // uncache this asset as there is a new version somewhere that has not been loaded. | |
2396 | asset.uncache(); | |
2397 | } | |
2398 | ||
2399 | if (Microloader.remainingUpdatingAssets === 0) { | |
2400 | Microloader.onAllUpdatedAssetsReady(); | |
2401 | } | |
2402 | }, | |
2403 | ||
2404 | onDeltaAssetUpdateLoaded: function(asset, oldAsset, result) { | |
2405 | var json, checksum, content; | |
2406 | result = Microloader.parseResult(result); | |
2407 | Microloader.remainingUpdatingAssets--; | |
2408 | ||
2409 | if (!result.error) { | |
2410 | try { | |
2411 | json = JSON.parse(result.content); | |
2412 | content = Microloader.patch(oldAsset.content, json); | |
2413 | checksum = Microloader.checksum(content, asset.assetConfig.hash); | |
2414 | if (!checksum) { | |
2415 | ||
2416 | // uncache this asset as there is a new version somewhere that has not been loaded. | |
2417 | asset.uncache(); | |
2418 | } else { | |
2419 | asset.updateContent(content); | |
2420 | Microloader.updatedAssets.push(asset); | |
2421 | } | |
2422 | } catch (e) { | |
2423 | _warn("Error parsing delta patch for " + asset.assetConfig.path + " with hash " + oldAsset.assetConfig.hash + " . This asset will be uncached for future loading"); | |
2424 | // uncache this asset as there is a new version somewhere that has not been loaded. | |
2425 | asset.uncache(); | |
2426 | } | |
2427 | } else { | |
2428 | _warn("Error loading delta patch for " + asset.assetConfig.path + " with hash " + oldAsset.assetConfig.hash + " . This asset will be uncached for future loading"); | |
2429 | ||
2430 | // uncache this asset as there is a new version somewhere that has not been loaded. | |
2431 | asset.uncache(); | |
2432 | } | |
2433 | if (Microloader.remainingUpdatingAssets === 0) { | |
2434 | Microloader.onAllUpdatedAssetsReady(); | |
2435 | } | |
2436 | }, | |
2437 | ||
2438 | //TODO: Make this all transaction based to allow for reverting if quota is exceeded | |
2439 | onAllUpdatedAssetsReady: function() { | |
2440 | var asset; | |
2441 | Microloader.updatedAssetsReady = true; | |
2442 | ||
2443 | if (Microloader.updatedManifest) { | |
2444 | while (Microloader.removedAssets.length > 0) { | |
2445 | asset = Microloader.removedAssets.pop(); | |
2446 | asset.uncache(); | |
2447 | } | |
2448 | ||
2449 | if (Microloader.updatedManifest) { | |
2450 | Microloader.updatedManifest.cache(); | |
2451 | } | |
2452 | ||
2453 | while (Microloader.updatedAssets.length > 0) { | |
2454 | asset = Microloader.updatedAssets.pop(); | |
2455 | asset.cache(); | |
2456 | } | |
2457 | ||
2458 | } | |
2459 | ||
2460 | Microloader.notifyUpdateReady(); | |
2461 | }, | |
2462 | ||
2463 | notifyUpdateReady: function () { | |
2464 | if (Microloader.appCacheState !== 'checking' && Microloader.updatedAssetsReady) { | |
2465 | if (Microloader.appCacheState === 'updated' || Microloader.updatedManifest) { | |
2466 | Microloader.appUpdate = { | |
2467 | updated: true, | |
2468 | app: Microloader.appCacheState === 'updated', | |
2469 | manifest: Microloader.updatedManifest && Microloader.updatedManifest.exportContent() | |
2470 | }; | |
2471 | ||
2472 | Microloader.fireAppUpdate(); | |
2473 | } | |
2474 | } | |
2475 | }, | |
2476 | ||
2477 | fireAppUpdate: function() { | |
2478 | if (Ext.GlobalEvents) { | |
2479 | // We defer dispatching this event slightly in order to let the application finish loading | |
2480 | // as we are still very early in the lifecycle | |
2481 | Ext.defer(function() { | |
2482 | Ext.GlobalEvents.fireEvent('appupdate', Microloader.appUpdate); | |
2483 | }, 100); | |
2484 | } | |
2485 | }, | |
2486 | ||
2487 | checksum: function(content, hash) { | |
2488 | if(!content || !hash) { | |
2489 | return false; | |
2490 | } | |
2491 | ||
2492 | var passed = true, | |
2493 | hashLn = hash.length, | |
2494 | checksumType = content.substring(0, 1); | |
2495 | ||
2496 | if (checksumType == '/') { | |
2497 | if (content.substring(2, hashLn + 2) !== hash) { | |
2498 | passed = false; | |
2499 | } | |
2500 | } else if (checksumType == 'f') { | |
2501 | if (content.substring(10, hashLn + 10) !== hash) { | |
2502 | passed = false; | |
2503 | } | |
2504 | } else if (checksumType == '.') { | |
2505 | if (content.substring(1, hashLn + 1) !== hash) { | |
2506 | passed = false; | |
2507 | } | |
2508 | } | |
2509 | return passed; | |
2510 | }, | |
2511 | parseResult: function(result) { | |
2512 | var rst = {}; | |
2513 | if ((result.exception || result.status === 0) && !Boot.env.phantom) { | |
2514 | rst.error = true; | |
2515 | } else if ((result.status >= 200 && result.status < 300) || result.status === 304 | |
2516 | || Boot.env.phantom | |
2517 | || (result.status === 0 && result.content.length > 0) | |
2518 | ) { | |
2519 | rst.content = result.content; | |
2520 | } else { | |
2521 | rst.error = true; | |
2522 | } | |
2523 | return rst; | |
2524 | } | |
2525 | }; | |
2526 | ||
2527 | return Microloader; | |
2528 | }()); | |
2529 | ||
2530 | /** | |
2531 | * @type {String/Object} | |
2532 | */ | |
2533 | Ext.manifest = Ext.manifest || "bootstrap"; | |
2534 | ||
2535 | Ext.Microloader.run(); |