File Coverage

blib/lib/WWW/Mechanize/Firefox.pm
Criterion Covered Total %
statement 65 917 7.0
branch 3 404 0.7
condition 0 250 0.0
subroutine 21 132 15.9
pod 79 88 89.7
total 168 1791 9.3


line stmt bran cond sub pod time code
1             package WWW::Mechanize::Firefox;
2 80     80   947979 use 5.006; #weaken
  80         183  
3 80     80   267 use strict;
  80         80  
  80         1521  
4 80     80   35133 use Time::HiRes qw(sleep); # hires sleep()
  80         75717  
  80         280  
5              
6 80     80   40583 use URI ();
  80         368793  
  80         1477  
7 80     80   353 use File::Basename qw(dirname);
  80         135  
  80         4809  
8 80     80   33981 use HTTP::Response ();
  80         1135178  
  80         1593  
9 80     80   35152 use HTML::Selector::XPath 'selector_to_xpath';
  80         147243  
  80         4167  
10 80     80   31015 use MIME::Base64 'decode_base64';
  80         34687  
  80         3874  
11 80     80   30996 use WWW::Mechanize::Link;
  80         22470  
  80         1868  
12 80     80   29464 use Firefox::Application;
  80         143  
  80         1790  
13 80     80   336 use MozRepl::RemoteObject ();
  80         83  
  80         825  
14 80     80   225 use MozRepl::RemoteObject::Methods ();
  80         84  
  80         865  
15 80     80   27991 use HTTP::Cookies::MozRepl ();
  80         138  
  80         1169  
16 80     80   33875 use HTTP::Request::Common ();
  80         203259  
  80         1561  
17 80     80   351 use Scalar::Util qw'blessed weaken';
  80         76  
  80         3594  
18 80     80   282 use Encode qw(encode decode);
  80         85  
  80         3092  
19 80     80   274 use Carp qw(carp croak );
  80         86  
  80         2930  
20              
21 80     80   261 use vars qw'$VERSION %link_spec @CARP_NOT';
  80         86  
  80         48388  
