All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.htmlunit.css.ComputedCssStyleDeclaration Maven / Gradle / Ivy

Go to download

XLT (Xceptance LoadTest) is an extensive load and performance test tool developed and maintained by Xceptance.

There is a newer version: 8.6.0
Show newest version
/*
 * Copyright (c) 2002-2023 Gargoyle Software Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.htmlunit.css;

import static org.htmlunit.BrowserVersionFeatures.CSS_STYLE_PROP_DISCONNECTED_IS_EMPTY;
import static org.htmlunit.css.CssStyleSheet.ABSOLUTE;
import static org.htmlunit.css.CssStyleSheet.AUTO;
import static org.htmlunit.css.CssStyleSheet.FIXED;
import static org.htmlunit.css.CssStyleSheet.NONE;
import static org.htmlunit.css.StyleAttributes.Definition.AZIMUTH;
import static org.htmlunit.css.StyleAttributes.Definition.BORDER_COLLAPSE;
import static org.htmlunit.css.StyleAttributes.Definition.BORDER_SPACING;
import static org.htmlunit.css.StyleAttributes.Definition.CAPTION_SIDE;
import static org.htmlunit.css.StyleAttributes.Definition.COLOR;
import static org.htmlunit.css.StyleAttributes.Definition.CURSOR;
import static org.htmlunit.css.StyleAttributes.Definition.DIRECTION;
import static org.htmlunit.css.StyleAttributes.Definition.DISPLAY;
import static org.htmlunit.css.StyleAttributes.Definition.ELEVATION;
import static org.htmlunit.css.StyleAttributes.Definition.EMPTY_CELLS;
import static org.htmlunit.css.StyleAttributes.Definition.FONT;
import static org.htmlunit.css.StyleAttributes.Definition.FONT_FAMILY;
import static org.htmlunit.css.StyleAttributes.Definition.FONT_SIZE;
import static org.htmlunit.css.StyleAttributes.Definition.FONT_STYLE;
import static org.htmlunit.css.StyleAttributes.Definition.FONT_VARIANT;
import static org.htmlunit.css.StyleAttributes.Definition.FONT_WEIGHT;
import static org.htmlunit.css.StyleAttributes.Definition.LETTER_SPACING;
import static org.htmlunit.css.StyleAttributes.Definition.LINE_HEIGHT;
import static org.htmlunit.css.StyleAttributes.Definition.LIST_STYLE;
import static org.htmlunit.css.StyleAttributes.Definition.LIST_STYLE_IMAGE;
import static org.htmlunit.css.StyleAttributes.Definition.LIST_STYLE_POSITION;
import static org.htmlunit.css.StyleAttributes.Definition.LIST_STYLE_TYPE;
import static org.htmlunit.css.StyleAttributes.Definition.ORPHANS;
import static org.htmlunit.css.StyleAttributes.Definition.PITCH;
import static org.htmlunit.css.StyleAttributes.Definition.PITCH_RANGE;
import static org.htmlunit.css.StyleAttributes.Definition.POSITION;
import static org.htmlunit.css.StyleAttributes.Definition.QUOTES;
import static org.htmlunit.css.StyleAttributes.Definition.RICHNESS;
import static org.htmlunit.css.StyleAttributes.Definition.SPEAK;
import static org.htmlunit.css.StyleAttributes.Definition.SPEAK_HEADER;
import static org.htmlunit.css.StyleAttributes.Definition.SPEAK_NUMERAL;
import static org.htmlunit.css.StyleAttributes.Definition.SPEAK_PUNCTUATION;
import static org.htmlunit.css.StyleAttributes.Definition.SPEECH_RATE;
import static org.htmlunit.css.StyleAttributes.Definition.STRESS;
import static org.htmlunit.css.StyleAttributes.Definition.TEXT_ALIGN;
import static org.htmlunit.css.StyleAttributes.Definition.TEXT_INDENT;
import static org.htmlunit.css.StyleAttributes.Definition.TEXT_TRANSFORM;
import static org.htmlunit.css.StyleAttributes.Definition.VISIBILITY;
import static org.htmlunit.css.StyleAttributes.Definition.VOICE_FAMILY;
import static org.htmlunit.css.StyleAttributes.Definition.VOLUME;
import static org.htmlunit.css.StyleAttributes.Definition.WHITE_SPACE;
import static org.htmlunit.css.StyleAttributes.Definition.WIDOWS;
import static org.htmlunit.css.StyleAttributes.Definition.WIDTH;
import static org.htmlunit.css.StyleAttributes.Definition.WORD_SPACING;

import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.commons.lang3.StringUtils;
import org.htmlunit.cssparser.dom.AbstractCSSRuleImpl;
import org.htmlunit.cssparser.dom.CSSStyleDeclarationImpl;
import org.htmlunit.cssparser.dom.Property;
import org.htmlunit.cssparser.parser.selector.Selector;
import org.htmlunit.cssparser.parser.selector.SelectorSpecificity;

import org.htmlunit.BrowserVersion;
import org.htmlunit.WebWindow;
import org.htmlunit.css.StyleAttributes.Definition;
import org.htmlunit.html.DomElement;
import org.htmlunit.html.DomNode;
import org.htmlunit.html.HtmlBody;
import org.htmlunit.html.HtmlElement;
import org.htmlunit.javascript.host.Element;

/**
 * An object for a CSSStyleDeclaration, which is computed.
 *
 * @see org.htmlunit.javascript.host.Window#getComputedStyle(Object, String)
 *
 * @author Ahmed Ashour
 * @author Marc Guillemot
 * @author Ronald Brill
 * @author Frank Danek
 * @author Alex Gorbatovsky
 * @author cd alexndr
 */
