File Coverage

blib/lib/WWW/Scripter.pm
Criterion Covered Total %
statement 848 924 91.7
branch 345 434 79.4
condition 124 190 65.2
subroutine 171 195 87.6
pod 58 59 98.3
total 1546 1802 85.7


line stmt bran cond sub pod time code
1 26     26   1084493 use 5.006;
  26         106  
  26         8748  
2              
3             package WWW::Scripter;
4              
5             our $VERSION = '0.030';
6              
7 26     26   154 use strict; use warnings; no warnings qw 'utf8 parenthesis bareword';
  26     26   54  
  26     26   3295  
  26         470  
  26         54  
  26         1117  
  26         153  
  26         55  
  26         1348  
8              
9 26     26   33201 use CSS'DOM'Interface;
  26         165609  
  26         2199  
10 26     26   36061 use Encode qw'encode decode';
  26         459266  
  26         3263  
11 26     26   241 use Exporter 5.57 'import';
  26         790  
  26         842  
12 26     26   45947 use HTML::DOM 0.045; # weaken_response
  26         5097486  
  26         1951  
13 26     26   315 use HTML::DOM::EventTarget 0.053; # DOMAttrModified with correct type and
  26         466  
  26         775  
14 26     26   30391 use HTML::DOM::Interface 0.019 ':all'; # cancellability
  26         194250  
  26         6559  
15 26     26   29533 use HTML::DOM::View 0.018;
  26         14472  
  26         840  
16 26     26   23824 use HTTP::Headers::Util 'split_header_words';
  26         25474  
  26         2327  
17 26     26   22844 use HTTP::Response;
  26         628856  
  26         1092  
18 26     26   23453 use HTTP::Request;
  26         27552  
  26         927  
19 26     26   229 use Scalar::Util 1.09 qw 'blessed weaken reftype';
  26         913  
  26         2109  
20 26     26   280 use List'Util 'sum';
  26         54  
  26         2676  
21 26     26   45824 use LWP::UserAgent;
  26         548712  
  26         1024  
22 26     26   31875 use Time::HiRes 'time';
  26         88815  
  26         213  
