~ubuntu-branches/ubuntu/precise/maas/precise-updates

« back to all changes in this revision

Viewing changes to debian/extras/jslibs/yui/router/router-debug.js

Tags: 1.2+bzr1373+dfsg-0ubuntu1~12.04.4
* SECURITY UPDATE: failure to authenticate downloaded content (LP: #1039513)
  - debian/patches/CVE-2013-1058.patch: Authenticate downloaded files with
    GnuPG and MD5SUM files. Thanks to Julian Edwards.
  - CVE-2013-1058
* SECURITY UPDATE: configuration options may be loaded from current working
  directory (LP: #1158425)
  - debian/patches/CVE-2013-1057-1-2.patch: Do not load configuration
    options from the current working directory. Thanks to Julian Edwards.
  - CVE-2013-1057

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
YUI 3.5.1 (build 22)
 
3
Copyright 2012 Yahoo! Inc. All rights reserved.
 
4
Licensed under the BSD License.
 
5
http://yuilibrary.com/license/
 
6
*/
 
7
YUI.add('router', function(Y) {
 
8
 
 
9
/**
 
10
Provides URL-based routing using HTML5 `pushState()` or the location hash.
 
11
 
 
12
@module app
 
13
@submodule router
 
14
@since 3.4.0
 
15
**/
 
16
 
 
17
var HistoryHash = Y.HistoryHash,
 
18
    QS          = Y.QueryString,
 
19
    YArray      = Y.Array,
 
20
 
 
21
    win = Y.config.win,
 
22
 
 
23
    // We have to queue up pushState calls to avoid race conditions, since the
 
24
    // popstate event doesn't actually provide any info on what URL it's
 
25
    // associated with.
 
26
    saveQueue = [],
 
27
 
 
28
    /**
 
29
    Fired when the router is ready to begin dispatching to route handlers.
 
30
 
 
31
    You shouldn't need to wait for this event unless you plan to implement some
 
32
    kind of custom dispatching logic. It's used internally in order to avoid
 
33
    dispatching to an initial route if a browser history change occurs first.
 
34
 
 
35
    @event ready
 
36
    @param {Boolean} dispatched `true` if routes have already been dispatched
 
37
      (most likely due to a history change).
 
38
    @fireOnce
 
39
    **/
 
40
    EVT_READY = 'ready';
 
41
 
 
42
/**
 
43
Provides URL-based routing using HTML5 `pushState()` or the location hash.
 
44
 
 
45
This makes it easy to wire up route handlers for different application states
 
46
while providing full back/forward navigation support and bookmarkable, shareable
 
47
URLs.
 
48
 
 
49
@class Router
 
50
@param {Object} [config] Config properties.
 
51
    @param {Boolean} [config.html5] Overrides the default capability detection
 
52
        and forces this router to use (`true`) or not use (`false`) HTML5
 
53
        history.
 
54
    @param {String} [config.root=''] Root path from which all routes should be
 
55
        evaluated.
 
56
    @param {Array} [config.routes=[]] Array of route definition objects.
 
57
@constructor
 
58
@extends Base
 
59
@since 3.4.0
 
60
**/
 
61
function Router() {
 
62
    Router.superclass.constructor.apply(this, arguments);
 
63
}
 
64
 
 
65
Y.Router = Y.extend(Router, Y.Base, {
 
66
    // -- Protected Properties -------------------------------------------------
 
67
 
 
68
    /**
 
69
    Whether or not `_dispatch()` has been called since this router was
 
70
    instantiated.
 
71
 
 
72
    @property _dispatched
 
73
    @type Boolean
 
74
    @default undefined
 
75
    @protected
 
76
    **/
 
77
 
 
78
    /**
 
79
    Whether or not we're currently in the process of dispatching to routes.
 
80
 
 
81
    @property _dispatching
 
82
    @type Boolean
 
83
    @default undefined
 
84
    @protected
 
85
    **/
 
86
 
 
87
    /**
 
88
    Cached copy of the `html5` attribute for internal use.
 
89
 
 
90
    @property _html5
 
91
    @type Boolean
 
92
    @protected
 
93
    **/
 
94
 
 
95
    /**
 
96
    Whether or not the `ready` event has fired yet.
 
97
 
 
98
    @property _ready
 
99
    @type Boolean
 
100
    @default undefined
 
101
    @protected
 
102
    **/
 
103
 
 
104
    /**
 
105
    Regex used to match parameter placeholders in route paths.
 
106
 
 
107
    Subpattern captures:
 
108
 
 
109
      1. Parameter prefix character. Either a `:` for subpath parameters that
 
110
         should only match a single level of a path, or `*` for splat parameters
 
111
         that should match any number of path levels.
 
112
 
 
113
      2. Parameter name, if specified, otherwise it is a wildcard match.
 
114
 
 
115
    @property _regexPathParam
 
116
    @type RegExp
 
117
    @protected
 
118
    **/
 
119
    _regexPathParam: /([:*])([\w\-]+)?/g,
 
120
 
 
121
    /**
 
122
    Regex that matches and captures the query portion of a URL, minus the
 
123
    preceding `?` character, and discarding the hash portion of the URL if any.
 
124
 
 
125
    @property _regexUrlQuery
 
126
    @type RegExp
 
127
    @protected
 
128
    **/
 
129
    _regexUrlQuery: /\?([^#]*).*$/,
 
130
 
 
131
    /**
 
132
    Regex that matches everything before the path portion of a URL (the origin).
 
133
    This will be used to strip this part of the URL from a string when we
 
134
    only want the path.
 
135
 
 
136
    @property _regexUrlOrigin
 
137
    @type RegExp
 
138
    @protected
 
139
    **/
 
140
    _regexUrlOrigin: /^(?:[^\/#?:]+:\/\/|\/\/)[^\/]*/,
 
141
 
 
142
    // -- Lifecycle Methods ----------------------------------------------------
 
143
    initializer: function (config) {
 
144
        var self = this;
 
145
 
 
146
        self._html5  = self.get('html5');
 
147
        self._routes = [];
 
148
        self._url    = self._getURL();
 
149
 
 
150
        // Necessary because setters don't run on init.
 
151
        self._setRoutes(config && config.routes ? config.routes :
 
152
                self.get('routes'));
 
153
 
 
154
        // Set up a history instance or hashchange listener.
 
155
        if (self._html5) {
 
156
            self._history = new Y.HistoryHTML5({force: true});
 
157
            Y.after('history:change', self._afterHistoryChange, self);
 
158
        } else {
 
159
            Y.on('hashchange', self._afterHistoryChange, win, self);
 
160
        }
 
161
 
 
162
        // Fire a `ready` event once we're ready to route. We wait first for all
 
163
        // subclass initializers to finish, then for window.onload, and then an
 
164
        // additional 20ms to allow the browser to fire a useless initial
 
165
        // `popstate` event if it wants to (and Chrome always wants to).
 
166
        self.publish(EVT_READY, {
 
167
            defaultFn  : self._defReadyFn,
 
168
            fireOnce   : true,
 
169
            preventable: false
 
170
        });
 
171
 
 
172
        self.once('initializedChange', function () {
 
173
            Y.once('load', function () {
 
174
                setTimeout(function () {
 
175
                    self.fire(EVT_READY, {dispatched: !!self._dispatched});
 
176
                }, 20);
 
177
            });
 
178
        });
 
179
    },
 
180
 
 
181
    destructor: function () {
 
182
        if (this._html5) {
 
183
            Y.detach('history:change', this._afterHistoryChange, this);
 
184
        } else {
 
185
            Y.detach('hashchange', this._afterHistoryChange, win);
 
186
        }
 
187
    },
 
188
 
 
189
    // -- Public Methods -------------------------------------------------------
 
190
 
 
191
    /**
 
192
    Dispatches to the first route handler that matches the current URL, if any.
 
193
 
 
194
    If `dispatch()` is called before the `ready` event has fired, it will
 
195
    automatically wait for the `ready` event before dispatching. Otherwise it
 
196
    will dispatch immediately.
 
197
 
 
198
    @method dispatch
 
199
    @chainable
 
200
    **/
 
201
    dispatch: function () {
 
202
        this.once(EVT_READY, function () {
 
203
            this._ready = true;
 
204
 
 
205
            if (this._html5 && this.upgrade()) {
 
206
                return;
 
207
            } else {
 
208
                this._dispatch(this._getPath(), this._getURL());
 
209
            }
 
210
        });
 
211
 
 
212
        return this;
 
213
    },
 
214
 
 
215
    /**
 
216
    Gets the current route path, relative to the `root` (if any).
 
217
 
 
218
    @method getPath
 
219
    @return {String} Current route path.
 
220
    **/
 
221
    getPath: function () {
 
222
        return this._getPath();
 
223
    },
 
224
 
 
225
    /**
 
226
    Returns `true` if this router has at least one route that matches the
 
227
    specified URL, `false` otherwise.
 
228
 
 
229
    This method enforces the same-origin security constraint on the specified
 
230
    `url`; any URL which is not from the same origin as the current URL will
 
231
    always return `false`.
 
232
 
 
233
    @method hasRoute
 
234
    @param {String} url URL to match.
 
235
    @return {Boolean} `true` if there's at least one matching route, `false`
 
236
      otherwise.
 
237
    **/
 
238
    hasRoute: function (url) {
 
239
        if (!this._hasSameOrigin(url)) {
 
240
            return false;
 
241
        }
 
242
 
 
243
        url = this.removeQuery(this.removeRoot(url));
 
244
 
 
245
        return !!this.match(url).length;
 
246
    },
 
247
 
 
248
    /**
 
249
    Returns an array of route objects that match the specified URL path.
 
250
 
 
251
    This method is called internally to determine which routes match the current
 
252
    path whenever the URL changes. You may override it if you want to customize
 
253
    the route matching logic, although this usually shouldn't be necessary.
 
254
 
 
255
    Each returned route object has the following properties:
 
256
 
 
257
      * `callback`: A function or a string representing the name of a function
 
258
        this router that should be executed when the route is triggered.
 
259
      * `keys`: An array of strings representing the named parameters defined in
 
260
        the route's path specification, if any.
 
261
      * `path`: The route's path specification, which may be either a string or
 
262
        a regex.
 
263
      * `regex`: A regular expression version of the route's path specification.
 
264
        This regex is used to determine whether the route matches a given path.
 
265
 
 
266
    @example
 
267
        router.route('/foo', function () {});
 
268
        router.match('/foo');
 
269
        // => [{callback: ..., keys: [], path: '/foo', regex: ...}]
 
270
 
 
271
    @method match
 
272
    @param {String} path URL path to match.
 
273
    @return {Object[]} Array of route objects that match the specified path.
 
274
    **/
 
275
    match: function (path) {
 
276
        return YArray.filter(this._routes, function (route) {
 
277
            return path.search(route.regex) > -1;
 
278
        });
 
279
    },
 
280
 
 
281
    /**
 
282
    Removes the `root` URL from the front of _url_ (if it's there) and returns
 
283
    the result. The returned path will always have a leading `/`.
 
284
 
 
285
    @method removeRoot
 
286
    @param {String} url URL.
 
287
    @return {String} Rootless path.
 
288
    **/
 
289
    removeRoot: function (url) {
 
290
        var root = this.get('root');
 
291
 
 
292
        // Strip out the non-path part of the URL, if any (e.g.
 
293
        // "http://foo.com"), so that we're left with just the path.
 
294
        url = url.replace(this._regexUrlOrigin, '');
 
295
 
 
296
        if (root && url.indexOf(root) === 0) {
 
297
            url = url.substring(root.length);
 
298
        }
 
299
 
 
300
        return url.charAt(0) === '/' ? url : '/' + url;
 
301
    },
 
302
 
 
303
    /**
 
304
    Removes a query string from the end of the _url_ (if one exists) and returns
 
305
    the result.
 
306
 
 
307
    @method removeQuery
 
308
    @param {String} url URL.
 
309
    @return {String} Queryless path.
 
310
    **/
 
311
    removeQuery: function (url) {
 
312
        return url.replace(/\?.*$/, '');
 
313
    },
 
314
 
 
315
    /**
 
316
    Replaces the current browser history entry with a new one, and dispatches to
 
317
    the first matching route handler, if any.
 
318
 
 
319
    Behind the scenes, this method uses HTML5 `pushState()` in browsers that
 
320
    support it (or the location hash in older browsers and IE) to change the
 
321
    URL.
 
322
 
 
323
    The specified URL must share the same origin (i.e., protocol, host, and
 
324
    port) as the current page, or an error will occur.
 
325
 
 
326
    @example
 
327
        // Starting URL: http://example.com/
 
328
 
 
329
        router.replace('/path/');
 
330
        // New URL: http://example.com/path/
 
331
 
 
332
        router.replace('/path?foo=bar');
 
333
        // New URL: http://example.com/path?foo=bar
 
334
 
 
335
        router.replace('/');
 
336
        // New URL: http://example.com/
 
337
 
 
338
    @method replace
 
339
    @param {String} [url] URL to set. This URL needs to be of the same origin as
 
340
      the current URL. This can be a URL relative to the router's `root`
 
341
      attribute. If no URL is specified, the page's current URL will be used.
 
342
    @chainable
 
343
    @see save()
 
344
    **/
 
345
    replace: function (url) {
 
346
        return this._queue(url, true);
 
347
    },
 
348
 
 
349
    /**
 
350
    Adds a route handler for the specified URL _path_.
 
351
 
 
352
    The _path_ parameter may be either a string or a regular expression. If it's
 
353
    a string, it may contain named parameters: `:param` will match any single
 
354
    part of a URL path (not including `/` characters), and `*param` will match
 
355
    any number of parts of a URL path (including `/` characters). These named
 
356
    parameters will be made available as keys on the `req.params` object that's
 
357
    passed to route handlers.
 
358
 
 
359
    If the _path_ parameter is a regex, all pattern matches will be made
 
360
    available as numbered keys on `req.params`, starting with `0` for the full
 
361
    match, then `1` for the first subpattern match, and so on.
 
362
 
 
363
    Here's a set of sample routes along with URL paths that they match:
 
364
 
 
365
      * Route: `/photos/:tag/:page`
 
366
        * URL: `/photos/kittens/1`, params: `{tag: 'kittens', page: '1'}`
 
367
        * URL: `/photos/puppies/2`, params: `{tag: 'puppies', page: '2'}`
 
368
 
 
369
      * Route: `/file/*path`
 
370
        * URL: `/file/foo/bar/baz.txt`, params: `{path: 'foo/bar/baz.txt'}`
 
371
        * URL: `/file/foo`, params: `{path: 'foo'}`
 
372
 
 
373
    If multiple route handlers match a given URL, they will be executed in the
 
374
    order they were added. The first route that was added will be the first to
 
375
    be executed.
 
376
 
 
377
    @example
 
378
        router.route('/photos/:tag/:page', function (req, res, next) {
 
379
          Y.log('Current tag: ' + req.params.tag);
 
380
          Y.log('Current page number: ' + req.params.page);
 
381
        });
 
382
 
 
383
    @method route
 
384
    @param {String|RegExp} path Path to match. May be a string or a regular
 
385
      expression.
 
386
    @param {Function|String} callback Callback function to call whenever this
 
387
        route is triggered. If specified as a string, the named function will be
 
388
        called on this router instance.
 
389
      @param {Object} callback.req Request object containing information about
 
390
          the request. It contains the following properties.
 
391
        @param {Array|Object} callback.req.params Captured parameters matched by
 
392
          the route path specification. If a string path was used and contained
 
393
          named parameters, then this will be a key/value hash mapping parameter
 
394
          names to their matched values. If a regex path was used, this will be
 
395
          an array of subpattern matches starting at index 0 for the full match,
 
396
          then 1 for the first subpattern match, and so on.
 
397
        @param {String} callback.req.path The current URL path.
 
398
        @param {Object} callback.req.query Query hash representing the URL query
 
399
          string, if any. Parameter names are keys, and are mapped to parameter
 
400
          values.
 
401
        @param {String} callback.req.url The full URL.
 
402
        @param {String} callback.req.src What initiated the dispatch. In an
 
403
          HTML5 browser, when the back/forward buttons are used, this property
 
404
          will have a value of "popstate".
 
405
      @param {Object} callback.res Response object containing methods and
 
406
          information that relate to responding to a request. It contains the
 
407
          following properties.
 
408
        @param {Object} callback.res.req Reference to the request object.
 
409
      @param {Function} callback.next Callback to pass control to the next
 
410
        matching route. If you don't call this function, then no further route
 
411
        handlers will be executed, even if there are more that match. If you do
 
412
        call this function, then the next matching route handler (if any) will
 
413
        be called, and will receive the same `req` object that was passed to
 
414
        this route (so you can use the request object to pass data along to
 
415
        subsequent routes).
 
416
    @chainable
 
417
    **/
 
418
    route: function (path, callback) {
 
419
        var keys = [];
 
420
 
 
421
        this._routes.push({
 
422
            callback: callback,
 
423
            keys    : keys,
 
424
            path    : path,
 
425
            regex   : this._getRegex(path, keys)
 
426
        });
 
427
 
 
428
        return this;
 
429
    },
 
430
 
 
431
    /**
 
432
    Saves a new browser history entry and dispatches to the first matching route
 
433
    handler, if any.
 
434
 
 
435
    Behind the scenes, this method uses HTML5 `pushState()` in browsers that
 
436
    support it (or the location hash in older browsers and IE) to change the
 
437
    URL and create a history entry.
 
438
 
 
439
    The specified URL must share the same origin (i.e., protocol, host, and
 
440
    port) as the current page, or an error will occur.
 
441
 
 
442
    @example
 
443
        // Starting URL: http://example.com/
 
444
 
 
445
        router.save('/path/');
 
446
        // New URL: http://example.com/path/
 
447
 
 
448
        router.save('/path?foo=bar');
 
449
        // New URL: http://example.com/path?foo=bar
 
450
 
 
451
        router.save('/');
 
452
        // New URL: http://example.com/
 
453
 
 
454
    @method save
 
455
    @param {String} [url] URL to set. This URL needs to be of the same origin as
 
456
      the current URL. This can be a URL relative to the router's `root`
 
457
      attribute. If no URL is specified, the page's current URL will be used.
 
458
    @chainable
 
459
    @see replace()
 
460
    **/
 
461
    save: function (url) {
 
462
        return this._queue(url);
 
463
    },
 
464
 
 
465
    /**
 
466
    Upgrades a hash-based URL to an HTML5 URL if necessary. In non-HTML5
 
467
    browsers, this method is a noop.
 
468
 
 
469
    @method upgrade
 
470
    @return {Boolean} `true` if the URL was upgraded, `false` otherwise.
 
471
    **/
 
472
    upgrade: function () {
 
473
        if (!this._html5) {
 
474
            return false;
 
475
        }
 
476
 
 
477
        // Get the full hash in all its glory!
 
478
        var hash = HistoryHash.getHash();
 
479
 
 
480
        if (hash && hash.charAt(0) === '/') {
 
481
            // This is an HTML5 browser and we have a hash-based path in the
 
482
            // URL, so we need to upgrade the URL to a non-hash URL. This
 
483
            // will trigger a `history:change` event, which will in turn
 
484
            // trigger a dispatch.
 
485
            this.once(EVT_READY, function () {
 
486
                this.replace(hash);
 
487
            });
 
488
 
 
489
            return true;
 
490
        }
 
491
 
 
492
        return false;
 
493
    },
 
494
 
 
495
    // -- Protected Methods ----------------------------------------------------
 
496
 
 
497
    /**
 
498
    Wrapper around `decodeURIComponent` that also converts `+` chars into
 
499
    spaces.
 
500
 
 
501
    @method _decode
 
502
    @param {String} string String to decode.
 
503
    @return {String} Decoded string.
 
504
    @protected
 
505
    **/
 
506
    _decode: function (string) {
 
507
        return decodeURIComponent(string.replace(/\+/g, ' '));
 
508
    },
 
509
 
 
510
    /**
 
511
    Shifts the topmost `_save()` call off the queue and executes it. Does
 
512
    nothing if the queue is empty.
 
513
 
 
514
    @method _dequeue
 
515
    @chainable
 
516
    @see _queue
 
517
    @protected
 
518
    **/
 
519
    _dequeue: function () {
 
520
        var self = this,
 
521
            fn;
 
522
 
 
523
        // If window.onload hasn't yet fired, wait until it has before
 
524
        // dequeueing. This will ensure that we don't call pushState() before an
 
525
        // initial popstate event has fired.
 
526
        if (!YUI.Env.windowLoaded) {
 
527
            Y.once('load', function () {
 
528
                self._dequeue();
 
529
            });
 
530
 
 
531
            return this;
 
532
        }
 
533
 
 
534
        fn = saveQueue.shift();
 
535
        return fn ? fn() : this;
 
536
    },
 
537
 
 
538
    /**
 
539
    Dispatches to the first route handler that matches the specified _path_.
 
540
 
 
541
    If called before the `ready` event has fired, the dispatch will be aborted.
 
542
    This ensures normalized behavior between Chrome (which fires a `popstate`
 
543
    event on every pageview) and other browsers (which do not).
 
544
 
 
545
    @method _dispatch
 
546
    @param {String} path URL path.
 
547
    @param {String} url Full URL.
 
548
    @param {String} src What initiated the dispatch.
 
549
    @chainable
 
550
    @protected
 
551
    **/
 
552
    _dispatch: function (path, url, src) {
 
553
        var self   = this,
 
554
            routes = self.match(path),
 
555
            req, res;
 
556
 
 
557
        self._dispatching = self._dispatched = true;
 
558
 
 
559
        if (!routes || !routes.length) {
 
560
            self._dispatching = false;
 
561
            return self;
 
562
        }
 
563
 
 
564
        req = self._getRequest(path, url, src);
 
565
        res = self._getResponse(req);
 
566
 
 
567
        req.next = function (err) {
 
568
            var callback, matches, route;
 
569
 
 
570
            if (err) {
 
571
                Y.error(err);
 
572
            } else if ((route = routes.shift())) {
 
573
                matches  = route.regex.exec(path);
 
574
                callback = typeof route.callback === 'string' ?
 
575
                        self[route.callback] : route.callback;
 
576
 
 
577
                // Use named keys for parameter names if the route path contains
 
578
                // named keys. Otherwise, use numerical match indices.
 
579
                if (matches.length === route.keys.length + 1) {
 
580
                    req.params = YArray.hash(route.keys, matches.slice(1));
 
581
                } else {
 
582
                    req.params = matches.concat();
 
583
                }
 
584
 
 
585
                callback.call(self, req, res, req.next);
 
586
            }
 
587
        };
 
588
 
 
589
        req.next();
 
590
 
 
591
        self._dispatching = false;
 
592
        return self._dequeue();
 
593
    },
 
594
 
 
595
    /**
 
596
    Gets the current path from the location hash, or an empty string if the
 
597
    hash is empty.
 
598
 
 
599
    @method _getHashPath
 
600
    @return {String} Current hash path, or an empty string if the hash is empty.
 
601
    @protected
 
602
    **/
 
603
    _getHashPath: function () {
 
604
        return HistoryHash.getHash().replace(this._regexUrlQuery, '');
 
605
    },
 
606
 
 
607
    /**
 
608
    Gets the location origin (i.e., protocol, host, and port) as a URL.
 
609
 
 
610
    @example
 
611
        http://example.com
 
612
 
 
613
    @method _getOrigin
 
614
    @return {String} Location origin (i.e., protocol, host, and port).
 
615
    @protected
 
616
    **/
 
617
    _getOrigin: function () {
 
618
        var location = Y.getLocation();
 
619
        return location.origin || (location.protocol + '//' + location.host);
 
620
    },
 
621
 
 
622
    /**
 
623
    Gets the current route path, relative to the `root` (if any).
 
624
 
 
625
    @method _getPath
 
626
    @return {String} Current route path.
 
627
    @protected
 
628
    **/
 
629
    _getPath: function () {
 
630
        var path = (!this._html5 && this._getHashPath()) ||
 
631
                Y.getLocation().pathname;
 
632
 
 
633
        return this.removeQuery(this.removeRoot(path));
 
634
    },
 
635
 
 
636
    /**
 
637
    Gets the current route query string.
 
638
 
 
639
    @method _getQuery
 
640
    @return {String} Current route query string.
 
641
    @protected
 
642
    **/
 
643
    _getQuery: function () {
 
644
        var location = Y.getLocation(),
 
645
            hash, matches;
 
646
 
 
647
        if (this._html5) {
 
648
            return location.search.substring(1);
 
649
        }
 
650
 
 
651
        hash    = HistoryHash.getHash();
 
652
        matches = hash.match(this._regexUrlQuery);
 
653
 
 
654
        return hash && matches ? matches[1] : location.search.substring(1);
 
655
    },
 
656
 
 
657
    /**
 
658
    Creates a regular expression from the given route specification. If _path_
 
659
    is already a regex, it will be returned unmodified.
 
660
 
 
661
    @method _getRegex
 
662
    @param {String|RegExp} path Route path specification.
 
663
    @param {Array} keys Array reference to which route parameter names will be
 
664
      added.
 
665
    @return {RegExp} Route regex.
 
666
    @protected
 
667
    **/
 
668
    _getRegex: function (path, keys) {
 
669
        if (path instanceof RegExp) {
 
670
            return path;
 
671
        }
 
672
 
 
673
        // Special case for catchall paths.
 
674
        if (path === '*') {
 
675
            return (/.*/);
 
676
        }
 
677
 
 
678
        path = path.replace(this._regexPathParam, function (match, operator, key) {
 
679
            // Only `*` operators are supported for key-less matches to allowing
 
680
            // in-path wildcards like: '/foo/*'.
 
681
            if (!key) {
 
682
                return operator === '*' ? '.*' : match;
 
683
            }
 
684
 
 
685
            keys.push(key);
 
686
            return operator === '*' ? '(.*?)' : '([^/#?]*)';
 
687
        });
 
688
 
 
689
        return new RegExp('^' + path + '$');
 
690
    },
 
691
 
 
692
    /**
 
693
    Gets a request object that can be passed to a route handler.
 
694
 
 
695
    @method _getRequest
 
696
    @param {String} path Current path being dispatched.
 
697
    @param {String} url Current full URL being dispatched.
 
698
    @param {String} src What initiated the dispatch.
 
699
    @return {Object} Request object.
 
700
    @protected
 
701
    **/
 
702
    _getRequest: function (path, url, src) {
 
703
        return {
 
704
            path : path,
 
705
            query: this._parseQuery(this._getQuery()),
 
706
            url  : url,
 
707
            src  : src
 
708
        };
 
709
    },
 
710
 
 
711
    /**
 
712
    Gets a response object that can be passed to a route handler.
 
713
 
 
714
    @method _getResponse
 
715
    @param {Object} req Request object.
 
716
    @return {Object} Response Object.
 
717
    @protected
 
718
    **/
 
719
    _getResponse: function (req) {
 
720
        // For backwards compatibility, the response object is a function that
 
721
        // calls `next()` on the request object and returns the result.
 
722
        var res = function () {
 
723
            return req.next.apply(this, arguments);
 
724
        };
 
725
 
 
726
        res.req = req;
 
727
        return res;
 
728
    },
 
729
 
 
730
    /**
 
731
    Getter for the `routes` attribute.
 
732
 
 
733
    @method _getRoutes
 
734
    @return {Object[]} Array of route objects.
 
735
    @protected
 
736
    **/
 
737
    _getRoutes: function () {
 
738
        return this._routes.concat();
 
739
    },
 
740
 
 
741
    /**
 
742
    Gets the current full URL.
 
743
 
 
744
    @method _getURL
 
745
    @return {String} URL.
 
746
    @protected
 
747
    **/
 
748
    _getURL: function () {
 
749
        return Y.getLocation().toString();
 
750
    },
 
751
 
 
752
    /**
 
753
    Returns `true` when the specified `url` is from the same origin as the
 
754
    current URL; i.e., the protocol, host, and port of the URLs are the same.
 
755
 
 
756
    All host or path relative URLs are of the same origin. A scheme-relative URL
 
757
    is first prefixed with the current scheme before being evaluated.
 
758
 
 
759
    @method _hasSameOrigin
 
760
    @param {String} url URL to compare origin with the current URL.
 
761
    @return {Boolean} Whether the URL has the same origin of the current URL.
 
762
    @protected
 
763
    **/
 
764
    _hasSameOrigin: function (url) {
 
765
        var origin = ((url && url.match(this._regexUrlOrigin)) || [])[0];
 
766
 
 
767
        // Prepend current scheme to scheme-relative URLs.
 
768
        if (origin && origin.indexOf('//') === 0) {
 
769
            origin = Y.getLocation().protocol + origin;
 
770
        }
 
771
 
 
772
        return !origin || origin === this._getOrigin();
 
773
    },
 
774
 
 
775
    /**
 
776
    Joins the `root` URL to the specified _url_, normalizing leading/trailing
 
777
    `/` characters.
 
778
 
 
779
    @example
 
780
        router.set('root', '/foo');
 
781
        router._joinURL('bar');  // => '/foo/bar'
 
782
        router._joinURL('/bar'); // => '/foo/bar'
 
783
 
 
784
        router.set('root', '/foo/');
 
785
        router._joinURL('bar');  // => '/foo/bar'
 
786
        router._joinURL('/bar'); // => '/foo/bar'
 
787
 
 
788
    @method _joinURL
 
789
    @param {String} url URL to append to the `root` URL.
 
790
    @return {String} Joined URL.
 
791
    @protected
 
792
    **/
 
793
    _joinURL: function (url) {
 
794
        var root = this.get('root');
 
795
 
 
796
        url = this.removeRoot(url);
 
797
 
 
798
        if (url.charAt(0) === '/') {
 
799
            url = url.substring(1);
 
800
        }
 
801
 
 
802
        return root && root.charAt(root.length - 1) === '/' ?
 
803
                root + url :
 
804
                root + '/' + url;
 
805
    },
 
806
 
 
807
    /**
 
808
    Parses a URL query string into a key/value hash. If `Y.QueryString.parse` is
 
809
    available, this method will be an alias to that.
 
810
 
 
811
    @method _parseQuery
 
812
    @param {String} query Query string to parse.
 
813
    @return {Object} Hash of key/value pairs for query parameters.
 
814
    @protected
 
815
    **/
 
816
    _parseQuery: QS && QS.parse ? QS.parse : function (query) {
 
817
        var decode = this._decode,
 
818
            params = query.split('&'),
 
819
            i      = 0,
 
820
            len    = params.length,
 
821
            result = {},
 
822
            param;
 
823
 
 
824
        for (; i < len; ++i) {
 
825
            param = params[i].split('=');
 
826
 
 
827
            if (param[0]) {
 
828
                result[decode(param[0])] = decode(param[1] || '');
 
829
            }
 
830
        }
 
831
 
 
832
        return result;
 
833
    },
 
834
 
 
835
    /**
 
836
    Queues up a `_save()` call to run after all previously-queued calls have
 
837
    finished.
 
838
 
 
839
    This is necessary because if we make multiple `_save()` calls before the
 
840
    first call gets dispatched, then both calls will dispatch to the last call's
 
841
    URL.
 
842
 
 
843
    All arguments passed to `_queue()` will be passed on to `_save()` when the
 
844
    queued function is executed.
 
845
 
 
846
    @method _queue
 
847
    @chainable
 
848
    @see _dequeue
 
849
    @protected
 
850
    **/
 
851
    _queue: function () {
 
852
        var args = arguments,
 
853
            self = this;
 
854
 
 
855
        saveQueue.push(function () {
 
856
            if (self._html5) {
 
857
                if (Y.UA.ios && Y.UA.ios < 5) {
 
858
                    // iOS <5 has buggy HTML5 history support, and needs to be
 
859
                    // synchronous.
 
860
                    self._save.apply(self, args);
 
861
                } else {
 
862
                    // Wrapped in a timeout to ensure that _save() calls are
 
863
                    // always processed asynchronously. This ensures consistency
 
864
                    // between HTML5- and hash-based history.
 
865
                    setTimeout(function () {
 
866
                        self._save.apply(self, args);
 
867
                    }, 1);
 
868
                }
 
869
            } else {
 
870
                self._dispatching = true; // otherwise we'll dequeue too quickly
 
871
                self._save.apply(self, args);
 
872
            }
 
873
 
 
874
            return self;
 
875
        });
 
876
 
 
877
        return !this._dispatching ? this._dequeue() : this;
 
878
    },
 
879
 
 
880
    /**
 
881
    Saves a history entry using either `pushState()` or the location hash.
 
882
 
 
883
    This method enforces the same-origin security constraint; attempting to save
 
884
    a `url` that is not from the same origin as the current URL will result in
 
885
    an error.
 
886
 
 
887
    @method _save
 
888
    @param {String} [url] URL for the history entry.
 
889
    @param {Boolean} [replace=false] If `true`, the current history entry will
 
890
      be replaced instead of a new one being added.
 
891
    @chainable
 
892
    @protected
 
893
    **/
 
894
    _save: function (url, replace) {
 
895
        var urlIsString = typeof url === 'string';
 
896
 
 
897
        // Perform same-origin check on the specified URL.
 
898
        if (urlIsString && !this._hasSameOrigin(url)) {
 
899
            Y.error('Security error: The new URL must be of the same origin as the current URL.');
 
900
            return this;
 
901
        }
 
902
 
 
903
        // Force _ready to true to ensure that the history change is handled
 
904
        // even if _save is called before the `ready` event fires.
 
905
        this._ready = true;
 
906
 
 
907
        if (this._html5) {
 
908
            this._history[replace ? 'replace' : 'add'](null, {
 
909
                url: urlIsString ? this._joinURL(url) : url
 
910
            });
 
911
        } else {
 
912
            // Remove the root from the URL before it's set as the hash.
 
913
            urlIsString && (url = this.removeRoot(url));
 
914
 
 
915
            // The `hashchange` event only fires when the new hash is actually
 
916
            // different. This makes sure we'll always dequeue and dispatch,
 
917
            // mimicking the HTML5 behavior.
 
918
            if (url === HistoryHash.getHash()) {
 
919
                this._dispatch(this._getPath(), this._getURL());
 
920
            } else {
 
921
                HistoryHash[replace ? 'replaceHash' : 'setHash'](url);
 
922
            }
 
923
        }
 
924
 
 
925
        return this;
 
926
    },
 
927
 
 
928
    /**
 
929
    Setter for the `routes` attribute.
 
930
 
 
931
    @method _setRoutes
 
932
    @param {Object[]} routes Array of route objects.
 
933
    @return {Object[]} Array of route objects.
 
934
    @protected
 
935
    **/
 
936
    _setRoutes: function (routes) {
 
937
        this._routes = [];
 
938
 
 
939
        YArray.each(routes, function (route) {
 
940
            this.route(route.path, route.callback);
 
941
        }, this);
 
942
 
 
943
        return this._routes.concat();
 
944
    },
 
945
 
 
946
    // -- Protected Event Handlers ---------------------------------------------
 
947
 
 
948
    /**
 
949
    Handles `history:change` and `hashchange` events.
 
950
 
 
951
    @method _afterHistoryChange
 
952
    @param {EventFacade} e
 
953
    @protected
 
954
    **/
 
955
    _afterHistoryChange: function (e) {
 
956
        var self       = this,
 
957
            src        = e.src,
 
958
            prevURL    = self._url,
 
959
            currentURL = self._getURL();
 
960
 
 
961
        self._url = currentURL;
 
962
 
 
963
        // Handles the awkwardness that is the `popstate` event. HTML5 browsers
 
964
        // fire `popstate` right before they fire `hashchange`, and Chrome fires
 
965
        // `popstate` on page load. If this router is not ready or the previous
 
966
        // and current URLs only differ by their hash, then we want to ignore
 
967
        // this `popstate` event.
 
968
        if (src === 'popstate' &&
 
969
                (!self._ready || prevURL.replace(/#.*$/, '') === currentURL.replace(/#.*$/, ''))) {
 
970
 
 
971
            return;
 
972
        }
 
973
 
 
974
        self._dispatch(self._getPath(), currentURL, src);
 
975
    },
 
976
 
 
977
    // -- Default Event Handlers -----------------------------------------------
 
978
 
 
979
    /**
 
980
    Default handler for the `ready` event.
 
981
 
 
982
    @method _defReadyFn
 
983
    @param {EventFacade} e
 
984
    @protected
 
985
    **/
 
986
    _defReadyFn: function (e) {
 
987
        this._ready = true;
 
988
    }
 
989
}, {
 
990
    // -- Static Properties ----------------------------------------------------
 
991
    NAME: 'router',
 
992
 
 
993
    ATTRS: {
 
994
        /**
 
995
        Whether or not this browser is capable of using HTML5 history.
 
996
 
 
997
        Setting this to `false` will force the use of hash-based history even on
 
998
        HTML5 browsers, but please don't do this unless you understand the
 
999
        consequences.
 
1000
 
 
1001
        @attribute html5
 
1002
        @type Boolean
 
1003
        @initOnly
 
1004
        **/
 
1005
        html5: {
 
1006
            // Android versions lower than 3.0 are buggy and don't update
 
1007
            // window.location after a pushState() call, so we fall back to
 
1008
            // hash-based history for them.
 
1009
            //
 
1010
            // See http://code.google.com/p/android/issues/detail?id=17471
 
1011
            valueFn: function () { return Y.Router.html5; },
 
1012
            writeOnce: 'initOnly'
 
1013
        },
 
1014
 
 
1015
        /**
 
1016
        Absolute root path from which all routes should be evaluated.
 
1017
 
 
1018
        For example, if your router is running on a page at
 
1019
        `http://example.com/myapp/` and you add a route with the path `/`, your
 
1020
        route will never execute, because the path will always be preceded by
 
1021
        `/myapp`. Setting `root` to `/myapp` would cause all routes to be
 
1022
        evaluated relative to that root URL, so the `/` route would then execute
 
1023
        when the user browses to `http://example.com/myapp/`.
 
1024
 
 
1025
        @attribute root
 
1026
        @type String
 
1027
        @default `''`
 
1028
        **/
 
1029
        root: {
 
1030
            value: ''
 
1031
        },
 
1032
 
 
1033
        /**
 
1034
        Array of route objects.
 
1035
 
 
1036
        Each item in the array must be an object with the following properties:
 
1037
 
 
1038
          * `path`: String or regex representing the path to match. See the docs
 
1039
            for the `route()` method for more details.
 
1040
 
 
1041
          * `callback`: Function or a string representing the name of a function
 
1042
            on this router instance that should be called when the route is
 
1043
            triggered. See the docs for the `route()` method for more details.
 
1044
 
 
1045
        This attribute is intended to be used to set routes at init time, or to
 
1046
        completely reset all routes after init. To add routes after init without
 
1047
        resetting all existing routes, use the `route()` method.
 
1048
 
 
1049
        @attribute routes
 
1050
        @type Object[]
 
1051
        @default `[]`
 
1052
        @see route
 
1053
        **/
 
1054
        routes: {
 
1055
            value : [],
 
1056
            getter: '_getRoutes',
 
1057
            setter: '_setRoutes'
 
1058
        }
 
1059
    },
 
1060
 
 
1061
    // Used as the default value for the `html5` attribute, and for testing.
 
1062
    html5: Y.HistoryBase.html5 && (!Y.UA.android || Y.UA.android >= 3)
 
1063
});
 
1064
 
 
1065
/**
 
1066
The `Controller` class was deprecated in YUI 3.5.0 and is now an alias for the
 
1067
`Router` class. Use that class instead. This alias will be removed in a future
 
1068
version of YUI.
 
1069
 
 
1070
@class Controller
 
1071
@constructor
 
1072
@extends Base
 
1073
@deprecated Use `Router` instead.
 
1074
@see Router
 
1075
**/
 
1076
Y.Controller = Y.Router;
 
1077
 
 
1078
 
 
1079
}, '3.5.1' ,{requires:['array-extras', 'base-build', 'history'], optional:['querystring-parse']});