File Coverage

blib/lib/Win32/CaptureIE.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Win32::CaptureIE;
2              
3 1     1   6832 use 5.006;
  1         3  
  1         34  
4 1     1   6 use strict;
  1         2  
  1         29  
5 1     1   7 use warnings;
  1         12  
  1         297  
6              
7             require Exporter;
8              
9             our @ISA = qw(Exporter);
10              
11             our %EXPORT_TAGS = ( 'default' => [ qw(
12             StartIE
13             QuitIE
14             Navigate
15             PopUp
16             RunJS
17             Wait
18             Refresh
19             GetElement
20             GetAll
21             GetDoc
22              
23             CaptureElement
24             CaptureElements
25             CapturePage
26             CaptureBrowser
27             CaptureRows
28             CaptureThumbshot
29             CaptureArea
30              
31             $IE
32             $Doc
33             $Body
34             $HWND_IE
35             $HWND_Browser
36             $CaptureBorder
37             $PopUp_IE
38             $PopUp_HWND_IE
39             $PopUp_HWND_Browser
40             ) ] );
41              
42             $EXPORT_TAGS{all} = [ map {@$_} values %EXPORT_TAGS ];
43              
44             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
45              
46             our @EXPORT = @{ $EXPORT_TAGS{'default'} };
47              
48             our $VERSION = '1.30';
49             our $IE;
50             our $HWND_IE;
51             our $HWND_Browser;
52             our $Doc;
53             our $Body;
54             our $PopUp_IE;
55             our $PopUp_HWND_IE;
56             our $PopUp_HWND_Browser;
57             our $ProcessPopUps = 0;
58             our $CaptureBorder = 1;
59             our ($MOUSE_x, $MOUSE_y);
60              
61 1     1   1446 use Win32::OLE qw(in valof EVENTS);
  0            
  0            
