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