23             BEGIN {
24 26     26   8002 require constant;
25 26         35425 require WWW::Mechanize;
26 26 50       3138716 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 26         3924 import constant Mech => 'WWW::Mechanize';
30             }
31              
32             BEGIN {
33 26 50   26   71 if(eval { require Hash::Util::FieldHash }) {
  26         391  
34 26         27939 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 84541 my $class = shift;
78 77         328 my %args = @_;
79 77 100       414 exists $args{max_docs}
80             and $args{stack_depth} = -1+delete$args{max_docs};
81 77         417 my $max_history = delete $args{max_history};
82              
83 77         1003 my $self = $class->SUPER::new(%args);
84              
85 77         499668 $$self{Scripter_max_hist} = $max_history;
86 77         1007 $script_handlers{$self} = {};
87 77         442 $scriptable{$self} = 1;
88              
89 77         604 $self->{page_stack} = WWW'Scripter'History->new( $self );
90              
91 77         272 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         1401 ];
105              
106 77 50       584 unless(exists $args{agent}) {
107 77         912 $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         6435 _initial_page($self);
116              
117 77         8992 $self;
118             }
119              
120             sub _initial_page {
121 143     143   1109 my $req = new HTTP::Request 'GET', 'about:blank';
122 143         289251 my $res = new HTTP::Response 200, OK => [
123             'content-length' => 0,
124             'content-type' => 'text/html',
125             ], '';
126 143         20927 $res->request($req);
127             shift->_update_page(
128 143         1854 $req, $res
129             );
130             }
131              
132             sub clone {
133 59     59 1 551 my $clone = (my $self = shift)->SUPER::clone(@_);
134 59         34771 $$_{$clone}=$$_{$self} for \(
135             %scriptable,%script_handlers
136             );
137 59         138 $class_info{$clone} = [@{$class_info{$self}}];
  59         606  
138 59         221 $clone->{handlers} = $self->{handlers};
139 59         1481 $clone->{page_stack} = WWW'Scripter'History->new($clone);
140 59         2313 delete @$clone{};
141 59         448 $clone->_clone_plugins;
142 59         299 $clone;
143             }
144              
145 35   50 35 1 1576 sub title { (shift->document||return)->title(@_) }
146              
147             sub content {
148 7     7 1 1552 my $self = shift;
149 7 100 66     42 if($self->is_html && $self->document) {
150 6         174 my %parms = @_;
151 6         26 my $cs = (my $doc = $self->document)->charset;;
152 6 100 66     151 if(exists $parms{format} && $parms{format} eq 'text') {
153 2         15 my $text = $doc->documentElement->as_text;
154 2 50       1495 return defined $cs ? encode $cs, $text : $text;
155             }
156 4         194 my $content = $doc->innerHTML;
157 4 50       3719 $content = encode $cs, $content if defined $cs;
158 4         184 $self->{content} = $content; # banana
159             }
160 5         81 $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 26     26   249 no warnings 'redefine';
  26         55  
  26         45436  
168 16     16 1 1454 my $self = shift;
169 16         86 my %parms = ( n=>1, @_ );
170              
171 16 50       95 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         283 my $link = $self->find_link(%parms);
177 16 100 66     7945 if($link and tag $link =~ '^a') {
178 15         221 my $follow;
179 15         51 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         455 DOMActivate_default => sub { ++$follow }
196             )
197 13     13   576 }
198 15         167 );
199 15 100       591 return unless $follow;
200 13   66     78 return ($self->find_target($dom_link->target)||$self)
201             ->get($link->url);
202             }
203             else {
204 1 50       18 $self->die(
205             'Link not found: ',
206             join ", ", map "$_ => '$parms{$_}'", sort keys %parms
207             )
208             if $self->{autocheck};
209             }
210             Scripter_plit:
211 1         8 }
212              
213              
214             sub request {
215 237     237 1 258734 for (my $foo) { # protect against tied $_
216 237         668 my $self = shift;
217 237 100       971 return unless defined(my $request = shift);
218              
219 236         1860 $request = $self->_modify_request( $request );
220              
221 236         37424 my $meth = $request->method;
222 236         3027 my $orig_uri = $request->uri;
223 236         1533 my $new_uri;
224 236 0       1146 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 236         5697 my $skip_fetch;
229 236 100       1203 if(defined($orig_uri->fragment)) {
230 8   33     213 ($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     262 $meth eq "GET" and $new_uri->eq($self->uri) and ++$skip_fetch;
236             }
237 236 100       3811 if ($new_uri) {
238 8         85 $request->uri($new_uri);
239             }
240              
241 236         655 my $response;
242              
243 236 100       646 if($skip_fetch) {
244 6         22 $response = $self->response;
245             }
246             else {
247 230         1138 Scripter_REQUEST: {
248 230         375 Scripter_ABORT: {
249 230         671 $response = $self->_make_request( $request, @_ );
250 229         1020253 last Scripter_REQUEST;
251             }
252 1         9 return 1
253             }
254             }
255              
256 235 50 33     1314 if ( $meth eq 'GET' || $meth eq 'POST' ) {
257 235 100       1215 $self->get_event_listeners('unload') and
258             $self->trigger_event('unload'),
259             $self->{page_stack}->_delete_res;
260              
261 235 100       9202 $self->{page_stack}->${\(
  235         2430  
262             $self->{Scripter_replace} ? '_replace' : '_add'
263             )}($request, $response, $orig_uri);
264             }
265              
266 235         2615 return $self->_update_page($request, $response);
267             }
268             }
269              
270             # Protect against tied $_
271 218     218 1 363651 sub get { return SUPER::get{@_} for my $foo }
  218         1899  
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 421     421   13995 my ($self, $request, $res) = @_;
281              
282 421         1141 $self->{req} = $request;
283 421         1819 $self->{redirected_uri} = $request->uri->as_string;
284              
285 421         17448 $self->{res} = $res;
286              
287 421         7445 $self->{status} = $res->code;
288 421         5242 $self->{base} = $res->base;
289 421   100     262775 $self->{ct} = $res->content_type || '';
290              
291 421 100       15562 if ( $res->is_success ) {
292 412         5694 $self->{uri} = $self->{redirected_uri};
293 412         1103 $self->{last_uri} = $self->{uri};
294             }
295              
296 421 100       2042 if ( $res->is_error ) {
297 9 50       155 if ( $self->{autocheck} ) {
298 0         0 $self->die( 'Error ', $request->method, 'ing ', $request->uri, ': ', $res->message );
299             }
300             }
301              
302 421         5913 $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 421         9788 my $content = $res->decoded_content(charset => "none");
307 421 50       52287 $content = $res->content if (not defined $content);
308              
309 421         723 $content .= &{\&{Mech."::_taintedness"}};
  421         643  
  421         3075  
310              
311 421 100 66     310829 if (
      100        
312             !defined $$self{Scripter_dumb} || $$self{Scripter_dumb}
313             and $self->is_html
314             ) {
315 389         8407 $res = $self->update_html($content);
316             }
317             else {
318 32         349 $self->{content} = $content;
319 32         175 $self->document(undef);
320             }
321              
322 421         6334 return $res;
323             } # _update_page
324              
325             sub _fetch_url {
326 16     16   31 my ($self) = @'_;
327             my $fetcher = $self->{Scripter_f}
328 16   66     100 ||= do {
329             (
330 7         47 my $clone = $self->clone->clear_history(1)
331             )->dom_enabled(0);
332 7         28 $clone->max_history(1);
333 7         32 $clone;
334             };
335 16         57 $fetcher->{last_uri} = $self->{uri};
336 16         167 require URI;
337 16         62 my $base = $self->base;
338 16 50       93 $_[1] = URI->new_abs( $_[1], $base )
339             if $base;
340 16         5119 $fetcher->get($_[1]);
341             }
342              
343             sub update_html {
344 389     389 1 1201 my ($self,$src) = @_;
345              
346             # Restore an existing document (in case we are coming back from
347             # another page).
348 389         830 my $res = $self->{res};
349 389 100       2341 if(my $doc = $document{$res}) {
350 44         193 $self->document($doc);
351 44         494 $self->{form} = ($self->{forms} = $doc->forms)->[0];
352 44         10562 return $res;
353             }
354              
355 345         612 my $life_raft = $self;
356 345         1363 weaken($self);
357              
358 345         2291 $self->document($document{$res} = my $tree = new HTML::DOM
359             response => $res,
360             weaken_response => 1,
361             cookie_jar => $self->cookie_jar);
362              
363 345     0   107351 $tree->error_handler(sub{$self->warn($@)});
  0         0  
364              
365             $tree->default_event_handler_for( link => sub {
366 5     5   197 my $link = shift->target;
367 5   66     60 ($self->find_target($link->target)||$self)
368             ->get($link->href)
369 345         10177 });
370             $tree->default_event_handler_for( submit => sub {
371 2     2   74 my $form = shift->target;
372 2   33     16 ($self->find_target($form->target)||$self)
373             ->request($form->make_request);
374 345         8018 });
375              
376 345 100       14184 if(%{$script_handlers{$self}}) {
  345         2010  
377 48         188 my $script_type = $res->header(
378             'Content-Script-Type');
379             defined $script_type or $tree->elem_handler(meta =>
380             sub {
381 1     1   1202 my($tree, $elem) = @_;
382 26     26   185 no warnings 'uninitialized';
  26         71  
  26         47923  
383 1 50       27 return unless lc $elem->attr('http-equiv')
384             eq 'content-script-type';
385 0         0 $script_type = $elem->attr('content');
386 48 100       3293 });
387              
388             $tree->elem_handler(script => sub {
389 20 100   20   51843 return unless $scriptable{$self};
390 18         44 my($tree, $elem) = @_;
391              
392 18         109 my $lang = $elem->attr('type');
393 18 50       6879 defined $lang
394             or $lang = $elem->attr('language');
395 18 50       178 defined $lang or $lang = $script_type;
396              
397 18         26 my $uri;
398 18         44 my($inline, $code, $line) = 0;
399 18 100       53 if($uri = $elem->attr('src')) {
400 8         106 my $res = _fetch_url($self, $uri);
401 8 50       41 $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 8         99 my $cs = {
419             map @$_,
420             split_header_words $res->header(
421             'Content-Type'
422             )
423             }->{charset};
424              
425 8   50     1849 $code = decode $cs||$elem->charset
426             ||$tree->charset||'latin1',
427             $res->decoded_content(charset=>'none');
428            
429            
430 8         8077 $line = 1;
431             }
432             else {
433 10   100     256 $code = ($elem->firstChild||return)->data;
434 9         397 ++$inline;
435 9         49 $uri = $self->uri;
436 9 100       252 if(defined(
437             my $offset = $elem->content_offset
438             )) {
439 8         70 $line = _line_no(
440             $src,$elem->content_offset
441             );
442             }
443 1         10 else { $uri .= " (generated HTML)" }
444             };
445 17 50       293 length $code or return; # optimisation
446            
447 17         84 my $h = $self->_handler_for_lang($lang);
448 17 100       166 $h && $h->eval($self, $code,
449             $uri, $line, $inline);
450 17 100       584 $@ and $self->warn($@);
451 48         2426 });
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 48         1534 });
459              
460             $tree->event_attr_handler(sub {
461 17 100   17   14928 return unless $scriptable{$self};
462 15         47 my($elem, $event, $code, $offset) = @_;
463 15         72 my $lang = $elem->attr('language');
464 15 50       171 defined $lang or $lang = $script_type;
465              
466 15         91 my $uri = $self->uri;
467 15 50       407 my $line = defined $offset ? _line_no(
468             $src, $offset
469             ) : undef;
470              
471 15         55 local *@;
472 15 100       54 if(my $h = $self->_handler_for_lang($lang))
473             {
474 14         82 my $ret = $h->event2sub(
475             $self,$elem,$event,$code,$uri,$line
476             );
477 14 100       735 $@ and $self->warn($@);
478 14         76 return $ret;
479             }
480 48         1951 });
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 345         3953 });
488              
489 345 100       11587 if($self->{Scripter_i}){
490             $tree->elem_handler(img => my $img_cb = sub {
491 11 100   11   4123 return unless defined (my $src = $_[1]->attr('src'));
492 8         111 my $res = _fetch_url($self, $src);
493 8 100       53 defined $self->{Scripter_ih} &&
494             $self->{Scripter_ih}($self,$_[1],$res);
495 8         57 });
496             $tree->elem_handler(input => sub {
497 3 100   3   3167 return unless $_[1]->type eq 'image';
498 2         81 goto &$img_cb;
499 8         227 });
500             $tree->default_event_handler(sub {
501 7 50   7   6143 return unless (my $event = shift)->type eq 'DOMAttrModified';
502 7 50       45 return unless (my $target = target $event)->tag=~/^i(mg|nput)\z/;
503 7 100 100     117 return if $1 eq 'nput' && $target->type ne 'image';
504 5         102 &$img_cb(undef, $target);
505 8         252 });
506             }
507              
508             $tree->defaultView(
509 345         2060 $self
510             );
511 345         6987 $tree->event_parent($self);
512 345         5878 $tree->set_location_object($self->location);
513              
514             $tree->elem_handler(iframe => my $frame_handler = sub {
515 40     40   47346 my ($doc,$elem) = @_;
516 40         229 my $subwin = $self->clone->clear_history(1);
517 40 100       828 if(defined(my $name = attr $elem 'name')) {
518 16         283 name $subwin $name
519             }
520 40         570 $elem->contentWindow($subwin);
521 40         399 $subwin->_set_parent(my $parent = $doc->defaultView);
522 40 100       219 length(my $src = $elem->src) or return;
523 17         624 $subwin->get(new_abs URI $src, $parent->base);
524 345         6322 });
525 345         11035 $tree->elem_handler(frame => $frame_handler);
526              
527             # Find out the encoding:
528 345         11027 my $cs = {
529             map @$_,
530             split_header_words $res->header('Content-Type')
531             }->{charset};
532 345 100 50     32872 $cs or $res->can('content_charset')
      50        