public class ComputedCssStyleDeclaration extends AbstractCssStyleDeclaration {

    /** The set of 'inheritable' definitions. */
    private static final Set INHERITABLE_DEFINITIONS = EnumSet.of(
        AZIMUTH,
        BORDER_COLLAPSE,
        BORDER_SPACING,
        CAPTION_SIDE,
        COLOR,
        CURSOR,
        DIRECTION,
        ELEVATION,
        EMPTY_CELLS,
        FONT_FAMILY,
        FONT_SIZE,
        FONT_STYLE,
        FONT_VARIANT,
        FONT_WEIGHT,
        FONT,
        LETTER_SPACING,
        LINE_HEIGHT,
        LIST_STYLE_IMAGE,
        LIST_STYLE_POSITION,
        LIST_STYLE_TYPE,
        LIST_STYLE,
        ORPHANS,
        PITCH_RANGE,
        PITCH,
        QUOTES,
        RICHNESS,
        SPEAK_HEADER,
        SPEAK_NUMERAL,
        SPEAK_PUNCTUATION,
        SPEAK,
        SPEECH_RATE,
        STRESS,
        TEXT_ALIGN,
        TEXT_INDENT,
        TEXT_TRANSFORM,
        VISIBILITY,
        VOICE_FAMILY,
        VOLUME,
        WHITE_SPACE,
        WIDOWS,
        WORD_SPACING);

    /** Denotes a value which should be returned as is. */
    public static final String EMPTY_FINAL = new String("");

    /** The computed, cached width of the element to which this computed style belongs (no padding, borders, etc.). */
    private Integer width_;

    /**
     * The computed, cached height of the element to which this computed style belongs (no padding, borders, etc.),
     * taking child elements into account.
     */
    private Integer height_;

    /**
     * The computed, cached height of the element to which this computed style belongs (no padding, borders, etc.),
     * not taking child elements into account.
     */
    private Integer height2_;

    /** The computed, cached horizontal padding (left + right) of the element to which this computed style belongs. */
    private Integer paddingHorizontal_;

    /** The computed, cached vertical padding (top + bottom) of the element to which this computed style belongs. */
    private Integer paddingVertical_;

    /** The computed, cached horizontal border (left + right) of the element to which this computed style belongs. */
    private Integer borderHorizontal_;

