burak emre
burak emre

Reputation: 1591

How can I detect overwritten css properties?

I can get all css properties for an element with document.stylesheets but some of those are not active because those properties are overwritten. In firebug (chrome developer tools also has this feature), if there's an overwritten css property, you will see something like that: enter image description here

The only way I can think of is comparing active css property for the element (in jQuery $(element).css(property)) and defined css property in document.stylesheets but it's not a reliable way to do it. Any suggestion?

Upvotes: 5

Views: 3235

Answers (3)

Beat Richartz
Beat Richartz

Reputation: 9622

In webkit, you can use getMatchedCSSRules to achieve what you want. It returns a CSS Rule set in order of inheritance applied by the browser and it is what the webkit inspector used some time ago. For Gecko based browsers like Firefox there seems to be a polyfill available here although I've not tested it.

A basic, working solution

The following code is also available as a fiddle

Because getMatchedCSSRules only works with inline stylesheets, you will first have to inline your linked stylesheets:

function inlineStyles() {
    var stylesheets = document.getElementsByTagName('link'),
        head = document.getElementsByTagName('head')[0];

    for (var i = 0; i < stylesheets.length; i++) {
        if (stylesheets[i].getAttribute('rel') == 'stylesheet') {
            (function (stylesheet) {
                var xmlhttp = new XMLHttpRequest();
                xmlhttp.open("GET", stylesheet.getAttribute('href'), true);
                xmlhttp.onreadystatechange = function () {
                    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
                        var inlineStyle = document.createElement('style');
                        inlineStyle.setAttribute('type', 'text/css');
                        inlineStyle.innerText = xmlhttp.responseText;
                        head.replaceChild(inlineStyle, stylesheet);
                    }
                };
                xmlhttp.send();
            })(stylesheets[i]);
        } else {
            continue;
        }
    };
}

Then, the big chunk: This is a first shot, improve at will. It can handle inherit rules and up to one !important definition, but that's it. For really complicated setups, this will have to be improved:

function getStyle(s, id) {
    var element = typeof id === 'string' ? document.getElementById(id) : id,
        css = window.getMatchedCSSRules(element),
        style = window.getComputedStyle(element),
        value = style[s],
        styles = [],
        rules = [],
        inherited, currentRule;

    // if there's a computed style, start calculation
    if (value) {

        // add matched rules if there are any
        if (css) {
            for (var i = 0; i < css.length; i++) {
                styles.push(css[i]);
            }
        }

        // add the element style attribute as a matched rule
        styles.push({
            style: element.style,
            cssText: 'element.style {' + element.getAttribute('style') + ' }'
        });

        for (var i = styles.length - 1; i >= 0; i--) {
            var def = styles[i],
                rule = {
                    index: rules.length,
                    style: s,
                    value: styles[i].style[s],
                    cssText: def.cssText
                };

            if (rule.value == 'inherit' && !currentRule) {
                if (inherited = getInherited(element, s, value)) {
                    rule.inheritedFrom = inherited;
                    currentRule = rule;
                    inherited = undefined;
                } else {
                    rules.push(rule);
                }
            } else if (rule.value == 'inherit' && currentRule && isImportant(s, value, def.cssText)) {
                if (inherited = getInherited(element, s, def)) {
                    rule.inheritedFrom = inherited;
                    rules.splice(currentRule.index, 0, currentRule);
                    currentRule = rule;
                    inherited = undefined;
                } else {
                    rules.push(rule);
                }
            } else if (rule.value == value && !currentRule) {
                currentRule = rule;
            } else if (rule.value == value && currentRule && isImportant(s, value, def.cssText)) {
                rules.splice(currentRule.index, 0, currentRule);
                currentRule = rule;
            } else if (rule.value.length) {
                rules.push(rule)
            }
        }

        return {
            current: currentRule,
            overwritten: rules
        };

    } else {
        return false;
    }
}

If Inheritance is taking place, we walk up the DOM Nodes to find the element that defines the CSS Rule with this helper function and get its style:

function getInherited(element, s, value) {
    while (element.parentNode && window.getComputedStyle(element.parentNode)[s] == value) {
        element = element.parentNode;
    }

    if (element) {
        return getStyle(s, element).current;
    } else {
        return null;
    }
}

We determine if a CSS Rule is marked as important with this helper function:

function isImportant(s, style, text) {
    return new RegExp(s.replace(/([A-Z])/g, '-$1').toLowerCase() + ':\\s+' + style + '\\s+!important').test(text)
}

Have a look at the fiddle to see it working

Upvotes: 8

Matthew R.
Matthew R.

Reputation: 4350

You would have to parse the CSS files that are linked to the page to be able to determine the styles that are being used. You would also have to determine what properties can be used to target elements in CSS. Here is the flow you would have to follow:

Elements can be targets by: tags, IDs, classes, pseudo classes and inheritance

You would then have a given element, for instance:

<a href="#" id="link" class="button">Woop</a>

That element has several selectors associated to it by default. All elements can inherit parent CSS, so you would have to create a hierarchy of parent elements. Later styles will take precedence. You then have to take into account the tag a and any parent that also shares this selector such as body a. Likewise, you would have to create a hierarchy of inherited attributes. You would repeat this process for classes, attributes, and pseudo-classes. Once you have compiled your list of trees for the element, you can then parse the CSS documents for the appropriate styles.

This is definitely a huge undertaking and there is no sense in reinventing the wheel. What I would suggest is looking at open source projects that have already done this. WebKit is an open-source project that has already built this system. Consider using their implementation as a starting point and make your changes from there.

Upvotes: 0

Xenology
Xenology

Reputation: 2452

This snippet may be useful to you, its helped me in the past, apply it in one of your CSS files, and you will be able to see what objects are being overridden by other class / id rules by finding the specific places where elements CSS declarations are, and by tracking their parents. My guess is you're having issues with inheritance.

* { outline: 2px dotted red }
* * { outline: 2px dotted green }
* * * { outline: 2px dotted orange }
* * * * { outline: 2px dotted blue }
* * * * * { outline: 1px solid red }
* * * * * * { outline: 1px solid green }
* * * * * * * { outline: 1px solid orange }
* * * * * * * * { outline: 1px solid blue }

Alternatively you could add a :hover attribute to those, and it would make it a bit easier to navigate, but the basic principal of the snippet is to show you the nesting of elements, and may help you to determine where the issues in your CSS are

Upvotes: 2

Related Questions