22             $VERSION = '0.79';
23             @CARP_NOT = ('MozRepl::RemoteObject',
24             'MozRepl::AnyEvent',
25             'MozRepl::RemoteObject::Instance'
26             ); # we trust these blindly
27              
28             =head1 NAME
29              
30             WWW::Mechanize::Firefox - use Firefox as if it were WWW::Mechanize
31              
32             =head1 SYNOPSIS
33              
34             use WWW::Mechanize::Firefox;
35             my $mech = WWW::Mechanize::Firefox->new();
36             $mech->get('http://google.com');
37              
38             $mech->eval_in_page('alert("Hello Firefox")');
39             my $png = $mech->content_as_png();
40              
41             This module will let you automate Firefox through the
42             Mozrepl plugin. You need to have installed
43             that plugin in your Firefox.
44              
45             For more examples see L.
46              
47             =head1 CONSTRUCTOR and CONFIGURATION
48              
49             =head2 C<< $mech->new( %args ) >>
50              
51             use WWW::Mechanize::Firefox;
52             my $mech = WWW::Mechanize::Firefox->new();
53              
54             Creates a new instance and connects it to Firefox.
55              
56             Note that Firefox must have the C
57             extension installed and enabled.
58              
59             The following options are recognized:
60              
61             =over 4
62              
63             =item *
64              
65             C - regex for the title of the tab to reuse. If no matching tab is
66             found, the constructor dies.
67              
68             If you pass in the string C, the currently
69             active tab will be used instead.
70              
71             If you pass in a L instance, this will be used
72             as the new tab. This is convenient if you have an existing tab
73             in Firefox as object already, for example created through
74             LC<< ->addTab() >>.
75              
76             =item *
77              
78             C - will create a new tab if no existing tab matching
79             the criteria given in C can be found.
80              
81             =item *
82              
83             C - make the tab the active tab
84              
85             =item *
86              
87             C - name of the program to launch if we can't connect to it on
88             the first try.
89              
90             =item *
91              
92             C - an array reference of ids of subframes to include when
93             searching for elements on a page.
94              
95             If you want to always search through all frames, just pass C<1>. This
96             is the default.
97              
98             To prevent searching through frames, pass
99              
100             frames => 0
101              
102             To whitelist frames to be searched, pass the list
103             of frame selectors:
104              
105             frames => ['#content_frame']
106              
107             =item *
108              
109             C - whether web failures converted are fatal Perl errors. See
110             the C accessor. True by default to make error checking easier.
111              
112             To make errors non-fatal, pass
113              
114             autodie => 0
115              
116             in the constructor.
117              
118             =item *
119              
120             C - the name of the User Agent to use. This overrides
121             how Firefox identifies itself.
122              
123             =item *
124              
125             C - array reference to log levels, passed through to L
126              
127             =item *
128              
129             C - L buffer size, if the default of 1MB is not enough
130              
131             =item *
132              
133             C - the set of default Javascript events to listen for while
134             waiting for a reply. In fact, WWW::Mechanize::Firefox will almost always
135             wait until a 'DOMContentLoaded' or 'load' event. 'pagehide' events
136             will tell it for what frames to wait.
137              
138             The default set is
139              
140             'DOMContentLoaded','load',
141             'pageshow',
142             'pagehide',
143             'error','abort','stop',
144              
145             =item *
146              
147             C - a premade L
148              
149             =item *
150              
151             C - a premade L instance or a connection string
152             suitable for initializing one
153              
154             =item *
155              
156             C - whether to use the command queueing of L.
157             Default is 1.
158              
159             =item *
160              
161             C - whether to use native JSON encoder of Firefox
162              
163             js_JSON => 'native', # force using the native JSON encoder
164              
165             The default is to autodetect whether a native JSON encoder is available and
166             whether the transport is UTF-8 safe.
167              
168             =item *
169              
170             C - the events that are sent to an input field before its
171             value is changed. By default this is C<[focus]>.
172              
173             =item *
174              
175             C - the events that are sent to an input field after its
176             value is changed. By default this is C<[blur, change]>.
177              
178             =back
179              
180             =cut
181              
182             sub new {
183 60     60 1 85967 my ($class, %args) = @_;
184            
185 60 50       210 if (! ref $args{ app }) {
186 60         181 my @passthrough = qw(launch repl bufsize log use_queue js_JSON);
187 60 100       112 my %options = map { exists $args{ $_ } ? ($_ => delete $args{ $_ }) : () }
  360         553  
188             @passthrough;
189 60         353 $args{ app } = Firefox::Application->new(
190             %options
191             );
192             };
193            
194 0 0         if (my $tabname = delete $args{ tab }) {
195 0 0         if (! ref $tabname) {
    0          
196 0 0         if ($tabname eq 'current') {
197 0           $args{ tab } = $args{ app }->selectedTab();
198             } else {
199 0           croak "Don't know what to do with tab '$tabname'. Did you mean qr{$tabname}?";
200             };
201             } elsif ('MozRepl::RemoteObject::Instance' eq ref $tabname) {
202             # Nothing to do - we already got a tab passed in
203             # Just put it back in place
204 0           $args{ tab } = $tabname;
205             } else {
206 0           ($args{ tab }) = grep { $_->{title} =~ /$tabname/ }
207 0           $args{ app }->openTabs();
208 0 0         if (! $args{ tab }) {
209 0 0         if (! delete $args{ create }) {
210 0           croak "Couldn't find a tab matching /$tabname/";
211             } else {
212             # fall through into tab creation
213             };
214             } else {
215 0           $args{ tab } = $args{ tab }->{tab};
216             };
217             };
218             };
219 0 0         if (! $args{ tab }) {
220 0 0         my @autoclose = exists $args{ autoclose } ? (autoclose => $args{ autoclose }) : ();
221 0           $args{ tab } = $args{ app }->addTab( @autoclose );
222 0           my $body = $args{ tab }->MozRepl::RemoteObject::Methods::dive(qw[ linkedBrowser contentWindow document body ]);
223 0           $body->{innerHTML} = __PACKAGE__;
224             };
225              
226 0 0         if (delete $args{ autoclose }) {
227 0           $args{ app }->autoclose_tab($args{ tab });
228             };
229 0 0         if (! exists $args{ autodie }) { $args{ autodie } = 1 };
  0            
230            
231             $args{ events } ||= [
232 0   0       'DOMContentLoaded','load',
233             'pageshow', # Navigation from cache will use "pageshow"
234             #'pagehide',
235             'error','abort','stop',
236             ];
237 0   0       $args{ on_event } ||= undef;
238 0   0       $args{ pre_value } ||= ['focus'];
239 0   0       $args{ post_value } ||= ['change','blur'];
240 0 0         if( ! exists $args{ frames }) {
241 0   0       $args{ frames } ||= 1; # we default to searching frames
242             };
243              
244             die "No tab found"
245 0 0         unless $args{tab};
246            
247 0 0         if (delete $args{ activate }) {
248 0           $args{ app }->activateTab( $args{ tab });
249             };
250            
251 0   0       $args{ response } ||= undef;
252 0   0       $args{ current_form } ||= undef;
253              
254 0   0       $args{ event_log } ||= [];
255            
256 0           my $agent = delete $args{ agent };
257            
258 0           my $self= bless \%args, $class;
259            
260 0           $self->_initXpathResultTypes;
261              
262 0 0         if( defined $agent ) {
263 0           $self->agent( $agent );
264             };
265            
266 0           $self
267             };
268              
269             sub DESTROY {
270 0     0     my ($self) = @_;
271 0           local $@;
272 0 0         if (my $app = delete $self->{ app }) {
273 0           %$self = (); # wipe out all references we keep
274             # but keep $app alive until we can dispose of it
275             # as the last thing, now:
276 0           $app = undef;
277             };
278             }
279              
280             =head2 C<< $mech->agent( $product_id ); >>
281              
282             $mech->agent('wonderbot/JS 1.0');
283              
284             Set the product token that is used to identify the user agent on the network.
285             The agent value is sent as the "User-Agent" header in the requests. The default
286             is whatever Firefox uses.
287              
288             To reset the user agent to the Firefox default, pass an empty string:
289              
290             $mech->agent('');
291              
292             =cut
293              
294             sub agent {
295 0     0 1   my ($self,$name) = @_;
296 0 0         if( defined $name ) {
    0          
297 0           $self->add_header('User-Agent',$name);
298             } elsif( $name eq '' ) {
299 0           $self->delete_header('User-Agent');
300             };
301             };
302              
303             =head2 C<< $mech->autodie( [$state] ) >>
304              
305             $mech->autodie(0);
306              
307             Accessor to get/set whether warnings become fatal.
308              
309             =cut
310              
311 0 0   0 1   sub autodie { $_[0]->{autodie} = $_[1] if @_ == 2; $_[0]->{autodie} }
  0            