62             use Win32::Screenshot qw(:all);
63             use POSIX qw(ceil floor);
64             use strict;
65              
66             ##########################################################################
67              
68             # HACK: DocumentComplete event is not fired when refreshing page but
69             # only DownloadComplete event, so if refreshing page we need to wait for
70             # DownloadComplete but not if we are navigating to a page
71             our $refreshing_page = 0;
72              
73             sub StartIE {
74             my %arg = @_;
75              
76             # Open a new browser window and save its' window handle
77             $IE = Win32::OLE->new("InternetExplorer.Application");
78             Win32::OLE->WithEvents($IE,\&EventHandler,"DWebBrowserEvents2");
79             Win32::OLE->Option(Warn => 4);
80             $HWND_IE = $IE->{HWND};
81              
82             # Let's size the window
83             $IE->{height} = $arg{height} || 600;
84             $IE->{width} = $arg{width} || 808;
85             $IE->{visible} = 1;
86              
87             # Show blank page (let the browser create rendering area)
88             Navigate('about:blank');
89              
90             # We need the window on top because we want to get the screen shots
91             Minimize($HWND_IE); Restore($HWND_IE); # this seem to work
92             BringWindowToTop( $HWND_IE );
93              
94             # Find the largest child window, suppose that this is the area where the page is rendered
95             my ($sz, $i) = (0, 0);
96             for ( ListChilds($HWND_IE) ) {
97             next unless $_->{visible};
98             if ( $sz < (($_->{rect}[2]-$_->{rect}[0])*($_->{rect}[3]-$_->{rect}[1])) ) {
99             $sz = (($_->{rect}[2]-$_->{rect}[0])*($_->{rect}[3]-$_->{rect}[1]));
100             $i = $_->{hwnd};
101             }
102             }
103             $HWND_Browser = $i;
104              
105             ($MOUSE_x, $MOUSE_y) = GetCursorPos();
106             SetCursorPos(0, 0);
107             }
108              
109             sub QuitIE () {
110             $IE->Quit();
111             $IE = undef;
112              
113             SetCursorPos($MOUSE_x, $MOUSE_y);
114             }
115              
116             sub Navigate ($) {
117             (defined $PopUp_IE ? $PopUp_IE : $IE)->Navigate($_[0]);
118             Win32::OLE->MessageLoop();
119             GetDoc();
120             }
121              
122             sub PopUp (&) {
123             $ProcessPopUps = 1;
124             &{$_[0]}();
125             Win32::OLE->MessageLoop();
126             GetDoc();
127              
128             # Find the largest child window, suppose that this is the area where the page is rendered
129             $PopUp_HWND_IE = $PopUp_IE->{HWND};
130             my ($sz, $i) = (0, 0);
131             for ( ListChilds($PopUp_HWND_IE) ) {
132             next unless $_->{visible};
133             if ( $sz < (($_->{rect}[2]-$_->{rect}[0])*($_->{rect}[3]-$_->{rect}[1])) ) {
134             $sz = (($_->{rect}[2]-$_->{rect}[0])*($_->{rect}[3]-$_->{rect}[1]));
135             $i = $_->{hwnd};
136             }
137             }
138             $PopUp_HWND_Browser = $i;
139              
140             $ProcessPopUps = 0;
141             }
142              
143             sub RunJS ($) {
144             (defined $PopUp_IE ? $PopUp_IE : $IE)->Navigate("javascript:$_[0]");
145             Wait(1);
146             GetDoc();
147             }
148              
149             sub Wait (;$) {
150             my $time = shift || 1;
151             while ( $time > 0 ) {
152             Win32::OLE->SpinMessageLoop;
153             select undef, undef, undef, 0.1;
154             $time -= 0.1;
155             }
156             }
157              
158             sub Refresh () {
159             $refreshing_page = 1;
160             $IE->Refresh2(3);
161             Win32::OLE->MessageLoop();
162             GetDoc();
163             }
164              
165             sub GetDoc () {
166             $Doc = defined $PopUp_IE ? $PopUp_IE->{Document} : $IE->{Document};
167             $Body = (! $Doc->compatMode || $Doc->compatMode eq 'BackCompat') ? $Doc->{Body} : $Doc->{Body}->{parentNode};
168             }
169              
170             sub GetElement ($) {
171             return $Doc->getElementById($_[0]);
172             }
173              
174             sub GetAll ($;$$) {
175             my $tag = uc shift;
176             my $sub = ref $_[0] eq 'CODE' ? shift : undef;
177             my $idx = shift;
178             my @elements;
179              
180             local $_;
181             for ( my $i = 0 ; $i < $Doc->All->length ; $i++ ) {
182             $_ = $Doc->All($i);
183             push @elements, $_ if $_->tagName eq $tag && (!defined $sub || &$sub( $_ ));
184             last if defined $idx && @elements > $idx;
185             }
186              
187             return defined $idx ? $elements[$idx] : @elements;
188             }
189              
190             #####################################################################
191              
192             sub CaptureRows {
193             my $tab = ref $_[0] ? shift : GetElement(shift);
194             my %rows = map {$_ => 1} ref $_[0] ? @{$_[0]} : @_;
195             return undef if $tab->tagName ne 'TABLE' || !%rows;
196              
197             # temporary disable post processing
198             my $img;
199             {
200             local @Win32::Screenshot::POST_PROCESS = ();
201             $img = CaptureElement($tab);
202             }
203              
204             # skip over CaptureBorder and table border (start on top of 1st row)
205             my $pos = $CaptureBorder + $tab->rows(0)->{offsetTop};
206              
207             for ( my $row = 0 ; $row < $tab->rows->{length} ; $row++ ) {
208             if ( $rows{$row} ) { # we want this row, skip over it
209             $pos += $tab->rows($row)->{offsetHeight};
210             } else { # don't want this one, chop it out of the picture
211             $img->Chop('x'=>0, 'y'=>$pos, 'width'=>0, 'height'=>$tab->rows($row)->{offsetHeight});
212             }
213             }
214              
215             return PostProcessImage( $img );
216             }
217              
218             sub CaptureThumbshot {
219              
220             # that's not a good idea to capture thumbshots of popup windows
221             return if defined $PopUp_IE;
222              
223             GetDoc();
224              
225             # resize the window to set the client area to 800x600
226             $IE->{width} = $IE->{width} + 800-$Body->clientWidth;
227             $IE->{height} = $IE->{height} + 600-$Body->clientHeight;
228              
229             # scrollTo(0, 0)
230             $Body->{scrollTop} = 0;
231             $Body->{scrollLeft} = 0;
232             Win32::OLE->SpinMessageLoop();
233              
234             return CaptureWindowRect($HWND_Browser, $Body->clientLeft, $Body->clientTop, $Body->clientWidth, $Body->clientHeight );
235             }
236              
237             sub CaptureElement {
238             my $e = ref $_[0] ? shift : GetElement(shift);
239             my $args = ProcessArgs(ref $_[0] eq 'HASH' ? shift : {});
240             return CapturePage() if $e->tagName eq 'BODY';
241              
242             GetDoc();
243              
244             my ($px, $py, $sx, $sy, $w, $h);
245              
246             # This is the size of the object including its border
247             $w = $e->offsetWidth;
248             $h = $e->offsetHeight;
249              
250             # Let's calculate the absolute position of the object on the page
251             my $p = $e;
252             while ( $p ) {
253             $px += $p->offsetLeft;
254             $py += $p->offsetTop;
255             $p = $p->offsetParent;
256             }
257              
258             # Expand the area by capture border
259             $px -= ($args->{border_left}||0);
260             $py -= ($args->{border_top}||0);
261             $w += ($args->{border_left}||0) + ($args->{border_right}||0);
262             $h += ($args->{border_top}||0) + ($args->{border_bottom}||0);
263              
264             return CaptureArea($px, $py, $w, $h);
265             }
266              
267             sub ProcessArgs {
268             my $a = shift;
269             my %args;
270              
271             $args{border_left} = exists $a->{border_left} ? $a->{border_left} : exists $a->{border} ? $a->{border} : $CaptureBorder;
272             $args{border_right} = exists $a->{border_right} ? $a->{border_right} : exists $a->{border} ? $a->{border} : $CaptureBorder;
273             $args{border_top} = exists $a->{border_top} ? $a->{border_top} : exists $a->{border} ? $a->{border} : $CaptureBorder;
274             $args{border_bottom} = exists $a->{border_bottom} ? $a->{border_bottom} : exists $a->{border} ? $a->{border} : $CaptureBorder;
275             return \%args;
276             }
277              
278             sub CaptureElements {
279             my @elements = map { ! ref $_ ? GetElement($_) : $_ } grep { ! ref $_ || ref $_ eq 'Win32::OLE' } @_;
280             my $args = ProcessArgs(ref $_[-1] eq 'HASH' ? $_[-1] : {});
281              
282             my ($tlx, $tly, $brx, $bry);
283             my ($px, $py, $sx, $sy, $w, $h);
284              
285             GetDoc();
286              
287             # calculate absolute position on the page for all elements
288             # and get bounding rect
289             for my $e ( @elements ) {
290             my $p = $e;
291              
292             my ($x, $y) = (0, 0);
293             while ( $p ) {
294             $x += $p->offsetLeft;
295             $y += $p->offsetTop;
296             $p = $p->offsetParent;
297             }
298              
299             $tlx = $x if !defined $tlx || $tlx > $x;
300             $tly = $y if !defined $tly || $tly > $y;
301             $brx = $x+$e->offsetWidth if !defined $brx || $brx < $x+$e->offsetWidth;
302             $bry = $y+$e->offsetHeight if !defined $bry || $bry < $y+$e->offsetHeight;
303             }
304              
305             $w = $brx - $tlx + 1;
306             $h = $bry - $tly + 1;
307             $px = $tlx;
308             $py = $tly;
309              
310             $px -= ($args->{border_left}||0);
311             $py -= ($args->{border_top}||0);
312             $w += ($args->{border_left}||0) + ($args->{border_right}||0);
313             $h += ($args->{border_top}||0) + ($args->{border_bottom}||0);
314              
315             return CaptureArea($px, $py, $w, $h);
316             }
317              
318             sub CapturePage {
319             my ($px, $py, $sx, $sy, $w, $h);
320              
321             GetDoc();
322              
323             return CaptureArea(0, 0, $Body->scrollWidth, $Body->scrollHeight);
324             }
325              
326             sub CaptureArea ($$$$) {
327             my ($px, $py, $w, $h) = @_;
328             my ($sx, $sy);
329              
330             $px = 0 if $px < 0;
331             $py = 0 if $py < 0;
332             $w = $Body->{scrollWidth}-$px if $px+$w > $Body->{scrollWidth};
333             $h = $Body->{scrollHeight}-$py if $py+$h > $Body->{scrollHeight};
334              
335             # Scrolls the page so that top of the object is visible at the top of the window.
336             $Body->{scrollTop} = $py - 2;
337             $Body->{scrollLeft} = $px - 2;
338              
339             # The position on the screen is different due to page scrolling and Body border
340             $sx = $px - $Body->scrollLeft + $Body->clientLeft;
341             $sy = $py - $Body->scrollTop + $Body->clientTop;
342              
343             if ( $sx+$w < $Body->clientWidth && $sy+$h < $Body->clientHeight ) {
344              
345             # If the whole object is visible
346             return CaptureWindowRect(defined $PopUp_IE ? $PopUp_HWND_Browser : $HWND_Browser, $sx, $sy, $w, $h );
347              
348             } else {
349              
350             # If only part of it is visible
351             return CaptureAndScroll($px, $py, $w, $h);
352             }
353             }
354              
355             sub CaptureAndScroll ($$$$) {
356             my ($px, $py, $w, $h) = @_;
357             my ($strip, $final, $pw, $ph, $ch, $cw, $maxw, $maxh, $sx, $sy);
358              
359             GetDoc();
360              
361             # Captured area
362             $final = '';
363             $cw = 0;
364             $ch = 0;
365              
366             # We will do the screen capturing in more steps by areas of maximum dimensions $maxw x $maxh
367             $maxw = $Body->clientWidth;
368             $maxh = $Body->clientHeight;
369              
370             for ( my $cnt_x=0 ; $cw < $w ; $cnt_x++ ) {
371              
372             # Scroll to the top and right
373             $Body->{scrollTop} = $px - 2;
374             $Body->{scrollLeft} = $px - 2 + $cnt_x * int($maxw*0.9);
375             Win32::OLE->SpinMessageLoop;
376              
377             $strip = '';
378             $ch = 0;
379              
380             for ( my $cnt_y=0 ; $ch < $h ; $cnt_y++ ) {
381              
382             $Body->{scrollTop} = $px - 2 + $cnt_y * int($maxh*0.9);
383             Win32::OLE->SpinMessageLoop;
384              
385             # Recalculate the position on the screen
386             $sx = $px - $Body->scrollLeft + $Body->clientLeft + $cw;
387             $sy = $py - $Body->scrollTop + $Body->clientTop + $ch;
388              
389             # Calculate the dimensions of the part to be captured
390             $pw = ($px+$cw) - $Body->scrollLeft + $maxw > $maxw ? $maxw - ($px+$cw) + $Body->scrollLeft : $maxw;
391             $pw = $cw + $pw > $w ? $w - $cw : $pw;
392              
393             $ph = ($py+$ch) - $Body->scrollTop + $maxh > $maxh ? $maxh - ($py+$ch) + $Body->scrollTop : $maxh;
394             $ph = $ch + $ph > $h ? $h - $ch : $ph;
395              
396             # Capture the part and append it to the strip
397             $strip .= (CaptureHwndRect(defined $PopUp_IE ? $PopUp_HWND_Browser : $HWND_Browser, $sx, $sy, $pw, $ph))[2];
398              
399             $ch += $ph;
400             }
401              
402             $final = JoinRawData( $cw, $pw, $h, $final, $strip );
403              
404             $cw += $pw;
405             }
406              
407             return CreateImage( $w, $h, $final );
408             }
409              
410              
411             sub CaptureBrowser {
412              
413             GetDoc();
414              
415             # scrollTo(0, 0)
416             $Body->doScroll('pageUp') while $Body->scrollTop > 0;
417             $Body->doScroll('pageLeft') while $Body->scrollLeft > 0;
418              
419             return CaptureWindow( defined $PopUp_IE ? $PopUp_HWND_IE : $HWND_IE );
420             }
421              
422             ##########################################################################
423              
424             sub EventHandler {
425             my ($obj,$event,@args) = @_;
426              
427             # if the document is fully loaded and ready after Navigate()
428             if ($event eq 'DocumentComplete' && $IE->ReadyState() == 4) {
429             Win32::OLE->QuitMessageLoop;
430             }
431              
432             # if the document is fully loaded and ready after Refresh()
433             if ($event eq 'DownloadComplete' && $refreshing_page) {
434             $refreshing_page = 0;
435             Win32::OLE->QuitMessageLoop;
436             }
437              
438             # if new window is going to be created
439             if ($event eq 'NewWindow2') {
440              
441             # if we want to process popups and don't have any yet
442             if ( $ProcessPopUps && ! $PopUp_IE ) {
443              
444             # create a browser for the window
445             $PopUp_IE = Win32::OLE->new("InternetExplorer.Application")->{Application};
446             Win32::OLE->WithEvents($PopUp_IE, \&PopUpEventHandler, "DWebBrowserEvents2");
447             $args[0]->Put($PopUp_IE);
448              
449             # wait while the window is busy
450             while ($PopUp_IE->{Busy} == 1) { select(undef, undef, undef, 0.2); }
451              
452             # if we do not want any popups, cancel that
453             } else {
454             $args[1]->Put(1);
455             }
456              
457             }
458             }
459              
460              
461             sub PopUpEventHandler {
462             my ($obj,$event,@args) = @_;
463              
464             # if the document is fully loaded and ready after Navigate()
465             if ($event eq 'DocumentComplete' && $IE->ReadyState() == 4) {
466             Win32::OLE->QuitMessageLoop;
467             }
468              
469             # if the document is fully loaded and ready after Refresh()
470             if ($event eq 'DownloadComplete' && $refreshing_page) {
471             $refreshing_page = 0;
472             Win32::OLE->QuitMessageLoop;
473             }
474              
475             # if new window is going to be created, cancel that, we can handle only one popup
476             if ($event eq 'NewWindow2') {
477             $args[1]->Put(1);
478             }
479              
480             # if the window has been closed destroy the object
481             if ($event eq 'OnQuit') {
482             $PopUp_IE = undef;
483             $PopUp_HWND_IE = undef;
484             $PopUp_HWND_Browser = undef;
485             }
486              
487             }
488              
489             ##########################################################################
490              
491             1;
492              
493             __END__