    /** The computed, cached vertical border (top + bottom) of the element to which this computed style belongs. */
    private Integer borderVertical_;

    /** The computed, cached top of the element to which this computed style belongs. */
    private Integer top_;

    /**
     * Local modifications maintained here rather than in the element. We use a sorted
     * map so that results are deterministic and thus easily testable.
     */
    private final SortedMap localModifications_ = new TreeMap<>();

    /** The wrapped CSSStyleDeclaration */
    private ElementCssStyleDeclaration elementStyleDeclaration_;

    public ComputedCssStyleDeclaration(final ElementCssStyleDeclaration styleDeclaration) {
        elementStyleDeclaration_ = styleDeclaration;
        getDomElementOrNull().setDefaults(this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getStylePriority(final String name) {
        return elementStyleDeclaration_.getStylePriority(name);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getCssText() {
        return elementStyleDeclaration_.getCssText();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getStyleAttribute(final String name) {
        final StyleElement element = getStyleElement(name);
        if (element != null && element.getValue() != null) {
            final String value = element.getValue();
            if (!value.contains("url")) {
                return org.htmlunit.util.StringUtils.toRootLowerCaseWithCache(value);
            }
            return value;
        }
        return "";
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getStyleAttribute(final Definition definition, final boolean getDefaultValueIfEmpty) {
        final BrowserVersion browserVersion = getDomElementOrNull().getPage().getWebClient().getBrowserVersion();
        final boolean feature = browserVersion.hasFeature(CSS_STYLE_PROP_DISCONNECTED_IS_EMPTY);
        final boolean isDefInheritable = INHERITABLE_DEFINITIONS.contains(definition);

        // to make the fuzzer happy the recursion was removed
        final ComputedCssStyleDeclaration[] queue = new ComputedCssStyleDeclaration[] {this};
        String value = null;
        while (queue[0] != null) {
            value = getStyleAttributeWorker(definition, getDefaultValueIfEmpty,
                        browserVersion, feature, isDefInheritable, queue);
        }

        return value;
    }

    private static String getStyleAttributeWorker(final Definition definition,
                final boolean getDefaultValueIfEmpty, final BrowserVersion browserVersion,
                final boolean feature, final boolean isDefInheritable,
                final ComputedCssStyleDeclaration[] queue) {
        final ComputedCssStyleDeclaration decl = queue[0];
        queue[0] = null;

        final DomElement domElem = decl.getDomElementOrNull();
        if (!domElem.isAttachedToPage() && feature) {
            return EMPTY_FINAL;
        }

        String value = decl.getStyleAttribute(definition.getAttributeName());
        if (value.isEmpty()) {
            final DomNode parent = domElem.getParentNode();
            if (isDefInheritable && parent instanceof DomElement) {
                final WebWindow window = domElem.getPage().getEnclosingWindow();

                queue[0] = window.getComputedStyle((DomElement) parent, null);
            }
            else if (getDefaultValueIfEmpty) {
                value = definition.getDefaultComputedValue(browserVersion);
            }
        }

        return value;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setCssText(final String value) {
        // read only
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setStyleAttribute(final String name, final String newValue, final String important) {
        // read only
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String removeStyleAttribute(final String name) {
        // read only
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getLength() {
        return elementStyleDeclaration_.getLength();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object item(final int index) {
        return elementStyleDeclaration_.item(index);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public AbstractCSSRuleImpl getParentRule() {
        return elementStyleDeclaration_.getParentRule();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public StyleElement getStyleElement(final String name) {
        final StyleElement existent = elementStyleDeclaration_.getStyleElement(name);

        if (localModifications_ != null) {
            final StyleElement localStyleMod = localModifications_.get(name);
            if (localStyleMod == null) {
                return existent;
            }

            if (existent == null) {
                // Local modifications represent either default style elements or style elements
                // defined in stylesheets; either way, they shouldn't overwrite any style
                // elements derived directly from the HTML element's "style" attribute.
                return localStyleMod;
            }

            // replace if !IMPORTANT
            if (StyleElement.PRIORITY_IMPORTANT.equals(localStyleMod.getPriority())) {
                if (existent.isImportant()) {
                    if (existent.getSpecificity().compareTo(localStyleMod.getSpecificity()) < 0) {
                        return localStyleMod;
                    }
                }
                else {
                    return localStyleMod;
                }
            }
        }
        return existent;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public StyleElement getStyleElementCaseInSensitive(final String name) {
        return elementStyleDeclaration_.getStyleElementCaseInSensitive(name);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map getStyleMap() {
        return elementStyleDeclaration_.getStyleMap();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Element getElementOrNull() {
        return elementStyleDeclaration_.getElementOrNull();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DomElement getDomElementOrNull() {
        return elementStyleDeclaration_.getDomElementOrNull();
    }

    /**
     * @return the display setting
     */
    public String getDisplay() {
        final DomElement domElem = getDomElementOrNull();
        if (!domElem.isAttachedToPage()) {
            final BrowserVersion browserVersion = domElem.getPage().getWebClient().getBrowserVersion();
            if (browserVersion.hasFeature(CSS_STYLE_PROP_DISCONNECTED_IS_EMPTY)) {
                return "";
            }
        }

        // don't use defaultIfEmpty for performance
        // (no need to calculate the default if not empty)
        final String value = getStyleAttribute(DISPLAY.getAttributeName());
        if (StringUtils.isEmpty(value)) {
            if (domElem instanceof HtmlElement) {
                return ((HtmlElement) domElem).getDefaultStyleDisplay().value();
            }
            return "";
        }
        return value;
    }

    /**
     * @return the width
     */
    public String getWidth() {
        if (NONE.equals(getDisplay())) {
            return AUTO;
        }

        final DomElement domElem = getDomElementOrNull();
        if (!domElem.isAttachedToPage()) {
            final BrowserVersion browserVersion = domElem.getPage().getWebClient().getBrowserVersion();
            if (browserVersion.hasFeature(CSS_STYLE_PROP_DISCONNECTED_IS_EMPTY)) {
                return "";
            }
            if (getStyleAttribute(WIDTH, true).isEmpty()) {
                return AUTO;
            }
        }

        final int windowWidth = domElem.getPage().getEnclosingWindow().getInnerWidth();
        return CssPixelValueConverter.pixelString(domElem, new CssPixelValueConverter.CssValue(0, windowWidth) {
            @Override
            public String get(final ComputedCssStyleDeclaration style) {
                final String value = style.getStyleAttribute(WIDTH, true);
                if (StringUtils.isEmpty(value)) {
                    final String position = getStyleAttribute(POSITION, true);
                    if (ABSOLUTE.equals(position) || FIXED.equals(position)) {
                        final String content = domElem.getVisibleText();
                        // do this only for small content
                        // at least for empty div's this is more correct
                        if (null != content && content.length() < 13) {
                            return (content.length() * 7) + "px";
                        }
                    }

                    int windowDefaultValue = getWindowDefaultValue();
                    if (domElem instanceof HtmlBody) {
                        windowDefaultValue -= 16;
                    }
                    return windowDefaultValue + "px";
                }
                else if (AUTO.equals(value)) {
                    int windowDefaultValue = getWindowDefaultValue();
                    if (domElem instanceof HtmlBody) {
                        windowDefaultValue -= 16;
                    }
                    return windowDefaultValue + "px";
                }

                return value;
            }
        });
    }

    /**
     * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
     * @return the cached width
     */
    public Integer getCachedWidth() {
        return width_;
    }

    /**
     * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
     * @param width the new value
     * @return the param width
     */
    public int setCachedWidth(final int width) {
        width_ = Integer.valueOf(width);
        return width;
    }

    /**
     * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
     * @return the cached height
     */
    public Integer getCachedHeight() {
        return height_;
    }

    /**
     * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
     * @param height the new value
     * @return the param height
     */
    public int setCachedHeight(final int height) {
        height_ = Integer.valueOf(height);
        return height;
    }

    /**
     * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
     * @return the cached height2
     */
    public Integer getCachedHeight2() {
        return height2_;
    }

    /**
     * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
     * @param height the new value
     * @return the param height2
     */
    public int setCachedHeight2(final int height) {
        height2_ = Integer.valueOf(height);
        return height;
    }

    /**
     * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
     * @return the cached top
     */
    public Integer getCachedTop() {
        return top_;
    }

    /**
     * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
     * @param top the new value
     */
    public void setCachedTop(final Integer top) {
        top_ = top;
    }

    /**
     * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
     * @return the cached padding horizontal
     */
    public Integer getCachedPaddingHorizontal() {
        return paddingHorizontal_;
    }

    /**
     * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
     * @param paddingHorizontal the new value
     * @return the param paddingHorizontal
     */
    public int setCachedPaddingHorizontal(final int paddingHorizontal) {
        paddingHorizontal_ = Integer.valueOf(paddingHorizontal);
        return paddingHorizontal;
    }

    /**
     * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
     * @return the cached padding vertical
     */
    public Integer getCachedPaddingVertical() {
        return paddingVertical_;
    }

    /**
     * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
     * @param paddingVertical the new value
     * @return the param paddingVertical
     */
    public int setCachedPaddingVertical(final int paddingVertical) {
        paddingVertical_ = Integer.valueOf(paddingVertical);
        return paddingVertical;
    }

    /**
     * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
     * @return the cached border horizontal
     */
    public Integer getCachedBorderHorizontal() {
        return borderHorizontal_;
    }

    /**
     * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
     * @param borderHorizontal the new value
     * @return the param borderHorizontal
     */
    public int setCachedBorderHorizontal(final int borderHorizontal) {
        borderHorizontal_ = Integer.valueOf(borderHorizontal);
        return borderHorizontal;
    }

    /**
     * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
     * @return the cached border vertical
     */
    public Integer getCachedBorderVertical() {
        return borderVertical_;
    }

    /**
     * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
     * @param borderVertical the new value
     * @return the param borderVertical
     */
    public int setCachedBorderVertical(final int borderVertical) {
        borderVertical_ = Integer.valueOf(borderVertical);
        return borderVertical;
    }

    /**
     * Makes a local, "computed", modification to this CSS style.
     *
     * @param declaration the style declaration
     * @param selector the selector determining that the style applies to this element
     */
    public void applyStyleFromSelector(final CSSStyleDeclarationImpl declaration, final Selector selector) {
        final SelectorSpecificity specificity = selector.getSelectorSpecificity();
        for (final Property prop : declaration.getProperties()) {
            final String name = prop.getName();
            final String value = declaration.getPropertyValue(name);
            final String priority = declaration.getPropertyPriority(name);
            applyLocalStyleAttribute(name, value, priority, specificity);
        }
    }

    private void applyLocalStyleAttribute(final String name, final String newValue, final String priority,
            final SelectorSpecificity specificity) {
        if (!StyleElement.PRIORITY_IMPORTANT.equals(priority)) {
            final StyleElement existingElement = localModifications_.get(name);
            if (existingElement != null) {
                if (existingElement.isImportant()) {
                    return; // can't override a !important rule by a normal rule. Ignore it!
                }
                else if (specificity.compareTo(existingElement.getSpecificity()) < 0) {
                    return; // can't override a rule with a rule having higher specificity
                }
            }
        }
        final StyleElement element = new StyleElement(name, newValue, priority, specificity);
        localModifications_.put(name, element);
    }

    /**
     * Makes a local, "computed", modification to this CSS style that won't override other
     * style attributes of the same name. This method should be used to set default values
     * for style attributes.
     *
     * @param name the name of the style attribute to set
     * @param newValue the value of the style attribute to set
     */
    public void setDefaultLocalStyleAttribute(final String name, final String newValue) {
        final StyleElement element = new StyleElement(name, newValue, "", SelectorSpecificity.DEFAULT_STYLE_ATTRIBUTE);
        localModifications_.put(name, element);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy