Created
January 25, 2019 12:23
-
-
Save victor141516/8cdb7ad695ac87b96d8f668bc039fe5c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2018 The Chromium Authors. All rights reserved. | |
// Use of this source code is governed by a BSD-style license that can be | |
// found in the LICENSE file. | |
/** | |
* @param {!SDK.DOMNode} node | |
* @param {boolean=} justSelector | |
* @return {string} | |
*/ | |
fullQualifiedSelector = function (node, justSelector) { | |
if (node.nodeType !== Node.ELEMENT_NODE) | |
return node.localName || node.nodeName.toLowerCase(); | |
return cssPath(node, justSelector); | |
}; | |
/** | |
* @param {!SDK.DOMNode} node | |
* @param {boolean=} optimized | |
* @return {string} | |
*/ | |
cssPath = function (node, optimized) { | |
if (node.nodeType !== Node.ELEMENT_NODE) | |
return ''; | |
const steps = []; | |
let contextNode = node; | |
while (contextNode) { | |
const step = _cssPathStep(contextNode, !!optimized, contextNode === node); | |
if (!step) | |
break; // Error - bail out early. | |
steps.push(step); | |
if (step.optimized) | |
break; | |
contextNode = contextNode.parentNode; | |
} | |
steps.reverse(); | |
return steps.join(' > '); | |
}; | |
/** | |
* @param {!SDK.DOMNode} node | |
* @return {boolean} | |
*/ | |
canGetJSPath = function (node) { | |
let wp = node; | |
while (wp) { | |
if (wp.ancestorShadowRoot() && wp.ancestorShadowRoot().shadowRootType() !== SDK.DOMNode.ShadowRootTypes.Open) | |
return false; | |
wp = wp.ancestorShadowHost(); | |
} | |
return true; | |
}; | |
/** | |
* @param {!SDK.DOMNode} node | |
* @param {boolean=} optimized | |
* @return {string} | |
*/ | |
jsPath = function (node, optimized) { | |
if (node.nodeType !== Node.ELEMENT_NODE) | |
return ''; | |
const path = []; | |
let wp = node; | |
while (wp) { | |
path.push(cssPath(wp, optimized)); | |
wp = wp.ancestorShadowHost(); | |
} | |
path.reverse(); | |
let result = ''; | |
for (let i = 0; i < path.length; ++i) { | |
if (i) | |
result += `.shadowRoot.querySelector('${path[i]}')`; | |
else | |
result += `document.querySelector('${path[i]}')`; | |
} | |
return result; | |
}; | |
/** | |
* @param {!SDK.DOMNode} node | |
* @param {boolean} optimized | |
* @param {boolean} isTargetNode | |
* @return {?Step} | |
*/ | |
_cssPathStep = function (node, optimized, isTargetNode) { | |
if (node.nodeType !== Node.ELEMENT_NODE) | |
return null; | |
const id = node.getAttribute('id'); | |
if (optimized) { | |
if (id) | |
return new Step(idSelector(id), true); | |
const nodeNameLower = node.nodeName.toLowerCase(); | |
if (nodeNameLower === 'body' || nodeNameLower === 'head' || nodeNameLower === 'html') | |
return new Step(node.localName, true); | |
} | |
const nodeName = node.localName; | |
if (id) | |
return new Step(nodeName + idSelector(id), true); | |
const parent = node.parentNode; | |
if (!parent || parent.nodeType === Node.DOCUMENT_NODE) | |
return new Step(nodeName, true); | |
/** | |
* @param {!SDK.DOMNode} node | |
* @return {!Array.<string>} | |
*/ | |
function prefixedElementClassNames(node) { | |
const classAttribute = node.getAttribute('class'); | |
if (!classAttribute) | |
return []; | |
return classAttribute.split(/\s+/g).filter(Boolean).map(function (name) { | |
// The prefix is required to store "__proto__" in a object-based map. | |
return '$' + name; | |
}); | |
} | |
/** | |
* @param {string} id | |
* @return {string} | |
*/ | |
function idSelector(id) { | |
return '#' + escapeIdentifierIfNeeded(id); | |
} | |
/** | |
* @param {string} ident | |
* @return {string} | |
*/ | |
function escapeIdentifierIfNeeded(ident) { | |
if (isCSSIdentifier(ident)) | |
return ident; | |
const shouldEscapeFirst = /^(?:[0-9]|-[0-9-]?)/.test(ident); | |
const lastIndex = ident.length - 1; | |
return ident.replace(/./g, function (c, i) { | |
return ((shouldEscapeFirst && i === 0) || !isCSSIdentChar(c)) ? escapeAsciiChar(c, i === lastIndex) : c; | |
}); | |
} | |
/** | |
* @param {string} c | |
* @param {boolean} isLast | |
* @return {string} | |
*/ | |
function escapeAsciiChar(c, isLast) { | |
return '\\' + toHexByte(c) + (isLast ? '' : ' '); | |
} | |
/** | |
* @param {string} c | |
*/ | |
function toHexByte(c) { | |
let hexByte = c.charCodeAt(0).toString(16); | |
if (hexByte.length === 1) | |
hexByte = '0' + hexByte; | |
return hexByte; | |
} | |
/** | |
* @param {string} c | |
* @return {boolean} | |
*/ | |
function isCSSIdentChar(c) { | |
if (/[a-zA-Z0-9_-]/.test(c)) | |
return true; | |
return c.charCodeAt(0) >= 0xA0; | |
} | |
/** | |
* @param {string} value | |
* @return {boolean} | |
*/ | |
function isCSSIdentifier(value) { | |
// Double hyphen prefixes are not allowed by specification, but many sites use it. | |
return /^-{0,2}[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value); | |
} | |
const prefixedOwnClassNamesArray = prefixedElementClassNames(node); | |
let needsClassNames = false; | |
let needsNthChild = false; | |
let ownIndex = -1; | |
let elementIndex = -1; | |
const siblings = parent.children; | |
for (let i = 0; | |
(ownIndex === -1 || !needsNthChild) && i < siblings.length; ++i) { | |
const sibling = siblings[i]; | |
if (sibling.nodeType !== Node.ELEMENT_NODE) | |
continue; | |
elementIndex += 1; | |
if (sibling === node) { | |
ownIndex = elementIndex; | |
continue; | |
} | |
if (needsNthChild) | |
continue; | |
if (sibling.localName !== nodeName) | |
continue; | |
needsClassNames = true; | |
const ownClassNames = new Set(prefixedOwnClassNamesArray); | |
if (!ownClassNames.size) { | |
needsNthChild = true; | |
continue; | |
} | |
const siblingClassNamesArray = prefixedElementClassNames(sibling); | |
for (let j = 0; j < siblingClassNamesArray.length; ++j) { | |
const siblingClass = siblingClassNamesArray[j]; | |
if (!ownClassNames.has(siblingClass)) | |
continue; | |
ownClassNames.delete(siblingClass); | |
if (!ownClassNames.size) { | |
needsNthChild = true; | |
break; | |
} | |
} | |
} | |
let result = nodeName; | |
if (isTargetNode && nodeName.toLowerCase() === 'input' && node.getAttribute('type') && !node.getAttribute('id') && | |
!node.getAttribute('class')) | |
result += '[type="' + node.getAttribute('type') + '"]'; | |
if (needsNthChild) { | |
result += ':nth-child(' + (ownIndex + 1) + ')'; | |
} else if (needsClassNames) { | |
for (const prefixedName of prefixedOwnClassNamesArray) | |
result += '.' + escapeIdentifierIfNeeded(prefixedName.substr(1)); | |
} | |
return new Step(result, false); | |
}; | |
/** | |
* @param {!SDK.DOMNode} node | |
* @param {boolean=} optimized | |
* @return {string} | |
*/ | |
xPath = function (node, optimized) { | |
if (node.nodeType === Node.DOCUMENT_NODE) | |
return '/'; | |
const steps = []; | |
let contextNode = node; | |
while (contextNode) { | |
const step = _xPathValue(contextNode, optimized); | |
if (!step) | |
break; // Error - bail out early. | |
steps.push(step); | |
if (step.optimized) | |
break; | |
contextNode = contextNode.parentNode; | |
} | |
steps.reverse(); | |
return (steps.length && steps[0].optimized ? '' : '/') + steps.join('/'); | |
}; | |
/** | |
* @param {!SDK.DOMNode} node | |
* @param {boolean=} optimized | |
* @return {?Step} | |
*/ | |
_xPathValue = function (node, optimized) { | |
let ownValue; | |
const ownIndex = _xPathIndex(node); | |
if (ownIndex === -1) | |
return null; // Error. | |
switch (node.nodeType) { | |
case Node.ELEMENT_NODE: | |
if (optimized && node.getAttribute('id')) | |
return new Step('//*[@id="' + node.getAttribute('id') + '"]', true); | |
ownValue = node.localName; | |
break; | |
case Node.ATTRIBUTE_NODE: | |
ownValue = '@' + node.nodeName; | |
break; | |
case Node.TEXT_NODE: | |
case Node.CDATA_SECTION_NODE: | |
ownValue = 'text()'; | |
break; | |
case Node.PROCESSING_INSTRUCTION_NODE: | |
ownValue = 'processing-instruction()'; | |
break; | |
case Node.COMMENT_NODE: | |
ownValue = 'comment()'; | |
break; | |
case Node.DOCUMENT_NODE: | |
ownValue = ''; | |
break; | |
default: | |
ownValue = ''; | |
break; | |
} | |
if (ownIndex > 0) | |
ownValue += '[' + ownIndex + ']'; | |
return new Step(ownValue, node.nodeType === Node.DOCUMENT_NODE); | |
}; | |
/** | |
* @param {!SDK.DOMNode} node | |
* @return {number} | |
*/ | |
_xPathIndex = function (node) { | |
// Returns -1 in case of error, 0 if no siblings matching the same expression, <XPath index among the same expression-matching sibling nodes> otherwise. | |
function areNodesSimilar(left, right) { | |
if (left === right) | |
return true; | |
if (left.nodeType === Node.ELEMENT_NODE && right.nodeType === Node.ELEMENT_NODE) | |
return left.localName === right.localName; | |
if (left.nodeType === right.nodeType) | |
return true; | |
// XPath treats CDATA as text nodes. | |
const leftType = left.nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : left.nodeType; | |
const rightType = right.nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : right.nodeType; | |
return leftType === rightType; | |
} | |
const siblings = node.parentNode ? node.parentNode.children : null; | |
if (!siblings) | |
return 0; // Root node - no siblings. | |
let hasSameNamedElements; | |
for (let i = 0; i < siblings.length; ++i) { | |
if (areNodesSimilar(node, siblings[i]) && siblings[i] !== node) { | |
hasSameNamedElements = true; | |
break; | |
} | |
} | |
if (!hasSameNamedElements) | |
return 0; | |
let ownIndex = 1; // XPath indices start with 1. | |
for (let i = 0; i < siblings.length; ++i) { | |
if (areNodesSimilar(node, siblings[i])) { | |
if (siblings[i] === node) | |
return ownIndex; | |
++ownIndex; | |
} | |
} | |
return -1; // An error occurred: |node| not found in parent's children. | |
}; | |
/** | |
* @unrestricted | |
*/ | |
Step = class { | |
/** | |
* @param {string} value | |
* @param {boolean} optimized | |
*/ | |
constructor(value, optimized) { | |
this.value = value; | |
this.optimized = optimized || false; | |
} | |
/** | |
* @override | |
* @return {string} | |
*/ | |
toString() { | |
return this.value; | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment