File Coverage

blib/lib/Chrome/DevToolsProtocol/Target.pm
Criterion Covered Total %
statement 47 418 11.2
branch 0 74 0.0
condition 0 46 0.0
subroutine 16 70 22.8
pod 23 27 85.1
total 86 635 13.5


line stmt bran cond sub pod time code
1             package Chrome::DevToolsProtocol::Target;
2 68     68   1393 use 5.010; # for //
  68         286  
3 68     68   1679 use strict;
  68         1440  
  68         1451  
4 68     68   1627 use warnings;
  68         164  
  68         1946  
5 68     68   376 use Moo;
  68         170  
  68         469  
6 68     68   25396 use Filter::signatures;
  68         1524  
  68         2964  
7 68     68   2311 no warnings 'experimental::signatures';
  68         204  
  68         2811  
8 68     68   457 use feature 'signatures';
  68         172  
  68         8161  
9 68     68   450 use Future;
  68         1270  
  68         1660  
10 68     68   430 use Future::HTTP;
  68         157  
  68         1819  
11 68     68   368 use Carp qw(croak carp);
  68         1334  
  68         3912  
12 68     68   2095 use JSON;
  68         211  
  68         4478  
13 68     68   10744 use Data::Dumper;
  68         1340  
  68         3284  
14 68     68   459 use Chrome::DevToolsProtocol::Transport;
  68         155  
  68         2063  
15 68     68   382 use Scalar::Util 'weaken', 'isweak';
  68         148  
  68         3468  
16 68     68   395 use Try::Tiny;
  68         133  
  68         3701  
17 68     68   488 use PerlX::Maybe;
  68         156  
  68         717  
