src/cm/media/js/lib/yui/yui_3.10.3/build/router/router-debug.js
changeset 525 89ef5ed3c48b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cm/media/js/lib/yui/yui_3.10.3/build/router/router-debug.js	Tue Jul 16 14:29:46 2013 +0200
@@ -0,0 +1,1437 @@
+/*
+YUI 3.10.3 (build 2fb5187)
+Copyright 2013 Yahoo! Inc. All rights reserved.
+Licensed under the BSD License.
+http://yuilibrary.com/license/
+*/
+
+YUI.add('router', function (Y, NAME) {
+
+/**
+Provides URL-based routing using HTML5 `pushState()` or the location hash.
+
+@module app
+@submodule router
+@since 3.4.0
+**/
+
+var HistoryHash = Y.HistoryHash,
+    QS          = Y.QueryString,
+    YArray      = Y.Array,
+
+    win = Y.config.win,
+
+    // Holds all the active router instances. This supports the static
+    // `dispatch()` method which causes all routers to dispatch.
+    instances = [],
+
+    // We have to queue up pushState calls to avoid race conditions, since the
+    // popstate event doesn't actually provide any info on what URL it's
+    // associated with.
+    saveQueue = [],
+
+    /**
+    Fired when the router is ready to begin dispatching to route handlers.
+
+    You shouldn't need to wait for this event unless you plan to implement some
+    kind of custom dispatching logic. It's used internally in order to avoid
+    dispatching to an initial route if a browser history change occurs first.
+
+    @event ready
+    @param {Boolean} dispatched `true` if routes have already been dispatched
+      (most likely due to a history change).
+    @fireOnce
+    **/
+    EVT_READY = 'ready';
+
+/**
+Provides URL-based routing using HTML5 `pushState()` or the location hash.
+
+This makes it easy to wire up route handlers for different application states
+while providing full back/forward navigation support and bookmarkable, shareable
+URLs.
+
+@class Router
+@param {Object} [config] Config properties.
+    @param {Boolean} [config.html5] Overrides the default capability detection
+        and forces this router to use (`true`) or not use (`false`) HTML5
+        history.
+    @param {String} [config.root=''] Root path from which all routes should be
+        evaluated.
+    @param {Array} [config.routes=[]] Array of route definition objects.
+@constructor
+@extends Base
+@since 3.4.0
+**/
+function Router() {
+    Router.superclass.constructor.apply(this, arguments);
+}
+
+Y.Router = Y.extend(Router, Y.Base, {
+    // -- Protected Properties -------------------------------------------------
+
+    /**
+    Whether or not `_dispatch()` has been called since this router was
+    instantiated.
+
+    @property _dispatched
+    @type Boolean
+    @default undefined
+    @protected
+    **/
+
+    /**
+    Whether or not we're currently in the process of dispatching to routes.
+
+    @property _dispatching
+    @type Boolean
+    @default undefined
+    @protected
+    **/
+
+    /**
+    History event handle for the `history:change` or `hashchange` event
+    subscription.
+
+    @property _historyEvents
+    @type EventHandle
+    @protected
+    **/
+
+    /**
+    Cached copy of the `html5` attribute for internal use.
+
+    @property _html5
+    @type Boolean
+    @protected
+    **/
+
+    /**
+    Whether or not the `ready` event has fired yet.
+
+    @property _ready
+    @type Boolean
+    @default undefined
+    @protected
+    **/
+
+    /**
+    Regex used to match parameter placeholders in route paths.
+
+    Subpattern captures:
+
+      1. Parameter prefix character. Either a `:` for subpath parameters that
+         should only match a single level of a path, or `*` for splat parameters
+         that should match any number of path levels.
+
+      2. Parameter name, if specified, otherwise it is a wildcard match.
+
+    @property _regexPathParam
+    @type RegExp
+    @protected
+    **/
+    _regexPathParam: /([:*])([\w\-]+)?/g,
+
+    /**
+    Regex that matches and captures the query portion of a URL, minus the
+    preceding `?` character, and discarding the hash portion of the URL if any.
+
+    @property _regexUrlQuery
+    @type RegExp
+    @protected
+    **/
+    _regexUrlQuery: /\?([^#]*).*$/,
+
+    /**
+    Regex that matches everything before the path portion of a URL (the origin).
+    This will be used to strip this part of the URL from a string when we
+    only want the path.
+
+    @property _regexUrlOrigin
+    @type RegExp
+    @protected
+    **/
+    _regexUrlOrigin: /^(?:[^\/#?:]+:\/\/|\/\/)[^\/]*/,
+
+    // -- Lifecycle Methods ----------------------------------------------------
+    initializer: function (config) {
+        var self = this;
+
+        self._html5  = self.get('html5');
+        self._routes = [];
+        self._url    = self._getURL();
+
+        // Necessary because setters don't run on init.
+        self._setRoutes(config && config.routes ? config.routes :
+                self.get('routes'));
+
+        // Set up a history instance or hashchange listener.
+        if (self._html5) {
+            self._history       = new Y.HistoryHTML5({force: true});
+            self._historyEvents =
+                    Y.after('history:change', self._afterHistoryChange, self);
+        } else {
+            self._historyEvents =
+                    Y.on('hashchange', self._afterHistoryChange, win, self);
+        }
+
+        // Fire a `ready` event once we're ready to route. We wait first for all
+        // subclass initializers to finish, then for window.onload, and then an
+        // additional 20ms to allow the browser to fire a useless initial
+        // `popstate` event if it wants to (and Chrome always wants to).
+        self.publish(EVT_READY, {
+            defaultFn  : self._defReadyFn,
+            fireOnce   : true,
+            preventable: false
+        });
+
+        self.once('initializedChange', function () {
+            Y.once('load', function () {
+                setTimeout(function () {
+                    self.fire(EVT_READY, {dispatched: !!self._dispatched});
+                }, 20);
+            });
+        });
+
+        // Store this router in the collection of all active router instances.
+        instances.push(this);
+    },
+
+    destructor: function () {
+        var instanceIndex = YArray.indexOf(instances, this);
+
+        // Remove this router from the collection of active router instances.
+        if (instanceIndex > -1) {
+            instances.splice(instanceIndex, 1);
+        }
+
+        if (this._historyEvents) {
+            this._historyEvents.detach();
+        }
+    },
+
+    // -- Public Methods -------------------------------------------------------
+
+    /**
+    Dispatches to the first route handler that matches the current URL, if any.
+
+    If `dispatch()` is called before the `ready` event has fired, it will
+    automatically wait for the `ready` event before dispatching. Otherwise it
+    will dispatch immediately.
+
+    @method dispatch
+    @chainable
+    **/
+    dispatch: function () {
+        this.once(EVT_READY, function () {
+            this._ready = true;
+
+            if (!this.upgrade()) {
+                this._dispatch(this._getPath(), this._getURL());
+            }
+        });
+
+        return this;
+    },
+
+    /**
+    Gets the current route path, relative to the `root` (if any).
+
+    @method getPath
+    @return {String} Current route path.
+    **/
+    getPath: function () {
+        return this._getPath();
+    },
+
+    /**
+    Returns `true` if this router has at least one route that matches the
+    specified URL, `false` otherwise.
+
+    This method enforces the same-origin security constraint on the specified
+    `url`; any URL which is not from the same origin as the current URL will
+    always return `false`.
+
+    @method hasRoute
+    @param {String} url URL to match.
+    @return {Boolean} `true` if there's at least one matching route, `false`
+      otherwise.
+    **/
+    hasRoute: function (url) {
+        var path;
+
+        if (!this._hasSameOrigin(url)) {
+            return false;
+        }
+
+        if (!this._html5) {
+            url = this._upgradeURL(url);
+        }
+
+        path = this.removeQuery(this.removeRoot(url));
+
+        return !!this.match(path).length;
+    },
+
+    /**
+    Returns an array of route objects that match the specified URL path.
+
+    This method is called internally to determine which routes match the current
+    path whenever the URL changes. You may override it if you want to customize
+    the route matching logic, although this usually shouldn't be necessary.
+
+    Each returned route object has the following properties:
+
+      * `callback`: A function or a string representing the name of a function
+        this router that should be executed when the route is triggered.
+
+      * `keys`: An array of strings representing the named parameters defined in
+        the route's path specification, if any.
+
+      * `path`: The route's path specification, which may be either a string or
+        a regex.
+
+      * `regex`: A regular expression version of the route's path specification.
+        This regex is used to determine whether the route matches a given path.
+
+    @example
+        router.route('/foo', function () {});
+        router.match('/foo');
+        // => [{callback: ..., keys: [], path: '/foo', regex: ...}]
+
+    @method match
+    @param {String} path URL path to match.
+    @return {Object[]} Array of route objects that match the specified path.
+    **/
+    match: function (path) {
+        return YArray.filter(this._routes, function (route) {
+            return path.search(route.regex) > -1;
+        });
+    },
+
+    /**
+    Removes the `root` URL from the front of _url_ (if it's there) and returns
+    the result. The returned path will always have a leading `/`.
+
+    @method removeRoot
+    @param {String} url URL.
+    @return {String} Rootless path.
+    **/
+    removeRoot: function (url) {
+        var root = this.get('root');
+
+        // Strip out the non-path part of the URL, if any (e.g.
+        // "http://foo.com"), so that we're left with just the path.
+        url = url.replace(this._regexUrlOrigin, '');
+
+        if (root && url.indexOf(root) === 0) {
+            url = url.substring(root.length);
+        }
+
+        return url.charAt(0) === '/' ? url : '/' + url;
+    },
+
+    /**
+    Removes a query string from the end of the _url_ (if one exists) and returns
+    the result.
+
+    @method removeQuery
+    @param {String} url URL.
+    @return {String} Queryless path.
+    **/
+    removeQuery: function (url) {
+        return url.replace(/\?.*$/, '');
+    },
+
+    /**
+    Replaces the current browser history entry with a new one, and dispatches to
+    the first matching route handler, if any.
+
+    Behind the scenes, this method uses HTML5 `pushState()` in browsers that
+    support it (or the location hash in older browsers and IE) to change the
+    URL.
+
+    The specified URL must share the same origin (i.e., protocol, host, and
+    port) as the current page, or an error will occur.
+
+    @example
+        // Starting URL: http://example.com/
+
+        router.replace('/path/');
+        // New URL: http://example.com/path/
+
+        router.replace('/path?foo=bar');
+        // New URL: http://example.com/path?foo=bar
+
+        router.replace('/');
+        // New URL: http://example.com/
+
+    @method replace
+    @param {String} [url] URL to set. This URL needs to be of the same origin as
+      the current URL. This can be a URL relative to the router's `root`
+      attribute. If no URL is specified, the page's current URL will be used.
+    @chainable
+    @see save()
+    **/
+    replace: function (url) {
+        return this._queue(url, true);
+    },
+
+    /**
+    Adds a route handler for the specified URL _path_.
+
+    The _path_ parameter may be either a string or a regular expression. If it's
+    a string, it may contain named parameters: `:param` will match any single
+    part of a URL path (not including `/` characters), and `*param` will match
+    any number of parts of a URL path (including `/` characters). These named
+    parameters will be made available as keys on the `req.params` object that's
+    passed to route handlers.
+
+    If the _path_ parameter is a regex, all pattern matches will be made
+    available as numbered keys on `req.params`, starting with `0` for the full
+    match, then `1` for the first subpattern match, and so on.
+
+    Here's a set of sample routes along with URL paths that they match:
+
+      * Route: `/photos/:tag/:page`
+        * URL: `/photos/kittens/1`, params: `{tag: 'kittens', page: '1'}`
+        * URL: `/photos/puppies/2`, params: `{tag: 'puppies', page: '2'}`
+
+      * Route: `/file/*path`
+        * URL: `/file/foo/bar/baz.txt`, params: `{path: 'foo/bar/baz.txt'}`
+        * URL: `/file/foo`, params: `{path: 'foo'}`
+
+    **Middleware**: Routes also support an arbitrary number of callback
+    functions. This allows you to easily reuse parts of your route-handling code
+    with different route. This method is liberal in how it processes the
+    specified `callbacks`, you can specify them as separate arguments, or as
+    arrays, or both.
+
+    If multiple route match a given URL, they will be executed in the order they
+    were added. The first route that was added will be the first to be executed.
+
+    **Passing Control**: Invoking the `next()` function within a route callback
+    will pass control to the next callback function (if any) or route handler
+    (if any). If a value is passed to `next()`, it's assumed to be an error,
+    therefore stopping the dispatch chain, unless that value is: `"route"`,
+    which is special case and dispatching will skip to the next route handler.
+    This allows middleware to skip any remaining middleware for a particular
+    route.
+
+    @example
+        router.route('/photos/:tag/:page', function (req, res, next) {
+          Y.log('Current tag: ' + req.params.tag);
+          Y.log('Current page number: ' + req.params.page);
+        });
+
+        // Using middleware.
+
+        router.findUser = function (req, res, next) {
+            req.user = this.get('users').findById(req.params.user);
+            next();
+        };
+
+        router.route('/users/:user', 'findUser', function (req, res, next) {
+            // The `findUser` middleware puts the `user` object on the `req`.
+            Y.log('Current user:' req.user.get('name'));
+        });
+
+    @method route
+    @param {String|RegExp} path Path to match. May be a string or a regular
+      expression.
+    @param {Array|Function|String} callbacks* Callback functions to call
+        whenever this route is triggered. These can be specified as separate
+        arguments, or in arrays, or both. If a callback is specified as a
+        string, the named function will be called on this router instance.
+
+      @param {Object} callbacks.req Request object containing information about
+          the request. It contains the following properties.
+
+        @param {Array|Object} callbacks.req.params Captured parameters matched by
+          the route path specification. If a string path was used and contained
+          named parameters, then this will be a key/value hash mapping parameter
+          names to their matched values. If a regex path was used, this will be
+          an array of subpattern matches starting at index 0 for the full match,
+          then 1 for the first subpattern match, and so on.
+        @param {String} callbacks.req.path The current URL path.
+        @param {Number} callbacks.req.pendingCallbacks Number of remaining
+          callbacks the route handler has after this one in the dispatch chain.
+        @param {Number} callbacks.req.pendingRoutes Number of matching routes
+          after this one in the dispatch chain.
+        @param {Object} callbacks.req.query Query hash representing the URL
+          query string, if any. Parameter names are keys, and are mapped to
+          parameter values.
+        @param {String} callbacks.req.url The full URL.
+        @param {String} callbacks.req.src What initiated the dispatch. In an
+          HTML5 browser, when the back/forward buttons are used, this property
+          will have a value of "popstate".
+
+      @param {Object} callbacks.res Response object containing methods and
+          information that relate to responding to a request. It contains the
+          following properties.
+        @param {Object} callbacks.res.req Reference to the request object.
+
+      @param {Function} callbacks.next Function to pass control to the next
+          callback or the next matching route if no more callbacks (middleware)
+          exist for the current route handler. If you don't call this function,
+          then no further callbacks or route handlers will be executed, even if
+          there are more that match. If you do call this function, then the next
+          callback (if any) or matching route handler (if any) will be called.
+          All of these functions will receive the same `req` and `res` objects
+          that were passed to this route (so you can use these objects to pass
+          data along to subsequent callbacks and routes).
+        @param {String} [callbacks.next.err] Optional error which will stop the
+          dispatch chaining for this `req`, unless the value is `"route"`, which
+          is special cased to jump skip past any callbacks for the current route
+          and pass control the next route handler.
+    @chainable
+    **/
+    route: function (path, callbacks) {
+        callbacks = YArray.flatten(YArray(arguments, 1, true));
+
+        var keys = [];
+
+        this._routes.push({
+            callbacks: callbacks,
+            keys     : keys,
+            path     : path,
+            regex    : this._getRegex(path, keys),
+
+            // For back-compat.
+            callback: callbacks[0]
+        });
+
+        return this;
+    },
+
+    /**
+    Saves a new browser history entry and dispatches to the first matching route
+    handler, if any.
+
+    Behind the scenes, this method uses HTML5 `pushState()` in browsers that
+    support it (or the location hash in older browsers and IE) to change the
+    URL and create a history entry.
+
+    The specified URL must share the same origin (i.e., protocol, host, and
+    port) as the current page, or an error will occur.
+
+    @example
+        // Starting URL: http://example.com/
+
+        router.save('/path/');
+        // New URL: http://example.com/path/
+
+        router.save('/path?foo=bar');
+        // New URL: http://example.com/path?foo=bar
+
+        router.save('/');
+        // New URL: http://example.com/
+
+    @method save
+    @param {String} [url] URL to set. This URL needs to be of the same origin as
+      the current URL. This can be a URL relative to the router's `root`
+      attribute. If no URL is specified, the page's current URL will be used.
+    @chainable
+    @see replace()
+    **/
+    save: function (url) {
+        return this._queue(url);
+    },
+
+    /**
+    Upgrades a hash-based URL to an HTML5 URL if necessary. In non-HTML5
+    browsers, this method is a noop.
+
+    @method upgrade
+    @return {Boolean} `true` if the URL was upgraded, `false` otherwise.
+    **/
+    upgrade: function () {
+        if (!this._html5) {
+            return false;
+        }
+
+        // Get the resolve hash path.
+        var hashPath = this._getHashPath();
+
+        if (hashPath) {
+            // This is an HTML5 browser and we have a hash-based path in the
+            // URL, so we need to upgrade the URL to a non-hash URL. This
+            // will trigger a `history:change` event, which will in turn
+            // trigger a dispatch.
+            this.once(EVT_READY, function () {
+                this.replace(hashPath);
+            });
+
+            return true;
+        }
+
+        return false;
+    },
+
+    // -- Protected Methods ----------------------------------------------------
+
+    /**
+    Wrapper around `decodeURIComponent` that also converts `+` chars into
+    spaces.
+
+    @method _decode
+    @param {String} string String to decode.
+    @return {String} Decoded string.
+    @protected
+    **/
+    _decode: function (string) {
+        return decodeURIComponent(string.replace(/\+/g, ' '));
+    },
+
+    /**
+    Shifts the topmost `_save()` call off the queue and executes it. Does
+    nothing if the queue is empty.
+
+    @method _dequeue
+    @chainable
+    @see _queue
+    @protected
+    **/
+    _dequeue: function () {
+        var self = this,
+            fn;
+
+        // If window.onload hasn't yet fired, wait until it has before
+        // dequeueing. This will ensure that we don't call pushState() before an
+        // initial popstate event has fired.
+        if (!YUI.Env.windowLoaded) {
+            Y.once('load', function () {
+                self._dequeue();
+            });
+
+            return this;
+        }
+
+        fn = saveQueue.shift();
+        return fn ? fn() : this;
+    },
+
+    /**
+    Dispatches to the first route handler that matches the specified _path_.
+
+    If called before the `ready` event has fired, the dispatch will be aborted.
+    This ensures normalized behavior between Chrome (which fires a `popstate`
+    event on every pageview) and other browsers (which do not).
+
+    @method _dispatch
+    @param {String} path URL path.
+    @param {String} url Full URL.
+    @param {String} src What initiated the dispatch.
+    @chainable
+    @protected
+    **/
+    _dispatch: function (path, url, src) {
+        var self      = this,
+            decode    = self._decode,
+            routes    = self.match(path),
+            callbacks = [],
+            matches, req, res;
+
+        self._dispatching = self._dispatched = true;
+
+        if (!routes || !routes.length) {
+            self._dispatching = false;
+            return self;
+        }
+
+        req = self._getRequest(path, url, src);
+        res = self._getResponse(req);
+
+        req.next = function (err) {
+            var callback, name, route;
+
+            if (err) {
+                // Special case "route" to skip to the next route handler
+                // avoiding any additional callbacks for the current route.
+                if (err === 'route') {
+                    callbacks = [];
+                    req.next();
+                } else {
+                    Y.error(err);
+                }
+
+            } else if ((callback = callbacks.shift())) {
+                if (typeof callback === 'string') {
+                    name     = callback;
+                    callback = self[name];
+
+                    if (!callback) {
+                        Y.error('Router: Callback not found: ' + name, null, 'router');
+                    }
+                }
+
+                // Allow access to the number of remaining callbacks for the
+                // route.
+                req.pendingCallbacks = callbacks.length;
+
+                callback.call(self, req, res, req.next);
+
+            } else if ((route = routes.shift())) {
+                // Make a copy of this route's `callbacks` so the original array
+                // is preserved.
+                callbacks = route.callbacks.concat();
+
+                // Decode each of the path matches so that the any URL-encoded
+                // path segments are decoded in the `req.params` object.
+                matches = YArray.map(route.regex.exec(path) || [], decode);
+
+                // Use named keys for parameter names if the route path contains
+                // named keys. Otherwise, use numerical match indices.
+                if (matches.length === route.keys.length + 1) {
+                    req.params = YArray.hash(route.keys, matches.slice(1));
+                } else {
+                    req.params = matches.concat();
+                }
+
+                // Allow access to the number of remaining routes for this
+                // request.
+                req.pendingRoutes = routes.length;
+
+                // Execute this route's `callbacks`.
+                req.next();
+            }
+        };
+
+        req.next();
+
+        self._dispatching = false;
+        return self._dequeue();
+    },
+
+    /**
+    Returns the resolved path from the hash fragment, or an empty string if the
+    hash is not path-like.
+
+    @method _getHashPath
+    @param {String} [hash] Hash fragment to resolve into a path. By default this
+        will be the hash from the current URL.
+    @return {String} Current hash path, or an empty string if the hash is empty.
+    @protected
+    **/
+    _getHashPath: function (hash) {
+        hash || (hash = HistoryHash.getHash());
+
+        // Make sure the `hash` is path-like.
+        if (hash && hash.charAt(0) === '/') {
+            return this._joinURL(hash);
+        }
+
+        return '';
+    },
+
+    /**
+    Gets the location origin (i.e., protocol, host, and port) as a URL.
+
+    @example
+        http://example.com
+
+    @method _getOrigin
+    @return {String} Location origin (i.e., protocol, host, and port).
+    @protected
+    **/
+    _getOrigin: function () {
+        var location = Y.getLocation();
+        return location.origin || (location.protocol + '//' + location.host);
+    },
+
+    /**
+    Gets the current route path, relative to the `root` (if any).
+
+    @method _getPath
+    @return {String} Current route path.
+    @protected
+    **/
+    _getPath: function () {
+        var path = (!this._html5 && this._getHashPath()) ||
+                Y.getLocation().pathname;
+
+        return this.removeQuery(this.removeRoot(path));
+    },
+
+    /**
+    Returns the current path root after popping off the last path segment,
+    making it useful for resolving other URL paths against.
+
+    The path root will always begin and end with a '/'.
+
+    @method _getPathRoot
+    @return {String} The URL's path root.
+    @protected
+    @since 3.5.0
+    **/
+    _getPathRoot: function () {
+        var slash = '/',
+            path  = Y.getLocation().pathname,
+            segments;
+
+        if (path.charAt(path.length - 1) === slash) {
+            return path;
+        }
+
+        segments = path.split(slash);
+        segments.pop();
+
+        return segments.join(slash) + slash;
+    },
+
+    /**
+    Gets the current route query string.
+
+    @method _getQuery
+    @return {String} Current route query string.
+    @protected
+    **/
+    _getQuery: function () {
+        var location = Y.getLocation(),
+            hash, matches;
+
+        if (this._html5) {
+            return location.search.substring(1);
+        }
+
+        hash    = HistoryHash.getHash();
+        matches = hash.match(this._regexUrlQuery);
+
+        return hash && matches ? matches[1] : location.search.substring(1);
+    },
+
+    /**
+    Creates a regular expression from the given route specification. If _path_
+    is already a regex, it will be returned unmodified.
+
+    @method _getRegex
+    @param {String|RegExp} path Route path specification.
+    @param {Array} keys Array reference to which route parameter names will be
+      added.
+    @return {RegExp} Route regex.
+    @protected
+    **/
+    _getRegex: function (path, keys) {
+        if (path instanceof RegExp) {
+            return path;
+        }
+
+        // Special case for catchall paths.
+        if (path === '*') {
+            return (/.*/);
+        }
+
+        path = path.replace(this._regexPathParam, function (match, operator, key) {
+            // Only `*` operators are supported for key-less matches to allowing
+            // in-path wildcards like: '/foo/*'.
+            if (!key) {
+                return operator === '*' ? '.*' : match;
+            }
+
+            keys.push(key);
+            return operator === '*' ? '(.*?)' : '([^/#?]*)';
+        });
+
+        return new RegExp('^' + path + '$');
+    },
+
+    /**
+    Gets a request object that can be passed to a route handler.
+
+    @method _getRequest
+    @param {String} path Current path being dispatched.
+    @param {String} url Current full URL being dispatched.
+    @param {String} src What initiated the dispatch.
+    @return {Object} Request object.
+    @protected
+    **/
+    _getRequest: function (path, url, src) {
+        return {
+            path : path,
+            query: this._parseQuery(this._getQuery()),
+            url  : url,
+            src  : src
+        };
+    },
+
+    /**
+    Gets a response object that can be passed to a route handler.
+
+    @method _getResponse
+    @param {Object} req Request object.
+    @return {Object} Response Object.
+    @protected
+    **/
+    _getResponse: function (req) {
+        // For backwards compatibility, the response object is a function that
+        // calls `next()` on the request object and returns the result.
+        var res = function () {
+            return req.next.apply(this, arguments);
+        };
+
+        res.req = req;
+        return res;
+    },
+
+    /**
+    Getter for the `routes` attribute.
+
+    @method _getRoutes
+    @return {Object[]} Array of route objects.
+    @protected
+    **/
+    _getRoutes: function () {
+        return this._routes.concat();
+    },
+
+    /**
+    Gets the current full URL.
+
+    @method _getURL
+    @return {String} URL.
+    @protected
+    **/
+    _getURL: function () {
+        var url = Y.getLocation().toString();
+
+        if (!this._html5) {
+            url = this._upgradeURL(url);
+        }
+
+        return url;
+    },
+
+    /**
+    Returns `true` when the specified `url` is from the same origin as the
+    current URL; i.e., the protocol, host, and port of the URLs are the same.
+
+    All host or path relative URLs are of the same origin. A scheme-relative URL
+    is first prefixed with the current scheme before being evaluated.
+
+    @method _hasSameOrigin
+    @param {String} url URL to compare origin with the current URL.
+    @return {Boolean} Whether the URL has the same origin of the current URL.
+    @protected
+    **/
+    _hasSameOrigin: function (url) {
+        var origin = ((url && url.match(this._regexUrlOrigin)) || [])[0];
+
+        // Prepend current scheme to scheme-relative URLs.
+        if (origin && origin.indexOf('//') === 0) {
+            origin = Y.getLocation().protocol + origin;
+        }
+
+        return !origin || origin === this._getOrigin();
+    },
+
+    /**
+    Joins the `root` URL to the specified _url_, normalizing leading/trailing
+    `/` characters.
+
+    @example
+        router.set('root', '/foo');
+        router._joinURL('bar');  // => '/foo/bar'
+        router._joinURL('/bar'); // => '/foo/bar'
+
+        router.set('root', '/foo/');
+        router._joinURL('bar');  // => '/foo/bar'
+        router._joinURL('/bar'); // => '/foo/bar'
+
+    @method _joinURL
+    @param {String} url URL to append to the `root` URL.
+    @return {String} Joined URL.
+    @protected
+    **/
+    _joinURL: function (url) {
+        var root = this.get('root');
+
+        // Causes `url` to _always_ begin with a "/".
+        url = this.removeRoot(url);
+
+        if (url.charAt(0) === '/') {
+            url = url.substring(1);
+        }
+
+        return root && root.charAt(root.length - 1) === '/' ?
+                root + url :
+                root + '/' + url;
+    },
+
+    /**
+    Returns a normalized path, ridding it of any '..' segments and properly
+    handling leading and trailing slashes.
+
+    @method _normalizePath
+    @param {String} path URL path to normalize.
+    @return {String} Normalized path.
+    @protected
+    @since 3.5.0
+    **/
+    _normalizePath: function (path) {
+        var dots  = '..',
+            slash = '/',
+            i, len, normalized, segments, segment, stack;
+
+        if (!path || path === slash) {
+            return slash;
+        }
+
+        segments = path.split(slash);
+        stack    = [];
+
+        for (i = 0, len = segments.length; i < len; ++i) {
+            segment = segments[i];
+
+            if (segment === dots) {
+                stack.pop();
+            } else if (segment) {
+                stack.push(segment);
+            }
+        }
+
+        normalized = slash + stack.join(slash);
+
+        // Append trailing slash if necessary.
+        if (normalized !== slash && path.charAt(path.length - 1) === slash) {
+            normalized += slash;
+        }
+
+        return normalized;
+    },
+
+    /**
+    Parses a URL query string into a key/value hash. If `Y.QueryString.parse` is
+    available, this method will be an alias to that.
+
+    @method _parseQuery
+    @param {String} query Query string to parse.
+    @return {Object} Hash of key/value pairs for query parameters.
+    @protected
+    **/
+    _parseQuery: QS && QS.parse ? QS.parse : function (query) {
+        var decode = this._decode,
+            params = query.split('&'),
+            i      = 0,
+            len    = params.length,
+            result = {},
+            param;
+
+        for (; i < len; ++i) {
+            param = params[i].split('=');
+
+            if (param[0]) {
+                result[decode(param[0])] = decode(param[1] || '');
+            }
+        }
+
+        return result;
+    },
+
+    /**
+    Queues up a `_save()` call to run after all previously-queued calls have
+    finished.
+
+    This is necessary because if we make multiple `_save()` calls before the
+    first call gets dispatched, then both calls will dispatch to the last call's
+    URL.
+
+    All arguments passed to `_queue()` will be passed on to `_save()` when the
+    queued function is executed.
+
+    @method _queue
+    @chainable
+    @see _dequeue
+    @protected
+    **/
+    _queue: function () {
+        var args = arguments,
+            self = this;
+
+        saveQueue.push(function () {
+            if (self._html5) {
+                if (Y.UA.ios && Y.UA.ios < 5) {
+                    // iOS <5 has buggy HTML5 history support, and needs to be
+                    // synchronous.
+                    self._save.apply(self, args);
+                } else {
+                    // Wrapped in a timeout to ensure that _save() calls are
+                    // always processed asynchronously. This ensures consistency
+                    // between HTML5- and hash-based history.
+                    setTimeout(function () {
+                        self._save.apply(self, args);
+                    }, 1);
+                }
+            } else {
+                self._dispatching = true; // otherwise we'll dequeue too quickly
+                self._save.apply(self, args);
+            }
+
+            return self;
+        });
+
+        return !this._dispatching ? this._dequeue() : this;
+    },
+
+    /**
+    Returns the normalized result of resolving the `path` against the current
+    path. Falsy values for `path` will return just the current path.
+
+    @method _resolvePath
+    @param {String} path URL path to resolve.
+    @return {String} Resolved path.
+    @protected
+    @since 3.5.0
+    **/
+    _resolvePath: function (path) {
+        if (!path) {
+            return Y.getLocation().pathname;
+        }
+
+        if (path.charAt(0) !== '/') {
+            path = this._getPathRoot() + path;
+        }
+
+        return this._normalizePath(path);
+    },
+
+    /**
+    Resolves the specified URL against the current URL.
+
+    This method resolves URLs like a browser does and will always return an
+    absolute URL. When the specified URL is already absolute, it is assumed to
+    be fully resolved and is simply returned as is. Scheme-relative URLs are
+    prefixed with the current protocol. Relative URLs are giving the current
+    URL's origin and are resolved and normalized against the current path root.
+
+    @method _resolveURL
+    @param {String} url URL to resolve.
+    @return {String} Resolved URL.
+    @protected
+    @since 3.5.0
+    **/
+    _resolveURL: function (url) {
+        var parts    = url && url.match(this._regexURL),
+            origin, path, query, hash, resolved;
+
+        if (!parts) {
+            return Y.getLocation().toString();
+        }
+
+        origin = parts[1];
+        path   = parts[2];
+        query  = parts[3];
+        hash   = parts[4];
+
+        // Absolute and scheme-relative URLs are assumed to be fully-resolved.
+        if (origin) {
+            // Prepend the current scheme for scheme-relative URLs.
+            if (origin.indexOf('//') === 0) {
+                origin = Y.getLocation().protocol + origin;
+            }
+
+            return origin + (path || '/') + (query || '') + (hash || '');
+        }
+
+        // Will default to the current origin and current path.
+        resolved = this._getOrigin() + this._resolvePath(path);
+
+        // A path or query for the specified URL trumps the current URL's.
+        if (path || query) {
+            return resolved + (query || '') + (hash || '');
+        }
+
+        query = this._getQuery();
+
+        return resolved + (query ? ('?' + query) : '') + (hash || '');
+    },
+
+    /**
+    Saves a history entry using either `pushState()` or the location hash.
+
+    This method enforces the same-origin security constraint; attempting to save
+    a `url` that is not from the same origin as the current URL will result in
+    an error.
+
+    @method _save
+    @param {String} [url] URL for the history entry.
+    @param {Boolean} [replace=false] If `true`, the current history entry will
+      be replaced instead of a new one being added.
+    @chainable
+    @protected
+    **/
+    _save: function (url, replace) {
+        var urlIsString = typeof url === 'string',
+            currentPath, root, hash;
+
+        // Perform same-origin check on the specified URL.
+        if (urlIsString && !this._hasSameOrigin(url)) {
+            Y.error('Security error: The new URL must be of the same origin as the current URL.');
+            return this;
+        }
+
+        // Joins the `url` with the `root`.
+        if (urlIsString) {
+            url = this._joinURL(url);
+        }
+
+        // Force _ready to true to ensure that the history change is handled
+        // even if _save is called before the `ready` event fires.
+        this._ready = true;
+
+        if (this._html5) {
+            this._history[replace ? 'replace' : 'add'](null, {url: url});
+        } else {
+            currentPath = Y.getLocation().pathname;
+            root        = this.get('root');
+            hash        = HistoryHash.getHash();
+
+            if (!urlIsString) {
+                url = hash;
+            }
+
+            // Determine if the `root` already exists in the current location's
+            // `pathname`, and if it does then we can exclude it from the
+            // hash-based path. No need to duplicate the info in the URL.
+            if (root === currentPath || root === this._getPathRoot()) {
+                url = this.removeRoot(url);
+            }
+
+            // The `hashchange` event only fires when the new hash is actually
+            // different. This makes sure we'll always dequeue and dispatch
+            // _all_ router instances, mimicking the HTML5 behavior.
+            if (url === hash) {
+                Y.Router.dispatch();
+            } else {
+                HistoryHash[replace ? 'replaceHash' : 'setHash'](url);
+            }
+        }
+
+        return this;
+    },
+
+    /**
+    Setter for the `routes` attribute.
+
+    @method _setRoutes
+    @param {Object[]} routes Array of route objects.
+    @return {Object[]} Array of route objects.
+    @protected
+    **/
+    _setRoutes: function (routes) {
+        this._routes = [];
+
+        YArray.each(routes, function (route) {
+            // Makes sure to check `callback` for back-compat.
+            var callbacks = route.callbacks || route.callback;
+
+            this.route(route.path, callbacks);
+        }, this);
+
+        return this._routes.concat();
+    },
+
+    /**
+    Upgrades a hash-based URL to a full-path URL, if necessary.
+
+    The specified `url` will be upgraded if its of the same origin as the
+    current URL and has a path-like hash. URLs that don't need upgrading will be
+    returned as-is.
+
+    @example
+        app._upgradeURL('http://example.com/#/foo/'); // => 'http://example.com/foo/';
+
+    @method _upgradeURL
+    @param {String} url The URL to upgrade from hash-based to full-path.
+    @return {String} The upgraded URL, or the specified URL untouched.
+    @protected
+    @since 3.5.0
+    **/
+    _upgradeURL: function (url) {
+        // We should not try to upgrade paths for external URLs.
+        if (!this._hasSameOrigin(url)) {
+            return url;
+        }
+
+        var hash       = (url.match(/#(.*)$/) || [])[1] || '',
+            hashPrefix = Y.HistoryHash.hashPrefix,
+            hashPath;
+
+        // Strip any hash prefix, like hash-bangs.
+        if (hashPrefix && hash.indexOf(hashPrefix) === 0) {
+            hash = hash.replace(hashPrefix, '');
+        }
+
+        // If the hash looks like a URL path, assume it is, and upgrade it!
+        if (hash) {
+            hashPath = this._getHashPath(hash);
+
+            if (hashPath) {
+                return this._resolveURL(hashPath);
+            }
+        }
+
+        return url;
+    },
+
+    // -- Protected Event Handlers ---------------------------------------------
+
+    /**
+    Handles `history:change` and `hashchange` events.
+
+    @method _afterHistoryChange
+    @param {EventFacade} e
+    @protected
+    **/
+    _afterHistoryChange: function (e) {
+        var self       = this,
+            src        = e.src,
+            prevURL    = self._url,
+            currentURL = self._getURL();
+
+        self._url = currentURL;
+
+        // Handles the awkwardness that is the `popstate` event. HTML5 browsers
+        // fire `popstate` right before they fire `hashchange`, and Chrome fires
+        // `popstate` on page load. If this router is not ready or the previous
+        // and current URLs only differ by their hash, then we want to ignore
+        // this `popstate` event.
+        if (src === 'popstate' &&
+                (!self._ready || prevURL.replace(/#.*$/, '') === currentURL.replace(/#.*$/, ''))) {
+
+            return;
+        }
+
+        self._dispatch(self._getPath(), currentURL, src);
+    },
+
+    // -- Default Event Handlers -----------------------------------------------
+
+    /**
+    Default handler for the `ready` event.
+
+    @method _defReadyFn
+    @param {EventFacade} e
+    @protected
+    **/
+    _defReadyFn: function (e) {
+        this._ready = true;
+    }
+}, {
+    // -- Static Properties ----------------------------------------------------
+    NAME: 'router',
+
+    ATTRS: {
+        /**
+        Whether or not this browser is capable of using HTML5 history.
+
+        Setting this to `false` will force the use of hash-based history even on
+        HTML5 browsers, but please don't do this unless you understand the
+        consequences.
+
+        @attribute html5
+        @type Boolean
+        @initOnly
+        **/
+        html5: {
+            // Android versions lower than 3.0 are buggy and don't update
+            // window.location after a pushState() call, so we fall back to
+            // hash-based history for them.
+            //
+            // See http://code.google.com/p/android/issues/detail?id=17471
+            valueFn: function () { return Y.Router.html5; },
+            writeOnce: 'initOnly'
+        },
+
+        /**
+        Absolute root path from which all routes should be evaluated.
+
+        For example, if your router is running on a page at
+        `http://example.com/myapp/` and you add a route with the path `/`, your
+        route will never execute, because the path will always be preceded by
+        `/myapp`. Setting `root` to `/myapp` would cause all routes to be
+        evaluated relative to that root URL, so the `/` route would then execute
+        when the user browses to `http://example.com/myapp/`.
+
+        @attribute root
+        @type String
+        @default `''`
+        **/
+        root: {
+            value: ''
+        },
+
+        /**
+        Array of route objects.
+
+        Each item in the array must be an object with the following properties:
+
+          * `path`: String or regex representing the path to match. See the docs
+            for the `route()` method for more details.
+
+          * `callbacks`: Function or a string representing the name of a
+            function on this router instance that should be called when the
+            route is triggered. An array of functions and/or strings may also be
+            provided. See the docs for the `route()` method for more details.
+
+        This attribute is intended to be used to set routes at init time, or to
+        completely reset all routes after init. To add routes after init without
+        resetting all existing routes, use the `route()` method.
+
+        @attribute routes
+        @type Object[]
+        @default `[]`
+        @see route
+        **/
+        routes: {
+            value : [],
+            getter: '_getRoutes',
+            setter: '_setRoutes'
+        }
+    },
+
+    // Used as the default value for the `html5` attribute, and for testing.
+    html5: Y.HistoryBase.html5 && (!Y.UA.android || Y.UA.android >= 3),
+
+    // To make this testable.
+    _instances: instances,
+
+    /**
+    Dispatches to the first route handler that matches the specified `path` for
+    all active router instances.
+
+    This provides a mechanism to cause all active router instances to dispatch
+    to their route handlers without needing to change the URL or fire the
+    `history:change` or `hashchange` event.
+
+    @method dispatch
+    @static
+    @since 3.6.0
+    **/
+    dispatch: function () {
+        var i, len, router;
+
+        for (i = 0, len = instances.length; i < len; i += 1) {
+            router = instances[i];
+
+            if (router) {
+                router._dispatch(router._getPath(), router._getURL());
+            }
+        }
+    }
+});
+
+/**
+The `Controller` class was deprecated in YUI 3.5.0 and is now an alias for the
+`Router` class. Use that class instead. This alias will be removed in a future
+version of YUI.
+
+@class Controller
+@constructor
+@extends Base
+@deprecated Use `Router` instead.
+@see Router
+**/
+Y.Controller = Y.Router;
+
+
+}, '3.10.3', {"optional": ["querystring-parse"], "requires": ["array-extras", "base-build", "history"]});