nemesarial / CSSResolver

// ==UserScript==
// @name        CSSResolver
// @namespace   me.markh.cssresolver
// @description Easily resolve the shortest css route to any element on the page. 
// @include     *
// @version     1
// @grant       GM_setClipboard
// @require     http://code.jquery.com/jquery-1.11.2.min.js
// ==/UserScript==


this.$ = this.jQuery = jQuery.noConflict(true);

if (!("contextMenu" in document.documentElement &&
      "HTMLMenuItemElement" in window)) return;

;


jQuery(function () {
    jQuery.fn.fcss = function (iselect, $that, depth) {
        depth = depth || 1;
        iselect = iselect || [''];
        $that = $that || [null];
        var selectors = [];
        var uniques = [];

        //if(depth > 5) return [];

        var k_combinations = function (set, k) {
            var i, j, combs, head, tailcombs;

            if (k > set.length || k <= 0) {
                return [];
            }

            if (k == set.length) {
                return [set];
            }

            if (k == 1) {
                combs = [];
                for (i = 0; i < set.length; i++) {
                    combs.push([set[i]]);
                }
                return combs;
            }

            // Assert {1 < k < set.length}

            combs = [];
            for (i = 0; i < set.length - k + 1; i++) {
                head = set.slice(i, i + 1);
                tailcombs = k_combinations(set.slice(i + 1), k - 1);
                for (j = 0; j < tailcombs.length; j++) {
                    combs.push(head.concat(tailcombs[j]));
                }
            }
            return combs;
        }


        var combinations = function (set) {
            var k, i, combs, k_combs;
            combs = [];

            // Calculate all non-empty k-combinations
            for (k = 1; k <= set.length; k++) {
                k_combs = k_combinations(set, k);
                for (i = 0; i < k_combs.length; i++) {
                    combs.push(k_combs[i]);
                }
            }
            return combs;
        }

        $(this).each(function () {
            var $this = $(this);
            for (var idx in this.attributes) {
                var attr = this.attributes[idx];
                if (typeof(attr.value) != 'undefined') {
                    if (attr.name.substr(0, 5) == 'data-' && (attr.name.substr(0, 15) !== 'data-validation' ) || (['name', 'id', 'type'].indexOf(attr.name) >= 0)) {
                        selectors.push('[' + attr.name + '=\'' + attr.value + '\']');
                    }
                }
            }
            if(typeof(this.className)!=='undefined'){
                var classes = this.className.split(' ');
                classes.forEach(function (cls) {
                    if (cls.trim() !== '') selectors.push('.' + cls);
                });
            }

            var combos = combinations(selectors);

            var unique = [];
            iselect.forEach(function (ctx) {
                combos.forEach(function (combo) {

                    var strCombo = (combo.join('') + ' ' + ctx).trim();
                    var $combo = $(strCombo);
                    if ($combo.length == 1) {
                        //console.log({combo:$combo[0],that: $that[0],tthis:$this[0], c: ($that[0]===null)?$this[0]:$that[0]});
                        if ($combo[0] == (($that[0]===null)?$this[0]:$that[0])) {
                            unique.push(strCombo);
                        }
                    }
                });
            });

            unique.sort(function (a, b) {
                return a.length - b.length;
            });

            if (unique.length > 0) {
                uniques.push(unique);
            } else {
                if (['BODY', 'HTML'].indexOf(this.tagName) < 0) {
                    uniques = $this.parent().fcss(combos, ($that[0]===null)? $this : $that, depth + 1);
                }
            }
        });
        return uniques.length == 0 ? [] : (uniques.length > 1 ? uniques : uniques[0]);
    };
});






var body = document.body;
body.addEventListener("contextmenu", initMenu, false);
var menu = body.appendChild(document.createElement('menu'));
menu.outerHTML = '<menu id="css-selector-menu" type="context">\
  <menu id="css-selector-menu-label" label="CSS Selectors"></menu>\
</menu>';

function hello(val){
  GM_setClipboard(val);
}

function initMenu(aEvent){
  var node = aEvent.target;
  var selectors=$(node).fcss().splice(0,10);
  if(selectors.length > 0){
    body.setAttribute('contextmenu','css-selector-menu');
    $('#css-selector-menu-label').attr('label', 'CSS Selectors ('+selectors.length+')');
    $('#css-selector-menu-label').children().remove();
    selectors.forEach(function(selector){
      var item=$('<menuitem>');
      item.attr('label',selector);
      item.data('selector',selector);
      $('#css-selector-menu-label').append(item);
      item.on('click',function(){
        hello(selector);
      })
    })
  }else{
    body.removeAttribute('contextmenu');
  }
}