<commit_before>/*
 * Copyright 2010-2012 Esrille 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
 *
 *     http://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.
 */

#include "ViewCSSImp.h"

#include <org/w3c/dom/Text.h>
#include <org/w3c/dom/Comment.h>
#include <org/w3c/dom/events/MutationEvent.h>
#include <org/w3c/dom/html/HTMLDivElement.h>
#include <org/w3c/dom/html/HTMLInputElement.h>
#include <org/w3c/dom/html/HTMLLinkElement.h>
#include <org/w3c/dom/html/HTMLStyleElement.h>

#include <new>
#include <boost/bind.hpp>

#include "CSSImportRuleImp.h"
#include "CSSMediaRuleImp.h"
#include "CSSStyleRuleImp.h"
#include "CSSStyleDeclarationImp.h"
#include "CSSStyleSheetImp.h"
#include "DocumentImp.h"
#include "MediaListImp.h"
#include "html/HTMLElementImp.h"
#include "html/HTMLTemplateElementImp.h"

#include "Box.h"
#include "Table.h"
#include "StackingContext.h"

#include "Test.util.h"

namespace org { namespace w3c { namespace dom { namespace bootstrap {

namespace {

Block* getCurrentBox(CSSStyleDeclarationImp* style, bool asTablePart)
{
    Box* box = style->getBox();
    if (!box)
        return 0;
    if (!style->display.isTableParts() || asTablePart)
        return dynamic_cast<Block*>(box);

    // return the surrounding anonymous table wrapper box
    while (box && !dynamic_cast<TableWrapperBox*>(box))
        box = box->getParentBox();
    assert(box);
    return dynamic_cast<TableWrapperBox*>(box);
}

}

ViewCSSImp::ViewCSSImp(DocumentWindowPtr window, css::CSSStyleSheet defaultStyleSheet, css::CSSStyleSheet userStyleSheet) :
    window(window),
    dpi(96),
    zoom(1.0f),
    mutationListener(boost::bind(&ViewCSSImp::handleMutation, this, _1)),
    defaultStyleSheet(defaultStyleSheet),
    userStyleSheet(userStyleSheet),
    overflow(CSSOverflowValueImp::Auto),
    stackingContexts(0),
    hovered(0),
    quotingDepth(0),
    scrollWidth(0.0f),
    scrollHeight(0.0f),
    hoveredBox(0),
    last(0),
    delay(0)
{
    setMediumFontSize(16);
    getDocument().addEventListener(u"DOMAttrModified", &mutationListener);
    getDocument().addEventListener(u"DOMCharacterDataModified", &mutationListener);
    getDocument().addEventListener(u"DOMNodeInserted", &mutationListener);
    getDocument().addEventListener(u"DOMNodeRemoved", &mutationListener);
}

ViewCSSImp::~ViewCSSImp()
{
    getDocument().removeEventListener(u"DOMAttrModified", &mutationListener);
    getDocument().removeEventListener(u"DOMCharacterDataModified", &mutationListener);
    getDocument().removeEventListener(u"DOMNodeInserted", &mutationListener);
    getDocument().removeEventListener(u"DOMNodeRemoved", &mutationListener);
}

Box* ViewCSSImp::boxFromPoint(int x, int y)
{
    if (!boxTree)
        return 0;
    x += window->getScrollX();
    y += window->getScrollY();
    if (Box* target = boxTree.get()->boxFromPoint(x, y))
        return target;
    return boxTree.get();
}

bool ViewCSSImp::isHovered(Node node)
{
    // TODO: Check if we need to process forefront node only or not.
    for (Node i = hovered; i; i = i.getParentNode()) {
        if (node == i)
            return true;
    }
    return false;
}

void ViewCSSImp::removeComputedStyle(Element element)
{
    if (CSSStyleDeclarationImp* style = getStyle(element)) {
        style->revert(element);
        map.erase(element);
    }
}

void ViewCSSImp::handleMutation(events::Event event)
{
    if (!boxTree)
        return;

    events::MutationEvent mutation(interface_cast<events::MutationEvent>(event));
    if (mutation.getType() == u"DOMCharacterDataModified") {
        Node parentNode = interface_cast<Node>(mutation.getRelatedNode());
        if (Element::hasInstance(parentNode)) {
            Element element(interface_cast<Element>(parentNode));
            if (CSSStyleDeclarationImp* style = getStyle(element))
                style->updateInlines(element);
        }
        return;
    } else if (mutation.getType() == u"DOMNodeInserted") {
        Node parentNode = interface_cast<Node>(mutation.getRelatedNode());
        if (!Element::hasInstance(parentNode))
            return;
        Node target = interface_cast<Node>(event.getTarget());
        if (Element::hasInstance(target))
            setFlags(Box::NEED_SELECTOR_MATCHING);
        else if (Element::hasInstance(parentNode)) {
            Element element(interface_cast<Element>(parentNode));
            if (CSSStyleDeclarationImp* style = getStyle(element))
                style->updateInlines(element);
        }
        return;
    } else if (mutation.getType() == u"DOMNodeRemoved") {
        Node parentNode = interface_cast<Node>(mutation.getRelatedNode());
        if (!Element::hasInstance(parentNode))
            return;
        Node target = interface_cast<Node>(event.getTarget());
        if (Element::hasInstance(target)) {
            removeComputedStyle(interface_cast<Element>(target));
            setFlags(Box::NEED_SELECTOR_MATCHING);
        } else if (Element::hasInstance(parentNode)) {
            Element element(interface_cast<Element>(parentNode));
            if (CSSStyleDeclarationImp* style = getStyle(element))
                style->updateInlines(element);
        }
        return;
    } else if (mutation.getType() == u"DOMAttrModified") {
        Node target = interface_cast<Node>(event.getTarget());
        if (Element::hasInstance(target)) {
            if (CSSStyleDeclarationImp* style = getStyle(interface_cast<Element>(target))) {
                style->requestReconstruct(Box::NEED_STYLE_RECALCULATION);
                style->clearFlags(CSSStyleDeclarationImp::Computed);
                if (mutation.getAttrName() != u"style") {
                    // Request a selector re-matching for the element
                    style->setFlags(CSSStyleDeclarationImp::NeedSelectorMatching);
                    setFlags(Box::NEED_SELECTOR_MATCHING);
                }
            }
        }
        return;
    }

    setFlags(Box::NEED_SELECTOR_REMATCHING);
}

void ViewCSSImp::findDeclarations(CSSRuleListImp::RuleSet& set, Element element, css::CSSRuleList list, unsigned importance)
{
    CSSRuleListImp* ruleList = dynamic_cast<CSSRuleListImp*>(list.self());
    if (!ruleList)
        return;
    ruleList->find(set, this, element, importance);
}

void ViewCSSImp::resolveXY(float left, float top)
{
    if (boxTree)
        boxTree->resolveXY(this, left, top, 0);
}

void ViewCSSImp::addStyle(const Element& element, CSSStyleDeclarationImp* style)
{
    assert(element);
    assert(style);
    map[element] = style;
}

void ViewCSSImp::constructComputedStyles()
{
    constructComputedStyle(getDocument(), 0);
    clearFlags(Box::NEED_SELECTOR_MATCHING | Box::NEED_SELECTOR_REMATCHING);  // TODO: Refine
}

Element ViewCSSImp::updateStyleRules(Element element, CSSStyleDeclarationImp* style, CSSStyleDeclarationImp* parentStyle)
{
    CSSStyleDeclarationImp* elementDecl(0);
    html::HTMLElement htmlElement(0);
    if (html::HTMLElement::hasInstance(element)) {
        htmlElement = interface_cast<html::HTMLElement>(element);
        elementDecl = dynamic_cast<CSSStyleDeclarationImp*>(htmlElement.getStyle().self());
    }

    if (CSSStyleSheetImp* sheet = dynamic_cast<CSSStyleSheetImp*>(defaultStyleSheet.self()))
        findDeclarations(style->ruleSet, element, sheet->getCssRules(), CSSRuleListImp::UserAgent);
    if (CSSStyleSheetImp* sheet = dynamic_cast<CSSStyleSheetImp*>(userStyleSheet.self()))
        findDeclarations(style->ruleSet, element, sheet->getCssRules(), CSSRuleListImp::User);
    if (elementDecl) {
        CSSStyleDeclarationImp* nonCSS = elementDecl->getPseudoElementStyle(CSSPseudoElementSelector::NonCSS);
        if (nonCSS) {
            // TODO: emplace() seems to be not ready yet with libstdc++.
            CSSRuleListImp::PrioritizedRule rule(CSSRuleListImp::User, nonCSS);
            style->ruleSet.insert(rule);
        }
    }
    unsigned importance = CSSRuleListImp::Author;
    stylesheets::StyleSheetList styleSheetList(getDocument().getStyleSheets());
    for (unsigned i = 0; i < styleSheetList.getLength(); ++i) {
        CSSStyleSheetImp* sheet = dynamic_cast<CSSStyleSheetImp*>(styleSheetList.getElement(i).self());
        findDeclarations(style->ruleSet, element, sheet->getCssRules(), importance++);
        // TODO: Check overflow of importance
    }

    style->compute(this, parentStyle, element);
    if (parentStyle && htmlElement && htmlElement.getLocalName() == u"body") {
        parentStyle->bodyStyle = style;
        parentStyle->clearFlags(CSSStyleDeclarationImp::Computed);
    }

    // Set style->affectedBits
    if (!hoverList.empty()) {
        style->affectedBits |= 1u << CSSPseudoClassSelector::Hover;
        for (auto i = hoverList.begin(); i != hoverList.end(); ++i) {
            if (*i != element.self()) {
                Element e(*i);
                if (CSSStyleDeclarationImp* s = getStyle(e))
                    s->affectedBits |= 1u << CSSPseudoClassSelector::Hover;
            }
        }
        hoverList.clear();
    }

    // Expand binding
    html::HTMLTemplateElement shadowTree(0);
    if (style->binding.getValue() != CSSBindingValueImp::None) {
        if (HTMLElementImp* imp = dynamic_cast<HTMLElementImp*>(element.self())) {
            imp->generateShadowContent(style);
            if (shadowTree = imp->getShadowTree()) {
                if (auto imp = dynamic_cast<HTMLTemplateElementImp*>(shadowTree.self()))
                    imp->setHost(element);
            }
        }
    } // TODO: detach the shadow tree from element (if any)

    style->updateInlines(element); // TODO ???

    style->clearFlags(CSSStyleDeclarationImp::Computed);    // TODO: Only styles of children need to be recomputed

    return shadowTree ? shadowTree : element;
}

void ViewCSSImp::constructComputedStyle(Node node, CSSStyleDeclarationImp* parentStyle)
{
    CSSStyleDeclarationImp* style = 0;
    Element element((node.getNodeType() == Node::ELEMENT_NODE) ? interface_cast<Element>(node) : 0);
    if (element) {
        if (map.find(element) != map.end()) {
            style = map[element].get();
            assert(style);
            if (style->getFlags() & CSSStyleDeclarationImp::NeedSelectorMatching) {
                style->clearFlags(CSSStyleDeclarationImp::NeedSelectorMatching);
                CSSStyleDeclarationBoard board(style);
                style->resetComputedStyle();
                node = updateStyleRules(element, style, parentStyle);
                style->restoreComputedValues(board);
            }
        } else {
            style = new(std::nothrow) CSSStyleDeclarationImp;
            if (!style)
                return;  // TODO: error
            addStyle(element, style);
            node = updateStyleRules(element, style, parentStyle);
        }
    }
    for (Node child = node.getFirstChild(); child; child = child.getNextSibling())
        constructComputedStyle(child, style);

    if (!parentStyle && style)
        overflow = style->overflow.getValue();
}

void ViewCSSImp::calculateComputedStyles()
{
    CSSAutoNumberingValueImp::CounterContext counterContext(this);
    for (Node child = getDocument().getFirstChild(); child; child = child.getNextSibling()) {
        if (child.getNodeType() == Node::ELEMENT_NODE)
            calculateComputedStyle(interface_cast<Element>(child), 0, &counterContext, 0);
    }
    clearFlags(Box::NEED_STYLE_RECALCULATION);  // TODO: Refine
    if (3 <= getLogLevel())
        printComputedValues(getDocument(), this);
}

void ViewCSSImp::calculateComputedStyle(Element element, CSSStyleDeclarationImp* parentStyle, CSSAutoNumberingValueImp::CounterContext* counterContext, unsigned flags)
{
    assert(counterContext);

#ifndef NDEBUG
    std::u16string tag(interface_cast<html::HTMLElement>(element).getTagName());
    std::u16string id(interface_cast<html::HTMLElement>(element).getId());
#endif

    CSSStyleDeclarationImp* style = getStyle(element);
    if (!style)
        return;
    if (flags && (!parentStyle || parentStyle->bodyStyle != style))
        style->clearFlags(flags);

    // If the fundamental values such as 'display' are changed, the box(es) associated with the
    // style need to be reverted.
    if (!style->isComputed()) {
        CSSStyleDeclarationBoard board(style);
        style->compute(this, parentStyle, element);
        unsigned comp = board.compare(style);
        if (comp & Box::NEED_EXPANSION) {
            Block* block = style->revert(element);
            if (style->display.isInlineLevel() && block && !(block->getFlags() & Box::NEED_EXPANSION))
                block->clearInlines();
        } else if (comp & Box::NEED_REFLOW) {
            if (Block* block = getCurrentBox(style, true))
                block->setFlags(Box::NEED_REFLOW);
        }
        flags |= CSSStyleDeclarationImp::Computed;  // The child styles have to be recomputed.
    }

    Element shadow = element;
    if (HTMLElementImp* imp = dynamic_cast<HTMLElementImp*>(element.self())) {
        if (imp->getShadowTree())
            shadow = imp->getShadowTree();
    }

    if (!style->display.isNone())
        style->updateCounters(this, counterContext);

    CSSAutoNumberingValueImp::CounterContext cc(this);
    ElementImp* imp(dynamic_cast<ElementImp*>(shadow.self()));
    assert(imp);

    if (!style->display.isNone()) {
        style->marker = updatePseudoElement(style, CSSPseudoElementSelector::Marker, shadow, style->marker, &cc);
        style->before = updatePseudoElement(style, CSSPseudoElementSelector::Before, shadow, style->before, &cc);
    }
    for (Node child = shadow.getFirstChild(); child; child = child.getNextSibling()) {
        if (child.getNodeType() == Node::ELEMENT_NODE)
            calculateComputedStyle(interface_cast<Element>(child), style, &cc, flags);
    }
    if (!style->display.isNone())
        style->after = updatePseudoElement(style, CSSPseudoElementSelector::After, shadow, style->after, &cc);

    style->emptyInline = 0;
    if (style->display.isInline() && !isReplacedElement(shadow)) {
        // Empty inline elements still have margins, padding, borders and a line height. cf. 10.8
        if (!shadow.hasChildNodes() && !style->marker && !style->before && !style->after)
            style->emptyInline = 4;
        else {
            if (style->marker || style->before)
                style->emptyInline = 1;
            else if (style->marginLeft.getPx() || style->borderLeftWidth.getPx() || style->paddingLeft.getPx()) {
                Node child = shadow.getFirstChild();
                if (child.getNodeType() != Node::TEXT_NODE)
                    style->emptyInline = 1;
            }
            if (style->after)
                style->emptyInline |= 2;
            else if (style->marginRight.getPx() || style->borderRightWidth.getPx() || style->paddingRight.getPx()) {
                Node child = shadow.getLastChild();
                if (child.getNodeType() != Node::TEXT_NODE)
                    style->emptyInline |= 2;
            }
        }
    }
}

Element ViewCSSImp::updatePseudoElement(CSSStyleDeclarationImp* style, int id, Element element, Element pseudoElement, CSSAutoNumberingValueImp::CounterContext* counterContext)
{
    assert(!style->display.isNone());
    CSSStyleDeclarationImp* pseudoStyle = style->getPseudoElementStyle(id);
    if (!pseudoStyle)
        return 0;
    pseudoStyle->compute(this, style, element);
    if (pseudoStyle->display.isNone() || pseudoStyle->content.isNone())
        return 0;

    pseudoStyle->updateCounters(this, counterContext);
<commit_msg>(ViewCSSImp::calculateComputedStyle) : Fix bugs around processing pseudo elements when display value is none (ViewCSSImp::updatePseudoElement) : Refine<commit_after>/*
 * Copyright 2010-2012 Esrille 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
 *
 *     http://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.
 */

#include "ViewCSSImp.h"

#include <org/w3c/dom/Text.h>
#include <org/w3c/dom/Comment.h>
#include <org/w3c/dom/events/MutationEvent.h>
#include <org/w3c/dom/html/HTMLDivElement.h>
#include <org/w3c/dom/html/HTMLInputElement.h>
#include <org/w3c/dom/html/HTMLLinkElement.h>
#include <org/w3c/dom/html/HTMLStyleElement.h>

#include <new>
#include <boost/bind.hpp>

#include "CSSImportRuleImp.h"
#include "CSSMediaRuleImp.h"
#include "CSSStyleRuleImp.h"
#include "CSSStyleDeclarationImp.h"
#include "CSSStyleSheetImp.h"
#include "DocumentImp.h"
#include "MediaListImp.h"
#include "html/HTMLElementImp.h"
#include "html/HTMLTemplateElementImp.h"

#include "Box.h"
#include "Table.h"
#include "StackingContext.h"

#include "Test.util.h"

namespace org { namespace w3c { namespace dom { namespace bootstrap {

namespace {

Block* getCurrentBox(CSSStyleDeclarationImp* style, bool asTablePart)
{
    Box* box = style->getBox();
    if (!box)
        return 0;
    if (!style->display.isTableParts() || asTablePart)
        return dynamic_cast<Block*>(box);

    // return the surrounding anonymous table wrapper box
    while (box && !dynamic_cast<TableWrapperBox*>(box))
        box = box->getParentBox();
    assert(box);
    return dynamic_cast<TableWrapperBox*>(box);
}

}

ViewCSSImp::ViewCSSImp(DocumentWindowPtr window, css::CSSStyleSheet defaultStyleSheet, css::CSSStyleSheet userStyleSheet) :
    window(window),
    dpi(96),
    zoom(1.0f),
    mutationListener(boost::bind(&ViewCSSImp::handleMutation, this, _1)),
    defaultStyleSheet(defaultStyleSheet),
    userStyleSheet(userStyleSheet),
    overflow(CSSOverflowValueImp::Auto),
    stackingContexts(0),
    hovered(0),
    quotingDepth(0),
    scrollWidth(0.0f),
    scrollHeight(0.0f),
    hoveredBox(0),
    last(0),
    delay(0)
{
    setMediumFontSize(16);
    getDocument().addEventListener(u"DOMAttrModified", &mutationListener);
    getDocument().addEventListener(u"DOMCharacterDataModified", &mutationListener);
    getDocument().addEventListener(u"DOMNodeInserted", &mutationListener);
    getDocument().addEventListener(u"DOMNodeRemoved", &mutationListener);
}

ViewCSSImp::~ViewCSSImp()
{
    getDocument().removeEventListener(u"DOMAttrModified", &mutationListener);
    getDocument().removeEventListener(u"DOMCharacterDataModified", &mutationListener);
    getDocument().removeEventListener(u"DOMNodeInserted", &mutationListener);
    getDocument().removeEventListener(u"DOMNodeRemoved", &mutationListener);
}

Box* ViewCSSImp::boxFromPoint(int x, int y)
{
    if (!boxTree)
        return 0;
    x += window->getScrollX();
    y += window->getScrollY();
    if (Box* target = boxTree.get()->boxFromPoint(x, y))
        return target;
    return boxTree.get();
}

bool ViewCSSImp::isHovered(Node node)
{
    // TODO: Check if we need to process forefront node only or not.
    for (Node i = hovered; i; i = i.getParentNode()) {
        if (node == i)
            return true;
    }
    return false;
}

void ViewCSSImp::removeComputedStyle(Element element)
{
    if (CSSStyleDeclarationImp* style = getStyle(element)) {
        style->revert(element);
        map.erase(element);
    }
}

void ViewCSSImp::handleMutation(events::Event event)
{
    if (!boxTree)
        return;

    events::MutationEvent mutation(interface_cast<events::MutationEvent>(event));
    if (mutation.getType() == u"DOMCharacterDataModified") {
        Node parentNode = interface_cast<Node>(mutation.getRelatedNode());
        if (Element::hasInstance(parentNode)) {
            Element element(interface_cast<Element>(parentNode));
            if (CSSStyleDeclarationImp* style = getStyle(element))
                style->updateInlines(element);
        }
        return;
    } else if (mutation.getType() == u"DOMNodeInserted") {
        Node parentNode = interface_cast<Node>(mutation.getRelatedNode());
        if (!Element::hasInstance(parentNode))
            return;
        Node target = interface_cast<Node>(event.getTarget());
        if (Element::hasInstance(target))
            setFlags(Box::NEED_SELECTOR_MATCHING);
        else if (Element::hasInstance(parentNode)) {
            Element element(interface_cast<Element>(parentNode));
            if (CSSStyleDeclarationImp* style = getStyle(element))
                style->updateInlines(element);
        }
        return;
    } else if (mutation.getType() == u"DOMNodeRemoved") {
        Node parentNode = interface_cast<Node>(mutation.getRelatedNode());
        if (!Element::hasInstance(parentNode))
            return;
        Node target = interface_cast<Node>(event.getTarget());
        if (Element::hasInstance(target)) {
            removeComputedStyle(interface_cast<Element>(target));
            setFlags(Box::NEED_SELECTOR_MATCHING);
        } else if (Element::hasInstance(parentNode)) {
            Element element(interface_cast<Element>(parentNode));
            if (CSSStyleDeclarationImp* style = getStyle(element))
                style->updateInlines(element);
        }
        return;
    } else if (mutation.getType() == u"DOMAttrModified") {
        Node target = interface_cast<Node>(event.getTarget());
        if (Element::hasInstance(target)) {
            if (CSSStyleDeclarationImp* style = getStyle(interface_cast<Element>(target))) {
                style->requestReconstruct(Box::NEED_STYLE_RECALCULATION);
                style->clearFlags(CSSStyleDeclarationImp::Computed);
                if (mutation.getAttrName() != u"style") {
                    // Request a selector re-matching for the element
                    style->setFlags(CSSStyleDeclarationImp::NeedSelectorMatching);
                    setFlags(Box::NEED_SELECTOR_MATCHING);
                }
            }
        }
        return;
    }

    setFlags(Box::NEED_SELECTOR_REMATCHING);
}

void ViewCSSImp::findDeclarations(CSSRuleListImp::RuleSet& set, Element element, css::CSSRuleList list, unsigned importance)
{
    CSSRuleListImp* ruleList = dynamic_cast<CSSRuleListImp*>(list.self());
    if (!ruleList)
        return;
    ruleList->find(set, this, element, importance);
}

void ViewCSSImp::resolveXY(float left, float top)
{
    if (boxTree)
        boxTree->resolveXY(this, left, top, 0);
}

void ViewCSSImp::addStyle(const Element& element, CSSStyleDeclarationImp* style)
{
    assert(element);
    assert(style);
    map[element] = style;
}

void ViewCSSImp::constructComputedStyles()
{
    constructComputedStyle(getDocument(), 0);
    clearFlags(Box::NEED_SELECTOR_MATCHING | Box::NEED_SELECTOR_REMATCHING);  // TODO: Refine
}

Element ViewCSSImp::updateStyleRules(Element element, CSSStyleDeclarationImp* style, CSSStyleDeclarationImp* parentStyle)
{
    CSSStyleDeclarationImp* elementDecl(0);
    html::HTMLElement htmlElement(0);
    if (html::HTMLElement::hasInstance(element)) {
        htmlElement = interface_cast<html::HTMLElement>(element);
        elementDecl = dynamic_cast<CSSStyleDeclarationImp*>(htmlElement.getStyle().self());
    }

    if (CSSStyleSheetImp* sheet = dynamic_cast<CSSStyleSheetImp*>(defaultStyleSheet.self()))
        findDeclarations(style->ruleSet, element, sheet->getCssRules(), CSSRuleListImp::UserAgent);
    if (CSSStyleSheetImp* sheet = dynamic_cast<CSSStyleSheetImp*>(userStyleSheet.self()))
        findDeclarations(style->ruleSet, element, sheet->getCssRules(), CSSRuleListImp::User);
    if (elementDecl) {
        CSSStyleDeclarationImp* nonCSS = elementDecl->getPseudoElementStyle(CSSPseudoElementSelector::NonCSS);
        if (nonCSS) {
            // TODO: emplace() seems to be not ready yet with libstdc++.
            CSSRuleListImp::PrioritizedRule rule(CSSRuleListImp::User, nonCSS);
            style->ruleSet.insert(rule);
        }
    }
    unsigned importance = CSSRuleListImp::Author;
    stylesheets::StyleSheetList styleSheetList(getDocument().getStyleSheets());
    for (unsigned i = 0; i < styleSheetList.getLength(); ++i) {
        CSSStyleSheetImp* sheet = dynamic_cast<CSSStyleSheetImp*>(styleSheetList.getElement(i).self());
        findDeclarations(style->ruleSet, element, sheet->getCssRules(), importance++);
        // TODO: Check overflow of importance
    }

    style->compute(this, parentStyle, element);
    if (parentStyle && htmlElement && htmlElement.getLocalName() == u"body") {
        parentStyle->bodyStyle = style;
        parentStyle->clearFlags(CSSStyleDeclarationImp::Computed);
    }

    // Set style->affectedBits
    if (!hoverList.empty()) {
        style->affectedBits |= 1u << CSSPseudoClassSelector::Hover;
        for (auto i = hoverList.begin(); i != hoverList.end(); ++i) {
            if (*i != element.self()) {
                Element e(*i);
                if (CSSStyleDeclarationImp* s = getStyle(e))
                    s->affectedBits |= 1u << CSSPseudoClassSelector::Hover;
            }
        }
        hoverList.clear();
    }

    // Expand binding
    html::HTMLTemplateElement shadowTree(0);
    if (style->binding.getValue() != CSSBindingValueImp::None) {
        if (HTMLElementImp* imp = dynamic_cast<HTMLElementImp*>(element.self())) {
            imp->generateShadowContent(style);
            if (shadowTree = imp->getShadowTree()) {
                if (auto imp = dynamic_cast<HTMLTemplateElementImp*>(shadowTree.self()))
                    imp->setHost(element);
            }
        }
    } // TODO: detach the shadow tree from element (if any)

    style->updateInlines(element); // TODO ???

    style->clearFlags(CSSStyleDeclarationImp::Computed);    // TODO: Only styles of children need to be recomputed

    return shadowTree ? shadowTree : element;
}

void ViewCSSImp::constructComputedStyle(Node node, CSSStyleDeclarationImp* parentStyle)
{
    CSSStyleDeclarationImp* style = 0;
    Element element((node.getNodeType() == Node::ELEMENT_NODE) ? interface_cast<Element>(node) : 0);
    if (element) {
        if (map.find(element) != map.end()) {
            style = map[element].get();
            assert(style);
            if (style->getFlags() & CSSStyleDeclarationImp::NeedSelectorMatching) {
                style->clearFlags(CSSStyleDeclarationImp::NeedSelectorMatching);
                CSSStyleDeclarationBoard board(style);
                style->resetComputedStyle();
                node = updateStyleRules(element, style, parentStyle);
                style->restoreComputedValues(board);
            }
        } else {
            style = new(std::nothrow) CSSStyleDeclarationImp;
            if (!style)
                return;  // TODO: error
            addStyle(element, style);
            node = updateStyleRules(element, style, parentStyle);
        }
    }
    for (Node child = node.getFirstChild(); child; child = child.getNextSibling())
        constructComputedStyle(child, style);

    if (!parentStyle && style)
        overflow = style->overflow.getValue();
}

void ViewCSSImp::calculateComputedStyles()
{
    CSSAutoNumberingValueImp::CounterContext counterContext(this);
    for (Node child = getDocument().getFirstChild(); child; child = child.getNextSibling()) {
        if (child.getNodeType() == Node::ELEMENT_NODE)
            calculateComputedStyle(interface_cast<Element>(child), 0, &counterContext, 0);
    }
    clearFlags(Box::NEED_STYLE_RECALCULATION);  // TODO: Refine
    if (3 <= getLogLevel())
        printComputedValues(getDocument(), this);
}

void ViewCSSImp::calculateComputedStyle(Element element, CSSStyleDeclarationImp* parentStyle, CSSAutoNumberingValueImp::CounterContext* counterContext, unsigned flags)
{
    assert(counterContext);

#ifndef NDEBUG
    std::u16string tag(interface_cast<html::HTMLElement>(element).getTagName());
    std::u16string id(interface_cast<html::HTMLElement>(element).getId());
#endif

    CSSStyleDeclarationImp* style = getStyle(element);
    if (!style)
        return;
    if (flags && (!parentStyle || parentStyle->bodyStyle != style))
        style->clearFlags(flags);

    // If the fundamental values such as 'display' are changed, the box(es) associated with the
    // style need to be reverted.
    if (!style->isComputed()) {
        CSSStyleDeclarationBoard board(style);
        style->compute(this, parentStyle, element);
        unsigned comp = board.compare(style);
        if (comp & Box::NEED_EXPANSION) {
            Block* block = style->revert(element);
            if (style->display.isInlineLevel() && block && !(block->getFlags() & Box::NEED_EXPANSION))
                block->clearInlines();
        } else if (comp & Box::NEED_REFLOW) {
            if (Block* block = getCurrentBox(style, true))
                block->setFlags(Box::NEED_REFLOW);
        }
        flags |= CSSStyleDeclarationImp::Computed;  // The child styles have to be recomputed.
    }

    Element shadow = element;
    if (HTMLElementImp* imp = dynamic_cast<HTMLElementImp*>(element.self())) {
        if (imp->getShadowTree())
            shadow = imp->getShadowTree();
    }

    if (!style->display.isNone())
        style->updateCounters(this, counterContext);

    CSSAutoNumberingValueImp::CounterContext cc(this);
    ElementImp* imp(dynamic_cast<ElementImp*>(shadow.self()));
    assert(imp);

    style->marker = updatePseudoElement(style, CSSPseudoElementSelector::Marker, shadow, style->marker, &cc);
    style->before = updatePseudoElement(style, CSSPseudoElementSelector::Before, shadow, style->before, &cc);
    for (Node child = shadow.getFirstChild(); child; child = child.getNextSibling()) {
        if (child.getNodeType() == Node::ELEMENT_NODE)
            calculateComputedStyle(interface_cast<Element>(child), style, &cc, flags);
    }
    style->after = updatePseudoElement(style, CSSPseudoElementSelector::After, shadow, style->after, &cc);

    style->emptyInline = 0;
    if (style->display.isInline() && !isReplacedElement(shadow)) {
        // Empty inline elements still have margins, padding, borders and a line height. cf. 10.8
        if (!shadow.hasChildNodes() && !style->marker && !style->before && !style->after)
            style->emptyInline = 4;
        else {
            if (style->marker || style->before)
                style->emptyInline = 1;
            else if (style->marginLeft.getPx() || style->borderLeftWidth.getPx() || style->paddingLeft.getPx()) {
                Node child = shadow.getFirstChild();
                if (child.getNodeType() != Node::TEXT_NODE)
                    style->emptyInline = 1;
            }
            if (style->after)
                style->emptyInline |= 2;
            else if (style->marginRight.getPx() || style->borderRightWidth.getPx() || style->paddingRight.getPx()) {
                Node child = shadow.getLastChild();
                if (child.getNodeType() != Node::TEXT_NODE)
                    style->emptyInline |= 2;
            }
        }
    }
}

Element ViewCSSImp::updatePseudoElement(CSSStyleDeclarationImp* style, int id, Element element, Element pseudoElement, CSSAutoNumberingValueImp::CounterContext* counterContext)
{
    if (style->display.isNone())
        return 0;
    CSSStyleDeclarationImp* pseudoStyle = style->getPseudoElementStyle(id);
    if (!pseudoStyle)
        return 0;
    pseudoStyle->compute(this, style, element);
    if (pseudoStyle->display.isNone() || pseudoStyle->content.isNone())
        return 0;

    pseudoStyle->updateCounters(this, counterContext);

{'fraction_non_alphanumeric': 0.1277568668927116, 'fraction_numerical': 0.004199458736429527, 'mean_word_length': 3.346086251182912, 'pattern_counts': {'":': 0, '<': 93, '<?xml version=': 0, '>': 251, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, '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': '6248329', 'n_tokens_mistral': 9261, 'n_tokens_neox': 9127, 'n_words': 1836}