File Coverage

blib/lib/WWW/Mechanize/Chrome/DOMops.pm
Criterion Covered Total %
statement 14 148 9.4
branch 0 70 0.0
condition 0 36 0.0
subroutine 5 7 71.4
pod 2 2 100.0
total 21 263 7.9


line stmt bran cond sub pod time code
1             package WWW::Mechanize::Chrome::DOMops;
2              
3 3     3   1054520 use 5.006;
  3         29  
4 3     3   15 use strict;
  3         10  
  3         58  
5 3     3   12 use warnings;
  3         7  
  3         98  
6              
7 3     3   18 use Exporter qw(import);
  3         5  
  3         201  
8             our @EXPORT = qw(
9             zap
10             find
11             VERBOSE_DOMops
12             );
13              
14 3     3   1154 use Data::Roundtrip qw/perl2dump no-unicode-escape-permanently/;
  3         63169  
  3         25  
15            
16             our $VERSION = '0.06';
17              
18             # caller can set this to 0,1,2,3
19             our $VERBOSE_DOMops = 0;
20              
21             # Here are JS helpers. We use these in our own internal JS code.
22             # They are visible to the user's JS callbacks.
23             my $_aux_js_functions = <<'EOJ';
24             const getAllChildren = (htmlElement) => {
25             if( (htmlElement === null) || (htmlElement === undefined) ){
26             console.log("getAllChildren() : warning null input");
27             return [];
28             }
29             if( VERBOSE_DOMops > 1 ){ console.log("getAllChildren() : called for element '"+htmlElement+"' with tag '"+htmlElement.tagName+"' and id '"+htmlElement.id+"' ..."); }
30              
31             if (htmlElement.children.length === 0) return [htmlElement];
32              
33             let allChildElements = [];
34              
35             for (let i = 0; i < htmlElement.children.length; i++) {
36             let children = getAllChildren(htmlElement.children[i]);
37             if (children) allChildElements.push(...children);
38             }
39             allChildElements.push(htmlElement);
40              
41             return allChildElements;
42             };
43             EOJ
44              
45             # The input is a hashref of parameters
46             # the 'element-*' parameters specify some condition to be matched
47             # for example id to be such and such.
48             # The conditions can be combined either as a union (OR)
49             # or an intersection (AND). Default is intersection.
50             # The param || => 1 changes this to Union.
51             #
52             # returns -3 parameters error
53             # returns -2 if javascript failed
54             # returns -1 if one or more of the specified selectors failed to match
55             # returns >=0 : the number of elements matched
56             sub find {
57 0     0 1   my $params = $_[0];
58 0   0       my $parent = ( caller(1) )[3] || "N/A";
59 0           my $whoami = ( caller(0) )[3];
60              
61 0 0         if( $WWW::Mechanize::Chrome::DOMops::VERBOSE_DOMops > 0 ){ print STDOUT "$whoami (via $parent) : called ...\n" }
  0            
62              
63 0 0         my $amech_obj = exists($params->{'mech-obj'}) ? $params->{'mech-obj'} : undef;
64 0 0         if( ! $amech_obj ){
65 0           my $anerrmsg = "$whoami (via $parent) : a mech-object is required via 'mech-obj'.";
66 0           print STDERR $anerrmsg."\n";
67             return {
68 0           'status' => -3,
69             'message' => $anerrmsg
70             }
71             }
72 0 0         my $js_outfile = exists($params->{'js-outfile'}) ? $params->{'js-outfile'} : undef;
73              
74             # html element selectors:
75             # e.g. params->{'element-name'} = ['a','b'] or params->{'element-name'} = 'a'
76 0           my @known_selectors = ('element-name', 'element-class', 'element-tag', 'element-id', 'element-cssselector');
77 0           my (%selectors, $have_a_selector, $m);
78 0           for my $asel (@known_selectors){
79 0 0 0       next unless exists($params->{$asel}) and defined($params->{$asel});
80 0 0         if( ref($params->{$asel}) eq '' ){
    0          
81 0           $selectors{$asel} = '["' . $params->{$asel} . '"]';
82             } elsif( ref($params->{$asel}) eq 'ARRAY' ){
83 0           $selectors{$asel} = '["' . join('","', @{$params->{$asel}}) . '"]';
  0            
84             } else {
85 0           my $anerrmsg = "$whoami (via $parent) : error, parameter '$asel' expects a scalar or an ARRAYref and not '".ref($params->{$asel})."'.";
86 0           print STDERR $anerrmsg."\n";
87             return {
88 0           'status' => -3,
89             'message' => $anerrmsg
90             }
91             }
92 0           $have_a_selector = 1;
93 0 0         if( $WWW::Mechanize::Chrome::DOMops::VERBOSE_DOMops > 1 ){ print STDOUT "$whoami (via $parent) : found selector '$asel' with value '".$selectors{$asel}."'.\n" }
  0            
94             }
95 0 0         if( not $have_a_selector ){
96 0           my $anerrmsg = "$whoami (via $parent) : at least one selector must be specified by supplying one or more parameters from these: '".join("','", @known_selectors)."'.";
97 0           print STDERR $anerrmsg."\n";
98             return {
99 0           'status' => -3,
100             'message' => $anerrmsg
101             }
102             }
103              
104             # If specified it will add an ID to any html element which does not have an ID (field id).
105             # The ID will be prefixed by this string and have an incrementing counter postfixed
106 0           my $insert_id_if_none;
107 0 0 0       if( exists($params->{'insert-id-if-none-random'}) && defined($params->{'insert-id-if-none-random'}) ){
    0 0        
108             # we are given a prefix and also asked to add our own rands
109 0           $insert_id_if_none = $params->{'insert-id-if-none-random'} . int(rand(1_000_000)) . int(rand(1_000_000)) . int(rand(1_000_000));
110             } elsif( exists($params->{'insert-id-if-none'}) && defined($params->{'insert-id-if-none'}) ){
111             # we are given a prefix and no randomisation, both cases we will be adding the counter at the end
112 0           $insert_id_if_none = $params->{'insert-id-if-none'};
113             }
114              
115             # these callbacks are pieces of javascript code to execute but they should not have the function
116             # preamble or postamble, just the function content. The parameter 'htmlElement' is what
117             # we pass in and it is the currently matched HTML element.
118             # whatever the callback returns (including nothing = undef) will be recorded
119             # The callbacks are in an array with keys 'code' and 'name'.
120             # The callbacks are executed in the same order they have in this array
121             # the results are recorded in the same order in an array, one result for one htmlElement matched.
122             # callback(s) to execute for each html element matched in the 1st level (that is, not including children of match)
123 0           my @known_callbacks = ('find-cb-on-matched', 'find-cb-on-matched-and-their-children');
124 0           my %callbacks;
125 0           for my $acbname (@known_callbacks){
126 0 0 0       if( exists($params->{$acbname}) && defined($m=$params->{$acbname}) ){
127             # one or more callbacks must be contained in an ARRAY
128 0 0         if( ref($m) ne 'ARRAY' ){
129 0           my $anerrmsg = "$whoami (via $parent) : error callback parameter '$acbname' must be an array of hashes each containing a 'code' and a 'name' field. You supplied a '".ref($m)."'.";
130 0           print STDERR $anerrmsg."\n";
131 0           return { 'status' => -3, 'message' => $anerrmsg }
132             }
133 0           for my $acbitem (@$m){
134             # each callback must be a hash with a 'code' and 'name' key
135 0 0 0       if( ! exists($acbitem->{'code'})
136             || ! exists($acbitem->{'name'})
137             ){
138 0           my $anerrmsg = "$whoami (via $parent) : error callback parameter '$acbname' must be an array of hashes each containing a 'code' and a 'name' field.";
139 0           print STDERR $anerrmsg."\n";
140 0           return { 'status' => -3, 'message' => $anerrmsg }
141             }
142             }
143 0           $callbacks{$acbname} = $m;
144 0 0         if( $WWW::Mechanize::Chrome::DOMops::VERBOSE_DOMops > 0 ){ print STDOUT "$whoami (via $parent) : adding ".scalar(@$m)." callback(s) of type '$acbname' ...\n" }
  0            
145             }
146             }
147              
148             # each specifier yields a list each, how to combine this list?:
149             # intersection (default): specified with '||' => 0 or '&&' => 1 in params,
150             # the list is produced by the intersection set of all individual result sets (elements-by-name, by-id, etc.)
151             # This means an item must exist in ALL result sets which were specified by the caller.
152             # or
153             # union: specified with '||' => 1 or '&&' => 0 in params
154             # the list is produced by the union set of all individual result sets (elements-by-name, by-id, etc.)
155             # This means an item must exist in just one result set specified by the caller.
156             # Remember that the caller can specify elements by name ('element-name' => '...'), by id, by tag etc.
157             my $Union = (exists($params->{'||'}) && defined($params->{'||'}) && ($params->{'||'} == 1))
158 0   0       || (exists($params->{'&&'}) && defined($params->{'&&'}) && ($params->{'&&'} == 0))
159             || 0 # <<< default is intersection (superfluous but verbose)
160             ;
161              
162 0 0         if( $WWW::Mechanize::Chrome::DOMops::VERBOSE_DOMops > 1 ){ print "$whoami (via $parent) : using ".($Union?'UNION':'INTERSECTION')." to combine the matched elements.\n"; }
  0 0          
163             # there is no way to break a JS eval'ed via perl and return something back unless
164             # one uses gotos or an anonymous function, see
165             # https://www.perlmonks.org/index.pl?node_id=1232479
166             # Here we are preparing JS code to be eval'ed in the page
167              
168             # do we have user-specified JS code for adjusting the return value of each match?
169             # this falls under the 'element-information-from-matched' input parameter
170             # if we don't have we use our own default so this JS function will be used always for extracting info from matched
171             # NOTE: a user-specified must make sure that it returns a HASH
172 0           my $element_information_from_matched_function = "const element_information_from_matched_function = (htmlElement) => {\n";
173 0 0 0       if( exists($params->{'element-information-from-matched'}) && defined($m=$params->{'element-information-from-matched'}) ){
174 0           $element_information_from_matched_function .= $m;
175             } else {
176             # there is no user-specified function for extracting info from each matched element, so use our own default:
177 0           $element_information_from_matched_function .= "\t" . 'return {"tag" : htmlElement.tagName, "id" : htmlElement.id};';
178             }
179 0           $element_information_from_matched_function .= "\n} // end element_information_from_matched_function\n";
180              
181             # do we have user-specified JS code for callbacks?
182             # this falls under the 'find-cb-on-matched' and 'find-cb-on-matched-and-their-children' input parameters
183 0           my $cb_functions = "const cb_functions = {\n";
184 0           for my $acbname (@known_callbacks){
185 0 0         next unless exists $callbacks{$acbname};
186 0           $m = $callbacks{$acbname};
187 0           $cb_functions .= " \"${acbname}\" : [\n";
188 0           for my $acb (@$m){
189 0           my $code = $acb->{'code'};
190 0           my $name = $acb->{'name'}; # something to identify it with, can contain any chars etc.
191 0           $cb_functions .= <<EOJ;
192             {"code" : (htmlElement) => { ${code} }, "name" : "${name}"},
193             EOJ
194             }
195 0           $cb_functions =~ s/,\n$//m;
196 0           $cb_functions .= "\n ],\n";
197             }
198 0           $cb_functions =~ s/,\n*$//s;
199 0           $cb_functions .= "\n};";
200              
201             # This is the JS code to execute, we restrict its scope
202             # TODO: don't accumulate JS code for repeated find() calls on the same mech obj
203             # perhaps create a class which is overwritten on multiple calls?
204 0           my $jsexec = '{ /* our own scope */' # <<< run it inside its own scope because multiple mech->eval() accumulate and global vars are re-declared etc.
205             . "\n\nconst VERBOSE_DOMops = ${WWW::Mechanize::Chrome::DOMops::VERBOSE_DOMops};\n\n"
206             . $_aux_js_functions . "\n\n"
207             . $element_information_from_matched_function . "\n\n"
208             . $cb_functions . "\n\n"
209             # and now here-doc an anonymous function which will be called to orchestrate the whole operation, avanti maestro!
210             . <<'EOJ';
211             // the return value of this anonymous function is what perl's eval will get back
212             (function(){
213             var retval = -1; // this is what we return
214             // returns -1 for when one of the element searches matched nothing
215             // returns 0 if after intersection/union nothing was found to delete
216             // returns >0 : the number of elements deleted
217             var anelem, anelems, i, j;
218             var allfound = [];
219             var allfound_including_children = [];
220             var elems = [];
221             EOJ
222 0           for my $asel (@known_selectors){ $jsexec .= "\telems['${asel}'] = null;\n"; }
  0            
223 0           $jsexec .= <<EOJ;
224             const union = ${Union};
225             EOJ
226 0 0         $jsexec .= "\tconst insert_id_if_none = ".(defined($insert_id_if_none) ? "'${insert_id_if_none}'" : "null").";\n";
227 0           $jsexec .= "\tconst known_callbacks = [\"" . join('", "', @known_callbacks) . "\"];\n";
228 0           my %selfuncs = (
229             'element-class' => 'document.getElementsByClassName',
230             'element-tag' => 'document.getElementsByTagName',
231             'element-name' => 'document.getElementsByName',
232             'element-id' => 'document.getElementById',
233             'element-cssselector' => 'document.querySelectorAll'
234             );
235 0           for my $aselname (keys %selectors){
236 0           my $selfunc = $selfuncs{$aselname};
237 0           my $aselvalue = $selectors{$aselname};
238 0           $jsexec .= <<EOJ;
239             // selector '${aselname}' was specified: ${aselvalue}
240             for(let asel of ${aselvalue}){
241             // this can return an array or a single html element (e.g. in ById)
242             if( VERBOSE_DOMops > 1 ){ console.log("$whoami (via $parent) via js-eval : selecting elements with this function '${selfunc}' ..."); }
243             let tmp = ${selfunc}(asel);
244             // if getElementsBy return an HTMLCollection,
245             // getElementBy (e.g. ById) returns an html element
246             // and querySelectorAll returns NodeList
247             // convert them all to an array:
248             if( (tmp === null) || (tmp === undefined) ){
249             if( VERBOSE_DOMops > 1 ){ console.log("$whoami (via $parent) : nothing matched."); }
250             continue;
251             }
252             anelems = (tmp.constructor.name === 'HTMLCollection') || (tmp.constructor.name === 'NodeList')
253             ? Array.prototype.slice.call(tmp) : [tmp]
254             ;
255             if( anelems == null ){
256             if( union == 0 ){
257             msg = "$whoami (via $parent) via js-eval : element(s) selected with ${aselname} '"+asel+"' not found, this specifier has failed and will not continue with the rest.";
258             console.log(msg);
259             return {"status":-1,"message":msg};
260             } else {
261             console.log("$whoami (via $parent) via js-eval : element(s) selected with ${aselname} '"+asel+"' not found (but because we are doing a union of the results, we continue with the other specifiers).");
262             continue;
263             }
264             }
265             if( anelems.length == 0 ){
266             if( union == 0 ){
267             msg = "$whoami (via $parent) via js-eval : element(s) selected with ${aselname} '"+asel+"' not found, this specifier has failed and will not continue with the rest.";
268             console.log(msg);
269             return {"status":-1,"message":msg};
270             } else {
271             console.log("$whoami (via $parent) via js-eval : element(s) selected with ${aselname} '"+asel+"' not found (but because we are doing a union of the results, we continue with the other specifiers).");
272             continue;
273             }
274             }
275             // now anelems is an array
276             if( elems["${aselname}"] === null ){
277             elems["${aselname}"] = anelems;
278             } else {
279             elems["${aselname}"] = elems["${aselname}"].length > 0 ? [...elems["${aselname}"], ...anelems] : anelems;
280             }
281             allfound = allfound.length > 0 ? [...allfound, ...anelems] : anelems;
282             if( VERBOSE_DOMops > 1 ){
283             console.log("$whoami (via $parent) via js-eval : found "+elems["${aselname}"].length+" elements selected with ${aselname} '"+asel+"'");
284             if( (VERBOSE_DOMops > 2) && (elems["${aselname}"].length>0) ){
285             for(let el of elems["${aselname}"]){ console.log(" tag: '"+el.tagName+"', id: '"+el.id+"'"); }
286             console.log("--- end of the elements selected with ${aselname}.");
287             }
288             }
289             }
290             EOJ
291             } # for my $aselname (keys %selectors){
292              
293             # if even one specified has failed, we do not reach this point, it returns -1
294 0 0         if( $Union ){
295             # union of all elements matched individually without duplicates:
296             # we just remove the duplicates from the allfound
297             # from https://stackoverflow.com/questions/9229645/remove-duplicate-values-from-js-array (by Christian Landgren)
298 0           $jsexec .= "\t// calculating the UNION of all elements found...\n";
299 0 0         if( $WWW::Mechanize::Chrome::DOMops::VERBOSE_DOMops > 1 ){ $jsexec .= "\t".'console.log("calculating the UNION of all elements found (without duplicates).\n");'."\n"; }
  0            
300 0           $jsexec .= "\t".'allfound.slice().sort(function(a,b){return a > b}).reduce(function(a,b){if (a.slice(-1)[0] !== b) a.push(b);return a;},[]);'."\n";
301             } else {
302             # intersection of all the elements matched individually
303 0           $jsexec .= "\t// calculating the INTERSECTION of all elements found...\n";
304 0 0         if( $WWW::Mechanize::Chrome::DOMops::VERBOSE_DOMops > 1 ){ $jsexec .= "\t".'console.log("calculating the INTERSECTION of all elements found per selector category (if any).\n");'."\n"; }
  0            
305 0           $jsexec .= "\tvar opts = ['".join("','", @known_selectors)."'];\n";
306 0           $jsexec .= <<'EOJ';
307             allfound = null;
308             var nopts = opts.length;
309             var n1, n2, I;
310             for(let i=0;i<nopts;i++){
311             n1 = opts[i];
312             if( (elems[n1] != null) && (elems[n1].length > 0) ){ allfound = elems[n1].slice(0); I = i; break; }
313             }
314             for(let j=0;j<nopts;j++){
315             if( j == I ) continue;
316             n2 = opts[j];
317             if( elems[n2] != null ){
318             var array2 = elems[n2];
319             // intersection of total and current
320             allfound = allfound.filter(function(n) {
321             return array2.indexOf(n) !== -1;
322             });
323             }
324             }
325             if( allfound === null ){ allfound = []; }
326             EOJ
327             } # if Union/Intersection
328              
329             # post-process and return
330 0           $jsexec .= <<'EOJ';
331             // first, make a separate list of all the children of those found (recursively all children)
332             for(let i=allfound.length;i-->0;){ allfound_including_children.push(...getAllChildren(allfound[i])); }
333             // second, add id to any html element which does not have any
334             if( insert_id_if_none !== null ){
335             let counter = 0;
336             for(let i=allfound.length;i-->0;){
337             let el = allfound[i];
338             if( el.id == '' ){ el.id = insert_id_if_none+'_'+counter++; }
339             }
340             for(let i=allfound_including_children.length;i-->0;){
341             let el = allfound_including_children[i];
342             if( el.id == '' ){ el.id = insert_id_if_none+'_'+counter++; }
343             }
344             // now that we are sure each HTML element has an ID we can remove duplicates if any
345             // basically there will not be duplicates in the 1st level but in all-levels there may be
346             let unis = {};
347             for(let i=allfound.length;i-->0;){
348             let el = allfound[i];
349             unis[el.id] = el;
350             }
351             allfound = Object.values(unis);
352             unis = {};
353             for(let i=allfound_including_children.length;i-->0;){
354             let el = allfound_including_children[i];
355             unis[el.id] = el;
356             }
357             allfound_including_children = Object.values(unis);
358             }
359              
360             if( VERBOSE_DOMops > 1 ){
361             console.log("Eventually matched "+allfound.length+" elements");
362             if( (VERBOSE_DOMops > 2) && (allfound.length>0) ){
363             console.log("---begin matched elements:");
364             for(let el of allfound){ console.log(" tag: '"+el.tagName+"', id: '"+el.id+"'"); }
365             console.log("---end matched elements.");
366             }
367             }
368             // now call the js callback function on those matched (not the children, if you want children then do it in the cb)
369             let cb_results = {};
370             for(let acbname of known_callbacks){
371             // this *crap* does not work: if( ! acbname in cb_functions ){ continue; }
372             // and caused me a huge waste of time
373             if( ! cb_functions[acbname] ){ continue; }
374             if( VERBOSE_DOMops > 1 ){ console.log("found callback for '"+acbname+"' and processing its code blocks ..."); }
375             let res1 = [];
376             let adata = acbname == 'find-cb-on-matched-and-their-children' ? allfound_including_children : allfound;
377             for(let acb of cb_functions[acbname]){
378             let res2 = [];
379             for(let i=0;i<adata.length;i++){
380             let el = adata[i];
381             if( VERBOSE_DOMops > 1 ){ console.log("executing callback of type '"+acbname+"' (name: '"+acb["name"]+"') on matched element tag '"+el.tagName+"' and id '"+el.id+"' ..."); }
382             let ares;
383             try {
384             // calling the callback ...
385             ares = acb["code"](el);
386             } catch(err) {
387             msg = "error, call to the user-specified callback of type '"+acbname+"' (name: '"+acb["name"]+"') has failed with exception : "+err.message;
388             console.log(msg);
389             return {"status":-1,"message":msg};
390             }
391             res2.push({"name":acb["name"],"result":ares});
392             if( VERBOSE_DOMops > 1 ){ console.log("success executing callback of type '"+acbname+"' (name: '"+acb["name"]+"') on matched element tag '"+el.tagName+"' and id '"+el.id+"'. Result is '"+ares+"'."); }
393             }
394             res1.push(res2);
395             }
396             cb_results[acbname] = res1;
397             }
398              
399             // returned will be an array of hashes : [{"tag":tag, "id":id}, ...] for each html element matched
400             // the hash for each match is constructed with element_information_from_matched_function() which must return a hash
401             // and can be user-specified or use our own default
402             var returnedids = [], returnedids_of_children_too = [];
403             for(let i=allfound.length;i-->0;){
404             let el = allfound[i];
405             let elinfo;
406             try {
407             elinfo = element_information_from_matched_function(el);
408             } catch(err){
409             msg = "error, call to the user-specified 'element-information-from-matched' has failed for directly matched element with exception : "+err.message;
410             console.log(msg);
411             return {"status":-1,"message":msg};
412             }
413             returnedids.push(elinfo);
414             }
415             for(let i=allfound_including_children.length;i-->0;){
416             let el = allfound_including_children[i];
417             let elinfo;
418             try {
419             elinfo = element_information_from_matched_function(el);
420             } catch(err){
421             msg = "error, call to the user-specified 'element-information-from-matched' has failed for directly matched (or one of its descendents) element with exception : "+err.message;
422             console.log(msg);
423             return {"status":-1,"message":msg};
424             }
425             returnedids_of_children_too.push(elinfo);
426             }
427              
428             let ret = {
429             "found" : {
430             "first-level" : returnedids,
431             "all-levels" : returnedids_of_children_too
432             },
433             "status" : returnedids.length
434             };
435             if( Object.keys(cb_results).length > 0 ){
436             ret["cb-results"] = cb_results;
437             }
438             console.dir(ret);
439              
440             return ret;
441             })(); // end of anonymous function and now execute it
442             }; // end our eval scope
443             EOJ
444 0 0         if( $WWW::Mechanize::Chrome::DOMops::VERBOSE_DOMops > 2 ){ print "--begin javascript code to eval:\n\n${jsexec}\n\n--end javascript code.\n$whoami (via $parent) : evaluating above javascript code.\n" }
  0            
445              
446 0 0         if( defined $js_outfile ){
447 0 0         if( open(my $FH, '>', $js_outfile) ){ print $FH $jsexec; close $FH }
  0            
  0            
448 0           else { print STDERR "$whoami (via $parent) : warning, failed to open file '$js_outfile' for writing the output javascript code, skipping it ...\n" }
449             }
450 0           my ($retval, $typ);
451 0           eval { ($retval, $typ) = $amech_obj->eval($jsexec) };
  0            
452 0 0         if( $@ ){
453 0           print STDERR "--begin javascript to eval:\n\n${jsexec}\n\n--end javascript code.\n$whoami (via $parent) : eval of above javascript has failed: $@\n";
454             return {
455 0           'status' => -2,
456             'message' => "eval has failed: $@"
457             };
458             };
459 0 0         if( ! defined $retval ){
460 0           print STDERR "--begin javascript to eval:\n\n${jsexec}\n\n--end javascript code.\n$whoami (via $parent) : eval of above javascript has returned an undefined result.\n";
461             return {
462 0           'status' => -2,
463             'message' => "eval returned un undefined result."
464             };
465             }
466              
467 0           return $retval; # success
468             }
469              
470             # The input is a hashref of parameters
471             # the 'element-*' parameters specify some condition to be matched
472             # for example id to be such and such.
473             # The conditions can be combined either as a union (OR)
474             # or an intersection (AND). Default is intersection.
475             # The param || => 1 changes this to Union.
476             #
477             # returns a hash of results, which contains status
478             # status is -2 if javascript failed
479             # status is -1 if one or more of the specified selectors failed to match
480             # status is >=0 : the number of elements deleted
481             # an error 'message' if status < 0
482             # and various other items if status >= 0
483             sub zap {
484 0     0 1   my $params = $_[0];
485 0   0       my $parent = ( caller(1) )[3] || "N/A";
486 0           my $whoami = ( caller(0) )[3];
487              
488 0 0         if( $WWW::Mechanize::Chrome::DOMops::VERBOSE_DOMops > 0 ){ print STDOUT "$whoami (via $parent) : called ...\n" }
  0            
489              
490 0 0         my $amech_obj = exists($params->{'mech-obj'}) ? $params->{'mech-obj'} : undef;
491 0 0         if( ! $amech_obj ){ print STDERR "$whoami (via $parent) : a mech-object is required via 'mech-obj'.\n"; return 0 }
  0            
  0            
492              
493             my $cbex = exists($params->{'find-cb-on-matched'}) && defined($params->{'find-cb-on-matched'})
494 0 0 0       ? [ @{$params->{'find-cb-on-matched'}} ] : [];
  0            
495             # execute our callback last, after all user-specified if any
496 0           push @$cbex, {
497             'code' => 'htmlElement.parentNode.removeChild(htmlElement); return 1;',
498             'name' => '_thezapper'
499             };
500 0           my %myparams = (
501             'find-cb-on-matched' => $cbex
502             );
503 0 0 0       if( ! (exists($params->{'insert-id-if-none-random'}) && defined($params->{'insert-id-if-none-random'}))
      0        
      0        
504             && ! (exists($params->{'insert-id-if-none'}) && defined($params->{'insert-id-if-none'}))
505             ){
506             # if no fixing of missing html element ids we ask for it and also let it be randomised
507 0           $myparams{'insert-id-if-none-random'} = '_domops_created_id';
508             }
509              
510 0           my $ret = find({
511             'mech-obj' => $amech_obj,
512             %$params,
513             # overwrite anything like these the user specified:
514             %myparams
515             });
516              
517 0 0         if( ! defined $ret ){
518 0           my $anerrmsg = perl2dump($params)."$whoami (via $parent) : error, call to find() has failed for above parameters.";
519 0           print STDERR $anerrmsg."\n";
520             return {
521 0           'status' => -2,
522             'message' => $anerrmsg
523             }
524             }
525 0 0         if( $ret->{'status'} < 0 ){
526 0           my $anerrmsg = perl2dump($params)."$whoami (via $parent) : error, call to find() has failed for above parameters with this error message: ".$ret->{'message'};
527 0           print STDERR $anerrmsg."\n";
528             return {
529 0           'status' => -2,
530             'message' => $anerrmsg
531             }
532             }
533              
534 0           return $ret; # success
535             }
536              
537             ## POD starts here
538              
539             =pod
540              
541             =head1 NAME
542              
543             WWW::Mechanize::Chrome::DOMops - Operations on the DOM loaded in Chrome
544              
545             =head1 VERSION
546              
547             Version 0.06
548              
549             =head1 SYNOPSIS
550              
551             This module provides a set of tools to operate on the DOM
552             loaded onto the provided <WWW::Mechanize::Chrome> object
553             after fetching a URL.
554              
555             Operating on the DOM is powerful but there are
556             security risks involved if the browser and profile
557             you used for loading this DOM is your everyday browser and profile.
558              
559             Please read L<SECURITY WARNING> before continuing on to the main course.
560              
561             Currently, L<WWW::Mechanize::Chrome::DOMops> provides these tools:
562              
563             =over 4
564              
565             =item * C<find()> : finds HTML elements,
566              
567             =item * C<zap()> : deletes HTML elements.
568              
569             =back
570              
571             Both C<find()> and C<zap()> return some information from
572             each match and its descendents (like C<tag>, C<id> etc.).
573             This information can be tweaked by the caller.
574             C<find()> and C<zap()> optionally execute javascript code on
575             each match and its descendents and can return data back to
576             the caller perl code.
577              
578             The selection of the HTML elements in the DOM
579             can be done in various ways:
580              
581             =over 4
582              
583             =item * by B<CSS selector>,
584              
585             =item * by B<tag>,
586              
587             =item * by B<class>.
588              
589             =item * by B<id>,
590              
591             =item * by B<name>.
592              
593             =back
594              
595             There is more information about this in section L<ELEMENT SELECTORS>.
596              
597             Here are some usage scenaria:
598              
599             use WWW::Mechanize::Chrome::DOMops qw/zap find VERBOSE_DOMops/;
600              
601             # adjust verbosity: 0, 1, 2, 3
602             $WWW::Mechanize::Chrome::VERBOSE_DOMops = 3;
603              
604             # First, create a mech object and load a URL on it
605             # Note: you need google-chrome binary installed in your system!
606             # See section CREATING THE MECH OBJECT for creating the mech
607             # and how to redirect its javascript console to perl's output
608             my $mechobj = WWW::Mechanize::Chrome->new();
609             # fetch a page which will setup a DOM on which to operate:
610             $mechobj->get('https://www.bbbbbbbbb.com');
611              
612             # find elements in the DOM, select by id, tag, name, or
613             # by CSS selector.
614             my $ret = find({
615             'mech-obj' => $mechobj,
616             # find elements whose class is in the provided
617             # scalar class name or array of class names
618             'element-class' => ['slanted-paragraph', 'class2', 'class3'],
619             # *OR* their tag is this:
620             'element-tag' => 'p',
621             # *OR* their name is this:
622             'element-name' => ['aname', 'name2'],
623             # *OR* their id is this:
624             'element-id' => ['id1', 'id2'],
625             # *OR* just provide a CSS selector and get done with it already
626             # the best choice
627             'element-cssselector' => 'a-css-selector',
628             # specifies that we should use the union of the above sets
629             # hence the *OR* in above comment
630             '||' => 1,
631             # this says to find all elements whose class
632             # is such-and-such AND element tag is such-and-such
633             # '&&' => 1 means to calculate the INTERSECTION of all
634             # individual matches.
635              
636             # build the information sent back from each match
637             'element-information-from-matched' => <<'EOJ',
638             // begin JS code to extract information from each match and return it
639             // back as a hash
640             const r = htmlElement.hasAttribute("role")
641             ? htmlElement.getAttribute("role") : "<no role present>"
642             ;
643             return {"tag" : htmlElement.tagName, "id" : htmlElement.id, "role" : r};
644             EOJ
645             # optionally run javascript code on all those elements matched
646             'find-cb-on-matched' => [
647             {
648             'code' =><<'EOJS',
649             // the element to operate on is 'htmlElement'
650             console.log("operating on this element "+htmlElement.tagName);
651             // this is returned back in the results of find() under
652             // key "cb-results"->"find-cb-on-matched"
653             return 1;
654             EOJS
655             'name' => 'func1'
656             }, {...}
657             ],
658             # optionally run javascript code on all those elements
659             # matched AND THEIR CHILDREN too!
660             'find-cb-on-matched-and-their-children' => [
661             {
662             'code' =><<'EOJS',
663             // the element to operate on is 'htmlElement'
664             console.log("operating on this element "+htmlElement.tagName);
665             // this is returned back in the results of find() under
666             // key "cb-results"->"find-cb-on-matched" notice the complex data
667             return {"abc":"123",{"xyz":[1,2,3]}};
668             EOJS
669             'name' => 'func2'
670             }
671             ],
672             # optionally ask it to create a valid id for any HTML
673             # element returned which does not have an id.
674             # The text provided will be postfixed with a unique
675             # incrementing counter value
676             'insert-id-if-none' => '_prefix_id',
677             # or ask it to randomise that id a bit to avoid collisions
678             'insert-id-if-none-random' => '_prefix_id',
679              
680             # optionally, also output the javascript code to a file for debugging
681             'js-outfile' => 'output.js',
682             });
683              
684              
685             # Delete an element from the DOM
686             $ret = zap({
687             'mech-obj' => $mechobj,
688             'element-id' => 'paragraph-123'
689             });
690              
691             # Mass murder:
692             $ret = zap({
693             'mech-obj' => $mechobj,
694             'element-tag' => ['div', 'span', 'p'],
695             '||' => 1, # the union of all those matched with above criteria
696             });
697              
698             # error handling
699             if( $ret->{'status'} < 0 ){ die "error: ".$ret->{'message'} }
700             # status of -3 indicates parameter errors,
701             # -2 indicates that eval of javascript code inside the mech object
702             # has failed (syntax errors perhaps, which could have been introduced
703             # by user-specified callback
704             # -1 indicates that javascript code executed correctly but
705             # failed somewhere in its logic.
706              
707             print "Found " . $ret->{'status'} . " matches which are: "
708             # ... results are in $ret->{'found'}->{'first-level'}
709             # ... and also in $ret->{'found'}->{'all-levels'}
710             # the latter contains a recursive list of those
711             # found AND ALL their children
712              
713             =head1 EXPORT
714              
715             the sub to find element(s) in the DOM
716              
717             find()
718              
719             the sub to delete element(s) from the DOM
720              
721             zap()
722              
723             and the flag to denote verbosity (default is 0, no verbosity)
724              
725             $WWW::Mechanize::Chrome::DOMops::VERBOSE_DOMops
726              
727              
728             =head1 SUBROUTINES/METHODS
729              
730             =head2 find($params)
731              
732             It finds HTML elements in the DOM currently loaded on the
733             parameters-specified L<WWW::Mechanize::Chrome> object. The
734             parameters are:
735              
736             =over 4
737              
738             =item * C<mech-obj> : user must supply a L<WWW::Mechanize::Chrome> object,
739             this is required. See section
740             L<CREATING THE MECH OBJECT> for an example of creating the mech object with some parameters
741             which work for me and javascript console output propagated on to perl's output.
742              
743             =item * C<element-information-from-matched> : optional javascript code to be run
744             on each HTML element matched in order to construct the information data
745             whih is returned back. If none
746             specified the following default will be used, which returns tagname and id:
747              
748             // the matched element is provided in htmlElement
749             return {"tag" : htmlElement.tagName, "id" : htmlElement.id};
750              
751             Basically the code is expected to be the B<body of a function> which
752             accepts one parameter: C<htmlElement> (that is the element matched).
753             That means it B<must not have>
754             the function preamble (function name, signature, etc.).
755             Neither it must have the postamble, which is the end-block curly bracket.
756             This piece of code B<must return a HASH>.
757             The code can throw exceptions which will be caught
758             (because the code is run within a try-catch block)
759             and the error message will be propagated to the perl code with status of -1.
760              
761             =item * C<insert-id-if-none> : some HTML elements simply do not have
762             an id (e.g. C<<p>>). If any of these elements is matched,
763             its tag and its id (empty string) will be returned.
764             By specifying this parameter (as a string, e.g. C<_replacing_empty_ids>)
765             all such elements matched will have their id set to
766             C<_replacing_empty_ids_X> where X is an incrementing counter
767             value starting from a random number. By running C<find()>
768             more than once on the same on the same DOM you are risking
769             having the same ID. So provide a different prefix every time.
770             Or use C<insert-id-if-none-random>, see below.
771              
772             =item * C<insert-id-if-none-random> : each time C<find()> is called
773             a new random base id will be created formed by the specified prefix (as with
774             C<insert-id-if-none>) plus a long random string plus the incrementing
775             counter, as above. This is supposed to be better at
776             avoiding collisions but it can not guarantee it.
777             If you are setting C<rand()>'s seed to the same number
778             before you call C<find()> then you are guaranteed to
779             have collisions.
780              
781             =item * C<find-cb-on-matched> : an array of
782             user-specified javascript code
783             to be run on each element matched in the order
784             the elements are returned and in the order of the javascript
785             code in the specified array. Each item of the array
786             is a hash with keys C<code> and C<name>. The former
787             contains the code to be run assuming that the
788             html element to operate on is named C<htmlElement>.
789             The code must end with a C<return> statement which
790             will be recorded and returned back to perl code.
791             The code can throw exceptions which will be caught
792             (because the callback is run within a try-catch block)
793             and the error message will be propagated to the perl code with status of -1.
794             Basically the code is expected to be the B<body of a function> which
795             accepts one parameter: C<htmlElement> (that is the element matched).
796             That means it B<must not have>
797             the function preamble (function name, signature, etc.).
798             Neither it must have the postamble, which is the end-block curly bracket.
799              
800             Key C<name> is just for
801             making this process more descriptive and will
802             be printed on log messages and returned back with
803             the results. C<name> can contain any characters.
804             Here is an example:
805              
806             'find-cb-on-matched' : [
807             {
808             # this returns a complex data type
809             'code' => 'console.log("found id "+htmlElement.id); return {"a":"1","b":"2"};'
810             'name' => 'func1'
811             },
812             {
813             'code' => 'console.log("second func: found id "+htmlElement.id); return 1;'
814             'name' => 'func2'
815             },
816             ]
817              
818             =item * C<find-cb-on-matched-and-their-children> : exactly the same
819             as C<find-cb-on-matched> but it operates on all those HTML elements
820             matched and also all their children and children of children etc.
821              
822             =item * C<js-outfile> : optionally save the javascript
823             code (which is evaluated within the mech object) to a file.
824              
825             =item * C<element selectors> are covered in section L</ELEMENT SELECTORS>.
826              
827             =back
828              
829             B<JAVASCRIPT HELPERS>
830              
831             There is one javascript function available to all user-specified callbacks:
832              
833             =over 2
834              
835             =item * C<getAllChildren(anHtmlElement)> : it returns
836             back an array of HTML elements which are the children (at any depth)
837             of the given C<anHtmlElement>.
838              
839             =back
840              
841             B<RETURN VALUE>:
842              
843             The returned value is a hashref with at least a C<status> key
844             which is greater or equal to zero in case of success and
845             denotes the number of matched HTML elements. Or it is -3, -2 or
846             -1 in case of errors:
847              
848             =over 4
849              
850             =item * C<-3> : there is an error with the parameters passed to this sub.
851              
852             =item * C<-2> : there is a syntax error in the javascript code to be
853             evaluated by the mech object with something like C<$mech_obj->eval()>.
854             Most likely this syntax error is with user-specified callback code.
855             Note that all the javascript code to be evaluated is dumped to stderr
856             by increasing the verbosity. But also it can be saved to a local file
857             for easier debugging by supplying the C<js-outfile> parameter to
858             C<find()> or C<zap()>.
859              
860             =item * C<-1> : there is a logical error while running the javascript code.
861             For example a division by zero etc. This can be both in the callback code
862             as well as in the internal javascript code for edge cases not covered
863             by my tests. Please report these.
864             Note that all the javascript code to be evaluated is dumped to stderr
865             by increasing the verbosity. But also it can be saved to a local file
866             for easier debugging by supplying the C<js-outfile> parameter to
867             C<find()> or C<zap()>.
868              
869             =back
870              
871             If C<status> is not negative, then this is success and its value
872             denotes the number of matched HTML elements. Which can be zero
873             or more. In this case the returned hash contains this
874              
875             "found" => {
876             "first-level" => [
877             {
878             "tag" => "NAV",
879             "id" => "nav-id-1"
880             }
881             ],
882             "all-levels" => [
883             {
884             "tag" => "NAV",
885             "id" => "nav-id-1"
886             },
887             {
888             "id" => "li-id-2",
889             "tag" => "LI"
890             },
891             ]
892             }
893              
894             Key C<first-level> contains those items matched directly while
895             key C<all-levels> contains those matched directly as well as those
896             matched because they are descendents (direct or indirect)
897             of each matched element.
898              
899             Each item representing a matched HTML element has two fields:
900             C<tag> and C<id>. Beware of missing C<id> or
901             use C<insert-id-if-none> or C<insert-id-if-none-random> to
902             fill in the missing ids.
903              
904             If C<find-cb-on-matched> or C<find-cb-on-matched-and-their-children>
905             were specified, then the returned result contains this additional data:
906              
907             "cb-results" => {
908             "find-cb-on-matched" => [
909             [
910             {
911             "name" => "func1",
912             "result" => {
913             "a" => 1,
914             "b" => 2
915             }
916             }
917             ],
918             [
919             {
920             "result" => 1,
921             "name" => "func2"
922             }
923             ]
924             ],
925             "find-cb-on-matched-and-their-children" => ...
926             },
927              
928             C<find-cb-on-matched> and/or C<find-cb-on-matched-and-their-children> will
929             be present depending on whether corresponding value in the input
930             parameters was specified or not. Each of these contain the return
931             result for running the callback on each HTML element in the same
932             order as returned under key C<found>.
933              
934             HTML elements allows for missing C<id>. So field C<id> can be empty
935             unless caller set the C<insert-id-if-none> input parameter which
936             will create a unique id for each HTML element matched but with
937             missing id. These changes will be saved in the DOM.
938             When this parameter is specified, the returned HTML elements will
939             be checked for duplicates because now all of them have an id field.
940             Therefore, if you did not specify this parameter results may
941             contain duplicate items and items with empty id field.
942             If you did specify this parameter then some elements of the DOM
943             (those matched by our selectors) will have their missing id
944             created and saved in the DOM.
945              
946             Another implication of using this parameter when
947             running it twice or more with the same value is that
948             you can get same ids. So, always supply a different
949             value to this parameter if run more than once on the
950             same DOM.
951              
952             =head2 zap($params)
953              
954             It removes HTML element(s) from the DOM currently loaded on the
955             parameters-specified L<WWW::Mechanize::Chrome> object. The params
956             are exactly the same as with L</find($params)> except that
957             C<insert-id-if-none> is ignored.
958              
959             C<zap()> is implemented as a C<find()> with
960             an additional callback for all elements matched
961             in the first level (not their children) as:
962              
963             'find-cb-on-matched' => {
964             'code' => 'htmlElement.parentNode.removeChild(htmlElement); return 1;',
965             'name' => '_thezapper'
966             };
967              
968              
969             B<RETURN VALUE>:
970              
971             Return value is exactly the same as with L</find($params)>
972              
973             =head2 $WWW::Mechanize::Chrome::DOMops::VERBOSE_DOMops
974              
975             Set this upon loading the module to C<0, 1, 2, 3>
976             to adjust verbosity. C<0> implies no verbosity.
977              
978             =head1 ELEMENT SELECTORS
979              
980             C<Element selectors> are how one selects HTML elements from the DOM.
981             There are 5 ways to select HTML elements: by id, class, tag, name
982             or via a CSS selector. Multiple selectors can be specified
983             as well as multiple criteria in each selector (e.g. multiple
984             class names in a C<element-class> selector). The results
985             from each selector are combined into a list of
986             unique HTML elements (BEWARE of missing id fields) by
987             means of UNION or INTERSECTION of the individual matches
988              
989             These are the valid selectors:
990              
991             =over 2
992              
993             =item * C<element-class> : find DOM elements matching this class name
994              
995             =item * C<element-tag> : find DOM elements matching this element tag
996              
997             =item * C<element-id> : find DOM element matching this element id
998              
999             =item * C<element-name> : find DOM element matching this element name
1000              
1001             =item * C<element-cssselector> : find DOM element matching this CSS selector
1002              
1003             =back
1004              
1005             And one of these two must be used to combine the results
1006             into a final list
1007              
1008             =over 2
1009              
1010             =item * C<&&> : Intersection. When set to 1 the result is the intersection of all individual results.
1011             Meaning that an element will make it to the final list if it was matched
1012             by every selector specified. This is the default.
1013              
1014             =item * C<||> : Union. When set to 1 the result is the union of all individual results.
1015             Meaning that an element will make it to the final list if it was matched
1016             by at least one of the selectors specified.
1017              
1018             =back
1019              
1020             =head1 CREATING THE MECH OBJECT
1021              
1022             The mech (L<WWW::Mechanize::Chrome>) object must be supplied
1023             to the functions in this module. It must be created by the caller.
1024             This is how I do it:
1025              
1026             use WWW::Mechanize::Chrome;
1027             use Log::Log4perl qw(:easy);
1028             Log::Log4perl->easy_init($ERROR);
1029              
1030             my %default_mech_params = (
1031             headless => 1,
1032             # log => $mylogger,
1033             launch_arg => [
1034             '--window-size=600x800',
1035             '--password-store=basic', # do not ask me for stupid chrome account password
1036             # '--remote-debugging-port=9223',
1037             # '--enable-logging', # see also log above
1038             '--disable-gpu',
1039             '--no-sandbox',
1040             '--ignore-certificate-errors',
1041             '--disable-background-networking',
1042             '--disable-client-side-phishing-detection',
1043             '--disable-component-update',
1044             '--disable-hang-monitor',
1045             '--disable-save-password-bubble',
1046             '--disable-default-apps',
1047             '--disable-infobars',
1048             '--disable-popup-blocking',
1049             ],
1050             );
1051              
1052             my $mech_obj = eval {
1053             WWW::Mechanize::Chrome->new(%default_mech_params)
1054             };
1055             die $@ if $@;
1056              
1057             # This transfers all javascript code's console.log(...)
1058             # messages to perl's warn()
1059             # we need to keep $console var in scope!
1060             my $console = $mech_obj->add_listener('Runtime.consoleAPICalled', sub {
1061             warn
1062             "js console: "
1063             . join ", ",
1064             map { $_->{value} // $_->{description} }
1065             @{ $_[0]->{params}->{args} };
1066             })
1067             ;
1068              
1069             # and now fetch a page
1070             my $URL = '...';
1071             my $retmech = $mech_obj->get($URL);
1072             die "failed to fetch $URL" unless defined $retmech;
1073             $mech_obj->sleep(1); # let it settle
1074             # now the mech object has loaded the URL and has a DOM hopefully.
1075             # You can pass it on to find() or zap() to operate on the DOM.
1076              
1077              
1078             =head1 SECURITY WARNING
1079              
1080             L<WWW::Mechanize::Chrome> invokes the C<google-chrome>
1081             executable
1082             on behalf of the current user. Headless or not, C<google-chrome>
1083             is invoked. Depending on the launch parameters, either
1084             a fresh, new browser session will be created or the
1085             session of the current user with their profile, data, cookies,
1086             passwords, history, etc. will be used. The latter case is very
1087             dangerous.
1088              
1089             This behaviour is controlled by L<WWW::Mechanize::Chrome>'s
1090             L<constructor|WWW::Mechanize::Chrome#WWW::Mechanize::Chrome-%3Enew(-%options-)>
1091             parameters which, in turn, are used for launching
1092             the C<google-chrome> executable. Specifically,
1093             see L<WWW::Mechanize::Chrome#separate_session>,
1094             L<<WWW::Mechanize::Chrome#data_directory>
1095             and L<WWW::Mechanize::Chrome#incognito>.
1096              
1097             B<Unless you really need to mechsurf with your current session, aim
1098             to launching the browser with a fresh new session.
1099             This is the safest option.>
1100              
1101             B<Do not rely on default behaviour as this may change over
1102             time. Be explicit.>
1103              
1104             Also, be warned that L<WWW::Mechanize::Chrome::DOMops> executes
1105             javascript code on that C<google-chrome> instance.
1106             This is done nternally with javascript code hardcoded
1107             into the L<WWW::Mechanize::Chrome::DOMops>'s package files.
1108              
1109             On top of that L<WWW::Mechanize::Chrome::DOMops> allows
1110             for B<user-specified javascript code> to be executed on
1111             that C<google-chrome> instance. For example the callbacks
1112             on each element found, etc.
1113              
1114             This is an example of what can go wrong if
1115             you are not using a fresh C<google-chrome>
1116             session:
1117              
1118             You have just used C<google-chrome> to access your
1119             yahoo webmail and you did not logout.
1120             So, there will be an
1121             access cookie in the C<google-chrome> when you later
1122             invoke it via L<WWW::Mechanize::Chrome> (remember
1123             you have not told it to use a fresh session).
1124              
1125             If you allow
1126             unchecked user-specified (or copy-pasted from ChatGPT)
1127             javascript code in
1128             L<WWW::Mechanize::Chrome::DOMops>'s
1129             C<find()>, C<zap()>, etc. then it is, theoretically,
1130             possible that this javascript code
1131             initiates an XHR to yahoo and fetch your emails and
1132             pass them on to your perl code.
1133              
1134             But there is another problem,
1135             L<WWW::Mechanize::Chrome::DOMops>'s
1136             integrity of the embedded javascript code may have
1137             been compromised to exploit your current session.
1138              
1139             This is very likely with a Windows installation which,
1140             being the security swiss cheese it is, it
1141             is possible for anyone to compromise your module's code.
1142             It is less likely in Linux, if your modules are
1143             installed by root and are read-only for normal users.
1144             But, still, it is possible to be compromised (by root).
1145              
1146             Another issue is with the saved passwords and
1147             the browser's auto-fill when landing on a login form.
1148              
1149             Therefore, for all these reasons, B<it is advised not to invoke (via L<WWW::Mechanize::Chrome>)
1150             C<google-chrome> with your
1151             current/usual/everyday/email-access/bank-access
1152             identity so that it does not have access to
1153             your cookies, passwords, history etc.>
1154              
1155             It is better to create a fresh
1156             C<google-chrome>
1157             identity/profile and use that for your
1158             C<WWW::Mechanize::Chrome::DOMops> needs.
1159              
1160             No matter what identity you use, you may want
1161             to erase the cookies and history of C<google-chrome>
1162             upon its exit. That's a good practice.
1163              
1164             It is also advised to review the
1165             javascript code you provide
1166             via L<WWW::Mechanize::Chrome::DOMops> callbacks if
1167             it is taken from 3rd-party, human or not, e.g. ChatGPT.
1168              
1169             Additionally, make sure that the current
1170             installation of L<WWW::Mechanize::Chrome::DOMops>
1171             in your system is not compromised with malicious javascript
1172             code injected into it. For this you can check its MD5 hash.
1173              
1174             =head1 DEPENDENCIES
1175              
1176             This module depends on L<WWW::Mechanize::Chrome> which, in turn,
1177             depends on the C<google-chrome> executable be installed on the
1178             host computer. See L<WWW::Mechanize::Chrome::Install> on
1179             how to install the executable.
1180              
1181             Test scripts (which create there own mech object) will detect the absence
1182             of C<google-chrome> binary and exit gracefully, meaning the test passes.
1183             But with a STDERR message to the user. Who will hopefully notice it and
1184             proceed to C<google-chrome> installation. In any event, this module
1185             will be installed with or without C<google-chrome>.
1186              
1187             =head1 AUTHOR
1188              
1189             Andreas Hadjiprocopis, C<< <bliako at cpan.org> >>
1190              
1191             =head1 CODING CONDITIONS
1192              
1193             This code was written under extreme climate conditions of 44 Celsius.
1194             Keep packaging those
1195             vegs in kilos of plastic wrappers, keep obsolidating our perfectly good
1196             hardware, keep inventing new consumer needs and brainwash them
1197             down our throats, in short B<Crack Deep the Roof Beam, Capitalism>.
1198              
1199             =head1 BUGS
1200              
1201             Please report any bugs or feature requests to C<bug-www-mechanize-chrome-domops at rt.cpan.org>, or through
1202             the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=WWW-Mechanize-Chrome-DOMops>. I will be notified, and then you'll
1203             automatically be notified of progress on your bug as I make changes.
1204              
1205             =head1 SUPPORT
1206              
1207             You can find documentation for this module with the perldoc command.
1208              
1209             perldoc WWW::Mechanize::Chrome::DOMops
1210              
1211              
1212             You can also look for information at:
1213              
1214             =over 4
1215              
1216             =item * RT: CPAN's request tracker (report bugs here)
1217              
1218             L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=WWW-Mechanize-Chrome-DOMops>
1219              
1220             =item * AnnoCPAN: Annotated CPAN documentation
1221              
1222             L<http://annocpan.org/dist/WWW-Mechanize-Chrome-DOMops>
1223              
1224             =item * CPAN Ratings
1225              
1226             L<https://cpanratings.perl.org/d/WWW-Mechanize-Chrome-DOMops>
1227              
1228             =item * Search CPAN
1229              
1230             L<https://metacpan.org/release/WWW-Mechanize-Chrome-DOMops>
1231              
1232             =back
1233              
1234             =head1 DEDICATIONS
1235              
1236             Almaz
1237              
1238              
1239             =head1 ACKNOWLEDGEMENTS
1240              
1241             L<CORION> for publishing L<WWW::Mechanize::Chrome> and all its
1242             contributors.
1243              
1244              
1245             =head1 LICENSE AND COPYRIGHT
1246              
1247             Copyright 2019 Andreas Hadjiprocopis.
1248              
1249             This program is free software; you can redistribute it and/or modify it
1250             under the terms of the the Artistic License (2.0). You may obtain a
1251             copy of the full license at:
1252              
1253             L<http://www.perlfoundation.org/artistic_license_2_0>
1254              
1255             Any use, modification, and distribution of the Standard or Modified
1256             Versions is governed by this Artistic License. By using, modifying or
1257             distributing the Package, you accept this license. Do not use, modify,
1258             or distribute the Package, if you do not accept this license.
1259              
1260             If your Modified Version has been derived from a Modified Version made
1261             by someone other than you, you are nevertheless required to ensure that
1262             your Modified Version complies with the requirements of this license.
1263              
1264             This license does not grant you the right to use any trademark, service
1265             mark, tradename, or logo of the Copyright Holder.
1266              
1267             This license includes the non-exclusive, worldwide, free-of-charge
1268             patent license to make, have made, use, offer to sell, sell, import and
1269             otherwise transfer the Package with respect to any patent claims
1270             licensable by the Copyright Holder that are necessarily infringed by the
1271             Package. If you institute patent litigation (including a cross-claim or
1272             counterclaim) against any party alleging that the Package constitutes
1273             direct or contributory patent infringement, then this Artistic License
1274             to you shall terminate on the date that such litigation is filed.
1275              
1276             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
1277             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
1278             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
1279             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
1280             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
1281             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
1282             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
1283             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1284              
1285              
1286             =cut
1287              
1288             1; # End of WWW::Mechanize::Chrome::DOMops