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 52     52   789 use Mojo::Base -base;
  52         155  
  52         442  
3              
4 52     52   447 use Carp qw(croak);
  52         225  
  52         3009  
5 52     52   417 use Mojo::DynamicMethods -dispatch;
  52         164  
  52         499  
6 52     52   509 use Mojo::Util;
  52         191  
  52         2466  
7 52     52   26726 use Mojolicious::Routes::Pattern;
  52         216  
  52         489  
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   58 my $self = shift;
        27      
25 27         64 my $dynamic = $dyn_methods->{$self->root}{$method};
26 27 50       140 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         23 };
30             }
31              
32             sub add_child {
33 996     996 1 1988 my ($self, $route) = @_;
34 996         1529 push @{$self->children}, $route->remove->parent($self);
  996         2541  
35 996         2504 $route->pattern->types($self->root->types);
36 996         2692 return $self;
37             }
38              
39 550 100   550 1 3471 sub any { shift->_generate_route(ref $_[0] eq 'ARRAY' ? shift : [], @_) }
40              
41 2     2 1 7 sub delete { shift->_generate_route(DELETE => @_) }
42              
43 14     14 1 967 sub find { shift->_index->{shift()} }
44              
45 387     387 1 1276 sub get { shift->_generate_route(GET => @_) }
46              
47 1408     1408 1 3369 sub has_custom_name { !!shift->{custom} }
48              
49             sub has_websocket {
50 287     287 1 508 my $self = shift;
51 287 100       2244 return $self->{has_websocket} if exists $self->{has_websocket};
52 98         181 return $self->{has_websocket} = grep { $_->is_websocket } @{$self->_chain};
  243         545  
  98         238  
53             }
54              
55 17785 100   17785 1 34354 sub is_endpoint { $_[0]->inline ? undef : !@{$_[0]->children} }
  17109         33506  