312              
313             =head2 C<< $mech->events() >>
314              
315             $mech->events( ['load'] );
316              
317             Sets or gets the set of Javascript events that WWW::Mechanize::Firefox
318             will wait for after requesting a new page. Returns an array reference.
319              
320             Changing the set of events will most likely make WWW::Mechanize::Firefox
321             stall while waiting for a response.
322              
323             This method is special to WWW::Mechanize::Firefox.
324              
325             =cut
326              
327 0 0   0 1   sub events { $_[0]->{events} = $_[1] if (@_ > 1); $_[0]->{events} };
  0            
328              
329             =head2 C<< $mech->on_event() >>
330              
331             $mech->on_event(1); # prints every page load event
332              
333             # or give it a callback
334             $mech->on_event(sub { warn "Page loaded with $ev->{name} event" });
335              
336             Gets/sets the notification handler for the Javascript event
337             that finished a page load. Set it to C<1> to output via C,
338             or a code reference to call it with the event.
339              
340             This method is special to WWW::Mechanize::Firefox.
341              
342             =cut
343              
344 0 0   0 1   sub on_event { $_[0]->{on_event} = $_[1] if (@_ > 1); $_[0]->{on_event} };
  0            
345              
346             =head2 C<< $mech->cookies() >>
347              
348             my $cookie_jar = $mech->cookies();
349              
350             Returns a L object that was initialized
351             from the live Firefox instance.
352              
353             B C<< ->set_cookie >> is not yet implemented,
354             as is saving the cookie jar.
355              
356             =cut
357              
358             sub cookies {
359 0     0 1   return HTTP::Cookies::MozRepl->new(
360             repl => $_[0]->repl
361             )
362             }
363              
364             =head1 JAVASCRIPT METHODS
365              
366             =head2 C<< $mech->allow( %options ) >>
367              
368             Enables or disables browser features for the current tab.
369             The following options are recognized:
370              
371             =over 4
372              
373             =item *
374              
375             C - Whether to allow plugin execution.
376              
377             =item *
378              
379             C - Whether to allow Javascript execution.
380              
381             =item *
382              
383             C - Attribute stating if refresh based redirects can be allowed.
384              
385             =item *
386              
387             C, C - Attribute stating if it should allow subframes (framesets/iframes) or not.
388              
389             =item *
390              
391             C - Attribute stating whether or not images should be loaded.
392              
393             =back
394              
395             Options not listed remain unchanged.
396              
397             =head3 Disable Javascript
398              
399             $mech->allow( javascript => 0 );
400              
401             =cut
402              
403 80     80   1395 use vars '%known_options';
  80         107  
  80         275620  
