File Coverage

blib/lib/WWW/Scripter.pm
Criterion Covered Total %
statement 852 921 92.5
branch 347 438 79.2
condition 128 198 64.6
subroutine 172 193 89.1
pod 54 55 98.1
total 1553 1805 86.0


line stmt bran cond sub pod time code
1 27     27   410805 use 5.006;
  27         118  
2              
3             package WWW::Scripter;
4              
5             our $VERSION = '0.032';
6              
7 27     27   98 use strict; use warnings; no warnings qw 'utf8 parenthesis bareword';
  27     27   23  
  27     27   432  
  27         71  
  27         30  
  27         1191  
  27         75  
  27         29  
  27         730  
8              
9 27     27   10609 use CSS'DOM'Interface;
  27         66060  
  27         1085  
10 27     27   11867 use Encode qw'encode decode';
  27         190059  
  27         1612  
11 27     27   124 use Exporter 5.57 'import';
  27         358  
  27         697  
12 27     27   13093 use HTML::DOM 0.045; # weaken_response
  27         2037499  
  27         1249  
13 27     27   176 use HTML::DOM::EventTarget 0.053; # DOMAttrModified with correct type and
  27         303  
  27         584  
14 27     27   12130 use HTML::DOM::Interface 0.019 ':all'; # cancellability
  27         78689  
  27         4098  
15 27     27   9353 use HTML::DOM::View 0.018;
  27         7741  
  27         636  
16 27     27   9804 use HTTP::Headers::Util 'split_header_words';
  27         17047  
  27         1332  
17 27     27   8713 use HTTP::Response;
  27         323462  
  27         658  
18 27     27   9182 use HTTP::Request;
  27         16733  
  27         788  
19 27     27   121 use Scalar::Util 1.09 qw 'blessed weaken reftype';
  27         497  
  27         1378  
20 27     27   105 use List'Util 'sum';
  27         35  
  27         1820  
21 27     27   13613 use LWP::UserAgent;
  27         252414  
  27         695  
22 27     27   11130 use Time::HiRes 'time';
  27         25390  
  27         91  
