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   394 use Mojo::Base -base;
  51         148  
  51         423  
3              
4 51     51   370 use Carp qw(croak);
  51         134  
  51         2747  
5 51     51   415 use Mojo::DynamicMethods -dispatch;
  51         189  
  51         440  
6 51     51   365 use Mojo::Util;
  51         134  
  51         2222  
7 51     51   24450 use Mojolicious::Routes::Pattern;
  51         242  
  51         448  
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   52 my $self = shift;
        27      
25 27         55 my $dynamic = $dyn_methods->{$self->root}{$method};
26 27 50       115 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         26 };
30             }
31              
32             sub add_child {
33 993     993 1 1967 my ($self, $route) = @_;
34 993         1581 push @{$self->children}, $route->remove->parent($self);
  993         2381  
35 993         2257 $route->pattern->types($self->root->types);
36 993         2374 return $self;
37             }
38              
39 550 100   550 1 3350 sub any { shift->_generate_route(ref $_[0] eq 'ARRAY' ? shift : [], @_) }
40              
41 2     2 1 5 sub delete { shift->_generate_route(DELETE => @_) }
42              
43 14     14 1 886 sub find { shift->_index->{shift()} }
44              
45 384     384 1 1188 sub get { shift->_generate_route(GET => @_) }
46              
47 1399     1399 1 3131 sub has_custom_name { !!shift->{custom} }
48              
49             sub has_websocket {
50 287     287 1 566 my $self = shift;
51 287 100       1855 return $self->{has_websocket} if exists $self->{has_websocket};
52 98         149 return $self->{has_websocket} = grep { $_->is_websocket } @{$self->_chain};
  243         486  
  98         223  
53             }
54              
55 17741 100   17741 1 32573 sub is_endpoint { $_[0]->inline ? undef : !@{$_[0]->children} }
  17065         32594  
