File Coverage

blib/lib/Chrome/DevToolsProtocol.pm
Criterion Covered Total %
statement 68 486 13.9
branch 0 86 0.0
condition 0 58 0.0
subroutine 23 79 29.1
pod 28 34 82.3
total 119 743 16.0


line stmt bran cond sub pod time code
1             package Chrome::DevToolsProtocol;
2 68     68   444710 use 5.012; # for say, //
  68         302  
3 68     68   402 use strict;
  68         143  
  68         1603  
4 68     68   365 use warnings;
  68         182  
  68         1937  
5 68     68   37599 use Moo;
  68         775522  
  68         366  
6 68     68   101373 use PerlX::Maybe;
  68         9908  
  68         615  
7 68     68   4394 use Filter::signatures;
  68         108242  
  68         686  
8 68     68   1815 no warnings 'experimental::signatures';
  68         925  
  68         2293  
9 68     68   464 use feature 'signatures';
  68         651  
  68         7228  
10 68     68   42804 use Future;
  68         821471  
  68         2594  
11 68     68   28717 use Future::HTTP;
  68         41312  
  68         2274  
12 68     68   472 use Carp qw(croak carp);
  68         157  
  68         3414  
13 68     68   45342 use JSON;
  68         707273  
  68         419  
14 68     68   44032 use Data::Dumper;
  68         387857  
  68         6897  
15 68     68   32199 use Chrome::DevToolsProtocol::Transport;
  68         185  
  68         2541  
16 68     68   476 use Scalar::Util 'weaken', 'isweak';
  68         171  
  68         3569  
17 68     68   34106 use Try::Tiny;
  68         85653  
  68         3638  
18 68     68   2676 use URI;
  68         18491  
  68         332640  