23             BEGIN {
24 27     27   4308 require constant;
25 27         16744 require WWW::Mechanize;
26 27 50       1523518 VERSION WWW::Mechanize $LWP::UserAgent::VERSION >= 5.815 ? 1.52 : 1.2;
27             # Version 1.52 is necessary for LWP 5.815 compatibility. Version 1.2 is
28             # needed otherwise for its handling of cookie jars during cloning.
29 27         2617 import constant Mech => 'WWW::Mechanize';
30             }
31              
32             BEGIN {
33 27 50   27   51 if(eval { require Hash::Util::FieldHash }) {
  27         245  
34 27         15398 import Hash::Util::FieldHash qw < fieldhash fieldhashes >;
35             } else {
36 0         0 require Tie::RefHash::Weak;
37 0         0 VERSION Tie::RefHash::Weak 0.08; # fieldhash
38 0         0 import Tie::RefHash::Weak qw < fieldhash fieldhashes >;
39             }
40             }
41              
42             our @ISA = (Mech, qw( HTML::DOM::View HTML::DOM::EventTarget ));
43              
44             eval <<'' unless exists &UNIVERSAL'DOES;
45             sub DOES {
46             goto &{$_[0]->can("SUPER::DOES")||$_[0]->can("isa")}
47             }
48              
49             our @EXPORT_OK = qw/abort/;
50             our %EXPORT_TAGS = (
51             all => \@EXPORT_OK,
52             );
53              
54             # Fields that we don’t want fiddled with when the page stack is
55             # manipulated:
56             fieldhashes \my( %scriptable, %script_handlers, %scrn,
57             %class_info, %navi );
58             # ~~~ Actually, most of these can be eliminated, since we can store them
59             # directly in the object, as we are not doing that cloning that Mech
60             # used to do between pages.
61              
62             # Fields keyed by document:
63             fieldhashes \my( %timeouts, %timers, %frames, %evtg, %status, %dstatus );
64              
65             fieldhash my %document; # keyed by response — we actually use
66             # HTML::DOM::View’s storage for the current doc,
67             # but this field hash is necessary when we return
68             # to a page.
69              
70             # These are used to create a link between a WWW::Mechanize::(Image|Link)
71             # object and the DOM equivalent.
72             fieldhash my %dom_obj;
73              
74             # ------------- Mech overrides (or does it?) ------------- #
75              
76             sub new {
77 77     77 1 345413 my $class = shift;
78 77         172 my %args = @_;
79             exists $args{max_docs}
80 77 100       227 and $args{stack_depth} = -1+delete$args{max_docs};
81 77         116 my $max_history = delete $args{max_history};
82              
83 77         479 my $self = $class->SUPER::new(%args);
84              
85 77         234314 $$self{Scripter_max_hist} = $max_history;
86 77         528 $script_handlers{$self} = {};
87 77         199 $scriptable{$self} = 1;
88              
89 77         326 $self->{page_stack} = WWW'Scripter'History->new( $self );
90              
91 77         152 weaken(my $self_fc = $self); # for closures
92             $class_info{$self} = [
93             \(%HTML::DOM'Interface, %CSS'DOM'Interface, our%Interface), {
94             'WWW::Scripter::Image' => "Image",
95             Image => {
96             _constructor => sub {
97 0     0   0 my $i = $self_fc->document->createElement('img');
98 0 0       0 @_ and $i->attr('width',shift);
99 0 0       0 @_ and $i->attr('height',shift);
100 0         0 $i
101             }
102             },
103             }
104 77         608 ];
105              
106 77 50       224 unless(exists $args{agent}) {
107 77         282 $self->agent("WWW::Scripter/$VERSION");
108             }
109              
110             # I would like to avoid doing this when it is not necessary, but
111             # the alternative would require overriding HTML::DOM::View’s
112             # document method, and that might slow things down more, since
113             # document is called more often than new Scripter objects
114             # are created.
115 77         2963 _initial_page($self);
116              
117 77         380 $self;
118             }
119              
120             sub _initial_page {
121 143     143   543 my $req = new HTTP::Request 'GET', 'about:blank';
122 143         109075 my $res = new HTTP::Response 200, OK => [
123             'content-length' => 0,
124             'content-type' => 'text/html',
125             ], '';
126 143         10858 $res->request($req);
127             shift->_update_page(
128 143         959 $req, $res
129             );
130             }
131              
132             sub clone {
133 59     59 1 258 my $clone = (my $self = shift)->SUPER::clone(@_);
134 59         16595 $$_{$clone}=$$_{$self} for \(
135             %scriptable,%script_handlers
136             );
137 59         81 $class_info{$clone} = [@{$class_info{$self}}];
  59         241  
138 59         103 $clone->{handlers} = $self->{handlers};
139 59         617 $clone->{page_stack} = WWW'Scripter'History->new($clone);
140 59         937 delete @$clone{};
141 59         199 $clone->_clone_plugins;
142 59         154 $clone;
143             }
144              
145 35   50 35 1 1294 sub title { (shift->document||return)->title(@_) }
146              
147             sub content {
148 7     7 1 519 my $self = shift;
149 7 100 66     22 if($self->is_html && $self->document) {
150 6         103 my %parms = @_;
151 6         17 my $cs = (my $doc = $self->document)->charset;;
152 6 100 66     90 if(exists $parms{format} && $parms{format} eq 'text') {
153 2         11 my $text = $doc->documentElement->as_text;
154 2 50       939 return defined $cs ? encode $cs, $text : $text;
155             }
156 4         16 my $content = $doc->innerHTML;
157 4 50       2288 $content = encode $cs, $content if defined $cs;
158 4         120 $self->{content} = $content; # banana
159             }
160 5         45 $self->SUPER::content(@_);
161             }
162              
163             #sub discontent { ... }
164              
165             # Some parts of this were taken straight from WWW::Mechanize.
166             sub follow_link {
167 27     27   128 no warnings 'redefine';
  27         34  
  27         11689  
168 16     16 1 945 my $self = shift;
169 16         52 my %parms = ( n=>1, @_ );
170              
171 16 50       52 if ( $parms{n} eq 'all' ) {
172 0         0 delete $parms{n};
173 0         0 $self->warn( q{follow_link(n=>"all") is not valid} );
174             }
175              
176 16         79 my $link = $self->find_link(%parms);
177 16 100 66     860 if($link and tag $link =~ '^a') {
178 15         130 my $follow;
179 15         21 my $dom_link = $dom_obj{$link};
180             $dom_link->trigger_event('click',
181             # We used to have simply DOMActivate_default => ...
182             # but that did absolutely nothing, since the
183             # *_default arguments apply solely to the current
184             # event (which is a click event). So we have
185             # to override HTML::DOM::Element’s click_default
186             # to trigger the DOMActivate event with the
187             # DOMActivate_default argument. And, no, some sort
188             # of localisation mechanism would not do instead,
189             # because event handlers could click other links
190             # (or even this one again), which events should
191             # remain unaffected by this *_default override.
192             # ~~~ Or should they???
193             click_default => sub {
194             $dom_link->trigger_event('DOMActivate',
195 13         289 DOMActivate_default => sub { ++$follow }
196             )
197 13     13   305 }
198 15         81 );
199 15 100       306 return unless $follow;
200 13   66     42 return ($self->find_target($dom_link->target)||$self)
201             ->get($link->url);
202             }
203             else {
204             $self->die(
205             'Link not found: ',
206             join ", ", map "$_ => '$parms{$_}'", sort keys %parms
207             )
208 1 50       15 if $self->{autocheck};
209             }
210             Scripter_plit:
211             }
212              
213              
214             sub request {
215 239     239 1 65548 for (my $foo) { # protect against tied $_
216 239         279 my $self = shift;
217 239 100       504 return unless defined(my $request = shift);
218              
219 238         662 $request = $self->_modify_request( $request );
220              
221 238         19592 my $meth = $request->method;
222 238         1411 my $orig_uri = $request->uri;
223 238         792 my $new_uri;
224 238 0       561 if ((my $path = $orig_uri->path) =~ s-^(/*)/\.\./-$1||'/'-e) {
  0 50       0  
225 0 0       0 0while $path =~ s\\$1||'/'\e;
  0         0  
226 0         0 ($new_uri = $orig_uri->clone)->path($path)
227             }
228 238         3002 my $skip_fetch;
229 238 100       542 if(defined($orig_uri->fragment)) {
230 9   33     103 ($new_uri ||= $orig_uri->clone)->fragment(undef);
231              
232             # Skip fetching the URL if it is the same (and there is a fragment).
233             # We don’t need to strip the fragment from $self->uri before compari-
234             # son as that always contains the actual URL sent in the request.
235 9 100 66     185 $meth eq "GET" and $new_uri->eq($self->uri) and ++$skip_fetch;
236             }
237 238 100       1785 if ($new_uri) {
238 9         56 $request->uri($new_uri);
239             }
240              
241 238         300 my $response;
242              
243 238 100       374 if($skip_fetch) {
244 7         17 $response = $self->response;
245             }
246             else {
247             Scripter_REQUEST: {
248 231         171 Scripter_ABORT: {
249 231         153 $response = $self->_make_request( $request, @_ );
  231         608  
250 230         1508326 last Scripter_REQUEST;
251             }
252 1         5 return 1
253             }
254             }
255              
256 237 50 33     690 if ( $meth eq 'GET' || $meth eq 'POST' ) {
257             $self->get_event_listeners('unload') and
258             $self->trigger_event('unload'),
259 237 100       502 $self->{page_stack}->_delete_res;
260              
261 237         3415 $self->{page_stack}->${\(
262 237 100       861 $self->{Scripter_replace} ? '_replace' : '_add'
263             )}($request, $response, $orig_uri);
264             }
265              
266 237         1005 return $self->_update_page($request, $response);
267             }
268             }
269              
270             for my $method (qw < get put post head >){
271 27     27   132 no strict 'refs';
  27         35  
  27         15353  
272             *$method = sub {
273 220     220   102463 for(my $foo) { # protect against tied $_
274 220         332 my ($self, $uri) = (shift, shift);
275 220 50       539 $uri = $uri->url if ref $uri eq 'WWW::Mechanize::Link';
276 220         468 my $abs = new_abs URI $uri, my $base = $self->base;
277             # URI screws up data fragments
278 220 50 66     37421 if ($abs =~ /^data:#/i && $abs ne $uri && $uri =~ /^#/) {
      66        
279 1         19 $abs = "$base$uri";
280             }
281             # For get and put, we have replicated here what the Mech methods do,
282             # so for speed’s sake go straight to LWP.
283 220         1109 return $self->${\"LWP::UserAgent::$method"}($abs, @_);
  220         1137  
284             }
285             };
286             }
287              
288              
289             # The only difference between this one and Mech is the args to
290             # decoded_content. I.e., this is the way Mech *used* to work.
291             sub _update_page {
292 423     423   7654 my ($self, $request, $res) = @_;
293              
294 423         530 $self->{req} = $request;
295 423         917 $self->{redirected_uri} = $request->uri->as_string;
296              
297 423         4183 $self->{res} = $res;
298              
299 423         1736 $self->{status} = $res->code;
300 423         2855 $self->{base} = $res->base;
301 423   100     105126 $self->{ct} = $res->content_type || '';
302              
303 423 100       8473 if ( $res->is_success ) {
304 414         2386 $self->{uri} = $self->{redirected_uri};
305 414         527 $self->{last_uri} = $self->{uri};
306             }
307              
308 423 100       863 if ( $res->is_error ) {
309 9 50       70 if ( $self->{autocheck} ) {
310 0         0 $self->die( 'Error ', $request->method, 'ing ', $request->uri, ': ', $res->message );
311             }
312             }
313              
314 423         2505 $self->_reset_page;
315              
316             # Try to decode the content. Undef will be returned if there's nothing to decompress.
317             # See docs in HTTP::Message for details. Do we need to expose the options there?
318 423         4288 my $content = $res->decoded_content(charset => "none");
319 423 50       25863 $content = $res->content if (not defined $content);
320              
321 423         387 $content .= &{\&{Mech."::_taintedness"}};
  423         312  
  423         1591  
322              
323 423 100 66     116848 if (
      100        
324             !defined $$self{Scripter_dumb} || $$self{Scripter_dumb}
325             and $self->is_html
326             ) {
327 391         3559 $res = $self->update_html($content);
328             }
329             else {
330 32         183 $self->{content} = $content;
331 32         92 $self->document(undef);
332             }
333              
334 423         2209 return $res;
335             } # _update_page
336              
337             sub _fetch_url {
338 16     16   21 my ($self) = @'_;
339             my $fetcher = $self->{Scripter_f}
340 16   66     48 ||= do {
341             (
342 7         29 my $clone = $self->clone->clear_history(1)
343             )->dom_enabled(0);
344 7         17 $clone->max_history(1);
345 7         17 $clone;
346             };
347 16         25 $fetcher->{last_uri} = $self->{uri};
348 16         81 require URI;
349 16         35 my $base = $self->base;
350 16 50       64 $_[1] = URI->new_abs( $_[1], $base )
351             if $base;
352 16         2539 $fetcher->get($_[1]);
353             }
354              
355             sub update_html {
356 391     391 1 482 my ($self,$src) = @_;
357              
358             # Restore an existing document (in case we are coming back from
359             # another page).
360 391         692 my $res = $self->{res};
361 391 100       1311 if(my $doc = $document{$res}) {
362 45         134 $self->document($doc);
363 45         305 $self->{form} = ($self->{forms} = $doc->forms)->[0];
364 45         6553 return $res;
365             }
366              
367 346         308 my $life_raft = $self;
368 346         652 weaken($self);
369              
370 346         860 $self->document($document{$res} = my $tree = new HTML::DOM
371             response => $res,
372             weaken_response => 1,
373             cookie_jar => $self->cookie_jar);
374              
375 346     0   42069 $tree->error_handler(sub{$self->warn($@)});
  0         0  
376              
377             $tree->default_event_handler_for( link => sub {
378 5     5   105 my $link = shift->target;
379 5   66     27 ($self->find_target($link->target)||$self)
380             ->get($link->href)
381 346         3756 });
382             $tree->default_event_handler_for( submit => sub {
383 2     2   37 my $form = shift->target;
384 2   33     9 ($self->find_target($form->target)||$self)
385             ->request($form->make_request);
386 346         3827 });
387              
388 346 100       2407 if(%{$script_handlers{$self}}) {
  346         990  
389 48         108 my $script_type = $res->header(
390             'Content-Script-Type');
391             defined $script_type or $tree->elem_handler(meta =>
392             sub {
393 1     1   569 my($tree, $elem) = @_;
394 27     27   130 no warnings 'uninitialized';
  27         38  
  27         28294  
395 1 50       16 return unless lc $elem->attr('http-equiv')
396             eq 'content-script-type';
397 0         0 $script_type = $elem->attr('content');
398 48 100       1797 });
399              
400             $tree->elem_handler(script => sub {
401 20 100   20   20778 return unless $scriptable{$self};
402 18         24 my($tree, $elem) = @_;
403              
404 18         49 my $lang = $elem->attr('type');
405 18 50       148 defined $lang
406             or $lang = $elem->attr('language');
407 18 50       106 defined $lang or $lang = $script_type;
408              
409 18         17 my $uri;
410 18         18 my($inline, $code, $line) = 0;
411 18 100       33 if($uri = $elem->attr('src')) {
412 8         56 my $res = _fetch_url($self, $uri);
413 8 50       23 $res->is_success or do {
414 0         0 my $url = $self->uri;
415 0         0 my $offset = $elem->content_offset;
416 0 0       0 if (!defined $offset) {
417 0         0 $url .= ' (generated HTML)';
418             }
419             else {
420 0         0 $url .= ' line '
421             . _line_no($src,$offset);
422             }
423 0         0 $self->warn("couldn't get script $uri: "
424             . $res->status_line . " at $url"
425             ),
426             return;
427             };
428              
429             # Find out the encoding:
430             my $cs = {
431             map @$_,
432             split_header_words $res->header(
433             'Content-Type'
434             )
435 8         56 }->{charset};
436              
437 8   50     409 $code = decode $cs||$elem->charset
438             ||$tree->charset||'latin1',
439             $res->decoded_content(
440             charset=>'none', raise_error=>1
441             );
442            
443            
444 8         3818 $line = 1;
445             }
446             else {
447 10   100     88 $code = ($elem->firstChild||return)->data;
448 9         179 ++$inline;
449 9         27 $uri = $self->uri;
450 9 100       132 if(defined(
451             my $offset = $elem->content_offset
452             )) {
453 8         40 $line = _line_no(
454             $src,$elem->content_offset
455             );
456             }
457 1         7 else { $uri .= " (generated HTML)" }
458             };
459 17 50       64 length $code or return; # optimisation
460            
461 17         39 my $h = $self->_handler_for_lang($lang);
462 17 100       71 $h && $h->eval($self, $code,
463             $uri, $line, $inline);
464 17 100       409 $@ and $self->warn($@);
465 48         1010 });
466              
467             $tree->elem_handler(noscript => sub {
468 0 0   0   0 return unless $scriptable{$self};
469 0         0 $_[1]->detach#->delete;
470             # ~~~ delete currently stops it from work-
471             # ing; I need to looook into this.
472 48         759 });
473              
474             $tree->event_attr_handler(sub {
475 17 100   17   6441 return unless $scriptable{$self};
476 15         29 my($elem, $event, $code, $offset) = @_;
477 15         34 my $lang = $elem->attr('language');
478 15 50       96 defined $lang or $lang = $script_type;
479              
480 15         34 my $uri = $self->uri;
481 15 50       217 my $line = defined $offset ? _line_no(
482             $src, $offset
483             ) : undef;
484              
485 15         32 local *@;
486 15 100       31 if(my $h = $self->_handler_for_lang($lang))
487             {
488 14         38 my $ret = $h->event2sub(
489             $self,$elem,$event,$code,$uri,$line
490             );
491 14 100       429 $@ and $self->warn($@);
492 14         46 return $ret;
493             }
494 48         740 });
495             }
496              
497             $tree->elem_handler(noscript => sub {
498 0 0 0 0   0 return if $scriptable{$self} && %{$script_handlers{$self}};
  0         0  
499 0         0 $_[1]->replace_with_content->delete;
500             # ~~~ why does this need delete?
501 346         1645 });
502              
503 346 100       5906 if($self->{Scripter_i}){
504             $tree->elem_handler(img => my $img_cb = sub {
505 11 100   11   2031 return unless defined (my $src = $_[1]->attr('src'));
506 8         59 my $res = _fetch_url($self, $src);
507             defined $self->{Scripter_ih} &&
508 8 100       30 $self->{Scripter_ih}($self,$_[1],$res);
509 8         31 });
510             $tree->elem_handler(input => sub {
511 3 100   3   1515 return unless $_[1]->type eq 'image';
512 2         40 goto &$img_cb;
513 8         135 });
514             $tree->default_event_handler(sub {
515 7 50   7   3320 return unless (my $event = shift)->type eq 'DOMAttrModified';
516 7 50       29 return unless (my $target = target $event)->tag=~/^i(mg|nput)\z/;
517 7 100 100     76 return if $1 eq 'nput' && $target->type ne 'image';
518 5         72 &$img_cb(undef, $target);
519 8         136 });
520             }
521              
522             $tree->defaultView(
523 346         835 $self
524             );
525 346         3357 $tree->event_parent($self);
526 346         3018 $tree->set_location_object($self->location);
527              
528             $tree->elem_handler(iframe => my $frame_handler = sub {
529 40     40   19691 my ($doc,$elem) = @_;
530 40         102 my $subwin = $self->clone->clear_history(1);
531 40 100       143 if(defined(my $name = attr $elem 'name')) {
532 16         151 name $subwin $name
533             }
534 40         293 $elem->contentWindow($subwin);
535 40         243 $subwin->_set_parent(my $parent = $doc->defaultView);
536 40 100       105 length(my $src = $elem->src) or return;
537 17         416 $subwin->get(new_abs URI $src, $parent->base);
538 346         2920 });
539 346         5022 $tree->elem_handler(frame => $frame_handler);
540              
541             # Find out the encoding:
542             my $cs = {
543             map @$_,
544             split_header_words $res->header('Content-Type')
545 346         4597 }->{charset};
546 346 100 33     19189 $cs or $res->can('content_charset')
      33        
547             and $cs = (
548             $LWP::UserAgent::VERSION <= 5.834 && local *_,
549             $res->content_charset
550             );
551 346   100     109208 $tree->charset($cs||'iso-8859-1');
552              
553             # banana
554 346         3416 $self->{form} = undef;
555 346         805 $self->{forms} = $tree->forms;
556              
557 346 100       9644 $tree->write(defined $cs ? decode $cs, $src : $src);
558 346         153462 $tree->close;
559              
560             # This used to trigger the load event on the body element (which
561             # conformed to HTML 5 at the time [10 June 2008 draft]), but which
562             # was not fully compatible with any existing browser. HTML 5
563             # changed to what Firefox and Safari did (some time before Septem-
564             # ber, 2009), which is what we now have here. (It still doesn’t
565             # quite make sense, as the document is not actually the target.)
566 346         68661 $self->trigger_event('load', target => $tree);
567              
568             # banana
569 346   66     11274 $self->{form} ||= $self->{forms}[0];
570              
571 346         45589 return $self->{res};
572             }
573              
574             # Not an override, but used by update_html
575             sub _handler_for_lang {
576 59     59   98 my ($self,$lang) = @_;
577 59 100       117 if(defined $lang) {
578 35         44 while(my($lang_re,$handler) = each
579 40         255 %{$script_handlers{$self}}) {
580 34 100       71 next if $lang_re eq 'default';
581             $lang =~ $lang_re and
582             # reset iterator:
583 29 50       312 keys %{$script_handlers{$self}},
  29         269  
584             return $handler;
585             }
586             }
587 30   66     180 return $script_handlers{$self}{default} || ();
588             }
589              
590             # Not an override, but used by update_html
591             sub _line_no {
592 23     23   52 my ($src,$offset) = @_;
593 23 50       48 defined $offset or Carp::cluck;
594 23         287 return 1 + (() =
595             substr($src,0,$offset)
596             =~ /\cm\cj?|[\cj\x{2028}\x{2029}]/g
597             );
598             }
599              
600             my %link_tags = (
601             a => 'href',
602             area => 'href',
603             frame => 'src',
604             iframe => 'src',
605             link => 'href',
606             meta => 'content',
607             );
608              
609             # ~~~ This ends up creating a new WSL object every time we come back to the
610             # same page. We need a way to make this more efficient. The same goes
611             # for images.
612             sub _extract_links {
613 18     18   564 my $self = shift;
614 18         21 my @links;
615 18 100       44 if (my $doc = $self->document) {
616             tie @links, WWW'Scripter'Links:: =>
617             HTML::DOM::NodeList::Magic->new(
618             sub { grep {
619 18     18   236 my $tag = tag $_;
  166         3358  
620 27     27   131 no warnings 'uninitialized';
  27         39  
  27         41193  
621             exists $link_tags{$tag}
622 166 100 66     740 and defined $_->attr($link_tags{$tag})
      100        
623             and $tag ne 'meta'
624             || lc $_->attr('http-equiv') eq 'refresh'
625 17         192 } $doc->descendants }, $doc
626             );
627             }
628             # banana
629 18         37 $self->{links} = \@links;
630 18         23 $self->{_extracted_links} = 1;
631              
632 18         30 return;
633             }
634              
635             sub _extract_images {
636 1     1   12 my $doc = (my $self= shift)->document;
637             my $list = HTML::DOM::NodeList::Magic->new(
638 2     2   22 sub { grep tag $_ =~ /^i(?:mg|nput)\z/,
639             $doc->descendants },
640 1         10 $doc
641             );
642 1         19 tie my @images, WWW'Scripter'Images:: => $list;
643              
644             # banana
645 1         2 $self->{images} = \@images;
646 1         3 $self->{_extracted_images} = 1;
647              
648 1         2 return;
649             }
650              
651             sub back {
652 30     30 1 3206 shift->{page_stack}->go(-1)
653             }
654              
655             sub submit {
656 7 100   7 1 487 if(defined wantarray) {
657             # We have to return the response object if a request was made, so we
658             # override the default event handler for this particular case.
659 4         5 my $go_for_it;
660             (my $form = $_[0]->current_form)->trigger_event(
661             'submit',
662 3     3   56 submit_default => sub { ++$go_for_it }
663 4         11 );
664 4 100 33     77 $go_for_it
665             ? ($_[0]->find_target($form->target)||$_[0])
666             ->request($form->make_request)
667             : ()
668             }
669             else {
670             shift->current_form->submit
671 3         13 }
672             }
673              
674             sub base {
675 299     299 1 359 my $self = shift;
676 299   100     730 my $base = ($self->document || return SUPER'base $self @_)->base;
677 287 100 100     88868 if($base eq 'about:blank' and (my $parent = $self->parent) != $self) {
678 29         75 return $parent->base;
679             }
680 258 50       1101 length $base ? $base : undef;
681             }
682              
683             sub click { # This duplicates a lot of code from WWW::Mechanize::click,
684             # HTML::DOM::Element::Form::click and HTML::DOM::Ele-
685             # ment::Input, but I don’t see a way around it.
686 3 100   3 1 431 if(defined wantarray) {
687             # We have to return the response object if a request was made, so we
688             # override the default event handler for this particular case.
689 2         3 my ($self, $button, $x, $y) = @_;
690              
691             # From HTML::DOM::Element::Form (ultimately from HTML::Form):
692             # try to find first submit button to activate
693 2         1 my $input;
694 2         5 my $form = $self->current_form;
695 2         11 for ($form->inputs) {
696 0 0       0 next unless $_->type =~ /^(?:submit|image)\z/;
697 0 0 0     0 next if $button && $_->name ne $button;
698 0 0       0 next if $_->disabled;
699 0         0 $input = $_;
700 0         0 last;
701             }
702 2 50 33     113 Carp::croak("No clickable input with name $button")
703             if $button && !$input;
704              
705             # From HTML::DOM::Element::Input:
706             # We can’t put this in multiple statements, as the ‘local’ would go out
707             # of scope too soon.
708 2         3 my $continue;
709             $input and
710             # ~~~ We are breaking encapsulation here.
711             local($$input{_HTML_DOM_clicked}) = [$x,$y],
712             $input->trigger_event(
713             'click',
714             click_default => sub {
715             $input->trigger_event(
716 0         0 'DOMActivate', DOMActivate_default => sub { ++$continue }
717             )
718 0     0   0 }
719 2 50 0     3 ),
720             $continue || return;
721              
722 2         2 my $go_for_it;
723             $form->trigger_event(
724             'submit',
725 1     1   19 submit_default => sub { ++$go_for_it }
726 2         8 );
727 2 100 33     42 $go_for_it
728             ? ($self->find_target($form->target)||$self)
729             ->request($form->make_request)
730             : ()
731             }
732             else {
733             # Unlike the submit method, we *can* delegate to the superclass here,
734             # as the form’s click method (which Mech->click calls) calls our
735             # default_event_handler_for submit, which chooses the right target.
736 1         7 shift->SUPER::click(@_);
737             }
738             }
739              
740              
741             # ------------- Window interface ------------- #
742              
743             # This does not follow the same format as %HTML::DOM::Interface; this cor-
744             # responds to the format of hashes *within* %H:D:I. The other format does
745             # not apply here, since we can’t bind the class like other classes. This
746             # needs to be bound to the global object (at least in JavaScript).
747             our %WindowInterface = (
748             %{$HTML::DOM::Interface{AbstractView}},
749             %{$HTML::DOM::Interface{EventTarget}},
750             alert => VOID|METHOD,
751             confirm => BOOL|METHOD,
752             prompt => STR|METHOD,
753             location => OBJ,
754             setTimeout => NUM|METHOD,
755             clearTimeout => NUM|METHOD,
756             setInterval => NUM|METHOD,
757             clearInterval => NUM|METHOD,
758             open => OBJ|METHOD,
759             blur => VOID|METHOD,
760             close => VOID|METHOD,
761             focus => VOID|METHOD,
762             window => OBJ|READONLY,
763             self => OBJ|READONLY,
764             navigator => OBJ|READONLY,
765             screen => OBJ|READONLY,
766             top => OBJ|READONLY,
767             frames => OBJ|READONLY,
768             length => NUM|READONLY,
769             parent => OBJ|READONLY,
770             name => STR,
771             scroll => VOID|METHOD,
772             scrollBy => VOID|METHOD,
773             scrollTo => VOID|METHOD,
774             history => OBJ|READONLY,
775             # See the comment preceding the commented-out subs.
776             # status => STR,
777             # defaultStatus => STR,
778             );
779              
780             sub alert {
781 0     0 1 0 my $self = shift;
782 0 0   0   0 &{$$self{Scripter_alert}||sub{print @_,"\n";()}}(@_);
  0         0  
  0         0  
  0         0  
783             }
784             sub confirm {
785 0     0 1 0 my $self = shift;
786 0   0     0 ($$self{Scripter_confirm}||$self->die(
787             "There is no default confirm function"
788             ))->(@_)
789             }
790             sub prompt {
791 0     0 1 0 my $self = shift;
792 0   0     0 ($$self{Scripter_prompt}||$self->die(
793             "There is no default prompt function"
794             ))->(@_)
795             }
796              
797             sub location {
798 370     370 1 2834 my $self = shift;
799 370   66     1140 my $loc = $self->{Scripter_loc} ||= WWW::Scripter::Location->new(
800             $self
801             );
802 370 50       4356 $loc->href(@_) if @_;
803 370         886 $loc;
804             }
805              
806             sub navigator {
807 3     3 1 4 my $self = shift;
808 3   33     18 $navi{$self} ||=
809             new WWW::Scripter::Navigator:: $self;
810             }
811              
812             sub screen {
813 1     1 1 2 my $self = shift;
814 1   50     19 $scrn{$self} ||=
815             bless \my $foo, WWW::Scripter::Screen::;
816             }
817             @WWW::Scripter::Interface{WWW::Scripter::Screen::,'Screen'} = (
818             'Screen', {}
819             );
820              
821             sub setTimeout {
822 15     15 1 760 my $doc = shift->document;
823 15         73 my $time = time;
824 15         19 my ($code, $ms) = (shift,shift);
825 15         20 $ms /= 1000;
826 15   100     76 my $t_o = $timeouts{$doc}||=[];
827 15         45 $$t_o[my $id = @$t_o] =
828             [$ms+$time, $code, @_];
829 15         26 return $id;
830             }
831              
832             sub clearTimeout {
833 1     1 1 3 delete $timeouts{shift->document}[shift];
834 1         4 return;
835             }
836              
837             sub setInterval {
838 5     5 1 56 my $doc = shift->document;
839 5         29 my $time = time;
840 5         7 my ($code, $ms) = (shift,shift);
841 5         82 $ms /= 1000;
842 5   100     31 my $t_o = $timers{$doc}||=[];
843 5         17 $$t_o[my $id = @$t_o] =
844             [$ms+$time, $code, @_];
845 5         13 return $id;
846             }
847              
848             sub clearInterval {
849 5     5 1 18 delete $timers{shift->document}[shift];
850 5         48 return;
851             }
852              
853             sub open {
854 25     25 1 1592 my($self,$url,$target,undef,$replace) = @_;
855 25 100       75 $target
856             = $self->find_target(defined $target ? $target : '_blank');
857 25 100 100     108 if(defined $url and length $url) {
    100          
858 14 50       23 if(my $base = $self->base) {
859 14         128 require URI;
860 14         34 $url = URI->new_abs( $url, $base );
861             }
862 14   66     3717 $target||=$self->top;
863 14 100       44 $replace
864             ? $target->location->replace($url)
865             : $target->get($url);
866 14         38 $target;
867             }
868             elsif(!$target) {
869             # undef or "" in single-window mode: append an ‘unbrowsed’
870             # history entry to simulate a new window
871 5         14 (my $ret = $self->top)->{page_stack}->_add();
872 5         12 _initial_page($ret);
873 5         12 $ret;
874             }
875             else {
876             # open("") with existing window; do nothing
877 6         14 $target
878             }
879             }
880              
881             sub close {
882 6 100   6 1 1232 if(my $g = $_[0]{Scripter_g}) {
883 5         22 $g->detach($_[0]);
884             }
885             else {
886 1         2 $_[0]->history->go(-1);
887             }
888             _:
889             }
890              
891             sub focus {
892 3 50   3 1 13 my $g = $_[0]{Scripter_g} or return;
893 3         9 $g->bring_to_front(shift);
894 3         6 return;
895             }
896              
897             sub blur {
898 3 50   3 1 10 my $g = $_[0]{Scripter_g} or return;
899 3         6 my($maybe_self,$next) = $g->windows;
900 3 100       8 $next or return;
901 2 100       5 $maybe_self == $_[0] or return;
902 1         4 $g->bring_to_front($next);
903 1         3 return;
904             }
905              
906              
907 689     689 1 3442 sub history { $_[0]{page_stack} }
908              
909             sub frames {
910 188     188 1 8222 my $doc = $_[0]->document;
911 188   100     1938 my $frames = $frames{$doc||''} # the ||'' is for non-HTML docu-
      66        
912             ||= WWW::Scripter'Frames->new( $_[0], $doc ); # ments, which all share
913 188 100       550 wantarray ? @$frames : $frames # an empty frames
914             } # collection
915              
916 1     1 1 5 sub window { $_[0] }
917             *self = *window;
918 2     2 1 9 sub length { $frames{$_[0]->document}->length }
919              
920             sub top {
921 15     15 1 158 my $self = shift;
922 15 100       58 $$self{Scripter_t} || do {
923 6         8 my $parent = $self;
924 6         6 while() {
925             $$parent{Scripter_pa} or
926 11 100       44 weaken( $$self{Scripter_t} = $parent), last;
927 5         6 $parent = $$parent{Scripter_pa};
928             }
929             $$self{Scripter_t}
930 6         30 };
931             }
932              
933             sub parent {
934 138     138 1 564 my $self = shift;
935 138 100       826 $$self{Scripter_pa} || $self;
936             }
937              
938 40     40   415 sub _set_parent { weaken( $_[0]{Scripter_pa} = $_[1] ) }
939              
940             sub name {
941 64     64 1 58 my $self = shift;
942 64         64 my $old = $$self{Scripter_nm};
943 64 100       120 $$self{Scripter_nm} = $_[0] if @_;
944 64         116 $old;
945             }
946              
947       0 1   sub scroll{}; *scrollBy=*scrollTo=*scroll;
948              
949             # ~~~ This conflicts with Mech’s method. We probably need to bite the
950             # bullet and provide a separate window object for scripts.
951             #sub status {
952             # my $old = $status{my $doc = shift->document};
953             # no warnings 'uninitialized';
954             # $status{$doc} = "$_[0]" if @_;
955             # defined $old ? $old : ''
956             #}
957             #
958             # ~~~ This one is commented out because it makes no sense without the
959             # previous one.
960             #sub defaultStatus {
961             # my $old = $dstatus{my $doc = shift->document};
962             # no warnings 'uninitialized';
963             # $dstatus{$doc} = "$_[0]" if @_;
964             # defined $old ? $old : ''
965             #}
966              
967             # ------------- Window-Related Public Methods -------------- #
968              
969 0     0 1 0 sub set_alert_function { ${$_[0]}{Scripter_alert} = $_[1]; }
  0         0  
970 0     0 1 0 sub set_confirm_function { ${$_[0]}{Scripter_confirm} = $_[1]; }
  0         0  
971 0     0 1 0 sub set_prompt_function { ${$_[0]}{Scripter_prompt} = $_[1]; }
  0         0  
972              
973             sub check_timers {
974 60     60 1 1000642 my $time = time;
975 60         119 my $self = shift;
976 60         334 local *_;
977 60         108 my $doing_timers_now;
978             my $jh;
979 60         278 for my $timers(\%timeouts, \%timers) {
980 120   100     527 my $t_o = $$timers{$self->document}||next;
981 85         1129 for my $id(0..$#$t_o) {
982 118 50       707 next unless $_ = $$t_o[$id];
983 27     27   139 no warnings 'uninitialized';
  27         43  
  27         10009  
984 118         178 local *@;
985             $$_[0] <= $time and
986             reftype $$_[1] eq 'CODE' || (
987             exists $INC{'overload.pm'}
988             && defined blessed $$_[1]
989             && overload'Method($$_[1],'&{}')
990             )
991 118 100 66     1375 ? eval { $$_[1]->(@$_[2..$#$_]) }
  58 100 66     2973  
    100 66        
992             : (
993             $jh ||= $self->_handler_for_lang('JavaScript')
994             and $jh->eval($self,$$_[1])
995             ),
996             $@ && $self->warn($@),
997             $doing_timers_now ? $$_[0] = time : delete $$t_o[$id];
998             }
999 120         1392 } continue { ++$doing_timers_now }
1000 60         205 $_->check_timers for $self->frames;
1001             # ~~~ Should we try to trigger the timers in the right order if,
1002             # exempli gratia, an iframe’s timer was registered with 200 as
1003             # the timeout, and then the main window with 210 immediately
1004             # thereafter?
1005             return
1006 60         1809 }
1007              
1008             sub count_timers {
1009 36     36 1 881 my $self = shift;
1010 36         37 my $count;
1011 36         92 for(\%timeouts, \%timers) {
1012 72 100       170 if(my $t_o = $$_{$self->document}) {
1013             #use DDS; Dump [map $_&&[map "$_", @$_], @$t_o];
1014 62         417 for my $id(0..$#$t_o) {
1015 38 50       64 next unless $$t_o[$id];
1016 38         72 ++$count
1017             }
1018             }
1019             }
1020 36 100 66     158 sum $count||(), map $_->count_timers, $self->frames or 0;
1021             }
1022              
1023             sub wait_for_timers {
1024 3     3 1 11 my($self, %args) = @_;
1025 3 100       10 my $start_time = time if $args{max_wait};
1026 3   100     14 my $interval = $args{interval} || .1;
1027 3   100     10 my $min = $args{min_timers} || 0;
1028 3         7 $self->check_timers;
1029 3   100     5 while(
      66        
1030             $self->count_timers > $min
1031             and !$args{max_wait} || time-$start_time < $args{max_wait}
1032             ) {
1033 22         3105111 select(undef,undef,undef,$interval);
1034 22         306 $self->check_timers;
1035             }
1036             _:
1037             }
1038              
1039             sub window_group {
1040 47     47 1 176 my $old = (my $self = shift)->{Scripter_g};
1041 47 100       138 @_ and weaken($self->{Scripter_g} = shift);
1042 47         89 $old
1043             }
1044              
1045             sub find_target {
1046 49     49 1 634 my $self = shift;
1047 49         53 my $name = shift;
1048 27     27   116 no warnings 'uninitialized';
  27         34  
  27         9636  
1049 49 100 66     160 if(!CORE::length $name and my $doc = document $self) {
1050 10 100       117 if(my $base_elem = $doc->look_down(_tag => 'base', target => qr)(?:\)))){
1051 1         77 $name = $base_elem->attr('target');
1052             }
1053             }
1054 49 100       984 CORE::length $name or return $self;
1055 40 100       111 if($name =~ /^_[Bb][Ll][Aa][Nn][Kk]\z/) {
1056 11 100       30 if(my $g = $$self{Scripter_g}) {
1057 4         15 attach $g my $neww = $self->clone->clear_history(1);
1058 4         12 return $neww;
1059             }
1060 7         17 return undef;
1061             }
1062 29 100       67 $name =~ /^_[Ss][Ee][Ll][Ff]\z/ and return $self;
1063 28 100       51 $name =~ /^_[Pp][Aa][Rr][Ee][Nn][Tt]\z/ and return $self->parent;
1064 26 100       62 $name =~ /^_[Tt][Oo][Pp]\z/ and return $self->top;
1065              
1066             # Search subframes, and then ancestors (including their subframes), in
1067             # breadth-first order
1068 24         22 my $current_ancestor = $self;
1069 24         17 my $prev_ancestor;
1070 24         22 while() {
1071 30 100       49 $current_ancestor->name eq $name and return $current_ancestor;
1072 29 100       71 my $next_level = [
1073             $prev_ancestor
1074             ? grep $_ != $prev_ancestor, $current_ancestor->frames
1075             : $current_ancestor->frames
1076             ];
1077 29         1401 while($next_level) {
1078 36         338 my $tmp = $next_level; $next_level = undef;
  36         30  
1079 36         66 for(@$tmp) {
1080 18 100       37 if($_->name eq $name) { return $_ }
  11         46  
1081 7         12 push @$next_level, $_->frames;
1082             }
1083             }
1084 18         17 $prev_ancestor = $current_ancestor;
1085 18         29 $current_ancestor = $current_ancestor->parent;
1086 18 100       42 last if $prev_ancestor == $current_ancestor;
1087             }
1088              
1089             # If we reach this point, there are no frames named $name. Return undef
1090             # in single-window mode, or look for a window.
1091 12 100       33 my $g = $$self{Scripter_g} or return undef;
1092 8   66     39 my $named = ($$self{Scripter_n}||=&fieldhash({}))->{$self->response}||={};
      100        
1093             # The extra ${} is there since a reference in a tied hash element cannot
1094             # be weakened directly, as the element is just temporary each time.
1095             $$named{$name} && ${$$named{$name}}->window_group
1096 4         11 ? ${$$named{$name}}
1097 8 100 100     96 : do {
1098 4         11 attach $g my $neww = $self->clone->clear_history(1);
1099 4         5 weaken(${$$named{$name}} = $neww);
  4         14  
1100 4         9 $neww
1101             }
1102             }
1103              
1104             # ------------- EventTarget interface ------------- #
1105              
1106             *event_listeners_enabled = *scripts_enabled;
1107              
1108             # What we are doing here is delegating event handler/listener storage to
1109             # a response object (and fooling EventTarget into thinking that the
1110             # response object is an EventTarget). This is so that each page has its own
1111             # set of event handlers, but we still use the WWW::Scripter object as the
1112             # event target.
1113             for my $meth (qw b addEventListener removeEventListener event_handler
1114             get_event_listeners b) {
1115 27     27   120 no strict 'refs';
  27         31  
  27         16546  
1116             my $full_meth= "HTML::DOM::EventTarget::$meth";
1117             *$meth = sub {
1118 766     766   69920 shift->response->$full_meth(@_);
1119             }
1120             }
1121              
1122              
1123             # ------------- Image Hooks -------------- #
1124              
1125             sub fetch_images {
1126 5     5 1 287 my $old = (my $self = shift)->{Scripter_i};
1127 5 100       13 @_ and $self->{Scripter_i} = shift;
1128 5         12 $old
1129             }
1130              
1131             sub image_handler {
1132 6     6 1 361 my $old = (my $self = shift)->{Scripter_ih};
1133 6 100       14 @_ and $self->{Scripter_ih} = shift;
1134 6         11 $old
1135             }
1136              
1137             # ------------- Scripting hooks and what-not ------------- #
1138              
1139             sub eval {
1140 0     0 1 0 my ($self,$code) = (shift,shift);
1141 0         0 my $h = $self->_handler_for_lang(my $lang = shift);
1142 0   0     0 my $ret = (
1143             $h or $self->die(
1144             defined $lang ? "No scripting handlers have been registered for $lang"
1145             : "No scripting handlers have been registered"
1146             )
1147             )->eval($self,$code);
1148 0 0       0 $@ and $self->warn($@);
1149 0         0 $ret;
1150             }
1151              
1152             sub use_plugin {
1153 14     14 1 4035 my ($self, $plugin, @opts) = (shift, shift, @_);
1154 14   100     39 my $plugins = $self->{plugins} ||= {};
1155 14         17 $plugin = _plugin2module($plugin);
1156 14 50       27 return $plugins->{$plugin} if $self->{cloning};
1157 14 100       28 if(exists $plugins->{$plugin}) {
1158 2 100       7 $plugins->{$plugin}->options(@opts) if @opts;
1159             }
1160             else {
1161 12         31 (my $plugin_file = $plugin) =~ s-::-/-g;
1162 12         61 require "$plugin_file.pm";
1163 12         1220 $plugins->{$plugin} = $plugin->init($self, \@opts);
1164 12 100       70 $plugins->{$plugin}->options(@opts) if @opts;
1165             }
1166 13         40 $plugins->{$plugin};
1167             }
1168              
1169             sub plugin {
1170 5     5 1 6 my $self = shift;
1171 5         9 my $plugin = _plugin2module(shift);
1172             return exists $self->{plugins}{$plugin}
1173 5 50 50     32 ? $self->{plugins}{$plugin} || 1 : 0;
1174             }
1175              
1176             sub _plugin2module { # This is NOT a method
1177 19     19   20 my $name = shift;
1178 19 100       52 return $name if $name =~ /::/;
1179 2         7 $name =~ s/-/::/g;
1180 2         5 return __PACKAGE__."::Plugin::$name";
1181             }
1182              
1183             sub _clone_plugins {
1184 59     59   67 my $self = shift;
1185 59 100       160 return unless $self->{plugins};
1186 1         2 my $plugins = $self->{plugins} = { %{$self->{plugins}} };
  1         4  
1187 1         4 while ( my($pn,$po) = each %$plugins ) {
1188             # plugin name, plugin object
1189 4 100 100     39 next unless $po && defined blessed $po && $po->can('clone');
      100        
1190 1         4 $plugins->{$pn} = $po->clone($self);
1191             }
1192             }
1193              
1194             sub dom_enabled {
1195 8     8 1 17 my $old = (my $self = shift)->{Scripter_dumb};
1196 8 50       24 defined $old or $old = 1; # default
1197 8 50       17 if(@_) {{
1198 8         9 $$self{Scripter_dumb} = !!$_[0]; # We don’t want undef
  8         18  
1199             }} # resetting it.
1200             $old
1201 8         10 }
1202              
1203             sub scripts_enabled {
1204 357     357 1 20110 my $old = $scriptable{my $self = shift};
1205 357 50       736 defined $old or $old = 1; # default
1206 357 100       692 if(@_) {{
1207 6         9 $scriptable{$self} = !!$_[0]; # We don’t want undef resetting it.
  6         22  
1208 6   50     31 ($self->document ||last) ->event_listeners_enabled(shift) ;
1209             }}
1210             $old
1211 357         656 }
1212             # used by HTML::DOM::EventTarget:
1213             *event_listeners_enabled = *scripts_enabled;
1214              
1215             sub script_handler {
1216 20     20 1 174 my($self,$key) = (shift,shift);
1217 20         58 my $old = $script_handlers{$self}{$key};
1218 20 50       72 @_ and $script_handlers{$self}{$key} = shift;
1219 20         40 $old
1220             }
1221              
1222             sub class_info {
1223 5     5 1 672 my $self = shift;
1224 5 50       11 @_ and push @{ $class_info{$self} }, shift;
  0         0  
1225 5 50       15 @{ $class_info{$self} } if defined wantarray;
  5         30  
1226             }
1227              
1228             # ------------- Miss Elaine E. S. ------------- #
1229              
1230             # This function is exported upon request.
1231             sub abort {
1232 27     27   135 no warnings 'exiting';
  27         34  
  27         5716  
1233 1     1 0 306 last Scripter_ABORT;
1234             }
1235              
1236             sub forward {
1237 14     14 1 1572 my $self = shift;
1238 14         24 $self->{page_stack}->go(1);
1239             }
1240              
1241             sub clear_history {
1242 66     66 1 1947 my $self = shift;
1243 66         170 $$self{'page_stack'}->_clear(@_);
1244 66 100       146 if (shift) {
1245 61         152 $self->_reset_page;
1246              
1247             # list of keys taken from _update_page
1248 61         602 delete $self->{$_} for qw[ req redirected_url res status base ct
1249             uri last_uri content ];
1250 61         121 _initial_page($self);
1251             }
1252 66         142 return $self;
1253             }
1254              
1255             sub max_docs {
1256 6     6 1 52 my $self= shift;
1257 6 100       16 defined wantarray and my $old = $self->stack_depth+1;
1258 6 100       36 $self->stack_depth(shift()-1) if @_;
1259 6         20 $old;
1260             }
1261              
1262             sub max_history {
1263 11     11 1 19 my $old = (my $self = shift)->{Scripter_max_hist};
1264 11 100       28 @_ and $self->{Scripter_max_hist} = shift;
1265 11         18 $old
1266             }
1267              
1268             # ------------- History object ------------- #
1269              
1270             package WWW::Scripter::History;
1271              
1272             <<'mldistwatch' if 0;
1273             use WWW::Scripter; $VERSION = $WWW'Scripter'VERSION;
1274             mldistwatch
1275             our $VERSION = $WWW'Scripter'VERSION;
1276              
1277 27     27   518 BEGIN { *fieldhashes = *WWW::Scripter::fieldhashes }
1278 27     27   113 use HTML::DOM::Interface qw 'NUM STR READONLY METHOD VOID';
  27         65  
  27         1657  
1279 27     27   101 use Scalar::Util 'weaken';
  27         42  
  27         23559  
1280              
1281             =begin comment
1282              
1283             History notes
1284              
1285             A history object is a blessed array ref. That array ref holds the browser
1286             history entries. Each entry is itself an array ref containing:
1287              
1288             0 - request object
1289             1 - response object
1290             2 - URL
1291             3 - state info
1292             4 - title
1293              
1294             The length of the array tells us whether it is a state-info entry. The URL
1295             is used both for fragments and for state objects. The second element will
1296             be blank if it has been erased because of max_docs.
1297              
1298             The history object has a pointer to the ‘current’ history item
1299             ($index{$self}).
1300              
1301             Document objects are referenced by response: $document{$response}. The
1302             window’s ‘document’ method is inherited from HTML::DOM::View, and we set it
1303             whenever history is browsed, retrieving it from %document.
1304              
1305             The ‘unbrowsed’ state that used to be mentioned in HTML 5 (before it got
1306             really convoluted) is represented by an empty array. An empty array can
1307             exist alongside other entries, as we add one when we simulate a
1308             new window in single-window mode.
1309              
1310             Response objects are also listed in the array ref stored in $res{$self} in
1311             the order in which they were accessed. Subroutines that add to this array
1312             then call _clean($self), which then eliminates duplicate entries and
1313             deletes from the history object itself as many of the oldest response
1314             objects as are necessary to satisfy max_docs.
1315              
1316             =end comment
1317              
1318             =cut
1319              
1320             $$_{~~__PACKAGE__} = 'History',
1321             $$_{History} = {
1322             length => NUM|READONLY,
1323             index => NUM|READONLY,
1324             userAgent => STR|READONLY,
1325             go => METHOD|VOID,
1326             back => METHOD|VOID,
1327             forward => METHOD|VOID,
1328             pushState => METHOD|VOID,
1329             }
1330             for \%WWW::Scripter::Interface;
1331              
1332             fieldhashes \my ( %w, %index, %res );
1333              
1334             sub new {
1335 136     136   740 my ($pack,$mech) = @_;
1336 136         267 my $self = bless [[]], $pack;
1337 136         154 weaken(${$w{$self}} = $mech);
  136         828  
1338 136         311 $index{$self} = 0;
1339 136         281 $res{$self} = [];
1340 136         242 $self
1341             }
1342              
1343             sub _add {
1344 228     228   225 my $self = shift;
1345 228 100       451 if(defined $$self[-1][0]) { # if there is no ‘undef’ entry
1346 128         455 splice @$self, ++$index{$self};
1347 128         250 push @$self, \@_;
1348 128 100       286 $_[1] and push(@{$res{$self}}, $_[1]), _clean($self,1);
  123         425  
1349             }
1350             else {
1351 100         185 $$self[-1] = \@_;
1352 100 50       246 push @{$res{$self}}, $_[1] if $_[1];
  100         392  
1353             }
1354             }
1355              
1356             # Called when browsing to a stale history entry and also by
1357             # location->replace
1358             sub _replace {
1359 14     14   17 my $self = shift;
1360 14 100       31 if(defined $$self[-1][0]) { # if browsing has occurred
1361 13         28 $$self[$index{$self}] = \@_;
1362 13 50       29 $_[1] and push(@{$res{$self}}, $_[1]), _clean($self);
  13         47  
1363             }
1364             else {
1365 1         2 $$self[-1] = \@_;
1366 1 50       4 push @{$res{$self}}, $_[1] if $_[1];
  1         4  
1367             }
1368             }
1369              
1370             sub _delete_res {
1371 1     1   59 delete $_[0][$index{$_[0]}][1];
1372             }
1373              
1374             sub _clear { # called by Scripter->clear_history
1375 66     66   66 my $self = shift;
1376 66 100       236 @$self = shift() ? undef : $$self[$index{$self}];
1377 66         138 $index{$self} = 0;
1378             }
1379              
1380             sub length {
1381 23     23   285 scalar @{+shift}
  23         82  
1382             }
1383              
1384             sub index { # ~~~ We can probably make this modifiable later.
1385             $index{+shift}
1386 20     20   69 }
1387              
1388             sub go {
1389 51     51   56 my $self = shift;
1390 51 100       102 if(0==$_[0]) {
1391 2         3 ${$w{$self}}->reload;
  2         13  
1392             }
1393             else {
1394 49         90 my $new_pos = $index{$self}+shift;
1395 49 100 100     235 $new_pos < 0 || $new_pos > $#$self and return;
1396 45         85 $index{$self} = $new_pos;
1397              
1398             # ~~~ trigger popstate
1399              
1400             # If there is a response object, we just reset the page from that. If
1401             # there isn’t then this is a stale entry and we need to
1402             # re-fetch the page.
1403 45         57 my $entry = $$self[$new_pos];
1404 45 100       72 if(defined $$entry[1]) { # response
1405 36         37 ${$w{$self}}->_update_page(@$entry)
  36         106  
1406             }
1407             else {
1408 9         10 local(my $w = ${$w{$self}})->{Scripter_replace} = 1;
  9         24  
1409 9         18 $w->request($$entry[0]);
1410             }
1411             }
1412 47         84 return;
1413             }
1414              
1415 0     0   0 sub back { shift->go(-1) }
1416 0     0   0 sub forward { shift->go(1) }
1417              
1418             sub pushState {
1419 4     4   145 my $self = shift;
1420              
1421 4         10 my $index = $index{$self}++;
1422 4         5 my($req,$res) = @{$$self[$index]}[0,1];
  4         8  
1423              
1424             # count future entries that share the same doc
1425 4         3 my $to_delete;
1426 4         12 for($index+1..$#$self) {
1427 2 100 50     9 ($$self[$_][1]||0) == $res ? ++$to_delete : last;
1428             }
1429              
1430             # replace those future entries with the new item
1431 4   100     20 splice @$self, $index+1, $to_delete||0, [ $req, $res, $_[2], @_ ];
1432              
1433 4         6 _clean($self);
1434              
1435 4         5 return;
1436             }
1437              
1438             sub _clean {
1439 140     140   152 my($self, $check_max_hist) = @_;
1440 140 100       235 if($check_max_hist) {
1441 123         105 my $max = (my $w = ${$w{$self}})->{Scripter_max_hist};
  123         250  
1442 123 100 100     342 if($max && @$self > $max) {
1443 10         14 my $diff = @$self-$max;
1444 10         14 $index{$self} -= $diff;
1445 10         16 splice @$self, 0, $diff;
1446             }
1447             }
1448 140         127 my $max = ${$w{$self}}->stack_depth + 1;
  140         513  
1449 140         627 my $res = $res{$self};
1450 140         147 my %res;
1451 140         227 for(@$self) {
1452 565 100       1362 defined $$_[1] and $res{0+$$_[1]}++
1453             }
1454 140 50       286 if($max) { # ~~~ It may be more efficient if, instead of searching for
1455 140         109 my @res; # duplicates here, we scan for the ones we know we’ve added
1456             my %seen; # in _add and _replace.
1457 140         192 for(reverse @$res) {
1458 543         399 my $refaddr = 0+$_;
1459 543 100 100     2019 unshift @res, $_ if exists $res{$refaddr} && !$seen{$refaddr}++;
1460             }
1461 140 100       816 @$res = @res, return unless @res > $max;
1462 13         13 my $diff = @res-$max;
1463 13         11 my %to_delete;
1464 13         37 @to_delete{map 0+$_, splice @res, 0,$diff}=();
1465 13         20 @$res = @res;
1466 13         20 for(@$self) {
1467 50 100       65 next unless defined $$_[1];
1468 39 100       396 delete $$_[1] if exists $to_delete{0+$$_[1]};
1469             }
1470             }
1471             else {
1472 0         0 @$res = grep exists $res{refaddr $_}, @$res;
1473             }
1474             }
1475              
1476             sub _uri {
1477 646     646   482 my $self = shift;
1478 646 100       1876 $$self[$index{$self}][2] || ${$w{$self}}->uri;
  163         636  
1479             }
1480              
1481             # ~~~
1482              
1483             # ------------- Location object ------------- #
1484              
1485             package WWW'Scripter'Location;
1486              
1487 27     27   134 use HTML::DOM::Interface qw'STR METHOD VOID';
  27         33  
  27         1262  
1488 27     27   97 use Scalar::Util 'weaken';
  27         30  
  27         1488  
1489              
1490 27     27   103 use overload fallback => 1, '""' => sub{${+shift}->history->_uri};
  27     618   31  
  27         234  
  618         2918  
  618         1029  
1491              
1492             $$_{~~__PACKAGE__} = 'Location',
1493             $$_{Location} = {
1494             assign => VOID|METHOD,
1495             hash => STR,
1496             host => STR,
1497             hostname => STR,
1498             href => STR,
1499             pathname => STR,
1500             port => STR,
1501             protocol => STR,
1502             search => STR,
1503             reload => VOID|METHOD,
1504             replace => VOID|METHOD,
1505             }
1506             for \%WWW::Scripter::Interface;
1507              
1508             sub new { # usage: new .....::Location $mech
1509 134     134   146 my $class = shift;
1510 134         257 weaken (my $mech = shift);
1511 134         202 my $self = bless \$mech, $class;
1512 134         814 $self;
1513             }
1514              
1515             sub hash {
1516 5     5   9 my $loc = shift;
1517 5         11 my $old = (my $uri = $$loc->history->_uri)->fragment;
1518 5 100       63 $old = "#$old" if defined $old;
1519 5 100       11 if (@_){
1520 1         7 shift() =~ /#?(.*)/s;
1521 1         3 (my $uri_copy = $uri->clone)->fragment($1);
1522 1 50       45 $uri_copy->eq($uri) or $$loc->get($uri_copy);
1523             }
1524 5 100       26 $old||''
1525             }
1526              
1527             sub host {
1528 2     2   294 my $loc = shift;
1529 2         4 my $uri = $$loc->history->_uri;
1530 2 100       14 if (@_) {
1531 1         5 (my $uri = $uri->clone)->port("");
1532 1         47 $uri->host_port(shift);
1533 1         61 $$loc->get($uri);
1534             }
1535 2 50       9 defined wantarray ? $uri->host_port : ()
1536             }
1537              
1538             sub hostname {
1539 2     2   294 my $loc = shift;
1540 2         6 my $uri = $$loc->history->_uri;
1541 2 100       14 if (@_) {
1542 1         4 (my $uri = $uri->clone)->host(shift);
1543 1         58 $$loc->get($uri);
1544             }
1545 2 50       9 defined wantarray ? $uri->host : ()
1546             }
1547              
1548             sub href {
1549 13     13   1730 my $loc = shift;
1550 13 100       47 my $old = $$loc->history->_uri->as_string if defined wantarray;
1551 13 100       91 if (@_) {
1552 4         11 $$loc->get(shift);
1553             }
1554 13         39 $old;
1555             }
1556              
1557 1     1   2 sub assign { ${$_[0]}->get($_[1]); () }
  1         4  
  1         6  
1558              
1559             sub pathname {
1560 2     2   258 my $loc = shift;
1561 2         5 my $uri = $$loc->history->_uri;
1562 2 100       14 if (@_) {
1563 1         4 (my $uri = $uri->clone)->path(shift);
1564 1         25 $$loc->get($uri);
1565             }
1566 2 50       9 defined wantarray ? $uri->path : ()
1567             }
1568              
1569             sub port {
1570 2     2   295 my $loc = shift;
1571 2         7 my $uri = $$loc->history->_uri;
1572 2 100       13 if (@_) {
1573 1         3 (my $uri = $uri->clone)->port(shift);
1574 1         43 $$loc->get($uri);
1575             }
1576 2 50       8 defined wantarray ? $uri->port : ()
1577             }
1578              
1579             sub protocol {
1580 2     2   271 my $loc = shift;
1581 2         84 my $uri = $$loc->history->_uri;
1582 2 100       15 if (@_) {
1583 1         5 shift() =~ /(.*):?/s;
1584 1         4 (my $uri = $uri->clone)->scheme($1);
1585 1         1593 $$loc->get($uri);
1586             }
1587 2 50       10 defined wantarray ? $uri->scheme . ':' : ()
1588             }
1589              
1590             sub search {
1591 3     3   5 my $loc = shift;
1592 3         8 my $uri = $$loc->history->_uri;
1593 3 100       21 if (@_){
1594 2         7 shift() =~ /(\??)(.*)/s;
1595             (
1596 2 50 33     6 my $uri_copy = $uri->clone
1597             )->query(
1598             $1||length$2 ? "$2" : undef
1599             );
1600 2         89 $$loc->get($uri_copy);
1601             }
1602 3 100       11 return unless defined wantarray;
1603 2         6 my $q = $uri->query;
1604 2 50       31 defined $q ? "?$q" : "";
1605             }
1606              
1607              
1608             # ~~~ Safari doesn't support forceGet. Do I need to?
1609             sub reload { # args (forceGet)
1610 1     1   2 ${+shift}->reload
  1         6  
1611             }
1612             sub replace { # args (URL)
1613 5     5   6 my $mech = ${+shift};
  5         8  
1614 5         11 local $$mech{Scripter_replace } = 1;
1615 5         10 $mech->get(shift);
1616             }
1617              
1618              
1619             # ------------- Navigator object ------------- #
1620              
1621             package WWW::Scripter::Navigator;
1622              
1623 27     27   18972 use HTML::DOM::Interface qw'STR READONLY METHOD BOOL';
  27         34  
  27         1388  
1624 27     27   104 use Scalar::Util 'weaken';
  27         31  
  27         3196  
1625              
1626             $$_{~~__PACKAGE__} = 'Navigator',
1627             $$_{Navigator} = {
1628             appName => STR|READONLY,
1629             appCodeName => STR|READONLY,
1630             appVersion => STR|READONLY,
1631             userAgent => STR|READONLY,
1632             javaEnabled => METHOD|BOOL,
1633             platform => STR|READONLY,
1634             taintEnabled => METHOD|BOOL,
1635             cookieEnabled => BOOL|READONLY,
1636             }
1637             for \%WWW::Scripter::Interface;
1638              
1639 27         2112 use constant 1.03 our $_const = {
1640             mech => 0,
1641             name => 1,
1642             vers => 2,
1643             cnam => 3,
1644             plat => 4,
1645 27     27   102 };
  27         436  
1646 27     27   103 { no strict; delete @{__PACKAGE__."::"}{_const => keys %$_const} }
  27         36  
  27         7139  
1647              
1648             sub new {
1649 3     3   12 weaken((my $self = bless[],pop)->[mech] = pop);
1650 3         9 $self;
1651             }
1652              
1653             sub appName {
1654 4     4   479 my $self = shift;
1655 4         6 my $old = $self->[name];
1656 4 100       10 defined $old or $old = ref $self->[mech];
1657 4 100       7 @_ and $self->[name] = shift;
1658 4         10 return $old;
1659             }
1660              
1661             sub appCodeName {
1662 3     3   4 my $self = shift;
1663 3         4 my $old = $self->[cnam];
1664 3 100       7 defined $old or $old = ref $self->[mech];
1665 3 100       6 @_ and $self->[cnam] = shift;
1666 3         14 return $old;
1667             }
1668              
1669             sub appVersion {
1670 3     3   4 my $self = shift;
1671 3         4 my $old = $self->[vers];
1672 3 100 66     13 if(!defined $old and defined wantarray) {
1673 2         4 $old = $self->userAgent;
1674 2 50       80 $old =~ /(\d.*)/s
1675             ? $old = $1
1676             : $old = ref($self->[mech])->VERSION;
1677             }
1678 3 100       8 @_ and $self->[vers] = shift;
1679 3         15 return $old;
1680             }
1681              
1682             sub userAgent {
1683 3     3   9 shift->[mech]->agent;
1684             }
1685              
1686             sub platform {
1687 10     10   305 my $self = shift;
1688 10         11 my $old = $self->[plat];
1689 10 50 33     41 if(!defined $old and defined wantarray) {
1690 10         21 my $ua = $self->[mech]->agent;
1691 27     27   104 no warnings 'uninitialized';
  27         30  
  27         6100  
1692 10 50       290 $old
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
1693             = $ua =~ /\bWin(?:dows|32)?\b/ ? 'Win32'
1694             : $ua =~ /\bMac(?:intosh)\b/ ? $ua =~ /\bIntel\b/
1695             ? 'MacIntel' : 'MacPPC'
1696             : $ua =~ /\b(FreeBSD(?: i386)?|Linux)\b/
1697             ? $1
1698             : $^O eq 'MSWin32' ? 'Win32'
1699             : $^O eq 'MacOS' ? 'MacPPC'
1700             : $^O eq 'freebsd' ? 'FreeBSD'
1701             : $^O eq 'linux' ? 'Linux'
1702             : $^O ne 'darwin' ? $^O
1703             : pack "s", 28526, eq 'on' ? 'MacPPC' : 'MacIntel';
1704             }
1705 10 50       14 @_ and $self->[plat] = shift;
1706 10         28 return $old;
1707             }
1708              
1709       2     sub javaEnabled{}
1710             *taintEnabled=*javaEnabled;
1711              
1712 2     2   48 sub cookieEnabled { defined $_[0][mech]->cookie_jar }
1713              
1714             # ------------- about: protocol ------------- #
1715              
1716             package WWW'Scripter'_about_protocol;
1717              
1718             # ~~~ This method may be a bad idea if someone else wants to implement
1719             # other aspects of the about: protocol. Maybe we should use an LWP
1720             # handler. (Then we would, of course, require a later LWP.)
1721              
1722             <<'mldistwatch' if 0;
1723             use WWW::Scripter; $VERSION = $WWW'Scripter'VERSION;
1724             mldistwatch
1725             our $VERSION = $WWW'Scripter'VERSION;
1726              
1727 27     27   122 use LWP::Protocol;
  27         27  
  27         4788  
1728              
1729             our @ISA = LWP::Protocol::;
1730              
1731             LWP::Protocol'implementor about => __PACKAGE__;
1732              
1733             sub request { # based on the one in LWP::Protocol::file
1734 24     24   5932 my($self, $request, $proxy, $arg) = @_;
1735              
1736 24 50       76 if(defined $proxy) {
1737 0         0 return new HTTP::Response 400,,
1738             'The about: protocol does not work with proxies';
1739             }
1740              
1741 24         123 my $url= $request->url;
1742 24         122 my $scheme = $url->scheme;
1743              
1744 24 50       293 if ($scheme ne 'about') {
1745 0         0 return new HTTP::Response 500,
1746             "WWW::Scripter::_about_protocol called for $scheme";
1747             }
1748              
1749 24 100       66 return new HTTP::Response 404,
1750             "Nothing exists at $url" unless $url eq 'about:blank';
1751              
1752 22         173 my $response = new HTTP::Response 200, 'OK', [
1753             Content_Length=>0,
1754             Content_Type =>'text/html',
1755             ];
1756              
1757 22     22   1613 $self->collect($arg, $response, sub {\''});
  22         8210  
1758             }
1759              
1760             # ------------- Link and image lists for Mech ------------- #
1761              
1762             package WWW::Scripter::Links;
1763              
1764 27 50   27   1314 BEGIN { eval "require ".WWW'Scripter'Mech."::Link" or die $@ }
1765              
1766             sub TIEARRAY {
1767 17     17   319 bless \(my $links = pop), shift;
1768             }
1769              
1770             sub FETCH {
1771 51     51   187 my $link = ${$_[0]}->[$_[1]];
  51         93  
1772 51         383 my $mech_link = bless [], WWW'Scripter'Mech."::Link";
1773 51         116 tie @$mech_link, WWW'Scripter'Link::, $link;
1774 51         205 $dom_obj{$mech_link} = $link;
1775 51         115 $mech_link;
1776             }
1777 20     20   2637 sub FETCHSIZE { scalar @${$_[0]} }
  20         65  
1778 0     0   0 sub EXISTS { exists ${$_[0]}->links->[$_[1]] }
  0         0  
1779              
1780             package WWW::Scripter::Link;
1781              
1782 51     51   73 sub TIEARRAY { bless \(my $x = $_[1]) }
1783             sub FETCH {
1784 139     139   2074 my $self = shift;
1785 139         150 for(shift) {
1786             return
1787             $_ == 0 ? $$self->tag eq 'meta' # url
1788             ? $$self->attr('content') =~ /^\d+\s*;\s*url\s*=\s*(\S+)/i
1789 8         104 ? do { my $url = $1;
1790 8 100       30 $url =~ s/^"(.+)"$/$1/ or $url =~ s/^'(.+)'$/$1/;
1791 8         28 $url }
1792             : undef
1793 139 50       434 : $$self->attr($link_tags{$$self->tag}) :
    100          
    100          
    50          
    50          
    100          
    100          
    100          
    100          
1794             $_ == 1 ? $$self->tag eq 'a' ? $$self->as_text : undef : # text
1795             $_ == 2 ? $$self->attr('name') : # name
1796             $_ == 3 ? $$self->tag : # tag
1797             $_ == 4 ? $$self->ownerDocument->base : # base
1798             $_ == 5 ? {$$self->all_external_attr} : # attrs
1799             undef
1800             }
1801             }
1802 0     0   0 sub FETCHSIZE { 6 }
1803              
1804             package WWW::Scripter::Images;
1805              
1806 27 50   27   17337 BEGIN { eval "require ".WWW'Scripter'Mech."::Image" or die $@ }
1807              
1808             sub TIEARRAY {
1809 1     1   2 bless \(my $links = pop), shift;
1810             }
1811              
1812             sub FETCH {
1813 7     7   438 my $img = ${$_[0]}->[$_[1]];
  7         14  
1814 7         53 my $mech_img = new WWW'Scripter'Image:: $img;
1815 7         32 $dom_obj{$mech_img} = $img;
1816 7         17 $mech_img;
1817             }
1818 3     3   694 sub FETCHSIZE { scalar @${$_[0]} }
  3         10  
1819 0     0   0 sub EXISTS { exists ${$_[0]}->links->[$_[1]] }
  0         0  
1820              
1821             package WWW::Scripter::Image;
1822             our @ISA = WWW::Scripter::Mech."::Image";
1823 7     7   11 sub new { bless \(my $frin = pop) }
1824 6     6   64 sub url { ${$_[0]}->attr('src') }
  6         22  
1825 0     0   0 sub base { ${$_[0]}-ownerDocument->base }
  0         0  
1826 5     5   19 sub name { ${$_[0]}->attr('name') }
  5         9  
1827 5     5   37 sub tag { ${$_[0]}->tag }
  5         9  
1828 5     5   57 sub height { ${$_[0]}->attr('height') }
  5         12  
1829 5     5   27 sub width { ${$_[0]}->attr('width') }
  5         11  
1830 5     5   25 sub alt { ${$_[0]}->attr('alt') }
  5         9  
1831              
1832              
1833             # ------------- Frames list ------------- #
1834              
1835             package WWW::Scripter::Frames;
1836              
1837             # ~~~ This is horribly inefficient and clunky. It probably needs to be
1838             # programmed in full here, or at least the ‘Collection’ part (a tiny
1839             # bit of copy&paste).
1840              
1841 27     27   15445 use HTML::DOM::Collection;
  27         39  
  27         588  
1842 27     27   94 use HTML::DOM::NodeList::Magic;
  27         27  
  27         5548  
1843             our @ISA = "HTML::DOM::Collection";
1844              
1845             {
1846             WWW::Scripter'fieldhash my %w;
1847             my @empty_array;
1848            
1849             sub new {
1850 49     49   67 ; my($pack,$window,$doc) = @_
1851             ; my $ret = $pack->SUPER'new(
1852             $doc
1853             ? HTML::DOM::NodeList::Magic->new(
1854 47     47   842 sub { $doc->look_down(_tag => qr/^i?frame\z/) },
1855 49 100       291 $doc
1856             )
1857             : HTML'DOM'NodeList->new(\@empty_array)
1858             )
1859 49         1075 ; Scalar'Util'weaken($_) for $doc, $window;
1860 49         285 ; $w{$ret} = \$window;
1861 49         337 ; $ret
1862             }
1863            
1864 0 0   0   0 sub window { ${$w{+shift}||return undef} }
  0         0  
1865             }
1866              
1867             use overload fallback => 1,'@{}' => sub {
1868 180     180   163 [map $_->contentWindow, @{shift->${\'SUPER::(@{}'}}]
  180         212  
  180         717  
1869 27     27   152 };
  27         23  
  27         174  
1870              
1871 7   50 7   291 sub FETCH { (shift->SUPER::FETCH(@_)||return)->contentWindow }
1872              
1873              
1874 26     26   11498 !!*!*!!*!*!!*!*!!*!*!!*!*!!*!*!!*!*!!*!*!!*!*!!*!*!!*!*!!*!*!!*!*!!*!
  26         22198  
  26         2302