56              
57 220     220 1 1148 sub is_reserved { !!$RESERVED{$_[1]} }
58              
59 2567     2567 1 9240 sub is_websocket { !!shift->{websocket} }
60              
61             sub methods {
62 3796     3796 1 6891 my $self = shift;
63 3796 100       11830 return $self->{methods} unless @_;
64 983 100       1537 my $methods = [map uc($_), @{ref $_[0] ? $_[0] : [@_]}];
  983         3781  
65 983 100       2683 $self->{methods} = $methods if @$methods;
66 983         1631 return $self;
67             }
68              
69             sub name {
70 1695     1695 1 3013 my $self = shift;
71 1695 100       7957 return $self->{name} unless @_;
72 66         272 @$self{qw(name custom)} = (shift, 1);
73 66         236 return $self;
74             }
75              
76 2     2 1 13 sub options { shift->_generate_route(OPTIONS => @_) }
77              
78             sub parse {
79 995     995 1 1760 my $self = shift;
80 995   100     2334 $self->{name} = $self->pattern->parse(@_)->unparsed // '';
81 995         5112 $self->{name} =~ s/\W+//g;
82 995         3483 return $self;
83             }
84              
85 3     3 1 21 sub patch { shift->_generate_route(PATCH => @_) }
86 23     23 1 116 sub post { shift->_generate_route(POST => @_) }
87 10     10 1 53 sub put { shift->_generate_route(PUT => @_) }
88              
89             sub remove {
90 998     998 1 1832 my $self = shift;
91 998 100       2287 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 620 my ($self, $values) = @_;
98 290   100     487 my $path = join '', map { $_->pattern->render($values, !@{$_->children} && !$_->partial) } @{$self->_chain};
  667         1523  
  290         700  
99 290   100     1553 return $path || '/';
100             }
101              
102 1045     1045 1 2495 sub root { shift->_chain->[0] }
103              
104             sub requires {
105 3431     3431 1 5543 my $self = shift;
106              
107             # Routes with conditions can't be cached
108 3431 100       10975 return $self->{requires} unless @_;
109 1001 100       2392 my $conditions = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
110 1001 100       3073 return $self unless @$conditions;
111 21         63 $self->{requires} = $conditions;
112 21         64 $self->root->cache->max_keys(0);
113              
114 21         88 return $self;
115             }
116              
117             sub suggested_method {
118 48     48 1 102 my $self = shift;
119              
120 48         80 my %via;
121 48         146 for my $route (@{$self->_chain}) {
  48         716  
122 98 100 100     156 next unless my @via = @{$route->methods // []};
  98         225  
123 31 100       162 %via = map { $_ => 1 } keys %via ? grep { $via{$_} } @via : @via;
  46         193  
  2         9  
124             }
125              
126 48 100 100     246 return 'POST' if $via{POST} && !$via{GET};
127 40 100 100     263 return $via{GET} ? 'GET' : (sort keys %via)[0] || 'GET';
128             }
129              
130             sub to {
131 1510     1510 1 2268 my $self = shift;
132              
133 1510         2987 my $pattern = $self->pattern;
134 1510 100       3239 return $pattern->defaults unless @_;
135 1508         3852 my ($shortcut, %defaults) = Mojo::Util::_options(@_);
136              
137 1508 100       3390 if ($shortcut) {
138              
139             # Application
140 379 100 100     2969 if (ref $shortcut || $shortcut =~ /^[\w:]+$/) { $defaults{app} = $shortcut }
  5 50       17  
141              
142             # Controller and action
143             elsif ($shortcut =~ /^([\w\-:]+)?\#(\w+)?$/) {
144 374 100       1477 $defaults{controller} = $1 if defined $1;
145 374 100       1113 $defaults{action} = $2 if defined $2;
146             }
147             }
148              
149 1508         3114 @{$pattern->defaults}{keys %defaults} = values %defaults;
  1508         3213  
150              
151 1508         4088 return $self;
152             }
153              
154             sub to_string {
155 5   100 5 1 16 join '', map { $_->pattern->unparsed // '' } @{shift->_chain};
  12         36  
  5         17  
156             }
157              
158 17     17 1 63 sub under { shift->_generate_route(under => @_) }
159              
160             sub websocket {
161 35     35 1 110 my $route = shift->get(@_);
162 35         81 $route->{websocket} = 1;
163 35         103 return $route;
164             }
165              
166             sub _chain {
167 1486     1486   3453 my @chain = (my $parent = shift);
168 1486         3485 unshift @chain, $parent while $parent = $parent->parent;
169 1486         5329 return \@chain;
170             }
171              
172             sub _generate_route {
173 994     994   2486 my ($self, $methods, @args) = @_;
174              
175 994         1672 my (@conditions, @constraints, %defaults, $name, $pattern);
176 994         2750 while (defined(my $arg = shift @args)) {
177              
178             # First scalar is the pattern
179 1503 100 100     7037 if (!ref $arg && !$pattern) { $pattern = $arg }
  943 100 100     2502  
    100          
    100          
    100          
    50          
180              
181             # Scalar
182 16         57 elsif (!ref $arg && @args) { push @conditions, $arg, shift @args }
183              
184             # Last scalar is the route name
185 50         193 elsif (!ref $arg) { $name = $arg }
186              
187             # Callback
188 298         1030 elsif (ref $arg eq 'CODE') { $defaults{cb} = $arg }
189              
190             # Constraints
191 76         239 elsif (ref $arg eq 'ARRAY') { push @constraints, @$arg }
192              
193             # Defaults
194 120         643 elsif (ref $arg eq 'HASH') { %defaults = (%defaults, %$arg) }
195             }
196              
197 994         2624 my $route = $self->_route($pattern, @constraints)->requires(\@conditions)->to(\%defaults);
198 994 100       3665 $methods eq 'under' ? $route->inline(1) : $route->methods($methods);
199              
200 994 100       5602 return defined $name ? $route->name($name) : $route;
201             }
202              
203             sub _index {
204 30     30   73 my $self = shift;
205              
206 30         77 my (%auto, %custom);
207 30         62 my @children = (@{$self->children});
  30         108  
208 30         138 while (my $child = shift @children) {
209 1294 100 66     2254 if ($child->has_custom_name) { $custom{$child->name} ||= $child }
  89         210  
210 1205   66     2025 else { $auto{$child->name} ||= $child }
211 1294         2098 push @children, @{$child->children};
  1294         2428  
212             }
213              
214 30         981 return {%auto, %custom};
215             }
216              
217             sub _route {
218 994     994   1474 my $self = shift;
219              
220 994         3104 my $route = $self->add_child(__PACKAGE__->new->parse(@_))->children->[-1];
221 994         2310 my $new_pattern = $route->pattern;
222 0         0 croak qq{Route pattern "@{[$new_pattern->unparsed]}" contains a reserved stash value}
223 994 50       1544 if grep { $self->is_reserved($_) } @{$new_pattern->placeholders};
  103         386  
  994         2169  
224              
225 994         2329 my $old_pattern = $self->pattern;
226 994         2271 my $constraints = $old_pattern->constraints;
227 994 100 100     2453 $new_pattern->constraints->{format} //= $constraints->{format} if exists $constraints->{format};
228 994         2200 my $defaults = $old_pattern->defaults;
229 994 100 66     2261 $new_pattern->defaults->{format} //= $defaults->{format} if exists $defaults->{format};
230              
231 994         2606 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