56              
57 220     220 1 1427 sub is_reserved { !!$RESERVED{$_[1]} }
58              
59 2557     2557 1 9098 sub is_websocket { !!shift->{websocket} }
60              
61             sub methods {
62 3775     3775 1 6611 my $self = shift;
63 3775 100       11253 return $self->{methods} unless @_;
64 980 100       1518 my $methods = [map uc($_), @{ref $_[0] ? $_[0] : [@_]}];
  980         3733  
65 980 100       2650 $self->{methods} = $methods if @$methods;
66 980         1663 return $self;
67             }
68              
69             sub name {
70 1684     1684 1 2851 my $self = shift;
71 1684 100       6937 return $self->{name} unless @_;
72 66         225 @$self{qw(name custom)} = (shift, 1);
73 66         217 return $self;
74             }
75              
76 2     2 1 9 sub options { shift->_generate_route(OPTIONS => @_) }
77              
78             sub parse {
79 992     992 1 1616 my $self = shift;
80 992   100     2392 $self->{name} = $self->pattern->parse(@_)->unparsed // '';
81 992         5135 $self->{name} =~ s/\W+//g;
82 992         2955 return $self;
83             }
84              
85 3     3 1 17 sub patch { shift->_generate_route(PATCH => @_) }
86 23     23 1 101 sub post { shift->_generate_route(POST => @_) }
87 10     10 1 35 sub put { shift->_generate_route(PUT => @_) }
88              
89             sub remove {
90 995     995 1 1771 my $self = shift;
91 995 100       2218 return $self unless my $parent = $self->parent;
92 2         5 @{$parent->children} = grep { $_ ne $self } @{$parent->children};
  2         5  
  4         14  
  2         6  
93 2         5 return $self->parent(undef);
94             }
95              
96             sub render {
97 290     290 1 689 my ($self, $values) = @_;
98 290   100     500 my $path = join '', map { $_->pattern->render($values, !@{$_->children} && !$_->partial) } @{$self->_chain};
  667         1525  
  290         737  
99 290   100     1469 return $path || '/';
100             }
101              
102 1042     1042 1 2406 sub root { shift->_chain->[0] }
103              
104             sub requires {
105 3418     3418 1 5565 my $self = shift;
106              
107             # Routes with conditions can't be cached
108 3418 100       10743 return $self->{requires} unless @_;
109 998 100       2413 my $conditions = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
110 998 100       3136 return $self unless @$conditions;
111 21         53 $self->{requires} = $conditions;
112 21         55 $self->root->cache->max_keys(0);
113              
114 21         83 return $self;
115             }
116              
117             sub suggested_method {
118 48     48 1 85 my $self = shift;
119              
120 48         84 my %via;
121 48         82 for my $route (@{$self->_chain}) {
  48         134  
122 98 100 100     159 next unless my @via = @{$route->methods // []};
  98         219  
123 31 100       121 %via = map { $_ => 1 } keys %via ? grep { $via{$_} } @via : @via;
  46         187  
  2         7  
124             }
125              
126 48 100 100     254 return 'POST' if $via{POST} && !$via{GET};
127 40 100 100     257 return $via{GET} ? 'GET' : (sort keys %via)[0] || 'GET';
128             }
129              
130             sub to {
131 1507     1507 1 2309 my $self = shift;
132              
133 1507         3006 my $pattern = $self->pattern;
134 1507 100       3409 return $pattern->defaults unless @_;
135 1505         3751 my ($shortcut, %defaults) = Mojo::Util::_options(@_);
136              
137 1505 100       3337 if ($shortcut) {
138              
139             # Application
140 379 100 100     2933 if (ref $shortcut || $shortcut =~ /^[\w:]+$/) { $defaults{app} = $shortcut }
  5 50       16  
141              
142             # Controller and action
143             elsif ($shortcut =~ /^([\w\-:]+)?\#(\w+)?$/) {
144 374 100       1397 $defaults{controller} = $1 if defined $1;
145 374 100       1088 $defaults{action} = $2 if defined $2;
146             }
147             }
148              
149 1505         3076 @{$pattern->defaults}{keys %defaults} = values %defaults;
  1505         3140  
150              
151 1505         3862 return $self;
152             }
153              
154             sub to_string {
155 5   100 5 1 13 join '', map { $_->pattern->unparsed // '' } @{shift->_chain};
  12         31  
  5         14  
156             }
157              
158 17     17 1 53 sub under { shift->_generate_route(under => @_) }
159              
160             sub websocket {
161 35     35 1 97 my $route = shift->get(@_);
162 35         74 $route->{websocket} = 1;
163 35         101 return $route;
164             }
165              
166             sub _chain {
167 1483     1483   3321 my @chain = (my $parent = shift);
168 1483         3371 unshift @chain, $parent while $parent = $parent->parent;
169 1483         5130 return \@chain;
170             }
171              
172             sub _generate_route {
173 991     991   2461 my ($self, $methods, @args) = @_;
174              
175 991         1600 my (@conditions, @constraints, %defaults, $name, $pattern);
176 991         2635 while (defined(my $arg = shift @args)) {
177              
178             # First scalar is the pattern
179 1499 100 100     6714 if (!ref $arg && !$pattern) { $pattern = $arg }
  940 100 100     2504  
    100          
    100          
    100          
    50          
180              
181             # Scalar
182 16         66 elsif (!ref $arg && @args) { push @conditions, $arg, shift @args }
183              
184             # Last scalar is the route name
185 50         167 elsif (!ref $arg) { $name = $arg }
186              
187             # Callback
188 298         955 elsif (ref $arg eq 'CODE') { $defaults{cb} = $arg }
189              
190             # Constraints
191 76         235 elsif (ref $arg eq 'ARRAY') { push @constraints, @$arg }
192              
193             # Defaults
194 119         655 elsif (ref $arg eq 'HASH') { %defaults = (%defaults, %$arg) }
195             }
196              
197 991         2405 my $route = $self->_route($pattern, @constraints)->requires(\@conditions)->to(\%defaults);
198 991 100       3653 $methods eq 'under' ? $route->inline(1) : $route->methods($methods);
199              
200 991 100       4560 return defined $name ? $route->name($name) : $route;
201             }
202              
203             sub _index {
204 30     30   75 my $self = shift;
205              
206 30         68 my (%auto, %custom);
207 30         62 my @children = (@{$self->children});
  30         162  
208 30         152 while (my $child = shift @children) {
209 1293 100 66     2196 if ($child->has_custom_name) { $custom{$child->name} ||= $child }
  89         213  
210 1204   66     1917 else { $auto{$child->name} ||= $child }
211 1293         2127 push @children, @{$child->children};
  1293         2358  
212             }
213              
214 30         889 return {%auto, %custom};
215             }
216              
217             sub _route {
218 991     991   1490 my $self = shift;
219              
220 991         2930 my $route = $self->add_child(__PACKAGE__->new->parse(@_))->children->[-1];
221 991         2362 my $new_pattern = $route->pattern;
222 0         0 croak qq{Route pattern "@{[$new_pattern->unparsed]}" contains a reserved stash value}
223 991 50       1554 if grep { $self->is_reserved($_) } @{$new_pattern->placeholders};
  103         524  
  991         2127  
224              
225 991         2323 my $old_pattern = $self->pattern;
226 991         2298 my $constraints = $old_pattern->constraints;
227 991 100 100     2334 $new_pattern->constraints->{format} //= $constraints->{format} if exists $constraints->{format};
228 991         2186 my $defaults = $old_pattern->defaults;
229 991 100 66     2160 $new_pattern->defaults->{format} //= $defaults->{format} if exists $defaults->{format};
230              
231 991         2577 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