mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
This CL is progress towards deleting the concept of an HTMLElement entirely. We won't actually get all the way there in this CL series, but we're getting closer. This CL also will let us make custom elements just be Elements instead of HTMLElements. R=eseidel@chromium.org Review URL: https://codereview.chromium.org/942933003
588 lines
23 KiB
C++
588 lines
23 KiB
C++
/*
|
|
* Copyright (C) 2007, 2008, 2009 Apple Computer, Inc.
|
|
* Copyright (C) 2010, 2011 Google Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "sky/engine/config.h"
|
|
#include "sky/engine/core/editing/EditingStyle.h"
|
|
|
|
#include "gen/sky/core/HTMLNames.h"
|
|
#include "sky/engine/bindings/exception_state_placeholder.h"
|
|
#include "sky/engine/core/css/CSSComputedStyleDeclaration.h"
|
|
#include "sky/engine/core/css/CSSPropertyMetadata.h"
|
|
#include "sky/engine/core/css/CSSValueList.h"
|
|
#include "sky/engine/core/css/CSSValuePool.h"
|
|
#include "sky/engine/core/css/FontSize.h"
|
|
#include "sky/engine/core/css/StylePropertySet.h"
|
|
#include "sky/engine/core/css/StyleRule.h"
|
|
#include "sky/engine/core/css/parser/BisonCSSParser.h"
|
|
#include "sky/engine/core/css/resolver/StyleResolver.h"
|
|
#include "sky/engine/core/dom/Document.h"
|
|
#include "sky/engine/core/dom/Element.h"
|
|
#include "sky/engine/core/dom/Node.h"
|
|
#include "sky/engine/core/dom/NodeTraversal.h"
|
|
#include "sky/engine/core/dom/Position.h"
|
|
#include "sky/engine/core/dom/QualifiedName.h"
|
|
#include "sky/engine/core/editing/Editor.h"
|
|
#include "sky/engine/core/editing/FrameSelection.h"
|
|
#include "sky/engine/core/editing/HTMLInterchange.h"
|
|
#include "sky/engine/core/editing/htmlediting.h"
|
|
#include "sky/engine/core/frame/LocalFrame.h"
|
|
#include "sky/engine/core/rendering/RenderBox.h"
|
|
#include "sky/engine/core/rendering/RenderObject.h"
|
|
#include "sky/engine/core/rendering/style/RenderStyle.h"
|
|
|
|
namespace blink {
|
|
|
|
static const CSSPropertyID& textDecorationPropertyForEditing()
|
|
{
|
|
static const CSSPropertyID property = RuntimeEnabledFeatures::css3TextDecorationsEnabled() ? CSSPropertyTextDecorationLine : CSSPropertyTextDecoration;
|
|
return property;
|
|
}
|
|
|
|
// Editing style properties must be preserved during editing operation.
|
|
// e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph.
|
|
// NOTE: Use either allEditingProperties() or inheritableEditingProperties() to
|
|
// respect runtime enabling of properties.
|
|
static const CSSPropertyID staticEditingProperties[] = {
|
|
CSSPropertyBackgroundColor,
|
|
CSSPropertyColor,
|
|
CSSPropertyFontFamily,
|
|
CSSPropertyFontSize,
|
|
CSSPropertyFontStyle,
|
|
CSSPropertyFontVariant,
|
|
CSSPropertyFontWeight,
|
|
CSSPropertyLetterSpacing,
|
|
CSSPropertyLineHeight,
|
|
CSSPropertyOrphans,
|
|
CSSPropertyTextAlign,
|
|
// FIXME: CSSPropertyTextDecoration needs to be removed when CSS3 Text
|
|
// Decoration feature is no longer experimental.
|
|
CSSPropertyTextDecoration,
|
|
CSSPropertyTextDecorationLine,
|
|
CSSPropertyTextIndent,
|
|
CSSPropertyWhiteSpace,
|
|
CSSPropertyWidows,
|
|
CSSPropertyWordSpacing,
|
|
CSSPropertyWebkitTextDecorationsInEffect,
|
|
CSSPropertyWebkitTextFillColor,
|
|
CSSPropertyWebkitTextStrokeColor,
|
|
CSSPropertyWebkitTextStrokeWidth,
|
|
};
|
|
|
|
enum EditingPropertiesType { OnlyInheritableEditingProperties, AllEditingProperties };
|
|
|
|
static const Vector<CSSPropertyID>& allEditingProperties()
|
|
{
|
|
DEFINE_STATIC_LOCAL(Vector<CSSPropertyID>, properties, ());
|
|
if (properties.isEmpty()) {
|
|
CSSPropertyMetadata::filterEnabledCSSPropertiesIntoVector(staticEditingProperties, WTF_ARRAY_LENGTH(staticEditingProperties), properties);
|
|
if (RuntimeEnabledFeatures::css3TextDecorationsEnabled())
|
|
properties.remove(properties.find(CSSPropertyTextDecoration));
|
|
}
|
|
return properties;
|
|
}
|
|
|
|
static const Vector<CSSPropertyID>& inheritableEditingProperties()
|
|
{
|
|
DEFINE_STATIC_LOCAL(Vector<CSSPropertyID>, properties, ());
|
|
if (properties.isEmpty()) {
|
|
CSSPropertyMetadata::filterEnabledCSSPropertiesIntoVector(staticEditingProperties, WTF_ARRAY_LENGTH(staticEditingProperties), properties);
|
|
for (size_t index = 0; index < properties.size();) {
|
|
if (!CSSPropertyMetadata::isInheritedProperty(properties[index])) {
|
|
properties.remove(index);
|
|
continue;
|
|
}
|
|
++index;
|
|
}
|
|
}
|
|
return properties;
|
|
}
|
|
|
|
template <class StyleDeclarationType>
|
|
static PassRefPtr<MutableStylePropertySet> copyEditingProperties(StyleDeclarationType* style, EditingPropertiesType type = OnlyInheritableEditingProperties)
|
|
{
|
|
if (type == AllEditingProperties)
|
|
return style->copyPropertiesInSet(allEditingProperties());
|
|
return style->copyPropertiesInSet(inheritableEditingProperties());
|
|
}
|
|
|
|
static inline bool isEditingProperty(int id)
|
|
{
|
|
return allEditingProperties().contains(static_cast<CSSPropertyID>(id));
|
|
}
|
|
|
|
static PassRefPtr<MutableStylePropertySet> editingStyleFromComputedStyle(PassRefPtr<CSSComputedStyleDeclaration> style, EditingPropertiesType type = OnlyInheritableEditingProperties)
|
|
{
|
|
if (!style)
|
|
return MutableStylePropertySet::create();
|
|
return copyEditingProperties(style.get(), type);
|
|
}
|
|
|
|
enum LegacyFontSizeMode { AlwaysUseLegacyFontSize, UseLegacyFontSizeOnlyIfPixelValuesMatch };
|
|
|
|
static bool isTransparentColorValue(CSSValue*);
|
|
static bool hasTransparentBackgroundColor(CSSStyleDeclaration*);
|
|
|
|
static PassRefPtr<CSSValue> backgroundColorInEffect(Node*);
|
|
|
|
class HTMLElementEquivalent {
|
|
WTF_MAKE_FAST_ALLOCATED;
|
|
DECLARE_EMPTY_VIRTUAL_DESTRUCTOR_WILL_BE_REMOVED(HTMLElementEquivalent);
|
|
public:
|
|
static PassOwnPtr<HTMLElementEquivalent> create(CSSPropertyID propertyID, CSSValueID primitiveValue, const HTMLQualifiedName& tagName)
|
|
{
|
|
return adoptPtr(new HTMLElementEquivalent(propertyID, primitiveValue, tagName));
|
|
}
|
|
|
|
virtual bool matches(const Element* element) const { return !m_tagName || element->hasTagName(*m_tagName); }
|
|
virtual bool hasAttribute() const { return false; }
|
|
virtual bool propertyExistsInStyle(const StylePropertySet* style) const { return style->getPropertyCSSValue(m_propertyID); }
|
|
virtual bool valueIsPresentInStyle(HTMLElement*, StylePropertySet*) const;
|
|
virtual void addToStyle(Element*, EditingStyle*) const;
|
|
|
|
protected:
|
|
HTMLElementEquivalent(CSSPropertyID);
|
|
HTMLElementEquivalent(CSSPropertyID, const HTMLQualifiedName& tagName);
|
|
HTMLElementEquivalent(CSSPropertyID, CSSValueID primitiveValue, const HTMLQualifiedName& tagName);
|
|
const CSSPropertyID m_propertyID;
|
|
const RefPtr<CSSPrimitiveValue> m_primitiveValue;
|
|
const HTMLQualifiedName* m_tagName; // We can store a pointer because HTML tag names are const global.
|
|
};
|
|
|
|
DEFINE_EMPTY_DESTRUCTOR_WILL_BE_REMOVED(HTMLElementEquivalent);
|
|
|
|
HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id)
|
|
: m_propertyID(id)
|
|
, m_tagName(0)
|
|
{
|
|
}
|
|
|
|
HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, const HTMLQualifiedName& tagName)
|
|
: m_propertyID(id)
|
|
, m_tagName(&tagName)
|
|
{
|
|
}
|
|
|
|
HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, CSSValueID primitiveValue, const HTMLQualifiedName& tagName)
|
|
: m_propertyID(id)
|
|
, m_primitiveValue(CSSPrimitiveValue::createIdentifier(primitiveValue))
|
|
, m_tagName(&tagName)
|
|
{
|
|
ASSERT(primitiveValue != CSSValueInvalid);
|
|
}
|
|
|
|
bool HTMLElementEquivalent::valueIsPresentInStyle(HTMLElement* element, StylePropertySet* style) const
|
|
{
|
|
RefPtr<CSSValue> value = style->getPropertyCSSValue(m_propertyID);
|
|
return matches(element) && value && value->isPrimitiveValue() && toCSSPrimitiveValue(value.get())->getValueID() == m_primitiveValue->getValueID();
|
|
}
|
|
|
|
void HTMLElementEquivalent::addToStyle(Element*, EditingStyle* style) const
|
|
{
|
|
style->setProperty(m_propertyID, m_primitiveValue->cssText());
|
|
}
|
|
|
|
class HTMLTextDecorationEquivalent final : public HTMLElementEquivalent {
|
|
public:
|
|
static PassOwnPtr<HTMLElementEquivalent> create(CSSValueID primitiveValue, const HTMLQualifiedName& tagName)
|
|
{
|
|
return adoptPtr(new HTMLTextDecorationEquivalent(primitiveValue, tagName));
|
|
}
|
|
virtual bool propertyExistsInStyle(const StylePropertySet*) const override;
|
|
virtual bool valueIsPresentInStyle(HTMLElement*, StylePropertySet*) const override;
|
|
|
|
private:
|
|
HTMLTextDecorationEquivalent(CSSValueID primitiveValue, const HTMLQualifiedName& tagName);
|
|
};
|
|
|
|
HTMLTextDecorationEquivalent::HTMLTextDecorationEquivalent(CSSValueID primitiveValue, const HTMLQualifiedName& tagName)
|
|
: HTMLElementEquivalent(textDecorationPropertyForEditing(), primitiveValue, tagName)
|
|
// m_propertyID is used in HTMLElementEquivalent::addToStyle
|
|
{
|
|
}
|
|
|
|
bool HTMLTextDecorationEquivalent::propertyExistsInStyle(const StylePropertySet* style) const
|
|
{
|
|
return style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect)
|
|
|| style->getPropertyCSSValue(textDecorationPropertyForEditing());
|
|
}
|
|
|
|
bool HTMLTextDecorationEquivalent::valueIsPresentInStyle(HTMLElement* element, StylePropertySet* style) const
|
|
{
|
|
RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
|
|
if (!styleValue)
|
|
styleValue = style->getPropertyCSSValue(textDecorationPropertyForEditing());
|
|
return matches(element) && styleValue && styleValue->isValueList() && toCSSValueList(styleValue.get())->hasValue(m_primitiveValue.get());
|
|
}
|
|
|
|
class HTMLAttributeEquivalent : public HTMLElementEquivalent {
|
|
public:
|
|
static PassOwnPtr<HTMLAttributeEquivalent> create(CSSPropertyID propertyID, const HTMLQualifiedName& tagName, const QualifiedName& attrName)
|
|
{
|
|
return adoptPtr(new HTMLAttributeEquivalent(propertyID, tagName, attrName));
|
|
}
|
|
static PassOwnPtr<HTMLAttributeEquivalent> create(CSSPropertyID propertyID, const QualifiedName& attrName)
|
|
{
|
|
return adoptPtr(new HTMLAttributeEquivalent(propertyID, attrName));
|
|
}
|
|
|
|
virtual bool matches(const Element* element) const override { return HTMLElementEquivalent::matches(element) && element->hasAttribute(m_attrName); }
|
|
virtual bool hasAttribute() const override { return true; }
|
|
virtual bool valueIsPresentInStyle(HTMLElement*, StylePropertySet*) const override;
|
|
virtual void addToStyle(Element*, EditingStyle*) const override;
|
|
virtual PassRefPtr<CSSValue> attributeValueAsCSSValue(Element*) const;
|
|
inline const QualifiedName& attributeName() const { return m_attrName; }
|
|
|
|
protected:
|
|
HTMLAttributeEquivalent(CSSPropertyID, const HTMLQualifiedName& tagName, const QualifiedName& attrName);
|
|
HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& attrName);
|
|
const QualifiedName& m_attrName; // We can store a reference because HTML attribute names are const global.
|
|
};
|
|
|
|
HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const HTMLQualifiedName& tagName, const QualifiedName& attrName)
|
|
: HTMLElementEquivalent(id, tagName)
|
|
, m_attrName(attrName)
|
|
{
|
|
}
|
|
|
|
HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const QualifiedName& attrName)
|
|
: HTMLElementEquivalent(id)
|
|
, m_attrName(attrName)
|
|
{
|
|
}
|
|
|
|
bool HTMLAttributeEquivalent::valueIsPresentInStyle(HTMLElement* element, StylePropertySet* style) const
|
|
{
|
|
RefPtr<CSSValue> value = attributeValueAsCSSValue(element);
|
|
RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(m_propertyID);
|
|
|
|
return compareCSSValuePtr(value, styleValue);
|
|
}
|
|
|
|
void HTMLAttributeEquivalent::addToStyle(Element* element, EditingStyle* style) const
|
|
{
|
|
if (RefPtr<CSSValue> value = attributeValueAsCSSValue(element))
|
|
style->setProperty(m_propertyID, value->cssText());
|
|
}
|
|
|
|
PassRefPtr<CSSValue> HTMLAttributeEquivalent::attributeValueAsCSSValue(Element* element) const
|
|
{
|
|
ASSERT(element);
|
|
const AtomicString& value = element->getAttribute(m_attrName);
|
|
if (value.isNull())
|
|
return nullptr;
|
|
|
|
RefPtr<MutableStylePropertySet> dummyStyle = nullptr;
|
|
dummyStyle = MutableStylePropertySet::create();
|
|
dummyStyle->setProperty(m_propertyID, value);
|
|
return dummyStyle->getPropertyCSSValue(m_propertyID);
|
|
}
|
|
|
|
float EditingStyle::NoFontDelta = 0.0f;
|
|
|
|
EditingStyle::EditingStyle()
|
|
: m_fixedPitchFontType(NonFixedPitchFont)
|
|
, m_fontSizeDelta(NoFontDelta)
|
|
{
|
|
}
|
|
|
|
EditingStyle::EditingStyle(ContainerNode* node, PropertiesToInclude propertiesToInclude)
|
|
: m_fixedPitchFontType(NonFixedPitchFont)
|
|
, m_fontSizeDelta(NoFontDelta)
|
|
{
|
|
init(node, propertiesToInclude);
|
|
}
|
|
|
|
EditingStyle::EditingStyle(const Position& position, PropertiesToInclude propertiesToInclude)
|
|
: m_fixedPitchFontType(NonFixedPitchFont)
|
|
, m_fontSizeDelta(NoFontDelta)
|
|
{
|
|
init(position.deprecatedNode(), propertiesToInclude);
|
|
}
|
|
|
|
EditingStyle::EditingStyle(const StylePropertySet* style)
|
|
: m_mutableStyle(style ? style->mutableCopy() : nullptr)
|
|
, m_fixedPitchFontType(NonFixedPitchFont)
|
|
, m_fontSizeDelta(NoFontDelta)
|
|
{
|
|
extractFontSizeDelta();
|
|
}
|
|
|
|
EditingStyle::EditingStyle(CSSPropertyID propertyID, const String& value)
|
|
: m_mutableStyle(nullptr)
|
|
, m_fixedPitchFontType(NonFixedPitchFont)
|
|
, m_fontSizeDelta(NoFontDelta)
|
|
{
|
|
setProperty(propertyID, value);
|
|
}
|
|
|
|
EditingStyle::~EditingStyle()
|
|
{
|
|
}
|
|
|
|
void EditingStyle::init(Node* node, PropertiesToInclude propertiesToInclude)
|
|
{
|
|
RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = CSSComputedStyleDeclaration::create(node);
|
|
m_mutableStyle = propertiesToInclude == AllProperties && computedStyleAtPosition ? computedStyleAtPosition->copyProperties() : editingStyleFromComputedStyle(computedStyleAtPosition);
|
|
|
|
if (propertiesToInclude == EditingPropertiesInEffect) {
|
|
if (RefPtr<CSSValue> value = backgroundColorInEffect(node))
|
|
m_mutableStyle->setProperty(CSSPropertyBackgroundColor, value->cssText());
|
|
if (RefPtr<CSSValue> value = computedStyleAtPosition->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect))
|
|
m_mutableStyle->setProperty(CSSPropertyTextDecoration, value->cssText());
|
|
}
|
|
|
|
if (node && node->computedStyle()) {
|
|
RenderStyle* renderStyle = node->computedStyle();
|
|
removeTextFillAndStrokeColorsIfNeeded(renderStyle);
|
|
replaceFontSizeByKeywordIfPossible(renderStyle, computedStyleAtPosition.get());
|
|
}
|
|
|
|
m_fixedPitchFontType = computedStyleAtPosition->fixedPitchFontType();
|
|
extractFontSizeDelta();
|
|
}
|
|
|
|
void EditingStyle::removeTextFillAndStrokeColorsIfNeeded(RenderStyle* renderStyle)
|
|
{
|
|
// If a node's text fill color is currentColor, then its children use
|
|
// their font-color as their text fill color (they don't
|
|
// inherit it). Likewise for stroke color.
|
|
if (renderStyle->textFillColor().isCurrentColor())
|
|
m_mutableStyle->removeProperty(CSSPropertyWebkitTextFillColor);
|
|
if (renderStyle->textStrokeColor().isCurrentColor())
|
|
m_mutableStyle->removeProperty(CSSPropertyWebkitTextStrokeColor);
|
|
}
|
|
|
|
void EditingStyle::setProperty(CSSPropertyID propertyID, const String& value)
|
|
{
|
|
if (!m_mutableStyle)
|
|
m_mutableStyle = MutableStylePropertySet::create();
|
|
|
|
m_mutableStyle->setProperty(propertyID, value);
|
|
}
|
|
|
|
void EditingStyle::replaceFontSizeByKeywordIfPossible(RenderStyle* renderStyle, CSSComputedStyleDeclaration* computedStyle)
|
|
{
|
|
ASSERT(renderStyle);
|
|
if (renderStyle->fontDescription().keywordSize())
|
|
m_mutableStyle->setProperty(CSSPropertyFontSize, computedStyle->getFontSizeCSSValuePreferringKeyword()->cssText());
|
|
}
|
|
|
|
void EditingStyle::extractFontSizeDelta()
|
|
{
|
|
if (!m_mutableStyle)
|
|
return;
|
|
|
|
if (m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize)) {
|
|
// Explicit font size overrides any delta.
|
|
m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
|
|
return;
|
|
}
|
|
|
|
// Get the adjustment amount out of the style.
|
|
RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta);
|
|
if (!value || !value->isPrimitiveValue())
|
|
return;
|
|
|
|
CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(value.get());
|
|
|
|
// Only PX handled now. If we handle more types in the future, perhaps
|
|
// a switch statement here would be more appropriate.
|
|
if (!primitiveValue->isPx())
|
|
return;
|
|
|
|
m_fontSizeDelta = primitiveValue->getFloatValue();
|
|
m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
|
|
}
|
|
|
|
bool EditingStyle::isEmpty() const
|
|
{
|
|
return (!m_mutableStyle || m_mutableStyle->isEmpty()) && m_fontSizeDelta == NoFontDelta;
|
|
}
|
|
|
|
void EditingStyle::clear()
|
|
{
|
|
m_mutableStyle.clear();
|
|
m_fixedPitchFontType = NonFixedPitchFont;
|
|
m_fontSizeDelta = NoFontDelta;
|
|
}
|
|
|
|
PassRefPtr<EditingStyle> EditingStyle::copy() const
|
|
{
|
|
RefPtr<EditingStyle> copy = EditingStyle::create();
|
|
if (m_mutableStyle)
|
|
copy->m_mutableStyle = m_mutableStyle->mutableCopy();
|
|
copy->m_fixedPitchFontType = m_fixedPitchFontType;
|
|
copy->m_fontSizeDelta = m_fontSizeDelta;
|
|
return copy;
|
|
}
|
|
|
|
void EditingStyle::removeBlockProperties()
|
|
{
|
|
if (!m_mutableStyle)
|
|
return;
|
|
|
|
m_mutableStyle->removeBlockProperties();
|
|
}
|
|
|
|
static const Vector<OwnPtr<HTMLElementEquivalent> >& htmlElementEquivalents()
|
|
{
|
|
DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLElementEquivalent> >, HTMLElementEquivalents, ());
|
|
return HTMLElementEquivalents;
|
|
}
|
|
|
|
static const Vector<OwnPtr<HTMLAttributeEquivalent> >& htmlAttributeEquivalents()
|
|
{
|
|
DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLAttributeEquivalent> >, HTMLAttributeEquivalents, ());
|
|
return HTMLAttributeEquivalents;
|
|
}
|
|
|
|
bool EditingStyle::elementIsStyledSpanOrHTMLEquivalent(const Element* element)
|
|
{
|
|
ASSERT(element);
|
|
bool elementIsSpanOrElementEquivalent = false;
|
|
const Vector<OwnPtr<HTMLElementEquivalent> >& HTMLElementEquivalents = htmlElementEquivalents();
|
|
size_t i;
|
|
for (i = 0; i < HTMLElementEquivalents.size(); ++i) {
|
|
if (HTMLElementEquivalents[i]->matches(element)) {
|
|
elementIsSpanOrElementEquivalent = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
AttributeCollection attributes = element->attributes();
|
|
if (attributes.isEmpty())
|
|
return elementIsSpanOrElementEquivalent; // span, b, etc... without any attributes
|
|
|
|
unsigned matchedAttributes = 0;
|
|
const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
|
|
for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
|
|
if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->attributeName() != HTMLNames::dirAttr)
|
|
matchedAttributes++;
|
|
}
|
|
|
|
if (!elementIsSpanOrElementEquivalent && !matchedAttributes)
|
|
return false; // element is not a span, a html element equivalent, or font element.
|
|
|
|
if (element->getAttribute(HTMLNames::classAttr) == AppleStyleSpanClass)
|
|
matchedAttributes++;
|
|
|
|
if (element->hasAttribute(HTMLNames::styleAttr)) {
|
|
if (const StylePropertySet* style = element->inlineStyle()) {
|
|
unsigned propertyCount = style->propertyCount();
|
|
for (unsigned i = 0; i < propertyCount; ++i) {
|
|
if (!isEditingProperty(style->propertyAt(i).id()))
|
|
return false;
|
|
}
|
|
}
|
|
matchedAttributes++;
|
|
}
|
|
|
|
// font with color attribute, span with style attribute, etc...
|
|
ASSERT(matchedAttributes <= attributes.size());
|
|
return matchedAttributes >= attributes.size();
|
|
}
|
|
|
|
void EditingStyle::mergeTypingStyle(Document* document)
|
|
{
|
|
ASSERT(document);
|
|
|
|
RefPtr<EditingStyle> typingStyle = document->frame()->selection().typingStyle();
|
|
if (!typingStyle || typingStyle == this)
|
|
return;
|
|
|
|
mergeStyle(typingStyle->style(), OverrideValues);
|
|
}
|
|
|
|
static void mergeTextDecorationValues(CSSValueList* mergedValue, const CSSValueList* valueToMerge)
|
|
{
|
|
DEFINE_STATIC_REF_WILL_BE_PERSISTENT(CSSPrimitiveValue, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
|
|
DEFINE_STATIC_REF_WILL_BE_PERSISTENT(CSSPrimitiveValue, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
|
|
if (valueToMerge->hasValue(underline) && !mergedValue->hasValue(underline))
|
|
mergedValue->append(underline);
|
|
|
|
if (valueToMerge->hasValue(lineThrough) && !mergedValue->hasValue(lineThrough))
|
|
mergedValue->append(lineThrough);
|
|
}
|
|
|
|
void EditingStyle::mergeStyle(const StylePropertySet* style, CSSPropertyOverrideMode mode)
|
|
{
|
|
if (!style)
|
|
return;
|
|
|
|
if (!m_mutableStyle) {
|
|
m_mutableStyle = style->mutableCopy();
|
|
return;
|
|
}
|
|
|
|
unsigned propertyCount = style->propertyCount();
|
|
for (unsigned i = 0; i < propertyCount; ++i) {
|
|
StylePropertySet::PropertyReference property = style->propertyAt(i);
|
|
RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(property.id());
|
|
|
|
// text decorations never override values
|
|
if ((property.id() == textDecorationPropertyForEditing() || property.id() == CSSPropertyWebkitTextDecorationsInEffect) && property.value()->isValueList() && value) {
|
|
if (value->isValueList()) {
|
|
mergeTextDecorationValues(toCSSValueList(value.get()), toCSSValueList(property.value()));
|
|
continue;
|
|
}
|
|
value = nullptr; // text-decoration: none is equivalent to not having the property
|
|
}
|
|
|
|
if (mode == OverrideValues || (mode == DoNotOverrideValues && !value))
|
|
m_mutableStyle->setProperty(property.id(), property.value()->cssText());
|
|
}
|
|
}
|
|
|
|
bool isTransparentColorValue(CSSValue* cssValue)
|
|
{
|
|
if (!cssValue)
|
|
return true;
|
|
if (!cssValue->isPrimitiveValue())
|
|
return false;
|
|
CSSPrimitiveValue* value = toCSSPrimitiveValue(cssValue);
|
|
if (value->isRGBColor())
|
|
return !alphaChannel(value->getRGBA32Value());
|
|
return false;
|
|
}
|
|
|
|
bool hasTransparentBackgroundColor(CSSStyleDeclaration* style)
|
|
{
|
|
RefPtr<CSSValue> cssValue = style->getPropertyCSSValueInternal(CSSPropertyBackgroundColor);
|
|
return isTransparentColorValue(cssValue.get());
|
|
}
|
|
|
|
PassRefPtr<CSSValue> backgroundColorInEffect(Node* node)
|
|
{
|
|
for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) {
|
|
RefPtr<CSSComputedStyleDeclaration> ancestorStyle = CSSComputedStyleDeclaration::create(ancestor);
|
|
if (!hasTransparentBackgroundColor(ancestorStyle.get()))
|
|
return ancestorStyle->getPropertyCSSValue(CSSPropertyBackgroundColor);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
}
|