mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
585 lines
23 KiB
C++
585 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/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,
|
|
CSSPropertyTextAlign,
|
|
// FIXME: CSSPropertyTextDecoration needs to be removed when CSS3 Text
|
|
// Decoration feature is no longer experimental.
|
|
CSSPropertyTextDecoration,
|
|
CSSPropertyTextDecorationLine,
|
|
CSSPropertyTextIndent,
|
|
CSSPropertyWhiteSpace,
|
|
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;
|
|
}
|
|
|
|
}
|