404             %known_options = (
405             'javascript' => 'allowJavascript',
406             'plugins' => 'allowPlugins',
407             'metaredirects' => 'allowMetaRedirects',
408             'subframes' => 'allowSubframes',
409             'frames' => 'allowSubframes',
410             'images' => 'allowImages',
411             );
412              
413             sub allow {
414 0     0 1   my ($self,%options) = @_;
415 0           my $shell = $self->docshell;
416 0           for my $opt (sort keys %options) {
417 0 0         if (my $opt_js = $known_options{ $opt }) {
418 0           $shell->{$opt_js} = $options{ $opt };
419             } else {
420 0           carp "Unknown option '$opt_js' (ignored)";
421             };
422             };
423             };
424              
425             =head2 C<< $mech->js_errors() >>
426              
427             print $_->{message}
428             for $mech->js_errors();
429              
430             An interface to the Javascript Error Console
431              
432             Returns the list of errors in the JEC
433              
434             Maybe this should be called C or
435             C instead.
436              
437             =cut
438              
439             sub js_console {
440 0     0 0   my ($self) = @_;
441 0           my $getConsoleService = $self->repl->declare(<<'JS');
442             function() {
443             return Components.classes["@mozilla.org/consoleservice;1"]
444             .getService(Components.interfaces.nsIConsoleService);
445             }
446             JS
447 0           $getConsoleService->()
448             }
449              
450             sub js_errors {
451 0     0 1   my ($self,$page) = @_;
452 0           my $console = $self->js_console;
453 0           my $getErrorMessages = $self->repl->declare(<<'JS', 'list');
454             function (consoleService) {
455             var out = {};
456             consoleService.getMessageArray(out, {});
457             return out.value || []
458             };
459             JS
460 0           $getErrorMessages->($console);
461             }
462              
463             =head2 C<< $mech->clear_js_errors() >>
464              
465             $mech->clear_js_errors();
466              
467             Clears all Javascript messages from the console
468              
469             =cut
470              
471             sub clear_js_errors {
472 0     0 1   my ($self,$page) = @_;
473 0           $self->js_console->reset;
474              
475             };
476              
477             =head2 C<< $mech->eval_in_page( $str [, $env [, $document]] ) >>
478              
479             =head2 C<< $mech->eval( $str [, $env [, $document]] ) >>
480              
481             my ($value, $type) = $mech->eval( '2+2' );
482              
483             Evaluates the given Javascript fragment in the
484             context of the web page.
485             Returns a pair of value and Javascript type.
486              
487             This allows access to variables and functions declared
488             "globally" on the web page.
489              
490             The returned result needs to be treated with
491             extreme care because
492             it might lead to Javascript execution in the context of
493             your application instead of the context of the webpage.
494             This should be evident for functions and complex data
495             structures like objects. When working with results from
496             untrusted sources, you can only safely use simple
497             types like C.
498              
499             If you want to modify the environment the code is run under,
500             pass in a hash reference as the second parameter. All keys
501             will be inserted into the C object as well as
502             C. Also, complex data structures are only
503             supported if they contain no objects.
504             If you need finer control, you'll have to
505             write the Javascript yourself.
506              
507             This method is special to WWW::Mechanize::Firefox.
508              
509             Also, using this method opens a potential B as
510             the returned values can be objects and using these objects
511             can execute malicious code in the context of the Firefox application.
512              
513             =cut
514              
515             sub eval_in_page {
516 0     0 1   my ($self,$str,$env,$doc,$window) = @_;
517 0   0       $env ||= {};
518 0           my $js_env = {};
519 0   0       $doc ||= $self->document;
520            
521             # do a manual transfer of keys, to circumvent our stupid
522             # transformation routine:
523 0 0         if (keys %$env) {
524 0           $js_env = $self->repl->declare(<<'JS')->();
525             function () { return new Object }
526             JS
527 0           for my $k (keys %$env) {
528 0           $js_env->{$k} = $env->{$k};
529             };
530             };
531            
532 0           my $eval_in_sandbox = $self->repl->declare(<<'JS', 'list');
533             function (w,d,str,env,caller,line) {
534             var unsafeWin = w.wrappedJSObject;
535             var safeWin = XPCNativeWrapper(unsafeWin);
536             var sandbox = Components.utils.Sandbox(safeWin);
537             sandbox.window = safeWin;
538             sandbox.document = d;
539             // Transfer the environment
540             for (var e in env) {
541             sandbox[e] = env[e]
542             sandbox.window[e] = env[e]
543             }
544             sandbox.__proto__ = unsafeWin;
545              
546             var res = Components.utils.evalInSandbox(str, sandbox, "1.8",caller,line);
547             return [res,typeof(res)];
548             };
549             JS
550 0   0       $window ||= $self->tab->{linkedBrowser}->{contentWindow};
551             # Report errors from scope of caller
552             # This feels weirdly backwards here, but oh well:
553             #local @CARP_NOT = (ref $self->repl); # we trust this
554            
555 0           my ($caller,$line) = (caller)[1,2];
556            
557 0           $eval_in_sandbox->($window,$doc,$str,$js_env,$caller,$line);
558             };
559             *eval = \&eval_in_page;
560              
561             =head2 C<< $mech->unsafe_page_property_access( ELEMENT ) >>
562              
563             Allows you unsafe access to properties of the current page. Using
564             such properties is an incredibly bad idea.
565              
566             This is why the function Cs. If you really want to use
567             this function, edit the source code.
568              
569             =cut
570              
571             sub unsafe_page_property_access {
572 0     0 1   my ($mech,$element) = @_;
573 0           die;
574 0           my $window = $mech->tab->{linkedBrowser}->{contentWindow};
575 0           my $unsafe = $window->{wrappedJSObject};
576 0           $unsafe->{$element}
577             };
578              
579             =head1 UI METHODS
580              
581             See also L for how to add more than one tab
582             and how to manipulate windows and tabs.
583              
584             =head2 C<< $mech->application() >>
585              
586             my $ff = $mech->application();
587              
588             Returns the L object for manipulating
589             more parts of the Firefox UI and application.
590              
591             =cut
592              
593 0     0 1   sub application { $_[0]->{app} };
594              
595             =head2 C<< $mech->autoclose_tab >>
596              
597             $mech->autoclose_tab( 0 ); # keep tab open after program end
598              
599             Set whether to close the tab associated with the instance.
600              
601             =cut
602              
603             sub autoclose_tab {
604 0     0 1   my $self = shift;
605 0           $self->application->autoclose_tab($self->tab, @_);
606             };
607              
608             =head2 C<< $mech->tab() >>
609              
610             Gets the object that represents the Firefox tab used by WWW::Mechanize::Firefox.
611              
612             This method is special to WWW::Mechanize::Firefox.
613              
614             =cut
615              
616 0     0 1   sub tab { $_[0]->{tab} };
617              
618             =head2 C<< $mech->make_progress_listener( %callbacks ) >>
619              
620             my $eventlistener = $mech->progress_listener(
621             onStateChange => \&onStateChange,
622             );
623              
624             Creates an unconnected C<< nsIWebProgressListener >> interface
625             which calls the Perl subroutines you pass in.
626              
627             Returns a handle. Once the handle gets released, all callbacks will
628             get stopped. Also, all Perl callbacks will get deregistered from the
629             Javascript bridge, so make sure not to use the same callback
630             in different progress listeners at the same time.
631             The sender may still call your callbacks.
632              
633             =cut
634              
635             sub make_progress_listener {
636 0     0 1   my ($mech,%handlers) = @_;
637 0           my $NOTIFY_STATE = $mech->repl->constant('Components.interfaces.nsIWebProgress.NOTIFY_STATE_ALL')
638             + $mech->repl->constant('Components.interfaces.nsIWebProgress.NOTIFY_STATUS')
639             ;
640 0           my ($obj) = $mech->repl->expr('new Object');
641 0           for my $key (keys %handlers) {
642 0           $obj->{$key} = $handlers{$key};
643             };
644             #warn "Listener created";
645            
646 0           my $mk_nsIWebProgressListener = $mech->repl->declare(<<'JS');
647             function (myListener) {
648             var callbacks = ["onStateChange",
649             "onLocationChange",
650             "onProgressChange",
651             "onStatusChange",
652             "onSecurityChange"
653             // ,"onProgressChange64"
654             // ,"onRefreshAttempted"
655             ];
656             for (var h in callbacks) {
657             var e = callbacks[h];
658             if (! myListener[e]) {
659             myListener[e] = function(){}
660             } else {
661             // alert("Setting callback for " + e);
662             };
663             };
664             myListener.QueryInterface = function(aIID) {
665             if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
666             // aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
667             aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
668             aIID.equals(Components.interfaces.nsISupports))
669             return this;
670             throw Components.results.NS_NOINTERFACE;
671             };
672             return myListener
673             }
674             JS
675            
676             # Declare it here so we don't close over $lsn!
677             my $release = sub {
678 0 0   0     $_[0]->bridge->remove_callback(values %handlers)
679             if $_[0]->bridge;
680 0           };
681 0           my $lsn = $mk_nsIWebProgressListener->($obj);
682 0           $lsn->__on_destroy($release);
683 0           $lsn
684             };
685              
686              
687             =head2 C<< $mech->progress_listener( $source, %callbacks ) >>
688              
689             my $eventlistener = progress_listener(
690             $browser,
691             onLocationChange => \&onLocationChange,
692             );
693              
694             Sets up the callbacks for the C<< nsIWebProgressListener >> interface
695             to be the Perl subroutines you pass in.
696              
697             C< $source > needs to support C<.addProgressListener> and C<.removeProgressListener>.
698              
699             Returns a handle. Once the handle gets released, all callbacks will
700             get stopped. Also, all Perl callbacks will get deregistered from the
701             Javascript bridge, so make sure not to use the same callback
702             in different progress listeners at the same time.
703              
704             =cut
705              
706             sub progress_listener {
707 0     0 1   my ($self,$source,%handlers) = @_;
708            
709 0           my $lsn = $self->make_progress_listener(%handlers);
710 0           $lsn->{source} = $source;
711            
712 0           $lsn->__release_action('if(self.source)try{self.source.removeProgressListener(self)}catch(e){}');
713 0           my $NOTIFY_STATE = $self->repl->constant('Components.interfaces.nsIWebProgress.NOTIFY_STATE_ALL')
714             + $self->repl->constant('Components.interfaces.nsIWebProgress.NOTIFY_LOCATION')
715             + $self->repl->constant('Components.interfaces.nsIWebProgress.NOTIFY_STATUS');
716 0           $source->addProgressListener($lsn,$NOTIFY_STATE);
717 0           $lsn
718             };
719              
720             =head2 C<< $mech->repl() >>
721              
722             my ($value,$type) = $mech->repl->expr('2+2');
723              
724             Gets the L instance that is used.
725              
726             This method is special to WWW::Mechanize::Firefox.
727              
728             =cut
729              
730 0     0 1   sub repl { $_[0]->application->repl };
731              
732             =head2 C<< $mech->highlight_node( @nodes ) >>
733              
734             my @links = $mech->selector('a');
735             $mech->highlight_node(@links);
736              
737             Convenience method that marks all nodes in the arguments
738             with
739              
740             background: red;
741             border: solid black 1px;
742             display: block; /* if the element was display: none before */
743              
744             This is convenient if you need visual verification that you've
745             got the right nodes.
746              
747             There currently is no way to restore the nodes to their original
748             visual state except reloading the page.
749              
750             =cut
751              
752             sub highlight_node {
753 0     0 1   my ($self,@nodes) = @_;
754 0           for (@nodes) {
755 0           my $style = $_->{style};
756             $style->{display} = 'block'
757 0 0         if $style->{display} eq 'none';
758 0           $style->{background} = 'red';
759 0           $style->{border} = 'solid black 1px;';
760             };
761             };
762              
763             =head1 NAVIGATION METHODS
764              
765             =head2 C<< $mech->get( $url, %options ) >>
766              
767             $mech->get( $url, ':content_file' => $tempfile );
768              
769             Retrieves the URL C into the tab.
770              
771             It returns a faked L object for interface compatibility
772             with L.
773              
774             Recognized options:
775              
776             =over 4
777              
778             =item *
779              
780             C<< :content_file >> - filename to store the data in
781              
782             =item *
783              
784             C<< no_cache >> - if true, bypass the browser cache
785              
786             =item *
787              
788             C<< synchronize >> - wait until all elements have loaded
789              
790             The default is to wait until all elements have loaded. You can switch
791             this off by passing
792              
793             synchronize => 0
794              
795             for example if you want to manually poll for an element that appears fairly
796             early during the load of a complex page.
797              
798             =back
799              
800             =cut
801              
802             sub get {
803 0     0 1   my ($self,$url, %options) = @_;
804 0           my $b = $self->tab->{linkedBrowser};
805 0           $self->clear_current_form;
806            
807 0           my $flags = 0;
808 0 0         if ($options{ no_cache }) {
809 0           $flags = $self->repl->constant('nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE');
810             };
811 0 0         if (! exists $options{ synchronize }) {
812 0           $options{ synchronize } = $self->events;
813             };
814 0 0         if( !ref $options{ synchronize }) {
815             $options{ synchronize } = $options{ synchronize }
816 0 0         ? $self->events
817             : []
818             };
819            
820             $self->_sync_call( $options{ synchronize }, sub {
821 0 0   0     if (my $target = delete $options{":content_file"}) {
822 0           $self->save_url($url => ''.$target, %options);
823             } else {
824 0           $b->loadURIWithFlags(''.$url,$flags);
825             };
826 0           });
827             };
828              
829             =head2 C<< $mech->get_local( $filename , %options ) >>
830              
831             $mech->get_local('test.html');
832              
833             Shorthand method to construct the appropriate
834             C<< file:// >> URI and load it into Firefox. Relative
835             paths will be interpreted as relative to C<$0>.
836              
837             This method accepts the same options as C<< ->get() >>.
838              
839             This method is special to WWW::Mechanize::Firefox but could
840             also exist in WWW::Mechanize through a plugin.
841              
842             =cut
843              
844             sub get_local {
845 0     0 1   my ($self, $htmlfile, %options) = @_;
846 0           require Cwd;
847 0           require File::Spec;
848 0           my $fn = File::Spec->rel2abs(
849             File::Spec->catfile(dirname($0),$htmlfile),
850             Cwd::getcwd(),
851             );
852 0           $fn =~ s!\\!/!g; # fakey "make file:// URL"
853              
854 0           $self->get("file://$fn", %options);
855             }
856              
857             =head2 C<< $mech->post( $url, %options ) >>
858              
859             $mech->post( 'http://example.com',
860             params => { param => "Hello World" },
861             headers => {
862             "Content-Type" => 'application/x-www-form-urlencoded',
863             },
864             charset => 'utf-8',
865             );
866              
867             Sends a POST request to C<$url>.
868              
869             A C header will be automatically calculated if
870             it is not given.
871              
872             The following options are recognized:
873              
874             =over 4
875              
876             =item *
877              
878             C - a hash of HTTP headers to send. If not given,
879             the content type will be generated automatically.
880              
881             =item *
882              
883             C - the raw data to send, if you've encoded it already.
884              
885             =back
886              
887             =cut
888              
889             sub post {
890 0     0 1   my ($self, $url, %options) = @_;
891 0           my $b = $self->tab->{linkedBrowser};
892 0           $self->clear_current_form;
893              
894 0           my $flags = 0;
895 0 0         if ($options{no_cache}) {
896 0           $flags = $self->repl->constant('nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE');
897             };
898 0 0         if (! exists $options{synchronize}) {
899 0           $options{synchronize} = $self->events;
900             };
901 0 0         if( !ref $options{synchronize}) {
902             $options{synchronize} = $options{synchronize}
903 0 0         ? $self->events
904             : []
905             };
906              
907             # If we don't have data, encode the parameters:
908 0 0         if( !$options{ data }) {
909 0           my $req= HTTP::Request::Common::POST( $url, $options{params} );
910 0           $options{ data } = $req->content;
911             };
912              
913 0   0       $options{ charset } ||= 'utf-8';
914 0   0       $options{ headers } ||= {};
915 0   0       $options{ headers }->{"Content-Type"} ||= "application/x-www-form-urlencoded";
916 0 0         if( $options{ charset }) {
917 0           $options{ headers }->{"Content-Type"} .= "; charset=$options{ charset }";
918             };
919              
920 0           my $streamPostData = $self->repl->declare(<<'JS');
921             function(headers, dataString) {
922             // POST method requests must wrap the encoded text in a MIME stream
923             const Cc = Components.classes;
924             const Ci = Components.interfaces;
925             var stringStream = Cc["@mozilla.org/io/string-input-stream;1"].
926             createInstance(Ci.nsIStringInputStream);
927             if ("data" in stringStream) // Gecko 1.9 or newer
928             stringStream.data = dataString;
929             else // 1.8 or older
930             stringStream.setData(dataString, dataString.length);
931              
932             var postData = Cc["@mozilla.org/network/mime-input-stream;1"].
933             createInstance(Ci.nsIMIMEInputStream);
934             for( h in headers ) {
935             postData.addHeader( h, headers[h] );
936             };
937             postData.addContentLength = true;
938             postData.setData(stringStream);
939              
940             return postData;
941             }
942             JS
943              
944             $self->_sync_call($options{synchronize}, sub {
945 0     0     my $postData = $streamPostData->($options{headers}, $options{data});
946 0           $b->loadURIWithFlags(''.$url, $flags, undef, $options{charset}, $postData);
947 0           });
948             }
949              
950             =head2 C<< $mech->add_header( $name => $value, ... ) >>
951              
952             $mech->add_header(
953             'X-WWW-Mechanize-Firefox' => "I'm using it",
954             Encoding => 'text/klingon',
955             );
956              
957             This method sets up custom headers that will be sent with B HTTP(S)
958             request that Firefox makes.
959              
960             Using multiple instances of WWW::Mechanize::Firefox objects with the same
961             application together with changed request headers will most likely have weird
962             effects. So don't do that.
963              
964             Note that currently, we only support one value per header.
965              
966             Some versions of Firefox don't work with the method that is used to set
967             the custom headers. Please see C for the exact
968             versions where the implemented mechanism doesn't work. Roughly, this is
969             for versions 17 to 24 of Firefox.
970              
971             =cut
972              
973             # This subroutine creates the custom header observer. It has a hashref
974             # of headers that it will add to EACH request that Firefox sends out.
975             # It removes itself when the Perl object gets destroyed.
976             sub _custom_header_observer {
977 0     0     my ($self, @headers) = @_;
978              
979             # This routine was taken from http://d.hatena.ne.jp/oppara/20090410/p1
980 0           my $on_modify_request = $self->repl->declare(<<'JS');
981             function() { // headers passed via arguments
982             const Cc= Components.classes;
983             const Ci= Components.interfaces;
984             const observerService= Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
985             var h= [].slice.call(arguments);
986             var hr= {};
987             for( var i=0; i
988             var k= h[i];
989             var v= h[i+1];
990             hr[k]= v;
991             };
992            
993             var myObserver= {
994             headers: hr,
995             observe: function(subject,topic,data) {
996             if(topic != 'http-on-modify-request') return;
997            
998             var http = subject.QueryInterface(Ci.nsIHttpChannel);
999             for( var k in this.headers) {
1000             var v= this.headers[k];
1001             http.setRequestHeader(k,v, false);
1002              
1003             if (k== 'Referer' && http.referrer) {
1004             http.referrer.spec = v;
1005             };
1006             };
1007             }
1008             }
1009             observerService.addObserver(myObserver,'http-on-modify-request',false);
1010             return myObserver;
1011             };
1012             JS
1013 0           my $obs = $on_modify_request->(@headers);
1014              
1015             # Clean up after ourselves
1016 0           $obs->__release_action(<<'JS');
1017             const Cc= Components.classes;
1018             const Ci= Components.interfaces;
1019             const observerService= Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
1020             try {
1021             observerService.removeObserver(self,'http-on-modify-request',false);
1022             } catch (e) {}
1023             JS
1024 0           return $obs;
1025             };
1026              
1027             sub add_header {
1028 0     0 1   my ($self, @headers) = @_;
1029 0   0       $self->{custom_header_observer} ||= $self->_custom_header_observer;
1030            
1031             # This is slooow, but we only do it when changing the headers...
1032 0           my $h = $self->{custom_header_observer}->{headers};
1033 0           while( my ($k,$v) = splice @headers, 0, 2 ) {
1034 0           $h->{$k} = $v;
1035             };
1036             };
1037              
1038             =head2 C<< $mech->delete_header( $name , $name2... ) >>
1039              
1040             $mech->delete_header( 'User-Agent' );
1041            
1042             Removes HTTP headers from the agent's list of special headers. Note
1043             that Firefox may still send a header with its default value.
1044              
1045             =cut
1046              
1047             sub delete_header {
1048 0     0 1   my ($self, @headers) = @_;
1049            
1050 0 0 0       if( $self->{custom_header_observer} and @headers ) {
1051             # This is slooow, but we only do it when changing the headers...
1052 0           my $h = $self->{custom_header_observer}->{headers};
1053            
1054             delete $h->{$_}
1055 0           for( @headers );
1056             };
1057             };
1058              
1059             =head2 C<< $mech->reset_headers >>
1060              
1061             $mech->reset_headers();
1062              
1063             Removes all custom headers and makes Firefox send its defaults again.
1064              
1065             =cut
1066              
1067             sub reset_headers {
1068 0     0 1   my ($self) = @_;
1069 0           delete $self->{custom_header_observer};
1070             };
1071              
1072             sub _addLoadEventListener {
1073 0     0     my ($self,%options) = @_;
1074            
1075 0   0       $options{ tab } ||= $self->tab;
1076 0   0       $options{ window } ||= $self->application->getMostRecentWindow;
1077 0   0       $options{ events } ||= $self->events;
1078 0           my $add_load_listener = $self->repl->declare(<<'JS');
1079             function( mainWindow, tab, waitForLoad, events ) {
1080             var browser= mainWindow.gBrowser.getBrowserForTab( tab );
1081              
1082             var lock= {
1083             "busy": 1,
1084             "log":[],
1085             "events": events,
1086             "browser": browser,
1087             "cb": undefined,
1088             "release": function() {
1089             for(var i=0; i
1090             this.browser.removeEventListener(this.events[i], this.cb, true);
1091             };
1092             }
1093             };
1094             var unloadedFrames= [];
1095            
1096             lock.cb= function (e) {
1097             var t= e.target;
1098             var toplevel= (t == browser.contentDocument);
1099             lock.log.push("Event "+e.type);
1100             var reloadedFrame= false;
1101             lock.log.push( "" + unloadedFrames.length + " frames.");
1102            
1103             if( "FRAME" == t.tagName
1104             || "IFRAME" == t.tagName ) {
1105             loc= t.src;
1106             } else if( !t.tagName ) {
1107             // Document
1108             loc= t.URL;
1109             } else { // ignore
1110             lock.log.push("Ignoring " + e.type + " on " + t.tagName);
1111             };
1112             try {
1113             if( t instanceof HTMLDocument ) {
1114             // We are only interested in HTML pages here
1115             var container= t.defaultView.frameElement || browser.contentWindow;
1116             for( var i=0; i < unloadedFrames.length; i++ ) {
1117             try {
1118             // lock.log.push( "" + i + " " + unloadedFrames[i].id + " - " + unloadedFrames[i].src );
1119             reloadedFrame= reloadedFrame
1120             || unloadedFrames[i] === container;
1121             } catch (e) {
1122             // alert("Some frame element has gone away already...");
1123             };
1124             // alert("Caught " + e.type + " on remembered element. Great - " + reloadedFrame);
1125             };
1126            
1127             if ("pagehide" == e.type && container ) {
1128             // alert("pagehide on container /lock"+lock.id);
1129             // A frame or window gets reloaded.
1130             // A frame gets reloaded. We remember it so we can
1131             // tell when it has completed. We won't get a separate
1132             // completion event on the parent document :-(
1133             lock.log.push("Remembering frame parent, for 'load' event");
1134             unloadedFrames.push( container );
1135             // Maybe we should just attach all events here?!
1136             };
1137             };
1138             } catch (e) { alert("Error while looking: " + e.message+" " + e.line) };
1139            
1140             // if (! toplevel && !reloadedFrame ) { return ; };
1141             lock.log.push("<> " + e.type + " on " + loc);
1142            
1143             if( (reloadedFrame)
1144             // && !waitForLoad
1145             && "DOMContentLoaded" == e.type
1146             ) {
1147             // We loaded a document
1148             // See if it contains (i)frames
1149             // and wait for "load" to fire if so
1150             // alert("Reloaded a container /lock:" + lock.id);
1151             lock.log.push("DOMContentLoaded for toplevel");
1152             var q= "//IFRAME|//FRAME";
1153             var frames= t.evaluate(q,t,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ).snapshotLength;
1154             lock.log.push("Found " + frames + " frames");
1155             if( frames ) {
1156             lock.log.push("Waiting for 'load' because we found frames");
1157             waitForLoad= true;
1158             } else if( /^about:neterror\?/.test( loc ) || !waitForLoad ) {
1159             lock.log.push("Early out on DOMContentLoaded");
1160             lock.busy= 0;
1161             };
1162              
1163             } else if( (reloadedFrame)
1164             && ( "load" == e.type
1165             || "pageshow" == e.type
1166             )) { // We always are done on "load" on toplevel
1167             lock.log.push("'" + e.type + "' on top level, old state was " + lock.busy);
1168             lock.busy= 0;
1169              
1170             } else if( (toplevel || reloadedFrame)
1171             && ("error" == e.type || "stop" == e.type)) { // We always are done on "load" on toplevel
1172             lock.log.push("'" + e.type + "' on top level, old state was " + lock.busy);
1173             lock.busy= 0;
1174             };
1175            
1176             };
1177            
1178             for(var i=0; i
1179             browser.addEventListener(events[i], lock.cb, true);
1180             };
1181             lock.log.push("Listening");
1182            
1183             return lock
1184             }
1185             JS
1186 0           return $add_load_listener->($options{ window }, $options{ tab }, 1, $options{ events } );
1187             }
1188              
1189             sub _addEventListener {
1190 0     0     my ($self,@args) = @_;
1191 0 0 0       if (@args <= 2 and ref($args[0]) eq 'MozRepl::RemoteObject::Instance') {
1192 0           @args = [@args];
1193             };
1194 0           for (@args) {
1195 0   0       $_->[1] ||= $self->events;
1196 0 0         $_->[1] = [$_->[1]]
1197             unless ref $_->[1];
1198             };
1199             # Now, flatten the arg list again...
1200 0           @args = map { @$_ } @args;
  0            
1201              
1202             # This registers multiple events for a one-shot event
1203 0           my $make_semaphore = $self->repl->declare(<<'JS');
1204             function() {
1205             var lock = { "busy": 0, "event" : null };
1206             var listeners = [];
1207             var pairs = arguments;
1208             for( var k = 0; k < pairs.length ; k++) {
1209             var b = pairs[k];
1210             k++;
1211             var events = pairs[k];
1212            
1213             for( var i = 0; i < events.length; i++) {
1214             var evname = events[i];
1215             var callback = (function(listeners,evname){
1216             return function(e) {
1217             if (! lock.busy) {
1218             lock.busy++;
1219             lock.event = e.type;
1220             lock.js_event = {};
1221             lock.js_event.target = e.originalTarget;
1222             lock.js_event.type = e.type;
1223             //alert("Caught first event " + e.type + " " + e.message);
1224             } else {
1225             //alert("Caught duplicate event " + e.type + " " + e.message);
1226             };
1227             for( var j = 0; j < listeners.length; j++) {
1228             listeners[j][0].removeEventListener(listeners[j][1],listeners[j][2],true);
1229             };
1230             };
1231             })(listeners,evname);
1232             listeners.push([b,evname,callback]);
1233             b.addEventListener(evname,callback,true);
1234             };
1235             };
1236             return lock
1237             }
1238             JS
1239             # $browser,$events
1240 0           return $make_semaphore->(@args);
1241             };
1242              
1243             sub _wait_while_busy {
1244 0     0     my ($self,@elements) = @_;
1245             # Now do the busy-wait
1246             # Should this also include a ->poll()
1247             # and a callback?
1248 0           my $i=0;
1249 0           while (1) {
1250 0           $i++;
1251 0 0         last if($i == 30 );
1252 0           for my $element (@elements) {
1253 0 0 0       if ((my $s = $element->{busy} || 0) < 1) {
1254 0           for my $element (@elements) {
1255 0           push @{ $self->{event_log} },
1256 0           join "\n", @{ $element->{log}};
  0            
1257             };
1258 0           return $element;
1259             };
1260             };
1261 0           sleep 0.1;
1262            
1263             # if (time-$timer > 4) {
1264             # $timer= time;
1265             # for my $element (@elements) {
1266             # for (@{ $element->{log}}) {
1267             # print $_,"\n";
1268             # };
1269             # print "---\n";
1270             # };
1271             # };
1272             };
1273             }
1274              
1275             =head2 C<< $mech->synchronize( $event, $callback ) >>
1276              
1277             Wraps a synchronization semaphore around the callback
1278             and waits until the event C<$event> fires on the browser.
1279             If you want to wait for one of multiple events to occur,
1280             pass an array reference as the first parameter.
1281              
1282             Usually, you want to use it like this:
1283              
1284             my $l = $mech->xpath('//a[@onclick]', single => 1);
1285             $mech->synchronize('DOMFrameContentLoaded', sub {
1286             $mech->click( $l );
1287             });
1288              
1289             It is necessary to synchronize with the browser whenever
1290             a click performs an action that takes longer and
1291             fires an event on the browser object.
1292              
1293             The C event is fired by Firefox when
1294             the whole DOM and all C