File Coverage

blib/lib/Mojolicious/Routes/Route.pm
Criterion Covered Total %
statement 146 149 97.9
branch 64 68 94.1
condition 34 37 91.8
subroutine 38 38 100.0
pod 26 27 96.3
total 308 319 96.5


line stmt bran cond sub pod time code
1             package Mojolicious::Routes::Route;
2 51     51   440 use Mojo::Base -base;
  51         177  
  51         408  
3              
4 51     51   426 use Carp qw(croak);
  51         172  
  51         2795  
5 51     51   424 use Mojo::DynamicMethods -dispatch;
  51         218  
  51         460  
6 51     51   394 use Mojo::Util;
  51         160  
  51         2567  
7 51     51   25316 use Mojolicious::Routes::Pattern;
  51         206  
  51         451  
8              
9             has [qw(inline partial)];
10             has 'children' => sub { [] };
11             has parent => undef, weak => 1;
12             has pattern => sub { Mojolicious::Routes::Pattern->new };
13              
14             # Reserved stash values
15             my %RESERVED = map { $_ => 1 } (
16             qw(action app cb controller data extends format handler inline json layout namespace path status template text),
17             qw(variant)
18             );
19              
20             sub BUILD_DYNAMIC {
21 3     3 0 12 my ($class, $method, $dyn_methods) = @_;
22              
23             return sub {
24 27     27   57 my $self = shift;
        27      
25 27         60 my $dynamic = $dyn_methods->{$self->root}{$method};
26 27 50       110 return $self->$dynamic(@_) if $dynamic;
27 0         0 my $package = ref($self);
28 0         0 croak qq{Can't locate object method "$method" via package "$package"};
29 3         28 };
30             }
31              
32             sub add_child {
33 993     993 1 2139 my ($self, $route) = @_;
34 993         1449 push @{$self->children}, $route->remove->parent($self);
  993         2468  
35 993         2371 $route->pattern->types($self->root->types);
36 993         2564 return $self;
37             }
38              
39 550 100   550 1 3617 sub any { shift->_generate_route(ref $_[0] eq 'ARRAY' ? shift : [], @_) }
40              
41 2     2 1 8 sub delete { shift->_generate_route(DELETE => @_) }
42              
43 14     14 1 798 sub find { shift->_index->{shift()} }
44              
45 384     384 1 1305 sub get { shift->_generate_route(GET => @_) }
46              
47 1399     1399 1 3243 sub has_custom_name { !!shift->{custom} }
48              
49             sub has_websocket {
50 287     287 1 551 my $self = shift;
51 287 100       1993 return $self->{has_websocket} if exists $self->{has_websocket};
52 98         173 return $self->{has_websocket} = grep { $_->is_websocket } @{$self->_chain};
  243         570  
  98         229  
53             }
54              
55 17741 100   17741 1 34271 sub is_endpoint { $_[0]->inline ? undef : !@{$_[0]->children} }
  17065         33062  
56              
57 220     220 1 1145 sub is_reserved { !!$RESERVED{$_[1]} }
58              
59 2557     2557 1 9648 sub is_websocket { !!shift->{websocket} }
60              
61             sub methods {
62 3775     3775 1 6735 my $self = shift;
63 3775 100       11678 return $self->{methods} unless @_;
64 980 100       1498 my $methods = [map uc($_), @{ref $_[0] ? $_[0] : [@_]}];
  980         3742  
65 980 100       2768 $self->{methods} = $methods if @$methods;
66 980         1675 return $self;
67             }
68              
69             sub name {
70 1684     1684 1 2936 my $self = shift;
71 1684 100       7285 return $self->{name} unless @_;
72 66         245 @$self{qw(name custom)} = (shift, 1);
73 66         232 return $self;
74             }
75              
76 2     2 1 16 sub options { shift->_generate_route(OPTIONS => @_) }
77              
78             sub parse {
79 992     992 1 1672 my $self = shift;
80 992   100     2470 $self->{name} = $self->pattern->parse(@_)->unparsed // '';
81 992         5016 $self->{name} =~ s/\W+//g;
82 992         3111 return $self;
83             }
84              
85 3     3 1 19 sub patch { shift->_generate_route(PATCH => @_) }
86 23     23 1 118 sub post { shift->_generate_route(POST => @_) }
87 10     10 1 28 sub put { shift->_generate_route(PUT => @_) }
88              
89             sub remove {
90 995     995 1 1751 my $self = shift;
91 995 100       2236 return $self unless my $parent = $self->parent;
92 2         4 @{$parent->children} = grep { $_ ne $self } @{$parent->children};
  2         6  
  4         15  
  2         5  
93 2         6 return $self->parent(undef);
94             }
95              
96             sub render {
97 290     290 1 665 my ($self, $values) = @_;
98 290   100     537 my $path = join '', map { $_->pattern->render($values, !@{$_->children} && !$_->partial) } @{$self->_chain};
  667         1614  
  290         788  
99 290   100     1552 return $path || '/';
100             }
101              
102 1042     1042 1 2521 sub root { shift->_chain->[0] }
103              
104             sub requires {
105 3418     3418 1 5572 my $self = shift;
106              
107             # Routes with conditions can't be cached
108 3418 100       10853 return $self->{requires} unless @_;
109 998 100       2486 my $conditions = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
110 998 100       3224 return $self unless @$conditions;
111 21         68 $self->{requires} = $conditions;
112 21         73 $self->root->cache->max_keys(0);
113              
114 21         87 return $self;
115             }
116              
117             sub suggested_method {
118 48     48 1 100 my $self = shift;
119              
120 48         81 my %via;
121 48         92 for my $route (@{$self->_chain}) {
  48         178  
122 98 100 100     172 next unless my @via = @{$route->methods // []};
  98         257  
123 31 100       141 %via = map { $_ => 1 } keys %via ? grep { $via{$_} } @via : @via;
  46         203  
  2         5  
124             }
125              
126 48 100 100     286 return 'POST' if $via{POST} && !$via{GET};
127 40 100 100     296 return $via{GET} ? 'GET' : (sort keys %via)[0] || 'GET';
128             }
129              
130             sub to {
131 1507     1507 1 2364 my $self = shift;
132              
133 1507         2936 my $pattern = $self->pattern;
134 1507 100       3156 return $pattern->defaults unless @_;
135 1505         3880 my ($shortcut, %defaults) = Mojo::Util::_options(@_);
136              
137 1505 100       3443 if ($shortcut) {
138              
139             # Application
140 379 100 100     3011 if (ref $shortcut || $shortcut =~ /^[\w:]+$/) { $defaults{app} = $shortcut }
  5 50       18  
141              
142             # Controller and action
143             elsif ($shortcut =~ /^([\w\-:]+)?\#(\w+)?$/) {
144 374 100       1470 $defaults{controller} = $1 if defined $1;
145 374 100       1120 $defaults{action} = $2 if defined $2;
146             }
147             }
148              
149 1505         3020 @{$pattern->defaults}{keys %defaults} = values %defaults;
  1505         3170  
150              
151 1505         3908 return $self;
152             }
153              
154             sub to_string {
155 5   100 5 1 13 join '', map { $_->pattern->unparsed // '' } @{shift->_chain};
  12         34  
  5         14  
156             }
157              
158 17     17 1 55 sub under { shift->_generate_route(under => @_) }
159              
160             sub websocket {
161 35     35 1 137 my $route = shift->get(@_);
162 35         93 $route->{websocket} = 1;
163 35         118 return $route;
164             }
165              
166             sub _chain {
167 1483     1483   3412 my @chain = (my $parent = shift);
168 1483         3571 unshift @chain, $parent while $parent = $parent->parent;
169 1483         5347 return \@chain;
170             }
171              
172             sub _generate_route {
173 991     991   2612 my ($self, $methods, @args) = @_;
174              
175 991         1635 my (@conditions, @constraints, %defaults, $name, $pattern);
176 991         2662 while (defined(my $arg = shift @args)) {
177              
178             # First scalar is the pattern
179 1499 100 100     7015 if (!ref $arg && !$pattern) { $pattern = $arg }
  940 100 100     2547  
    100          
    100          
    100          
    50          
180              
181             # Scalar
182 16         79 elsif (!ref $arg && @args) { push @conditions, $arg, shift @args }
183              
184             # Last scalar is the route name
185 50         232 elsif (!ref $arg) { $name = $arg }
186              
187             # Callback
188 298         978 elsif (ref $arg eq 'CODE') { $defaults{cb} = $arg }
189              
190             # Constraints
191 76         245 elsif (ref $arg eq 'ARRAY') { push @constraints, @$arg }
192              
193             # Defaults
194 119         686 elsif (ref $arg eq 'HASH') { %defaults = (%defaults, %$arg) }
195             }
196              
197 991         2563 my $route = $self->_route($pattern, @constraints)->requires(\@conditions)->to(\%defaults);
198 991 100       3659 $methods eq 'under' ? $route->inline(1) : $route->methods($methods);
199              
200 991 100       4717 return defined $name ? $route->name($name) : $route;
201             }
202              
203             sub _index {
204 30     30   94 my $self = shift;
205              
206 30         78 my (%auto, %custom);
207 30         72 my @children = (@{$self->children});
  30         103  
208 30         142 while (my $child = shift @children) {
209 1293 100 66     2143 if ($child->has_custom_name) { $custom{$child->name} ||= $child }
  89         196  
210 1204   66     1959 else { $auto{$child->name} ||= $child }
211 1293         2126 push @children, @{$child->children};
  1293         2338  
212             }
213              
214 30         907 return {%auto, %custom};
215             }
216              
217             sub _route {
218 991     991   1547 my $self = shift;
219              
220 991         3178 my $route = $self->add_child(__PACKAGE__->new->parse(@_))->children->[-1];
221 991         2286 my $new_pattern = $route->pattern;
222 0         0 croak qq{Route pattern "@{[$new_pattern->unparsed]}" contains a reserved stash value}
223 991 50       1603 if grep { $self->is_reserved($_) } @{$new_pattern->placeholders};
  103         535  
  991         2158  
224              
225 991         2380 my $old_pattern = $self->pattern;
226 991         2412 my $constraints = $old_pattern->constraints;
227 991 100 100     2280 $new_pattern->constraints->{format} //= $constraints->{format} if exists $constraints->{format};
228 991         2094 my $defaults = $old_pattern->defaults;
229 991 100 66     2220 $new_pattern->defaults->{format} //= $defaults->{format} if exists $defaults->{format};
230              
231 991         2543 return $route;
232             }
233              
234             1;
235              
236             =encoding utf8
237              
238             =head1 NAME
239              
240             Mojolicious::Routes::Route - Route
241              
242             =head1 SYNOPSIS
243              
244             use Mojolicious::Routes::Route;
245              
246             my $r = Mojolicious::Routes::Route->new;
247              
248             =head1 DESCRIPTION
249              
250             L is the route container used by L.
251              
252             =head1 ATTRIBUTES
253              
254             L implements the following attributes.
255              
256             =head2 children
257              
258             my $children = $r->children;
259             $r = $r->children([Mojolicious::Routes::Route->new]);
260              
261             The children of this route, used for nesting routes.
262              
263             =head2 inline
264              
265             my $bool = $r->inline;
266             $r = $r->inline($bool);
267              
268             Allow L semantics for this route.
269              
270             =head2 parent
271              
272             my $parent = $r->parent;
273             $r = $r->parent(Mojolicious::Routes::Route->new);
274              
275             The parent of this route, usually a L object. Note that this attribute is weakened.
276              
277             =head2 partial
278              
279             my $bool = $r->partial;
280             $r = $r->partial($bool);
281              
282             Route has no specific end, remaining characters will be captured in C.
283              
284             =head2 pattern
285              
286             my $pattern = $r->pattern;
287             $r = $r->pattern(Mojolicious::Routes::Pattern->new);
288              
289             Pattern for this route, defaults to a L object.
290              
291             =head1 METHODS
292              
293             L inherits all methods from L and implements the following new ones.
294              
295             =head2 add_child
296              
297             $r = $r->add_child(Mojolicious::Routes::Route->new);
298              
299             Add a child to this route, it will be automatically removed from its current parent if necessary.
300              
301             # Reattach route
302             $r->add_child($r->find('foo'));
303              
304             =head2 any
305              
306             my $route = $r->any;
307             my $route = $r->any('/:foo');
308             my $route = $r->any('/:foo' => sub ($c) {...});
309             my $route = $r->any('/:foo' => sub ($c) {...} => 'name');
310             my $route = $r->any('/:foo' => {foo => 'bar'} => sub ($c) {...});
311             my $route = $r->any('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
312             my $route = $r->any('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
313             my $route = $r->any(['GET', 'POST'] => '/:foo' => sub ($c) {...});
314             my $route = $r->any(['GET', 'POST'] => '/:foo' => [foo => qr/\w+/]);
315              
316             Generate L object matching any of the listed HTTP request methods or all.
317              
318             # Route with pattern and destination
319             $r->any('/user')->to('user#whatever');
320              
321             All arguments are optional, but some have to appear in a certain order, like the two supported array reference values,
322             which contain the HTTP methods to match and restrictive placeholders.
323              
324             # Route with HTTP methods, pattern, restrictive placeholders and destination
325             $r->any(['DELETE', 'PUT'] => '/:foo' => [foo => qr/\w+/])->to('foo#bar');
326              
327             There are also two supported string values, containing the route pattern and the route name, defaulting to the pattern
328             C and a name based on the pattern.
329              
330             # Route with pattern, name and destination
331             $r->any('/:foo' => 'foo_route')->to('foo#bar');
332              
333             An arbitrary number of key/value pairs in between the route pattern and name can be used to specify route conditions.
334              
335             # Route with pattern, condition and destination
336             $r->any('/' => (agent => qr/Firefox/))->to('foo#bar');
337              
338             A hash reference is used to specify optional placeholders and default values for the stash.
339              
340             # Route with pattern, optional placeholder and destination
341             $r->any('/:foo' => {foo => 'bar'})->to('foo#bar');
342              
343             And a code reference can be used to specify a C value to be merged into the default values for the stash.
344              
345             # Route with pattern and a closure as destination
346             $r->any('/:foo' => sub ($c) {
347             $c->render(text => 'Hello World!');
348             });
349              
350             See L and L for more information.
351              
352             =head2 delete
353              
354             my $route = $r->delete;
355             my $route = $r->delete('/:foo');
356             my $route = $r->delete('/:foo' => sub ($c) {...});
357             my $route = $r->delete('/:foo' => sub ($c) {...} => 'name');
358             my $route = $r->delete('/:foo' => {foo => 'bar'} => sub ($c) {...});
359             my $route = $r->delete('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
360             my $route = $r->delete('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
361              
362             Generate L object matching only C requests, takes the same arguments as L
363             (except for the HTTP methods to match, which are implied). See L and
364             L for more information.
365              
366             # Route with destination
367             $r->delete('/user')->to('user#remove');
368              
369             =head2 find
370              
371             my $route = $r->find('foo');
372              
373             Find child route by name, custom names have precedence over automatically generated ones.
374              
375             # Change default parameters of a named route
376             $r->find('show_user')->to(foo => 'bar');
377              
378             =head2 get
379              
380             my $route = $r->get;
381             my $route = $r->get('/:foo');
382             my $route = $r->get('/:foo' => sub ($c) {...});
383             my $route = $r->get('/:foo' => sub ($c) {...} => 'name');
384             my $route = $r->get('/:foo' => {foo => 'bar'} => sub ($c) {...});
385             my $route = $r->get('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
386             my $route = $r->get('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
387              
388             Generate L object matching only C requests, takes the same arguments as L
389             (except for the HTTP methods to match, which are implied). See L and
390             L for more information.
391              
392             # Route with destination
393             $r->get('/user')->to('user#show');
394              
395             =head2 has_custom_name
396              
397             my $bool = $r->has_custom_name;
398              
399             Check if this route has a custom name.
400              
401             =head2 has_websocket
402              
403             my $bool = $r->has_websocket;
404              
405             Check if this route has a WebSocket ancestor and cache the result for future checks.
406              
407             =head2 is_endpoint
408              
409             my $bool = $r->is_endpoint;
410              
411             Check if this route qualifies as an endpoint.
412              
413             =head2 is_reserved
414              
415             my $bool = $r->is_reserved('controller');
416              
417             Check if string is a reserved stash value.
418              
419             =head2 is_websocket
420              
421             my $bool = $r->is_websocket;
422              
423             Check if this route is a WebSocket.
424              
425             =head2 methods
426              
427             my $methods = $r->methods;
428             $r = $r->methods('GET');
429             $r = $r->methods('GET', 'POST');
430             $r = $r->methods(['GET', 'POST']);
431              
432             Restrict HTTP methods this route is allowed to handle, defaults to no restrictions.
433              
434             # Route with two methods and destination
435             $r->any('/foo')->methods('GET', 'POST')->to('foo#bar');
436              
437             =head2 name
438              
439             my $name = $r->name;
440             $r = $r->name('foo');
441              
442             The name of this route, defaults to an automatically generated name based on the route pattern. Note that the name
443             C is reserved for referring to the current route.
444              
445             # Route with destination and custom name
446             $r->get('/user')->to('user#show')->name('show_user');
447              
448             =head2 options
449              
450             my $route = $r->options;
451             my $route = $r->options('/:foo');
452             my $route = $r->options('/:foo' => sub ($c) {...});
453             my $route = $r->options('/:foo' => sub ($c) {...} => 'name');
454             my $route = $r->options('/:foo' => {foo => 'bar'} => sub ($c) {...});
455             my $route = $r->options('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
456             my $route = $r->options('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
457              
458             Generate L object matching only C requests, takes the same arguments as L
459             (except for the HTTP methods to match, which are implied). See L and
460             L for more information.
461              
462             # Route with destination
463             $r->options('/user')->to('user#overview');
464              
465             =head2 parse
466              
467             $r = $r->parse('/user/:id');
468             $r = $r->parse('/user/:id', id => qr/\d+/);
469             $r = $r->parse(format => ['json', 'yaml']);
470              
471             Parse pattern.
472              
473             =head2 patch
474              
475             my $route = $r->patch;
476             my $route = $r->patch('/:foo');
477             my $route = $r->patch('/:foo' => sub ($c) {...});
478             my $route = $r->patch('/:foo' => sub ($c) {...} => 'name');
479             my $route = $r->patch('/:foo' => {foo => 'bar'} => sub ($c) {...});
480             my $route = $r->patch('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
481             my $route = $r->patch('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
482              
483             Generate L object matching only C requests, takes the same arguments as L
484             (except for the HTTP methods to match, which are implied). See L and
485             L for more information.
486              
487             # Route with destination
488             $r->patch('/user')->to('user#update');
489              
490             =head2 post
491              
492             my $route = $r->post;
493             my $route = $r->post('/:foo');
494             my $route = $r->post('/:foo' => sub ($c) {...});
495             my $route = $r->post('/:foo' => sub ($c) {...} => 'name');
496             my $route = $r->post('/:foo' => {foo => 'bar'} => sub ($c) {...});
497             my $route = $r->post('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
498             my $route = $r->post('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
499              
500             Generate L object matching only C requests, takes the same arguments as L
501             (except for the HTTP methods to match, which are implied). See L and
502             L for more information.
503              
504             # Route with destination
505             $r->post('/user')->to('user#create');
506              
507             =head2 put
508              
509             my $route = $r->put;
510             my $route = $r->put('/:foo');
511             my $route = $r->put('/:foo' => sub ($c) {...});
512             my $route = $r->put('/:foo' => sub ($c) {...} => 'name');
513             my $route = $r->put('/:foo' => {foo => 'bar'} => sub ($c) {...});
514             my $route = $r->put('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
515             my $route = $r->put('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
516              
517             Generate L object matching only C requests, takes the same arguments as L
518             (except for the HTTP methods to match, which are implied). See L and
519             L for more information.
520              
521             # Route with destination
522             $r->put('/user')->to('user#replace');
523              
524             =head2 remove
525              
526             $r = $r->remove;
527              
528             Remove route from parent.
529              
530             # Remove route completely
531             $r->find('foo')->remove;
532              
533             # Reattach route to new parent
534             $r->any('/foo')->add_child($r->find('bar')->remove);
535              
536             =head2 render
537              
538             my $path = $r->render({foo => 'bar'});
539              
540             Render route with parameters into a path.
541              
542             =head2 root
543              
544             my $root = $r->root;
545              
546             The L object this route is a descendant of.
547              
548             =head2 requires
549              
550             my $requires = $r->requires;
551             $r = $r->requires(foo => 1);
552             $r = $r->requires(foo => 1, bar => {baz => 'yada'});
553             $r = $r->requires([foo => 1, bar => {baz => 'yada'}]);
554              
555             Activate conditions for this route. Note that this automatically disables the routing cache, since conditions are too
556             complex for caching.
557              
558             # Route with condition and destination
559             $r->get('/foo')->requires(host => qr/mojolicious\.org/)->to('foo#bar');
560              
561             =head2 suggested_method
562              
563             my $method = $r->suggested_method;
564              
565             Suggested HTTP method for reaching this route, C and C are preferred.
566              
567             =head2 to
568              
569             my $defaults = $r->to;
570             $r = $r->to(action => 'foo');
571             $r = $r->to({action => 'foo'});
572             $r = $r->to('controller#action');
573             $r = $r->to('controller#action', foo => 'bar');
574             $r = $r->to('controller#action', {foo => 'bar'});
575             $r = $r->to(Mojolicious->new);
576             $r = $r->to(Mojolicious->new, foo => 'bar');
577             $r = $r->to(Mojolicious->new, {foo => 'bar'});
578             $r = $r->to('MyApp');
579             $r = $r->to('MyApp', foo => 'bar');
580             $r = $r->to('MyApp', {foo => 'bar'});
581              
582             Set default parameters for this route.
583              
584             =head2 to_string
585              
586             my $str = $r->to_string;
587              
588             Stringify the whole route.
589              
590             =head2 under
591              
592             my $route = $r->under(sub ($c) {...});
593             my $route = $r->under('/:foo' => sub ($c) {...});
594             my $route = $r->under('/:foo' => {foo => 'bar'});
595             my $route = $r->under('/:foo' => [foo => qr/\w+/]);
596             my $route = $r->under('/:foo' => (agent => qr/Firefox/));
597             my $route = $r->under([format => ['json', 'yaml']]);
598              
599             Generate L object for a nested route with its own intermediate destination, takes the same
600             arguments as L (except for the HTTP methods to match, which are not available). See
601             L and L for more information.
602              
603             # Longer version
604             $r->any('/:foo' => sub ($c) {...})->inline(1);
605              
606             # Intermediate destination and prefix shared between two routes
607             my $auth = $r->under('/user')->to('user#auth');
608             $auth->get('/show')->to('#show');
609             $auth->post('/create')->to('#create');
610              
611             =head2 websocket
612              
613             my $route = $r->websocket;
614             my $route = $r->websocket('/:foo');
615             my $route = $r->websocket('/:foo' => sub ($c) {...});
616             my $route = $r->websocket('/:foo' => sub ($c) {...} => 'name');
617             my $route = $r->websocket('/:foo' => {foo => 'bar'} => sub ($c) {...});
618             my $route = $r->websocket('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
619             my $route = $r->websocket('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
620              
621             Generate L object matching only WebSocket handshakes, takes the same arguments as L
622             (except for the HTTP methods to match, which are implied). See L and
623             L for more information.
624              
625             # Route with destination
626             $r->websocket('/echo')->to('example#echo');
627              
628             =head1 SHORTCUTS
629              
630             In addition to the L and L above you can also call shortcuts provided by L on
631             L objects.
632              
633             # Add a "firefox" shortcut
634             $r->root->add_shortcut(firefox => sub ($r, $path) {
635             $r->get($path, agent => qr/Firefox/);
636             });
637              
638             # Use "firefox" shortcut to generate routes
639             $r->firefox('/welcome')->to('firefox#welcome');
640             $r->firefox('/bye')->to('firefox#bye');
641              
642             =head1 SEE ALSO
643              
644             L, L, L.
645              
646             =cut