File Coverage

blib/lib/WWW/Scripter.pm
Criterion Covered Total %
statement 843 918 91.8
branch 345 434 79.4
condition 125 192 65.1
subroutine 171 195 87.6
pod 58 59 98.3
total 1542 1798 85.7


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