// @tag core// @define Ext.Boot var Ext = Ext || {}; //<editor-fold desc="Boot">/** * @class Ext.Boot * @singleton * @private */Ext.Boot = Ext.Boot || (function (emptyFn) { var doc = document, _emptyArray = [], _config = { /** * @cfg {Boolean} [disableCaching=true] * If `true` current timestamp is added to script URL's to prevent caching. * In debug builds, adding a "cache" or "disableCacheBuster" query parameter * to the page's URL will set this to `false`. */ disableCaching: (/[?&](?:cache|disableCacheBuster)\b/i.test(location.search) || !(/http[s]?\:/i.test(location.href)) || /(^|[ ;])ext-cache=1/.test(doc.cookie)) ? false : true, /** * @cfg {String} [disableCachingParam="_dc"] * The query parameter name for the cache buster's timestamp. */ disableCachingParam: '_dc', /** * @cfg {Boolean} loadDelay * Millisecond delay between asynchronous script injection (prevents stack * overflow on some user agents) 'false' disables delay but potentially * increases stack load. */ loadDelay: false, /** * @cfg {Boolean} preserveScripts * `false` to remove asynchronously loaded scripts, `true` to retain script * element for browser debugger compatibility and improved load performance. */ preserveScripts: true, /** * @cfg {String} [charset=UTF-8] * Optional charset to specify encoding of dynamic content. */ charset: 'UTF-8' }, _assetConfig= {}, cssRe = /\.css(?:\?|$)/i, resolverEl = doc.createElement('a'), isBrowser = typeof window !== 'undefined', _environment = { browser: isBrowser, node: !isBrowser && (typeof require === 'function'), phantom: (window && (window._phantom || window.callPhantom)) || /PhantomJS/.test(window.navigator.userAgent) }, _tags = (Ext.platformTags = {}), //<debug> // All calls to _debug are commented out to speed up old browsers a bit; // yes that makes a difference because the cost of concatenating strings // and passing them into _debug() adds up pretty quickly. _debug = function (message) { //console.log(message); }, //</debug> _apply = function (object, config, defaults) { if (defaults) { _apply(object, defaults); } if (object && config && typeof config === 'object') { for (var i in config) { object[i] = config[i]; } } return object; }, _merge = function() { var lowerCase = false, obj = Array.prototype.shift.call(arguments), index, i, len, value; if (typeof arguments[arguments.length - 1] === 'boolean') { lowerCase = Array.prototype.pop.call(arguments); } len = arguments.length; for (index = 0; index < len; index++) { value = arguments[index]; if (typeof value === 'object') { for (i in value) { obj[lowerCase ? i.toLowerCase() : i] = value[i]; } } } return obj; }, _getKeys = (typeof Object.keys == 'function') ? function(object){ if (!object) { return []; } return Object.keys(object); } : function(object) { var keys = [], property; for (property in object) { if (object.hasOwnProperty(property)) { keys.push(property); } } return keys; }, /* * The Boot loader class manages Request objects that contain one or * more individual urls that need to be loaded. Requests can be performed * synchronously or asynchronously, but will always evaluate urls in the * order specified on the request object. */ Boot = { loading: 0, loaded: 0, apply: _apply, env: _environment, config: _config, /** * @cfg {Object} assetConfig * A map (url->assetConfig) that contains information about assets loaded by the Microlaoder. */ assetConfig: _assetConfig, // Keyed by absolute URL this object holds "true" if that URL is already loaded // or an array of callbacks to call once it loads. scripts: { /* Entry objects 'http://foo.com/bar/baz/Thing.js': { done: true, el: scriptEl || linkEl, preserve: true, requests: [ request1, ... ] } */ }, /** * contains the current script name being loaded * (loadSync or sequential load only) */ currentFile: null, suspendedQueue: [], currentRequest: null, // when loadSync is called, need to cause subsequent load requests to also be loadSync, // eg, when Ext.require(...) is called syncMode: false, /* * simple helper method for debugging */ //<debug> debug: _debug, //</debug> /** * enables / disables loading scripts via script / link elements rather * than using ajax / eval */ useElements: true, listeners: [], Request: Request, Entry: Entry, allowMultipleBrowsers: false, browserNames: { ie: 'IE', firefox: 'Firefox', safari: 'Safari', chrome: 'Chrome', opera: 'Opera', dolfin: 'Dolfin', edge: 'Edge', webosbrowser: 'webOSBrowser', chromeMobile: 'ChromeMobile', chromeiOS: 'ChromeiOS', silk: 'Silk', other: 'Other' }, osNames: { ios: 'iOS', android: 'Android', windowsPhone: 'WindowsPhone', webos: 'webOS', blackberry: 'BlackBerry', rimTablet: 'RIMTablet', mac: 'MacOS', win: 'Windows', tizen: 'Tizen', linux: 'Linux', bada: 'Bada', chromeOS: 'ChromeOS', other: 'Other' }, browserPrefixes: { ie: 'MSIE ', edge: 'Edge/', firefox: 'Firefox/', chrome: 'Chrome/', safari: 'Version/', opera: 'OPR/', dolfin: 'Dolfin/', webosbrowser: 'wOSBrowser/', chromeMobile: 'CrMo/', chromeiOS: 'CriOS/', silk: 'Silk/' }, // When a UA reports multiple browsers this list is used to prioritize the 'real' browser // lower index number will win browserPriority: [ 'edge', 'opera', 'dolfin', 'webosbrowser', 'silk', 'chromeiOS', 'chromeMobile', 'ie', 'firefox', 'safari', 'chrome' ], osPrefixes: { tizen: '(Tizen )', ios: 'i(?:Pad|Phone|Pod)(?:.*)CPU(?: iPhone)? OS ', android: '(Android |HTC_|Silk/)', // Some HTC devices ship with an OSX userAgent by default, // so we need to add a direct check for HTC_ windowsPhone: 'Windows Phone ', blackberry: '(?:BlackBerry|BB)(?:.*)Version\/', rimTablet: 'RIM Tablet OS ', webos: '(?:webOS|hpwOS)\/', bada: 'Bada\/', chromeOS: 'CrOS ' }, fallbackOSPrefixes: { windows: 'win', mac: 'mac', linux: 'linux' }, devicePrefixes: { iPhone: 'iPhone', iPod: 'iPod', iPad: 'iPad' }, maxIEVersion: 12, /** * The default function that detects various platforms and sets tags * in the platform map accordingly. Examples are iOS, android, tablet, etc. * @param tags the set of tags to populate */ detectPlatformTags: function () { var me = this, ua = navigator.userAgent, isMobile = /Mobile(\/|\s)/.test(ua), element = document.createElement('div'), isEventSupported = function (name, tag) { if (tag === undefined) { tag = window; } var eventName = 'on' + name.toLowerCase(), isSupported = (eventName in element); if (!isSupported) { if (element.setAttribute && element.removeAttribute) { element.setAttribute(eventName, ''); isSupported = typeof element[eventName] === 'function'; if (typeof element[eventName] !== 'undefined') { element[eventName] = undefined; } element.removeAttribute(eventName); } } return isSupported; }, // Browser Detection getBrowsers = function () { var browsers = {}, maxIEVersion, prefix, value, key, index, len, match, version, matched; // MS Edge browser (and possibly others) can report multiple browsers in the UserAgent // "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" // we use this to prioritize the actual browser in this situation len = me.browserPriority.length; for (index = 0; index < len; index++) { key = me.browserPriority[index]; if (!matched) { value = me.browserPrefixes[key]; match = ua.match(new RegExp('(' + value + ')([\\w\\._]+)')); version = match && match.length > 1 ? parseInt(match[2]) : 0; if (version) { matched = true; } } else { version = 0; } browsers[key] = version; } //Deal with IE document mode if (browsers.ie) { var mode = document.documentMode; if (mode >= 8) { browsers.ie = mode; } } // Fancy IE greater than and less then quick tags version = browsers.ie || false; maxIEVersion = Math.max(version, me.maxIEVersion); for (index = 8; index <= maxIEVersion; ++index) { prefix = 'ie' + index; browsers[prefix + 'm'] = version ? version <= index : 0; browsers[prefix] = version ? version === index : 0; browsers[prefix + 'p'] = version ? version >= index : 0; } return browsers; }, //OS Detection getOperatingSystems = function () { var systems = {}, value, key, keys, index, len, match, matched, version, activeCount; keys = _getKeys(me.osPrefixes); len = keys.length; for (index = 0, activeCount = 0; index < len; index++) { key = keys[index]; value = me.osPrefixes[key]; match = ua.match(new RegExp('(' + value + ')([^\\s;]+)')); matched = match ? match[1] : null; // This is here because some HTC android devices show an OSX Snow Leopard userAgent by default. // And the Kindle Fire doesn't have any indicator of Android as the OS in its User Agent if (matched && (matched === 'HTC_' || matched === 'Silk/')) { version = 2.3; } else { version = match && match.length > 1 ? parseFloat(match[match.length - 1]) : 0; } if (version) { activeCount++; } systems[key] = version; } keys = _getKeys(me.fallbackOSPrefixes); // If no OS could be found we resort to the fallbacks, otherwise we just // falsify the fallbacks len = keys.length; for (index = 0; index < len; index++) { key = keys[index]; // No OS was detected from osPrefixes if (activeCount === 0) { value = me.fallbackOSPrefixes[key]; match = ua.toLowerCase().match(new RegExp(value)); systems[key] = match ? true : 0; } else { systems[key] = 0; } } return systems; }, // Device Detection getDevices = function () { var devices = {}, value, key, keys, index, len, match; keys = _getKeys(me.devicePrefixes); len = keys.length; for (index = 0; index < len; index++) { key = keys[index]; value = me.devicePrefixes[key]; match = ua.match(new RegExp(value)); devices[key] = match ? true : 0; } return devices; }, browsers = getBrowsers(), systems = getOperatingSystems(), devices = getDevices(), platformParams = Boot.loadPlatformsParam(); // We apply platformParams from the query here first to allow for forced user valued // to be used in calculation of generated tags _merge(_tags, browsers, systems, devices, platformParams, true); _tags.phone = !!((_tags.iphone || _tags.ipod) || (!_tags.silk && (_tags.android && (_tags.android < 3 || isMobile))) || (_tags.blackberry && isMobile) || (_tags.windowsphone)); _tags.tablet = !!(!_tags.phone && ( _tags.ipad || _tags.android || _tags.silk || _tags.rimtablet || (_tags.ie10 && /; Touch/.test(ua)) )); _tags.touch = // if the browser has touch events we can be reasonably sure the device has // a touch screen isEventSupported('touchend') || // browsers that use pointer event have maxTouchPoints > 0 if the // device supports touch input // http://www.w3.org/TR/pointerevents/#widl-Navigator-maxTouchPoints navigator.maxTouchPoints || // IE10 uses a vendor-prefixed maxTouchPoints property navigator.msMaxTouchPoints; _tags.desktop = !_tags.phone && !_tags.tablet; _tags.cordova = _tags.phonegap = !!(window.PhoneGap || window.Cordova || window.cordova); _tags.webview = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)(?!.*FBAN)/i.test(ua); _tags.androidstock = (_tags.android <= 4.3) && (_tags.safari || _tags.silk); // Re-apply any query params here to allow for user override of generated tags (desktop, touch, tablet, etc) _merge(_tags, platformParams, true); }, /** * Extracts user supplied platform tags from the "platformTags" query parameter * of the form: * * ?platformTags=name:state,name:state,... * * (each tag defaults to true when state is unspecified) * * Example: * * ?platformTags=isTablet,isPhone:false,isDesktop:0,iOS:1,Safari:true, ... * * @returns {Object} the platform tags supplied by the query string */ loadPlatformsParam: function () { // Check if the ?platform parameter is set in the URL var paramsString = window.location.search.substr(1), paramsArray = paramsString.split("&"), params = {}, i, platforms = {}, tmpArray, tmplen, platform, name, enabled; for (i = 0; i < paramsArray.length; i++) { tmpArray = paramsArray[i].split("="); params[tmpArray[0]] = tmpArray[1]; } if (params.platformTags) { tmpArray = params.platformTags.split(","); for (tmplen = tmpArray.length, i = 0; i < tmplen; i++) { platform = tmpArray[i].split(":"); name = platform[0]; enabled=true; if (platform.length > 1) { enabled = platform[1]; if (enabled === 'false' || enabled === '0') { enabled = false; } } platforms[name] = enabled; } } return platforms; }, filterPlatform: function (platform, excludes) { platform = _emptyArray.concat(platform || _emptyArray); excludes = _emptyArray.concat(excludes || _emptyArray); var plen = platform.length, elen = excludes.length, include = (!plen && elen), // default true if only excludes specified i, tag; for (i = 0; i < plen && !include; i++) { tag = platform[i]; include = !!_tags[tag]; } for (i = 0; i < elen && include; i++) { tag = excludes[i]; include = !_tags[tag]; } return include; }, init: function () { var scriptEls = doc.getElementsByTagName('script'), script = scriptEls[0], len = scriptEls.length, re = /\/ext(\-[a-z\-]+)?\.js$/, entry, src, state, baseUrl, key, n, origin; // No check for script definedness because there always should be at least one Boot.hasReadyState = ("readyState" in script); Boot.hasAsync = ("async" in script); Boot.hasDefer = ("defer" in script); Boot.hasOnLoad = ("onload" in script); // Feature detecting IE Boot.isIE8 = Boot.hasReadyState && !Boot.hasAsync && Boot.hasDefer && !Boot.hasOnLoad; Boot.isIE9 = Boot.hasReadyState && !Boot.hasAsync && Boot.hasDefer && Boot.hasOnLoad; Boot.isIE10p = Boot.hasReadyState && Boot.hasAsync && Boot.hasDefer && Boot.hasOnLoad; Boot.isIE10 = (new Function('/*@cc_on return @_jscript_version @*/')()) === 10; Boot.isIE10m = Boot.isIE10 || Boot.isIE9 || Boot.isIE8; // IE11 does not support conditional compilation so we detect it by exclusion Boot.isIE11 = Boot.isIE10p && !Boot.isIE10; // Since we are loading after other scripts, and we needed to gather them // anyway, we track them in _scripts so we don't have to ask for them all // repeatedly. for (n = 0; n < len; n++) { src = (script = scriptEls[n]).src; if (!src) { continue; } state = script.readyState || null; // If we find a script file called "ext-*.js", then the base path is that file's base path. if (!baseUrl && re.test(src)) { baseUrl = src; } if (!Boot.scripts[key = Boot.canonicalUrl(src)]) { //<debug>// _debug("creating entry " + key + " in Boot.init"); //</debug> entry = new Entry({ key: key, url: src, done: state === null || // non-IE state === 'loaded' || state === 'complete', // IE only el: script, prop: 'src' }); } } if (!baseUrl) { script = scriptEls[scriptEls.length - 1]; baseUrl = script.src; } Boot.baseUrl = baseUrl.substring(0, baseUrl.lastIndexOf('/') + 1); origin = window.location.origin || window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: ''); Boot.origin = origin; Boot.detectPlatformTags(); Ext.filterPlatform = Boot.filterPlatform; }, /** * This method returns a canonical URL for the given URL. * * For example, the following all produce the same canonical URL (which is the * last one): * * http://foo.com/bar/baz/zoo/derp/../../goo/Thing.js?_dc=12345 * http://foo.com/bar/baz/zoo/derp/../../goo/Thing.js * http://foo.com/bar/baz/zoo/derp/../jazz/../../goo/Thing.js * http://foo.com/bar/baz/zoo/../goo/Thing.js * http://foo.com/bar/baz/goo/Thing.js * * @private */ canonicalUrl: function (url) { // *WARNING WARNING WARNING* // This method yields the most correct result we can get but it is EXPENSIVE! // In ALL browsers! When called multiple times in a sequence, as if when // we resolve dependencies for entries, it will cause garbage collection events // and overall painful slowness. This is why we try to avoid it as much as we can. // // @TODO - see if we need this fallback logic // http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue resolverEl.href = url; var ret = resolverEl.href, dc = _config.disableCachingParam, pos = dc ? ret.indexOf(dc + '=') : -1, c, end; // If we have a _dc query parameter we need to remove it from the canonical // URL. if (pos > 0 && ((c = ret.charAt(pos - 1)) === '?' || c === '&')) { end = ret.indexOf('&', pos); end = (end < 0) ? '' : ret.substring(end); if (end && c === '?') { ++pos; // keep the '?' end = end.substring(1); // remove the '&' } ret = ret.substring(0, pos - 1) + end; } return ret; }, /** * Get the config value corresponding to the specified name. If no name is given, will return the config object * @param {String} name The config property name * @return {Object} */ getConfig: function (name) { return name ? Boot.config[name] : Boot.config; }, /** * Set the configuration. * @param {Object} config The config object to override the default values. * @return {Ext.Boot} this */ setConfig: function (name, value) { if (typeof name === 'string') { Boot.config[name] = value; } else { for (var s in name) { Boot.setConfig(s, name[s]); } } return Boot; }, getHead: function () { return Boot.docHead || (Boot.docHead = doc.head || doc.getElementsByTagName('head')[0]); }, create: function (url, key, cfg) { var config = cfg || {}; config.url = url; config.key = key; return Boot.scripts[key] = new Entry(config); }, getEntry: function (url, cfg, canonicalPath) { var key, entry; // Canonicalizing URLs via anchor element href yields the most correct result // but is *extremely* resource heavy so we need to avoid it whenever possible key = canonicalPath ? url : Boot.canonicalUrl(url); entry = Boot.scripts[key]; if (!entry) { entry = Boot.create(url, key, cfg); if (canonicalPath) { entry.canonicalPath = true; } } return entry; }, registerContent: function (url, type, content) { var cfg = { content: content, loaded: true, css: type === 'css' }; return Boot.getEntry(url, cfg); }, processRequest: function(request, sync) { request.loadEntries(sync); }, load: function (request) { //<debug>// _debug("Boot.load called"); //</debug> var request = new Request(request); if (request.sync || Boot.syncMode) { return Boot.loadSync(request); } // If there is a request in progress, we must // queue this new request to be fired when the current request completes. if (Boot.currentRequest) { //<debug>// _debug("current active request, suspending this request"); //</debug> // trigger assignment of entries now to ensure that overlapping // entries with currently running requests will synchronize state // with this pending one as they complete request.getEntries(); Boot.suspendedQueue.push(request); } else { Boot.currentRequest = request; Boot.processRequest(request, false); } return Boot; }, loadSync: function (request) { //<debug>// _debug("Boot.loadSync called"); //</debug> var request = new Request(request); Boot.syncMode++; Boot.processRequest(request, true); Boot.syncMode--; return Boot; }, loadBasePrefix: function(request) { request = new Request(request); request.prependBaseUrl = true; return Boot.load(request); }, loadSyncBasePrefix: function(request) { request = new Request(request); request.prependBaseUrl = true; return Boot.loadSync(request); }, requestComplete: function(request) { var next; if (Boot.currentRequest === request) { Boot.currentRequest = null; while(Boot.suspendedQueue.length > 0) { next = Boot.suspendedQueue.shift(); if(!next.done) { //<debug>// _debug("resuming suspended request"); //</debug> Boot.load(next); break; } } } if (!Boot.currentRequest && Boot.suspendedQueue.length == 0) { Boot.fireListeners(); } }, isLoading: function () { return !Boot.currentRequest && Boot.suspendedQueue.length == 0; }, fireListeners: function () { var listener; while (Boot.isLoading() && (listener = Boot.listeners.shift())) { listener(); } }, onBootReady: function (listener) { if (!Boot.isLoading()) { listener(); } else { Boot.listeners.push(listener); } }, /** * this is a helper function used by Ext.Loader to flush out * 'uses' arrays for classes in some Ext versions */ getPathsFromIndexes: function (indexMap, loadOrder) { // In older versions indexMap was an object instead of a sparse array if (!('length' in indexMap)) { var indexArray = [], index; for (index in indexMap) { if (!isNaN(+index)) { indexArray[+index] = indexMap[index]; } } indexMap = indexArray; } return Request.prototype.getPathsFromIndexes(indexMap, loadOrder); }, createLoadOrderMap: function(loadOrder) { return Request.prototype.createLoadOrderMap(loadOrder); }, fetch: function(url, complete, scope, async) { async = (async === undefined) ? !!complete : async; var xhr = new XMLHttpRequest(), result, status, content, exception = false, readyStateChange = function () { if (xhr && xhr.readyState == 4) { status = (xhr.status === 1223) ? 204 : (xhr.status === 0 && ((self.location || {}).protocol === 'file:' || (self.location || {}).protocol === 'ionp:')) ? 200 : xhr.status; content = xhr.responseText; result = { content: content, status: status, exception: exception }; if (complete) { complete.call(scope, result); } xhr.onreadystatechange = emptyFn; xhr = null; } }; if (async) { xhr.onreadystatechange = readyStateChange; } try { //<debug>// _debug("fetching " + url + " " + (async ? "async" : "sync")); //</debug> xhr.open('GET', url, async); xhr.send(null); } catch (err) { exception = err; readyStateChange(); return result; } if (!async) { readyStateChange(); } return result; }, notifyAll: function(entry) { entry.notifyRequests(); } }; function Request(cfg) { //The request class encapsulates a series of Entry objects //and provides notification around the completion of all Entries //in this request. if(cfg.$isRequest) { return cfg; } var cfg = cfg.url ? cfg : {url: cfg}, url = cfg.url, urls = url.charAt ? [ url ] : url, charset = cfg.charset || Boot.config.charset; _apply(this, cfg); delete this.url; this.urls = urls; this.charset = charset; }; Request.prototype = { $isRequest: true, createLoadOrderMap: function (loadOrder) { var len = loadOrder.length, loadOrderMap = {}, i, element; for (i = 0; i < len; i++) { element = loadOrder[i]; loadOrderMap[element.path] = element; } return loadOrderMap; }, getLoadIndexes: function (item, indexMap, loadOrder, includeUses, skipLoaded) { var resolved = [], queue = [item], itemIndex = item.idx, queue, entry, dependencies, depIndex, i, len; if (indexMap[itemIndex]) { // prevent cycles return resolved; } // Both indexMap and resolved are sparse arrays keyed by indexes. // This gives us a naturally sorted sequence of indexes later on // when we need to convert them to paths. // indexMap is the map of all indexes we have visited at least once // per the current expandUrls() invocation, and resolved is the map // of all dependencies for the current item that are not included // in indexMap. indexMap[itemIndex] = resolved[itemIndex] = true; while (item = queue.shift()) { // Canonicalizing URLs is expensive, we try to avoid it if (item.canonicalPath) { entry = Boot.getEntry(item.path, null, true); } else { entry = Boot.getEntry(this.prepareUrl(item.path)); } if (!(skipLoaded && entry.done)) { if (includeUses && item.uses && item.uses.length) { dependencies = item.requires.concat(item.uses); } else { dependencies = item.requires; } for (i = 0, len = dependencies.length; i < len; i++) { depIndex = dependencies[i]; if (!indexMap[depIndex]) { indexMap[depIndex] = resolved[depIndex] = true; queue.push(loadOrder[depIndex]); } } } } return resolved; }, getPathsFromIndexes: function (indexes, loadOrder) { var paths = [], index, len; // indexes is a sparse array with values being true for defined indexes for (index = 0, len = indexes.length; index < len; index++) { if (indexes[index]) { paths.push(loadOrder[index].path); } } return paths; }, expandUrl: function (url, loadOrder, loadOrderMap, indexMap, includeUses, skipLoaded) { var item, resolved; if (loadOrder) { item = loadOrderMap[url]; if (item) { resolved = this.getLoadIndexes(item, indexMap, loadOrder, includeUses, skipLoaded); if (resolved.length) { return this.getPathsFromIndexes(resolved, loadOrder); } } } return [url]; }, expandUrls: function (urls, includeUses) { var me = this, loadOrder = me.loadOrder, expanded = [], expandMap = {}, indexMap = [], loadOrderMap, tmpExpanded, i, len, t, tlen, tUrl; if (typeof urls === "string") { urls = [urls]; } if (loadOrder) { loadOrderMap = me.loadOrderMap; if (!loadOrderMap) { loadOrderMap = me.loadOrderMap = me.createLoadOrderMap(loadOrder); } } for (i = 0, len = urls.length; i < len; i++) { // We don't want to skip loaded entries (last argument === false). // There are some overrides that get loaded before their respective classes, // and when the class dependencies are processed we don't want to skip over // the overrides' dependencies just because they were loaded first. tmpExpanded = this.expandUrl(urls[i], loadOrder, loadOrderMap, indexMap, includeUses, false); for (t = 0, tlen = tmpExpanded.length; t < tlen; t++) { tUrl = tmpExpanded[t]; if (!expandMap[tUrl]) { expandMap[tUrl] = true; expanded.push(tUrl); } } } if (expanded.length === 0) { expanded = urls; } return expanded; }, expandLoadOrder: function () { var me = this, urls = me.urls, expanded; if (!me.expanded) { expanded = this.expandUrls(urls, true); me.expanded = true; } else { expanded = urls; } me.urls = expanded; // if we added some urls to the request to honor the indicated // load order, the request needs to be sequential if (urls.length != expanded.length) { me.sequential = true; } return me; }, getUrls: function () { this.expandLoadOrder(); return this.urls; }, prepareUrl: function(url) { if(this.prependBaseUrl) { return Boot.baseUrl + url; } return url; }, getEntries: function () { var me = this, entries = me.entries, loadOrderMap, item, i, entry, urls, url; if (!entries) { entries = []; urls = me.getUrls(); // If we have loadOrder array then the map will be expanded by now if (me.loadOrder) { loadOrderMap = me.loadOrderMap; } for (i = 0; i < urls.length; i++) { url = me.prepareUrl(urls[i]); if (loadOrderMap) { item = loadOrderMap[url]; } entry = Boot.getEntry(url, { buster: me.buster, charset: me.charset }, item && item.canonicalPath); entry.requests.push(me); entries.push(entry); } me.entries = entries; } return entries; }, loadEntries: function(sync) { var me = this, entries = me.getEntries(), len = entries.length, start = me.loadStart || 0, continueLoad, entries, entry, i; if(sync !== undefined) { me.sync = sync; } me.loaded = me.loaded || 0; me.loading = me.loading || len; for(i = start; i < len; i++) { entry = entries[i]; if(!entry.loaded) { continueLoad = entries[i].load(me.sync); } else { continueLoad = true; } if(!continueLoad) { me.loadStart = i; entry.onDone(function(){ me.loadEntries(sync); }); break; } } me.processLoadedEntries(); }, processLoadedEntries: function () { var me = this, entries = me.getEntries(), len = entries.length, start = me.startIndex || 0, i, entry; if (!me.done) { for (i = start; i < len; i++) { entry = entries[i]; if (!entry.loaded) { me.startIndex = i; return; } if (!entry.evaluated) { entry.evaluate(); } if (entry.error) { me.error = true; } } me.notify(); } }, notify: function () { var me = this; if (!me.done) { var error = me.error, fn = me[error ? 'failure' : 'success'], delay = ('delay' in me) ? me.delay : (error ? 1 : Boot.config.chainDelay), scope = me.scope || me; me.done = true; if (fn) { if (delay === 0 || delay > 0) { // Free the stack (and defer the next script) setTimeout(function () { fn.call(scope, me); }, delay); } else { fn.call(scope, me); } } me.fireListeners(); Boot.requestComplete(me); } }, onDone: function(listener) { var me = this, listeners = me.listeners || (me.listeners = []); if(me.done) { listener(me); } else { listeners.push(listener); } }, fireListeners: function() { var listeners = this.listeners, listener; if(listeners) { //<debug>// _debug("firing request listeners"); //</debug> while((listener = listeners.shift())) { listener(this); } } } }; function Entry(cfg) { //The Entry class is a token to manage the load and evaluation //state of a particular url. It is used to notify all Requests //interested in this url that the content is available. if(cfg.$isEntry) { return cfg; } //<debug>// _debug("creating entry for " + cfg.url); //</debug> var charset = cfg.charset || Boot.config.charset, manifest = Ext.manifest, loader = manifest && manifest.loader, cache = (cfg.cache !== undefined) ? cfg.cache : (loader && loader.cache), buster, busterParam; if (Boot.config.disableCaching) { if (cache === undefined) { cache = !Boot.config.disableCaching; } if (cache === false) { buster = +new Date(); } else if (cache !== true) { buster = cache; } if (buster) { busterParam = (loader && loader.cacheParam) || Boot.config.disableCachingParam; buster = busterParam + "=" + buster; } } _apply(this, cfg); this.charset = charset; this.buster = buster; this.requests = []; }; Entry.prototype = { $isEntry: true, done: false, evaluated: false, loaded: false, isCrossDomain: function() { var me = this; if(me.crossDomain === undefined) { //<debug>// _debug("checking " + me.getLoadUrl() + " for prefix " + Boot.origin); //</debug> me.crossDomain = (me.getLoadUrl().indexOf(Boot.origin) !== 0); } return me.crossDomain; }, isCss: function () { var me = this; if (me.css === undefined) { if (me.url) { var assetConfig = Boot.assetConfig[me.url]; me.css = assetConfig ? assetConfig.type === "css" : cssRe.test(me.url); } else { me.css = false; } } return this.css; }, getElement: function (tag) { var me = this, el = me.el; if (!el) { //<debug>// _debug("creating element for " + me.url); //</debug> if (me.isCss()) { tag = tag || "link"; el = doc.createElement(tag); if(tag == "link") { el.rel = 'stylesheet'; me.prop = 'href'; } else { me.prop="textContent"; } el.type = "text/css"; } else { tag = tag || "script"; el = doc.createElement(tag); el.type = 'text/javascript'; me.prop = 'src'; if (me.charset) { el.charset = me.charset; } if (Boot.hasAsync) { el.async = false; } } me.el = el; } return el; }, getLoadUrl: function () { var me = this, url; url = me.canonicalPath ? me.url : Boot.canonicalUrl(me.url); if (!me.loadUrl) { me.loadUrl = !!me.buster ? (url + (url.indexOf('?') === -1 ? '?' : '&') + me.buster) : url; } return me.loadUrl; }, fetch: function (req) { var url = this.getLoadUrl(), async = !!req.async, complete = req.complete; Boot.fetch(url, complete, this, async); }, onContentLoaded: function (response) { var me = this, status = response.status, content = response.content, exception = response.exception, url = this.getLoadUrl(); me.loaded = true; if ((exception || status === 0) && !_environment.phantom) { me.error = //<debug> ("Failed loading synchronously via XHR: '" + url + "'. It's likely that the file is either being loaded from a " + "different domain or from the local file system where cross " + "origin requests are not allowed for security reasons. Try " + "asynchronous loading instead.") || //</debug> true; me.evaluated = true; } else if ((status >= 200 && status < 300) || status === 304 || _environment.phantom || (status === 0 && content.length > 0) ) { me.content = content; } else { me.error = //<debug> ("Failed loading synchronously via XHR: '" + url + "'. Please verify that the file exists. XHR status code: " + status) || //</debug> true; me.evaluated = true; } }, createLoadElement: function(callback) { var me = this, el = me.getElement(); me.preserve = true; el.onerror = function() { me.error = true; if (callback) { callback(); callback = null; } }; if (Boot.isIE10m) { el.onreadystatechange = function() { if (this.readyState === 'loaded' || this.readyState === 'complete') { if (callback) { callback(); callback = this.onreadystatechange = this.onerror = null; } } }; } else { el.onload = function() { callback(); callback = this.onload = this.onerror = null; }; } // IE starts loading here el[me.prop] = me.getLoadUrl(); }, onLoadElementReady: function() { Boot.getHead().appendChild(this.getElement()); this.evaluated = true; }, inject: function (content, asset) { //<debug>// _debug("injecting content for " + this.url); //</debug> var me = this, head = Boot.getHead(), url = me.url, key = me.key, base, el, ieMode, basePath; if (me.isCss()) { me.preserve = true; basePath = key.substring(0, key.lastIndexOf("/") + 1); base = doc.createElement('base'); base.href = basePath; if(head.firstChild) { head.insertBefore(base, head.firstChild); } else { head.appendChild(base); } // reset the href attribute to cuase IE to pick up the change base.href = base.href; if (url) { content += "\n/*# sourceURL=" + key + " */"; } // create element after setting base el = me.getElement("style"); ieMode = ('styleSheet' in el); head.appendChild(base); if(ieMode) { head.appendChild(el); el.styleSheet.cssText = content; } else { el.textContent = content; head.appendChild(el); } head.removeChild(base); } else { // Debugger friendly, file names are still shown even though they're // eval'ed code. Breakpoints work on both Firebug and Chrome's Web // Inspector. if (url) { content += "\n//# sourceURL=" + key; } Ext.globalEval(content); } return me; }, loadCrossDomain: function() { var me = this, complete = function(){ me.el.onerror = me.el.onload = emptyFn; me.el = null; me.loaded = me.evaluated = me.done = true; me.notifyRequests(); }; me.createLoadElement(function(){ complete(); }); me.evaluateLoadElement(); // at this point, we need sequential evaluation, // which means we can't advance the load until // this entry has fully completed return false; }, loadElement: function() { var me = this, complete = function(){ me.el.onerror = me.el.onload = emptyFn; me.el = null; me.loaded = me.evaluated = me.done = true; me.notifyRequests(); }; me.createLoadElement(function(){ complete(); }); me.evaluateLoadElement(); return true; }, loadSync: function() { var me = this; me.fetch({ async: false, complete: function (response) { me.onContentLoaded(response); } }); me.evaluate(); me.notifyRequests(); }, load: function (sync) { var me = this; if (!me.loaded) { if(me.loading) { // if we're calling back through load and we're loading but haven't // yet loaded, then we should be in a sequential, cross domain // load scenario which means we can't continue the load on the // request until this entry has fully evaluated, which will mean // loaded = evaluated = done = true in one step. For css files, this // will happen immediately upon <link> element creation / insertion, // but <script> elements will set this upon load notification return false; } me.loading = true; // for async modes, we have some options if (!sync) { // if cross domain, just inject the script tag and let the onload // events drive the progression. // IE10 also needs sequential loading because of a bug that makes it // fire readystate event prematurely: // https://connect.microsoft.com/IE/feedback/details/729164/ie10-dynamic-script-element-fires-loaded-readystate-prematurely if (Boot.isIE10 || me.isCrossDomain()) { return me.loadCrossDomain(); } // for IE, use the readyStateChange allows us to load scripts in parallel // but serialize the evaluation by appending the script node to the // document else if(!me.isCss() && Boot.hasReadyState) { me.createLoadElement(function () { me.loaded = true; me.notifyRequests(); }); } else if(Boot.useElements && // older webkit, phantomjs included, won't fire load for link elements !(me.isCss() && _environment.phantom)) { return me.loadElement(); } // for other browsers, just ajax the content down in parallel, and use // globalEval to serialize evaluation else { me.fetch({ async: !sync, complete: function (response) { me.onContentLoaded(response); me.notifyRequests(); } }); } } // for sync mode in js, global eval FTW. IE won't honor the comment // paths in the debugger, so eventually we need a sync mode for IE that // uses the readyStateChange mechanism else { me.loadSync(); } } // signal that the load process can continue return true; }, evaluateContent: function () { this.inject(this.content); this.content = null; }, evaluateLoadElement: function() { Boot.getHead().appendChild(this.getElement()); }, evaluate: function () { var me = this; if(!me.evaluated) { if(me.evaluating) { return; } me.evaluating = true; if(me.content !== undefined) { me.evaluateContent(); } else if(!me.error) { me.evaluateLoadElement(); } me.evaluated = me.done = true; me.cleanup(); } }, cleanup: function () { var me = this, el = me.el, prop; if (!el) { return; } if (!me.preserve) { me.el = null; el.parentNode.removeChild(el); // Remove, since its useless now for (prop in el) { try { if (prop !== me.prop) { // If we set the src property to null IE // will try and request a script at './null' el[prop] = null; } delete el[prop]; // and prepare for GC } catch (cleanEx) { //ignore } } } // Setting to null can cause exceptions if IE ever needs to call these // again (like onreadystatechange). This emptyFn has nothing locked in // closure scope so it is about as safe as null for memory leaks. el.onload = el.onerror = el.onreadystatechange = emptyFn; }, notifyRequests: function () { var requests = this.requests, len = requests.length, i, request; for (i = 0; i < len; i++) { request = requests[i]; request.processLoadedEntries(); } if(this.done) { this.fireListeners(); } }, onDone: function(listener) { var me = this, listeners = me.listeners || (me.listeners = []); if(me.done) { listener(me); } else { listeners.push(listener); } }, fireListeners: function() { var listeners = this.listeners, listener; if(listeners && listeners.length > 0) { //<debug>// _debug("firing event listeners for url " + this.url); //</debug> while((listener = listeners.shift())) { listener(this); } } } }; /** * Turns on or off the "cache buster" applied to dynamically loaded scripts. Normally * dynamically loaded scripts have an extra query parameter appended to avoid stale * cached scripts. This method can be used to disable this mechanism, and is primarily * useful for testing. This is done using a cookie. * @param {Boolean} disable True to disable the cache buster. * @param {String} [path="/"] An optional path to scope the cookie. */ Ext.disableCacheBuster = function (disable, path) { var date = new Date(); date.setTime(date.getTime() + (disable ? 10 * 365 : -1) * 24 * 60 * 60 * 1000); date = date.toGMTString(); doc.cookie = 'ext-cache=1; expires=' + date + '; path=' + (path || '/'); }; //<if nonBrowser> if (_environment.node) { Boot.prototype.load = Boot.prototype.loadSync = function (request) { // @TODO require(filePath); onLoad.call(scope); }; Boot.prototype.init = emptyFn; }//</if> Boot.init(); return Boot; // NOTE: We run the eval at global scope to protect the body of the function and allow// compressors to still process it.}(function () {}));//(eval("/*@cc_on!@*/!1")); /** * This method evaluates the given code free of any local variable. This * will be at global scope, in others it will be in a function. * @param {String} code The code to evaluate. * @private * @method * @member Ext */Ext.globalEval = Ext.globalEval || (this.execScript ? function (code) { execScript(code); } : function ($$code) { eval.call(window, $$code); }); //<feature legacyBrowser>/* * Only IE8 & IE/Quirks lack Function.prototype.bind so we polyfill that here. */if (!Function.prototype.bind) { (function () { var slice = Array.prototype.slice, // To reduce overhead on call of the bound fn we have two flavors based on // whether we have args to prepend or not: bind = function (me) { var args = slice.call(arguments, 1), method = this; if (args.length) { return function () { var t = arguments; // avoid the slice/concat if the caller does not supply args return method.apply(me, t.length ? args.concat(slice.call(t)) : args); }; } // this is the majority use case - just fn.bind(this) and no args args = null; return function () { return method.apply(me, arguments); }; }; Function.prototype.bind = bind; bind.$extjs = true; // to detect this polyfill if one want to improve it }());}//</feature> //</editor-fold> Ext.setResourcePath = function (poolName, path) { var manifest = Ext.manifest || (Ext.manifest = {}), paths = manifest.resources || (manifest.resources = {}); if (manifest) { if (typeof poolName !== 'string') { Ext.apply(paths, poolName); } else { paths[poolName] = path; } manifest.resources = paths; }}; Ext.getResourcePath = function (path, poolName, packageName) { if (typeof path !== 'string') { poolName = path.pool; packageName = path.packageName; path = path.path; } var manifest = Ext.manifest, paths = manifest && manifest.resources, poolPath = paths[poolName], output = []; if (poolPath == null) { poolPath = paths.path; if (poolPath == null) { poolPath = 'resources'; } } if (poolPath) { output.push(poolPath); } if (packageName) { output.push(packageName); } output.push(path); return output.join('/');};