зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1588773 - Move css-selector.js helpers back to DevTools css-logic.js r=pbro
Depends on D49303 Some methods from css-logic were extracted from the devtools codebase to be used by context-menu files. This was only needed in order to compute the css-selectors for Inspect Element. If we use ContentDOMReference instead, those helpers can move back in the devtools codebase (leaving them in css-selector.js fails the all-files-referenced test for some reason as well) Differential Revision: https://phabricator.services.mozilla.com/D49330 --HG-- rename : toolkit/modules/tests/chrome/test_findCssSelector.html => devtools/shared/tests/mochitest/test_css-logic-findCssSelector.html extra : moz-landing-system : lando
This commit is contained in:
Родитель
c72c1dcdcc
Коммит
a399da3be9
|
@ -19,22 +19,6 @@ const MAX_DATA_URL_LENGTH = 40;
|
|||
|
||||
const Services = require("Services");
|
||||
|
||||
loader.lazyImporter(
|
||||
this,
|
||||
"findCssSelector",
|
||||
"resource://gre/modules/css-selector.js"
|
||||
);
|
||||
loader.lazyImporter(
|
||||
this,
|
||||
"findAllCssSelectors",
|
||||
"resource://gre/modules/css-selector.js"
|
||||
);
|
||||
loader.lazyImporter(
|
||||
this,
|
||||
"getCssPath",
|
||||
"resource://gre/modules/css-selector.js"
|
||||
);
|
||||
loader.lazyImporter(this, "getXPath", "resource://gre/modules/css-selector.js");
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"getCSSLexer",
|
||||
|
@ -47,7 +31,7 @@ loader.lazyRequireGetter(
|
|||
"devtools/shared/indentation",
|
||||
true
|
||||
);
|
||||
|
||||
const { getRootBindingParent } = require("devtools/shared/layout/utils");
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
const styleInspectorL10N = new LocalizationHelper(
|
||||
"devtools/shared/locales/styleinspector.properties"
|
||||
|
@ -495,38 +479,6 @@ function prettifyCSS(text, ruleCount) {
|
|||
|
||||
exports.prettifyCSS = prettifyCSS;
|
||||
|
||||
/**
|
||||
* Find a unique CSS selector for a given element
|
||||
* @returns a string such that ele.ownerDocument.querySelector(reply) === ele
|
||||
* and ele.ownerDocument.querySelectorAll(reply).length === 1
|
||||
*/
|
||||
exports.findCssSelector = findCssSelector;
|
||||
|
||||
/**
|
||||
* Retrieve the array of CSS selectors corresponding to the provided node.
|
||||
*
|
||||
* The selectors are ordered starting with the root document and ending with the deepest
|
||||
* nested frame. Additional items are used if the node is inside a frame or a shadow root,
|
||||
* each representing the CSS selector for finding the frame or root element in its parent
|
||||
* document.
|
||||
*/
|
||||
exports.findAllCssSelectors = findAllCssSelectors;
|
||||
|
||||
/**
|
||||
* Get the full CSS path for a given element.
|
||||
* @returns a string that can be used as a CSS selector for the element. It might not
|
||||
* match the element uniquely. It does however, represent the full path from the root
|
||||
* node to the element.
|
||||
*/
|
||||
exports.getCssPath = getCssPath;
|
||||
|
||||
/**
|
||||
* Get the xpath for a given element.
|
||||
* @param {DomNode} ele
|
||||
* @returns a string that can be used as an XPath to find the element uniquely.
|
||||
*/
|
||||
exports.getXPath = getXPath;
|
||||
|
||||
/**
|
||||
* Given a node, check to see if it is a ::marker, ::before, or ::after element.
|
||||
* If so, return the node that is accessible from within the document
|
||||
|
@ -585,3 +537,298 @@ function hasVisitedState(node) {
|
|||
);
|
||||
}
|
||||
exports.hasVisitedState = hasVisitedState;
|
||||
|
||||
/**
|
||||
* Return the node's parent shadow root if the node in shadow DOM, null
|
||||
* otherwise.
|
||||
*/
|
||||
function getShadowRoot(node) {
|
||||
const doc = node.ownerDocument;
|
||||
if (!doc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parent = doc.getBindingParent(node);
|
||||
const shadowRoot = parent && parent.openOrClosedShadowRoot;
|
||||
if (shadowRoot) {
|
||||
return shadowRoot;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the position of [element] in [nodeList].
|
||||
* @returns an index of the match, or -1 if there is no match
|
||||
*/
|
||||
function positionInNodeList(element, nodeList) {
|
||||
for (let i = 0; i < nodeList.length; i++) {
|
||||
if (element === nodeList[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a provided node, find the appropriate container/node couple so that
|
||||
* container.contains(node) and a CSS selector can be created from the
|
||||
* container to the node.
|
||||
*/
|
||||
function findNodeAndContainer(node) {
|
||||
const shadowRoot = getShadowRoot(node);
|
||||
if (shadowRoot) {
|
||||
// If the node is under a shadow root, the shadowRoot contains the node and
|
||||
// we can find the node via shadowRoot.querySelector(path).
|
||||
return {
|
||||
containingDocOrShadow: shadowRoot,
|
||||
node,
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise, get the root binding parent to get a non anonymous element that
|
||||
// will be accessible from the ownerDocument.
|
||||
const bindingParent = getRootBindingParent(node);
|
||||
return {
|
||||
containingDocOrShadow: bindingParent.ownerDocument,
|
||||
node: bindingParent,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a unique CSS selector for a given element
|
||||
* @returns a string such that:
|
||||
* - ele.containingDocOrShadow.querySelector(reply) === ele
|
||||
* - ele.containingDocOrShadow.querySelectorAll(reply).length === 1
|
||||
*/
|
||||
const findCssSelector = function(ele) {
|
||||
const { node, containingDocOrShadow } = findNodeAndContainer(ele);
|
||||
ele = node;
|
||||
|
||||
if (!containingDocOrShadow || !containingDocOrShadow.contains(ele)) {
|
||||
// findCssSelector received element not inside container.
|
||||
return "";
|
||||
}
|
||||
|
||||
const cssEscape = ele.ownerGlobal.CSS.escape;
|
||||
|
||||
// document.querySelectorAll("#id") returns multiple if elements share an ID
|
||||
if (
|
||||
ele.id &&
|
||||
containingDocOrShadow.querySelectorAll("#" + cssEscape(ele.id)).length === 1
|
||||
) {
|
||||
return "#" + cssEscape(ele.id);
|
||||
}
|
||||
|
||||
// Inherently unique by tag name
|
||||
const tagName = ele.localName;
|
||||
if (tagName === "html") {
|
||||
return "html";
|
||||
}
|
||||
if (tagName === "head") {
|
||||
return "head";
|
||||
}
|
||||
if (tagName === "body") {
|
||||
return "body";
|
||||
}
|
||||
|
||||
// We might be able to find a unique class name
|
||||
let selector, index, matches;
|
||||
for (let i = 0; i < ele.classList.length; i++) {
|
||||
// Is this className unique by itself?
|
||||
selector = "." + cssEscape(ele.classList.item(i));
|
||||
matches = containingDocOrShadow.querySelectorAll(selector);
|
||||
if (matches.length === 1) {
|
||||
return selector;
|
||||
}
|
||||
// Maybe it's unique with a tag name?
|
||||
selector = cssEscape(tagName) + selector;
|
||||
matches = containingDocOrShadow.querySelectorAll(selector);
|
||||
if (matches.length === 1) {
|
||||
return selector;
|
||||
}
|
||||
// Maybe it's unique using a tag name and nth-child
|
||||
index = positionInNodeList(ele, ele.parentNode.children) + 1;
|
||||
selector = selector + ":nth-child(" + index + ")";
|
||||
matches = containingDocOrShadow.querySelectorAll(selector);
|
||||
if (matches.length === 1) {
|
||||
return selector;
|
||||
}
|
||||
}
|
||||
|
||||
// Not unique enough yet.
|
||||
index = positionInNodeList(ele, ele.parentNode.children) + 1;
|
||||
selector = cssEscape(tagName) + ":nth-child(" + index + ")";
|
||||
if (ele.parentNode !== containingDocOrShadow) {
|
||||
selector = findCssSelector(ele.parentNode) + " > " + selector;
|
||||
}
|
||||
return selector;
|
||||
};
|
||||
exports.findCssSelector = findCssSelector;
|
||||
|
||||
/**
|
||||
* If the element is in a frame or under a shadowRoot, return the corresponding
|
||||
* element.
|
||||
*/
|
||||
function getSelectorParent(node) {
|
||||
const shadowRoot = getShadowRoot(node);
|
||||
if (shadowRoot) {
|
||||
// The element is in a shadowRoot, return the host component.
|
||||
return shadowRoot.host;
|
||||
}
|
||||
|
||||
// Otherwise return the parent frameElement.
|
||||
return node.ownerGlobal.frameElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the array of CSS selectors corresponding to the provided node.
|
||||
*
|
||||
* The selectors are ordered starting with the root document and ending with the deepest
|
||||
* nested frame. Additional items are used if the node is inside a frame or a shadow root,
|
||||
* each representing the CSS selector for finding the frame or root element in its parent
|
||||
* document.
|
||||
*
|
||||
* This format is expected by DevTools in order to handle the Inspect Node context menu
|
||||
* item.
|
||||
*
|
||||
* @param {node}
|
||||
* The node for which the CSS selectors should be computed
|
||||
* @return {Array}
|
||||
* An array of CSS selectors to find the target node. Several selectors can be
|
||||
* needed if the element is nested in frames and not directly in the root
|
||||
* document. The selectors are ordered starting with the root document and
|
||||
* ending with the deepest nested frame or shadow root.
|
||||
*/
|
||||
const findAllCssSelectors = function(node) {
|
||||
const selectors = [];
|
||||
while (node) {
|
||||
selectors.unshift(findCssSelector(node));
|
||||
node = getSelectorParent(node);
|
||||
}
|
||||
|
||||
return selectors;
|
||||
};
|
||||
exports.findAllCssSelectors = findAllCssSelectors;
|
||||
|
||||
/**
|
||||
* Get the full CSS path for a given element.
|
||||
*
|
||||
* @returns a string that can be used as a CSS selector for the element. It might not
|
||||
* match the element uniquely. It does however, represent the full path from the root
|
||||
* node to the element.
|
||||
*/
|
||||
function getCssPath(ele) {
|
||||
const { node, containingDocOrShadow } = findNodeAndContainer(ele);
|
||||
ele = node;
|
||||
if (!containingDocOrShadow || !containingDocOrShadow.contains(ele)) {
|
||||
// getCssPath received element not inside container.
|
||||
return "";
|
||||
}
|
||||
|
||||
const nodeGlobal = ele.ownerGlobal.Node;
|
||||
|
||||
const getElementSelector = element => {
|
||||
if (!element.localName) {
|
||||
return "";
|
||||
}
|
||||
|
||||
let label =
|
||||
element.nodeName == element.nodeName.toUpperCase()
|
||||
? element.localName.toLowerCase()
|
||||
: element.localName;
|
||||
|
||||
if (element.id) {
|
||||
label += "#" + element.id;
|
||||
}
|
||||
|
||||
if (element.classList) {
|
||||
for (const cl of element.classList) {
|
||||
label += "." + cl;
|
||||
}
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
const paths = [];
|
||||
|
||||
while (ele) {
|
||||
if (!ele || ele.nodeType !== nodeGlobal.ELEMENT_NODE) {
|
||||
break;
|
||||
}
|
||||
|
||||
paths.splice(0, 0, getElementSelector(ele));
|
||||
ele = ele.parentNode;
|
||||
}
|
||||
|
||||
return paths.length ? paths.join(" ") : "";
|
||||
}
|
||||
exports.getCssPath = getCssPath;
|
||||
|
||||
/**
|
||||
* Get the xpath for a given element.
|
||||
*
|
||||
* @param {DomNode} ele
|
||||
* @returns a string that can be used as an XPath to find the element uniquely.
|
||||
*/
|
||||
function getXPath(ele) {
|
||||
const { node, containingDocOrShadow } = findNodeAndContainer(ele);
|
||||
ele = node;
|
||||
if (!containingDocOrShadow || !containingDocOrShadow.contains(ele)) {
|
||||
// getXPath received element not inside container.
|
||||
return "";
|
||||
}
|
||||
|
||||
// Create a short XPath for elements with IDs.
|
||||
if (ele.id) {
|
||||
return `//*[@id="${ele.id}"]`;
|
||||
}
|
||||
|
||||
// Otherwise walk the DOM up and create a part for each ancestor.
|
||||
const parts = [];
|
||||
|
||||
const nodeGlobal = ele.ownerGlobal.Node;
|
||||
// Use nodeName (instead of localName) so namespace prefix is included (if any).
|
||||
while (ele && ele.nodeType === nodeGlobal.ELEMENT_NODE) {
|
||||
let nbOfPreviousSiblings = 0;
|
||||
let hasNextSiblings = false;
|
||||
|
||||
// Count how many previous same-name siblings the element has.
|
||||
let sibling = ele.previousSibling;
|
||||
while (sibling) {
|
||||
// Ignore document type declaration.
|
||||
if (
|
||||
sibling.nodeType !== nodeGlobal.DOCUMENT_TYPE_NODE &&
|
||||
sibling.nodeName == ele.nodeName
|
||||
) {
|
||||
nbOfPreviousSiblings++;
|
||||
}
|
||||
|
||||
sibling = sibling.previousSibling;
|
||||
}
|
||||
|
||||
// Check if the element has at least 1 next same-name sibling.
|
||||
sibling = ele.nextSibling;
|
||||
while (sibling) {
|
||||
if (sibling.nodeName == ele.nodeName) {
|
||||
hasNextSiblings = true;
|
||||
break;
|
||||
}
|
||||
sibling = sibling.nextSibling;
|
||||
}
|
||||
|
||||
const prefix = ele.prefix ? ele.prefix + ":" : "";
|
||||
const nth =
|
||||
nbOfPreviousSiblings || hasNextSiblings
|
||||
? `[${nbOfPreviousSiblings + 1}]`
|
||||
: "";
|
||||
|
||||
parts.push(prefix + ele.localName + nth);
|
||||
|
||||
ele = ele.parentNode;
|
||||
}
|
||||
|
||||
return parts.length ? "/" + parts.reverse().join("/") : "";
|
||||
}
|
||||
exports.getXPath = getXPath;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
tags = devtools
|
||||
skip-if = os == 'android'
|
||||
|
||||
[test_css-logic-findCssSelector.html]
|
||||
[test_css-logic-getCssPath.html]
|
||||
[test_css-logic-getXPath.html]
|
||||
skip-if = os == 'linux' && debug # Bug 1205739
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug </title>
|
||||
<title>Test for CSS logic helper </title>
|
||||
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
<script type="application/javascript">
|
||||
|
||||
/* globals findCssSelector */
|
||||
ChromeUtils.import("resource://gre/modules/css-selector.js", this);
|
||||
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
|
||||
const { findCssSelector } = require("devtools/shared/inspector/css-logic");
|
||||
|
||||
var _tests = [];
|
||||
function addTest(test) {
|
|
@ -1,327 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = [
|
||||
"findAllCssSelectors",
|
||||
"findCssSelector",
|
||||
"getCssPath",
|
||||
"getXPath",
|
||||
];
|
||||
|
||||
/**
|
||||
* Traverse getBindingParent until arriving upon the bound element
|
||||
* responsible for the generation of the specified node.
|
||||
* See https://developer.mozilla.org/en-US/docs/XBL/XBL_1.0_Reference/DOM_Interfaces#getBindingParent.
|
||||
*
|
||||
* @param {DOMNode} node
|
||||
* @return {DOMNode}
|
||||
* If node is not anonymous, this will return node. Otherwise,
|
||||
* it will return the bound element
|
||||
*
|
||||
*/
|
||||
function getRootBindingParent(node) {
|
||||
let doc = node.ownerDocument;
|
||||
if (!doc) {
|
||||
return node;
|
||||
}
|
||||
|
||||
let parent;
|
||||
while ((parent = doc.getBindingParent(node))) {
|
||||
node = parent;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the node's parent shadow root if the node in shadow DOM, null
|
||||
* otherwise.
|
||||
*/
|
||||
function getShadowRoot(node) {
|
||||
let doc = node.ownerDocument;
|
||||
if (!doc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parent = doc.getBindingParent(node);
|
||||
const shadowRoot = parent && parent.openOrClosedShadowRoot;
|
||||
if (shadowRoot) {
|
||||
return shadowRoot;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the position of [element] in [nodeList].
|
||||
* @returns an index of the match, or -1 if there is no match
|
||||
*/
|
||||
function positionInNodeList(element, nodeList) {
|
||||
for (let i = 0; i < nodeList.length; i++) {
|
||||
if (element === nodeList[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a provided node, find the appropriate container/node couple so that
|
||||
* container.contains(node) and a CSS selector can be created from the
|
||||
* container to the node.
|
||||
*/
|
||||
function findNodeAndContainer(node) {
|
||||
const shadowRoot = getShadowRoot(node);
|
||||
if (shadowRoot) {
|
||||
// If the node is under a shadow root, the shadowRoot contains the node and
|
||||
// we can find the node via shadowRoot.querySelector(path).
|
||||
return {
|
||||
containingDocOrShadow: shadowRoot,
|
||||
node,
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise, get the root binding parent to get a non anonymous element that
|
||||
// will be accessible from the ownerDocument.
|
||||
const bindingParent = getRootBindingParent(node);
|
||||
return {
|
||||
containingDocOrShadow: bindingParent.ownerDocument,
|
||||
node: bindingParent,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a unique CSS selector for a given element
|
||||
* @returns a string such that:
|
||||
* - ele.containingDocOrShadow.querySelector(reply) === ele
|
||||
* - ele.containingDocOrShadow.querySelectorAll(reply).length === 1
|
||||
*/
|
||||
const findCssSelector = function(ele) {
|
||||
const { node, containingDocOrShadow } = findNodeAndContainer(ele);
|
||||
ele = node;
|
||||
|
||||
if (!containingDocOrShadow || !containingDocOrShadow.contains(ele)) {
|
||||
// findCssSelector received element not inside container.
|
||||
return "";
|
||||
}
|
||||
|
||||
let cssEscape = ele.ownerGlobal.CSS.escape;
|
||||
|
||||
// document.querySelectorAll("#id") returns multiple if elements share an ID
|
||||
if (
|
||||
ele.id &&
|
||||
containingDocOrShadow.querySelectorAll("#" + cssEscape(ele.id)).length === 1
|
||||
) {
|
||||
return "#" + cssEscape(ele.id);
|
||||
}
|
||||
|
||||
// Inherently unique by tag name
|
||||
let tagName = ele.localName;
|
||||
if (tagName === "html") {
|
||||
return "html";
|
||||
}
|
||||
if (tagName === "head") {
|
||||
return "head";
|
||||
}
|
||||
if (tagName === "body") {
|
||||
return "body";
|
||||
}
|
||||
|
||||
// We might be able to find a unique class name
|
||||
let selector, index, matches;
|
||||
for (let i = 0; i < ele.classList.length; i++) {
|
||||
// Is this className unique by itself?
|
||||
selector = "." + cssEscape(ele.classList.item(i));
|
||||
matches = containingDocOrShadow.querySelectorAll(selector);
|
||||
if (matches.length === 1) {
|
||||
return selector;
|
||||
}
|
||||
// Maybe it's unique with a tag name?
|
||||
selector = cssEscape(tagName) + selector;
|
||||
matches = containingDocOrShadow.querySelectorAll(selector);
|
||||
if (matches.length === 1) {
|
||||
return selector;
|
||||
}
|
||||
// Maybe it's unique using a tag name and nth-child
|
||||
index = positionInNodeList(ele, ele.parentNode.children) + 1;
|
||||
selector = selector + ":nth-child(" + index + ")";
|
||||
matches = containingDocOrShadow.querySelectorAll(selector);
|
||||
if (matches.length === 1) {
|
||||
return selector;
|
||||
}
|
||||
}
|
||||
|
||||
// Not unique enough yet.
|
||||
index = positionInNodeList(ele, ele.parentNode.children) + 1;
|
||||
selector = cssEscape(tagName) + ":nth-child(" + index + ")";
|
||||
if (ele.parentNode !== containingDocOrShadow) {
|
||||
selector = findCssSelector(ele.parentNode) + " > " + selector;
|
||||
}
|
||||
return selector;
|
||||
};
|
||||
|
||||
/**
|
||||
* If the element is in a frame or under a shadowRoot, return the corresponding
|
||||
* element.
|
||||
*/
|
||||
function getSelectorParent(node) {
|
||||
const shadowRoot = getShadowRoot(node);
|
||||
if (shadowRoot) {
|
||||
// The element is in a shadowRoot, return the host component.
|
||||
return shadowRoot.host;
|
||||
}
|
||||
|
||||
// Otherwise return the parent frameElement.
|
||||
return node.ownerGlobal.frameElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the array of CSS selectors corresponding to the provided node.
|
||||
*
|
||||
* The selectors are ordered starting with the root document and ending with the deepest
|
||||
* nested frame. Additional items are used if the node is inside a frame or a shadow root,
|
||||
* each representing the CSS selector for finding the frame or root element in its parent
|
||||
* document.
|
||||
*
|
||||
* This format is expected by DevTools in order to handle the Inspect Node context menu
|
||||
* item.
|
||||
*
|
||||
* @param {node}
|
||||
* The node for which the CSS selectors should be computed
|
||||
* @return {Array}
|
||||
* An array of CSS selectors to find the target node. Several selectors can be
|
||||
* needed if the element is nested in frames and not directly in the root
|
||||
* document. The selectors are ordered starting with the root document and
|
||||
* ending with the deepest nested frame or shadow root.
|
||||
*/
|
||||
const findAllCssSelectors = function(node) {
|
||||
let selectors = [];
|
||||
while (node) {
|
||||
selectors.unshift(findCssSelector(node));
|
||||
node = getSelectorParent(node);
|
||||
}
|
||||
|
||||
return selectors;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the full CSS path for a given element.
|
||||
* @returns a string that can be used as a CSS selector for the element. It might not
|
||||
* match the element uniquely. It does however, represent the full path from the root
|
||||
* node to the element.
|
||||
*/
|
||||
function getCssPath(ele) {
|
||||
const { node, containingDocOrShadow } = findNodeAndContainer(ele);
|
||||
ele = node;
|
||||
if (!containingDocOrShadow || !containingDocOrShadow.contains(ele)) {
|
||||
// getCssPath received element not inside container.
|
||||
return "";
|
||||
}
|
||||
|
||||
const nodeGlobal = ele.ownerGlobal.Node;
|
||||
|
||||
const getElementSelector = element => {
|
||||
if (!element.localName) {
|
||||
return "";
|
||||
}
|
||||
|
||||
let label =
|
||||
element.nodeName == element.nodeName.toUpperCase()
|
||||
? element.localName.toLowerCase()
|
||||
: element.localName;
|
||||
|
||||
if (element.id) {
|
||||
label += "#" + element.id;
|
||||
}
|
||||
|
||||
if (element.classList) {
|
||||
for (const cl of element.classList) {
|
||||
label += "." + cl;
|
||||
}
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
const paths = [];
|
||||
|
||||
while (ele) {
|
||||
if (!ele || ele.nodeType !== nodeGlobal.ELEMENT_NODE) {
|
||||
break;
|
||||
}
|
||||
|
||||
paths.splice(0, 0, getElementSelector(ele));
|
||||
ele = ele.parentNode;
|
||||
}
|
||||
|
||||
return paths.length ? paths.join(" ") : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the xpath for a given element.
|
||||
* @param {DomNode} ele
|
||||
* @returns a string that can be used as an XPath to find the element uniquely.
|
||||
*/
|
||||
function getXPath(ele) {
|
||||
const { node, containingDocOrShadow } = findNodeAndContainer(ele);
|
||||
ele = node;
|
||||
if (!containingDocOrShadow || !containingDocOrShadow.contains(ele)) {
|
||||
// getXPath received element not inside container.
|
||||
return "";
|
||||
}
|
||||
|
||||
// Create a short XPath for elements with IDs.
|
||||
if (ele.id) {
|
||||
return `//*[@id="${ele.id}"]`;
|
||||
}
|
||||
|
||||
// Otherwise walk the DOM up and create a part for each ancestor.
|
||||
const parts = [];
|
||||
|
||||
const nodeGlobal = ele.ownerGlobal.Node;
|
||||
// Use nodeName (instead of localName) so namespace prefix is included (if any).
|
||||
while (ele && ele.nodeType === nodeGlobal.ELEMENT_NODE) {
|
||||
let nbOfPreviousSiblings = 0;
|
||||
let hasNextSiblings = false;
|
||||
|
||||
// Count how many previous same-name siblings the element has.
|
||||
let sibling = ele.previousSibling;
|
||||
while (sibling) {
|
||||
// Ignore document type declaration.
|
||||
if (
|
||||
sibling.nodeType !== nodeGlobal.DOCUMENT_TYPE_NODE &&
|
||||
sibling.nodeName == ele.nodeName
|
||||
) {
|
||||
nbOfPreviousSiblings++;
|
||||
}
|
||||
|
||||
sibling = sibling.previousSibling;
|
||||
}
|
||||
|
||||
// Check if the element has at least 1 next same-name sibling.
|
||||
sibling = ele.nextSibling;
|
||||
while (sibling) {
|
||||
if (sibling.nodeName == ele.nodeName) {
|
||||
hasNextSiblings = true;
|
||||
break;
|
||||
}
|
||||
sibling = sibling.nextSibling;
|
||||
}
|
||||
|
||||
const prefix = ele.prefix ? ele.prefix + ":" : "";
|
||||
const nth =
|
||||
nbOfPreviousSiblings || hasNextSiblings
|
||||
? `[${nbOfPreviousSiblings + 1}]`
|
||||
: "";
|
||||
|
||||
parts.push(prefix + ele.localName + nth);
|
||||
|
||||
ele = ele.parentNode;
|
||||
}
|
||||
|
||||
return parts.length ? "/" + parts.reverse().join("/") : "";
|
||||
}
|
|
@ -175,7 +175,6 @@ EXTRA_JS_MODULES += [
|
|||
'Console.jsm',
|
||||
'ContentDOMReference.jsm',
|
||||
'CreditCard.jsm',
|
||||
'css-selector.js',
|
||||
'DateTimePickerPanel.jsm',
|
||||
'DeferredTask.jsm',
|
||||
'Deprecated.jsm',
|
||||
|
|
|
@ -2,4 +2,3 @@
|
|||
|
||||
[test_bug544442_checkCert.xul]
|
||||
skip-if = verify
|
||||
[test_findCssSelector.html]
|
Загрузка…
Ссылка в новой задаче