19              
20             our $VERSION = '0.69';
21             our @CARP_NOT;
22              
23             =head1 NAME
24              
25             Chrome::DevToolsProtocol - asynchronous dispatcher for the DevTools protocol
26              
27             =head1 SYNOPSIS
28              
29             # Usually, WWW::Mechanize::Chrome automatically creates a driver for you
30             my $driver = Chrome::DevToolsProtocol->new(
31             port => 9222,
32             host => '127.0.0.1',
33             auto_close => 0,
34             error_handler => sub {
35             # Reraise the error
36             croak $_[1]
37             },
38             );
39             $driver->connect( new_tab => 1 )->get
40              
41             =head1 METHODS
42              
43             =head2 C<< ->new( %args ) >>
44              
45             my $driver = Chrome::DevToolsProtocol->new(
46             port => 9222,
47             host => '127.0.0.1',
48             auto_close => 0,
49             error_handler => sub {
50             # Reraise the error
51             croak $_[1]
52             },
53             );
54              
55             These members can mostly be set through the constructor arguments:
56              
57             =over 4
58              
59             =cut
60              
61 0     0     sub _build_log( $self ) {
  0            
  0            
62 0           require Log::Log4perl;
63 0           Log::Log4perl->get_logger(__PACKAGE__);
64             }
65              
66             =item B<host>
67              
68             The hostname to connect to
69              
70             =cut
71              
72             has 'host' => (
73             is => 'ro',
74             default => '127.0.0.1',
75             );
76              
77             =item B<port>
78              
79             The port to connect to
80              
81             =cut
82              
83             has 'port' => (
84             is => 'ro',
85             default => 9222,
86             );
87              
88             =item B<json>
89              
90             The JSON decoder used
91              
92             =cut
93              
94             has 'json' => (
95             is => 'ro',
96             default => sub { JSON->new },
97             );
98              
99             =item B<ua>
100              
101             The L<Future::HTTP> instance to talk to the Chrome DevTools
102              
103             =cut
104              
105             has 'ua' => (
106             is => 'ro',
107             default => sub { Future::HTTP->new },
108             );
109              
110             =item B<tab>
111              
112             Which tab to reuse (if any)
113              
114             =cut
115              
116             has 'tab' => (
117             is => 'rw',
118             );
119              
120             =item B<log>
121              
122             A premade L<Log::Log4perl> object to act as logger
123              
124             =cut
125              
126             has '_log' => (
127             is => 'ro',
128             default => \&_build_log,
129             );
130              
131             has 'receivers' => (
132             is => 'ro',
133             default => sub { {} },
134             );
135              
136             has 'reader_fh' => (
137             is => 'ro',
138             );
139              
140             has 'writer_fh' => (
141             is => 'ro',
142             );
143              
144             =item B<on_message>
145              
146             A callback invoked for every message
147              
148             =cut
149              
150             has 'on_message' => (
151             is => 'rw',
152             default => undef,
153             );
154              
155             has '_one_shot' => (
156             is => 'ro',
157             default => sub { [] },
158             );
159              
160             has 'listener' => (
161             is => 'ro',
162             default => sub { {} },
163             );
164              
165             has 'sequence_number' => (
166             is => 'rw',
167             default => sub { 1 },
168             );
169              
170             =item B<transport>
171              
172             The event-loop specific transport backend
173              
174             =cut
175              
176             has 'transport' => (
177             is => 'ro',
178             handles => ['future'],
179             );
180              
181             has 'is_connected' => (
182             is => 'rw',
183             );
184              
185             around BUILDARGS => sub( $orig, $class, %args ) {
186             $args{ _log } = delete $args{ 'log' };
187             $class->$orig( %args )
188             };
189              
190             =back
191              
192             =head2 C<< ->future >>
193              
194             my $f = $driver->future();
195              
196             Returns a backend-specific generic future
197              
198             =head2 C<< ->endpoint >>
199              
200             my $url = $driver->endpoint();
201              
202             Returns the URL endpoint to talk to for the connected tab
203              
204             =cut
205              
206             has 'json_log_fh' => (
207             is => 'rw', # actually, it isn't really rw, but set-once
208             );
209              
210             =head2 C<< json_log_fh >>
211              
212             Filehandle where all communications will be logged to, one line
213             per message/response. Each line will be of the format
214              
215             { type: "message", payload: { ... } }
216             { type: "reply", payload: { ... } }
217             { type: "event", payload: { ... } }
218              
219             The filehandle must be opened as :raw , as UTF-8 encoded bytes will
220             be written to it.
221              
222             =cut
223              
224             has 'endpoint' => (
225             is => 'rw', # actually, it isn't really rw, but set-once
226             );
227              
228             =head2 C<< ->add_listener >>
229              
230             my $l = $driver->add_listener(
231             'Page.domContentEventFired',
232             sub {
233             warn "The DOMContent event was fired";
234             },
235             );
236              
237             # ...
238              
239             undef $l; # stop listening
240              
241             Adds a callback for the given event name. The callback will be removed once
242             the return value goes out of scope.
243              
244             =cut
245              
246 0     0 1   sub add_listener( $self, $event, $callback ) {
  0            
  0            
  0            
  0            
247 0           my $listener = Chrome::DevToolsProtocol::EventListener->new(
248             protocol => $self,
249             callback => $callback,
250             event => $event,
251             );
252 0   0       $self->listener->{ $event } ||= [];
253 0           push @{ $self->listener->{ $event }}, $listener;
  0            
254 0           weaken $self->listener->{ $event }->[-1];
255 0           $listener
256             }
257              
258             =head2 C<< ->remove_listener >>
259              
260             $driver->remove_listener($l);
261              
262             Explicitly remove a listener.
263              
264             =cut
265              
266 0     0 1   sub remove_listener( $self, $listener ) {
  0            
  0            
  0            
267             # $listener->{event} can be undef during global destruction
268 0 0         if( my $event = $listener->event ) {
269 0   0       my $l = $self->listener->{ $event } ||= [];
270 0           @{$l} = grep { $_ != $listener }
  0            
271 0           grep { defined $_ }
272 0           @{$self->listener->{ $event }};
  0            
273             # re-weaken our references
274 0           for (0..$#$l) {
275 0           weaken $l->[$_];
276             };
277             };
278             }
279              
280             =head2 C<< ->log >>
281              
282             $driver->log('debug', "Warbling doodads", { doodad => 'this' } );
283              
284             Log a message
285              
286             =cut
287              
288 0     0 1   sub log( $self, $level, $message, @args ) {
  0            
  0            
  0            
  0            
  0            
289 0           my $logger = $self->_log;
290 0 0         if( !@args ) {
291 0           $logger->$level( $message )
292             } else {
293 0           my $enabled = "is_$level";
294 0 0         $logger->$level( join " ", $message, Dumper @args )
295             if( $logger->$enabled );
296             };
297             }
298              
299             =head2 C<< ->connect >>
300              
301             my $f = $driver->connect()->get;
302              
303             Asynchronously connect to the Chrome browser, returning a Future.
304              
305             =cut
306              
307 0     0 1   sub connect( $self, %args ) {
  0            
  0            
  0            
308 0           my $s = $self;
309 0           weaken $s;
310             # If we are still connected to a different tab, disconnect from it
311 0 0 0       if( $self->transport and ref $self->transport and $self->transport->type ne 'pipe') {
      0        
312 0           $self->transport->close();
313             };
314             # Kick off the connect
315 0           my $endpoint;
316 0   0       $args{ writer_fh } //= $self->writer_fh;
317 0   0       $args{ reader_fh } //= $self->reader_fh;
318 0   0       $args{ endpoint } //= $self->endpoint;
319 0 0 0       if( $args{ writer_fh } and $args{ reader_fh }) {
    0          
320             # Pipe connection
321 0   0       $args{ transport } ||= 'Chrome::DevToolsProtocol::Transport::Pipe';
322             $endpoint = {
323             reader_fh => $args{ reader_fh },
324             writer_fh => $args{ writer_fh },
325 0           };
326              
327             } elsif( $args{ endpoint }) {
328 0   0       $endpoint = $args{ endpoint } || $self->endpoint;
329 0           $self->log('trace', "Using endpoint $endpoint");
330              
331             # Set these values so anyone reusing the parameters will find them
332 0           my $ws = URI->new( $endpoint );
333 0 0         if( $s ) {
334 0           $s->{port} = $ws->port;
335 0           $s->{host} = $ws->host;
336             };
337             };
338              
339 0           my $got_endpoint;
340 0 0         if( ! $endpoint ) {
341 0 0         if( ! $self->port ) {
342 0           die "Can't connect without knowing the port?! " . $self->port;
343             };
344 0     0     $got_endpoint = $self->version_info()->then(sub( $info ) {
  0            
  0            
345 0           $s->log('debug', "Found webSocket URL", $info );
346             #$self->tab( $info );
347 0           return Future->done( $info->{webSocketDebuggerUrl} );
348 0           });
349              
350             } else {
351 0           $got_endpoint = Future->done( $endpoint );
352 0 0         if( ! ref $endpoint ) {
353             # We need to somehow find the tab id for our tab, so let's fake it:
354 0 0         $endpoint =~ m!/([^/]+)$!
355             or die "Couldn't find tab id in '$endpoint'";
356             $s->{tab} = {
357 0           targetId => $1,
358             };
359             };
360             };
361 0     0     $got_endpoint = $got_endpoint->then(sub($endpoint) {
  0            
  0            
362 0           $s->{ endpoint } = $endpoint;
363 0           return Future->done( $endpoint );
364 0     0     })->catch(sub(@args) {
  0            
  0            
365             #croak @args;
366 0           Future->fail( @args );
367 0           });
368             my $transport = delete $args{ transport }
369 0   0       || $self->transport
370             || 'Chrome::DevToolsProtocol::Transport';
371 0 0         if( ! ref $transport ) { # it's a classname
372 0           (my $transport_module = $transport) =~ s!::!/!g;
373 0           $transport_module .= '.pm';
374 0           require $transport_module;
375 0           $self->{transport} = $transport->new;
376 0           $transport = $self->{transport};
377             };
378             return $transport->connect( $self, $got_endpoint, sub {
379 0 0   0     if( $s ) {
380 0           $s->log( @_ )
381             }
382             #else {
383             # # We are in cleanup somewhere?!
384             # warn "@_";
385             #}
386             } )
387             #->on_ready(sub {
388             # use Data::Dumper;
389             # warn Dumper \@_;
390             #})
391             ->on_done(sub {
392 0     0     $s->is_connected(1);
393 0           });
394             };
395              
396             =head2 C<< ->close >>
397              
398             $driver->close();
399              
400             Shut down the connection to Chrome
401              
402             =cut
403              
404 0     0 1   sub close( $self ) {
  0            
  0            
405 0 0         if( my $t = $self->transport) {
406 0 0         if( ref $t ) {
407 0           undef $self->{transport};
408 0           $t->close();
409             };
410             };
411             };
412              
413             =head2 C<< ->sleep >>
414              
415             $driver->sleep(0.2)->get;
416              
417             Sleep for the amount of seconds in an event-loop compatible way
418              
419             =cut
420              
421 0     0 1   sub sleep( $self, $seconds ) {
  0            
  0            
  0            
422 0           $self->transport->sleep($seconds);
423             };
424              
425 0     0     sub DESTROY( $self ) {
  0            
  0            
426 0           delete $self->{ua};
427 0           $self->close;
428             }
429              
430             =head2 C<< ->one_shot >>
431              
432             my $f = $driver->one_shot('Page.domContentEventFired')->get;
433              
434             Returns a future that resolves when the event is received
435              
436             =cut
437              
438 0     0 1   sub one_shot( $self, @events ) {
  0            
  0            
  0            
439 0           my $result = $self->transport->future;
440 0           my $ref = $result;
441 0           weaken $ref;
442 0           my %events;
443 0           undef @events{ @events };
444 0           push @{ $self->_one_shot }, { events => \%events, future => \$ref };
  0            
445 0           $result
446             };
447              
448             my %stack;
449             my $r;
450 0     0 0   sub on_response( $self, $connection, $message ) {
  0            
  0            
  0            
  0            
451 0           my $response = eval { $self->json->decode( $message ) };
  0            
452 0 0         if( $@ ) {
453 0           $self->log('error', $@ );
454 0           warn $message;
455 0           return;
456             };
457              
458 0 0         if( my $fh = $self->json_log_fh ) {
459 0           say $fh $self->json->encode( { type => 'response', payload => $response } );
460             };
461              
462 0 0         if( ! exists $response->{id} ) {
463             # Generic message, dispatch that:
464 0 0         if( my $error = $response->{error} ) {
465 0           $self->log('error', "Error response from Chrome", $error );
466 0           return;
467             };
468              
469 0 0         (my $handler) = grep { exists $_->{events}->{ $response->{method} } and ${$_->{future}} } @{ $self->_one_shot };
  0            
  0            
  0            
470 0           my $handled;
471 0 0         if( $handler ) {
472 0           $self->log( 'trace', "Dispatching one-shot event", $response );
473 0           ${ $handler->{future} }->done( $response );
  0            
474              
475             # Remove the handler we just invoked
476 0 0 0       @{ $self->_one_shot } = grep { $_ and ${$_->{future}} and $_ != $handler } @{ $self->_one_shot };
  0            
  0            
  0            
  0            
477              
478 0           $handled++;
479             };
480              
481 0 0         if( my $listeners = $self->listener->{ $response->{method} } ) {
482 0           @$listeners = grep { defined $_ } @$listeners;
  0            
483             #if( $self->_log->is_trace ) {
484             # if( $response->{method} ne 'Target.receivedMessageFromTarget' ) {
485             # $self->log( 'trace', "Notifying listeners", $response );
486             # };
487             #} else {
488             # if( $response->{method} ne 'Target.receivedMessageFromTarget' ) {
489             # $self->log( 'debug', sprintf "Notifying listeners for '%s'", $response->{method} );
490             # };
491             #};
492 0           for my $listener (@$listeners) {
493 0           eval {
494 0           $listener->notify( $response );
495             };
496 0 0         $self->log('error', $@) if $@;
497 0 0         warn $@ if $@;
498             };
499             # re-weaken our references
500 0           for (0..$#$listeners) {
501 0 0         weaken $listeners->[$_]
502             if not isweak $listeners->[$_];
503             };
504             #if( $response->{method} ne 'Target.receivedMessageFromTarget' ) {
505             # $self->log('trace', "Message handled", $response);
506             #};
507              
508 0           $handled++;
509             };
510              
511 0 0         if( $self->on_message ) {
512 0 0         if( $self->_log->is_trace ) {
513 0           $self->log( 'trace', "Dispatching", $response );
514             } else {
515 0           my $frameId = $response->{params}->{frameId};
516 0           my $requestId = $response->{params}->{requestId};
517 0 0 0       if( $frameId || $requestId ) {
518 0   0       $self->log( 'debug', sprintf "Dispatching '%s' (%s:%s)", $response->{method}, $frameId || '-', $requestId || '-');
      0        
519             } else {
520 0           $self->log( 'debug', sprintf "Dispatching '%s'", $response->{method} );
521             };
522             };
523 0           $self->on_message->( $response );
524              
525 0           $handled++;
526             };
527              
528 0 0         if( ! $handled ) {
529 0 0         if( $self->_log->is_trace ) {
530 0           $self->log( 'trace', "Ignored message", $response );
531             } else {
532 0           my $frameId = $response->{params}->{frameId};
533 0           my $requestId = $response->{params}->{requestId};
534 0 0 0       if( $frameId || $requestId ) {
535 0   0       $self->log( 'debug', sprintf "Ignoring '%s' (%s:%s)", $response->{method}, $frameId || '-', $requestId || '-');
      0        
536             } else {
537 0           $self->log( 'debug', sprintf "Ignoring '%s'", $response->{method} );
538             };
539             };
540              
541             };
542             } else {
543              
544 0           my $id = $response->{id};
545 0           my $receiver = delete $self->{receivers}->{ $id };
546              
547 0 0         if( ! $receiver) {
    0          
    0          
548 0           $self->log( 'debug', "Ignored response to unknown receiver", $response )
549              
550             } elsif( $receiver eq 'ignore') {
551             # silently ignore that reply
552              
553             } elsif( $response->{error} ) {
554 0           $self->log( 'debug', "Replying to error $response->{id}", $response );
555 0   0       $receiver->die( join "\n", $response->{error}->{message},$response->{error}->{data} // '',$response->{error}->{code} // '');
      0        
556             } else {
557 0           $self->log( 'trace', "Replying to $response->{id}", $response );
558 0           $receiver->done( $response->{result} );
559             };
560             };
561             }
562              
563 0     0 0   sub next_sequence( $self ) {
  0            
  0            
564 0           my( $val ) = $self->current_sequence;
565 0           $self->sequence_number( $val+1 );
566 0           $val
567             };
568              
569 0     0 0   sub current_sequence( $self ) {
  0            
  0            
570 0           $self->sequence_number
571             };
572              
573 0     0 0   sub build_url( $self, %options ) {
  0            
  0            
  0            
574 0           my $url;
575 0 0 0       if( ! ($options{ host } || $options{ port })
      0        
576             and $self->{endpoint}) {
577             # recycle our endpoint if we have it
578 0           $url = URI->new($self->{ endpoint });
579 0           $url->scheme('http');
580 0           $url->path('json');
581 0           $url = "$url";
582             } else {
583 0           $url = URI->new('json','http');
584 0           $url->scheme('http');
585 0           $url->host( $self->host );
586 0           $url->port( $self->port );
587 0           $url = "$url";
588             };
589 0 0         $url .= '/' . $options{domain} if $options{ domain };
590 0           $url
591             };
592              
593             =head2 C<< $chrome->json_get >>
594              
595             my $data = $driver->json_get( 'version' )->get;
596              
597             Requests an URL and returns decoded JSON from the future
598              
599             =cut
600              
601 0     0 1   sub json_get($self, $domain, %options) {
  0            
  0            
  0            
  0            
602 0           my $url = $self->build_url( domain => $domain, %options );
603 0           $self->log('trace', "Fetching JSON from $url");
604 0           my $req = $self->ua->http_get( $url );
605 0     0     $req->then( sub( $payload, $headers ) {
  0            
  0            
  0            
606 0           $self->log('trace', "JSON response", $payload);
607 0           Future->done( $self->json->decode( $payload ))
608 0           });
609             };
610              
611 0     0     sub _send_packet( $self, $response, $method, %params ) {
  0            
  0            
  0            
  0            
  0            
612 0           my $id = $self->next_sequence;
613 0 0         if( $response ) {
614 0           $self->{receivers}->{ $id } = $response;
615             };
616              
617 0           my $msg = {
618             id => 0+$id,
619             method => $method,
620             params => \%params
621             };
622 0           my $payload = eval {
623 0           $self->json->encode($msg);
624             };
625 0 0         if( my $err = $@ ) {
626 0           $self->log('error', $@ );
627             };
628              
629 0 0         if( my $fh = $self->json_log_fh ) {
630 0           say $fh
631             $self->json->encode({
632             type => "message",
633             payload => $msg
634             });
635             };
636 0 0         if( $method ne 'Target.sendMessageToTarget' ) {
637 0           $self->log( 'trace', "Sent '$method' message", $payload );
638             };
639 0           my $result;
640             try {
641 0     0     $result = $self->transport->send( $payload );
642             } catch {
643 0     0     $self->log( 'error', "Sent '$method' message", $payload );
644 0           $self->log('error', $_ );
645 0           $result = Future->fail( $_ );
646 0           };
647 0           return $result
648             }
649              
650             =head2 C<< $chrome->send_packet >>
651              
652             $chrome->send_packet('Page.handleJavaScriptDialog',
653             accept => JSON::true,
654             );
655              
656             Sends a JSON packet to the remote end
657              
658             =cut
659              
660 0     0 1   sub send_packet( $self, $topic, %params ) {
  0            
  0            
  0            
  0            
661 0           $self->_send_packet( undef, $topic, %params )
662             }
663              
664             =head2 C<< $chrome->send_message >>
665              
666             my $future = $chrome->send_message('DOM.querySelectorAll',
667             selector => 'p',
668             nodeId => $node,
669             );
670             my $nodes = $future->get;
671              
672             This function expects a response. The future will not be resolved until Chrome
673             has sent a response to this query.
674              
675             =cut
676              
677 0     0 1   sub send_message( $self, $method, %params ) {
  0            
  0            
  0            
  0            
678 0           my $response = $self->future;
679 0           $self->_send_packet( $response, $method, %params )->retain;
680 0           $response
681             }
682              
683             =head2 C<< $chrome->callFunctionOn >>
684              
685             =cut
686              
687 0     0 1   sub callFunctionOn( $self, $function, %options ) {
  0            
  0            
  0            
  0            
688             $self->send_message('Runtime.callFunctionOn',
689             functionDeclaration => $function,
690             returnByValue => JSON::true,
691             arguments => $options{ arguments },
692             objectId => $options{ objectId },
693 0           %options
694             )
695             };
696              
697             =head2 C<< $chrome->evaluate >>
698              
699             =cut
700              
701 0     0 1   sub evaluate( $self, $string, %options ) {
  0            
  0            
  0            
  0            
702 0           $self->send_message('Runtime.evaluate',
703             expression => $string,
704             returnByValue => JSON::true,
705             %options
706             )
707             };
708              
709             =head2 C<< $chrome->eval >>
710              
711             my $result = $chrome->eval('2+2');
712              
713             Evaluates a Javascript string and returns the result.
714              
715             =cut
716              
717 0     0 1   sub eval( $self, $string ) {
  0            
  0            
  0            
718 0     0     $self->evaluate( $string )->then(sub( $result ) {
  0            
  0            
719             Future->done( $result->{result}->{value} )
720 0           });
  0            
721             };
722              
723             =head2 C<< $chrome->version_info >>
724              
725             print $chrome->version_info->get->{"Protocol-Version"};
726              
727             Returns the implemented ChromeDevTooslProtocol protocol version.
728              
729             =cut
730              
731 0     0 1   sub version_info($self) {
  0            
  0            
732 0     0     $self->json_get( 'version' )->then( sub( $payload ) {
  0            
  0            
733 0           Future->done( $payload );
734 0           });
735             };
736              
737             =head2 C<< $chrome->protocol_version >>
738              
739             print $chrome->protocol_version->get;
740              
741             =cut
742              
743 0     0 1   sub protocol_version($self) {
  0            
  0            
744 0     0     $self->version_info->then( sub( $payload ) {
  0            
  0            
745 0           Future->done( $payload->{"Protocol-Version"});
746 0           });
747             };
748              
749             =head2 C<< $target->getVersion >>
750              
751             Returns information about the Chrome instance we are connected to.
752              
753             =cut
754              
755 0     0 1   sub getVersion( $self ) {
  0            
  0            
756 0           $self->send_message( 'Browser.getVersion' )
757             }
758              
759             =head2 C<< $chrome->get_domains >>
760              
761             my $schema = $chrome->get_domains->get;
762              
763             Returns the topics of this Chrome DevToolsProtocol implementation.
764              
765             =cut
766              
767 0     0 1   sub get_domains( $self ) {
  0            
  0            
768 0           $self->send_message('Schema.getDomains');
769             }
770              
771             =head2 C<< $chrome->list_tabs >>
772              
773             my @tabs = $chrome->list_tabs->get();
774              
775             =cut
776              
777 0     0 1   sub list_tabs( $self, $type = 'page' ) {
  0            
  0            
  0            
778 0     0     return $self->json_get('list')->then(sub( $info ) {
  0            
  0            
779 0 0         @$info = grep { defined $type ? $_->{type} =~ /$type/i : 1 } @$info;
  0            
780 0           Future->done( @$info );
781 0           });
782             };
783              
784             =head2 C<< $chrome->new_tab >>
785              
786             my $new_tab = $chrome->new_tab('https://www.google.com')->get;
787              
788             =cut
789              
790 0     0 1   sub new_tab( $self, $url=undef, %options ) {
  0            
  0            
  0            
  0            
791             #my $u = $url ? '?' . $url : '';
792 0           $self->log('trace', "Creating new tab");
793 0           $self->createTarget( url => $url, %options );
794             };
795              
796             =head2 C<< $chrome->activate_tab >>
797              
798             $chrome->activate_tab( $tab )->get
799              
800             Brings the tab to the foreground of the application
801              
802             =cut
803              
804 0     0 1   sub activate_tab( $self, $tab ) {
  0            
  0            
  0            
805 0           my $url = $self->build_url( domain => 'activate/' . $tab->{id} );
806 0           $self->ua->http_get( $url );
807             };
808              
809             =head2 C<< $chrome->close_tab >>
810              
811             $chrome->close_tab( $tab )->get
812              
813             Closes the tab
814              
815             =cut
816              
817 0     0 1   sub close_tab( $self, $tab ) {
  0            
  0            
  0            
818 0           my $url = $self->build_url( domain => 'close/' . $tab->{id} );
819             $self->ua->http_get( $url, headers => { 'Connection' => 'close' } )
820             ->catch(
821             sub{ #use Data::Dumper; warn Dumper \@_;
822 0     0     Future->done
823 0           });
824             };
825              
826             =head2 C<< $chrome->getTargets >>
827              
828             my @targets = $chrome->getTargets->get;
829              
830             Gets the list of available targets
831              
832             =cut
833              
834 0     0 1   sub getTargets( $self ) {
  0            
  0            
835 0     0     $self->send_message('Target.getTargets')->then(sub( $info ) {
  0            
  0            
836             #use Data::Dumper; warn Dumper $info;
837 0           Future->done( @{$info->{targetInfos}})
  0            
838 0           });
839             }
840              
841             =head2 C<< $target->getTargetInfo >>
842              
843             my $info = $chrome->getTargetInfo( $targetId )->get;
844             print $info->{title};
845              
846             Returns information about the current target
847              
848             =cut
849              
850 0     0 1   sub getTargetInfo( $self, $targetId=undef ) {
  0            
  0            
  0            
851 0           $self->send_message('Target.getTargetInfo',
852             maybe targetId => $targetId
853 0     0     )->then(sub( $info ) {
  0            
854             Future->done( $info->{targetInfo})
855 0           });
  0            
856             }
857              
858             =head2 C<< $target->createTarget >>
859              
860             my $targetId = $chrome->createTarget(
861             url => 'about:blank',
862             width => 1280,
863             height => 800,
864             newWindow => JSON::false,
865             background => JSON::false,
866             )->get;
867             print $targetId;
868              
869             Creates a new target, optionally in a new window
870              
871             =cut
872              
873 0     0 1   sub createTarget( $self, %options ) {
  0            
  0            
  0            
874 0   0       $options{ url } //= 'about:blank';
875 0           $self->send_message('Target.createTarget',
876 0     0     %options )->then(sub( $info ) {
  0            
877             Future->done( $info->{targetId})
878 0           });
  0            
879             }
880              
881             =head2 C<< $target->attachToTarget >>
882              
883             my $sessionId = $chrome->attachToTarget(
884             targetId => $targetId,
885             )->get;
886             print $sessionId;
887              
888             Attaches to the target so we receive events generated by the target
889              
890             =cut
891              
892 0     0 1   sub attachToTarget( $self, %options ) {
  0            
  0            
  0            
893 0           $self->send_message('Target.attachToTarget',
894 0     0     %options )->then(sub( $info ) {
  0            
895             Future->done( $info->{sessionId})
896 0           });
  0            
897             }
898              
899             =head2 C<< $target->closeTarget >>
900              
901             my $targetId = $chrome->closeTarget(
902             targetId => $targetId
903             )->get;
904              
905             Creates a new target
906              
907             =cut
908              
909 0     0 1   sub closeTarget( $self, %options ) {
  0            
  0            
  0            
910 0           $self->send_message('Target.closeTarget',
911             %options )
912             }
913              
914             =head2 C<< $target->getWindowForTarget >>
915              
916             my $info = $chrome->getWindowForTarget( $targetId )->get;
917             print $info->{windowId};
918              
919             Returns information about the window of the current target
920              
921             =cut
922              
923 0     0 1   sub getWindowForTarget( $self, $targetId ) {
  0            
  0            
  0            
924 0           $self->send_message('Browser.getWindowForTarget',
925             targetId => $targetId
926             );
927             }
928              
929             =head2 C<< $chrome->getBrowserContexts >>
930              
931             my @browserContextIds = $chrome->getBrowserContexts->get;
932              
933             Gets the list of available browser contexts. These are separate sets of user
934             cookies etc.
935              
936             =cut
937              
938 0     0 1   sub getBrowserContexts( $self ) {
  0            
  0            
939 0     0     $self->send_message('Target.getBrowserContexts')->then(sub( $info ) {
  0            
  0            
940             #use Data::Dumper; warn Dumper $info;
941 0           Future->done( @{$info->{browserContextIds}})
  0            
942 0           });
943             }
944              
945              
946             package
947             Chrome::DevToolsProtocol::EventListener;
948 68     68   651 use strict;
  68         179  
  68         1922  
949 68     68   431 use Moo;
  68         179  
  68         628  
950 68     68   26237 use Carp 'croak';
  68         198  
  68         3633  
951 68     68   483 use Filter::signatures;
  68         168  
  68         484  
952 68     68   2581 no warnings 'experimental::signatures';
  68         190  
  68         2799  
953 68     68   495 use feature 'signatures';
  68         165  
  68         22621  
954              
955             our $VERSION = '0.69';
956              
957             has 'protocol' => (
958             is => 'ro',
959             weak_ref => 1,
960             );
961              
962             has 'callback' => (
963             is => 'ro',
964             );
965              
966             has 'event' => (
967             is => 'ro',
968             );
969              
970             around BUILDARGS => sub( $orig, $class, %args ) {
971             croak "Need an event" unless $args{ event };
972             croak "Need a callback" unless $args{ callback };
973             croak "Need a DevToolsProtocol in protocol" unless $args{ protocol };
974             return $class->$orig( %args )
975             };
976              
977 0     0 0   sub notify( $self, @info ) {
  0            
  0            
  0            
978 0           $self->callback->( @info )
979             }
980              
981 0     0 0   sub unregister( $self ) {
  0            
  0            
982 0 0         $self->protocol->remove_listener( $self )
983             if $self->protocol; # it's a weak ref so it might have gone away already
984 0           undef $self->{protocol};
985             }
986              
987             sub DESTROY {
988 0     0     $_[0]->unregister
989             }
990              
991             1;
992              
993             =head1 SEE ALSO
994              
995             The inofficial Chrome debugger API documentation at
996             L<https://github.com/buggerjs/bugger-daemon/blob/master/README.md#api>
997              
998             Chrome DevTools at L<https://chromedevtools.github.io/devtools-protocol/1-2>
999              
1000             =head1 REPOSITORY
1001              
1002             The public repository of this module is
1003             L<https://github.com/Corion/www-mechanize-chrome>.
1004              
1005             =head1 SUPPORT
1006              
1007             The public support forum of this module is L<https://perlmonks.org/>.
1008              
1009             =head1 BUG TRACKER
1010              
1011             Please report bugs in this module via the Github bug queue at
1012             L<https://github.com/Corion/WWW-Mechanize-Chrome/issues>
1013              
1014             =head1 AUTHOR
1015              
1016             Max Maischein C<corion@cpan.org>
1017              
1018             =head1 COPYRIGHT (c)
1019              
1020             Copyright 2010-2023 by Max Maischein C<corion@cpan.org>.
1021              
1022             =head1 LICENSE
1023              
1024             This module is released under the same terms as Perl itself.
1025              
1026             =cut