| 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 |