...
`), the whole content in `nodeB` would be considered as inserted (because matching heirarchy is top to bottom).
99 |
100 | ## Documentation
101 |
102 | The general usage documentation can be found on http://engineering.wingify.com/dom-comparator/
103 |
104 | ## Authors
105 |
106 | * Himanshu Kapoor ([@fleon](http://github.com/fleon))
107 | * Himanshu Kela ([@himanshukela](http://github.com/himanshukela))
108 |
109 | ## License
110 |
111 | [The MIT License](http://opensource.org/licenses/MIT)
112 |
113 | Copyright (c) 2014-16 Wingify Software Pvt. Ltd.
114 |
--------------------------------------------------------------------------------
/test/test-index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | , ', i) || haystack.substr(i, 3) === '-->') {
77 | return Node.COMMENT_NODE;
78 | }
79 | if (haystack.lastIndexOf(' haystack.lastIndexOf(']]>', i) || haystack.substr(i, 3) === ']]>') {
80 | return Node.CDATA_SECTION_NODE;
81 | }
82 | if (haystack.lastIndexOf('<', i) > haystack.lastIndexOf('>', i) || haystack.charAt(i) === '>') {
83 | return Node.ELEMENT_NODE;
84 | }
85 | return Node.TEXT_NODE;
86 | },
87 |
88 | /**
89 | * Gets the node name at the index the pointer is pointing to
90 | * in the haystack.
91 | *
92 | * @return {String} The node name.
93 | */
94 | nodeName: function () {
95 | var i = this.index,
96 | haystack = this.haystack;
97 | var nodeType = this.nodeType();
98 |
99 | if (nodeType === Node.ELEMENT_NODE) {
100 | var j = haystack.lastIndexOf('<', i) + 1;
101 | var k = haystack.indexOf(' ', j);
102 | var l = haystack.indexOf('>', j);
103 | var nodeName = haystack.substring(j, Math.min(
104 | k === -1 ? l : k,
105 | l === -1 ? k : l
106 | ));
107 |
108 | if (nodeName.charAt(0) === '/') {
109 | nodeName = nodeName.substr(1);
110 | }
111 |
112 | if ($('
').get(0).nodeName === 'DIV') {
113 | nodeName = nodeName.toUpperCase();
114 | }
115 |
116 | return nodeName;
117 | }
118 |
119 | return nodeType;
120 | },
121 |
122 | /**
123 | * Determines whether the node at the current index
124 | * points to a closing tag. Returns false if the node at
125 | * index is not an Element node.
126 | *
127 | * @return {Boolean} True, if it is a closing tag.
128 | */
129 | pointsToClosingTag: function () {
130 | if (this.nodeType() !== Node.ELEMENT_NODE) return false;
131 |
132 | var j = this.haystack.lastIndexOf('<', this.index);
133 | return this.haystack.charAt(j + 1) === '/';
134 | },
135 |
136 | /**
137 | * Determines whether the node at the current index points
138 | * to an empty tag. Empty tags include: area, base, br, col,
139 | * hr, img, input, link, meta, param. Empty tags don't have
140 | * closing tags.
141 | *
142 | * @return {Boolean} True, if the node at index is an empty tag.
143 | */
144 | pointsToEmptyTag: function () {
145 | var emptyTags = /area|base|br|col|hr|img|input|link|meta|param/i;
146 | return emptyTags.test(this.nodeName());
147 | },
148 |
149 | /**
150 | * Returns a new pointer with the same haystack pointing to the node
151 | * previous to this node. The previous node isn't necessarily the
152 | * previous DOM sibling. It could be the parent as well. Returns
153 | * null if there is no previous node.
154 | *
155 | * @return {VWO.DOMNodeStringPointer} Pointer to the previous node.
156 | */
157 | previousPointer: function () {
158 | var i = this.index,
159 | haystack = this.haystack;
160 | var nodeType = this.nodeType();
161 | var j;
162 | var pointer;
163 | if (nodeType === Node.TEXT_NODE) {
164 | j = haystack.lastIndexOf('>', i);
165 | pointer = this._pointerWithIndex(j);
166 | } else if (nodeType === Node.COMMENT_NODE) {
167 | j = haystack.lastIndexOf('', i);
256 | if (j === -1) return null;
257 | pointer = this._pointerWithIndex(j + 3);
258 | } else if (nodeType === Node.CDATA_SECTION_NODE) {
259 | j = haystack.indexOf(']]>', i);
260 | if (j === -1) return null;
261 | pointer = this._pointerWithIndex(j + 3);
262 | } else {
263 | j = haystack.indexOf('>', i);
264 | if (j === -1) return null;
265 | pointer = this._pointerWithIndex(j + 1);
266 | }
267 |
268 | if (pointer.index < 0 || pointer.index >= haystack.length) return null;
269 |
270 | return pointer;
271 | },
272 |
273 | /**
274 | * Returns a new pointer with the same haystack pointing to the next
275 | * DOM sibling of this node. If no next sibling is found, it returns
276 | * null.
277 | *
278 | * @return {VWO.DOMNodeStringPointer} Pointer to the next sibling node.
279 | */
280 | nextSiblingPointer: function () {
281 | var i = this.index,
282 | haystack = this.haystack;
283 | var nodeType = this.nodeType();
284 | var nodeName = this.nodeName();
285 | var j;
286 | var pointer;
287 |
288 | if (nodeType === Node.TEXT_NODE) {
289 | j = haystack.indexOf('<', i);
290 | pointer = this._pointerWithIndex(j);
291 | } else if (nodeType === Node.COMMENT_NODE) {
292 | j = haystack.indexOf('-->', i);
293 | if (j === -1) return null;
294 | pointer = this._pointerWithIndex(j + 3);
295 | } else if (nodeType === Node.CDATA_SECTION_NODE) {
296 | j = haystack.indexOf(']]>', i);
297 | if (j === -1) return null;
298 | pointer = this._pointerWithIndex(j + 3);
299 | } else {
300 | j = haystack.indexOf('>', i);
301 | if (j === -1) return null;
302 |
303 | if (this.pointsToClosingTag()) {
304 | pointer = this._pointerWithIndex(j + 1);
305 | } else {
306 | // it is NOT a closing tag, where does this close?
307 | var k = j - 1;
308 | var openingTags = [];
309 | var l = 0;
310 | while (openingTags) {
311 | k = haystack.toLowerCase().indexOf('' + nodeName.toLowerCase(), k + 1);
312 | k = haystack.indexOf('>', k);
313 | openingTags = haystack.substring(j, k).match(new RegExp('<' + nodeName, 'gi'));
314 | if (openingTags && l++ == openingTags.length) break;
315 | }
316 | pointer = this._pointerWithIndex(k + 1);
317 | }
318 | }
319 |
320 | if (pointer.index < 0 || pointer.index >= haystack.length) return null;
321 |
322 | // if next node IS an empty tag, or IS a closing tag
323 | if (pointer.nodeType() === Node.ELEMENT_NODE && (pointer.pointsToEmptyTag() ||
324 | haystack.charAt(haystack.indexOf('<', pointer.index) + 1) === '/')) {
325 | return null;
326 | }
327 |
328 | return pointer;
329 | },
330 |
331 | /**
332 | * Returns a new pointer with the same haystack pointing to the parent
333 | * DOM node of this node. If no parent is found, it returns null.
334 | *
335 | * @return {VWO.DOMNodeStringPointer} Pointer to the parent node.
336 | * @return {VWO.DOMNodeStringPointer} Pointer to the parent node.
337 | */
338 | parentPointer: function () {
339 | var i = this.index,
340 | haystack = this.haystack;
341 | var nodeType = this.nodeType();
342 | var nodeName = this.nodeName();
343 | var j;
344 | var prev = this.previousSiblingPointer();
345 | if (prev) {
346 | return prev.parentPointer();
347 | }
348 | var pointer;
349 |
350 | if (nodeType === Node.TEXT_NODE) {
351 | j = haystack.lastIndexOf('>', i);
352 | pointer = this._pointerWithIndex(j);
353 | } else if (nodeType === Node.COMMENT_NODE) {
354 | j = haystack.lastIndexOf('>', a.nodeName(), a.haystack.substr(a.index, 5), '->', mi1, b.nodeName(),
685 | b.haystack.substr(b.index, 5), "->", mi2, c.nodeName(), c.haystack.substr(c.index, 5));
686 | }
687 | }
688 | }
689 | }
690 | }
691 |
692 | nodeMatches = _(nodeMatches).chain().invert().invert().value();
693 | innerNodeMatches = _(innerNodeMatches).chain().invert().invert().value();
694 |
695 | console.log('nodeMatchesCount:', _(nodeMatches).keys().length);
696 | console.log('nodeMatches:', nodeMatches);
697 | console.log('innerNodeMatchesCount:', _(innerNodeMatches).keys().length);
698 | console.log('innerNodeMatches:', innerNodeMatches);
699 | console.log('totalCount:', _($.extend(nodeMatches, innerNodeMatches)).keys().length);
700 |
701 | this.matches = nodeMatches;
702 | this.stringA = stringA_original;
703 | this.stringB = stringB_original;
704 | return this;
705 | }
706 | };
--------------------------------------------------------------------------------
/src/dom-comparator.js:
--------------------------------------------------------------------------------
1 | var $ = window.vwo_$ || window.$;
2 | var _ = window.vwo__ || window._;
3 | var VWO = window.VWOInjected || window.VWO || {};
4 |
5 | /**
6 | * Compare two DOM trees and computes the final result
7 | * of changes.
8 | *
9 | * @class
10 | * @memberOf VWO
11 | */
12 | VWO.DOMComparator = function (params) {
13 | // clear the DOMNodePool before we begin
14 | VWO.DOMNodePool.clear();
15 |
16 | $.extend(true, this, params);
17 |
18 | if (this.nodeA) {
19 | this.elA = this.nodeA.el;
20 | }
21 | if (this.nodeB) {
22 | this.elB = this.nodeB.el;
23 | }
24 |
25 |
26 | /*
27 | Removing extra #text coming as inputs via textarea .
28 | */
29 | var h, leA = this.elA.length,
30 | leB = this.elB.length;
31 | for (h = 0; h < leA; h++) {
32 | if (this.elA[h].data) {
33 | this.elA.splice(h, 1);
34 | h--;
35 | leA--;
36 | }
37 | }
38 |
39 | for (h = 0; h < leB; h++) {
40 | if (this.elB[h].data) {
41 | this.elB.splice(h, 1);
42 | h--;
43 | leB--;
44 | }
45 | }
46 |
47 | /*
48 | Striping the nodes and removing extra spaces and line breaks
49 | taking care of all escape characters
50 |
51 | */
52 |
53 | var outA = stripNodes($(this.elA).outerHTML());
54 | var outB = stripNodes($(this.elB).outerHTML());
55 |
56 | /*
57 |
58 | Wrapper method is set to "
"
59 | Because it is not standard as ***
60 |
61 | */
62 |
63 | this.nodeA = VWO.DOMNodePool.create({
64 | el: $("" + outA + "").get(0)
65 | });
66 | this.nodeB = VWO.DOMNodePool.create({
67 | el: $("" + outB + "").get(0)
68 | });
69 | this.elAClone = $("" + outA + "").get(0);
70 | };
71 |
72 |
73 | function stripNodes(node) {
74 | var ans = node.replace(/(?:\r\n|\r|\n)/g, '');
75 | ans = ans.replace(/\>\s+\<')
76 | var out = '',
77 | l = ans.length,
78 | i = 0;
79 | while (i < l) {
80 | if (ans[i] == '"') {
81 | out += ans[i];
82 | while (1) {
83 | i = i + 1;
84 | if (ans[i] == '"') {
85 | out += ans[i];
86 | break;
87 | }
88 | if ((i + 1) < l && ans[i + 1] == "'") {
89 | out += ans[i];
90 | out += '\\';
91 | continue;
92 | }
93 | if (ans[i] == ' ') {
94 | if (ans[i + 1].search(/[^A-Z0-9a-z\s]/) == -1) {
95 | out += ans[i];
96 | continue;
97 | } else
98 | continue;
99 | }
100 |
101 | out += ans[i];
102 | }
103 | } else {
104 | if ((i + 1) < l && ans[i + 1] == "'") {
105 | out += ans[i];
106 | out += '\\';
107 | } else
108 | out += ans[i];
109 | }
110 | i = i + 1;
111 | }
112 |
113 | return out;
114 | };
115 |
116 |
117 | VWO.DOMComparator.create = function (params) {
118 | return new VWO.DOMComparator(params);
119 | };
120 |
121 | VWO.DOMComparator.prototype = {
122 | /** @type {Node} First of the two elements to compare. */
123 | elA: null,
124 |
125 | /** @type {Node} Second of the two elements to compare. */
126 | elB: null,
127 |
128 | /** @type {VWO.DOMNode} First of the two nodes to compare. */
129 | nodeA: null,
130 |
131 | /** @type {VWO.DOMNode} Second of the two nodes to compare. */
132 | nodeB: null,
133 |
134 | /**
135 | * Uses VWO.DOMMatchFinder to find matches between the two nodes.
136 | * The matches are used to set a 'matchedWith' property on each
137 | * of the nodes in two trees for comparison later. On the nodes in
138 | * the second tree, additional properties 'matchScore' and
139 | * 'matchDifference' are also set.
140 | */
141 |
142 |
143 | analyzeMatches: function () {
144 | var matches = VWO.DOMMatchFinder.create({
145 | nodeA: this.nodeA,
146 | nodeB: this.nodeB
147 | }).compare().matches;
148 |
149 | var nodeADescendants = this.nodeA.descendants();
150 | var nodeBDescendants = this.nodeB.descendants();
151 |
152 | for (var i in matches)
153 | if (matches.hasOwnProperty(i)) {
154 | var j = matches[i];
155 |
156 | var comparison = VWO.DOMNodeComparator.create({
157 | nodeA: nodeADescendants[i],
158 | nodeB: nodeBDescendants[j]
159 | });
160 |
161 | var matchScore = comparison.finalScore();
162 |
163 | if (matchScore) {
164 | nodeADescendants[i].matchedWith = nodeBDescendants[j];
165 | nodeBDescendants[j].matchedWith = nodeADescendants[i];
166 | nodeBDescendants[j].matchScore = comparison.finalScore();
167 | nodeBDescendants[j].matchDifference = comparison.difference();
168 | }
169 | }
170 | },
171 |
172 | /**
173 | * Detects newly inserted nodes in the second tree based on nodes
174 | * in the tree that do not have any matches in the initial tree.
175 | *
176 | * @return {Object[][]} A list of inital and final states
177 | * of the operations performed.
178 | */
179 | detectInserts: function () {
180 | var finalTree = this.nodeB.descendants();
181 | var finalOperationsList = [];
182 |
183 | // this loop runs for objects that are newly inserted
184 | _(finalTree).each(function (node, i) {
185 | var adjacentNode, nodeI;
186 |
187 | // if node has a match, nothing to do
188 | if (node.matchedWith) return;
189 |
190 | // if my parent doesn't have a match, I have already been
191 | // implicitly inserted.
192 | if (node.parent() && !node.parent().matchedWith) return;
193 |
194 | // node insertion at node.masterIndex
195 | adjacentNode = null;
196 | // get my match's next sibling who actually has a match
197 | // if found, node = thus found node
198 | // if no such sibling is found,
199 | // node = my match's parent's match
200 |
201 | // find
202 | nodeI = node;
203 | while (nodeI = nodeI.nextSibling()) {
204 | if (nodeI.matchedWith) break;
205 | }
206 |
207 | // match found
208 | var insertedNode = node.copy();
209 |
210 | if (nodeI && nodeI.matchedWith) {
211 | adjacentNode = nodeI.matchedWith;
212 | // adjacentNode.parent().addChildAt(insertedNode, adjacentNode.index());
213 | adjacentNode.parent().addChildAt(insertedNode, node.index());
214 |
215 | } else {
216 | adjacentNode = node.parent().matchedWith;
217 | adjacentNode.addChild(insertedNode);
218 | }
219 |
220 | var parentSelectorPath = insertedNode.parent().selectorPath();
221 | var indexInParent = insertedNode.index();
222 |
223 | // set a flag on the node
224 | insertedNode.isInserted = true;
225 |
226 | finalOperationsList.push(({
227 | name: 'insertNode',
228 | // an inserted node does not have a selector path
229 | selectorPath: null,
230 | content: {
231 | html: insertedNode.outerHTML(),
232 | parentSelectorPath: parentSelectorPath,
233 | indexInParent: indexInParent,
234 | existsInDOM: true
235 | }
236 | }));
237 | });
238 |
239 | return finalOperationsList;
240 | },
241 |
242 | /**
243 | * Detects text nodes that matched in both trees and may have their
244 | * 'textContent' changed.
245 | *
246 | * @return {Object[][]} A list of inital and final states
247 | * of the operations performed.
248 | */
249 | detectTextNodeChanges: function () {
250 | var initialTree = this.nodeA.descendants();
251 | var finalOperationsList = [];
252 |
253 | _(initialTree).each(function (node, i) {
254 | // if node isn't matched, nothing to do (it will be removed in the next iteration)
255 | if (!node.matchedWith) return;
256 |
257 | // if node is matched but its a 100% match, nothing to do still,
258 | // rearranges will happen in the next iteration
259 | if (node.matchedWith.matchScore === 1) return;
260 |
261 | // finally get the matchDifference (a set of attr and css changes)
262 | var matchDifference = node.matchedWith.matchDifference;
263 |
264 | // figure out changes for changeTextBefore, changeCommentBefore
265 | if (node.nodeType() !== Node.ELEMENT_NODE && matchDifference.newInnerText) { // insert new text and comment nodes
266 | var parentSelectorPath = node.parent().selectorPath();
267 | var indexInParent = node.index();
268 |
269 | finalOperationsList.push(({
270 | name: 'change' + (node.nodeType() === Node.TEXT_NODE ? 'Text' : 'Comment'),
271 | // a text / comment node does not have a selector path
272 | selectorPath: null,
273 | content: {
274 | text: matchDifference.newInnerText,
275 | parentSelectorPath: parentSelectorPath,
276 | indexInParent: indexInParent
277 | }
278 | }));
279 |
280 | node.el.textContent = matchDifference.newInnerText;
281 | }
282 | });
283 |
284 | return finalOperationsList;
285 | },
286 |
287 | /**
288 | * For all the nodes that matched, detects if the attributes changed.
289 | * If so, returns them.
290 | *
291 | * @return {Object[][]} A list of inital and final states
292 | * of the operations performed.
293 | */
294 | detectAttributeChanges: function () {
295 | var initialTree = this.nodeA.descendants();
296 | var finalOperationsList = [];
297 |
298 | _(initialTree).each(function (node, i) {
299 | // if node isn't matched, nothing to do (it will be removed in the next iteration)
300 | if (!node.matchedWith) return;
301 |
302 | // if node is matched but its a 100% match, nothing to do still,
303 | // replacements will happen in the next iteration
304 | if (node.matchedWith.matchScore === 1) return;
305 |
306 | // finally get the matchDifference (a set of attr and css changes
307 | var matchDifference = node.matchedWith.matchDifference;
308 |
309 | var attr = $.extend({},
310 | matchDifference.addedAttributes,
311 | matchDifference.changedAttributes);
312 | var oldattr;
313 |
314 |
315 | // Runs over all the attributes for e.g ._(attr).keys() = class
316 | if (_(attr).keys().length) {
317 | oldattr = {};
318 | _(attr).each(function (attr, key) {
319 | oldattr[key] = node.$().attr(key);
320 | });
321 |
322 | // node.$().attr('class') = class_name
323 | node.$().attr(attr);
324 | finalOperationsList.push(({
325 | name: 'attr',
326 | selectorPath: node.selectorPath(),
327 | content: attr
328 | }));
329 | }
330 |
331 |
332 | // Gets the list of all attributes removed
333 | var removedAttr = matchDifference.removedAttributes;
334 | if (_(removedAttr).keys().length) {
335 | oldattr = {};
336 | _(removedAttr).each(function (attr, key) {
337 | oldattr[key] = node.$().attr(key);
338 | node.$().removeAttr(key);
339 | });
340 |
341 | finalOperationsList.push(({
342 | name: 'removeAttr',
343 | selectorPath: node.selectorPath(),
344 | content: matchDifference.removedAttributes
345 | }));
346 | }
347 | });
348 |
349 | VWO.DOMNodePool.uncacheAll();
350 | VWO.DOMNodePool.cacheAll();
351 |
352 | return finalOperationsList;
353 | },
354 |
355 | /**
356 | * For all the nodes that matched, detects if the styles changed.
357 | * If so, returns them.
358 | *
359 | * @return {Object[][]} A list of inital and final states
360 | * of the operations performed.
361 | */
362 | detectStyleChanges: function () {
363 | var initialTree = this.nodeA.descendants();
364 | var finalOperationsList = [];
365 |
366 | _(initialTree).each(function (node, i) {
367 | // if node isn't matched, nothing to do (it will be removed in the next iteration)
368 | if (!node.matchedWith) return;
369 |
370 | // if node is matched but its a 100% match, nothing to do still,
371 | // replacements will happen in the next iteration
372 | if (node.matchedWith.matchScore === 1) return;
373 |
374 | // finally get the matchDifference (a set of attr and css changes
375 | var matchDifference = node.matchedWith.matchDifference;
376 |
377 | var css = $.extend({}, matchDifference.addedStyles, matchDifference.changedStyles);
378 | var oldcss;
379 |
380 | if (_(css).keys().length) {
381 | oldcss = {};
382 | _(css).each(function (css, key) {
383 | oldcss[key] = node.$().css(key);
384 | });
385 |
386 | node.$().css(css);
387 | finalOperationsList.push(({
388 | name: 'css',
389 | selectorPath: node.selectorPath(),
390 | content: css
391 | }));
392 | }
393 |
394 | var removedCss = matchDifference.removedStyles;
395 | if (_(removedCss).keys().length) {
396 | oldcss = {};
397 | _(removedCss).each(function (css, key) {
398 | oldcss[key] = node.$().css(key);
399 | node.$().css(key, '');
400 | });
401 |
402 | _(removedCss).each(function (css) {
403 | node.$().css(css, '');
404 | });
405 | finalOperationsList.push(({
406 | name: 'removeCss',
407 | selectorPath: node.selectorPath(),
408 | content: removedCss
409 | }));
410 | }
411 | });
412 |
413 | VWO.DOMNodePool.uncacheAll();
414 | VWO.DOMNodePool.cacheAll();
415 |
416 | return finalOperationsList;
417 | },
418 |
419 | /**
420 | * If the position (indexes) of the two matched nodes differs, it is
421 | * detected as a rearrange.
422 | *
423 | * @return {Object[][]} A list of inital and final states
424 | * of the operations performed.
425 | */
426 | detectRearranges: function () {
427 | var initialTree = this.nodeA.descendants();
428 | var finalOperationsList = [];
429 |
430 | _(initialTree).each(function (node, i) {
431 | var adjacentNode, nodeI;
432 |
433 | // if the node is not matched with anything, return
434 | // it has already been removed/will be removed in detectRemoves
435 | if (!node.matchedWith) return;
436 |
437 | // if my parent has matched with my match's parent, and my index is unchanged, continue.
438 | if (node.parent() && node.parent().matchedWith &&
439 | node.parent().matchedWith === node.matchedWith.parent()) {
440 | if (node.index() === node.matchedWith.index()) return;
441 |
442 | // if my index changed, and yet, my next sibling... wtf?
443 | }
444 |
445 | // if both me and my match are root nodes without any parent, continue
446 | if (!node.parent() && !node.matchedWith.parent()) return;
447 |
448 | // match found, begin replacement
449 | adjacentNode = null;
450 |
451 | var oldParentSelectorPath = node.parent().selectorPath();
452 | var oldIndexInParent = node.index();
453 |
454 | // get my match's next sibling who actually has a match
455 | // if found, node = thus found node
456 | // if no such sibling is found,
457 | // node = my match's parent's match
458 | nodeI = node.matchedWith;
459 | while (nodeI = nodeI.nextSibling()) {
460 | if (nodeI.matchedWith) break;
461 | }
462 |
463 | if (nodeI && nodeI.matchedWith) {
464 | adjacentNode = nodeI.matchedWith;
465 | adjacentNode.parent().addChildAt(node, adjacentNode.index());
466 | } else {
467 | var parentNodeOfMatch = node.matchedWith.parent();
468 |
469 | // well what if parent node of match was a new node
470 | // solution: insertion should happen beforehand
471 | // so that new elements in the final dom are actually a part of the
472 | // initial dom.
473 | if (parentNodeOfMatch.matchedWith) {
474 | adjacentNode = parentNodeOfMatch.matchedWith;
475 | } else {
476 | adjacentNode = parentNodeOfMatch;
477 | }
478 | adjacentNode.addChild(node);
479 | }
480 |
481 | var parentSelectorPath = node.parent().selectorPath();
482 | var indexInParent = node.index();
483 |
484 | finalOperationsList.push(({
485 | name: 'rearrange',
486 | // since text nodes can also be rearranged, selector path
487 | // is always set to null
488 | selectorPath: null,
489 | content: {
490 | parentSelectorPath: parentSelectorPath,
491 | indexInParent: indexInParent,
492 | oldParentSelectorPath: oldParentSelectorPath,
493 | oldIndexInParent: oldIndexInParent,
494 | existsInDOM: true
495 | }
496 | }));
497 | VWO.DOMNodePool.uncacheAll();
498 | VWO.DOMNodePool.cacheAll();
499 |
500 | });
501 |
502 | return finalOperationsList;
503 | },
504 |
505 | /**
506 | * For all nodes in the initial tree that do not have a match in the
507 | * final tree, a remove is detected.
508 | *
509 | * @return {Object[][]} A list of inital and final states
510 | * of the operations performed.
511 | */
512 | detectRemoves: function () {
513 | var initialTree = this.nodeA.descendants();
514 | var finalOperationsList = [];
515 |
516 | _(initialTree).each(function (node, i) {
517 | if (node.matchedWith) return;
518 |
519 | // if the node has been just inserted by detectInserts, ignore
520 | if (node.isInserted) return;
521 |
522 | // if my parent is removed, i am implicitly removed too.
523 | // removed = !matchedWith
524 | if (node.parent() && !node.parent().matchedWith) return;
525 |
526 | var parentSelectorPath = node.parent().selectorPath();
527 | var indexInParent = node.index();
528 |
529 | // this node has no match, this should be removed
530 | node.parent().removeChild(node);
531 |
532 | finalOperationsList.push(({
533 | name: 'deleteNode',
534 | // a remove operation cannot have a selector path,
535 | // a text node could also be removed
536 | selectorPath: null,
537 | content: {
538 | html: node.outerHTML(),
539 | parentSelectorPath: parentSelectorPath,
540 | indexInParent: indexInParent,
541 | existsInDOM: false
542 | }
543 | }));
544 | });
545 |
546 | return finalOperationsList;
547 | },
548 |
549 | detectRemovesInB: function () {
550 | var initialTree = this.nodeB.descendants();
551 | var finalOperationsList = [];
552 |
553 | _(initialTree).each(function (node, i) {
554 | if (node.matchedWith) return;
555 |
556 | // if the node has been just inserted by detectInserts, ignore
557 | if (node.isInserted) return;
558 |
559 | // if my parent is removed, i am implicitly removed too.
560 | // removed = !matchedWith
561 | if (node.parent() && !node.parent().matchedWith) return;
562 |
563 | var parentSelectorPath = node.parent().selectorPath();
564 | var indexInParent = node.index();
565 |
566 | // this node has no match, this should be removed
567 | node.parent().removeChild(node);
568 |
569 | finalOperationsList.push(({
570 | name: 'deleteNodeInB',
571 | // a remove operation cannot have a selector path,
572 | // a text node could also be removed
573 | selectorPath: null,
574 | content: {
575 | html: node.outerHTML(),
576 | node: node,
577 | parentSelectorPath: parentSelectorPath,
578 | indexInParent: indexInParent,
579 | existsInDOM: false
580 | }
581 | }));
582 | });
583 |
584 | VWO.DOMNodePool.uncacheAll();
585 | VWO.DOMNodePool.cacheAll();
586 |
587 | return finalOperationsList;
588 | },
589 |
590 |
591 |
592 | /**
593 | * Finally, verify if the comparison was successful.
594 | * (A console.log message is sent.)
595 | */
596 | verifyComparison: function () {
597 | console.log('comparison successful: ' + this.nodeA.equals(this.nodeB));
598 | return this.nodeA.equals(this.nodeB);
599 | },
600 |
601 | /**
602 | * Assuming the two nodes are set, begin the comparison.
603 | *
604 | * @return {Object[][]} A list of inital and final states
605 | * of the operations performed.
606 | */
607 | compare: function () {
608 | var self = this;
609 |
610 | this.analyzeMatches();
611 |
612 | var final_results = [];
613 |
614 | var result1 = [
615 | this.detectRemovesInB(),
616 | this.detectRearranges()
617 | ];
618 |
619 | VWO.DOMNodePool.uncacheAll();
620 | VWO.DOMNodePool.cacheAll();
621 |
622 | result1 = _(result1).flatten();
623 |
624 | function getActualIndex(parentSelectorPath, indexInParent) {
625 | var parentNode = VWO.DOMNode.create({
626 | el: self.nodeB.el.parentNode.querySelector(parentSelectorPath)
627 | });
628 | if (indexInParent < 0)
629 | return -1;
630 | var childNode = parentNode.children()[indexInParent];
631 | return Array.prototype.slice.apply(parentNode.el.childNodes).indexOf(childNode.el);
632 | };
633 |
634 | var output = [],
635 | index, path, html, text, val, attr, css, index1, index2, path1, path2;
636 | var l = result1.length;
637 | for (var i = (l - 1); i >= 0; i--) {
638 | var op = result1[i];
639 | if (op.name == 'deleteNodeInB') {
640 | index = getActualIndex(op.content.parentSelectorPath, op.content.indexInParent - 1);
641 | path = op.content.parentSelectorPath.split('DOMComparisonResult > ')[1];
642 | if (!path)
643 | path = op.content.parentSelectorPath;
644 | html = op.content.html;
645 | if (index == -1)
646 | output[i] = '$(' + JSON.stringify(path) + ').prepend(' + JSON.stringify(html) + ');';
647 | else
648 | output[i] = '$($(' + JSON.stringify(path) + ').get(0).childNodes[' + index + ']).after(' + JSON.stringify(html) + ');';
649 |
650 | var ctx = self.nodeB.el;
651 | var $ = function (selector) {
652 | if (selector == "HIM#DOMComparisonResult")
653 | return jQuery(ctx);
654 | return jQuery(selector, ctx);
655 | };
656 | eval(output[i]);
657 | } else
658 | final_results.push(result1[i]);
659 | }
660 | VWO.DOMNodePool.uncacheAll();
661 | VWO.DOMNodePool.cacheAll();
662 |
663 |
664 | var result = [
665 | this.detectInserts(),
666 | this.detectRemoves(),
667 | this.detectTextNodeChanges(),
668 | this.detectAttributeChanges(),
669 | this.detectStyleChanges(),
670 | ];
671 |
672 | result = _(result).flatten();
673 |
674 | var le = result.length;
675 |
676 | for (i = 0; i < le; i++)
677 | final_results.push(result[i]);
678 |
679 | console.log(final_results);
680 |
681 | this.verifyComparison();
682 |
683 |
684 |
685 | result.toJqueryCode = function toJqueryCode() {
686 | function getActualIndex(parentSelectorPath, indexInParent) {
687 | var parentNode = VWO.DOMNode.create({
688 | el: self.elAClone.parentNode.querySelector(parentSelectorPath)
689 | });
690 | if (indexInParent < 0)
691 | return -1;
692 | var childNode = parentNode.children()[indexInParent];
693 | return Array.prototype.slice.apply(parentNode.el.childNodes).indexOf(childNode.el);
694 | }
695 |
696 | var output = [],
697 | index, path, html, text, val, attr, css, index1, index2, path1, path2;
698 | for (var i = 0, l = this.length; i < l; i++) {
699 | var op = this[i];
700 | switch (op.name) {
701 | case 'insertNode':
702 | index = getActualIndex(op.content.parentSelectorPath, op.content.indexInParent - 1);
703 | path = op.content.parentSelectorPath.split('DOMComparisonResult > ')[1];
704 | html = op.content.html;
705 | if (index == -1)
706 | output[i] = '$(' + JSON.stringify(path) + ').append(' + JSON.stringify(html) + ');';
707 | else
708 | output[i] = '$($(' + JSON.stringify(path) + ').get(0).childNodes[' + index + ']).after(' + JSON.stringify(html) + ');';
709 | break;
710 |
711 | case 'rearrange':
712 | index1 = getActualIndex(op.content.parentSelectorPath, op.content.indexInParent - 1);
713 | index2 = getActualIndex(op.content.oldParentSelectorPath, op.content.oldIndexInParent);
714 | path1 = op.content.parentSelectorPath.split('DOMComparisonResult > ')[1];
715 | path2 = op.content.oldParentSelectorPath.split('DOMComparisonResult > ')[1];
716 | var node = '$(' + path2 + ').get(0).childNodes[' + index2 + ']';
717 | if (index1 == -1)
718 | output[i] = '$(' + JSON.stringify(path1) + ').append(' + node + ');';
719 | else
720 | output[i] = '$($(' + JSON.stringify(path1) + ').get(0).childNodes[' + index1 + ']).after(' + node + ');';
721 | case 'deleteNode':
722 | index = getActualIndex(op.content.parentSelectorPath, op.content.indexInParent);
723 | path = op.content.parentSelectorPath.split('DOMComparisonResult > ')[1];
724 | html = op.content.html;
725 | output[i] = '$($(' + JSON.stringify(path) + ').get(0).childNodes[' + index + ']).remove();';
726 | break;
727 | case 'changeText':
728 | case 'changeComment':
729 | index = getActualIndex(op.content.parentSelectorPath, op.content.indexInParent);
730 | path = op.content.parentSelectorPath.split('DOMComparisonResult > ')[1];
731 | text = op.content.text;
732 | output[i] = '$($(' + JSON.stringify(path) + ').get(0).childNodes[' + index + ']).remove();';
733 | var ctx = self.elAClone;
734 | var $ = function (selector) {
735 | return jQuery(selector, ctx);
736 | };
737 | eval(output[i]);
738 | output[i] = '$(' + JSON.stringify(path) + ').append(' + JSON.stringify(text) + ');';
739 | break;
740 | case 'attr':
741 | case 'css':
742 | path = op.selectorPath.split('DOMComparisonResult > ')[1];
743 | val = op.content;
744 | output[i] = '$(' + JSON.stringify(path) + ').' + op.name + '(' + JSON.stringify(val) + ');';
745 | break;
746 | case 'removeAttr':
747 | path = op.selectorPath.split('DOMComparisonResult > ')[1];
748 | attr = Object.keys(op.content);
749 | output[i] = '$(' + JSON.stringify(path) + ')' + attr.map(function (attr) {
750 | return '.removeAttr(' + JSON.stringify(attr) + ')';
751 | }).join('') + ';';
752 | break;
753 | case 'removeCss':
754 | path = op.selectorPath.split('DOMComparisonResult > ')[1];
755 | css = Object.keys(op.content);
756 | output[i] = '$(' + JSON.stringify(path) + ')' + css.map(function (css) {
757 | return '.css(' + JSON.stringify(css) + ', "")';
758 | }).join('') + ';';
759 | break;
760 | }
761 | var ctx = self.elAClone;
762 | var $ = function (selector) {
763 | return jQuery(selector, ctx);
764 | };
765 | eval(output[i]);
766 | }
767 | return self.elAClone;
768 | };
769 |
770 | return final_results;
771 | }
772 | };
--------------------------------------------------------------------------------
/dist/dom-comparator.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | The MIT License (MIT)
3 | http://opensource.org/licenses/MIT
4 |
5 | Copyright (c) 2014 Wingify Software Pvt. Ltd.
6 | http://wingify.com
7 | */
8 |
9 | var VWO = window.VWO || {};
10 |
11 | !function(exports,global){function stripNodes(node){var ans=node.replace(/(?:\r\n|\r|\n)/g,"");ans=ans.replace(/\>\s+\<");for(var out="",l=ans.length,i=0;l>i;){if('"'==ans[i])for(out+=ans[i];;){if(i+=1,'"'==ans[i]){out+=ans[i];break}if(l>i+1&&"'"==ans[i+1])out+=ans[i],out+="\\";else if(" "!=ans[i])out+=ans[i];else if(-1==ans[i+1].search(/[^A-Z0-9a-z\s]/)){out+=ans[i];continue}}else l>i+1&&"'"==ans[i+1]?(out+=ans[i],out+="\\"):out+=ans[i];i+=1}return out}global.closure=exports;var $=window.vwo_$||window.$,_=window.vwo__||window._,VWO=window.VWOInjected||window.VWO||{};VWO.DOMComparator=function(params){VWO.DOMNodePool.clear(),$.extend(!0,this,params),this.nodeA&&(this.elA=this.nodeA.el),this.nodeB&&(this.elB=this.nodeB.el);var h,leA=this.elA.length,leB=this.elB.length;for(h=0;leA>h;h++)this.elA[h].data&&(this.elA.splice(h,1),h--,leA--);for(h=0;leB>h;h++)this.elB[h].data&&(this.elB.splice(h,1),h--,leB--);var outA=stripNodes($(this.elA).outerHTML()),outB=stripNodes($(this.elB).outerHTML());this.nodeA=VWO.DOMNodePool.create({el:$(""+outA+"").get(0)}),this.nodeB=VWO.DOMNodePool.create({el:$(""+outB+"").get(0)}),this.elAClone=$(""+outA+"").get(0)},VWO.DOMComparator.create=function(params){return new VWO.DOMComparator(params)},VWO.DOMComparator.prototype={elA:null,elB:null,nodeA:null,nodeB:null,analyzeMatches:function(){var matches=VWO.DOMMatchFinder.create({nodeA:this.nodeA,nodeB:this.nodeB}).compare().matches,nodeADescendants=this.nodeA.descendants(),nodeBDescendants=this.nodeB.descendants();for(var i in matches)if(matches.hasOwnProperty(i)){var j=matches[i],comparison=VWO.DOMNodeComparator.create({nodeA:nodeADescendants[i],nodeB:nodeBDescendants[j]}),matchScore=comparison.finalScore();matchScore&&(nodeADescendants[i].matchedWith=nodeBDescendants[j],nodeBDescendants[j].matchedWith=nodeADescendants[i],nodeBDescendants[j].matchScore=comparison.finalScore(),nodeBDescendants[j].matchDifference=comparison.difference())}},detectInserts:function(){var finalTree=this.nodeB.descendants(),finalOperationsList=[];return _(finalTree).each(function(node){var adjacentNode,nodeI;if(!node.matchedWith&&(!node.parent()||node.parent().matchedWith)){for(adjacentNode=null,nodeI=node;(nodeI=nodeI.nextSibling())&&!nodeI.matchedWith;);var insertedNode=node.copy();nodeI&&nodeI.matchedWith?(adjacentNode=nodeI.matchedWith,adjacentNode.parent().addChildAt(insertedNode,node.index())):(adjacentNode=node.parent().matchedWith,adjacentNode.addChild(insertedNode));var parentSelectorPath=insertedNode.parent().selectorPath(),indexInParent=insertedNode.index();insertedNode.isInserted=!0,finalOperationsList.push({name:"insertNode",selectorPath:null,content:{html:insertedNode.outerHTML(),parentSelectorPath:parentSelectorPath,indexInParent:indexInParent,existsInDOM:!0}})}}),finalOperationsList},detectTextNodeChanges:function(){var initialTree=this.nodeA.descendants(),finalOperationsList=[];return _(initialTree).each(function(node){if(node.matchedWith&&1!==node.matchedWith.matchScore){var matchDifference=node.matchedWith.matchDifference;if(node.nodeType()!==Node.ELEMENT_NODE&&matchDifference.newInnerText){var parentSelectorPath=node.parent().selectorPath(),indexInParent=node.index();finalOperationsList.push({name:"change"+(node.nodeType()===Node.TEXT_NODE?"Text":"Comment"),selectorPath:null,content:{text:matchDifference.newInnerText,parentSelectorPath:parentSelectorPath,indexInParent:indexInParent}}),node.el.textContent=matchDifference.newInnerText}}}),finalOperationsList},detectAttributeChanges:function(){var initialTree=this.nodeA.descendants(),finalOperationsList=[];return _(initialTree).each(function(node){if(node.matchedWith&&1!==node.matchedWith.matchScore){var oldattr,matchDifference=node.matchedWith.matchDifference,attr=$.extend({},matchDifference.addedAttributes,matchDifference.changedAttributes);_(attr).keys().length&&(oldattr={},_(attr).each(function(attr,key){oldattr[key]=node.$().attr(key)}),node.$().attr(attr),finalOperationsList.push({name:"attr",selectorPath:node.selectorPath(),content:attr}));var removedAttr=matchDifference.removedAttributes;_(removedAttr).keys().length&&(oldattr={},_(removedAttr).each(function(attr,key){oldattr[key]=node.$().attr(key),node.$().removeAttr(key)}),finalOperationsList.push({name:"removeAttr",selectorPath:node.selectorPath(),content:matchDifference.removedAttributes}))}}),VWO.DOMNodePool.uncacheAll(),VWO.DOMNodePool.cacheAll(),finalOperationsList},detectStyleChanges:function(){var initialTree=this.nodeA.descendants(),finalOperationsList=[];return _(initialTree).each(function(node){if(node.matchedWith&&1!==node.matchedWith.matchScore){var oldcss,matchDifference=node.matchedWith.matchDifference,css=$.extend({},matchDifference.addedStyles,matchDifference.changedStyles);_(css).keys().length&&(oldcss={},_(css).each(function(css,key){oldcss[key]=node.$().css(key)}),node.$().css(css),finalOperationsList.push({name:"css",selectorPath:node.selectorPath(),content:css}));var removedCss=matchDifference.removedStyles;_(removedCss).keys().length&&(oldcss={},_(removedCss).each(function(css,key){oldcss[key]=node.$().css(key),node.$().css(key,"")}),_(removedCss).each(function(css){node.$().css(css,"")}),finalOperationsList.push({name:"removeCss",selectorPath:node.selectorPath(),content:removedCss}))}}),VWO.DOMNodePool.uncacheAll(),VWO.DOMNodePool.cacheAll(),finalOperationsList},detectRearranges:function(){var initialTree=this.nodeA.descendants(),finalOperationsList=[];return _(initialTree).each(function(node){var adjacentNode,nodeI;if(node.matchedWith&&!(node.parent()&&node.parent().matchedWith&&node.parent().matchedWith===node.matchedWith.parent()&&node.index()===node.matchedWith.index()||!node.parent()&&!node.matchedWith.parent())){adjacentNode=null;var oldParentSelectorPath=node.parent().selectorPath(),oldIndexInParent=node.index();for(nodeI=node.matchedWith;(nodeI=nodeI.nextSibling())&&!nodeI.matchedWith;);if(nodeI&&nodeI.matchedWith)adjacentNode=nodeI.matchedWith,adjacentNode.parent().addChildAt(node,adjacentNode.index());else{var parentNodeOfMatch=node.matchedWith.parent();adjacentNode=parentNodeOfMatch.matchedWith?parentNodeOfMatch.matchedWith:parentNodeOfMatch,adjacentNode.addChild(node)}var parentSelectorPath=node.parent().selectorPath(),indexInParent=node.index();finalOperationsList.push({name:"rearrange",selectorPath:null,content:{parentSelectorPath:parentSelectorPath,indexInParent:indexInParent,oldParentSelectorPath:oldParentSelectorPath,oldIndexInParent:oldIndexInParent,existsInDOM:!0}}),VWO.DOMNodePool.uncacheAll(),VWO.DOMNodePool.cacheAll()}}),finalOperationsList},detectRemoves:function(){var initialTree=this.nodeA.descendants(),finalOperationsList=[];return _(initialTree).each(function(node){if(!(node.matchedWith||node.isInserted||node.parent()&&!node.parent().matchedWith)){var parentSelectorPath=node.parent().selectorPath(),indexInParent=node.index();node.parent().removeChild(node),finalOperationsList.push({name:"deleteNode",selectorPath:null,content:{html:node.outerHTML(),parentSelectorPath:parentSelectorPath,indexInParent:indexInParent,existsInDOM:!1}})}}),finalOperationsList},detectRemovesInB:function(){var initialTree=this.nodeB.descendants(),finalOperationsList=[];return _(initialTree).each(function(node){if(!(node.matchedWith||node.isInserted||node.parent()&&!node.parent().matchedWith)){var parentSelectorPath=node.parent().selectorPath(),indexInParent=node.index();node.parent().removeChild(node),finalOperationsList.push({name:"deleteNodeInB",selectorPath:null,content:{html:node.outerHTML(),node:node,parentSelectorPath:parentSelectorPath,indexInParent:indexInParent,existsInDOM:!1}})}}),VWO.DOMNodePool.uncacheAll(),VWO.DOMNodePool.cacheAll(),finalOperationsList},verifyComparison:function(){return console.log("comparison successful: "+this.nodeA.equals(this.nodeB)),this.nodeA.equals(this.nodeB)},compare:function(){function getActualIndex(parentSelectorPath,indexInParent){var parentNode=VWO.DOMNode.create({el:self.nodeB.el.parentNode.querySelector(parentSelectorPath)});if(0>indexInParent)return-1;var childNode=parentNode.children()[indexInParent];return Array.prototype.slice.apply(parentNode.el.childNodes).indexOf(childNode.el)}var self=this;this.analyzeMatches();var final_results=[],result1=[this.detectRemovesInB(),this.detectRearranges()];VWO.DOMNodePool.uncacheAll(),VWO.DOMNodePool.cacheAll(),result1=_(result1).flatten();for(var output=[],index,path,html,text,val,attr,css,index1,index2,path1,path2,l=result1.length,i=l-1;i>=0;i--){var op=result1[i];if("deleteNodeInB"==op.name){index=getActualIndex(op.content.parentSelectorPath,op.content.indexInParent-1),path=op.content.parentSelectorPath.split("DOMComparisonResult > ")[1],path||(path=op.content.parentSelectorPath),html=op.content.html,output[i]=-1==index?"$("+JSON.stringify(path)+").prepend("+JSON.stringify(html)+");":"$($("+JSON.stringify(path)+").get(0).childNodes["+index+"]).after("+JSON.stringify(html)+");";var ctx=self.nodeB.el,$=function(selector){return"HIM#DOMComparisonResult"==selector?jQuery(ctx):jQuery(selector,ctx)};eval(output[i])}else final_results.push(result1[i])}VWO.DOMNodePool.uncacheAll(),VWO.DOMNodePool.cacheAll();var result=[this.detectInserts(),this.detectRemoves(),this.detectTextNodeChanges(),this.detectAttributeChanges(),this.detectStyleChanges()];result=_(result).flatten();var le=result.length;for(i=0;le>i;i++)final_results.push(result[i]);return console.log(final_results),this.verifyComparison(),result.toJqueryCode=function toJqueryCode(){function getActualIndex(parentSelectorPath,indexInParent){var parentNode=VWO.DOMNode.create({el:self.elAClone.parentNode.querySelector(parentSelectorPath)});if(0>indexInParent)return-1;var childNode=parentNode.children()[indexInParent];return Array.prototype.slice.apply(parentNode.el.childNodes).indexOf(childNode.el)}for(var output=[],index,path,html,text,val,attr,css,index1,index2,path1,path2,i=0,l=this.length;l>i;i++){var op=this[i];switch(op.name){case"insertNode":index=getActualIndex(op.content.parentSelectorPath,op.content.indexInParent-1),path=op.content.parentSelectorPath.split("DOMComparisonResult > ")[1],html=op.content.html,output[i]=-1==index?"$("+JSON.stringify(path)+").append("+JSON.stringify(html)+");":"$($("+JSON.stringify(path)+").get(0).childNodes["+index+"]).after("+JSON.stringify(html)+");";break;case"rearrange":index1=getActualIndex(op.content.parentSelectorPath,op.content.indexInParent-1),index2=getActualIndex(op.content.oldParentSelectorPath,op.content.oldIndexInParent),path1=op.content.parentSelectorPath.split("DOMComparisonResult > ")[1],path2=op.content.oldParentSelectorPath.split("DOMComparisonResult > ")[1];var node="$("+path2+").get(0).childNodes["+index2+"]";output[i]=-1==index1?"$("+JSON.stringify(path1)+").append("+node+");":"$($("+JSON.stringify(path1)+").get(0).childNodes["+index1+"]).after("+node+");";case"deleteNode":index=getActualIndex(op.content.parentSelectorPath,op.content.indexInParent),path=op.content.parentSelectorPath.split("DOMComparisonResult > ")[1],html=op.content.html,output[i]="$($("+JSON.stringify(path)+").get(0).childNodes["+index+"]).remove();";break;case"changeText":case"changeComment":index=getActualIndex(op.content.parentSelectorPath,op.content.indexInParent),path=op.content.parentSelectorPath.split("DOMComparisonResult > ")[1],text=op.content.text,output[i]="$($("+JSON.stringify(path)+").get(0).childNodes["+index+"]).remove();";var ctx=self.elAClone,$=function(selector){return jQuery(selector,ctx)};eval(output[i]),output[i]="$("+JSON.stringify(path)+").append("+JSON.stringify(text)+");";break;case"attr":case"css":path=op.selectorPath.split("DOMComparisonResult > ")[1],val=op.content,output[i]="$("+JSON.stringify(path)+")."+op.name+"("+JSON.stringify(val)+");";break;case"removeAttr":path=op.selectorPath.split("DOMComparisonResult > ")[1],attr=Object.keys(op.content),output[i]="$("+JSON.stringify(path)+")"+attr.map(function(attr){return".removeAttr("+JSON.stringify(attr)+")"}).join("")+";";break;case"removeCss":path=op.selectorPath.split("DOMComparisonResult > ")[1],css=Object.keys(op.content),output[i]="$("+JSON.stringify(path)+")"+css.map(function(css){return".css("+JSON.stringify(css)+', "")'}).join("")+";"}var ctx=self.elAClone,$=function(selector){return jQuery(selector,ctx)};eval(output[i])}return self.elAClone},final_results}};var $=window.vwo_$||window.$,_=window.vwo__||window._,VWO=window.VWOInjected||window.VWO||{};VWO.DOMMatchFinder=function(params){$.extend(!0,this,params)},VWO.DOMMatchFinder.create=function(params){return new VWO.DOMMatchFinder(params)},VWO.DOMMatchFinder.prototype={nodeA:null,nodeB:null,matches:null,stringA:function(){return this.nodeA.outerHTML().replace(/\r\n|\r|\n/gi,"\n")},stringB:function(){return this.nodeB.outerHTML().replace(/\r\n|\r|\n/gi,"\n")},compare:function(){function allIndexOf(str,toSearch){for(var indices=[],pos=str.indexOf(toSearch);-1!==pos;pos=str.indexOf(toSearch,pos+1))indices.push(pos);return indices}var i,stringA1=this.stringA(),stringB1=this.stringB(),stringA_original=this.stringA(),stringB_original=this.stringB(),splitOn="\n",A_len=stringA1.length,B_len=stringB1.length,start_comment_flag=0,stringA="",stringB="";for(i=0;A_len>i;i++)A_len>i+3&&"<"==stringA1[i]&&"!"==stringA1[i+1]&&"-"==stringA1[i+2]&&"-"==stringA1[i+3]?(start_comment_flag=1,i+=3):A_len>i+2&&1==start_comment_flag&&"-"==stringA1[i]&&"-"==stringA1[i+1]&&">"==stringA1[i+2]?(start_comment_flag=0,i+=2):0==start_comment_flag&&(stringA+=stringA1[i]);for(start_comment_flag=0,i=0;B_len>i;i++)B_len>i+3&&"<"==stringB1[i]&&"!"==stringB1[i+1]&&"-"==stringB1[i+2]&&"-"==stringB1[i+3]?(start_comment_flag=1,i+=3):B_len>i+2&&1==start_comment_flag&&"-"==stringB1[i]&&"-"==stringB1[i+1]&&">"==stringB1[i+2]?(start_comment_flag=0,i+=2):0==start_comment_flag&&(stringB+=stringB1[i]);var f=function(i){return 10>i?" "+i:i.toString()},couA=0,couB=0,result=VWO.StringComparator.create({stringA:stringA,stringB:stringB,matchA:{},matchB:{},couA:0,couB:0,ignoreA:[],ignoreB:[],splitOn:splitOn}).compare(),diffUnion=result.diffUnion,nodeMatches={},stringsInA=result.stringsInA,stringsInB=result.stringsInB;for(i=0;i0?splitOn:"")+stringsInA[indexInA]+(indexInAj;j++){var pointerInA,pointerInB;num=jl>j+1&&pointers[j+1].index-pointers[j].index==1?0:splitOn.length,pointerInA=VWO.DOMNodeStringPointer.create({index:startIndexInA+pointers[j].index+num,haystack:stringA}),pointerInB=VWO.DOMNodeStringPointer.create({index:startIndexInB+pointers[j].index+num,haystack:stringB}),nodeMatches[pointerInA.masterIndex()]=pointerInB.masterIndex()}}var innerNodeMatches={},stringsLastAddedInB=[];for(i=0;i=0&&diff.indexInB>=0&&(stringsLastAddedInB=[]),diff.indexInA<0&&stringsLastAddedInB.push(diff),diff.indexInB<0){if(!stringsLastAddedInB.length)continue;var stringInStringInA=diff.string,stringInStringInB=stringsLastAddedInB[0].string;indexInA=diff.indexInA,indexInB=stringsLastAddedInB[0].indexInB;var c1,c2,valA=[],valB=[],len_A=stringA.length,len_B=stringB.length,co=1,f=0;for(valA[0]=0,i=0;len_A>i;i++)c1=stringA[i],c2=c1.charCodeAt(0),c2>47&&58>c2||c2>64&&91>c2||c2>96&&123>c2||32==c2||95==c2?f&&(valA[co]=i,co+=1,f=0):f=1;for(couA=co,f=0,co=1,valB[0]=0,i=0;len_B>i;i++)c1=stringB[i],c2=c1.charCodeAt(0),c2>47&&58>c2||c2>64&&91>c2||c2>96&&123>c2||32==c2||95==c2?f&&(valB[co]=i,co+=1,f=0):f=1;couB=co;var str,sA,indB,pB=this.nodeB,num_childsB=pB.children().length,ignoreB=[],coB=0,recForB=function(num_childsB,pB){if(0!=num_childsB){var y;for(y=0;num_childsB>y;y++)recForB(pB.children()[y].children().length,pB.children()[y])}else{if(sA=pB.el.outerHTML,sA?indB=stringA.indexOf(sA):(sA=pB.el.nodeValue,indB=stringA.indexOf(sA)),-1!=indB)return;if(-1!=sA.indexOf("class")||-1!=sA.indexOf("href")||-1!=sA.indexOf("style"))return;for(;pB.parent()&&(pB=pB.parent(),str=pB.el.outerHTML,-1==str.indexOf("class")&&-1==str.indexOf("href")&&-1==str.indexOf("style"))&&(sA=pB.el.outerHTML,indB=stringA.indexOf(sA),-1==indB&&pB.parent()&&1==pB.parent().children().length);)prev=sA;var matching=[];matching.push({InB:[stringB.indexOf(sA),stringB.indexOf(sA)+sA.length]});var st,en,j,indexB1,indexB2,matching_len=matching.length;for(i=0;matching_len>i;i++){for(st=matching[i].InB[0],j=0;jst){indexB1=j;break}for(en=matching[i].InB[1],j=0;jen){indexB2=j-1;break}var lo=indexB2-indexB1+1;for(j=0;lo>j;j++)ignoreB[coB]=indexB1,indexB1++,coB++}}};recForB(num_childsB,pB);var pA=this.nodeA,num_childsA=pA.children().length,ignoreA=[],coA=0,recForA=function(num_childsA,pA){if(0!=num_childsA){var y;for(y=0;num_childsA>y;y++)recForA(pA.children()[y].children().length,pA.children()[y])}else{if(sA=pA.el.outerHTML,sA?indB=stringB.indexOf(sA):(sA=pA.el.nodeValue,indB=stringB.indexOf(sA)),-1!=indB)return;if(-1!=sA.indexOf("class")||-1!=sA.indexOf("href")||-1!=sA.indexOf("style"))return;for(;pA.parent()&&(pA=pA.parent(),str=pA.el.outerHTML,-1==str.indexOf("class")&&-1==str.indexOf("href")&&-1==str.indexOf("style"))&&(sA=pA.el.outerHTML,indB=stringB.indexOf(sA),-1==indB&&pA.parent()&&1==pA.parent().children().length);)prev=sA;var matching=[];matching.push({InA:[stringA.indexOf(sA),stringA.indexOf(sA)+sA.length]});var st,en,j,indexA1,indexA2,matching_len=matching.length;for(i=0;matching_len>i;i++){for(st=matching[i].InA[0],j=0;jst){indexA1=j;break}for(en=matching[i].InA[1],j=0;jen){indexA2=j-1;break}var lo=indexA2-indexA1+1;for(j=0;lo>j;j++)ignoreA[coA]=indexA1,indexA1++,coA++}}};recForA(num_childsA,pA);var p=this.nodeA,num_childs=p.children().length,matchesInA={},matchesInB={},rec=function(num_childs,p){if(0!=num_childs){var x;for(x=0;num_childs>x;x++){var matching=[];sA=p.children()[x].el.outerHTML;var instances=allIndexOf(stringB,sA);if(1!=instances.length)rec(p.children()[x].children().length,p.children()[x]);else{matching.push({InA:[stringA.indexOf(sA),stringA.indexOf(sA)+sA.length],InB:[stringB.indexOf(sA),stringB.indexOf(sA)+sA.length]});var st,en,j,indexA1,indexA2,indexB1,indexB2,matching_len=matching.length;for(i=0;matching_len>i;i++){for(st=matching[i].InA[0],j=0;jst){indexA1=j;break}for(en=matching[i].InA[1],j=0;jen){indexA2=j-1;break}for(st=matching[i].InB[0],j=0;jst){indexB1=j;break}for(en=matching[i].InB[1],j=0;jen){indexB2=j-1;break}var lo=indexA2-indexA1+1;for(j=0;lo>j;j++)matchesInA[indexA1]=indexB1,matchesInB[indexB1]=indexA1,indexA1++,indexB1++}}}}};rec(num_childs,p);var splitOnRegExpA=/[^a-z0-9_ \r\n]+/gi,splitOnRegExpB=/[^a-z0-9_ \r\n]+/gi,innerResult=VWO.StringComparator.create({stringA:stringInStringInA,stringB:stringInStringInB,matchA:matchesInA,matchB:matchesInB,couA:couA,couB:couB,ignoreA:ignoreA,ignoreB:ignoreB,splitOn:/[^a-z0-9_ \r\n]+/gi}).compare();console.log(innerResult);var indexInStringInA,indexInStringInB,matchInA,matchInB,matchRatio=0,unchangedRanges=[];for(j=0;j=0&&(matchInA=splitOnRegExpA.exec(stringInStringInA),indexInStringInA=matchInA?valA[innerDiff.indexInA]:stringInStringInA.length-innerDiff.string.length),innerDiff.indexInB>=0&&(matchInB=splitOnRegExpB.exec(stringInStringInB),indexInStringInB=matchInB?valB[innerDiff.indexInB]:stringInStringInB.length-innerDiff.string.length),innerDiff.indexInA>=0&&innerDiff.indexInB>=0){if(!innerDiff.string.length)continue;unchangedRanges.push({rangeInA:[indexInStringInA,indexInStringInA+innerDiff.string.length],rangeInB:[indexInStringInB,indexInStringInB+innerDiff.string.length]}),innerDiff.string.length>1&&(matchRatio+=innerDiff.string.length/stringInStringInB.length)}}if(matchRatio>3/stringInStringInB.length)for(stringsLastAddedInB.shift(),j=0;jk;k++){var num=1;rangeInA[1]-rangeInA[0]==1&&(num=0);var pointerInStringInA=VWO.DOMNodeStringPointer.create({index:startIndexInA+rangeInA[0]+pointersInString[k].index+num,haystack:stringA}),pointerInStringInB=VWO.DOMNodeStringPointer.create({index:startIndexInB+rangeInB[0]+pointersInString[k].index+num,haystack:stringB}),mi1=pointerInStringInA.masterIndex(),mi2=pointerInStringInB.masterIndex();innerNodeMatches[mi1]=mi2;var a=pointersInString[k],b=pointerInStringInA,c=pointerInStringInB;console.log(unchangedRanges[j].string,"-->>",a.nodeName(),a.haystack.substr(a.index,5),"->",mi1,b.nodeName(),b.haystack.substr(b.index,5),"->",mi2,c.nodeName(),c.haystack.substr(c.index,5))}}}}return nodeMatches=_(nodeMatches).chain().invert().invert().value(),innerNodeMatches=_(innerNodeMatches).chain().invert().invert().value(),console.log("nodeMatchesCount:",_(nodeMatches).keys().length),console.log("nodeMatches:",nodeMatches),console.log("innerNodeMatchesCount:",_(innerNodeMatches).keys().length),console.log("innerNodeMatches:",innerNodeMatches),console.log("totalCount:",_($.extend(nodeMatches,innerNodeMatches)).keys().length),this.matches=nodeMatches,this.stringA=stringA_original,this.stringB=stringB_original,this}};var $=window.vwo_$||window.$,_=window.vwo__||window._,VWO=window.VWOInjected||window.VWO||{};VWO.DOMNodeComparator=function(params){$.extend(!0,this,params)},VWO.DOMNodeComparator.create=function(params){return new VWO.DOMNodeComparator(params)},VWO.DOMNodeComparator.prototype={nodeA:null,nodeB:null,indexScore:function(){return Number(this.nodeA.masterIndex()===this.nodeB.masterIndex())},nodeTypeScore:function(){return Number(this.nodeA.nodeType()===this.nodeB.nodeType())},innerTextScore:function(){return Number(this.nodeA.innerText()===this.nodeB.innerText())},innerHTMLScore:function(){return Number(this.nodeA.innerHTML()===this.nodeB.innerHTML())},nodeNameScore:function(){return this.nodeTypeScore()?this.nodeA.nodeType()!==Node.ELEMENT_NODE?0:Number(this.nodeA.nodeName()===this.nodeB.nodeName()):0},parentScore:function(){return this.nodeA.parent()&&this.nodeB.parent()?.5+VWO.DOMNodeComparator.create({nodeA:this.nodeA.parent(),nodeB:this.nodeB.parent()}).parentScore()/2:0},nextSiblingScore:function(){return this.nodeA.nextSibling()&&!this.nodeB.nextSibling()?0:!this.nodeA.nextSibling()&&this.nodeB.nextSibling()?0:this.nodeA.nextSibling()||this.nodeB.nextSibling()?.5+VWO.DOMNodeComparator.create({nodeA:this.nodeA.nextSibling(),nodeB:this.nodeB.nextSibling()}).nextSiblingScore()/2:1},previousSiblingScore:function(){return this.nodeA.previousSibling()&&!this.nodeB.previousSibling()?0:!this.nodeA.previousSibling()&&this.nodeB.previousSibling()?0:this.nodeA.previousSibling()||this.nodeB.previousSibling()?.5+VWO.DOMNodeComparator.create({nodeA:this.nodeA.previousSibling(),nodeB:this.nodeB.previousSibling()}).previousSiblingScore()/2:1},siblingsScore:function(){return(this.nextSiblingScore()+this.previousSiblingScore())/2},adjacentElementsScore:function(){return(this.previousSiblingScore()+this.nextSiblingScore()+this.parentScore())/3},childrenScore:function(){if(this.nodeA.children().length&&!this.nodeB.children().length)return 0;if(!this.nodeA.children().length&&this.nodeB.children().length)return 0;if(!this.nodeA.children().length&&!this.nodeB.children().length)return 1;var score=0;return this.nodeA.children().forEach(function(childInNodeA){score+=Number(Boolean(this.nodeB.children().filter(function(childInNodeB){return childInNodeB.nodeName()===childInNodeA.nodeName()})[0]))}.bind(this)),score/this.nodeA.children().length},attributeScore:function(){if(!this.nodeNameScore())return 0;var nodeA=this.nodeA,nodeB=this.nodeB,nodeAAttributeKeys=_.keys(nodeA.attributes()),nodeBAttributeKeys=_.keys(nodeB.attributes()),totalAttributes=_.union(nodeBAttributeKeys,nodeAAttributeKeys).length;if(!totalAttributes)return 1;var addedAttributes=_(this.addedAttributes()).keys().length,removedAttributes=_(this.removedAttributes()).keys().length,changedAttributes=_(this.changedAttributes()).keys().length;return(totalAttributes-addedAttributes-removedAttributes-changedAttributes)/totalAttributes||0},addedAttributes:function(){if(!this.nodeNameScore())return{};var nodeA=this.nodeA,nodeB=this.nodeB,nodeAAttributeKeys=_.keys(nodeA.attributes()),nodeBAttributeKeys=_.keys(nodeB.attributes()),addedAttributeKeys=_.difference(nodeBAttributeKeys,nodeAAttributeKeys);return _.pick.apply(_,[nodeB.attributes()].concat(addedAttributeKeys))},changedAttributes:function(){if(!this.nodeNameScore())return{};var nodeA=this.nodeA,nodeB=this.nodeB,nodeAAttributes=nodeA.attributes(),nodeBAttributes=nodeB.attributes(),nodeAAttributeKeys=_.keys(nodeAAttributes),nodeBAttributeKeys=_.keys(nodeBAttributes),commonAttributeKeys=_.intersection(nodeBAttributeKeys,nodeAAttributeKeys),changedAttributeKeys=commonAttributeKeys.filter(function(item){return nodeAAttributes[item]!==nodeBAttributes[item]});return _.pick.apply(_,[nodeBAttributes].concat(changedAttributeKeys))},removedAttributes:function(){if(!this.nodeNameScore())return{};var nodeA=this.nodeA,nodeB=this.nodeB,nodeAAttributeKeys=_.keys(nodeA.attributes()),nodeBAttributeKeys=_.keys(nodeB.attributes()),removedAttributeKeys=_.difference(nodeAAttributeKeys,nodeBAttributeKeys);return _.pick.apply(_,[nodeA.attributes()].concat(removedAttributeKeys))},styleScore:function(){if(!this.nodeNameScore())return 0;var nodeA=this.nodeA,nodeB=this.nodeB,nodeAStyleKeys=_.keys(nodeA.styles()),nodeBStyleKeys=_.keys(nodeB.styles()),totalStyles=_.union(nodeBStyleKeys,nodeAStyleKeys).length;if(!totalStyles)return 1;var addedStyles=_(this.addedStyles()).keys().length,removedStyles=_(this.removedStyles()).keys().length,changedStyles=_(this.changedStyles()).keys().length;return(totalStyles-addedStyles-removedStyles-changedStyles)/totalStyles||0},addedStyles:function(){if(!this.nodeNameScore())return{};var nodeA=this.nodeA,nodeB=this.nodeB,nodeAStyleKeys=_.keys(nodeA.styles()),nodeBStyleKeys=_.keys(nodeB.styles()),addedStyleKeys=_.difference(nodeBStyleKeys,nodeAStyleKeys);return _.pick.apply(_,[nodeB.styles()].concat(addedStyleKeys))},changedStyles:function(){if(!this.nodeNameScore())return{};var nodeA=this.nodeA,nodeB=this.nodeB,nodeAStyles=nodeA.styles(),nodeBStyles=nodeB.styles(),nodeAStyleKeys=_.keys(nodeAStyles),nodeBStyleKeys=_.keys(nodeBStyles),commonStyleKeys=_.intersection(nodeBStyleKeys,nodeAStyleKeys),changedStyleKeys=commonStyleKeys.filter(function(item){return nodeAStyles[item]!==nodeBStyles[item]});return _.pick.apply(_,[nodeBStyles].concat(changedStyleKeys))},removedStyles:function(){if(!this.nodeNameScore())return{};var nodeA=this.nodeA,nodeB=this.nodeB,nodeAStyleKeys=_.keys(nodeA.styles()),nodeBStyleKeys=_.keys(nodeB.styles()),removedStyleKeys=_.difference(nodeAStyleKeys,nodeBStyleKeys);return _.pick.apply(_,[nodeA.styles()].concat(removedStyleKeys))},newInnerText:function(){return!this.nodeTypeScore()||this.nodeA.nodeType()===Node.ELEMENT_NODE||this.innerTextScore()?null:this.nodeB.innerText()},finalScore:function(){var nodeA=this.nodeA,nodeB=this.nodeB;if(!nodeA||!nodeB)return 0;var indexScore=this.indexScore(),nodeTypeScore=this.nodeTypeScore(),nodeNameScore=this.nodeNameScore(),attributeScore=this.attributeScore(),styleScore=this.styleScore(),innerHTMLScore=this.innerHTMLScore(),innerTextScore=this.innerTextScore();return nodeTypeScore&&nodeA.nodeType()===Node.TEXT_NODE?innerTextScore?1:indexScore?.9:0:nodeNameScore?"INPUT"===nodeA.nodeName().toUpperCase()&&nodeA.attributes().type!==nodeB.attributes().type?0:innerHTMLScore?(8+indexScore+(2*attributeScore+styleScore)/3)/10:(indexScore+8*innerHTMLScore+(2*attributeScore+styleScore)/3)/10:0},difference:function(){return{addedAttributes:this.addedAttributes(),removedAttributes:this.removedAttributes(),changedAttributes:this.changedAttributes(),addedStyles:this.addedStyles(),removedStyles:this.removedStyles(),changedStyles:this.changedStyles(),newInnerText:this.newInnerText()}}};var $=window.vwo_$||window.$,_=window.vwo__||window._,VWO=window.VWOInjected||window.VWO||{};VWO.DOMNodeStringPointer=function(params){$.extend(!0,this,params)},VWO.DOMNodeStringPointer.create=function(params){return new VWO.DOMNodeStringPointer(params)},VWO.DOMNodeStringPointer.prototype={haystack:"",index:0,_pointerWithIndex:function(i){return VWO.DOMNodeStringPointer.create({haystack:this.haystack,index:i})},allNodePointers:function(){var pointers=[],pointer=this._pointerWithIndex(0);for(pointers.push(pointer);pointer=pointer.nextPointer();)pointers.push(pointer);return pointers},nodeType:function(){var i=this.index,haystack=this.haystack;return haystack.lastIndexOf("",i)||"-->"===haystack.substr(i,3)?Node.COMMENT_NODE:haystack.lastIndexOf("haystack.lastIndexOf("]]>",i)||"]]>"===haystack.substr(i,3)?Node.CDATA_SECTION_NODE:haystack.lastIndexOf("<",i)>haystack.lastIndexOf(">",i)||">"===haystack.charAt(i)?Node.ELEMENT_NODE:Node.TEXT_NODE},nodeName:function(){var i=this.index,haystack=this.haystack,nodeType=this.nodeType();if(nodeType===Node.ELEMENT_NODE){var j=haystack.lastIndexOf("<",i)+1,k=haystack.indexOf(" ",j),l=haystack.indexOf(">",j),nodeName=haystack.substring(j,Math.min(-1===k?l:k,-1===l?k:l));return"/"===nodeName.charAt(0)&&(nodeName=nodeName.substr(1)),"DIV"===$("").get(0).nodeName&&(nodeName=nodeName.toUpperCase()),nodeName}return nodeType},pointsToClosingTag:function(){if(this.nodeType()!==Node.ELEMENT_NODE)return!1;var j=this.haystack.lastIndexOf("<",this.index);return"/"===this.haystack.charAt(j+1)},pointsToEmptyTag:function(){var emptyTags=/area|base|br|col|hr|img|input|link|meta|param/i;return emptyTags.test(this.nodeName())},previousPointer:function(){var j,pointer,i=this.index,haystack=this.haystack,nodeType=this.nodeType();return nodeType===Node.TEXT_NODE?(j=haystack.lastIndexOf(">",i),pointer=this._pointerWithIndex(j)):nodeType===Node.COMMENT_NODE?(j=haystack.lastIndexOf("",i),-1===j)return null;pointer=this._pointerWithIndex(j+3)}else if(nodeType===Node.CDATA_SECTION_NODE){if(j=haystack.indexOf("]]>",i),-1===j)return null;pointer=this._pointerWithIndex(j+3)}else{if(j=haystack.indexOf(">",i),-1===j)return null;pointer=this._pointerWithIndex(j+1)}return pointer.index<0||pointer.index>=haystack.length?null:pointer},nextSiblingPointer:function(){var j,pointer,i=this.index,haystack=this.haystack,nodeType=this.nodeType(),nodeName=this.nodeName();if(nodeType===Node.TEXT_NODE)j=haystack.indexOf("<",i),pointer=this._pointerWithIndex(j);else if(nodeType===Node.COMMENT_NODE){if(j=haystack.indexOf("-->",i),-1===j)return null;pointer=this._pointerWithIndex(j+3)}else if(nodeType===Node.CDATA_SECTION_NODE){if(j=haystack.indexOf("]]>",i),-1===j)return null;pointer=this._pointerWithIndex(j+3)}else{if(j=haystack.indexOf(">",i),-1===j)return null;if(this.pointsToClosingTag())pointer=this._pointerWithIndex(j+1);else{for(var k=j-1,openingTags=[],l=0;openingTags&&(k=haystack.toLowerCase().indexOf(""+nodeName.toLowerCase(),k+1),k=haystack.indexOf(">",k),openingTags=haystack.substring(j,k).match(new RegExp("<"+nodeName,"gi")),!openingTags||l++!=openingTags.length););pointer=this._pointerWithIndex(k+1)}}return pointer.index<0||pointer.index>=haystack.length?null:pointer.nodeType()!==Node.ELEMENT_NODE||!pointer.pointsToEmptyTag()&&"/"!==haystack.charAt(haystack.indexOf("<",pointer.index)+1)?pointer:null},parentPointer:function(){var j,i=this.index,haystack=this.haystack,nodeType=this.nodeType(),nodeName=this.nodeName(),prev=this.previousSiblingPointer();if(prev)return prev.parentPointer();var pointer;if(nodeType===Node.TEXT_NODE)j=haystack.lastIndexOf(">",i),pointer=this._pointerWithIndex(j);else if(nodeType===Node.COMMENT_NODE)j=haystack.lastIndexOf("0&&-1!==pos;)pos=this.indexOf(needle,pos+1);return pos};var STR_PAD_LEFT=1,STR_PAD_RIGHT=2,STR_PAD_BOTH=3;String.prototype.lpad=function(len,pad){return this.pad(len,pad,STR_PAD_LEFT)},String.prototype.rpad=function(len,pad){return this.pad(len,pad,STR_PAD_RIGHT)},String.prototype.pad=function pad(len,pad,dir){var str=this;if("undefined"==typeof len&&(len=0),"undefined"==typeof pad&&(pad=" "),"undefined"==typeof dir&&(dir=STR_PAD_RIGHT),len+1>=str.length)switch(dir){case STR_PAD_LEFT:str=new Array(len+1-str.length).join(pad)+str;break;case STR_PAD_BOTH:var padlen=len-str.length,right=Math.ceil(padlen/2),left=padlen-right;str=new Array(left+1).join(pad)+str+new Array(right+1).join(pad);break;default:str+=new Array(len+1-str.length).join(pad)}return str},Number.prototype.clamp=function(min,max){return"number"!=typeof min&&(min=-1/0),"number"!=typeof max&&(max=1/0),Math.max(min,Math.min(max,this))},Number.prototype.isWithinRange=function(min,max){return this>min&&max>this},jQuery_fn.nodeName=function(){var USE_LOWERCASE=!1,el=this.get(0);return el?USE_LOWERCASE?el.nodeName&&el.nodeName.toLowerCase():el.nodeName&&el.nodeName.toUpperCase():null},jQuery_fn.selectorPath=function(){if(!this.length)return"";if(this.nodeName().match(/^(body|head)$/i))return this.nodeName();if(this.get(0).nodeType!==Node.ELEMENT_NODE)return"";var currentPath=this.nodeName();if(currentPath.match(/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/)[0]!==currentPath&&(currentPath="*"),this.attr("id")){var id=this.attr("id");"string"!=typeof id&&(id=this.get(0).getAttribute(id)),"string"==typeof id&&$(currentPath+"#"+id).length<=1&&(id=id.replace(/(:|\.)/g,"\\$1"),currentPath+="#"+id)}return this.prev().length?this.prev().selectorPath()+" + "+currentPath:this.parent().length?this.parent().selectorPath()+" > "+currentPath+":first-child":currentPath},jQuery_fn.existsInDOM=function(){return this.parents("html").length>0},jQuery_fn.isAncestorOf=function(el){return $(el).parents().is(this)},jQuery_fn.isDescendantOf=function(el){return $(el).isAncestorOf(this)},jQuery_fn.outerHTML=function(){var content,$t=$(this);return $t[0]&&"outerHTML"in $t[0]?(content="",$t.each(function(){content+=this.outerHTML}),content):(content=$t.wrap("").parent().html(),$t.unwrap(),content)}}({},function(){return this}());
13 | //# sourceMappingURL=dom-comparator.min.js.map
--------------------------------------------------------------------------------