<commit_before>/* See license.txt for terms of usage */

define([
    "firebug/lib/trace",
    "firebug/lib/url",
    "firebug/lib/options",
    "firebug/chrome/window",
    "firebug/lib/xml",
    "firebug/lib/http",
    "firebug/lib/xpath"
],
function(FBTrace, Url, Options, Win, Xml, Http, Xpath) {

// ********************************************************************************************* //
// Constants

var Ci = Components.interfaces;
var Cc = Components.classes;

// ********************************************************************************************* //
// Module Implementation

var Css = {};

// ********************************************************************************************* //
// CSS

var cssKeywordMap = {};
var cssPropNames = {};
var cssColorNames = null;
var imageRules = null;
var domUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);

Css.getCSSKeywordsByProperty = function(nodeType, propName, avoid)
{
    if (!cssKeywordMap[nodeType])
    {
        cssKeywordMap[nodeType] = {};

        for (var name in Css.cssInfo[nodeType])
        {
            var list = [];

            var types = Css.cssInfo[nodeType][name];
            for (var i = 0; i < types.length; ++i)
            {
                var keywords = Css.cssKeywords[types[i]];
                if (keywords)
                    list.push.apply(list, keywords);
                else
                    list.push(types[i]);
            }

            cssKeywordMap[nodeType][name] = list;
        }
    }

    propName = propName.toLowerCase();

    if (avoid)
        return getCSSPropertyKeywordsExcludingCategories(nodeType, propName, avoid);

    return cssKeywordMap[nodeType][propName] || [];
};

function getCSSPropertyKeywordsExcludingCategories(nodeType, propName, avoid)
{
    if (!(nodeType in Css.cssInfo) || !(propName in Css.cssInfo[nodeType]))
        return [];

    var list = [];
    var types = Css.cssInfo[nodeType][propName];
    for (var i = 0; i < types.length; ++i)
    {
        var type = types[i];
        if (avoid.indexOf(type) !== -1)
            continue;
        var keywords = Css.cssKeywords[type];
        if (keywords)
            list.push.apply(list, keywords);
        else
            list.push(type);
    }
    return list;
}

Css.getCSSPropertyNames = function(nodeType)
{
    if (!cssPropNames[nodeType])
    {
        cssPropNames[nodeType] = [];

        for (var name in Css.cssInfo[nodeType])
            cssPropNames[nodeType].push(name);
    }

    return cssPropNames[nodeType];
};

Css.getCSSShorthandCategory = function(nodeType, shorthandProp, keyword)
{
    if (!(nodeType in Css.cssInfo) || !(shorthandProp in Css.cssInfo[nodeType]))
        return null;

    var category = null;
    var types = Css.cssInfo[nodeType][shorthandProp];
    for (var i = 0; i < types.length; ++i)
    {
        var type = types[i];
        var keywords = Css.cssKeywords[type];
        if (keywords ? (keywords.indexOf(keyword) !== -1) : (type === keyword))
        {
            // Set this as the matched category, or if there is one already
            // bail out (we don't have a unique one).
            if (category)
                return null;
            category = type;
        }
    }
    return category;
};

/**
 * Parses the CSS properties of a CSSStyleRule
 * @param {Object} style CSSStyleRule to get the properties of
 * @param {Object} element Element to which the style applies. Needed for parsing
 *      shorthand properties correctly.
 *
 * @returns {Array} Properties represented by {name, value, priority, longhandProps}
 */
Css.parseCSSProps = function(style, element)
{
    var props = [];

    if (!element)
    {
        for (var i = 0, len = style.length; i < len; ++i)
        {
            var prop = style.item(i);
            props.push({name: prop,
                value: style.getPropertyValue(prop),
                priority: style.getPropertyPriority(longhandProp)});
        }
    }
    else
    {
        var lineRE = /(?:[^;\(]*(?:\([^\)]*?\))?[^;\(]*)*;?/g;
        var propRE = /\s*([^:\s]*)\s*:\s*(.*?)\s*(?:! (important))?;?$/;
        var lines = style.cssText.match(lineRE);
        for (var i = 0, len = lines.length; i < len; ++i)
        {
            var match = propRE.exec(lines[i]);
            if (!match)
                continue;

            if (match[2])
            {
                var prop = {name: match[1], value: match[2], priority: match[3] || ""};

                // Add longhand properties to shorthand property
                var doc = element.ownerDocument;
                var dummyElement = doc.createElementNS(element.namespaceURI, element.tagName);
                var dummyStyle = dummyElement.style;
                dummyStyle.cssText = "";
                dummyStyle.setProperty(prop.name, prop.value, prop.priority);

                if (dummyStyle.length > 1)
                {
                    prop.longhandProps = [];
                    for (var j = 0, propLen = dummyStyle.length; j < propLen; ++j)
                    {
                        var longhandProp = dummyStyle.item(j);
                        prop.longhandProps.push({name: longhandProp,
                            value: dummyStyle.getPropertyValue(longhandProp),
                            priority: dummyStyle.getPropertyPriority(longhandProp)});
                    }
                }

                props.push(prop);
            }
        }
    }

    return props;
};

Css.isColorKeyword = function(keyword)
{
    if (keyword == "transparent")
        return false;

    if (!cssColorNames)
    {
        cssColorNames = [];

        var colors = Css.cssKeywords["color"];
        for (var i = 0; i < colors.length; ++i)
            cssColorNames.push(colors[i].toLowerCase());
    }

    return cssColorNames.indexOf(keyword.toLowerCase()) != -1;
};

Css.isImageRule = function(nodeType,rule)
{
    if (!imageRules)
    {
        imageRules = [];

        for (var i in Css.cssInfo[nodeType])
        {
            var r = i.toLowerCase();
            var suffix = "image";
            if (r.match(suffix + "$") == suffix || r == "background")
                imageRules.push(r);
        }
    }

    return imageRules.indexOf(rule.toLowerCase()) != -1;
};

Css.copyTextStyles = function(fromNode, toNode, style)
{
    var view = fromNode ? fromNode.ownerDocument.defaultView : null;
    if (view)
    {
        if (!style)
            style = view.getComputedStyle(fromNode, "");

        toNode.style.fontFamily = style.fontFamily;
        toNode.style.fontSize = style.fontSize;
        toNode.style.fontWeight = style.fontWeight;
        toNode.style.fontStyle = style.fontStyle;
        toNode.style.fontSizeAdjust = style.fontSizeAdjust;
        toNode.style.fontStretch = style.fontStretch;
        toNode.style.fontVariant = style.fontVariant;
        toNode.style.MozFontFeatureSettings = style.MozFontFeatureSettings;

        return style;
    }
};

Css.copyBoxStyles = function(fromNode, toNode, style)
{
    var view = fromNode.ownerDocument.defaultView;
    if (view)
    {
        if (!style)
            style = view.getComputedStyle(fromNode, "");

        toNode.style.marginTop = style.marginTop;
        toNode.style.marginRight = style.marginRight;
        toNode.style.marginBottom = style.marginBottom;
        toNode.style.marginLeft = style.marginLeft;
        toNode.style.borderTopWidth = style.borderTopWidth;
        toNode.style.borderRightWidth = style.borderRightWidth;
        toNode.style.borderBottomWidth = style.borderBottomWidth;
        toNode.style.borderLeftWidth = style.borderLeftWidth;
        toNode.style.unicodeBidi = style.unicodeBidi;

        return style;
    }
};

Css.readBoxStyles = function(style)
{
    const styleNames = {
        "margin-top": "marginTop", "margin-right": "marginRight",
        "margin-left": "marginLeft", "margin-bottom": "marginBottom",
        "border-top-width": "borderTop", "border-right-width": "borderRight",
        "border-left-width": "borderLeft", "border-bottom-width": "borderBottom",
        "padding-top": "paddingTop", "padding-right": "paddingRight",
        "padding-left": "paddingLeft", "padding-bottom": "paddingBottom",
        "z-index": "zIndex"
    };

    var styles = {};
    for (var styleName in styleNames)
        styles[styleNames[styleName]] = parseInt(style.getPropertyCSSValue(styleName).cssText) || 0;

    if (FBTrace.DBG_INSPECT)
        FBTrace.sysout("readBoxStyles ", styles);

    return styles;
};

Css.getBoxFromStyles = function(style, element)
{
    var args = Css.readBoxStyles(style);
    args.width = element.offsetWidth
        - (args.paddingLeft+args.paddingRight+args.borderLeft+args.borderRight);
    args.height = element.offsetHeight
        - (args.paddingTop+args.paddingBottom+args.borderTop+args.borderBottom);
    return args;
};

Css.getElementCSSSelector = function(element)
{
    if (!element || !element.localName)
        return "null";

    var label = Xml.getLocalName(element);
    if (element.id)
        label += "#" + element.id;

    if (element.classList && element.classList.length > 0)
        label += "." + element.classList.item(0);

    return label;
};

Css.getElementCSSPath = function(element)
{
    var paths = [];

    for (; element && element.nodeType == Node.ELEMENT_NODE; element = element.parentNode)
    {
        var selector = Css.getElementCSSSelector(element);
        paths.splice(0, 0, selector);
    }

    return paths.length ? paths.join(" ") : null;
};

// ********************************************************************************************* //
// CSS classes

var classNameReCache={};

Css.hasClass = function(node, name)
{
    if (!node || node.nodeType != Node.ELEMENT_NODE || !node.className || !name)
        return false;

    if (name.indexOf(" ") != -1)
    {
        var classes = name.split(" "), len = classes.length, found=false;
        for (var i = 0; i < len; i++)
        {
            var cls = classes[i].trim();
            if (cls != "")
            {
                if (Css.hasClass(node, cls) == false)
                    return false;
                found = true;
            }
        }
        return found;
    }

    var re;
    if (name.indexOf("-") == -1)
        re = classNameReCache[name] = classNameReCache[name] || new RegExp('(^|\\s)' + name + '(\\s|$)', "g");
    else // XXXsroussey don't cache these, they are often setting values. Should be using setUserData/getUserData???
        re = new RegExp('(^|\\s)' + name + '(\\s|$)', "g");
    return node.className.search(re) != -1;
};

Css.setClass = function(node, name)
{
    if (!node || node.nodeType != Node.ELEMENT_NODE || name == '')
        return;

    if (name.indexOf(" ") != -1)
    {
        var classes = name.split(" "), len = classes.length;
        for (var i = 0; i < len; i++)
        {
            var cls = classes[i].trim();
            if (cls != "")
            {
                Css.setClass(node, cls);
            }
        }
        return;
    }
    if (!Css.hasClass(node, name))
        node.className = node.className.trim() + " " + name;
};

Css.getClassValue = function(node, name)
{
    var re = new RegExp(name+"-([^ ]+)");
    var m = re.exec(node.className);
    return m ? m[1] : "";
};

Css.removeClass = function(node, name)
{
    if (!node || node.nodeType != Node.ELEMENT_NODE || node.className == '' || name == '')
        return;

    if (name.indexOf(" ") != -1)
    {
        var classes = name.split(" "), len = classes.length;
        for (var i = 0; i < len; i++)
        {
            var cls = classes[i].trim();
            if (cls != "")
            {
                if (Css.hasClass(node, cls) == false)
                    Css.removeClass(node, cls);
            }
        }
        return;
    }

    var re;
    if (name.indexOf("-") == -1)
        re = classNameReCache[name] = classNameReCache[name] || new RegExp('(^|\\s)' + name + '(\\s|$)', "g");
    else // XXXsroussey don't cache these, they are often setting values. Should be using setUserData/getUserData???
        re = new RegExp('(^|\\s)' + name + '(\\s|$)', "g");

    node.className = node.className.replace(re, " ");

};

Css.toggleClass = function(elt, name)
{
    if (Css.hasClass(elt, name))
        Css.removeClass(elt, name);
    else
        Css.setClass(elt, name);
};

Css.obscure = function(elt, obscured)
{
    if (obscured)
        Css.setClass(elt, "obscured");
    else
        Css.removeClass(elt, "obscured");
};

Css.setClassTimed = function(elt, name, context, timeout)
{
    if (FBTrace.DBG_HTML || FBTrace.DBG_SOURCEFILES)
    {
        FBTrace.sysout("css.setClassTimed elt.__setClassTimeout: "+elt.__setClassTimeout+
                " Xml.isVisible(elt): "+Xml.isVisible(elt)+
                " elt.__invisibleAtSetPoint: "+elt.__invisibleAtSetPoint);
    }

    if (!timeout)
        timeout = 1300;

    if (elt.__setClassTimeout)  // then we are already waiting to remove the class mark
        context.clearTimeout(elt.__setClassTimeout);  // reset the timer
    else                        // then we are not waiting to remove the mark
        Css.setClass(elt, name);

    if (!Xml.isVisible(elt))
    {
        if (elt.__invisibleAtSetPoint)
            elt.__invisibleAtSetPoint--;
        else
            elt.__invisibleAtSetPoint = 5;
    }
    else
    {
        delete elt.__invisibleAtSetPoint;
    }

    elt.__setClassTimeout = context.setTimeout(function()
    {
        delete elt.__setClassTimeout;

        if (elt.__invisibleAtSetPoint)  // then user can't see it, try again later
            Css.setClassTimed(elt, name, context, timeout);
        else
        {
            delete elt.__invisibleAtSetPoint;  // may be zero
            Css.removeClass(elt, name);
        }
    }, timeout);
};

Css.cancelClassTimed = function(elt, name, context)
{
    if (elt.__setClassTimeout)
    {
        Css.removeClass(elt, name);
        context.clearTimeout(elt.__setClassTimeout);
        delete elt.__setClassTimeout;
    }
};

Css.safeGetCSSRules = function(styleSheet)
{
    try
    {
        return styleSheet.cssRules;
    }
    catch (e)
    {
        if (FBTrace.DBG_ERRORS)
            FBTrace.sysout("safeGetCSSRules "+e, e);
    }

    return null;
};

Css.isValidStylesheet = function(styleSheet)
{
    try
    {
        var dummy = styleSheet.cssRules; // Mozilla throws
        return true;
    }
    catch (e)
    {
        if (FBTrace.DBG_ERRORS)
            FBTrace.sysout("isValidStylesheet "+e, e);
    }

    return false;
};

// ********************************************************************************************* //
// Stylesheet API

Css.shouldIgnoreSheet = function(sheet)
{
    // Ignore by the regular method, except for default stylesheets that are
    // used in case there is no other stylesheet.
    if (sheet.defaultStylesheet)
        return false;
    return (sheet.ownerNode && Firebug.shouldIgnore(sheet.ownerNode));
};

Css.createStyleSheet = function(doc, url)
{
    var style = doc.createElementNS("http://www.w3.org/1999/xhtml", "style");
    style.setAttribute("charset", "utf-8");
    style.setAttribute("type", "text/css");

    var cssText = url ? Http.getResource(url) : null;
    if (cssText)
    {
        var index = url.lastIndexOf("/");
        var absURL = url.substr(0, index+1);

        // Replace all relative URLs with absolute (using the passed url).
        // Note that stylesheets can come from various extensions and the source can
        // be even used in a browser env where relative URLs make more sense.
        var expr = /url\(([\'"]?)(?![\'"]?(?:[a-z]+:|\/))/gi;
        cssText = cssText.replace(expr, "url($1" + absURL);

        style.textContent = cssText;
    }

    Firebug.setIgnored(style);
    return style;
};

Css.addStyleSheet = function(doc, style)
{
    var heads = doc.getElementsByTagName("head");
    if (heads.length)
    {
        heads[0].appendChild(style);
    }
    else if (doc.documentElement)
    {
        doc.documentElement.appendChild(style);
    }
    else
    {
        if (FBTrace.DBG_ERRORS)
            FBTrace.sysout("css.addStyleSheet; ERROR to append a stylesheet");
    }
};

Css.appendStylesheet = function(doc, uri)
{
    // Make sure the stylesheet is not appended twice.
    var styleSheet = doc.getElementById(uri);
    if (styleSheet)
        return styleSheet;

    var styleSheet = Css.createStyleSheet(doc, uri);
    styleSheet.setAttribute("id", uri);
    Css.addStyleSheet(doc, styleSheet);

    return styleSheet;
},

Css.getStyleSheetByHref = function(url, context)
{
    if (!context.styleSheetMap)
        Css.createStyleSheetMap(context);  // fill cache

    // hasOwnProperty is called to prevent possible conflicts with prototype extensions
    // and strict mode warnings
    return context.styleSheetMap.hasOwnProperty(url) ? context.styleSheetMap[url] : undefined;
};

Css.createStyleSheetMap = function(context)
{
    context.styleSheetMap = {};

    function addSheet(sheet)
    {
        var sheetURL = Css.getURLForStyleSheet(sheet);
        context.styleSheetMap[sheetURL] = sheet;

        // recurse for imported sheets

        for (var i = 0; i < sheet.cssRules.length; ++i)
        {
            var rule = sheet.cssRules[i];
            if (rule instanceof CSSStyleRule)
            {
                if (rule.type == CSSRule.STYLE_RULE)  // once we get here no more imports
                    return;
            }
            else if (rule instanceof CSSImportRule)
            {
                addSheet(rule.styleSheet);
            }
        }
    }

    Win.iterateWindows(context.window, function(subwin)
    {
        var rootSheets = subwin.document.styleSheets;
        if (!rootSheets)
            return; // XUL?

        for (var i = 0; i < rootSheets.length; ++i)
        {
            try
            {
                addSheet(rootSheets[i]);
            }
            catch (err)
            {
                //if (FBTrace.DBG_ERRORS)
                //    FBTrace.sysout("css.createStyleSheetMap; EXCEPTION " + err, err);
            }
        }
    });

    if (FBTrace.DBG_ERRORS && FBTrace.DBG_CSS)
        FBTrace.sysout("css.createStyleSheetMap for "+context.getName(), context.styleSheetMap);

    return context.styleSheetMap;
};

Css.getAllStyleSheets = function(context)
{
    if (!context)
        return [];

    var styleSheets = [];

    var showUACSS = Options.get("showUserAgentCSS");
    function addSheet(sheet)
    {
        var sheetLocation =  Css.getURLForStyleSheet(sheet);

        if (!showUACSS && Url.isSystemURL(sheetLocation))
            return;

        if (Css.shouldIgnoreSheet(sheet))
            return;

        styleSheets.push(sheet);

        try
        {
            for (var i = 0; i < sheet.cssRules.length; ++i)
            {
                var rule = sheet.cssRules[i];
                if (rule instanceof window.CSSImportRule)
                    addSheet(rule.styleSheet);
            }
        }
        catch(e)
        {
            if (FBTrace.DBG_ERRORS)
                FBTrace.sysout("getAllStyleSheets sheet.cssRules FAILS for "+
                    (sheet?sheet.href:"null sheet")+e, e);
        }
    }

    Win.iterateWindows(context.window, function(subwin)
    {
        var rootSheets = subwin.document.styleSheets;
        for (var i = 0; i < rootSheets.length; ++i)
            addSheet(rootSheets[i]);
    });

    return styleSheets;
};

Css.getURLForStyleSheet = function(styleSheet)
{
    // http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html#StyleSheets-StyleSheet.
    // For inline style sheets, the value of this attribute is null.
    if (!styleSheet.href && !styleSheet.ownerNode)
        return null;

    return (styleSheet.href ? styleSheet.href : styleSheet.ownerNode.ownerDocument.documentURI);
};

/**
 * Retrieves the instance number for a given style sheet. The instance number
 * is sheet's index within the set of all other sheets whose URL is the same.
 */
Css.getInstanceForStyleSheet = function(styleSheet, ownerDocument)
{
    // ownerDocument is an optional hint for performance
    if (FBTrace.DBG_CSS)
    {
        FBTrace.sysout("getInstanceForStyleSheet href:" + styleSheet.href + " mediaText:" +
            styleSheet.media.mediaText + " path to ownerNode" +
            (styleSheet.ownerNode && Xpath.getElementXPath(styleSheet.ownerNode)), ownerDocument);
    }

    ownerDocument = ownerDocument || Css.getDocumentForStyleSheet(styleSheet);
    if (!ownerDocument)
        return;

    var ret = 0,
        styleSheets = ownerDocument.styleSheets,
        href = styleSheet.href;

    for (var i = 0; i < styleSheets.length; i++)
    {
        var curSheet = styleSheets[i];

        if (FBTrace.DBG_CSS)
        {
            FBTrace.sysout("getInstanceForStyleSheet: compare href " + i +
                " " + curSheet.href + " " + curSheet.media.mediaText + " " +
                (curSheet.ownerNode && Xpath.getElementXPath(curSheet.ownerNode)));
        }

        if (Css.shouldIgnoreSheet(curSheet))
            break;

        if (curSheet == styleSheet)
            break;

        if (curSheet.href == href)
            ret++;
    }
    return ret;
};

Css.getDocumentForStyleSheet = function(styleSheet)
{
    if (!styleSheet)
        return;

    while (styleSheet.parentStyleSheet && !styleSheet.ownerNode)
        styleSheet = styleSheet.parentStyleSheet;

    if (styleSheet.ownerNode)
        return styleSheet.ownerNode.ownerDocument;
};

// ********************************************************************************************* //

Css.stripUnits = function(value)
{
    // remove units from '0px', '0em' etc. leave non-zero units in-tact.
    return value.replace(/(url\(.*?\)|[^0]\S*\s*)|0(%|em|ex|px|in|cm|mm|pt|pc)(\s|$)/gi,
        function(_, skip, remove, whitespace)
        {
            return skip || ('0' + whitespace);
        }
    );
};

Css.extractURLs = function(value)
{
    var urls = [];
    var urlValues = value.match(/url\((["']).*?\1\)/g);

    for (var i in urlValues)
        urls.push(urlValues[i].replace(/url\((["'])(.*?)\1\)/, "$2"));

    return urls;
};

Css.colorNameToRGB = function(value)
{
    if (!domUtils.colorNameToRGB)
        return value;

    if (value === "transparent")
        return "rgba(0, 0, 0, 0)";

    try
    {
        var rgbValue = domUtils.colorNameToRGB(value);
        return "rgb(" + rgbValue.r + ", " + rgbValue.g + ", " + rgbValue.b + ")";
    }
    catch(e)
    {
        return value;
    }
};

Css.rgbToHex = function(value)
{<commit_msg>Issue 6730 (Color display setting doesn't affect color names in shorthands) http://code.google.com/p/fbug/issues/detail?id=6730<commit_after>/* See license.txt for terms of usage */

define([
    "firebug/lib/trace",
    "firebug/lib/url",
    "firebug/lib/options",
    "firebug/chrome/window",
    "firebug/lib/xml",
    "firebug/lib/http",
    "firebug/lib/xpath"
],
function(FBTrace, Url, Options, Win, Xml, Http, Xpath) {

// ********************************************************************************************* //
// Constants

var Ci = Components.interfaces;
var Cc = Components.classes;

// ********************************************************************************************* //
// Module Implementation

var Css = {};

// ********************************************************************************************* //
// CSS

var cssKeywordMap = {};
var cssPropNames = {};
var cssColorNames = null;
var imageRules = null;
var domUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);

Css.getCSSKeywordsByProperty = function(nodeType, propName, avoid)
{
    if (!cssKeywordMap[nodeType])
    {
        cssKeywordMap[nodeType] = {};

        for (var name in Css.cssInfo[nodeType])
        {
            var list = [];

            var types = Css.cssInfo[nodeType][name];
            for (var i = 0; i < types.length; ++i)
            {
                var keywords = Css.cssKeywords[types[i]];
                if (keywords)
                    list.push.apply(list, keywords);
                else
                    list.push(types[i]);
            }

            cssKeywordMap[nodeType][name] = list;
        }
    }

    propName = propName.toLowerCase();

    if (avoid)
        return getCSSPropertyKeywordsExcludingCategories(nodeType, propName, avoid);

    return cssKeywordMap[nodeType][propName] || [];
};

function getCSSPropertyKeywordsExcludingCategories(nodeType, propName, avoid)
{
    if (!(nodeType in Css.cssInfo) || !(propName in Css.cssInfo[nodeType]))
        return [];

    var list = [];
    var types = Css.cssInfo[nodeType][propName];
    for (var i = 0; i < types.length; ++i)
    {
        var type = types[i];
        if (avoid.indexOf(type) !== -1)
            continue;
        var keywords = Css.cssKeywords[type];
        if (keywords)
            list.push.apply(list, keywords);
        else
            list.push(type);
    }
    return list;
}

Css.getCSSPropertyNames = function(nodeType)
{
    if (!cssPropNames[nodeType])
    {
        cssPropNames[nodeType] = [];

        for (var name in Css.cssInfo[nodeType])
            cssPropNames[nodeType].push(name);
    }

    return cssPropNames[nodeType];
};

Css.getCSSShorthandCategory = function(nodeType, shorthandProp, keyword)
{
    if (!(nodeType in Css.cssInfo) || !(shorthandProp in Css.cssInfo[nodeType]))
        return null;

    var category = null;
    var types = Css.cssInfo[nodeType][shorthandProp];
    for (var i = 0; i < types.length; ++i)
    {
        var type = types[i];
        var keywords = Css.cssKeywords[type];
        if (keywords ? (keywords.indexOf(keyword) !== -1) : (type === keyword))
        {
            // Set this as the matched category, or if there is one already
            // bail out (we don't have a unique one).
            if (category)
                return null;
            category = type;
        }
    }
    return category;
};

/**
 * Parses the CSS properties of a CSSStyleRule
 * @param {Object} style CSSStyleRule to get the properties of
 * @param {Object} element Element to which the style applies. Needed for parsing
 *      shorthand properties correctly.
 *
 * @returns {Array} Properties represented by {name, value, priority, longhandProps}
 */
Css.parseCSSProps = function(style, element)
{
    var props = [];

    if (!element)
    {
        for (var i = 0, len = style.length; i < len; ++i)
        {
            var prop = style.item(i);
            props.push({name: prop,
                value: style.getPropertyValue(prop),
                priority: style.getPropertyPriority(longhandProp)});
        }
    }
    else
    {
        var lineRE = /(?:[^;\(]*(?:\([^\)]*?\))?[^;\(]*)*;?/g;
        var propRE = /\s*([^:\s]*)\s*:\s*(.*?)\s*(?:! (important))?;?$/;
        var lines = style.cssText.match(lineRE);
        for (var i = 0, len = lines.length; i < len; ++i)
        {
            var match = propRE.exec(lines[i]);
            if (!match)
                continue;

            if (match[2])
            {
                var prop = {name: match[1], value: match[2], priority: match[3] || ""};

                // Add longhand properties to shorthand property
                var doc = element.ownerDocument;
                var dummyElement = doc.createElementNS(element.namespaceURI, element.tagName);
                var dummyStyle = dummyElement.style;
                dummyStyle.cssText = "";
                dummyStyle.setProperty(prop.name, prop.value, prop.priority);

                if (dummyStyle.length > 1)
                {
                    prop.longhandProps = [];
                    for (var j = 0, propLen = dummyStyle.length; j < propLen; ++j)
                    {
                        var longhandProp = dummyStyle.item(j);
                        prop.longhandProps.push({name: longhandProp,
                            value: dummyStyle.getPropertyValue(longhandProp),
                            priority: dummyStyle.getPropertyPriority(longhandProp)});
                    }
                }

                props.push(prop);
            }
        }
    }

    return props;
};

Css.isColorKeyword = function(keyword)
{
    if (keyword == "transparent")
        return false;

    if (!cssColorNames)
    {
        cssColorNames = [];

        var colors = Css.cssKeywords["color"];
        for (var i = 0; i < colors.length; ++i)
            cssColorNames.push(colors[i].toLowerCase());
    }

    return cssColorNames.indexOf(keyword.toLowerCase()) != -1;
};

Css.isImageRule = function(nodeType,rule)
{
    if (!imageRules)
    {
        imageRules = [];

        for (var i in Css.cssInfo[nodeType])
        {
            var r = i.toLowerCase();
            var suffix = "image";
            if (r.match(suffix + "$") == suffix || r == "background")
                imageRules.push(r);
        }
    }

    return imageRules.indexOf(rule.toLowerCase()) != -1;
};

Css.copyTextStyles = function(fromNode, toNode, style)
{
    var view = fromNode ? fromNode.ownerDocument.defaultView : null;
    if (view)
    {
        if (!style)
            style = view.getComputedStyle(fromNode, "");

        toNode.style.fontFamily = style.fontFamily;
        toNode.style.fontSize = style.fontSize;
        toNode.style.fontWeight = style.fontWeight;
        toNode.style.fontStyle = style.fontStyle;
        toNode.style.fontSizeAdjust = style.fontSizeAdjust;
        toNode.style.fontStretch = style.fontStretch;
        toNode.style.fontVariant = style.fontVariant;
        toNode.style.MozFontFeatureSettings = style.MozFontFeatureSettings;

        return style;
    }
};

Css.copyBoxStyles = function(fromNode, toNode, style)
{
    var view = fromNode.ownerDocument.defaultView;
    if (view)
    {
        if (!style)
            style = view.getComputedStyle(fromNode, "");

        toNode.style.marginTop = style.marginTop;
        toNode.style.marginRight = style.marginRight;
        toNode.style.marginBottom = style.marginBottom;
        toNode.style.marginLeft = style.marginLeft;
        toNode.style.borderTopWidth = style.borderTopWidth;
        toNode.style.borderRightWidth = style.borderRightWidth;
        toNode.style.borderBottomWidth = style.borderBottomWidth;
        toNode.style.borderLeftWidth = style.borderLeftWidth;
        toNode.style.unicodeBidi = style.unicodeBidi;

        return style;
    }
};

Css.readBoxStyles = function(style)
{
    const styleNames = {
        "margin-top": "marginTop", "margin-right": "marginRight",
        "margin-left": "marginLeft", "margin-bottom": "marginBottom",
        "border-top-width": "borderTop", "border-right-width": "borderRight",
        "border-left-width": "borderLeft", "border-bottom-width": "borderBottom",
        "padding-top": "paddingTop", "padding-right": "paddingRight",
        "padding-left": "paddingLeft", "padding-bottom": "paddingBottom",
        "z-index": "zIndex"
    };

    var styles = {};
    for (var styleName in styleNames)
        styles[styleNames[styleName]] = parseInt(style.getPropertyCSSValue(styleName).cssText) || 0;

    if (FBTrace.DBG_INSPECT)
        FBTrace.sysout("readBoxStyles ", styles);

    return styles;
};

Css.getBoxFromStyles = function(style, element)
{
    var args = Css.readBoxStyles(style);
    args.width = element.offsetWidth
        - (args.paddingLeft+args.paddingRight+args.borderLeft+args.borderRight);
    args.height = element.offsetHeight
        - (args.paddingTop+args.paddingBottom+args.borderTop+args.borderBottom);
    return args;
};

Css.getElementCSSSelector = function(element)
{
    if (!element || !element.localName)
        return "null";

    var label = Xml.getLocalName(element);
    if (element.id)
        label += "#" + element.id;

    if (element.classList && element.classList.length > 0)
        label += "." + element.classList.item(0);

    return label;
};

Css.getElementCSSPath = function(element)
{
    var paths = [];

    for (; element && element.nodeType == Node.ELEMENT_NODE; element = element.parentNode)
    {
        var selector = Css.getElementCSSSelector(element);
        paths.splice(0, 0, selector);
    }

    return paths.length ? paths.join(" ") : null;
};

// ********************************************************************************************* //
// CSS classes

var classNameReCache={};

Css.hasClass = function(node, name)
{
    if (!node || node.nodeType != Node.ELEMENT_NODE || !node.className || !name)
        return false;

    if (name.indexOf(" ") != -1)
    {
        var classes = name.split(" "), len = classes.length, found=false;
        for (var i = 0; i < len; i++)
        {
            var cls = classes[i].trim();
            if (cls != "")
            {
                if (Css.hasClass(node, cls) == false)
                    return false;
                found = true;
            }
        }
        return found;
    }

    var re;
    if (name.indexOf("-") == -1)
        re = classNameReCache[name] = classNameReCache[name] || new RegExp('(^|\\s)' + name + '(\\s|$)', "g");
    else // XXXsroussey don't cache these, they are often setting values. Should be using setUserData/getUserData???
        re = new RegExp('(^|\\s)' + name + '(\\s|$)', "g");
    return node.className.search(re) != -1;
};

Css.setClass = function(node, name)
{
    if (!node || node.nodeType != Node.ELEMENT_NODE || name == '')
        return;

    if (name.indexOf(" ") != -1)
    {
        var classes = name.split(" "), len = classes.length;
        for (var i = 0; i < len; i++)
        {
            var cls = classes[i].trim();
            if (cls != "")
            {
                Css.setClass(node, cls);
            }
        }
        return;
    }
    if (!Css.hasClass(node, name))
        node.className = node.className.trim() + " " + name;
};

Css.getClassValue = function(node, name)
{
    var re = new RegExp(name+"-([^ ]+)");
    var m = re.exec(node.className);
    return m ? m[1] : "";
};

Css.removeClass = function(node, name)
{
    if (!node || node.nodeType != Node.ELEMENT_NODE || node.className == '' || name == '')
        return;

    if (name.indexOf(" ") != -1)
    {
        var classes = name.split(" "), len = classes.length;
        for (var i = 0; i < len; i++)
        {
            var cls = classes[i].trim();
            if (cls != "")
            {
                if (Css.hasClass(node, cls) == false)
                    Css.removeClass(node, cls);
            }
        }
        return;
    }

    var re;
    if (name.indexOf("-") == -1)
        re = classNameReCache[name] = classNameReCache[name] || new RegExp('(^|\\s)' + name + '(\\s|$)', "g");
    else // XXXsroussey don't cache these, they are often setting values. Should be using setUserData/getUserData???
        re = new RegExp('(^|\\s)' + name + '(\\s|$)', "g");

    node.className = node.className.replace(re, " ");

};

Css.toggleClass = function(elt, name)
{
    if (Css.hasClass(elt, name))
        Css.removeClass(elt, name);
    else
        Css.setClass(elt, name);
};

Css.obscure = function(elt, obscured)
{
    if (obscured)
        Css.setClass(elt, "obscured");
    else
        Css.removeClass(elt, "obscured");
};

Css.setClassTimed = function(elt, name, context, timeout)
{
    if (FBTrace.DBG_HTML || FBTrace.DBG_SOURCEFILES)
    {
        FBTrace.sysout("css.setClassTimed elt.__setClassTimeout: "+elt.__setClassTimeout+
                " Xml.isVisible(elt): "+Xml.isVisible(elt)+
                " elt.__invisibleAtSetPoint: "+elt.__invisibleAtSetPoint);
    }

    if (!timeout)
        timeout = 1300;

    if (elt.__setClassTimeout)  // then we are already waiting to remove the class mark
        context.clearTimeout(elt.__setClassTimeout);  // reset the timer
    else                        // then we are not waiting to remove the mark
        Css.setClass(elt, name);

    if (!Xml.isVisible(elt))
    {
        if (elt.__invisibleAtSetPoint)
            elt.__invisibleAtSetPoint--;
        else
            elt.__invisibleAtSetPoint = 5;
    }
    else
    {
        delete elt.__invisibleAtSetPoint;
    }

    elt.__setClassTimeout = context.setTimeout(function()
    {
        delete elt.__setClassTimeout;

        if (elt.__invisibleAtSetPoint)  // then user can't see it, try again later
            Css.setClassTimed(elt, name, context, timeout);
        else
        {
            delete elt.__invisibleAtSetPoint;  // may be zero
            Css.removeClass(elt, name);
        }
    }, timeout);
};

Css.cancelClassTimed = function(elt, name, context)
{
    if (elt.__setClassTimeout)
    {
        Css.removeClass(elt, name);
        context.clearTimeout(elt.__setClassTimeout);
        delete elt.__setClassTimeout;
    }
};

Css.safeGetCSSRules = function(styleSheet)
{
    try
    {
        return styleSheet.cssRules;
    }
    catch (e)
    {
        if (FBTrace.DBG_ERRORS)
            FBTrace.sysout("safeGetCSSRules "+e, e);
    }

    return null;
};

Css.isValidStylesheet = function(styleSheet)
{
    try
    {
        var dummy = styleSheet.cssRules; // Mozilla throws
        return true;
    }
    catch (e)
    {
        if (FBTrace.DBG_ERRORS)
            FBTrace.sysout("isValidStylesheet "+e, e);
    }

    return false;
};

// ********************************************************************************************* //
// Stylesheet API

Css.shouldIgnoreSheet = function(sheet)
{
    // Ignore by the regular method, except for default stylesheets that are
    // used in case there is no other stylesheet.
    if (sheet.defaultStylesheet)
        return false;
    return (sheet.ownerNode && Firebug.shouldIgnore(sheet.ownerNode));
};

Css.createStyleSheet = function(doc, url)
{
    var style = doc.createElementNS("http://www.w3.org/1999/xhtml", "style");
    style.setAttribute("charset", "utf-8");
    style.setAttribute("type", "text/css");

    var cssText = url ? Http.getResource(url) : null;
    if (cssText)
    {
        var index = url.lastIndexOf("/");
        var absURL = url.substr(0, index+1);

        // Replace all relative URLs with absolute (using the passed url).
        // Note that stylesheets can come from various extensions and the source can
        // be even used in a browser env where relative URLs make more sense.
        var expr = /url\(([\'"]?)(?![\'"]?(?:[a-z]+:|\/))/gi;
        cssText = cssText.replace(expr, "url($1" + absURL);

        style.textContent = cssText;
    }

    Firebug.setIgnored(style);
    return style;
};

Css.addStyleSheet = function(doc, style)
{
    var heads = doc.getElementsByTagName("head");
    if (heads.length)
    {
        heads[0].appendChild(style);
    }
    else if (doc.documentElement)
    {
        doc.documentElement.appendChild(style);
    }
    else
    {
        if (FBTrace.DBG_ERRORS)
            FBTrace.sysout("css.addStyleSheet; ERROR to append a stylesheet");
    }
};

Css.appendStylesheet = function(doc, uri)
{
    // Make sure the stylesheet is not appended twice.
    var styleSheet = doc.getElementById(uri);
    if (styleSheet)
        return styleSheet;

    var styleSheet = Css.createStyleSheet(doc, uri);
    styleSheet.setAttribute("id", uri);
    Css.addStyleSheet(doc, styleSheet);

    return styleSheet;
},

Css.getStyleSheetByHref = function(url, context)
{
    if (!context.styleSheetMap)
        Css.createStyleSheetMap(context);  // fill cache

    // hasOwnProperty is called to prevent possible conflicts with prototype extensions
    // and strict mode warnings
    return context.styleSheetMap.hasOwnProperty(url) ? context.styleSheetMap[url] : undefined;
};

Css.createStyleSheetMap = function(context)
{
    context.styleSheetMap = {};

    function addSheet(sheet)
    {
        var sheetURL = Css.getURLForStyleSheet(sheet);
        context.styleSheetMap[sheetURL] = sheet;

        // recurse for imported sheets

        for (var i = 0; i < sheet.cssRules.length; ++i)
        {
            var rule = sheet.cssRules[i];
            if (rule instanceof CSSStyleRule)
            {
                if (rule.type == CSSRule.STYLE_RULE)  // once we get here no more imports
                    return;
            }
            else if (rule instanceof CSSImportRule)
            {
                addSheet(rule.styleSheet);
            }
        }
    }

    Win.iterateWindows(context.window, function(subwin)
    {
        var rootSheets = subwin.document.styleSheets;
        if (!rootSheets)
            return; // XUL?

        for (var i = 0; i < rootSheets.length; ++i)
        {
            try
            {
                addSheet(rootSheets[i]);
            }
            catch (err)
            {
                //if (FBTrace.DBG_ERRORS)
                //    FBTrace.sysout("css.createStyleSheetMap; EXCEPTION " + err, err);
            }
        }
    });

    if (FBTrace.DBG_ERRORS && FBTrace.DBG_CSS)
        FBTrace.sysout("css.createStyleSheetMap for "+context.getName(), context.styleSheetMap);

    return context.styleSheetMap;
};

Css.getAllStyleSheets = function(context)
{
    if (!context)
        return [];

    var styleSheets = [];

    var showUACSS = Options.get("showUserAgentCSS");
    function addSheet(sheet)
    {
        var sheetLocation =  Css.getURLForStyleSheet(sheet);

        if (!showUACSS && Url.isSystemURL(sheetLocation))
            return;

        if (Css.shouldIgnoreSheet(sheet))
            return;

        styleSheets.push(sheet);

        try
        {
            for (var i = 0; i < sheet.cssRules.length; ++i)
            {
                var rule = sheet.cssRules[i];
                if (rule instanceof window.CSSImportRule)
                    addSheet(rule.styleSheet);
            }
        }
        catch(e)
        {
            if (FBTrace.DBG_ERRORS)
                FBTrace.sysout("getAllStyleSheets sheet.cssRules FAILS for "+
                    (sheet?sheet.href:"null sheet")+e, e);
        }
    }

    Win.iterateWindows(context.window, function(subwin)
    {
        var rootSheets = subwin.document.styleSheets;
        for (var i = 0; i < rootSheets.length; ++i)
            addSheet(rootSheets[i]);
    });

    return styleSheets;
};

Css.getURLForStyleSheet = function(styleSheet)
{
    // http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html#StyleSheets-StyleSheet.
    // For inline style sheets, the value of this attribute is null.
    if (!styleSheet.href && !styleSheet.ownerNode)
        return null;

    return (styleSheet.href ? styleSheet.href : styleSheet.ownerNode.ownerDocument.documentURI);
};

/**
 * Retrieves the instance number for a given style sheet. The instance number
 * is sheet's index within the set of all other sheets whose URL is the same.
 */
Css.getInstanceForStyleSheet = function(styleSheet, ownerDocument)
{
    // ownerDocument is an optional hint for performance
    if (FBTrace.DBG_CSS)
    {
        FBTrace.sysout("getInstanceForStyleSheet href:" + styleSheet.href + " mediaText:" +
            styleSheet.media.mediaText + " path to ownerNode" +
            (styleSheet.ownerNode && Xpath.getElementXPath(styleSheet.ownerNode)), ownerDocument);
    }

    ownerDocument = ownerDocument || Css.getDocumentForStyleSheet(styleSheet);
    if (!ownerDocument)
        return;

    var ret = 0,
        styleSheets = ownerDocument.styleSheets,
        href = styleSheet.href;

    for (var i = 0; i < styleSheets.length; i++)
    {
        var curSheet = styleSheets[i];

        if (FBTrace.DBG_CSS)
        {
            FBTrace.sysout("getInstanceForStyleSheet: compare href " + i +
                " " + curSheet.href + " " + curSheet.media.mediaText + " " +
                (curSheet.ownerNode && Xpath.getElementXPath(curSheet.ownerNode)));
        }

        if (Css.shouldIgnoreSheet(curSheet))
            break;

        if (curSheet == styleSheet)
            break;

        if (curSheet.href == href)
            ret++;
    }
    return ret;
};

Css.getDocumentForStyleSheet = function(styleSheet)
{
    if (!styleSheet)
        return;

    while (styleSheet.parentStyleSheet && !styleSheet.ownerNode)
        styleSheet = styleSheet.parentStyleSheet;

    if (styleSheet.ownerNode)
        return styleSheet.ownerNode.ownerDocument;
};

// ********************************************************************************************* //

Css.stripUnits = function(value)
{
    // remove units from '0px', '0em' etc. leave non-zero units in-tact.
    return value.replace(/(url\(.*?\)|[^0]\S*\s*)|0(%|em|ex|px|in|cm|mm|pt|pc)(\s|$)/gi,
        function(_, skip, remove, whitespace)
        {
            return skip || ('0' + whitespace);
        }
    );
};

Css.extractURLs = function(value)
{
    var urls = [];
    var urlValues = value.match(/url\((["']).*?\1\)/g);

    for (var i in urlValues)
        urls.push(urlValues[i].replace(/url\((["'])(.*?)\1\)/, "$2"));

    return urls;
};

Css.colorNameToRGB = function(value)
{
    if (!domUtils.colorNameToRGB)
        return value;

    var reSplit = /(\(|,|\)|\s)\s*/;
    var parts = value.split(reSplit);

    var newValue = "";
    for (var i=0, len=parts.length; i<len; ++i)
    {
        var part = parts[i];
        if (part === "transparent")
            newValue += "rgba(0, 0, 0, 0)";

        try
        {
            var rgbValue = domUtils.colorNameToRGB(part);
            newValue += "rgb(" + rgbValue.r + ", " + rgbValue.g + ", " + rgbValue.b + ")";
        }
        catch(e)
        {
            newValue += part;
        }
    }

    return newValue;
};

Css.rgbToHex = function(value)
{
{'fraction_non_alphanumeric': 0.1569167541098286, 'fraction_numerical': 0.003126093039524309, 'mean_word_length': 2.2135581313663506, 'pattern_counts': {'":': 26, '<': 34, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4134989', 'n_tokens_mistral': 14117, 'n_tokens_neox': 13695, 'n_words': 2934}