/* -*- 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 = ["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; };