    // @tag foundation,core
    // @require ClassManager.js
    // @define Ext.Loader

    /**
     * @author Jacky Nguyen <jacky@sencha.com>
     * @docauthor Jacky Nguyen <jacky@sencha.com>
     * @class Ext.Loader
     *
     * Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
     * via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
     * approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons of each approach:
     *
     * # Asynchronous Loading #
     *
     * - Advantages:
     *     + Cross-domain
     *     + No web server needed: you can run the application via the file system protocol (i.e: `file://path/to/your/index
     *  .html`)
     *     + Best possible debugging experience: error messages come with the exact file name and line number
     *
     * - Disadvantages:
     *     + Dependencies need to be specified before-hand
     *
     * ### Method 1: Explicitly include what you need: ###
     *
     *     // Syntax
     *     Ext.require({String/Array} expressions);
     *
     *     // Example: Single alias
     *     Ext.require('widget.window');
     *
     *     // Example: Single class name
     *     Ext.require('Ext.window.Window');
     *
     *     // Example: Multiple aliases / class names mix
     *     Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
     *
     *     // Wildcards
     *     Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
     *
     * ### Method 2: Explicitly exclude what you don't need: ###
     *
     *     // Syntax: Note that it must be in this chaining format.
     *     Ext.exclude({String/Array} expressions)
     *        .require({String/Array} expressions);
     *
     *     // Include everything except Ext.data.*
     *     Ext.exclude('Ext.data.*').require('*');
     *
     *     // Include all widgets except widget.checkbox*,
     *     // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
     *     Ext.exclude('widget.checkbox*').require('widget.*');
     *
     * # Synchronous Loading on Demand #
     *
     * - Advantages:
     *     + There's no need to specify dependencies before-hand, which is always the convenience of including ext-all.js
     *  before
     *
     * - Disadvantages:
     *     + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
     *     + Must be from the same domain due to XHR restriction
     *     + Need a web server, same reason as above
     *
     * There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
     *
     *     Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
     *
     *     Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
     *
     *     Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
     *
     * Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
     *  existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load the given
     *  class and all its dependencies.
     *
     * # Hybrid Loading - The Best of Both Worlds #
     *
     * It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
     *
     * ### Step 1: Start writing your application using synchronous approach.
     *
     * Ext.Loader will automatically fetch all dependencies on demand as they're needed during run-time. For example:
     *
     *     Ext.onReady(function(){
     *         var window = Ext.widget('window', {
     *             width: 500,
     *             height: 300,
     *             layout: {
     *                 type: 'border',
     *                 padding: 5
     *             },
     *             title: 'Hello Dialog',
     *             items: [{
     *                 title: 'Navigation',
     *                 collapsible: true,
     *                 region: 'west',
     *                 width: 200,
     *                 html: 'Hello',
     *                 split: true
     *             }, {
     *                 title: 'TabPanel',
     *                 region: 'center'
     *             }]
     *         });
     *
     *         window.show();
     *     })
     *
     * ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these: ###
     *
     *     [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code
     *     ClassManager.js:432
     *     [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
     *
     * Simply copy and paste the suggested code above `Ext.onReady`, i.e:
     *
     *     Ext.require('Ext.window.Window');
     *     Ext.require('Ext.layout.container.Border');
     *
     *     Ext.onReady(...);
     *
     * Everything should now load via asynchronous mode.
     *
     * # Deployment #
     *
     * It's important to note that dynamic loading should only be used during development on your local machines.
     * During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
     * the whole process of transitioning from / to between development / maintenance and production as easy as
     * possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies your application
     * needs in the exact loading sequence. It's as simple as concatenating all files in this array into one,
     * then include it on top of your application.
     *
     * This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
     *
     * @singleton
     */

    Ext.Loader = new function() {
        var Loader = this,
            Manager = Ext.ClassManager,
            Class = Ext.Class,
            flexSetter = Ext.Function.flexSetter,
            alias = Ext.Function.alias,
            pass = Ext.Function.pass,
            defer = Ext.Function.defer,
            arrayErase = Ext.Array.erase,
            //<if nonBrowser>
            isNonBrowser = typeof window == 'undefined',
            isNodeJS = isNonBrowser && (typeof require == 'function'),
            isJsdb = isNonBrowser && typeof system != 'undefined' && system.program.search(/jsdb/) !== -1,
            isPhantomJS = (typeof phantom != 'undefined' && phantom.fs),
            //</if>
            dependencyProperties = ['extend', 'mixins', 'requires'],
            isInHistory = {},
            history = [],
            slashDotSlashRe = /\/\.\//g,
            dotRe = /\./g,
            setPathCount = 0;

        Ext.apply(Loader, {

            /**
             * @private
             */
            isInHistory: isInHistory,

            /**
             * An array of class names to keep track of the dependency loading order.
             * This is not guaranteed to be the same everytime due to the asynchronous
             * nature of the Loader.
             *
             * @property {Array} history
             */
            history: history,

            /**
             * Configuration
             * @private
             */
            config: {
                /**
                 * @cfg {Boolean} enabled
                 * Whether or not to enable the dynamic dependency loading feature.
                 */
                enabled: false,

                /**
                 * @cfg {Boolean} scriptChainDelay
                 * millisecond delay between asynchronous script injection (prevents stack overflow on some user agents)
                 * 'false' disables delay but potentially increases stack load.
                 */
                scriptChainDelay : false,

                /**
                 * @cfg {Boolean} disableCaching
                 * Appends current timestamp to script files to prevent caching.
                 */
                disableCaching: true,

                /**
                 * @cfg {String} disableCachingParam
                 * The get parameter name for the cache buster's timestamp.
                 */
                disableCachingParam: '_dc',

                /**
                 * @cfg {Boolean} garbageCollect
                 * True to prepare an asynchronous script tag for garbage collection (effective only
                 * if {@link #preserveScripts preserveScripts} is false)
                 */
                garbageCollect : false,

                /**
                 * @cfg {Object} paths
                 * The mapping from namespaces to file paths
                 *
                 *     {
                 *         'Ext': '.', // This is set by default, Ext.layout.container.Container will be
                 *                     // loaded from ./layout/Container.js
                 *
                 *         'My': './src/my_own_folder' // My.layout.Container will be loaded from
                 *                                     // ./src/my_own_folder/layout/Container.js
                 *     }
                 *
                 * Note that all relative paths are relative to the current HTML document.
                 * If not being specified, for example, <code>Other.awesome.Class</code>
                 * will simply be loaded from <code>./Other/awesome/Class.js</code>
                 */
                paths: {
                    'Ext': '.'
                },

                /**
                 * @cfg {Boolean} preserveScripts
                 * False to remove and optionally {@link #garbageCollect garbage-collect} asynchronously loaded scripts,
                 * True to retain script element for browser debugger compatibility and improved load performance.
                 */
                preserveScripts : true,

                /**
                 * @cfg {String} scriptCharset
                 * Optional charset to specify encoding of dynamic script content.
                 */
                scriptCharset : undefined
            },

            /**
             * Set the configuration for the loader. This should be called right after ext-(debug).js
             * is included in the page, and before Ext.onReady. i.e:
             *
             *     <script type="text/javascript" src="ext-core-debug.js"></script>
             *     <script type="text/javascript">
             *         Ext.Loader.setConfig({
             *           enabled: true,
             *           paths: {
             *               'My': 'my_own_path'
             *           }
             *         });
             *     </script>
             *     <script type="text/javascript">
             *         Ext.require(...);
             *
             *         Ext.onReady(function() {
             *           // application code here
             *         });
             *     </script>
             *
             * Refer to config options of {@link Ext.Loader} for the list of possible properties
             *
             * @param {Object} config The config object to override the default values
             * @return {Ext.Loader} this
             */
            setConfig: function(name, value) {
                if (Ext.isObject(name) && arguments.length === 1) {
                    Ext.merge(Loader.config, name);

                    if ('paths' in name) {
                        Ext.app.collectNamespaces(name.paths);
                    }
                }
                else {
                    Loader.config[name] = (Ext.isObject(value)) ? Ext.merge(Loader.config[name], value) : value;

                    if (name === 'paths') {
                        Ext.app.collectNamespaces(value);
                    }
                }

                return Loader;
            },

            /**
             * 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) {
                if (name) {
                    return Loader.config[name];
                }

                return Loader.config;
            },

            /**
             * Sets the path of a namespace.
             * For Example:
             *
             *     Ext.Loader.setPath('Ext', '.');
             *
             * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
             * @param {String} [path] See {@link Ext.Function#flexSetter flexSetter}
             * @return {Ext.Loader} this
             * @method
             */
            setPath: flexSetter(function(name, path) {
                Loader.config.paths[name] = path;
                Ext.app.namespaces[name] = true;
                setPathCount++;

                return Loader;
            }),

            /**
             * Sets a batch of path entries
             *
             * @param {Object } paths a set of className: path mappings
             * @return {Ext.Loader} this
             */
            addClassPathMappings: function(paths) {
                var name;

                if(setPathCount == 0){
                    Loader.config.paths = paths;
                } else {
                    for(name in paths){
                        Loader.config.paths[name] = paths[name];
                    }
                }
                setPathCount++;
                return Loader;
            },

            /**
             * Translates a className to a file path by adding the
             * the proper prefix and converting the .'s to /'s. For example:
             *
             *     Ext.Loader.setPath('My', '/path/to/My');
             *
             *     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
             *
             * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
             *
             *     Ext.Loader.setPath({
             *         'My': '/path/to/lib',
             *         'My.awesome': '/other/path/for/awesome/stuff',
             *         'My.awesome.more': '/more/awesome/path'
             *     });
             *
             *     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
             *
             *     alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
             *
             *     alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
             *
             *     alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
             *
             * @param {String} className
             * @return {String} path
             */
            getPath: function(className) {
                var path = '',
                    paths = Loader.config.paths,
                    prefix = Loader.getPrefix(className);

                if (prefix.length > 0) {
                    if (prefix === className) {
                        return paths[prefix];
                    }

                    path = paths[prefix];
                    className = className.substring(prefix.length + 1);
                }

                if (path.length > 0) {
                    path += '/';
                }

                return path.replace(slashDotSlashRe, '/') + className.replace(dotRe, "/") + '.js';
            },

            /**
             * @private
             * @param {String} className
             */
            getPrefix: function(className) {
                var paths = Loader.config.paths,
                    prefix, deepestPrefix = '';

                if (paths.hasOwnProperty(className)) {
                    return className;
                }

                for (prefix in paths) {
                    if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
                        if (prefix.length > deepestPrefix.length) {
                            deepestPrefix = prefix;
                        }
                    }
                }

                return deepestPrefix;
            },

            /**
             * @private
             * @param {String} className
             */
            isAClassNameWithAKnownPrefix: function(className) {
                var prefix = Loader.getPrefix(className);

                // we can only say it's really a class if className is not equal to any known namespace
                return prefix !== '' && prefix !== className;
            },

            /**
             * Loads all classes by the given names and all their direct dependencies; optionally executes
             * the given callback function when finishes, within the optional scope.
             *
             * {@link Ext#require} is alias for {@link Ext.Loader#require}.
             *
             * @param {String/Array} expressions Can either be a string or an array of string
             * @param {Function} fn (Optional) The callback function
             * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
             * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
             */
            require: function(expressions, fn, scope, excludes) {
                if (fn) {
                    fn.call(scope);
                }
            },

            /**
             * Synchronously loads all classes by the given names and all their direct dependencies; optionally
             * executes the given callback function when finishes, within the optional scope.
             *
             * {@link Ext#syncRequire} is alias for {@link Ext.Loader#syncRequire}.
             *
             * @param {String/Array} expressions Can either be a string or an array of string
             * @param {Function} fn (Optional) The callback function
             * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
             * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
             */
            syncRequire: function() {},

            /**
             * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
             * Can be chained with more `require` and `exclude` methods, eg:
             *
             *     Ext.exclude('Ext.data.*').require('*');
             *
             *     Ext.exclude('widget.button*').require('widget.*');
             *
             * {@link Ext#exclude} is alias for {@link Ext.Loader#exclude}.
             *
             * @param {Array} excludes
             * @return {Object} object contains `require` method for chaining
             */
            exclude: function(excludes) {
                return {
                    require: function(expressions, fn, scope) {
                        return Loader.require(expressions, fn, scope, excludes);
                    },

                    syncRequire: function(expressions, fn, scope) {
                        return Loader.syncRequire(expressions, fn, scope, excludes);
                    }
                };
            },

            /**
             * Add a new listener to be executed when all required scripts are fully loaded
             *
             * @param {Function} fn The function callback to be executed
             * @param {Object} scope The execution scope (<code>this</code>) of the callback function
             * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
             */
            onReady: function(fn, scope, withDomReady, options) {
                var oldFn;

                if (withDomReady !== false && Ext.onDocumentReady) {
                    oldFn = fn;

                    fn = function() {
                        Ext.onDocumentReady(oldFn, scope, options);
                    };
                }

                fn.call(scope);
            }
        });

        //<feature classSystem.loader>
        var queue = [],
            isClassFileLoaded = {},
            isFileLoaded = {},
            classNameToFilePathMap = {},
            scriptElements = {},
            readyListeners = [],
            usedClasses = [],
            requiresMap = {},
            comparePriority = function(listenerA, listenerB) {
                return listenerB.priority - listenerA.priority;
            };

        Ext.apply(Loader, {
            /**
             * @private
             */
            documentHead: typeof document != 'undefined' && (document.head || document.getElementsByTagName('head')[0]),

            /**
             * Flag indicating whether there are still files being loaded
             * @private
             */
            isLoading: false,

            /**
             * Maintain the queue for all dependencies. Each item in the array is an object of the format:
             *
             *     {
             *          requires: [...], // The required classes for this queue item
             *          callback: function() { ... } // The function to execute when all classes specified in requires exist
             *     }
             *
             * @private
             */
            queue: queue,

            /**
             * Maintain the list of files that have already been handled so that they never get double-loaded
             * @private
             */
            isClassFileLoaded: isClassFileLoaded,

            /**
             * @private
             */
            isFileLoaded: isFileLoaded,

            /**
             * Maintain the list of listeners to execute when all required scripts are fully loaded
             * @private
             */
            readyListeners: readyListeners,

            /**
             * Contains classes referenced in `uses` properties.
             * @private
             */
            optionalRequires: usedClasses,

            /**
             * Map of fully qualified class names to an array of dependent classes.
             * @private
             */
            requiresMap: requiresMap,

            /**
             * @private
             */
            numPendingFiles: 0,

            /**
             * @private
             */
            numLoadedFiles: 0,

            /** @private */
            hasFileLoadError: false,

            /**
             * @private
             */
            classNameToFilePathMap: classNameToFilePathMap,

            /**
             * The number of scripts loading via loadScript.
             * @private
             */
            scriptsLoading: 0,

            /**
             * @private
             */
            syncModeEnabled: false,

            scriptElements: scriptElements,

            /**
             * Refresh all items in the queue. If all dependencies for an item exist during looping,
             * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
             * empty
             * @private
             */
            refreshQueue: function() {
                var ln = queue.length,
                    i, item, j, requires;

                // When the queue of loading classes reaches zero, trigger readiness

                if (!ln && !Loader.scriptsLoading) {
                    return Loader.triggerReady();
                }

                for (i = 0; i < ln; i++) {
                    item = queue[i];

                    if (item) {
                        requires = item.requires;

                        // Don't bother checking when the number of files loaded
                        // is still less than the array length
                        if (requires.length > Loader.numLoadedFiles) {
                            continue;
                        }

                        // Remove any required classes that are loaded
                        for (j = 0; j < requires.length; ) {
                            if (Manager.isCreated(requires[j])) {
                                // Take out from the queue
                                arrayErase(requires, j, 1);
                            }
                            else {
                                j++;
                            }
                        }

                        // If we've ended up with no required classes, call the callback
                        if (item.requires.length === 0) {
                            arrayErase(queue, i, 1);
                            item.callback.call(item.scope);
                            Loader.refreshQueue();
                            break;
                        }
                    }
                }

                return Loader;
            },

            /**
             * Inject a script element to document's head, call onLoad and onError accordingly
             * @private
             */
            injectScriptElement: function(url, onLoad, onError, scope, charset) {
                var script = document.createElement('script'),
                    dispatched = false,
                    config = Loader.config,
                    onLoadFn = function() {

                        if(!dispatched) {
                            dispatched = true;
                            script.onload = script.onreadystatechange = script.onerror = null;
                            if (typeof config.scriptChainDelay == 'number') {
                                //free the stack (and defer the next script)
                                defer(onLoad, config.scriptChainDelay, scope);
                            } else {
                                onLoad.call(scope);
                            }
                            Loader.cleanupScriptElement(script, config.preserveScripts === false, config.garbageCollect);
                        }

                    },
                    onErrorFn = function(arg) {
                        defer(onError, 1, scope);   //free the stack
                        Loader.cleanupScriptElement(script, config.preserveScripts === false, config.garbageCollect);
                    };

                script.type = 'text/javascript';
                script.onerror = onErrorFn;
                charset = charset || config.scriptCharset;
                if (charset) {
                    script.charset = charset;
                }

                /*
                 * IE9 Standards mode (and others) SHOULD follow the load event only
                 * (Note: IE9 supports both onload AND readystatechange events)
                 */
                if ('addEventListener' in script ) {
                    script.onload = onLoadFn;
                } else if ('readyState' in script) {   // for <IE9 Compatability
                    script.onreadystatechange = function() {
                        if ( this.readyState == 'loaded' || this.readyState == 'complete' ) {
                            onLoadFn();
                        }
                    };
                } else {
                     script.onload = onLoadFn;
                }

                script.src = url;
                (Loader.documentHead || document.getElementsByTagName('head')[0]).appendChild(script);

                return script;
            },

            /**
             * @private
             */
            removeScriptElement: function(url) {
                if (scriptElements[url]) {
                    Loader.cleanupScriptElement(scriptElements[url], true, !!Loader.getConfig('garbageCollect'));
                    delete scriptElements[url];
                }

                return Loader;
            },

            /**
             * @private
             */
            cleanupScriptElement: function(script, remove, collect) {
                var prop;
                script.onload = script.onreadystatechange = script.onerror = null;
                if (remove) {
                    Ext.removeNode(script);       // Remove, since its useless now
                    if (collect) {
                        for (prop in script) {
                            try {
                                if (prop != 'src') {
                                    // If we set the src property to null IE
                                    // will try and request a script at './null'
                                    script[prop] = null;
                                }
                                delete script[prop];      // and prepare for GC
                            } catch (cleanEx) {
                                //ignore
                            }
                        }
                    }
                }

                return Loader;
            },

            /**
             * Loads the specified script URL and calls the supplied callbacks. If this method
             * is called before {@link Ext#isReady}, the script's load will delay the transition
             * to ready. This can be used to load arbitrary scripts that may contain further
             * {@link Ext#require Ext.require} calls.
             *
             * @param {Object/String} options The options object or simply the URL to load.
             * @param {String} options.url The URL from which to load the script.
             * @param {Function} [options.onLoad] The callback to call on successful load.
             * @param {Function} [options.onError] The callback to call on failure to load.
             * @param {Object} [options.scope] The scope (`this`) for the supplied callbacks.
             */
            loadScript: function (options) {
                var config = Loader.getConfig(),
                    isString = typeof options == 'string',
                    url = isString ? options : options.url,
                    onError = !isString && options.onError,
                    onLoad = !isString && options.onLoad,
                    scope = !isString && options.scope,
                    onScriptError = function() {
                        Loader.numPendingFiles--;
                        Loader.scriptsLoading--;

                        if (onError) {
                            onError.call(scope, "Failed loading '" + url + "', please verify that the file exists");
                        }

                        if (Loader.numPendingFiles + Loader.scriptsLoading === 0) {
                            Loader.refreshQueue();
                        }
                    },
                    onScriptLoad = function () {
                        Loader.numPendingFiles--;
                        Loader.scriptsLoading--;

                        if (onLoad) {
                            onLoad.call(scope);
                        }

                        if (Loader.numPendingFiles + Loader.scriptsLoading === 0) {
                            Loader.refreshQueue();
                        }
                    },
                    src;

                Loader.isLoading = true;
                Loader.numPendingFiles++;
                Loader.scriptsLoading++;

                src = config.disableCaching ?
                    (url + '?' + config.disableCachingParam + '=' + Ext.Date.now()) : url;

                scriptElements[url] = Loader.injectScriptElement(src, onScriptLoad, onScriptError);
            },

            /**
             * Load a script file, supports both asynchronous and synchronous approaches
             * @private
             */
            loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
                if (isFileLoaded[url]) {
                    return Loader;
                }

                var config = Loader.getConfig(),
                    noCacheUrl = url + (config.disableCaching ? ('?' + config.disableCachingParam + '=' + Ext.Date.now()) : ''),
                    isCrossOriginRestricted = false,
                    xhr, status, onScriptError,
                    debugSourceURL = "";

                scope = scope || Loader;

                Loader.isLoading = true;

                if (!synchronous) {
                    onScriptError = function() {
                        //<debug error>
                        onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
                        //</debug>
                    };

                    scriptElements[url] = Loader.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
                } else {
                    if (typeof XMLHttpRequest != 'undefined') {
                        xhr = new XMLHttpRequest();
                    } else {
                        xhr = new ActiveXObject('Microsoft.XMLHTTP');
                    }

                    try {
                        xhr.open('GET', noCacheUrl, false);
                        xhr.send(null);
                    } catch (e) {
                        isCrossOriginRestricted = true;
                    }

                    status = (xhr.status === 1223) ? 204 :
                        (xhr.status === 0 && ((self.location || {}).protocol == 'file:' || (self.location || {}).protocol == 'ionp:')) ? 200 : xhr.status;

                    isCrossOriginRestricted = isCrossOriginRestricted || (status === 0);

                    if (isCrossOriginRestricted
                        //<if isNonBrowser>
                        && !isPhantomJS
                        //</if>
                    ) {
                        //<debug error>
                        onError.call(Loader, "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 whereby cross origin " +
                                           "requests are not allowed due to security reasons. Use asynchronous loading with " +
                                           "Ext.require instead.", synchronous);
                        //</debug>
                    }
                    else if ((status >= 200 && status < 300) || (status === 304)
                        //<if isNonBrowser>
                        || isPhantomJS
                        //</if>
                    ) {
                        // 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 (!Ext.isIE) {
                            debugSourceURL = "\n//@ sourceURL=" + url;
                        }

                        Ext.globalEval(xhr.responseText + debugSourceURL);

                        onLoad.call(scope);
                    }
                    else {
                        //<debug>
                        onError.call(Loader, "Failed loading synchronously via XHR: '" + url + "'; please " +
                                           "verify that the file exists. " +
                                           "XHR status code: " + status, synchronous);
                        //</debug>
                    }

                    // Prevent potential IE memory leak
                    xhr = null;
                }
            },

            // documented above
            syncRequire: function() {
                var syncModeEnabled = Loader.syncModeEnabled;

                if (!syncModeEnabled) {
                    Loader.syncModeEnabled = true;
                }

                Loader.require.apply(Loader, arguments);

                if (!syncModeEnabled) {
                    Loader.syncModeEnabled = false;
                }

                Loader.refreshQueue();
            },

            // documented above
            require: function(expressions, fn, scope, excludes) {
                var excluded = {},
                    included = {},
                    excludedClassNames = [],
                    possibleClassNames = [],
                    classNames = [],
                    references = [],
                    callback,
                    syncModeEnabled,
                    filePath, expression, exclude, className,
                    possibleClassName, i, j, ln, subLn;

                if (excludes) {
                    // Convert possible single string to an array.
                    excludes = (typeof excludes === 'string') ? [ excludes ] : excludes;

                    for (i = 0,ln = excludes.length; i < ln; i++) {
                        exclude = excludes[i];

                        if (typeof exclude == 'string' && exclude.length > 0) {
                            excludedClassNames = Manager.getNamesByExpression(exclude);

                            for (j = 0,subLn = excludedClassNames.length; j < subLn; j++) {
                                excluded[excludedClassNames[j]] = true;
                            }
                        }
                    }
                }

                // Convert possible single string to an array.
                expressions = (typeof expressions === 'string') ? [ expressions ] : (expressions ? expressions : []);

                if (fn) {
                    if (fn.length > 0) {
                        callback = function() {
                            var classes = [],
                                i, ln;

                            for (i = 0,ln = references.length; i < ln; i++) {
                                classes.push(Manager.get(references[i]));
                            }

                            return fn.apply(this, classes);
                        };
                    }
                    else {
                        callback = fn;
                    }
                }
                else {
                    callback = Ext.emptyFn;
                }

                scope = scope || Ext.global;

                for (i = 0,ln = expressions.length; i < ln; i++) {
                    expression = expressions[i];

                    if (typeof expression == 'string' && expression.length > 0) {
                        possibleClassNames = Manager.getNamesByExpression(expression);
                        subLn = possibleClassNames.length;

                        for (j = 0; j < subLn; j++) {
                            possibleClassName = possibleClassNames[j];

                            if (excluded[possibleClassName] !== true) {
                                references.push(possibleClassName);

                                if (!Manager.isCreated(possibleClassName) && !included[possibleClassName]) {
                                    included[possibleClassName] = true;
                                    classNames.push(possibleClassName);
                                }
                            }
                        }
                    }
                }

                // If the dynamic dependency feature is not being used, throw an error
                // if the dependencies are not defined
                if (classNames.length > 0) {
                    if (!Loader.config.enabled) {
                        throw new Error("Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
                                 "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', '));
                    }
                }
                else {
                    callback.call(scope);
                    return Loader;
                }

                syncModeEnabled = Loader.syncModeEnabled;

                if (!syncModeEnabled) {
                    queue.push({
                        requires: classNames.slice(), // this array will be modified as the queue is processed,
                                                      // so we need a copy of it
                        callback: callback,
                        scope: scope
                    });
                }

                ln = classNames.length;

                for (i = 0; i < ln; i++) {
                    className = classNames[i];

                    filePath = Loader.getPath(className);

                    // If we are synchronously loading a file that has already been asychronously loaded before
                    // we need to destroy the script tag and revert the count
                    // This file will then be forced loaded in synchronous
                    if (syncModeEnabled && isClassFileLoaded.hasOwnProperty(className)) {
                        if (!isClassFileLoaded[className]) {
                            Loader.numPendingFiles--;
                            Loader.removeScriptElement(filePath);
                            delete isClassFileLoaded[className];
                        }
                    }

                    if (!isClassFileLoaded.hasOwnProperty(className)) {
                        isClassFileLoaded[className] = false;
                        classNameToFilePathMap[className] = filePath;

                        Loader.numPendingFiles++;
                        Loader.loadScriptFile(
                            filePath,
                            pass(Loader.onFileLoaded, [className, filePath], Loader),
                            pass(Loader.onFileLoadError, [className, filePath], Loader),
                            Loader,
                            syncModeEnabled
                        );
                    }
                }

                if (syncModeEnabled) {
                    callback.call(scope);

                    if (ln === 1) {
                        return Manager.get(className);
                    }
                }

                return Loader;
            },

            /**
             * @private
             * @param {String} className
             * @param {String} filePath
             */
            onFileLoaded: function(className, filePath) {
                var loaded = isClassFileLoaded[className];
                Loader.numLoadedFiles++;

                isClassFileLoaded[className] = true;
                isFileLoaded[filePath] = true;

                // In FF, when we sync load something that has had a script tag inserted, the load event may
                // sometimes fire even if we clean it up and set it to null, so check if we're already loaded here.
                if (!loaded) {
                    Loader.numPendingFiles--;
                }

                if (Loader.numPendingFiles === 0) {
                    Loader.refreshQueue();
                }

                //<debug>
                if (!Loader.syncModeEnabled && Loader.numPendingFiles === 0 && Loader.isLoading && !Loader.hasFileLoadError) {
                    var missingClasses = [],
                        missingPaths = [],
                        requires,
                        i, ln, j, subLn;

                    for (i = 0,ln = queue.length; i < ln; i++) {
                        requires = queue[i].requires;

                        for (j = 0,subLn = requires.length; j < subLn; j++) {
                            if (isClassFileLoaded[requires[j]]) {
                                missingClasses.push(requires[j]);
                            }
                        }
                    }

                    if (missingClasses.length < 1) {
                        return;
                    }

                    missingClasses = Ext.Array.filter(Ext.Array.unique(missingClasses), function(item) {
                        return !requiresMap.hasOwnProperty(item);
                    }, Loader);

                    if (missingClasses.length < 1) {
                        return;
                    }

                    for (i = 0,ln = missingClasses.length; i < ln; i++) {
                        missingPaths.push(classNameToFilePathMap[missingClasses[i]]);
                    }

                    throw new Error("The following classes are not declared even if their files have been " +
                        "loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +
                        "corresponding files for possible typos: '" + missingPaths.join("', '"));
                }
                //</debug>
            },

            /**
             * @private
             */
            onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
                Loader.numPendingFiles--;
                Loader.hasFileLoadError = true;

                //<debug error>
                throw new Error("[Ext.Loader] " + errorMessage);
                //</debug>
            },

            /**
             * @private
             * Ensure that any classes referenced in the `uses` property are loaded.
             */
            addUsedClasses: function (classes) {
                var cls, i, ln;

                if (classes) {
                    classes = (typeof classes == 'string') ? [classes] : classes;
                    for (i = 0, ln = classes.length; i < ln; i++) {
                        cls = classes[i];
                        if (typeof cls == 'string' && !Ext.Array.contains(usedClasses, cls)) {
                            usedClasses.push(cls);
                        }
                    }
                }

                return Loader;
            },

            /**
             * @private
             */
            triggerReady: function() {
                var listener,
                    refClasses = usedClasses;

                if (Loader.isLoading) {
                    Loader.isLoading = false;

                    if (refClasses.length !== 0) {
                        // Clone then empty the array to eliminate potential recursive loop issue
                        refClasses = refClasses.slice();
                        usedClasses.length = 0;
                        // this may immediately call us back if all 'uses' classes
                        // have been loaded
                        Loader.require(refClasses, Loader.triggerReady, Loader);
                        return Loader;
                    }
                }

                Ext.Array.sort(readyListeners, comparePriority);

                // this method can be called with Loader.isLoading either true or false
                // (can be called with false when all 'uses' classes are already loaded)
                // this may bypass the above if condition
                while (readyListeners.length && !Loader.isLoading) {
                    // calls to refreshQueue may re-enter triggerReady
                    // so we cannot necessarily iterate the readyListeners array
                    listener = readyListeners.shift();
                    listener.fn.call(listener.scope);
                }

                return Loader;
            },

            // Documented above already
            onReady: function(fn, scope, withDomReady, options) {
                var oldFn;

                if (withDomReady !== false && Ext.onDocumentReady) {
                    oldFn = fn;

                    fn = function() {
                        Ext.onDocumentReady(oldFn, scope, options);
                    };
                }

                if (!Loader.isLoading) {
                    fn.call(scope);
                }
                else {
                    readyListeners.push({
                        fn: fn,
                        scope: scope,
                        priority: (options && options.priority) || 0
                    });
                }
            },

            /**
             * @private
             * @param {String} className
             */
            historyPush: function(className) {
                if (className && isClassFileLoaded.hasOwnProperty(className) && !isInHistory[className]) {
                    isInHistory[className] = true;
                    history.push(className);
                }
                return Loader;
            }
        });

        /**
         * 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.
         * @private
         */
        Ext.disableCacheBuster = function (disable, path) {
            var date = new Date();
            date.setTime(date.getTime() + (disable ? 10*365 : -1) * 24*60*60*1000);
            date = date.toGMTString();
            document.cookie = 'ext-cache=1; expires=' + date + '; path='+(path || '/');
        };

        //<if nonBrowser>
        if (isNonBrowser) {
            if (isNodeJS) {
                Ext.apply(Loader, {
                    syncModeEnabled: true,
                    setPath: flexSetter(function(name, path) {
                        path = require('fs').realpathSync(path);
                        Loader.config.paths[name] = path;

                        return Loader;
                    }),

                    loadScriptFile: function(filePath, onLoad, onError, scope, synchronous) {
                        require(filePath);
                        onLoad.call(scope);
                    }
                });
            }
            else if (isJsdb) {
                Ext.apply(Loader, {
                    syncModeEnabled: true,
                    loadScriptFile: function(filePath, onLoad, onError, scope, synchronous) {
                        load(filePath);
                        onLoad.call(scope);
                    }
                });
            }
        }
        //</if>
        //</feature>

        /**
         * @member Ext
         * @method require
         * @inheritdoc Ext.Loader#require
         */
        Ext.require = alias(Loader, 'require');

        /**
         * @member Ext
         * @method syncRequire
         * @inheritdoc Ext.Loader#syncRequire
         */
        Ext.syncRequire = alias(Loader, 'syncRequire');

        /**
         * Convenient shortcut to {@link Ext.Loader#exclude}
         * @member Ext
         * @method exclude
         * @inheritdoc Ext.Loader#exclude
         */
        Ext.exclude = alias(Loader, 'exclude');

        /**
         * @member Ext
         * @method onReady
         * @ignore
         */
        Ext.onReady = function(fn, scope, options) {
            Loader.onReady(fn, scope, true, options);
        };

        /**
         * @cfg {String[]} requires
         * @member Ext.Class
         * List of classes that have to be loaded before instantiating this class.
         * For example:
         *
         *     Ext.define('Mother', {
         *         requires: ['Child'],
         *         giveBirth: function() {
         *             // we can be sure that child class is available.
         *             return new Child();
         *         }
         *     });
         */
        Class.registerPreprocessor('loader', function(cls, data, hooks, continueFn) {
            //<debug>
            Ext.classSystemMonitor && Ext.classSystemMonitor(cls, 'Ext.Loader#loaderPreprocessor', arguments);
            //</debug>

            var me = this,
                dependencies = [],
                dependency,
                className = Manager.getName(cls),
                i, j, ln, subLn, value, propertyName, propertyValue,
                requiredMap, requiredDep;

            /*
            Loop through the dependencyProperties, look for string class names and push
            them into a stack, regardless of whether the property's value is a string, array or object. For example:
            {
                  extend: 'Ext.MyClass',
                  requires: ['Ext.some.OtherClass'],
                  mixins: {
                      observable: 'Ext.util.Observable';
                  }
            }
            which will later be transformed into:
            {
                  extend: Ext.MyClass,
                  requires: [Ext.some.OtherClass],
                  mixins: {
                      observable: Ext.util.Observable;
                  }
            }
            */

            for (i = 0,ln = dependencyProperties.length; i < ln; i++) {
                propertyName = dependencyProperties[i];

                if (data.hasOwnProperty(propertyName)) {
                    propertyValue = data[propertyName];

                    if (typeof propertyValue == 'string') {
                        dependencies.push(propertyValue);
                    }
                    else if (propertyValue instanceof Array) {
                        for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
                            value = propertyValue[j];

                            if (typeof value == 'string') {
                                dependencies.push(value);
                            }
                        }
                    }
                    else if (typeof propertyValue != 'function') {
                        for (j in propertyValue) {
                            if (propertyValue.hasOwnProperty(j)) {
                                value = propertyValue[j];

                                if (typeof value == 'string') {
                                    dependencies.push(value);
                                }
                            }
                        }
                    }
                }
            }

            if (dependencies.length === 0) {
                return;
            }

            //<feature classSystem.loader>
            //<debug error>
            var deadlockPath = [],
                detectDeadlock;

            /*
            Automatically detect deadlocks before-hand,
            will throw an error with detailed path for ease of debugging. Examples of deadlock cases:

            - A extends B, then B extends A
            - A requires B, B requires C, then C requires A

            The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
            no matter how deep the path is.
            */

            if (className) {
                requiresMap[className] = dependencies;
                //<debug>
                requiredMap = Loader.requiredByMap || (Loader.requiredByMap = {});

                for (i = 0,ln = dependencies.length; i < ln; i++) {
                    dependency = dependencies[i];
                    (requiredMap[dependency] || (requiredMap[dependency] = [])).push(className);
                }
                //</debug>
                detectDeadlock = function(cls) {
                    deadlockPath.push(cls);

                    if (requiresMap[cls]) {
                        if (Ext.Array.contains(requiresMap[cls], className)) {
                            throw new Error("Deadlock detected while loading dependencies! '" + className + "' and '" +
                                    deadlockPath[1] + "' " + "mutually require each other. Path: " +
                                    deadlockPath.join(' -> ') + " -> " + deadlockPath[0]);
                        }

                        for (i = 0,ln = requiresMap[cls].length; i < ln; i++) {
                            detectDeadlock(requiresMap[cls][i]);
                        }
                    }
                };

                detectDeadlock(className);
            }

            //</debug>
            //</feature>

            Loader.require(dependencies, function() {
                for (i = 0,ln = dependencyProperties.length; i < ln; i++) {
                    propertyName = dependencyProperties[i];

                    if (data.hasOwnProperty(propertyName)) {
                        propertyValue = data[propertyName];

                        if (typeof propertyValue == 'string') {
                            data[propertyName] = Manager.get(propertyValue);
                        }
                        else if (propertyValue instanceof Array) {
                            for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
                                value = propertyValue[j];

                                if (typeof value == 'string') {
                                    data[propertyName][j] = Manager.get(value);
                                }
                            }
                        }
                        else if (typeof propertyValue != 'function') {
                            for (var k in propertyValue) {
                                if (propertyValue.hasOwnProperty(k)) {
                                    value = propertyValue[k];

                                    if (typeof value == 'string') {
                                        data[propertyName][k] = Manager.get(value);
                                    }
                                }
                            }
                        }
                    }
                }

                continueFn.call(me, cls, data, hooks);
            });

            return false;
        }, true, 'after', 'className');

        //<feature classSystem.loader>
        /**
         * @cfg {String[]} uses
         * @member Ext.Class
         * List of optional classes to load together with this class. These aren't neccessarily loaded before
         * this class is created, but are guaranteed to be available before Ext.onReady listeners are
         * invoked. For example:
         *
         *     Ext.define('Mother', {
         *         uses: ['Child'],
         *         giveBirth: function() {
         *             // This code might, or might not work:
         *             // return new Child();
         *
         *             // Instead use Ext.create() to load the class at the spot if not loaded already:
         *             return Ext.create('Child');
         *         }
         *     });
         */
        Manager.registerPostprocessor('uses', function(name, cls, data) {
            //<debug>
            Ext.classSystemMonitor && Ext.classSystemMonitor(cls, 'Ext.Loader#usesPostprocessor', arguments);
            //</debug>

            var uses = data.uses;
            if (uses) {
                Loader.addUsedClasses(uses);
            }
        });

        Manager.onCreated(Loader.historyPush);
        //</feature>
    };

    // simple mechanism for automated means of injecting large amounts of dependency info
    // at the appropriate time in the load cycle
    if (Ext._classPathMetadata) {
        Ext.Loader.addClassPathMappings(Ext._classPathMetadata);
        Ext._classPathMetadata = null;
    }

    // initalize the default path of the framework
    (function() {
        var scripts = document.getElementsByTagName('script'),
            currentScript = scripts[scripts.length - 1],
            src = currentScript.src,
            path = src.substring(0, src.lastIndexOf('/') + 1),
            Loader = Ext.Loader;

        //<debug>
        if(src.indexOf("/platform/core/src/class/") != -1) {
            path = path + "../../../../extjs/";
        } else if(src.indexOf("/core/src/class/") != -1) {
            path = path + "../../../";
        }
        //</debug>

        Loader.setConfig({
            enabled: true,
            disableCaching:
                //<debug>
                (/[?&](?:cache|disableCacheBuster)\b/i.test(location.search) ||
                 /(^|[ ;])ext-cache=1/.test(document.cookie)) ? false :
                //</debug>
                true,
            paths: {
                'Ext': path + 'src'
            }
        });
    })();

    // allows a tools like dynatrace to deterministically detect onReady state by invoking
    // a callback (intended for external consumption)
    Ext._endTime = new Date().getTime();
    if (Ext._beforereadyhandler){
        Ext._beforereadyhandler();
    }