18              
19             our $VERSION = '0.71';
20             our @CARP_NOT;
21              
22             =head1 NAME
23              
24             Chrome::DevToolsProtocol::Target - wrapper for talking to a page in a Target
25              
26             =head1 SYNOPSIS
27              
28             # Usually, WWW::Mechanize::Chrome automatically creates a driver for you
29             my $driver = Chrome::DevToolsProtocol::Target->new(
30             transport => $target,
31             );
32             $driver->connect( new_tab => 1 )->get
33              
34             =head1 METHODS
35              
36             =head2 C<< ->new( %args ) >>
37              
38             my $driver = Chrome::DevToolsProtocol::Target->new(
39             transport => $target,
40             auto_close => 0,
41             error_handler => sub {
42             # Reraise the error
43             croak $_[1]
44             },
45             );
46              
47             These members can mostly be set through the constructor arguments:
48              
49             =over 4
50              
51             =cut
52              
53 0     0     sub _build_log( $self ) {
  0            
  0            
54 0           require Log::Log4perl;
55 0           Log::Log4perl->get_logger(__PACKAGE__);
56             }
57              
58              
59             =item B<json>
60              
61             The JSON decoder used
62              
63             =cut
64              
65             has 'json' => (
66             is => 'ro',
67             default => sub { JSON->new },
68             );
69              
70             =item B<tab>
71              
72             Which tab to reuse (if any)
73              
74             =cut
75              
76             has 'tab' => (
77             is => 'rw',
78             );
79              
80             =item B<autoclose>
81              
82             Close the tab when the object goes out of scope
83              
84             =cut
85              
86             has 'autoclose' => (
87             is => 'rw',
88             );
89              
90             =item B<log>
91              
92             A premade L<Log::Log4perl> object to act as logger
93              
94             =cut
95              
96             has 'receivers' => (
97             is => 'ro',
98             default => sub { {} },
99             );
100              
101             =item B<on_message>
102              
103             A callback invoked for every message
104              
105             =cut
106              
107             has 'on_message' => (
108             is => 'rw',
109             default => undef,
110             );
111              
112             has '_one_shot' => (
113             is => 'ro',
114             default => sub { [] },
115             );
116              
117             has 'listener' => (
118             is => 'ro',
119             default => sub { {} },
120             );
121              
122             has 'sequence_number' => (
123             is => 'rw',
124             default => sub { 1 },
125             );
126              
127             =item B<transport>
128              
129             The event-loop specific transport backend
130              
131             =cut
132              
133             has 'transport' => (
134             is => 'ro',
135             handles => [qw[future sleep endpoint log _log
136             getTargets
137             ]],
138             );
139              
140             # This is maybe deprecated?
141             has 'targetId' => (
142             is => 'rw',
143             );
144              
145             has 'sessionId' => (
146             is => 'rw',
147             );
148              
149             has 'browserContextId' => (
150             is => 'rw',
151             );
152              
153             around BUILDARGS => sub( $orig, $class, %args ) {
154             $args{ _log } = delete $args{ 'log' };
155             $class->$orig( %args )
156             };
157              
158             =back
159              
160             =head2 C<< ->future >>
161              
162             my $f = $driver->future();
163              
164             Returns a backend-specific generic future
165              
166             =head2 C<< ->endpoint >>
167              
168             my $url = $driver->endpoint();
169              
170             Returns the URL endpoint to talk to for the connected tab
171              
172             =head2 C<< ->add_listener >>
173              
174             my $l = $driver->add_listener(
175             'Page.domContentEventFired',
176             sub {
177             warn "The DOMContent event was fired";
178             },
179             );
180              
181             # ...
182              
183             undef $l; # stop listening
184              
185             Adds a callback for the given event name. The callback will be removed once
186             the return value goes out of scope.
187              
188             =cut
189              
190 0     0 1   sub add_listener( $self, $event, $callback ) {
  0            
  0            
  0            
  0            
191 0           my $listener = Chrome::DevToolsProtocol::EventListener->new(
192             protocol => $self,
193             callback => $callback,
194             event => $event,
195             );
196 0   0       $self->listener->{ $event } ||= [];
197 0           push @{ $self->listener->{ $event }}, $listener;
  0            
198 0           weaken $self->listener->{ $event }->[-1];
199 0           $listener
200             }
201              
202             =head2 C<< ->remove_listener >>
203              
204             $driver->remove_listener($l);
205              
206             Explicitly remove a listener.
207              
208             =cut
209              
210 0     0 1   sub remove_listener( $self, $listener ) {
  0            
  0            
  0            
211             # $listener->{event} can be undef during global destruction
212 0 0         if( my $event = $listener->event ) {
213 0   0       my $l = $self->listener->{ $event } ||= [];
214 0           @{$l} = grep { $_ != $listener }
  0            
215 0           grep { defined $_ }
216 0           @{$self->listener->{ $event }};
  0            
217             # re-weaken our references
218 0           for (0..$#$l) {
219 0           weaken $l->[$_];
220             };
221             };
222             }
223              
224             =head2 C<< ->log >>
225              
226             $driver->log('debug', "Warbling doodads", { doodad => 'this' } );
227              
228             Log a message
229              
230             =head2 C<< ->connect >>
231              
232             my $f = $driver->connect()->get;
233              
234             Asynchronously connect to the Chrome browser, returning a Future.
235              
236             =cut
237              
238 0     0 1   sub connect( $self, %args ) {
  0            
  0            
  0            
239 0           my $s = $self;
240 0           weaken $s;
241 0 0         my $done = $self->transport->is_connected
242             ? Future->done
243             : $self->transport->connect();
244              
245             $done = $done->then(sub {
246             $s->{l} = $s->transport->add_listener('Target.receivedMessageFromTarget', sub {
247 0 0         if( $s ) {
248             #$s->log( 'trace', '(target) receivedMessage', $_[0] );
249 0           my $id = $s->targetId;
250 0           my $sid = $s->sessionId;
251 0 0 0       if( exists $_[0]->{params}->{sessionId}
    0 0        
      0        
      0        
252             and $sid
253             and $_[0]->{params}->{sessionId} eq $sid) {
254 0           my $payload = $_[0]->{params}->{message};
255 0           $s->on_response( undef, $payload );
256             } elsif( !$id
257             or ($_[0]->{params}->{targetId} and $id eq $_[0]->{params}->{targetId})) {
258 0           my $payload = $_[0]->{params}->{message};
259 0           $s->on_response( undef, $payload );
260             };
261             #} else {
262             # warn "Target listener for answers has gone away";
263             # use Data::Dumper; warn Dumper($_[0]);
264             };
265 0     0     });
266 0           Future->done;
267 0           });
268              
269 0 0 0       if( $args{ new_tab } ) { # should be renamed "separate_session"
    0          
    0          
    0          
    0          
270 0 0         if( $args{ separate_session }) {
271             # Set up a new browser context
272 0     0     $done = $done->then( sub { $s->transport->send_message('Target.createBrowserContext')})
273 0     0     ->then( sub( $info ) {
  0            
  0            
274 0           $s->browserContextId( $info->{browserContextId} );
275 0           Future->done();
276 0           });
277              
278             } else {
279             # Find an existing browser context and use that one
280 0     0     $done = $done->then( sub { $s->getTargets })
281 0     0     ->then( sub( @targets ) {
  0            
  0            
282             #$self->browserContextId( $targets[0]->{browserContextId} );
283 0           Future->done();
284 0           });
285             }
286              
287             $done = $done->then(sub {
288 0     0     my $id = $s->browserContextId;
289              
290             $s->createTarget(
291 0   0       url => $args{ start_url } || 'about:blank',
292             maybe browserContextId => $id,
293             );
294 0     0     })->then(sub( $info ) {
  0            
  0            
295 0           $s->tab( $info );
296             $s->attach( $info->{targetId} )
297 0           });
  0            
298              
299             } elsif( ref $args{ tab } eq 'Regexp') {
300             # Let's assume that the tab is a regex:
301              
302             $done = $done->then(sub {
303 0     0     $s->getTargets()
304 0     0     })->then(sub( @tabs ) {
  0            
  0            
305 0           (my $tab) = grep { $_->{title} =~ /$args{ tab }/ } @tabs;
  0            
306              
307 0 0         if( ! $tab ) {
    0          
308 0           $s->log('warn', "Couldn't find a tab matching /$args{ tab }/");
309 0           croak "Couldn't find a tab matching /$args{ tab }/";
310             } elsif( ! $tab->{targetId} ) {
311 0           local @CARP_NOT = ('Future',@CARP_NOT);
312 0           croak "Found the tab but it didn't have a targetId";
313             };
314 0           $s->tab( $tab );
315             $s->attach( $tab->{targetId} )
316 0           });
  0            
317              
318             } elsif( ref $args{ tab } ) {
319             # Let's assume that the tab is a Target hash:
320 0           my $tab = $args{ tab };
321 0           $self->tab($tab);
322             $done = $done->then(sub {
323 0     0     $s->attach( $s->tab->{targetId});
324 0           });
325              
326             } elsif( defined $args{ tab } and $args{ tab } =~ /^\d{1,5}$/ ) {
327             $done = $done->then(sub {
328 0     0     $s->getTargets()
329 0     0     })->then(sub( @tabs ) {
  0            
  0            
330 0           my $res;
331 0 0         my @visible_tabs = grep { $_->{type} eq 'page' && $_->{targetId} } @tabs;
  0            
332 0 0         if( ! @visible_tabs ) {
333             $res = $s->createTarget(
334 0   0       url => $args{ start_url } || 'about:blank',
335             );
336             } else {
337 0           $res = Future->done( $visible_tabs[$args{ tab }] );
338             };
339 0           $res = $res->then(sub($tab) {
340 0           $s->tab( $tab );
341 0           $s->attach( $s->tab->{targetId} );
342 0           });
343 0           });
344              
345             } elsif( $args{ tab } ) {
346             # Let's assume that the tab is the tab id:
347             $done = $done->then(sub {
348             $s->getTargetInfo( $args{tab})
349 0     0     })->then(sub( $tab ) {
  0            
  0            
  0            
350 0           $s->tab($tab);
351 0           $s->attach( $tab->{targetId});
352 0           });
353              
354             } else {
355             # Attach to the first available tab we find
356 0     0     $done = $done->then(sub (@) {
  0            
357 0           $s->getTargets()
358 0     0     })->then(sub( @tabs ) {
  0            
  0            
359 0 0         (my $tab) = grep { $_->{type} eq 'page' && $_->{targetId} } @tabs;
  0            
360 0           $s->tab($tab);
361             $s->attach( $tab->{targetId} )
362 0           });
  0            
363             };
364              
365 0           $done
366             };
367              
368             =head2 C<< ->close >>
369              
370             $driver->close();
371              
372             Shut down the connection to our tab and close it.
373              
374             =cut
375              
376 0     0 1   sub close( $self ) {
  0            
  0            
377 0           $self->transport->closeTarget(targetId => $self->targetId );
378             }
379              
380 0     0     sub DESTROY( $self ) {
  0            
  0            
381 0 0         if( $self->autoclose ) {
382 0     0     $self->close->catch(sub {})->retain;
383             }
384             };
385              
386             =head2 C<< ->sleep >>
387              
388             $driver->sleep(0.2)->get;
389              
390             Sleep for the amount of seconds in an event-loop compatible way
391              
392             =head2 C<< ->one_shot >>
393              
394             my $f = $driver->one_shot('Page.domContentEventFired')->get;
395              
396             Returns a future that resolves when the event is received
397              
398             =cut
399              
400 0     0 1   sub one_shot( $self, @events ) {
  0            
  0            
  0            
401 0           my $result = $self->future;
402 0           my $ref = $result;
403 0           weaken $ref;
404 0           my %events;
405 0           undef @events{ @events };
406 0           push @{ $self->_one_shot }, { events => \%events, future => \$ref };
  0            
407 0           $result
408             };
409              
410             my %stack;
411             my $r;
412 0     0 0   sub on_response( $self, $connection, $message ) {
  0            
  0            
  0            
  0            
413 0           my $response = eval { $self->json->decode( $message ) };
  0            
414 0 0         if( $@ ) {
415 0           $self->log('error', $@ );
416 0           warn $message;
417 0           return;
418             };
419              
420 0 0         if( ! exists $response->{id} ) {
421             # Generic message, dispatch that:
422 0 0         if( my $error = $response->{error} ) {
423 0           $self->log('error', "Error response from Chrome", $error );
424 0           return;
425             };
426              
427 0 0         (my $handler) = grep { exists $_->{events}->{ $response->{method} } and ${$_->{future}} } @{ $self->_one_shot };
  0            
  0            
  0            
428 0           my $handled;
429 0 0         if( $handler ) {
430 0           $self->log( 'trace', "Dispatching one-shot event", $response );
431 0           ${ $handler->{future} }->done( $response );
  0            
432              
433             # Remove the handler we just invoked
434 0 0 0       @{ $self->_one_shot } = grep { $_ and ${$_->{future}} and $_ != $handler } @{ $self->_one_shot };
  0            
  0            
  0            
  0            
435              
436 0           $handled++;
437             };
438              
439 0 0         if( my $listeners = $self->listener->{ $response->{method} } ) {
440 0           @$listeners = grep { defined $_ } @$listeners;
  0            
441 0 0         if( $self->_log->is_trace ) {
442 0           $self->log( 'trace', "Notifying listeners", $response );
443             } else {
444 0           $self->log( 'debug', sprintf "Notifying listeners for '%s'", $response->{method} );
445             };
446 0           for my $listener (@$listeners) {
447 0           eval {
448 0           $listener->notify( $response );
449             };
450 0 0         warn $@ if $@;
451             };
452             # re-weaken our references
453 0           for (0..$#$listeners) {
454 0           weaken $listeners->[$_];
455             };
456              
457 0           $handled++;
458             };
459              
460 0 0         if( $self->on_message ) {
461 0 0         if( $self->transport->_log->is_trace ) {
462 0           $self->log( 'trace', "Dispatching", $response );
463             } else {
464 0           my $frameId = $response->{params}->{frameId};
465 0           my $requestId = $response->{params}->{requestId};
466 0 0 0       if( $frameId || $requestId ) {
467 0   0       $self->log( 'debug', sprintf "Dispatching '%s' (%s:%s)", $response->{method}, $frameId || '-', $requestId || '-');
      0        
468             } else {
469 0           $self->log( 'debug', sprintf "Dispatching '%s'", $response->{method} );
470             };
471             };
472 0           $self->on_message->( $response );
473              
474 0           $handled++;
475             };
476              
477 0 0         if( ! $handled ) {
478 0 0         if( $self->_log->is_trace ) {
479 0           $self->log( 'trace', "Ignored message", $response );
480             } else {
481 0           my $frameId = $response->{params}->{frameId};
482 0           my $requestId = $response->{params}->{requestId};
483 0 0 0       if( $frameId || $requestId ) {
484 0   0       $self->log( 'debug', sprintf "Ignoring '%s' (%s:%s)", $response->{method}, $frameId || '-', $requestId || '-');
      0        
485             } else {
486 0           $self->log( 'debug', sprintf "Ignoring '%s'", $response->{method} );
487             };
488             };
489              
490             };
491             } else {
492              
493 0           my $id = $response->{id};
494 0           my $receiver = delete $self->{receivers}->{ $id };
495              
496 0 0         if( ! $receiver) {
    0          
    0          
497 0           $self->log( 'debug', "Ignored response to unknown receiver", $response )
498              
499             } elsif( $receiver eq 'ignore') {
500             # silently ignore that reply
501              
502             } elsif( $response->{error} ) {
503 0           $self->log( 'debug', "Replying to error $response->{id}", $response );
504 0   0       $receiver->die( join "\n", $response->{error}->{message},$response->{error}->{data} // '',$response->{error}->{code} // '');
      0        
505             } else {
506 0           $self->log( 'trace', "Replying to $response->{id}", $response );
507 0           $receiver->done( $response->{result} );
508             };
509             };
510             }
511              
512 0     0 0   sub next_sequence( $self ) {
  0            
  0            
513 0           my( $val ) = $self->current_sequence;
514 0           $self->sequence_number( $val+1 );
515 0           $val
516             };
517              
518 0     0 0   sub current_sequence( $self ) {
  0            
  0            
519 0           $self->sequence_number
520             };
521              
522 0     0 0   sub build_url( $self, %options ) {
  0            
  0            
  0            
523 0           croak "$self can't build URLs";
524             };
525              
526             =head2 C<< $chrome->json_get >>
527              
528             my $data = $driver->json_get( 'version' )->get;
529              
530             Requests an URL and returns decoded JSON from the future
531              
532             =cut
533              
534 0     0 1   sub json_get($self, $domain, %options) {
  0            
  0            
  0            
  0            
535 0           croak "$self can't GET JSON data";
536             };
537              
538             =head2 C<< $chrome->version_info >>
539              
540             print $chrome->version_info->get->{"protocolVersion"};
541              
542             =cut
543              
544 0     0 1   sub version_info( $self ) {
  0            
  0            
545 0           $self->getVersion
546             }
547              
548             =head2 C<< $chrome->protocol_version >>
549              
550             print $chrome->protocol_version->get;
551              
552             =cut
553              
554 0     0 1   sub protocol_version( $self ) {
  0            
  0            
555 0     0     $self->getVersion->then(sub( $info ) {
  0            
  0            
556 0           Future->done($info->{"protocolVersion"})
557             })
558 0           }
559              
560 0     0     sub _send_packet( $self, $response, $method, %params ) {
  0            
  0            
  0            
  0            
  0            
561 0           my $id = $self->next_sequence;
562 0 0         if( $response ) {
563 0           $self->{receivers}->{ $id } = $response;
564             };
565              
566 0           my $s = $self;
567 0           weaken $s;
568              
569 0           my $payload = eval {
570 0           $self->json->encode({
571             id => 0+$id,
572             method => $method,
573             params => \%params
574             });
575             };
576 0 0         if( my $err = $@ ) {
577 0           $self->log('error', $@ );
578 0           $self->log('error', Dumper \%params );
579             };
580              
581 0           $self->log( 'trace', "Sent message", $payload );
582 0           my $result;
583             try {
584             # this is half right - we get an ack when the message was accepted
585             # but we want to send the real reply when it comes back from the
586             # real target. This is done in the listener for receivedMessageFromTarget
587             #my $ignore = $s->future->retain;
588 0     0     $result = $s->transport->_send_packet(
589             #$ignore, # this one leads to a circular reference somewhere when using AnyEvent backends?!
590             'ignore',
591             'Target.sendMessageToTarget',
592             message => $payload,
593             targetId => $s->targetId,
594             maybe sessionId => $s->sessionId,
595             );
596             } catch {
597 0     0     $self->log('error', $_ );
598 0           $result = Future->fail( $_ );
599 0           };
600 0           return $result
601             }
602              
603             =head2 C<< $chrome->send_packet >>
604              
605             $chrome->send_packet('Page.handleJavaScriptDialog',
606             accept => JSON::true,
607             );
608              
609             Sends a JSON packet to the remote end
610              
611             =cut
612              
613 0     0 1   sub send_packet( $self, $topic, %params ) {
  0            
  0            
  0            
  0            
614 0           $self->_send_packet( undef, $topic, %params )
615             }
616              
617             =head2 C<< $chrome->send_message >>
618              
619             my $future = $chrome->send_message('DOM.querySelectorAll',
620             selector => 'p',
621             nodeId => $node,
622             );
623             my $nodes = $future->get;
624              
625             This function expects a response. The future will not be resolved until Chrome
626             has sent a response to this query.
627              
628             =cut
629              
630 0     0 1   sub send_message( $self, $method, %params ) {
  0            
  0            
  0            
  0            
631 0           my $response = $self->future;
632             # We add our response listener before we've even sent our request to
633             # Chrome. This ensures that no amount of buffering etc. will make us
634             # miss a reply from Chrome to a request
635 0           my $f = $self->_send_packet( $response, $method, %params )->retain;
636 0           $response
637             }
638              
639             =head2 C<< $chrome->callFunctionOn >>
640              
641             =cut
642              
643 0     0 1   sub callFunctionOn( $self, $function, %options ) {
  0            
  0            
  0            
  0            
644             $self->send_message('Runtime.callFunctionOn',
645             functionDeclaration => $function,
646             returnByValue => JSON::true,
647             arguments => $options{ arguments },
648             objectId => $options{ objectId },
649 0           %options
650             )
651             };
652              
653             =head2 C<< $chrome->evaluate >>
654              
655             =cut
656              
657 0     0 1   sub evaluate( $self, $string, %options ) {
  0            
  0            
  0            
  0            
658 0           $self->send_message('Runtime.evaluate',
659             expression => $string,
660             returnByValue => JSON::true,
661             %options
662             )
663             };
664              
665             =head2 C<< $chrome->eval >>
666              
667             =cut
668              
669 0     0 1   sub eval( $self, $string ) {
  0            
  0            
  0            
670 0     0     $self->evaluate( $string )->then(sub( $result ) {
  0            
  0            
671             Future->done( $result->{result}->{value} )
672 0           });
  0            
673             };
674              
675             =head2 C<< $chrome->get_domains >>
676              
677             =cut
678              
679 0     0 1   sub get_domains( $self ) {
  0            
  0            
680 0           $self->send_message('Schema.getDomains');
681             }
682              
683             =head2 C<< $chrome->list_tabs >>
684              
685             my @tabs = $chrome->list_tabs->get();
686              
687             =cut
688              
689 0     0 1   sub list_tabs( $self, $type = 'page' ) {
  0            
  0            
  0            
690 0           croak "Won't list tabs, even though I could";
691             };
692              
693             =head2 C<< $chrome->new_tab >>
694              
695             my $new_tab = $chrome->new_tab('https://www.google.com')->get;
696              
697             =cut
698              
699 0     0 1   sub new_tab( $self, $url=undef ) {
  0            
  0            
  0            
700 0           croak "Won't create tabs, even though I could";
701             };
702              
703             =head2 C<< $chrome->activate_tab >>
704              
705             $chrome->activate_tab( $tab )->get
706              
707             Brings the tab to the foreground of the application
708              
709             =cut
710              
711 0     0 1   sub activate_tab( $self, $tab ) {
  0            
  0            
  0            
712 0           croak "Won't activate tabs, even though I could";
713             };
714              
715             =head2 C<< $target->getTargetInfo >>
716              
717             Returns information about the current target
718              
719             =cut
720              
721 0     0 1   sub getTargetInfo( $self, $targetId = $self->targetId ) {
  0            
  0            
  0            
722 0     0     $self->transport->getTargetInfo( $targetId )->then(sub( $info ) {
  0            
  0            
723 0           Future->done( $info )
724 0           });
725             }
726              
727             =head2 C<< $target->info >>
728              
729             Returns information about the current target
730              
731             =cut
732              
733 0     0 1   sub info( $self ) {
  0            
  0            
734 0           $self->getTargetInfo( $self->targetId )->get
735             }
736              
737             =head2 C<< $target->title >>
738              
739             Returns the title of the current target
740              
741             =cut
742              
743 0     0 1   sub title( $self ) {
  0            
  0            
744             $self->getTargetInfo( $self->targetId )->get->{title}
745 0           }
746              
747             =head2 C<< $target->getVersion >>
748              
749             Returns information about the Chrome instance we are connected to.
750              
751             =cut
752              
753 0     0 1   sub getVersion( $self ) {
  0            
  0            
754 0           $self->send_message( 'Browser.getVersion' )
755             }
756              
757             =head2 C<< $target->createTarget >>
758              
759             my $info = $chrome->createTarget(
760             url => 'about:blank',
761             width => 1280,
762             height => 800,
763             newWindow => JSON::false,
764             background => JSON::false,
765             )->get;
766             print $info->{targetId};
767              
768             Creates a new target
769              
770             =cut
771              
772 0     0 1   sub createTarget( $self, %options ) {
  0            
  0            
  0            
773 0   0       $options{ url } //= 'about:blank';
774 0           $self->transport->send_message('Target.createTarget',
775 0     0     %options )->then(sub( $info ) {
  0            
776 0           Future->done( $info )
777 0           });
778             }
779              
780              
781             =head2 C<< $target->attach >>
782              
783             $target->attach();
784              
785             Attaches to the target set up in C<targetId> and C<sessionId>. If a targetId is
786             given, attaches to it and remembers the value.
787              
788             =cut
789              
790 0     0 1   sub attach( $self, $targetId=$self->targetId ) {
  0            
  0            
  0            
791 0           my $s = $self;
792 0           weaken $s;
793 0           $self->targetId( $targetId );
794              
795 0     0     $self->{have_target_info} = $self->transport->one_shot('Target.attachedToTarget')->then(sub($r) {
  0            
  0            
796             # #$s->log('trace', "Attached to", $r );
797             #use Data::Dumper; warn Dumper $r;
798             #$s->browserContextId($r->{params}->{targetInfo}->{browserContextId});
799             # #$s->sessionId( $target->{sessionId});
800             # #$s->log('debug', "Attached to session $target->{sessionId}" );
801             # #undef $s->{have_session};
802 0           return Future->done;
803 0           })->retain;
804              
805 0           $self->transport->attachToTarget( targetId => $targetId )
806 0     0     ->on_done(sub( $sessionId ) {
  0            
807 0           $s->sessionId( $sessionId );
808 0           $s->log('debug', "Attached to tab $targetId, session $sessionId" );
809 0           });
810             };
811              
812             1;
813              
814             =head1 SEE ALSO
815              
816             The inofficial Chrome debugger API documentation at
817             L<https://github.com/buggerjs/bugger-daemon/blob/master/README.md#api>
818              
819             Chrome DevTools at L<https://chromedevtools.github.io/devtools-protocol/1-2>
820              
821             =head1 REPOSITORY
822              
823             The public repository of this module is
824             L<https://github.com/Corion/www-mechanize-chrome>.
825              
826             =head1 SUPPORT
827              
828             The public support forum of this module is L<https://perlmonks.org/>.
829              
830             =head1 BUG TRACKER
831              
832             Please report bugs in this module via the Github bug queue at
833             L<https://github.com/Corion/WWW-Mechanize-Chrome/issues>
834              
835             =head1 AUTHOR
836              
837             Max Maischein C<corion@cpan.org>
838              
839             =head1 COPYRIGHT (c)
840              
841             Copyright 2010-2023 by Max Maischein C<corion@cpan.org>.
842              
843             =head1 LICENSE
844              
845             This module is released under the same terms as Perl itself.
846              
847             =cut