зеркало из https://github.com/mozilla/gecko-dev.git
115 строки
3.4 KiB
JavaScript
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;
|
|
}
|