blob: 5a7f341254a865a55237055a3bdbdfc9f24cedd5 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2001 Peter Kelly (pmk@post.com)
* (C) 2001 Dirk Mueller (mueller@kde.org)
* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2010, 2012 Apple Inc. All rights reserved.
* Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#include "ElementAttributeData.h"
#include "Attr.h"
#include "CSSParser.h"
#include "CSSStyleSheet.h"
#include "StyledElement.h"
#include "WebCoreMemoryInstrumentation.h"
#include <wtf/MemoryInstrumentationVector.h>
namespace WebCore {
static size_t immutableElementAttributeDataSize(unsigned count)
{
return sizeof(ImmutableElementAttributeData) + sizeof(Attribute) * count;
}
PassRefPtr<ElementAttributeData> ElementAttributeData::createImmutable(const Vector<Attribute>& attributes)
{
void* slot = WTF::fastMalloc(immutableElementAttributeDataSize(attributes.size()));
return adoptRef(new (slot) ImmutableElementAttributeData(attributes));
}
PassRefPtr<ElementAttributeData> ElementAttributeData::create()
{
return adoptRef(new MutableElementAttributeData);
}
ImmutableElementAttributeData::ImmutableElementAttributeData(const Vector<Attribute>& attributes)
: ElementAttributeData(attributes.size())
{
for (unsigned i = 0; i < m_arraySize; ++i)
new (&reinterpret_cast<Attribute*>(&m_attributeArray)[i]) Attribute(attributes[i]);
}
MutableElementAttributeData::MutableElementAttributeData(const ImmutableElementAttributeData& other)
{
const ElementAttributeData& baseOther = static_cast<const ElementAttributeData&>(other);
m_inlineStyleDecl = baseOther.m_inlineStyleDecl;
m_attributeStyle = baseOther.m_attributeStyle;
m_classNames = baseOther.m_classNames;
m_idForStyleResolution = baseOther.m_idForStyleResolution;
// An ImmutableElementAttributeData should never have a mutable inline StylePropertySet attached.
ASSERT(!baseOther.m_inlineStyleDecl || !baseOther.m_inlineStyleDecl->isMutable());
m_attributeVector.reserveCapacity(baseOther.m_arraySize);
for (unsigned i = 0; i < baseOther.m_arraySize; ++i)
m_attributeVector.uncheckedAppend(other.immutableAttributeArray()[i]);
}
ImmutableElementAttributeData::~ImmutableElementAttributeData()
{
for (unsigned i = 0; i < m_arraySize; ++i)
(reinterpret_cast<Attribute*>(&m_attributeArray)[i]).~Attribute();
}
PassRefPtr<ElementAttributeData> ElementAttributeData::makeMutableCopy() const
{
ASSERT(!isMutable());
return adoptRef(new MutableElementAttributeData(static_cast<const ImmutableElementAttributeData&>(*this)));
}
typedef Vector<RefPtr<Attr> > AttrList;
typedef HashMap<Element*, OwnPtr<AttrList> > AttrListMap;
static AttrListMap& attrListMap()
{
DEFINE_STATIC_LOCAL(AttrListMap, map, ());
return map;
}
static AttrList* attrListForElement(Element* element)
{
ASSERT(element);
if (!element->hasAttrList())
return 0;
ASSERT(attrListMap().contains(element));
return attrListMap().get(element);
}
static AttrList* ensureAttrListForElement(Element* element)
{
ASSERT(element);
if (element->hasAttrList()) {
ASSERT(attrListMap().contains(element));
return attrListMap().get(element);
}
ASSERT(!attrListMap().contains(element));
element->setHasAttrList();
AttrListMap::AddResult result = attrListMap().add(element, adoptPtr(new AttrList));
return result.iterator->second.get();
}
static void removeAttrListForElement(Element* element)
{
ASSERT(element);
ASSERT(element->hasAttrList());
ASSERT(attrListMap().contains(element));
attrListMap().remove(element);
element->clearHasAttrList();
}
static Attr* findAttrInList(AttrList* attrList, const QualifiedName& name)
{
for (unsigned i = 0; i < attrList->size(); ++i) {
if (attrList->at(i)->qualifiedName() == name)
return attrList->at(i).get();
}
return 0;
}
PassRefPtr<Attr> ElementAttributeData::attrIfExists(Element* element, const QualifiedName& name) const
{
if (AttrList* attrList = attrListForElement(element))
return findAttrInList(attrList, name);
return 0;
}
PassRefPtr<Attr> ElementAttributeData::ensureAttr(Element* element, const QualifiedName& name) const
{
AttrList* attrList = ensureAttrListForElement(element);
RefPtr<Attr> attr = findAttrInList(attrList, name);
if (!attr) {
attr = Attr::create(element, name);
attrList->append(attr);
}
return attr.release();
}
void ElementAttributeData::setAttr(Element* element, const QualifiedName& name, Attr* attr) const
{
AttrList* attrList = ensureAttrListForElement(element);
if (findAttrInList(attrList, name))
return;
attrList->append(attr);
attr->attachToElement(element);
}
void ElementAttributeData::removeAttr(Element* element, const QualifiedName& name) const
{
AttrList* attrList = attrListForElement(element);
ASSERT(attrList);
for (unsigned i = 0; i < attrList->size(); ++i) {
if (attrList->at(i)->qualifiedName() == name) {
attrList->remove(i);
if (attrList->isEmpty())
removeAttrListForElement(element);
return;
}
}
ASSERT_NOT_REACHED();
}
StylePropertySet* ElementAttributeData::ensureInlineStyle(StyledElement* element)
{
ASSERT(isMutable());
if (!m_inlineStyleDecl) {
ASSERT(element->isStyledElement());
m_inlineStyleDecl = StylePropertySet::create(strictToCSSParserMode(element->isHTMLElement() && !element->document()->inQuirksMode()));
}
return m_inlineStyleDecl.get();
}
StylePropertySet* ElementAttributeData::ensureMutableInlineStyle(StyledElement* element)
{
ASSERT(isMutable());
if (m_inlineStyleDecl && !m_inlineStyleDecl->isMutable()) {
m_inlineStyleDecl = m_inlineStyleDecl->copy();
return m_inlineStyleDecl.get();
}
return ensureInlineStyle(element);
}
void ElementAttributeData::updateInlineStyleAvoidingMutation(StyledElement* element, const String& text) const
{
// We reconstruct the property set instead of mutating if there is no CSSOM wrapper.
// This makes wrapperless property sets immutable and so cacheable.
if (m_inlineStyleDecl && !m_inlineStyleDecl->isMutable())
m_inlineStyleDecl.clear();
if (!m_inlineStyleDecl)
m_inlineStyleDecl = CSSParser::parseInlineStyleDeclaration(text, element);
else
m_inlineStyleDecl->parseDeclaration(text, element->document()->elementSheet()->contents());
}
void ElementAttributeData::detachCSSOMWrapperIfNeeded(StyledElement* element)
{
if (m_inlineStyleDecl)
m_inlineStyleDecl->clearParentElement(element);
}
void ElementAttributeData::destroyInlineStyle(StyledElement* element)
{
detachCSSOMWrapperIfNeeded(element);
m_inlineStyleDecl = 0;
}
void ElementAttributeData::addAttribute(const Attribute& attribute, Element* element, SynchronizationOfLazyAttribute inSynchronizationOfLazyAttribute)
{
ASSERT(isMutable());
if (element && inSynchronizationOfLazyAttribute == NotInSynchronizationOfLazyAttribute)
element->willModifyAttribute(attribute.name(), nullAtom, attribute.value());
mutableAttributeVector().append(attribute);
if (element && inSynchronizationOfLazyAttribute == NotInSynchronizationOfLazyAttribute)
element->didAddAttribute(attribute);
}
void ElementAttributeData::removeAttribute(size_t index, Element* element, SynchronizationOfLazyAttribute inSynchronizationOfLazyAttribute)
{
ASSERT(isMutable());
ASSERT(index < length());
Attribute& attribute = mutableAttributeVector().at(index);
QualifiedName name = attribute.name();
if (element && inSynchronizationOfLazyAttribute == NotInSynchronizationOfLazyAttribute)
element->willRemoveAttribute(name, attribute.value());
if (RefPtr<Attr> attr = attrIfExists(element, name))
attr->detachFromElementWithValue(attribute.value());
mutableAttributeVector().remove(index);
if (element && inSynchronizationOfLazyAttribute == NotInSynchronizationOfLazyAttribute)
element->didRemoveAttribute(name);
}
bool ElementAttributeData::isEquivalent(const ElementAttributeData* other) const
{
if (!other)
return isEmpty();
unsigned len = length();
if (len != other->length())
return false;
for (unsigned i = 0; i < len; i++) {
const Attribute* attribute = attributeItem(i);
const Attribute* otherAttr = other->getAttributeItem(attribute->name());
if (!otherAttr || attribute->value() != otherAttr->value())
return false;
}
return true;
}
void ElementAttributeData::detachAttrObjectsFromElement(Element* element) const
{
ASSERT(element->hasAttrList());
for (unsigned i = 0; i < length(); ++i) {
const Attribute* attribute = attributeItem(i);
if (RefPtr<Attr> attr = attrIfExists(element, attribute->name()))
attr->detachFromElementWithValue(attribute->value());
}
// The loop above should have cleaned out this element's Attr map.
ASSERT(!element->hasAttrList());
}
void ElementAttributeData::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
size_t actualSize = m_isMutable ? sizeof(ElementAttributeData) : immutableElementAttributeDataSize(m_arraySize);
MemoryClassInfo info(memoryObjectInfo, this, WebCoreMemoryTypes::DOM, actualSize);
info.addMember(m_inlineStyleDecl);
info.addMember(m_attributeStyle);
info.addMember(m_classNames);
info.addMember(m_idForStyleResolution);
if (m_isMutable)
info.addMember(mutableAttributeVector());
for (unsigned i = 0, len = length(); i < len; i++)
info.addMember(*attributeItem(i));
}
size_t ElementAttributeData::getAttributeItemIndexSlowCase(const AtomicString& name, bool shouldIgnoreAttributeCase) const
{
// Continue to checking case-insensitively and/or full namespaced names if necessary:
for (unsigned i = 0; i < length(); ++i) {
const Attribute* attribute = attributeItem(i);
if (!attribute->name().hasPrefix()) {
if (shouldIgnoreAttributeCase && equalIgnoringCase(name, attribute->localName()))
return i;
} else {
// FIXME: Would be faster to do this comparison without calling toString, which
// generates a temporary string by concatenation. But this branch is only reached
// if the attribute name has a prefix, which is rare in HTML.
if (equalPossiblyIgnoringCase(name, attribute->name().toString(), shouldIgnoreAttributeCase))
return i;
}
}
return notFound;
}
void ElementAttributeData::cloneDataFrom(const ElementAttributeData& sourceData, const Element& sourceElement, Element& targetElement)
{
// FIXME: Cloned elements could start out with immutable attribute data.
ASSERT(isMutable());
const AtomicString& oldID = targetElement.getIdAttribute();
const AtomicString& newID = sourceElement.getIdAttribute();
if (!oldID.isNull() || !newID.isNull())
targetElement.updateId(oldID, newID);
const AtomicString& oldName = targetElement.getNameAttribute();
const AtomicString& newName = sourceElement.getNameAttribute();
if (!oldName.isNull() || !newName.isNull())
targetElement.updateName(oldName, newName);
clearAttributes(&targetElement);
if (sourceData.isMutable())
mutableAttributeVector() = sourceData.mutableAttributeVector();
else {
mutableAttributeVector().reserveInitialCapacity(sourceData.m_arraySize);
for (unsigned i = 0; i < sourceData.m_arraySize; ++i)
mutableAttributeVector().uncheckedAppend(sourceData.immutableAttributeArray()[i]);
}
for (unsigned i = 0; i < length(); ++i) {
const Attribute& attribute = mutableAttributeVector().at(i);
if (targetElement.isStyledElement() && attribute.name() == HTMLNames::styleAttr) {
static_cast<StyledElement&>(targetElement).styleAttributeChanged(attribute.value(), StyledElement::DoNotReparseStyleAttribute);
continue;
}
targetElement.attributeChanged(attribute);
}
if (targetElement.isStyledElement() && sourceData.m_inlineStyleDecl) {
m_inlineStyleDecl = sourceData.m_inlineStyleDecl->immutableCopyIfNeeded();
targetElement.setIsStyleAttributeValid(sourceElement.isStyleAttributeValid());
}
}
void ElementAttributeData::clearAttributes(Element* element)
{
ASSERT(isMutable());
if (element->hasAttrList())
detachAttrObjectsFromElement(element);
clearClass();
mutableAttributeVector().clear();
}
PassRefPtr<Attr> ElementAttributeData::getAttributeNode(const String& name, bool shouldIgnoreAttributeCase, Element* element) const
{
ASSERT(element);
const Attribute* attribute = getAttributeItem(name, shouldIgnoreAttributeCase);
if (!attribute)
return 0;
return ensureAttr(element, attribute->name());
}
PassRefPtr<Attr> ElementAttributeData::getAttributeNode(const QualifiedName& name, Element* element) const
{
ASSERT(element);
const Attribute* attribute = getAttributeItem(name);
if (!attribute)
return 0;
return ensureAttr(element, attribute->name());
}
}