533             and $cs = (
534             $LWP::UserAgent::VERSION <= 5.834 && local *_,
535             $res->content_charset
536             );
537 345   100     257505 $tree->charset($cs||'iso-8859-1');
538              
539             # banana
540 345         7560 $self->{form} = undef;
541 345         2728 $self->{forms} = $tree->forms;
542              
543 345 100       23344 $tree->write(defined $cs ? decode $cs, $src : $src);
544 345         340038 $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 345         133392 $self->trigger_event('load', target => $tree);
553              
554             # banana
555 345   66     23148 $self->{form} ||= $self->{forms}[0];
556              
557 345         87962 return $self->{res};
558             }
559              
560             # Not an override, but used by update_html
561             sub _handler_for_lang {
562 59     59   161 my ($self,$lang) = @_;
563 59 100       186 if(defined $lang) {
564 35         76 while(my($lang_re,$handler) = each
  40         643  
565             %{$script_handlers{$self}}) {
566 34 100       150 next if $lang_re eq 'default';
567 29         424 $lang =~ $lang_re and
568             # reset iterator:
569 29 50       413 keys %{$script_handlers{$self}},
570             return $handler;
571             }
572             }
573 30   66     365 return $script_handlers{$self}{default} || ();
574             }
575              
576             # Not an override, but used by update_html
577             sub _line_no {
578 23     23   96 my ($src,$offset) = @_;
579 23 50       85 defined $offset or Carp::cluck;
580 23         592 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   1123 my $self = shift;
600 18         35 my @links;
601 18 100       88 if (my $doc = $self->document) {
602             tie @links, WWW'Scripter'Links:: =>
603             HTML::DOM::NodeList::Magic->new(
604 166         8449 sub { grep {
605 18     18   418 my $tag = tag $_;
606 26     26   193 no warnings 'uninitialized';
  26         72  
  26         86400  
607 166 100 66     1661 exists $link_tags{$tag}
      100        
608             and defined $_->attr($link_tags{$tag})
609             and $tag ne 'meta'
610             || lc $_->attr('http-equiv') eq 'refresh'
611 17         458 } $doc->descendants }, $doc
612             );
613             }
614             # banana
615 18         70 $self->{links} = \@links;
616 18         58 $self->{_extracted_links} = 1;
617              
618 18         55 return;
619             }
620              
621             sub _extract_images {
622 1     1   26 my $doc = (my $self= shift)->document;
623             my $list = HTML::DOM::NodeList::Magic->new(
624 2     2   41 sub { grep tag $_ =~ /^i(?:mg|nput)\z/,
625             $doc->descendants },
626 1         17 $doc
627             );
628 1         39 tie my @images, WWW'Scripter'Images:: => $list;
629              
630             # banana
631 1         5 $self->{images} = \@images;
632 1         3 $self->{_extracted_images} = 1;
633              
634 1         4 return;
635             }
636              
637             sub back {
638 30     30 1 5979 shift->{page_stack}->go(-1)
639             }
640              
641             sub submit {
642 7 100   7 1 1352 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         8 my $go_for_it;
646             (my $form = $_[0]->current_form)->trigger_event(
647             'submit',
648 3     3   96 submit_default => sub { ++$go_for_it }
649 4         65 );
650 4 100 33     122 $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         31 }
658             }
659              
660             sub base {
661 539     539 1 3885 my $self = shift;
662 539   100     2592 my $base = ($self->document || return SUPER'base $self @_)->base;
663 515 100 100     413107 if($base eq 'about:blank' and (my $parent = $self->parent) != $self) {
664 53         181 return $parent->base;
665             }
666 462 50       3111 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 860 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         6 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         4 my $input;
680 2         9 my $form = $self->current_form;
681 2         21 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     239 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         4 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     7 ),
706             $continue || return;
707              
708 2         4 my $go_for_it;
709             $form->trigger_event(
710             'submit',
711 1     1   36 submit_default => sub { ++$go_for_it }
712 2         16 );
713 2 100 33     87 $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         17 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 368     368 1 5867 my $self = shift;
785 368   66     3177 my $loc = $self->{Scripter_loc} ||= WWW::Scripter::Location->new(
786             $self
787             );
788 368 50       9606 $loc->href(@_) if @_;
789 368         2386 $loc;
790             }
791              
792             sub navigator {
793 3     3 1 12 my $self = shift;
794 3   33     37 $navi{$self} ||=
795             new WWW::Scripter::Navigator:: $self;
796             }
797              
798             sub screen {
799 1     1 1 3 my $self = shift;
800 1   50     35 $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 1730 my $doc = shift->document;
809 15         144 my $time = time;
810 15         30 my ($code, $ms) = (shift,shift);
811 15         58 $ms /= 1000;
812 15   100     127 my $t_o = $timeouts{$doc}||=[];
813 15         56 $$t_o[my $id = @$t_o] =
814             [$ms+$time, $code, @_];
815 15         46 return $id;
816             }
817              
818             sub clearTimeout {
819 1     1 1 4 delete $timeouts{shift->document}[shift];
820 1         8 return;
821             }
822              
823             sub setInterval {
824 5     5 1 45 my $doc = shift->document;
825 5         51 my $time = time;
826 5         10 my ($code, $ms) = (shift,shift);
827 5         13 $ms /= 1000;
828 5   100     32 my $t_o = $timers{$doc}||=[];
829 5         17 $$t_o[my $id = @$t_o] =
830             [$ms+$time, $code, @_];
831 5         20 return $id;
832             }
833              
834             sub clearInterval {
835 5     5 1 30 delete $timers{shift->document}[shift];
836 5         84 return;
837             }
838              
839             sub open {
840 25     25 1 2792 my($self,$url,$target,undef,$replace) = @_;
841 25 100       157 $target
842             = $self->find_target(defined $target ? $target : '_blank');
843 25 100 100     191 if(defined $url and length $url) {
    100          
844 14 50       60 if(my $base = $self->base) {
845 14         140 require URI;
846 14         69 $url = URI->new_abs( $url, $base );
847             }
848 14   66     7292 $target||=$self->top;
849 14 100       82 $replace
850             ? $target->location->replace($url)
851             : $target->get($url);
852 14         78 $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         23 (my $ret = $self->top)->{page_stack}->_add();
858 5         18 _initial_page($ret);
859 5         20 $ret;
860             }
861             else {
862             # open("") with existing window; do nothing
863 6         26 $target
864             }
865             }
866              
867             sub close {
868 6 100   6 1 1825 if(my $g = $_[0]{Scripter_g}) {
869 5         29 $g->detach($_[0]);
870             }
871             else {
872 1         6 $_[0]->history->go(-1);
873             }
874             _:
875 6         22 }
876              
877             sub focus {
878 3 50   3 1 17 my $g = $_[0]{Scripter_g} or return;
879 3         11 $g->bring_to_front(shift);
880 3         9 return;
881             }
882              
883             sub blur {
884 3 50   3 1 13 my $g = $_[0]{Scripter_g} or return;
885 3         11 my($maybe_self,$next) = $g->windows;
886 3 100       11 $next or return;
887 2 100       9 $maybe_self == $_[0] or return;
888 1         6 $g->bring_to_front($next);
889 1         5 return;
890             }
891              
892              
893 684     684 1 7460 sub history { $_[0]{page_stack} }
894              
895             sub frames {
896 188     188 1 14794 my $doc = $_[0]->document;
897 188   100     3992 my $frames = $frames{$doc||''} # the ||'' is for non-HTML docu-
      66        
898             ||= WWW::Scripter'Frames->new( $_[0], $doc ); # ments, which all share
899 188 100       935 wantarray ? @$frames : $frames # an empty frames
900             } # collection
901              
902 1     1 1 10 sub window { $_[0] }
903             *self = *window;
904 2     2 1 9 sub length { $frames{$_[0]->document}->length }
905              
906             sub top {
907 15     15 1 249 my $self = shift;
908 15 100       95 $$self{Scripter_t} || do {
909 6         13 my $parent = $self;
910 6         12 while() {
911 11 100       67 $$parent{Scripter_pa} or
912             weaken( $$self{Scripter_t} = $parent), last;
913 5         12 $parent = $$parent{Scripter_pa};
914             }
915 6         42 $$self{Scripter_t}
916             };
917             }
918              
919             sub parent {
920 244     244 1 1143 my $self = shift;
921 244 100       2255 $$self{Scripter_pa} || $self;
922             }
923              
924 40     40   707 sub _set_parent { weaken( $_[0]{Scripter_pa} = $_[1] ) }
925              
926             sub name {
927 64     64 1 111 my $self = shift;
928 64         144 my $old = $$self{Scripter_nm};
929 64 100       227 $$self{Scripter_nm} = $_[0] if @_;
930 64         262 $old;
931             }
932              
933 0     0 1 0 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 1000750 my $time = time;
961 60         157 my $self = shift;
962 60         351 local *_;
963 60         174 my $doing_timers_now;
964             my $jh;
965 60         515 for my $timers(\%timeouts, \%timers) {
966 120   100     815 my $t_o = $$timers{$self->document}||next;
967 85         1623 for my $id(0..$#$t_o) {
968 118 50       1002 next unless $_ = $$t_o[$id];
969 26     26   221 no warnings 'uninitialized';
  26         72  
  26         16090  
970 118         329 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     5138 ? eval { $$_[1]->(@$_[2..$#$_]) }
  58 100 66     4874  
    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         2940 } continue { ++$doing_timers_now }
986 60         286 $_->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         2579 }
993              
994             sub count_timers {
995 36     36 1 2363 my $self = shift;
996 36         63 my $count;
997 36         143 for(\%timeouts, \%timers) {
998 72 100       326 if(my $t_o = $$_{$self->document}) {
999             #use DDS; Dump [map $_&&[map "$_", @$_], @$t_o];
1000 62         677 for my $id(0..$#$t_o) {
1001 38 50       113 next unless $$t_o[$id];
1002 38         140 ++$count
1003             }
1004             }
1005             }
1006 36 100 66     228 sum $count||(), map $_->count_timers, $self->frames or 0;
1007             }
1008              
1009             sub wait_for_timers {
1010 3     3 1 22 my($self, %args) = @_;
1011 3 100       18 my $start_time = time if $args{max_wait};
1012 3   100     22 my $interval = $args{interval} || .1;
1013 3   100     20 my $min = $args{min_timers} || 0;
1014 3         16 $self->check_timers;
1015 3   100     10 while(
      66        
1016             $self->count_timers > $min
1017             and !$args{max_wait} || time-$start_time < $args{max_wait}
1018             ) {
1019 22         3111647 select(undef,undef,undef,$interval);
1020 22         344 $self->check_timers;
1021             }
1022             _:
1023 3         92 }
1024              
1025             sub window_group {
1026 47     47 1 2458 my $old = (my $self = shift)->{Scripter_g};
1027 47 100       208 @_ and weaken($self->{Scripter_g} = shift);
1028 47         152 $old
1029             }
1030              
1031             sub find_target {
1032 49     49 1 1292 my $self = shift;
1033 49         103 my $name = shift;
1034 26     26   164 no warnings 'uninitialized';
  26         67  
  26         15794  
1035 49 100 66     286 if(!CORE::length $name and my $doc = document $self) {
1036 10 100       220 if(my $base_elem = $doc->look_down(_tag => 'base', target => qr)(?:\)))){
1037 1         147 $name = $base_elem->attr('target');
1038             }
1039             }
1040 49 100       16414 CORE::length $name or return $self;
1041 40 100       170 if($name =~ /^_[Bb][Ll][Aa][Nn][Kk]\z/) {
1042 11 100       43 if(my $g = $$self{Scripter_g}) {
1043 4         24 attach $g my $neww = $self->clone->clear_history(1);
1044 4         25 return $neww;
1045             }
1046 7         28 return undef;
1047             }
1048 29 100       134 $name =~ /^_[Ss][Ee][Ll][Ff]\z/ and return $self;
1049 28 100       127 $name =~ /^_[Pp][Aa][Rr][Ee][Nn][Tt]\z/ and return $self->parent;
1050 26 100       125 $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         55 my $current_ancestor = $self;
1055 24         38 my $prev_ancestor;
1056 24         41 while() {
1057 30 100       98 $current_ancestor->name eq $name and return $current_ancestor;
1058 29 100       125 my $next_level = [
1059             $prev_ancestor
1060             ? grep $_ != $prev_ancestor, $current_ancestor->frames
1061             : $current_ancestor->frames
1062             ];
1063 29         2388 while($next_level) {
1064 36         550 my $tmp = $next_level; $next_level = undef;
  36         55  
1065 36         203 for(@$tmp) {
1066 18 100       53 if($_->name eq $name) { return $_ }
  11         78  
1067 7         28 push @$next_level, $_->frames;
1068             }
1069             }
1070 18         34 $prev_ancestor = $current_ancestor;
1071 18         64 $current_ancestor = $current_ancestor->parent;
1072 18 100       69 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       61 my $g = $$self{Scripter_g} or return undef;
1078 8   66     64 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 4         19 $$named{$name} && ${$$named{$name}}->window_group
1082             ? ${$$named{$name}}
1083 8 100 100     162 : do {
1084 4         19 attach $g my $neww = $self->clone->clear_history(1);
1085 4         8 weaken(${$$named{$name}} = $neww);
  4         24  
1086 4         24 $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 26     26   186 no strict 'refs';
  26         65  
  26         25460  
1102             my $full_meth= "HTML::DOM::EventTarget::$meth";
1103             *$meth = sub {
1104 763     763   176963 shift->response->$full_meth(@_);
1105             }
1106             }
1107              
1108              
1109             # ------------- Image Hooks -------------- #
1110              
1111             sub fetch_images {
1112 5     5 1 920 my $old = (my $self = shift)->{Scripter_i};
1113 5 100       22 @_ and $self->{Scripter_i} = shift;
1114 5         24 $old
1115             }
1116              
1117             sub image_handler {
1118 6     6 1 938 my $old = (my $self = shift)->{Scripter_ih};
1119 6 100       21 @_ and $self->{Scripter_ih} = shift;
1120 6         21 $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 14     14 1 6632 my ($self, $plugin, @opts) = (shift, shift, @_);
1140 14   100     70 my $plugins = $self->{plugins} ||= {};
1141 14         43 $plugin = _plugin2module($plugin);
1142 14 50       55 return $plugins->{$plugin} if $self->{cloning};
1143 14 100       41 if(exists $plugins->{$plugin}) {
1144 2 100       14 $plugins->{$plugin}->options(@opts) if @opts;
1145             }
1146             else {
1147 12         49 (my $plugin_file = $plugin) =~ s-::-/-g;
1148 12         100 require "$plugin_file.pm";
1149 12         16948 $plugins->{$plugin} = $plugin->init($self, \@opts);
1150 12 100       120 $plugins->{$plugin}->options(@opts) if @opts;
1151             }
1152 13         101 $plugins->{$plugin};
1153             }
1154              
1155             sub plugin {
1156 5     5 1 13 my $self = shift;
1157 5         14 my $plugin = _plugin2module(shift);
1158 5 50 50     50 return exists $self->{plugins}{$plugin}
1159             ? $self->{plugins}{$plugin} || 1 : 0;
1160             }
1161              
1162             sub _plugin2module { # This is NOT a method
1163 19     19   31 my $name = shift;
1164 19 100       80 return $name if $name =~ /::/;
1165 2         8 $name =~ s/-/::/g;
1166 2         12 return __PACKAGE__."::Plugin::$name";
1167             }
1168              
1169             sub _clone_plugins {
1170 59     59   118 my $self = shift;
1171 59 100       338 return unless $self->{plugins};
1172 1         2 my $plugins = $self->{plugins} = { %{$self->{plugins}} };
  1         6  
1173 1         7 while ( my($pn,$po) = each %$plugins ) {
1174             # plugin name, plugin object
1175 4 100 100     65 next unless $po && defined blessed $po && $po->can('clone');
      100        
1176 1         5 $plugins->{$pn} = $po->clone($self);
1177             }
1178             }
1179              
1180             sub dom_enabled {
1181 8     8 1 34 my $old = (my $self = shift)->{Scripter_dumb};
1182 8 50       32 defined $old or $old = 1; # default
1183 8 50       30 if(@_) {{
  8         31  
1184 8         13 $$self{Scripter_dumb} = !!$_[0]; # We don’t want undef
1185             }} # resetting it.
1186             $old
1187 8         22 }
1188              
1189             sub scripts_enabled {
1190 356     356 1 43596 my $old = $scriptable{my $self = shift};
1191 356 50       17952 defined $old or $old = 1; # default
1192 356 100       1101 if(@_) {{
  6         32  
1193 6         10 $scriptable{$self} = !!$_[0]; # We don’t want undef resetting it.
1194 6   50     27 ($self->document ||last) ->event_listeners_enabled(shift) ;
1195             }}
1196             $old
1197 356         1438 }
1198             # used by HTML::DOM::EventTarget:
1199             *event_listeners_enabled = *scripts_enabled;
1200              
1201             sub script_handler {
1202 20     20 1 366 my($self,$key) = (shift,shift);
1203 20         117 my $old = $script_handlers{$self}{$key};
1204 20 50       125 @_ and $script_handlers{$self}{$key} = shift;
1205 20         54 $old
1206             }
1207              
1208             sub class_info {
1209 5     5 1 2011 my $self = shift;
1210 5 50       19 @_ and push @{ $class_info{$self} }, shift;
  0         0  
1211 5 50       23 @{ $class_info{$self} } if defined wantarray;
  5         57  
1212             }
1213              
1214             # ------------- Miss Elaine E. S. ------------- #
1215              
1216             # This function is exported upon request.
1217             sub abort {
1218 26     26   171 no warnings 'exiting';
  26         61  
  26         9084  
1219 1     1 0 505 last Scripter_ABORT;
1220             }
1221              
1222             sub forward {
1223 14     14 1 2447 my $self = shift;
1224 14         50 $self->{page_stack}->go(1);
1225             }
1226              
1227             sub clear_history {
1228 66     66 1 4051 my $self = shift;
1229 66         335 $$self{'page_stack'}->_clear(@_);
1230 66 100       286 if (shift) {
1231 61         304 $self->_reset_page;
1232              
1233             # list of keys taken from _update_page
1234 61         1409 delete $self->{$_} for qw[ req redirected_url res status base ct
1235             uri last_uri content ];
1236 61         250 _initial_page($self);
1237             }
1238 66         281 return $self;
1239             }
1240              
1241             sub max_docs {
1242 6     6 1 96 my $self= shift;
1243 6 100       30 defined wantarray and my $old = $self->stack_depth+1;
1244 6 100       34 $self->stack_depth(shift()-1) if @_;
1245 6         29 $old;
1246             }
1247              
1248             sub max_history {
1249 11     11 1 26 my $old = (my $self = shift)->{Scripter_max_hist};
1250 11 100       44 @_ and $self->{Scripter_max_hist} = shift;
1251 11         25 $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 26     26   728 BEGIN { *fieldhashes = *WWW::Scripter::fieldhashes }
1264 26     26   170 use HTML::DOM::Interface qw 'NUM STR READONLY METHOD VOID';
  26         50  
  26         2673  
1265 26     26   160 use Scalar::Util 'weaken';
  26         109  
  26         50212  
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   555 my ($pack,$mech) = @_;
1322 136         639 my $self = bless [[]], $pack;
1323 136         315 weaken(${$w{$self}} = $mech);
  136         3717  
1324 136         2901 $index{$self} = 0;
1325 136         988 $res{$self} = [];
1326 136         857 $self
1327             }
1328              
1329             sub _add {
1330 226     226   1113 my $self = shift;
1331 226 100       997 if(defined $$self[-1][0]) { # if there is no ‘undef’ entry
1332 126         1260 splice @$self, ++$index{$self};
1333 126         586 push @$self, \@_;
1334 126 100       541 $_[1] and push(@{$res{$self}}, $_[1]), _clean($self,1);
  121         1107  
1335             }
1336             else {
1337 100         299 $$self[-1] = \@_;
1338 100 50       445 push @{$res{$self}}, $_[1] if $_[1];
  100         1152  
1339             }
1340             }
1341              
1342             # Called when browsing to a stale history entry and also by
1343             # location->replace
1344             sub _replace {
1345 14     14   27 my $self = shift;
1346 14 100       52 if(defined $$self[-1][0]) { # if browsing has occurred
1347 13         44 $$self[$index{$self}] = \@_;
1348 13 50       51 $_[1] and push(@{$res{$self}}, $_[1]), _clean($self);
  13         74  
1349             }
1350             else {
1351 1         3 $$self[-1] = \@_;
1352 1 50       5 push @{$res{$self}}, $_[1] if $_[1];
  1         7  
1353             }
1354             }
1355              
1356             sub _delete_res {
1357 1     1   162 delete $_[0][$index{$_[0]}][1];
1358             }
1359              
1360             sub _clear { # called by Scripter->clear_history
1361 66     66   111 my $self = shift;
1362 66 100       392 @$self = shift() ? undef : $$self[$index{$self}];
1363 66         269 $index{$self} = 0;
1364             }
1365              
1366             sub length {
1367 23     23   677 scalar @{+shift}
  23         219  
1368             }
1369              
1370             sub index { # ~~~ We can probably make this modifiable later.
1371 20     20   117 $index{+shift}
1372             }
1373              
1374             sub go {
1375 51     51   76 my $self = shift;
1376 51 100       245 if(0==$_[0]) {
1377 2         2 ${$w{$self}}->reload;
  2         19  
1378             }
1379             else {
1380 49         142 my $new_pos = $index{$self}+shift;
1381 49 100 100     306 $new_pos < 0 || $new_pos > $#$self and return;
1382 45         150 $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         82 my $entry = $$self[$new_pos];
1390 45 100       122 if(defined $$entry[1]) { # response
1391 36         48 ${$w{$self}}->_update_page(@$entry)
  36         177  
1392             }
1393             else {
1394 9         12 local(my $w = ${$w{$self}})->{Scripter_replace} = 1;
  9         46  
1395 9         34 $w->request($$entry[0]);
1396             }
1397             }
1398 47         140 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   210 my $self = shift;
1406              
1407 4         17 my $index = $index{$self}++;
1408 4         7 my($req,$res) = @{$$self[$index]}[0,1];
  4         10  
1409              
1410             # count future entries that share the same doc
1411 4         5 my $to_delete;
1412 4         13 for($index+1..$#$self) {
1413 2 100 50     12 ($$self[$_][1]||0) == $res ? ++$to_delete : last;
1414             }
1415              
1416             # replace those future entries with the new item
1417 4   100     28 splice @$self, $index+1, $to_delete||0, [ $req, $res, $_[2], @_ ];
1418              
1419 4         10 _clean($self);
1420              
1421 4         10 return;
1422             }
1423              
1424             sub _clean {
1425 138     138   289 my($self, $check_max_hist) = @_;
1426 138 100       438 if($check_max_hist) {
1427 121         211 my $max = (my $w = ${$w{$self}})->{Scripter_max_hist};
  121         629  
1428 121 100 100     644 if($max && @$self > $max) {
1429 10         28 my $diff = @$self-$max;
1430 10         30 $index{$self} -= $diff;
1431 10         31 splice @$self, 0, $diff;
1432             }
1433             }
1434 138         335 my $max = ${$w{$self}}->stack_depth + 1;
  138         1289  
1435 138         1255 my $res = $res{$self};
1436 138         236 my %res;
1437 138         390 for(@$self) {
1438 550 100       12522 defined $$_[1] and $res{0+$$_[1]}++
1439             }
1440 138 50       463 if($max) { # ~~~ It may be more efficient if, instead of searching for
1441 138         222 my @res; # duplicates here, we scan for the ones we know we’ve added
1442             my %seen; # in _add and _replace.
1443 138         336 for(reverse @$res) {
1444 534         824 my $refaddr = 0+$_;
1445 534 100 100     8079 unshift @res, $_ if exists $res{$refaddr} && !$seen{$refaddr}++;
1446             }
1447 138 100       1564 @$res = @res, return unless @res > $max;
1448 13         23 my $diff = @res-$max;
1449 13         13 my %to_delete;
1450 13         54 @to_delete{map 0+$_, splice @res, 0,$diff}=();
1451 13         34 @$res = @res;
1452 13         27 for(@$self) {
1453 50 100       104 next unless defined $$_[1];
1454 39 100       679 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 641     641   995 my $self = shift;
1464 641 100       3564 $$self[$index{$self}][2] || ${$w{$self}}->uri;
  163         1215  
1465             }
1466              
1467             # ~~~
1468              
1469             # ------------- Location object ------------- #
1470              
1471             package WWW'Scripter'Location;
1472              
1473 26     26   228 use HTML::DOM::Interface qw'STR METHOD VOID';
  26         64  
  26         2571  
1474 26     26   175 use Scalar::Util 'weaken';
  26         47  
  26         7994  
1475              
1476 26     26   232 use overload fallback => 1, '""' => sub{${+shift}->history->_uri};
  26     613   53  
  26         379  
  613         6008  
  613         2410  
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   368 my $class = shift;
1496 134         518 weaken (my $mech = shift);
1497 134         512 my $self = bless \$mech, $class;
1498 134         1679 $self;
1499             }
1500              
1501             sub hash {
1502 5     5   22 my $loc = shift;
1503 5         21 my $old = (my $uri = $$loc->history->_uri)->fragment;
1504 5 100       97 $old = "#$old" if defined $old;
1505 5 100       17 if (@_){
1506 1         16 shift() =~ /#?(.*)/s;
1507 1         12 (my $uri_copy = $uri->clone)->fragment($1);
1508 1 50       97 $uri_copy->eq($uri) or $$loc->get($uri_copy);
1509             }
1510 5 100       72 $old||''
1511             }
1512              
1513             sub host {
1514 2     2   1139 my $loc = shift;
1515 2         10 my $uri = $$loc->history->_uri;
1516 2 100       24 if (@_) {
1517 1         12 (my $uri = $uri->clone)->port("");
1518 1         95 $uri->host_port(shift);
1519 1         177 $$loc->get($uri);
1520             }
1521 2 50       60 defined wantarray ? $uri->host_port : ()
1522             }
1523              
1524             sub hostname {
1525 2     2   1133 my $loc = shift;
1526 2         17 my $uri = $$loc->history->_uri;
1527 2 100       23 if (@_) {
1528 1         6 (my $uri = $uri->clone)->host(shift);
1529 1         101 $$loc->get($uri);
1530             }
1531 2 50       18 defined wantarray ? $uri->host : ()
1532             }
1533              
1534             sub href {
1535 13     13   4779 my $loc = shift;
1536 13 100       85 my $old = $$loc->history->_uri->as_string if defined wantarray;
1537 13 100       551 if (@_) {
1538 4         27 $$loc->get(shift);
1539             }
1540 13         152 $old;
1541             }
1542              
1543 1     1   3 sub assign { ${$_[0]}->get($_[1]); () }
  1         6  
  1         11  
1544              
1545             sub pathname {
1546 2     2   522 my $loc = shift;
1547 2         7 my $uri = $$loc->history->_uri;
1548 2 100       23 if (@_) {
1549 1         12 (my $uri = $uri->clone)->path(shift);
1550 1         49 $$loc->get($uri);
1551             }
1552 2 50       113 defined wantarray ? $uri->path : ()
1553             }
1554              
1555             sub port {
1556 2     2   937 my $loc = shift;
1557 2         13 my $uri = $$loc->history->_uri;
1558 2 100       24 if (@_) {
1559 1         7 (my $uri = $uri->clone)->port(shift);
1560 1         83 $$loc->get($uri);
1561             }
1562 2 50       20 defined wantarray ? $uri->port : ()
1563             }
1564              
1565             sub protocol {
1566 2     2   754 my $loc = shift;
1567 2         9 my $uri = $$loc->history->_uri;
1568 2 100       158 if (@_) {
1569 1         11 shift() =~ /(.*):?/s;
1570 1         7 (my $uri = $uri->clone)->scheme($1);
1571 1         4046 $$loc->get($uri);
1572             }
1573 2 50       18 defined wantarray ? $uri->scheme . ':' : ()
1574             }
1575              
1576             sub search {
1577 3     3   9 my $loc = shift;
1578 3         13 my $uri = $$loc->history->_uri;
1579 3 100       49 if (@_){
1580 2         10 shift() =~ /(\??)(.*)/s;
1581             (
1582 2 50 33     2605 my $uri_copy = $uri->clone
1583             )->query(
1584             $1||length$2 ? "$2" : undef
1585             );
1586 2         154 $$loc->get($uri_copy);
1587             }
1588 3 100       22 return unless defined wantarray;
1589 2         9 my $q = $uri->query;
1590 2 50       40 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         12  
1597             }
1598             sub replace { # args (URL)
1599 5     5   32 my $mech = ${+shift};
  5         14  
1600 5         25 local $$mech{Scripter_replace } = 1;
1601 5         21 $mech->get(shift);
1602             }
1603              
1604              
1605             # ------------- Navigator object ------------- #
1606              
1607             package WWW::Scripter::Navigator;
1608              
1609 26     26   35714 use HTML::DOM::Interface qw'STR READONLY METHOD BOOL';
  26         70  
  26         1987  
1610 26     26   174 use Scalar::Util 'weaken';
  26         64  
  26         6022  
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 26         3069 use constant 1.03 our $_const = {
1626             mech => 0,
1627             name => 1,
1628             vers => 2,
1629             cnam => 3,
1630             plat => 4,
1631 26     26   157 };
  26         961  
1632 26     26   141 { no strict; delete @{__PACKAGE__.::}{_const => keys %$_const} }
  26         157  
  26         15227  
1633              
1634             sub new {
1635 3     3   23 weaken((my $self = bless[],pop)->[mech] = pop);
1636 3         19 $self;
1637             }
1638              
1639             sub appName {
1640 4     4   585 my $self = shift;
1641 4         9 my $old = $self->[name];
1642 4 100       13 defined $old or $old = ref $self->[mech];
1643 4 100       13 @_ and $self->[name] = shift;
1644 4         22 return $old;
1645             }
1646              
1647             sub appCodeName {
1648 3     3   7 my $self = shift;
1649 3         5 my $old = $self->[cnam];
1650 3 100       12 defined $old or $old = ref $self->[mech];
1651 3 100       10 @_ and $self->[cnam] = shift;
1652 3         13 return $old;
1653             }
1654              
1655             sub appVersion {
1656 3     3   8 my $self = shift;
1657 3         7 my $old = $self->[vers];
1658 3 100 66     17 if(!defined $old and defined wantarray) {
1659 2         6 $old = $self->userAgent;
1660 2 50       174 $old =~ /(\d.*)/s
1661             ? $old = $1
1662             : $old = ref($self->[mech])->VERSION;
1663             }
1664 3 100       17 @_ and $self->[vers] = shift;
1665 3         29 return $old;
1666             }
1667              
1668             sub userAgent {
1669 3     3   19 shift->[mech]->agent;
1670             }
1671              
1672             sub platform {
1673 10     10   418 my $self = shift;
1674 10         21 my $old = $self->[plat];
1675 10 50 33     49 if(!defined $old and defined wantarray) {
1676 10         32 my $ua = $self->[mech]->agent;
1677 26     26   170 no warnings 'uninitialized';
  26         48  
  26         10140  
1678 10 50       413 $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       23 @_ and $self->[plat] = shift;
1692 10         42 return $old;
1693             }
1694              
1695 2     2   9 sub javaEnabled{}
1696             *taintEnabled=*javaEnabled;
1697              
1698 2     2   418 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 26     26   223 use LWP::Protocol;
  26         54  
  26         9003  
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   12925 my($self, $request, $proxy, $arg) = @_;
1721              
1722 24 50       271 if(defined $proxy) {
1723 0         0 return new HTTP::Response 400,,
1724             'The about: protocol does not work with proxies';
1725             }
1726              
1727 24         107 my $url= $request->url;
1728 24         229 my $scheme = $url->scheme;
1729              
1730 24 50       365 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       220 return new HTTP::Response 404,
1736             "Nothing exists at $url" unless $url eq 'about:blank';
1737              
1738 22         357 my $response = new HTTP::Response 200, 'OK', [
1739             Content_Length=>0,
1740             Content_Type =>'text/html',
1741             ];
1742              
1743 22     22   5508 $self->collect($arg, $response, sub {\''});
  22         27484  
1744             }
1745              
1746             # ------------- Link and image lists for Mech ------------- #
1747              
1748             package WWW::Scripter::Links;
1749              
1750 26 50   26   2413 BEGIN { eval "require ".WWW'Scripter'Mech."::Link" or die $@ }
1751              
1752             sub TIEARRAY {
1753 17     17   668 bless \(my $links = pop), shift;
1754             }
1755              
1756             sub FETCH {
1757 51     51   317 my $link = ${$_[0]}->[$_[1]];
  51         208  
1758 51         1025 my $mech_link = bless [], WWW'Scripter'Mech."::Link";
1759 51         369 tie @$mech_link, WWW'Scripter'Link::, $link;
1760 51         7369 $dom_obj{$mech_link} = $link;
1761 51         260 $mech_link;
1762             }
1763 20     20   8183 sub FETCHSIZE { scalar @${$_[0]} }
  20         115  
1764 0     0   0 sub EXISTS { exists ${$_[0]}->links->[$_[1]] }
  0         0  
1765              
1766             package WWW::Scripter::Link;
1767              
1768 51     51   155 sub TIEARRAY { bless \(my $x = $_[1]) }
1769             sub FETCH {
1770 139     139   5823 my $self = shift;
1771 139         339 for(shift) {
1772             return
1773             $_ == 0 ? $$self->tag eq 'meta' # url
1774             ? $$self->attr('content') =~ /^\d+\s*;\s*url\s*=\s*(\S+)/i
1775 139 50       906 ? do { my $url = $1;
  8 100       278  
    100          
    50          
    50          
    100          
    100          
    100          
    100          
1776 8 100       52 $url =~ s/^"(.+)"$/$1/ or $url =~ s/^'(.+)'$/$1/;
1777 8         59 $url }
1778             : undef
1779             : $$self->attr($link_tags{$$self->tag}) :
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 26 50   26   29089 BEGIN { eval "require ".WWW'Scripter'Mech."::Image" or die $@ }
1793              
1794             sub TIEARRAY {
1795 1     1   5 bless \(my $links = pop), shift;
1796             }
1797              
1798             sub FETCH {
1799 7     7   733 my $img = ${$_[0]}->[$_[1]];
  7         24  
1800 7         100 my $mech_img = new WWW'Scripter'Image:: $img;
1801 7         57 $dom_obj{$mech_img} = $img;
1802 7         30 $mech_img;
1803             }
1804 3     3   1467 sub FETCHSIZE { scalar @${$_[0]} }
  3         16  
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   17 sub new { bless \(my $frin = pop) }
1810 6     6   123 sub url { ${$_[0]}->attr('src') }
  6         40  
1811 0     0   0 sub base { ${$_[0]}-ownerDocument->base }
  0         0  
1812 5     5   33 sub name { ${$_[0]}->attr('name') }
  5         17  
1813 5     5   68 sub tag { ${$_[0]}->tag }
  5         18  
1814 5     5   53 sub height { ${$_[0]}->attr('height') }
  5         16  
1815 5     5   48 sub width { ${$_[0]}->attr('width') }
  5         16  
1816 5     5   50 sub alt { ${$_[0]}->attr('alt') }
  5         15  
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 26     26   24892 use HTML::DOM::Collection;
  26         65  
  26         897  
1828 26     26   152 use HTML::DOM::NodeList::Magic;
  26         50  
  26         10100  
1829             our @ISA = "HTML::DOM::Collection";
1830              
1831             {
1832             WWW::Scripter'fieldhash my %w;
1833             my @empty_array;
1834            
1835             sub new {
1836 49     49   380 ; my($pack,$window,$doc) = @_
1837             ; my $ret = $pack->SUPER'new(
1838             $doc
1839             ? HTML::DOM::NodeList::Magic->new(
1840 47     47   1599 sub { $doc->look_down(_tag => qr/^i?frame\z/) },
1841 49 100       548 $doc
1842             )
1843             : HTML'DOM'NodeList->new(\@empty_array)
1844             )
1845 49         1904 ; Scalar'Util'weaken($_) for $doc, $window;
1846 49         446 ; $w{$ret} = \$window;
1847 49         793 ; $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   561 [map $_->contentWindow, @{shift->${\'SUPER::(@{}'}}]
  180         315  
  180         1412  
1855 26     26   171 };
  26         69  
  26         465  
1856              
1857 7   50 7   575 sub FETCH { (shift->SUPER::FETCH(@_)||return)->contentWindow }
1858              
1859              
1860 26     26   27011 !!*!*!!*!*!!*!*!!*!*!!*!*!!*!*!!*!*!!*!*!!*!*!!*!*!!*!*!!*!*!!*!*!!*!
  26         37287  
  26         4138