blob: 87d5dd115accdc2cafb2e228e448cb9f0a8f08c8 [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:371// Copyright 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Tim van der Lippe97611c92020-02-12 16:56:585import * as SDK from '../sdk/sdk.js';
6
Blink Reformat4c46d092018-04-07 15:32:377/**
Tim van der Lippe97611c92020-02-12 16:56:588 * @param {!SDK.DOMModel.DOMNode} node
Blink Reformat4c46d092018-04-07 15:32:379 * @param {boolean=} justSelector
10 * @return {string}
11 */
Tim van der Lippe13f71fb2019-11-29 11:17:3912export const fullQualifiedSelector = function(node, justSelector) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3413 if (node.nodeType() !== Node.ELEMENT_NODE) {
Blink Reformat4c46d092018-04-07 15:32:3714 return node.localName() || node.nodeName().toLowerCase();
Tim van der Lippe1d6e57a2019-09-30 11:55:3415 }
Tim van der Lippe13f71fb2019-11-29 11:17:3916 return cssPath(node, justSelector);
Blink Reformat4c46d092018-04-07 15:32:3717};
18
19/**
Tim van der Lippe97611c92020-02-12 16:56:5820 * @param {!SDK.DOMModel.DOMNode} node
Blink Reformat4c46d092018-04-07 15:32:3721 * @param {boolean=} optimized
22 * @return {string}
23 */
Tim van der Lippe13f71fb2019-11-29 11:17:3924export const cssPath = function(node, optimized) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3425 if (node.nodeType() !== Node.ELEMENT_NODE) {
Blink Reformat4c46d092018-04-07 15:32:3726 return '';
Tim van der Lippe1d6e57a2019-09-30 11:55:3427 }
Blink Reformat4c46d092018-04-07 15:32:3728
29 const steps = [];
Jan Scheffler4f5c3742020-10-29 09:26:4630 let contextNode = /** @type {?SDK.DOMModel.DOMNode} */ (node);
Blink Reformat4c46d092018-04-07 15:32:3731 while (contextNode) {
Tim van der Lippe13f71fb2019-11-29 11:17:3932 const step = _cssPathStep(contextNode, !!optimized, contextNode === node);
Tim van der Lippe1d6e57a2019-09-30 11:55:3433 if (!step) {
Blink Reformat4c46d092018-04-07 15:32:3734 break;
Tim van der Lippe1d6e57a2019-09-30 11:55:3435 } // Error - bail out early.
36 steps.push(step);
37 if (step.optimized) {
38 break;
39 }
Blink Reformat4c46d092018-04-07 15:32:3740 contextNode = contextNode.parentNode;
41 }
42
43 steps.reverse();
44 return steps.join(' > ');
45};
46
47/**
Tim van der Lippe97611c92020-02-12 16:56:5848 * @param {!SDK.DOMModel.DOMNode} node
Andrey Lushnikovc197ddf2018-11-09 03:25:5949 * @return {boolean}
50 */
Tim van der Lippe13f71fb2019-11-29 11:17:3951export const canGetJSPath = function(node) {
Tim van der Lipped3f78ce2020-09-30 16:01:2352 /** @type {?SDK.DOMModel.DOMNode} */
Andrey Lushnikovc197ddf2018-11-09 03:25:5953 let wp = node;
54 while (wp) {
Tim van der Lipped3f78ce2020-09-30 16:01:2355 const shadowRoot = wp.ancestorShadowRoot();
56 if (shadowRoot && shadowRoot.shadowRootType() !== SDK.DOMModel.DOMNode.ShadowRootTypes.Open) {
Andrey Lushnikovc197ddf2018-11-09 03:25:5957 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:3458 }
Andrey Lushnikovc197ddf2018-11-09 03:25:5959 wp = wp.ancestorShadowHost();
60 }
61 return true;
62};
63
64/**
Tim van der Lippe97611c92020-02-12 16:56:5865 * @param {!SDK.DOMModel.DOMNode} node
Andrey Lushnikovc197ddf2018-11-09 03:25:5966 * @param {boolean=} optimized
67 * @return {string}
68 */
Tim van der Lippe13f71fb2019-11-29 11:17:3969export const jsPath = function(node, optimized) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3470 if (node.nodeType() !== Node.ELEMENT_NODE) {
Andrey Lushnikovc197ddf2018-11-09 03:25:5971 return '';
Tim van der Lippe1d6e57a2019-09-30 11:55:3472 }
Andrey Lushnikovc197ddf2018-11-09 03:25:5973
74 const path = [];
Tim van der Lipped3f78ce2020-09-30 16:01:2375 /** @type {?SDK.DOMModel.DOMNode} */
Andrey Lushnikovc197ddf2018-11-09 03:25:5976 let wp = node;
77 while (wp) {
Tim van der Lippe13f71fb2019-11-29 11:17:3978 path.push(cssPath(wp, optimized));
Andrey Lushnikovc197ddf2018-11-09 03:25:5979 wp = wp.ancestorShadowHost();
80 }
81 path.reverse();
82 let result = '';
83 for (let i = 0; i < path.length; ++i) {
Mathias Bynens6b5621b2019-02-12 19:36:5484 const string = JSON.stringify(path[i]);
Tim van der Lippe1d6e57a2019-09-30 11:55:3485 if (i) {
Mathias Bynens6b5621b2019-02-12 19:36:5486 result += `.shadowRoot.querySelector(${string})`;
Tim van der Lippe1d6e57a2019-09-30 11:55:3487 } else {
Mathias Bynens6b5621b2019-02-12 19:36:5488 result += `document.querySelector(${string})`;
Tim van der Lippe1d6e57a2019-09-30 11:55:3489 }
Andrey Lushnikovc197ddf2018-11-09 03:25:5990 }
91 return result;
92};
93
94/**
Tim van der Lippe97611c92020-02-12 16:56:5895 * @param {!SDK.DOMModel.DOMNode} node
Blink Reformat4c46d092018-04-07 15:32:3796 * @param {boolean} optimized
97 * @param {boolean} isTargetNode
Tim van der Lippe13f71fb2019-11-29 11:17:3998 * @return {?Step}
Blink Reformat4c46d092018-04-07 15:32:3799 */
Tim van der Lippe13f71fb2019-11-29 11:17:39100export const _cssPathStep = function(node, optimized, isTargetNode) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34101 if (node.nodeType() !== Node.ELEMENT_NODE) {
Blink Reformat4c46d092018-04-07 15:32:37102 return null;
Tim van der Lippe1d6e57a2019-09-30 11:55:34103 }
Blink Reformat4c46d092018-04-07 15:32:37104
105 const id = node.getAttribute('id');
106 if (optimized) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34107 if (id) {
Tim van der Lippe13f71fb2019-11-29 11:17:39108 return new Step(idSelector(id), true);
Tim van der Lippe1d6e57a2019-09-30 11:55:34109 }
Blink Reformat4c46d092018-04-07 15:32:37110 const nodeNameLower = node.nodeName().toLowerCase();
Tim van der Lippe1d6e57a2019-09-30 11:55:34111 if (nodeNameLower === 'body' || nodeNameLower === 'head' || nodeNameLower === 'html') {
Tim van der Lippe13f71fb2019-11-29 11:17:39112 return new Step(node.nodeNameInCorrectCase(), true);
Tim van der Lippe1d6e57a2019-09-30 11:55:34113 }
Blink Reformat4c46d092018-04-07 15:32:37114 }
115 const nodeName = node.nodeNameInCorrectCase();
116
Tim van der Lippe1d6e57a2019-09-30 11:55:34117 if (id) {
Tim van der Lippe13f71fb2019-11-29 11:17:39118 return new Step(nodeName + idSelector(id), true);
Tim van der Lippe1d6e57a2019-09-30 11:55:34119 }
Blink Reformat4c46d092018-04-07 15:32:37120 const parent = node.parentNode;
Tim van der Lippe1d6e57a2019-09-30 11:55:34121 if (!parent || parent.nodeType() === Node.DOCUMENT_NODE) {
Tim van der Lippe13f71fb2019-11-29 11:17:39122 return new Step(nodeName, true);
Tim van der Lippe1d6e57a2019-09-30 11:55:34123 }
Blink Reformat4c46d092018-04-07 15:32:37124
125 /**
Tim van der Lippe97611c92020-02-12 16:56:58126 * @param {!SDK.DOMModel.DOMNode} node
Blink Reformat4c46d092018-04-07 15:32:37127 * @return {!Array.<string>}
128 */
129 function prefixedElementClassNames(node) {
130 const classAttribute = node.getAttribute('class');
Tim van der Lippe1d6e57a2019-09-30 11:55:34131 if (!classAttribute) {
Blink Reformat4c46d092018-04-07 15:32:37132 return [];
Tim van der Lippe1d6e57a2019-09-30 11:55:34133 }
Blink Reformat4c46d092018-04-07 15:32:37134
135 return classAttribute.split(/\s+/g).filter(Boolean).map(function(name) {
136 // The prefix is required to store "__proto__" in a object-based map.
137 return '$' + name;
138 });
139 }
140
141 /**
142 * @param {string} id
143 * @return {string}
144 */
145 function idSelector(id) {
Mathias Bynens6b5621b2019-02-12 19:36:54146 return '#' + CSS.escape(id);
Blink Reformat4c46d092018-04-07 15:32:37147 }
148
149 const prefixedOwnClassNamesArray = prefixedElementClassNames(node);
150 let needsClassNames = false;
151 let needsNthChild = false;
152 let ownIndex = -1;
153 let elementIndex = -1;
154 const siblings = parent.children();
Jan Scheffler4f5c3742020-10-29 09:26:46155 for (let i = 0; siblings && (ownIndex === -1 || !needsNthChild) && i < siblings.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37156 const sibling = siblings[i];
Tim van der Lippe1d6e57a2019-09-30 11:55:34157 if (sibling.nodeType() !== Node.ELEMENT_NODE) {
Blink Reformat4c46d092018-04-07 15:32:37158 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34159 }
Blink Reformat4c46d092018-04-07 15:32:37160 elementIndex += 1;
161 if (sibling === node) {
162 ownIndex = elementIndex;
163 continue;
164 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34165 if (needsNthChild) {
Blink Reformat4c46d092018-04-07 15:32:37166 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34167 }
168 if (sibling.nodeNameInCorrectCase() !== nodeName) {
Blink Reformat4c46d092018-04-07 15:32:37169 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34170 }
Blink Reformat4c46d092018-04-07 15:32:37171
172 needsClassNames = true;
173 const ownClassNames = new Set(prefixedOwnClassNamesArray);
174 if (!ownClassNames.size) {
175 needsNthChild = true;
176 continue;
177 }
178 const siblingClassNamesArray = prefixedElementClassNames(sibling);
179 for (let j = 0; j < siblingClassNamesArray.length; ++j) {
180 const siblingClass = siblingClassNamesArray[j];
Tim van der Lippe1d6e57a2019-09-30 11:55:34181 if (!ownClassNames.has(siblingClass)) {
Blink Reformat4c46d092018-04-07 15:32:37182 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34183 }
Blink Reformat4c46d092018-04-07 15:32:37184 ownClassNames.delete(siblingClass);
185 if (!ownClassNames.size) {
186 needsNthChild = true;
187 break;
188 }
189 }
190 }
191
192 let result = nodeName;
193 if (isTargetNode && nodeName.toLowerCase() === 'input' && node.getAttribute('type') && !node.getAttribute('id') &&
Tim van der Lippe1d6e57a2019-09-30 11:55:34194 !node.getAttribute('class')) {
Paul Lewis90faf092020-09-02 07:50:34195 result += '[type=' + CSS.escape((node.getAttribute('type')) || '') + ']';
Tim van der Lippe1d6e57a2019-09-30 11:55:34196 }
Blink Reformat4c46d092018-04-07 15:32:37197 if (needsNthChild) {
198 result += ':nth-child(' + (ownIndex + 1) + ')';
199 } else if (needsClassNames) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34200 for (const prefixedName of prefixedOwnClassNamesArray) {
Mathias Bynens6b5621b2019-02-12 19:36:54201 result += '.' + CSS.escape(prefixedName.slice(1));
Tim van der Lippe1d6e57a2019-09-30 11:55:34202 }
Blink Reformat4c46d092018-04-07 15:32:37203 }
204
Tim van der Lippe13f71fb2019-11-29 11:17:39205 return new Step(result, false);
Blink Reformat4c46d092018-04-07 15:32:37206};
207
208/**
Tim van der Lippe97611c92020-02-12 16:56:58209 * @param {!SDK.DOMModel.DOMNode} node
Blink Reformat4c46d092018-04-07 15:32:37210 * @param {boolean=} optimized
211 * @return {string}
212 */
Tim van der Lippe13f71fb2019-11-29 11:17:39213export const xPath = function(node, optimized) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34214 if (node.nodeType() === Node.DOCUMENT_NODE) {
Blink Reformat4c46d092018-04-07 15:32:37215 return '/';
Tim van der Lippe1d6e57a2019-09-30 11:55:34216 }
Blink Reformat4c46d092018-04-07 15:32:37217
218 const steps = [];
Jan Scheffler4f5c3742020-10-29 09:26:46219 let contextNode = /** @type {?SDK.DOMModel.DOMNode} */ (node);
Blink Reformat4c46d092018-04-07 15:32:37220 while (contextNode) {
Tim van der Lippe13f71fb2019-11-29 11:17:39221 const step = _xPathValue(contextNode, optimized);
Tim van der Lippe1d6e57a2019-09-30 11:55:34222 if (!step) {
Blink Reformat4c46d092018-04-07 15:32:37223 break;
Tim van der Lippe1d6e57a2019-09-30 11:55:34224 } // Error - bail out early.
225 steps.push(step);
226 if (step.optimized) {
227 break;
228 }
Blink Reformat4c46d092018-04-07 15:32:37229 contextNode = contextNode.parentNode;
230 }
231
232 steps.reverse();
233 return (steps.length && steps[0].optimized ? '' : '/') + steps.join('/');
234};
235
236/**
Tim van der Lippe97611c92020-02-12 16:56:58237 * @param {!SDK.DOMModel.DOMNode} node
Blink Reformat4c46d092018-04-07 15:32:37238 * @param {boolean=} optimized
Tim van der Lippe13f71fb2019-11-29 11:17:39239 * @return {?Step}
Blink Reformat4c46d092018-04-07 15:32:37240 */
Tim van der Lippe13f71fb2019-11-29 11:17:39241export const _xPathValue = function(node, optimized) {
Blink Reformat4c46d092018-04-07 15:32:37242 let ownValue;
Tim van der Lippe13f71fb2019-11-29 11:17:39243 const ownIndex = _xPathIndex(node);
Tim van der Lippe1d6e57a2019-09-30 11:55:34244 if (ownIndex === -1) {
245 return null;
246 } // Error.
Blink Reformat4c46d092018-04-07 15:32:37247
248 switch (node.nodeType()) {
249 case Node.ELEMENT_NODE:
Tim van der Lippe1d6e57a2019-09-30 11:55:34250 if (optimized && node.getAttribute('id')) {
Tim van der Lippe13f71fb2019-11-29 11:17:39251 return new Step('//*[@id="' + node.getAttribute('id') + '"]', true);
Tim van der Lippe1d6e57a2019-09-30 11:55:34252 }
Blink Reformat4c46d092018-04-07 15:32:37253 ownValue = node.localName();
254 break;
255 case Node.ATTRIBUTE_NODE:
256 ownValue = '@' + node.nodeName();
257 break;
258 case Node.TEXT_NODE:
259 case Node.CDATA_SECTION_NODE:
260 ownValue = 'text()';
261 break;
262 case Node.PROCESSING_INSTRUCTION_NODE:
263 ownValue = 'processing-instruction()';
264 break;
265 case Node.COMMENT_NODE:
266 ownValue = 'comment()';
267 break;
268 case Node.DOCUMENT_NODE:
269 ownValue = '';
270 break;
271 default:
272 ownValue = '';
273 break;
274 }
275
Tim van der Lippe1d6e57a2019-09-30 11:55:34276 if (ownIndex > 0) {
Blink Reformat4c46d092018-04-07 15:32:37277 ownValue += '[' + ownIndex + ']';
Tim van der Lippe1d6e57a2019-09-30 11:55:34278 }
Blink Reformat4c46d092018-04-07 15:32:37279
Tim van der Lippe13f71fb2019-11-29 11:17:39280 return new Step(ownValue, node.nodeType() === Node.DOCUMENT_NODE);
Blink Reformat4c46d092018-04-07 15:32:37281};
282
283/**
Tim van der Lippe97611c92020-02-12 16:56:58284 * @param {!SDK.DOMModel.DOMNode} node
Blink Reformat4c46d092018-04-07 15:32:37285 * @return {number}
286 */
Tim van der Lippe13f71fb2019-11-29 11:17:39287export const _xPathIndex = function(node) {
Tim van der Lipped3f78ce2020-09-30 16:01:23288 /**
289 * Returns -1 in case of error, 0 if no siblings matching the same expression,
290 * <XPath index among the same expression-matching sibling nodes> otherwise.
291 *
292 * @param {!SDK.DOMModel.DOMNode} left
293 * @param {!SDK.DOMModel.DOMNode} right
294 */
Blink Reformat4c46d092018-04-07 15:32:37295 function areNodesSimilar(left, right) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34296 if (left === right) {
Blink Reformat4c46d092018-04-07 15:32:37297 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34298 }
Blink Reformat4c46d092018-04-07 15:32:37299
Tim van der Lippe1d6e57a2019-09-30 11:55:34300 if (left.nodeType() === Node.ELEMENT_NODE && right.nodeType() === Node.ELEMENT_NODE) {
Blink Reformat4c46d092018-04-07 15:32:37301 return left.localName() === right.localName();
Tim van der Lippe1d6e57a2019-09-30 11:55:34302 }
Blink Reformat4c46d092018-04-07 15:32:37303
Tim van der Lippe1d6e57a2019-09-30 11:55:34304 if (left.nodeType() === right.nodeType()) {
Blink Reformat4c46d092018-04-07 15:32:37305 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:34306 }
Blink Reformat4c46d092018-04-07 15:32:37307
308 // XPath treats CDATA as text nodes.
309 const leftType = left.nodeType() === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : left.nodeType();
310 const rightType = right.nodeType() === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : right.nodeType();
311 return leftType === rightType;
312 }
313
314 const siblings = node.parentNode ? node.parentNode.children() : null;
Tim van der Lippe1d6e57a2019-09-30 11:55:34315 if (!siblings) {
316 return 0;
317 } // Root node - no siblings.
Blink Reformat4c46d092018-04-07 15:32:37318 let hasSameNamedElements;
319 for (let i = 0; i < siblings.length; ++i) {
320 if (areNodesSimilar(node, siblings[i]) && siblings[i] !== node) {
321 hasSameNamedElements = true;
322 break;
323 }
324 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34325 if (!hasSameNamedElements) {
Blink Reformat4c46d092018-04-07 15:32:37326 return 0;
Tim van der Lippe1d6e57a2019-09-30 11:55:34327 }
Blink Reformat4c46d092018-04-07 15:32:37328 let ownIndex = 1; // XPath indices start with 1.
329 for (let i = 0; i < siblings.length; ++i) {
330 if (areNodesSimilar(node, siblings[i])) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34331 if (siblings[i] === node) {
Blink Reformat4c46d092018-04-07 15:32:37332 return ownIndex;
Tim van der Lippe1d6e57a2019-09-30 11:55:34333 }
Blink Reformat4c46d092018-04-07 15:32:37334 ++ownIndex;
335 }
336 }
337 return -1; // An error occurred: |node| not found in parent's children.
338};
339
340/**
341 * @unrestricted
342 */
Tim van der Lippe13f71fb2019-11-29 11:17:39343export class Step {
Blink Reformat4c46d092018-04-07 15:32:37344 /**
345 * @param {string} value
346 * @param {boolean} optimized
347 */
348 constructor(value, optimized) {
349 this.value = value;
350 this.optimized = optimized || false;
351 }
352
353 /**
354 * @override
355 * @return {string}
356 */
357 toString() {
358 return this.value;
359 }
Tim van der Lippe13f71fb2019-11-29 11:17:39360}