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   1065057 use 5.006;
  3         31  
4 3     3   20 use strict;
  3         9  
  3         61  
5 3     3   13 use warnings;
  3         5  
  3         112  
6              
7 3     3   17 use Exporter qw(import);
  3         6  
  3         193  
8             our @EXPORT = qw(
9             zap
10             find
11             VERBOSE_DOMops
12             );
13              
14 3     3   1603 use Data::Roundtrip qw/perl2dump no-unicode-escape-permanently/;
  3         67522  
  3         28  
15            
16             our $VERSION = '0.04';
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             =head1 NAME
540              
541             WWW::Mechanize::Chrome::DOMops - Operations on the DOM loaded in Chrome
542              
543             =head1 VERSION
544              
545             Version 0.04
546              
547             =head1 SYNOPSIS
548              
549             This module provides a set of tools to operate on the DOM of the
550             provided L<WWW::Mechanize::Chrome> object. Currently,
551             supported operations are:
552              
553             =over 4
554              
555             =item * C<find()> : finds HTML elements,
556              
557             =item * C<zap()> : deletes HTML elements.
558              
559             =back
560              
561             Both C<find()> and C<zap()> return some information from each match and its descendents (like C<tag>, C<id> etc.).
562             This information can be tweaked by the caller.
563             C<find()> and C<zap()> optionally execute javascript code on each match and its descendents and can return back data.
564              
565             The selection of the HTML elements in the DOM
566             can be done in various ways:
567              
568             =over 4
569              
570             =item * by B<CSS selector>,
571              
572             =item * by B<tag>,
573              
574             =item * by B<class>.
575              
576             =item * by B<id>,
577              
578             =item * by B<name>.
579              
580             =back
581              
582             There is more information about this in section L<ELEMENT SELECTORS>.
583              
584             Here are some usage scenaria:
585              
586             use WWW::Mechanize::Chrome::DOMops qw/zap find VERBOSE_DOMops/;
587              
588             # increase verbosity: 0, 1, 2, 3
589             $WWW::Mechanize::Chrome::VERBOSE_DOMops = 3;
590              
591             # First, create a mech object and load a URL on it
592             # Note: you need google-chrome binary installed in your system!
593             my $mechobj = WWW::Mechanize::Chrome->new();
594             $mechobj->get('https://www.bbbbbbbbb.com');
595              
596             # find elements in the DOM, select by id, tag, name, or
597             # by CSS selector.
598             my $ret = find({
599             'mech-obj' => $mechobj,
600             # find elements whose class is in the provided
601             # scalar class name or array of class names
602             'element-class' => ['slanted-paragraph', 'class2', 'class3'],
603             # *OR* their tag is this:
604             'element-tag' => 'p',
605             # *OR* their name is this:
606             'element-name' => ['aname', 'name2'],
607             # *OR* their id is this:
608             'element-id' => ['id1', 'id2'],
609             # *OR* just provide a CSS selector and get done with it already
610             # the best choice
611             'element-cssselector' => 'a-css-selector',
612             # specifies that we should use the union of the above sets
613             # hence the *OR* in above comment
614             '||' => 1,
615             # this says to find all elements whose class
616             # is such-and-such AND element tag is such-and-such
617             # '&&' => 1 means to calculate the INTERSECTION of all
618             # individual matches.
619              
620             # build the information sent back from each match
621             'element-information-from-matched' => <<'EOJ',
622             // begin JS code to extract information from each match and return it
623             // back as a hash
624             const r = htmlElement.hasAttribute("role")
625             ? htmlElement.getAttribute("role") : "<no role present>"
626             ;
627             return {"tag" : htmlElement.tagName, "id" : htmlElement.id, "role" : r};
628             EOJ
629             # optionally run javascript code on all those elements matched
630             'find-cb-on-matched' => [
631             {
632             'code' =><<'EOJS',
633             // the element to operate on is 'htmlElement'
634             console.log("operating on this element "+htmlElement.tagName);
635             // this is returned back in the results of find() under
636             // key "cb-results"->"find-cb-on-matched"
637             return 1;
638             EOJS
639             'name' => 'func1'
640             }, {...}
641             ],
642             # optionally run javascript code on all those elements
643             # matched AND THEIR CHILDREN too!
644             'find-cb-on-matched-and-their-children' => [
645             {
646             'code' =><<'EOJS',
647             // the element to operate on is 'htmlElement'
648             console.log("operating on this element "+htmlElement.tagName);
649             // this is returned back in the results of find() under
650             // key "cb-results"->"find-cb-on-matched" notice the complex data
651             return {"abc":"123",{"xyz":[1,2,3]}};
652             EOJS
653             'name' => 'func2'
654             }
655             ],
656             # optionally ask it to create a valid id for any HTML
657             # element returned which does not have an id.
658             # The text provided will be postfixed with a unique
659             # incrementing counter value
660             'insert-id-if-none' => '_prefix_id',
661             # or ask it to randomise that id a bit to avoid collisions
662             'insert-id-if-none-random' => '_prefix_id',
663              
664             # optionally, also output the javascript code to a file for debugging
665             'js-outfile' => 'output.js',
666             });
667              
668              
669             # Delete an element from the DOM
670             $ret = zap({
671             'mech-obj' => $mechobj,
672             'element-id' => 'paragraph-123'
673             });
674              
675             # Mass murder:
676             $ret = zap({
677             'mech-obj' => $mechobj,
678             'element-tag' => ['div', 'span', 'p'],
679             '||' => 1, # the union of all those matched with above criteria
680             });
681              
682             # error handling
683             if( $ret->{'status'} < 0 ){ die "error: ".$ret->{'message'} }
684             # status of -3 indicates parameter errors,
685             # -2 indicates that eval of javascript code inside the mech object
686             # has failed (syntax errors perhaps, which could have been introduced
687             # by user-specified callback
688             # -1 indicates that javascript code executed correctly but
689             # failed somewhere in its logic.
690              
691             print "Found " . $ret->{'status'} . " matches which are: "
692             # ... results are in $ret->{'found'}->{'first-level'}
693             # ... and also in $ret->{'found'}->{'all-levels'}
694             # the latter contains a recursive list of those
695             # found AND ALL their children
696              
697             =head1 EXPORT
698              
699             the sub to find element(s) in the DOM
700              
701             find()
702              
703             the sub to delete element(s) from the DOM
704              
705             zap()
706              
707             and the flag to denote verbosity (default is 0, no verbosity)
708              
709             $WWW::Mechanize::Chrome::DOMops::VERBOSE_DOMops
710              
711              
712             =head1 SUBROUTINES/METHODS
713              
714             =head2 find($params)
715              
716             It finds HTML elements in the DOM currently loaded on the
717             parameters-specified L<WWW::Mechanize::Chrome> object. The
718             parameters are:
719              
720             =over 4
721              
722             =item * C<mech-obj> : user must supply a L<WWW::Mechanize::Chrome>, this is required. See section
723             L<CREATING THE MECH OBJECT> for an example of creating the mech object with some parameters
724             (which work for me) and javascript console output propagated on to perl's output.
725              
726             =item * C<element-information-from-matched> : optional javascript code to be run
727             on each HTML element matched in order to construct the information data
728             whih is returned back. If none
729             specified the following default will be used, which returns tagname and id:
730              
731             // the matched element is provided in htmlElement
732             return {"tag" : htmlElement.tagName, "id" : htmlElement.id};
733              
734             Basically the code is expected to be the B<body of a function> which
735             accepts one parameter: C<htmlElement> (that is the element matched).
736             That means it B<must not have>
737             the function preamble (function name, signature, etc.).
738             Neither it must have the postamble, which is the end-block curly bracket.
739             This piece of code B<must return a HASH>.
740             The code can thow exceptions which will be caught
741             (because the code is run within a try-catch block)
742             and the error message will be propagated to the perl code with status of -1.
743              
744             =item * C<insert-id-if-none> : some HTML elements simply do not have
745             an id (e.g. C<<p>>). If any of these elements is matched,
746             its tag and its id (empty string) will be returned.
747             By specifying this parameter (as a string, e.g. C<_replacing_empty_ids>)
748             all such elements matched will have their id set to
749             C<_replacing_empty_ids_X> where X is an incrementing counter
750             value starting from a random number. By running C<find()>
751             more than once on the same on the same DOM you are risking
752             having the same ID. So provide a different prefix every time.
753             Or use C<insert-id-if-none-random>, see below.
754              
755             =item * C<insert-id-if-none-random> : each time C<find()> is called
756             a new random base id will be created formed by the specified prefix (as with
757             C<insert-id-if-none>) plus a long random string plus the incrementing
758             counter, as above. This is supposed to be better at
759             avoiding collisions but it can not guarantee it.
760             If you are setting C<rand()>'s seed to the same number
761             before you call C<find()> then you are guaranteed to
762             have collisions.
763              
764             =item * C<find-cb-on-matched> : an array of
765             user-specified javascript code
766             to be run on each element matched in the order
767             the elements are returned and in the order of the javascript
768             code in the specified array. Each item of the array
769             is a hash with keys C<code> and C<name>. The former
770             contains the code to be run assuming that the
771             html element to operate on is named C<htmlElement>.
772             The code must end with a C<return> statement which
773             will be recorded and returned back to perl code.
774             The code can thow exceptions which will be caught
775             (because the callback is run within a try-catch block)
776             and the error message will be propagated to the perl code with status of -1.
777             Basically the code is expected to be the B<body of a function> which
778             accepts one parameter: C<htmlElement> (that is the element matched).
779             That means it B<must not have>
780             the function preamble (function name, signature, etc.).
781             Neither it must have the postamble, which is the end-block curly bracket.
782              
783             Key C<name> is just for
784             making this process more descriptive and will
785             be printed on log messages and returned back with
786             the results. C<name> can contain any characters.
787             Here is an example:
788              
789             'find-cb-on-matched' : [
790             {
791             # this returns a complex data type
792             'code' => 'console.log("found id "+htmlElement.id); return {"a":"1","b":"2"};'
793             'name' => 'func1'
794             },
795             {
796             'code' => 'console.log("second func: found id "+htmlElement.id); return 1;'
797             'name' => 'func2'
798             },
799             ]
800              
801             =item * C<find-cb-on-matched-and-their-children> : exactly the same
802             as C<find-cb-on-matched> but it operates on all those HTML elements
803             matched and also all their children and children of children etc.
804              
805             =item * C<js-outfile> : optionally save the javascript
806             code (which is evaluated within the mech object) to a file.
807              
808             =item * C<element selectors> are covered in section L</ELEMENT SELECTORS>.
809              
810             =back
811              
812             B<JAVASCRIPT HELPERS>
813              
814             There is one javascript function available to all user-specified callbacks:
815              
816             =over 2
817              
818             =item * C<getAllChildren(anHtmlElement)> : it returns
819             back an array of HTML elements which are the children (at any depth)
820             of the given C<anHtmlElement>.
821              
822             =back
823              
824             B<RETURN VALUE>:
825              
826             The returned value is a hashref with at least a C<status> key
827             which is greater or equal to zero in case of success and
828             denotes the number of matched HTML elements. Or it is -3, -2 or
829             -1 in case of errors:
830              
831             =over 4
832              
833             =item * C<-3> : there is an error with the parameters passed to this sub.
834              
835             =item * C<-2> : there is a syntax error in the javascript code to be
836             evaluated by the mech object with something like C<$mech_obj->eval()>.
837             Most likely this syntax error is with user-specified callback code.
838             Note that all the javascript code to be evaluated is dumped to stderr
839             by increasing the verbosity. But also it can be saved to a local file
840             for easier debugging by supplying the C<js-outfile> parameter to
841             C<find()> or C<zap()>.
842              
843             =item * C<-1> : there is a logical error while running the javascript code.
844             For example a division by zero etc. This can be both in the callback code
845             as well as in the internal javascript code for edge cases not covered
846             by my tests. Please report these.
847             Note that all the javascript code to be evaluated is dumped to stderr
848             by increasing the verbosity. But also it can be saved to a local file
849             for easier debugging by supplying the C<js-outfile> parameter to
850             C<find()> or C<zap()>.
851              
852             =back
853              
854             If C<status> is not negative, then this is success and its value
855             denotes the number of matched HTML elements. Which can be zero
856             or more. In this case the returned hash contains this
857              
858             "found" => {
859             "first-level" => [
860             {
861             "tag" => "NAV",
862             "id" => "nav-id-1"
863             }
864             ],
865             "all-levels" => [
866             {
867             "tag" => "NAV",
868             "id" => "nav-id-1"
869             },
870             {
871             "id" => "li-id-2",
872             "tag" => "LI"
873             },
874             ]
875             }
876              
877             Key C<first-level> contains those items matched directly while
878             key C<all-levels> contains those matched directly as well as those
879             matched because they are descendents (direct or indirect)
880             of each matched element.
881              
882             Each item representing a matched HTML element has two fields:
883             C<tag> and C<id>. Beware of missing C<id> or
884             use C<insert-id-if-none> or C<insert-id-if-none-random> to
885             fill in the missing ids.
886              
887             If C<find-cb-on-matched> or C<find-cb-on-matched-and-their-children>
888             were specified, then the returned result contains this additional data:
889              
890             "cb-results" => {
891             "find-cb-on-matched" => [
892             [
893             {
894             "name" => "func1",
895             "result" => {
896             "a" => 1,
897             "b" => 2
898             }
899             }
900             ],
901             [
902             {
903             "result" => 1,
904             "name" => "func2"
905             }
906             ]
907             ],
908             "find-cb-on-matched-and-their-children" => ...
909             },
910              
911             C<find-cb-on-matched> and/or C<find-cb-on-matched-and-their-children> will
912             be present depending on whether corresponding value in the input
913             parameters was specified or not. Each of these contain the return
914             result for running the callback on each HTML element in the same
915             order as returned under key C<found>.
916              
917             HTML elements allows for missing C<id>. So field C<id> can be empty
918             unless caller set the C<insert-id-if-none> input parameter which
919             will create a unique id for each HTML element matched but with
920             missing id. These changes will be saved in the DOM.
921             When this parameter is specified, the returned HTML elements will
922             be checked for duplicates because now all of them have an id field.
923             Therefore, if you did not specify this parameter results may
924             contain duplicate items and items with empty id field.
925             If you did specify this parameter then some elements of the DOM
926             (those matched by our selectors) will have their missing id
927             created and saved in the DOM.
928              
929             Another implication of using this parameter when
930             running it twice or more with the same value is that
931             you can get same ids. So, always supply a different
932             value to this parameter if run more than once on the
933             same DOM.
934              
935             =head2 zap($params)
936              
937             It removes HTML element(s) from the DOM currently loaded on the
938             parameters-specified L<WWW::Mechanize::Chrome> object. The params
939             are exactly the same as with L</find($params)> except that
940             C<insert-id-if-none> is ignored.
941              
942             C<zap()> is implemented as a C<find()> with
943             an additional callback for all elements matched
944             in the first level (not their children) as:
945              
946             'find-cb-on-matched' => {
947             'code' => 'htmlElement.parentNode.removeChild(htmlElement); return 1;',
948             'name' => '_thezapper'
949             };
950              
951              
952             B<RETURN VALUE>:
953              
954             Return value is exactly the same as with L</find($params)>
955              
956             =head2 $WWW::Mechanize::Chrome::DOMops::VERBOSE_DOMops
957              
958             Set this upon loading the module to C<0, 1, 2, 3>
959             to increase verbosity. C<0> implies no verbosity.
960              
961             =head1 ELEMENT SELECTORS
962              
963             C<Element selectors> are how one selects HTML elements from the DOM.
964             There are 5 ways to select HTML elements: by id, class, tag, name
965             or via a CSS selector. Multiple selectors can be specified
966             as well as multiple criteria in each selector (e.g. multiple
967             class names in a C<element-class> selector). The results
968             from each selector are combined into a list of
969             unique HTML elements (BEWARE of missing id fields) by
970             means of UNION or INTERSECTION of the individual matches
971              
972             These are the valid selectors:
973              
974             =over 2
975              
976             =item * C<element-class> : find DOM elements matching this class name
977              
978             =item * C<element-tag> : find DOM elements matching this element tag
979              
980             =item * C<element-id> : find DOM element matching this element id
981              
982             =item * C<element-name> : find DOM element matching this element name
983              
984             =item * C<element-cssselector> : find DOM element matching this CSS selector
985              
986             =back
987              
988             And one of these two must be used to combine the results
989             into a final list
990              
991             =over 2
992              
993             =item C<&&> : Intersection. When set to 1 the result is the intersection of all individual results.
994             Meaning that an element will make it to the final list if it was matched
995             by every selector specified. This is the default.
996              
997             =item C<||> : Union. When set to 1 the result is the union of all individual results.
998             Meaning that an element will make it to the final list if it was matched
999             by at least one of the selectors specified.
1000              
1001             =back
1002              
1003             =head1 CREATING THE MECH OBJECT
1004              
1005             The mech (L<WWW::Mechanize::Chrome>) object must be supplied
1006             to the functions in this module. It must be created by the caller.
1007             This is how I do it:
1008              
1009             use WWW::Mechanize::Chrome;
1010             use Log::Log4perl qw(:easy);
1011             Log::Log4perl->easy_init($ERROR);
1012              
1013             my %default_mech_params = (
1014             headless => 1,
1015             # log => $mylogger,
1016             launch_arg => [
1017             '--window-size=600x800',
1018             '--password-store=basic', # do not ask me for stupid chrome account password
1019             # '--remote-debugging-port=9223',
1020             # '--enable-logging', # see also log above
1021             '--disable-gpu',
1022             '--no-sandbox',
1023             '--ignore-certificate-errors',
1024             '--disable-background-networking',
1025             '--disable-client-side-phishing-detection',
1026             '--disable-component-update',
1027             '--disable-hang-monitor',
1028             '--disable-save-password-bubble',
1029             '--disable-default-apps',
1030             '--disable-infobars',
1031             '--disable-popup-blocking',
1032             ],
1033             );
1034              
1035             my $mech_obj = eval {
1036             WWW::Mechanize::Chrome->new(%default_mech_params)
1037             };
1038             die $@ if $@;
1039              
1040             # This transfers all javascript code's console.log(...)
1041             # messages to perl's warn()
1042             # we need to keep $console var in scope!
1043             my $console = $mech_obj->add_listener('Runtime.consoleAPICalled', sub {
1044             warn
1045             "js console: "
1046             . join ", ",
1047             map { $_->{value} // $_->{description} }
1048             @{ $_[0]->{params}->{args} };
1049             })
1050             ;
1051              
1052             # and now fetch a page
1053             my $URL = '...';
1054             my $retmech = $mech_obj->get($URL);
1055             die "failed to fetch $URL" unless defined $retmech;
1056             $mech_obj->sleep(1); # let it settle
1057             # now the mech object has loaded the URL and has a DOM hopefully.
1058             # You can pass it on to find() or zap() to operate on the DOM.
1059              
1060              
1061             =head1 DEPENDENCIES
1062              
1063             This module depends on L<WWW::Mechanize::Chrome> which, in turn,
1064             depends on the C<google-chrome> executable be installed on the
1065             host computer. See L<WWW::Mechanize::Chrome::Install> on
1066             how to install the executable.
1067              
1068             Test scripts (which create there own mech object) will detect the absence
1069             of C<google-chrome> binary and exit gracefully, meaning the test passes.
1070             But with a STDERR message to the user. Who will hopefully notice it and
1071             proceed to C<google-chrome> installation. In any event, this module
1072             will be installed with or without C<google-chrome>.
1073              
1074             =head1 AUTHOR
1075              
1076             Andreas Hadjiprocopis, C<< <bliako at cpan.org> >>
1077              
1078             =head1 CODING CONDITIONS
1079              
1080             This code was written under extreme climate conditions of 44 Celsius.
1081             Keep packaging those
1082             vegs in kilos of plastic wrappers, keep obsolidating our perfectly good
1083             hardware, keep inventing new consumer needs and brainwash them
1084             down our throats, in short B<Crack Deep the Roof Beam, Capitalism>.
1085              
1086             =head1 BUGS
1087              
1088             Please report any bugs or feature requests to C<bug-www-mechanize-chrome-domops at rt.cpan.org>, or through
1089             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
1090             automatically be notified of progress on your bug as I make changes.
1091              
1092             =head1 SUPPORT
1093              
1094             You can find documentation for this module with the perldoc command.
1095              
1096             perldoc WWW::Mechanize::Chrome::DOMops
1097              
1098              
1099             You can also look for information at:
1100              
1101             =over 4
1102              
1103             =item * RT: CPAN's request tracker (report bugs here)
1104              
1105             L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=WWW-Mechanize-Chrome-DOMops>
1106              
1107             =item * AnnoCPAN: Annotated CPAN documentation
1108              
1109             L<http://annocpan.org/dist/WWW-Mechanize-Chrome-DOMops>
1110              
1111             =item * CPAN Ratings
1112              
1113             L<https://cpanratings.perl.org/d/WWW-Mechanize-Chrome-DOMops>
1114              
1115             =item * Search CPAN
1116              
1117             L<https://metacpan.org/release/WWW-Mechanize-Chrome-DOMops>
1118              
1119             =back
1120              
1121             =head1 DEDICATIONS
1122              
1123             Almaz
1124              
1125              
1126             =head1 ACKNOWLEDGEMENTS
1127              
1128             L<CORION> for publishing L<WWW::Mechanize::Chrome> and all its
1129             contributors.
1130              
1131              
1132             =head1 LICENSE AND COPYRIGHT
1133              
1134             Copyright 2019 Andreas Hadjiprocopis.
1135              
1136             This program is free software; you can redistribute it and/or modify it
1137             under the terms of the the Artistic License (2.0). You may obtain a
1138             copy of the full license at:
1139              
1140             L<http://www.perlfoundation.org/artistic_license_2_0>
1141              
1142             Any use, modification, and distribution of the Standard or Modified
1143             Versions is governed by this Artistic License. By using, modifying or
1144             distributing the Package, you accept this license. Do not use, modify,
1145             or distribute the Package, if you do not accept this license.
1146              
1147             If your Modified Version has been derived from a Modified Version made
1148             by someone other than you, you are nevertheless required to ensure that
1149             your Modified Version complies with the requirements of this license.
1150              
1151             This license does not grant you the right to use any trademark, service
1152             mark, tradename, or logo of the Copyright Holder.
1153              
1154             This license includes the non-exclusive, worldwide, free-of-charge
1155             patent license to make, have made, use, offer to sell, sell, import and
1156             otherwise transfer the Package with respect to any patent claims
1157             licensable by the Copyright Holder that are necessarily infringed by the
1158             Package. If you institute patent litigation (including a cross-claim or
1159             counterclaim) against any party alleging that the Package constitutes
1160             direct or contributory patent infringement, then this Artistic License
1161             to you shall terminate on the date that such litigation is filed.
1162              
1163             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
1164             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
1165             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
1166             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
1167             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
1168             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
1169             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
1170             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1171              
1172              
1173             =cut
1174              
1175             1; # End of WWW::Mechanize::Chrome::DOMops