gecko-dev/toolkit/modules/css-selector.js

115 строки
3.4 KiB
JavaScript

/* -*- 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";
this.EXPORTED_SYMBOLS = ["findCssSelector"];
/**
* 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 parent;
let doc = node.ownerDocument;
if (!doc) {
return node;
}
while ((parent = doc.getBindingParent(node))) {
node = parent;
}
return node;
}
/**
* 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;
}
/**
* 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
*/
const findCssSelector = function(ele) {
ele = getRootBindingParent(ele);
let document = ele.ownerDocument;
if (!document || !document.contains(ele)) {
throw new Error("findCssSelector received element not inside document");
}
let cssEscape = ele.ownerGlobal.CSS.escape;
// document.querySelectorAll("#id") returns multiple if elements share an ID
if (ele.id &&
document.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;
if (ele.classList.length > 0) {
for (let i = 0; i < ele.classList.length; i++) {
// Is this className unique by itself?
selector = "." + cssEscape(ele.classList.item(i));
matches = document.querySelectorAll(selector);
if (matches.length === 1) {
return selector;
}
// Maybe it's unique with a tag name?
selector = cssEscape(tagName) + selector;
matches = document.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 = document.querySelectorAll(selector);
if (matches.length === 1) {
return selector;
}
}
}
// Not unique enough yet. As long as it's not a child of the document,
// continue recursing up until it is unique enough.
if (ele.parentNode !== document) {
index = positionInNodeList(ele, ele.parentNode.children) + 1;
selector = findCssSelector(ele.parentNode) + " > " +
cssEscape(tagName) + ":nth-child(" + index + ")";
}
return selector;
};