mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-05 08:35:26 +00:00
1018 lines
39 KiB
C++
1018 lines
39 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/*
|
|
* representation of a declaration block (or style attribute) in a CSS
|
|
* stylesheet
|
|
*/
|
|
|
|
#include "mozilla/Util.h"
|
|
|
|
#include "mozilla/css/Declaration.h"
|
|
#include "nsPrintfCString.h"
|
|
|
|
namespace mozilla {
|
|
namespace css {
|
|
|
|
// check that we can fit all the CSS properties into a PRUint8
|
|
// for the mOrder array - if not, might need to use PRUint16!
|
|
MOZ_STATIC_ASSERT(eCSSProperty_COUNT_no_shorthands - 1 <= PR_UINT8_MAX,
|
|
"CSS longhand property numbers no longer fit in a PRUint8");
|
|
|
|
Declaration::Declaration()
|
|
: mImmutable(false)
|
|
{
|
|
MOZ_COUNT_CTOR(mozilla::css::Declaration);
|
|
}
|
|
|
|
Declaration::Declaration(const Declaration& aCopy)
|
|
: mOrder(aCopy.mOrder),
|
|
mData(aCopy.mData ? aCopy.mData->Clone() : nsnull),
|
|
mImportantData(aCopy.mImportantData
|
|
? aCopy.mImportantData->Clone() : nsnull),
|
|
mImmutable(false)
|
|
{
|
|
MOZ_COUNT_CTOR(mozilla::css::Declaration);
|
|
}
|
|
|
|
Declaration::~Declaration()
|
|
{
|
|
MOZ_COUNT_DTOR(mozilla::css::Declaration);
|
|
}
|
|
|
|
void
|
|
Declaration::ValueAppended(nsCSSProperty aProperty)
|
|
{
|
|
NS_ABORT_IF_FALSE(!mData && !mImportantData,
|
|
"should only be called while expanded");
|
|
NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty),
|
|
"shorthands forbidden");
|
|
// order IS important for CSS, so remove and add to the end
|
|
mOrder.RemoveElement(aProperty);
|
|
mOrder.AppendElement(aProperty);
|
|
}
|
|
|
|
void
|
|
Declaration::RemoveProperty(nsCSSProperty aProperty)
|
|
{
|
|
nsCSSExpandedDataBlock data;
|
|
ExpandTo(&data);
|
|
NS_ABORT_IF_FALSE(!mData && !mImportantData, "Expand didn't null things out");
|
|
|
|
if (nsCSSProps::IsShorthand(aProperty)) {
|
|
CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty) {
|
|
data.ClearLonghandProperty(*p);
|
|
mOrder.RemoveElement(*p);
|
|
}
|
|
} else {
|
|
data.ClearLonghandProperty(aProperty);
|
|
mOrder.RemoveElement(aProperty);
|
|
}
|
|
|
|
CompressFrom(&data);
|
|
}
|
|
|
|
bool
|
|
Declaration::HasProperty(nsCSSProperty aProperty) const
|
|
{
|
|
NS_ABORT_IF_FALSE(0 <= aProperty &&
|
|
aProperty < eCSSProperty_COUNT_no_shorthands,
|
|
"property ID out of range");
|
|
|
|
nsCSSCompressedDataBlock *data = GetValueIsImportant(aProperty)
|
|
? mImportantData : mData;
|
|
const nsCSSValue *val = data->ValueFor(aProperty);
|
|
return !!val;
|
|
}
|
|
|
|
bool
|
|
Declaration::AppendValueToString(nsCSSProperty aProperty,
|
|
nsAString& aResult) const
|
|
{
|
|
NS_ABORT_IF_FALSE(0 <= aProperty &&
|
|
aProperty < eCSSProperty_COUNT_no_shorthands,
|
|
"property ID out of range");
|
|
|
|
nsCSSCompressedDataBlock *data = GetValueIsImportant(aProperty)
|
|
? mImportantData : mData;
|
|
const nsCSSValue *val = data->ValueFor(aProperty);
|
|
if (!val) {
|
|
return false;
|
|
}
|
|
|
|
val->AppendToString(aProperty, aResult);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Declaration::GetValue(nsCSSProperty aProperty, nsAString& aValue) const
|
|
{
|
|
aValue.Truncate(0);
|
|
|
|
// simple properties are easy.
|
|
if (!nsCSSProps::IsShorthand(aProperty)) {
|
|
AppendValueToString(aProperty, aValue);
|
|
return;
|
|
}
|
|
|
|
// DOM Level 2 Style says (when describing CSS2Properties, although
|
|
// not CSSStyleDeclaration.getPropertyValue):
|
|
// However, if there is no shorthand declaration that could be added
|
|
// to the ruleset without changing in any way the rules already
|
|
// declared in the ruleset (i.e., by adding longhand rules that were
|
|
// previously not declared in the ruleset), then the empty string
|
|
// should be returned for the shorthand property.
|
|
// This means we need to check a number of cases:
|
|
// (1) Since a shorthand sets all sub-properties, if some of its
|
|
// subproperties were not specified, we must return the empty
|
|
// string.
|
|
// (2) Since 'inherit' and 'initial' can only be specified as the
|
|
// values for entire properties, we need to return the empty
|
|
// string if some but not all of the subproperties have one of
|
|
// those values.
|
|
// (3) Since a single value only makes sense with or without
|
|
// !important, we return the empty string if some values are
|
|
// !important and some are not.
|
|
// Since we're doing this check for 'inherit' and 'initial' up front,
|
|
// we can also simplify the property serialization code by serializing
|
|
// those values up front as well.
|
|
PRUint32 totalCount = 0, importantCount = 0,
|
|
initialCount = 0, inheritCount = 0;
|
|
CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty) {
|
|
if (*p == eCSSProperty__x_system_font ||
|
|
nsCSSProps::PropHasFlags(*p, CSS_PROPERTY_DIRECTIONAL_SOURCE)) {
|
|
// The system-font subproperty and the *-source properties don't count.
|
|
continue;
|
|
}
|
|
++totalCount;
|
|
const nsCSSValue *val = mData->ValueFor(*p);
|
|
NS_ABORT_IF_FALSE(!val || !mImportantData || !mImportantData->ValueFor(*p),
|
|
"can't be in both blocks");
|
|
if (!val && mImportantData) {
|
|
++importantCount;
|
|
val = mImportantData->ValueFor(*p);
|
|
}
|
|
if (!val) {
|
|
// Case (1) above: some subproperties not specified.
|
|
return;
|
|
}
|
|
if (val->GetUnit() == eCSSUnit_Inherit) {
|
|
++inheritCount;
|
|
} else if (val->GetUnit() == eCSSUnit_Initial) {
|
|
++initialCount;
|
|
}
|
|
}
|
|
if (importantCount != 0 && importantCount != totalCount) {
|
|
// Case (3), no consistent importance.
|
|
return;
|
|
}
|
|
if (initialCount == totalCount) {
|
|
// Simplify serialization below by serializing initial up-front.
|
|
nsCSSValue(eCSSUnit_Initial).AppendToString(eCSSProperty_UNKNOWN, aValue);
|
|
return;
|
|
}
|
|
if (inheritCount == totalCount) {
|
|
// Simplify serialization below by serializing inherit up-front.
|
|
nsCSSValue(eCSSUnit_Inherit).AppendToString(eCSSProperty_UNKNOWN, aValue);
|
|
return;
|
|
}
|
|
if (initialCount != 0 || inheritCount != 0) {
|
|
// Case (2): partially initial or inherit.
|
|
return;
|
|
}
|
|
|
|
nsCSSCompressedDataBlock *data = importantCount ? mImportantData : mData;
|
|
switch (aProperty) {
|
|
case eCSSProperty_margin:
|
|
case eCSSProperty_padding:
|
|
case eCSSProperty_border_color:
|
|
case eCSSProperty_border_style:
|
|
case eCSSProperty_border_width: {
|
|
const nsCSSProperty* subprops =
|
|
nsCSSProps::SubpropertyEntryFor(aProperty);
|
|
NS_ABORT_IF_FALSE(nsCSSProps::GetStringValue(subprops[0]).Find("-top") !=
|
|
kNotFound, "first subprop must be top");
|
|
NS_ABORT_IF_FALSE(nsCSSProps::GetStringValue(subprops[1]).Find("-right") !=
|
|
kNotFound, "second subprop must be right");
|
|
NS_ABORT_IF_FALSE(nsCSSProps::GetStringValue(subprops[2]).Find("-bottom") !=
|
|
kNotFound, "third subprop must be bottom");
|
|
NS_ABORT_IF_FALSE(nsCSSProps::GetStringValue(subprops[3]).Find("-left") !=
|
|
kNotFound, "fourth subprop must be left");
|
|
const nsCSSValue &topValue = *data->ValueFor(subprops[0]);
|
|
const nsCSSValue &rightValue = *data->ValueFor(subprops[1]);
|
|
const nsCSSValue &bottomValue = *data->ValueFor(subprops[2]);
|
|
const nsCSSValue &leftValue = *data->ValueFor(subprops[3]);
|
|
|
|
NS_ABORT_IF_FALSE(topValue.GetUnit() != eCSSUnit_Null, "null top");
|
|
topValue.AppendToString(subprops[0], aValue);
|
|
if (topValue != rightValue || topValue != leftValue ||
|
|
topValue != bottomValue) {
|
|
aValue.Append(PRUnichar(' '));
|
|
NS_ABORT_IF_FALSE(rightValue.GetUnit() != eCSSUnit_Null, "null right");
|
|
rightValue.AppendToString(subprops[1], aValue);
|
|
if (topValue != bottomValue || rightValue != leftValue) {
|
|
aValue.Append(PRUnichar(' '));
|
|
NS_ABORT_IF_FALSE(bottomValue.GetUnit() != eCSSUnit_Null, "null bottom");
|
|
bottomValue.AppendToString(subprops[2], aValue);
|
|
if (rightValue != leftValue) {
|
|
aValue.Append(PRUnichar(' '));
|
|
NS_ABORT_IF_FALSE(leftValue.GetUnit() != eCSSUnit_Null, "null left");
|
|
leftValue.AppendToString(subprops[3], aValue);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case eCSSProperty_border_radius:
|
|
case eCSSProperty__moz_outline_radius: {
|
|
const nsCSSProperty* subprops =
|
|
nsCSSProps::SubpropertyEntryFor(aProperty);
|
|
const nsCSSValue* vals[4] = {
|
|
data->ValueFor(subprops[0]),
|
|
data->ValueFor(subprops[1]),
|
|
data->ValueFor(subprops[2]),
|
|
data->ValueFor(subprops[3])
|
|
};
|
|
|
|
// For compatibility, only write a slash and the y-values
|
|
// if they're not identical to the x-values.
|
|
bool needY = false;
|
|
for (int i = 0; i < 4; i++) {
|
|
if (vals[i]->GetUnit() == eCSSUnit_Pair) {
|
|
needY = true;
|
|
vals[i]->GetPairValue().mXValue.AppendToString(subprops[i], aValue);
|
|
} else {
|
|
vals[i]->AppendToString(subprops[i], aValue);
|
|
}
|
|
if (i < 3)
|
|
aValue.Append(PRUnichar(' '));
|
|
}
|
|
|
|
if (needY) {
|
|
aValue.AppendLiteral(" / ");
|
|
for (int i = 0; i < 4; i++) {
|
|
if (vals[i]->GetUnit() == eCSSUnit_Pair) {
|
|
vals[i]->GetPairValue().mYValue.AppendToString(subprops[i], aValue);
|
|
} else {
|
|
vals[i]->AppendToString(subprops[i], aValue);
|
|
}
|
|
if (i < 3)
|
|
aValue.Append(PRUnichar(' '));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case eCSSProperty_border_image: {
|
|
// Even though there are some cases where we could omit
|
|
// 'border-image-source' (when it's none), it's probably not a
|
|
// good idea since it's likely to be confusing. It would also
|
|
// require adding the extra check that we serialize *something*.
|
|
AppendValueToString(eCSSProperty_border_image_source, aValue);
|
|
|
|
bool sliceDefault = data->HasDefaultBorderImageSlice();
|
|
bool widthDefault = data->HasDefaultBorderImageWidth();
|
|
bool outsetDefault = data->HasDefaultBorderImageOutset();
|
|
|
|
if (!sliceDefault || !widthDefault || !outsetDefault) {
|
|
aValue.Append(PRUnichar(' '));
|
|
AppendValueToString(eCSSProperty_border_image_slice, aValue);
|
|
if (!widthDefault || !outsetDefault) {
|
|
aValue.Append(NS_LITERAL_STRING(" /"));
|
|
if (!widthDefault) {
|
|
aValue.Append(PRUnichar(' '));
|
|
AppendValueToString(eCSSProperty_border_image_width, aValue);
|
|
}
|
|
if (!outsetDefault) {
|
|
aValue.Append(NS_LITERAL_STRING(" / "));
|
|
AppendValueToString(eCSSProperty_border_image_outset, aValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool repeatDefault = data->HasDefaultBorderImageRepeat();
|
|
if (!repeatDefault) {
|
|
aValue.Append(PRUnichar(' '));
|
|
AppendValueToString(eCSSProperty_border_image_repeat, aValue);
|
|
}
|
|
break;
|
|
}
|
|
case eCSSProperty_border: {
|
|
// If we have a non-default value for any of the properties that
|
|
// this shorthand sets but cannot specify, we have to return the
|
|
// empty string.
|
|
if (data->ValueFor(eCSSProperty_border_image_source)->GetUnit() !=
|
|
eCSSUnit_None ||
|
|
!data->HasDefaultBorderImageSlice() ||
|
|
!data->HasDefaultBorderImageWidth() ||
|
|
!data->HasDefaultBorderImageOutset() ||
|
|
!data->HasDefaultBorderImageRepeat() ||
|
|
data->ValueFor(eCSSProperty_border_top_colors)->GetUnit() !=
|
|
eCSSUnit_None ||
|
|
data->ValueFor(eCSSProperty_border_right_colors)->GetUnit() !=
|
|
eCSSUnit_None ||
|
|
data->ValueFor(eCSSProperty_border_bottom_colors)->GetUnit() !=
|
|
eCSSUnit_None ||
|
|
data->ValueFor(eCSSProperty_border_left_colors)->GetUnit() !=
|
|
eCSSUnit_None) {
|
|
break;
|
|
}
|
|
|
|
const nsCSSProperty* subproptables[3] = {
|
|
nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color),
|
|
nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_style),
|
|
nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_width)
|
|
};
|
|
bool match = true;
|
|
for (const nsCSSProperty** subprops = subproptables,
|
|
**subprops_end = ArrayEnd(subproptables);
|
|
subprops < subprops_end; ++subprops) {
|
|
// Check only the first four subprops in each table, since the
|
|
// others are extras for dimensional box properties.
|
|
const nsCSSValue *firstSide = data->ValueFor((*subprops)[0]);
|
|
for (PRInt32 side = 1; side < 4; ++side) {
|
|
const nsCSSValue *otherSide =
|
|
data->ValueFor((*subprops)[side]);
|
|
if (*firstSide != *otherSide)
|
|
match = false;
|
|
}
|
|
}
|
|
if (!match) {
|
|
// We can't express what we have in the border shorthand
|
|
break;
|
|
}
|
|
// tweak aProperty and fall through
|
|
aProperty = eCSSProperty_border_top;
|
|
}
|
|
case eCSSProperty_border_top:
|
|
case eCSSProperty_border_right:
|
|
case eCSSProperty_border_bottom:
|
|
case eCSSProperty_border_left:
|
|
case eCSSProperty_border_start:
|
|
case eCSSProperty_border_end:
|
|
case eCSSProperty__moz_column_rule:
|
|
case eCSSProperty_outline: {
|
|
const nsCSSProperty* subprops =
|
|
nsCSSProps::SubpropertyEntryFor(aProperty);
|
|
NS_ABORT_IF_FALSE(StringEndsWith(nsCSSProps::GetStringValue(subprops[2]),
|
|
NS_LITERAL_CSTRING("-color")) ||
|
|
StringEndsWith(nsCSSProps::GetStringValue(subprops[2]),
|
|
NS_LITERAL_CSTRING("-color-value")),
|
|
"third subprop must be the color property");
|
|
const nsCSSValue *colorValue = data->ValueFor(subprops[2]);
|
|
bool isMozUseTextColor =
|
|
colorValue->GetUnit() == eCSSUnit_Enumerated &&
|
|
colorValue->GetIntValue() == NS_STYLE_COLOR_MOZ_USE_TEXT_COLOR;
|
|
if (!AppendValueToString(subprops[0], aValue) ||
|
|
!(aValue.Append(PRUnichar(' ')),
|
|
AppendValueToString(subprops[1], aValue)) ||
|
|
// Don't output a third value when it's -moz-use-text-color.
|
|
!(isMozUseTextColor ||
|
|
(aValue.Append(PRUnichar(' ')),
|
|
AppendValueToString(subprops[2], aValue)))) {
|
|
aValue.Truncate();
|
|
}
|
|
break;
|
|
}
|
|
case eCSSProperty_margin_left:
|
|
case eCSSProperty_margin_right:
|
|
case eCSSProperty_margin_start:
|
|
case eCSSProperty_margin_end:
|
|
case eCSSProperty_padding_left:
|
|
case eCSSProperty_padding_right:
|
|
case eCSSProperty_padding_start:
|
|
case eCSSProperty_padding_end:
|
|
case eCSSProperty_border_left_color:
|
|
case eCSSProperty_border_left_style:
|
|
case eCSSProperty_border_left_width:
|
|
case eCSSProperty_border_right_color:
|
|
case eCSSProperty_border_right_style:
|
|
case eCSSProperty_border_right_width:
|
|
case eCSSProperty_border_start_color:
|
|
case eCSSProperty_border_start_style:
|
|
case eCSSProperty_border_start_width:
|
|
case eCSSProperty_border_end_color:
|
|
case eCSSProperty_border_end_style:
|
|
case eCSSProperty_border_end_width: {
|
|
const nsCSSProperty* subprops =
|
|
nsCSSProps::SubpropertyEntryFor(aProperty);
|
|
NS_ABORT_IF_FALSE(subprops[3] == eCSSProperty_UNKNOWN,
|
|
"not box property with physical vs. logical cascading");
|
|
AppendValueToString(subprops[0], aValue);
|
|
break;
|
|
}
|
|
case eCSSProperty_background: {
|
|
// We know from above that all subproperties were specified.
|
|
// However, we still can't represent that in the shorthand unless
|
|
// they're all lists of the same length. So if they're different
|
|
// lengths, we need to bail out.
|
|
// We also need to bail out if an item has background-clip and
|
|
// background-origin that are different and not the default
|
|
// values. (We omit them if they're both default.)
|
|
const nsCSSValueList *image =
|
|
data->ValueFor(eCSSProperty_background_image)->
|
|
GetListValue();
|
|
const nsCSSValuePairList *repeat =
|
|
data->ValueFor(eCSSProperty_background_repeat)->
|
|
GetPairListValue();
|
|
const nsCSSValueList *attachment =
|
|
data->ValueFor(eCSSProperty_background_attachment)->
|
|
GetListValue();
|
|
const nsCSSValueList *position =
|
|
data->ValueFor(eCSSProperty_background_position)->
|
|
GetListValue();
|
|
const nsCSSValueList *clip =
|
|
data->ValueFor(eCSSProperty_background_clip)->
|
|
GetListValue();
|
|
const nsCSSValueList *origin =
|
|
data->ValueFor(eCSSProperty_background_origin)->
|
|
GetListValue();
|
|
const nsCSSValuePairList *size =
|
|
data->ValueFor(eCSSProperty_background_size)->
|
|
GetPairListValue();
|
|
for (;;) {
|
|
if (size->mXValue.GetUnit() != eCSSUnit_Auto ||
|
|
size->mYValue.GetUnit() != eCSSUnit_Auto) {
|
|
// Non-default background-size, so can't be serialized as shorthand.
|
|
aValue.Truncate();
|
|
return;
|
|
}
|
|
image->mValue.AppendToString(eCSSProperty_background_image, aValue);
|
|
aValue.Append(PRUnichar(' '));
|
|
repeat->mXValue.AppendToString(eCSSProperty_background_repeat, aValue);
|
|
if (repeat->mYValue.GetUnit() != eCSSUnit_Null) {
|
|
repeat->mYValue.AppendToString(eCSSProperty_background_repeat, aValue);
|
|
}
|
|
aValue.Append(PRUnichar(' '));
|
|
attachment->mValue.AppendToString(eCSSProperty_background_attachment,
|
|
aValue);
|
|
aValue.Append(PRUnichar(' '));
|
|
position->mValue.AppendToString(eCSSProperty_background_position,
|
|
aValue);
|
|
|
|
NS_ABORT_IF_FALSE(clip->mValue.GetUnit() == eCSSUnit_Enumerated &&
|
|
origin->mValue.GetUnit() == eCSSUnit_Enumerated,
|
|
"should not have inherit/initial within list");
|
|
|
|
if (clip->mValue.GetIntValue() != NS_STYLE_BG_CLIP_BORDER ||
|
|
origin->mValue.GetIntValue() != NS_STYLE_BG_ORIGIN_PADDING) {
|
|
// The shorthand only has a single clip/origin value which sets
|
|
// both properties. So if they're different (and non-default),
|
|
// we can't represent the state using the shorthand.
|
|
MOZ_STATIC_ASSERT(NS_STYLE_BG_CLIP_BORDER ==
|
|
NS_STYLE_BG_ORIGIN_BORDER &&
|
|
NS_STYLE_BG_CLIP_PADDING ==
|
|
NS_STYLE_BG_ORIGIN_PADDING &&
|
|
NS_STYLE_BG_CLIP_CONTENT ==
|
|
NS_STYLE_BG_ORIGIN_CONTENT,
|
|
"bg-clip and bg-origin style constants must agree");
|
|
if (clip->mValue != origin->mValue) {
|
|
aValue.Truncate();
|
|
return;
|
|
}
|
|
|
|
aValue.Append(PRUnichar(' '));
|
|
clip->mValue.AppendToString(eCSSProperty_background_clip, aValue);
|
|
}
|
|
|
|
image = image->mNext;
|
|
repeat = repeat->mNext;
|
|
attachment = attachment->mNext;
|
|
position = position->mNext;
|
|
clip = clip->mNext;
|
|
origin = origin->mNext;
|
|
size = size->mNext;
|
|
|
|
if (!image) {
|
|
if (repeat || attachment || position || clip || origin || size) {
|
|
// Uneven length lists, so can't be serialized as shorthand.
|
|
aValue.Truncate();
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
if (!repeat || !attachment || !position || !clip || !origin || !size) {
|
|
// Uneven length lists, so can't be serialized as shorthand.
|
|
aValue.Truncate();
|
|
return;
|
|
}
|
|
aValue.Append(PRUnichar(','));
|
|
aValue.Append(PRUnichar(' '));
|
|
}
|
|
|
|
aValue.Append(PRUnichar(' '));
|
|
AppendValueToString(eCSSProperty_background_color, aValue);
|
|
break;
|
|
}
|
|
case eCSSProperty_font: {
|
|
// systemFont might not be present; the others are guaranteed to be
|
|
// based on the shorthand check at the beginning of the function
|
|
const nsCSSValue *systemFont =
|
|
data->ValueFor(eCSSProperty__x_system_font);
|
|
const nsCSSValue &style =
|
|
*data->ValueFor(eCSSProperty_font_style);
|
|
const nsCSSValue &variant =
|
|
*data->ValueFor(eCSSProperty_font_variant);
|
|
const nsCSSValue &weight =
|
|
*data->ValueFor(eCSSProperty_font_weight);
|
|
const nsCSSValue &size =
|
|
*data->ValueFor(eCSSProperty_font_size);
|
|
const nsCSSValue &lh =
|
|
*data->ValueFor(eCSSProperty_line_height);
|
|
const nsCSSValue &family =
|
|
*data->ValueFor(eCSSProperty_font_family);
|
|
const nsCSSValue &stretch =
|
|
*data->ValueFor(eCSSProperty_font_stretch);
|
|
const nsCSSValue &sizeAdjust =
|
|
*data->ValueFor(eCSSProperty_font_size_adjust);
|
|
const nsCSSValue &featureSettings =
|
|
*data->ValueFor(eCSSProperty_font_feature_settings);
|
|
const nsCSSValue &languageOverride =
|
|
*data->ValueFor(eCSSProperty_font_language_override);
|
|
|
|
if (systemFont &&
|
|
systemFont->GetUnit() != eCSSUnit_None &&
|
|
systemFont->GetUnit() != eCSSUnit_Null) {
|
|
if (style.GetUnit() != eCSSUnit_System_Font ||
|
|
variant.GetUnit() != eCSSUnit_System_Font ||
|
|
weight.GetUnit() != eCSSUnit_System_Font ||
|
|
size.GetUnit() != eCSSUnit_System_Font ||
|
|
lh.GetUnit() != eCSSUnit_System_Font ||
|
|
family.GetUnit() != eCSSUnit_System_Font ||
|
|
stretch.GetUnit() != eCSSUnit_System_Font ||
|
|
sizeAdjust.GetUnit() != eCSSUnit_System_Font ||
|
|
featureSettings.GetUnit() != eCSSUnit_System_Font ||
|
|
languageOverride.GetUnit() != eCSSUnit_System_Font) {
|
|
// This can't be represented as a shorthand.
|
|
return;
|
|
}
|
|
systemFont->AppendToString(eCSSProperty__x_system_font, aValue);
|
|
} else {
|
|
// The font-stretch, font-size-adjust,
|
|
// -moz-font-feature-settings, and -moz-font-language-override
|
|
// properties are reset by this shorthand property to their
|
|
// initial values, but can't be represented in its syntax.
|
|
if (stretch.GetUnit() != eCSSUnit_Enumerated ||
|
|
stretch.GetIntValue() != NS_STYLE_FONT_STRETCH_NORMAL ||
|
|
sizeAdjust.GetUnit() != eCSSUnit_None ||
|
|
featureSettings.GetUnit() != eCSSUnit_Normal ||
|
|
languageOverride.GetUnit() != eCSSUnit_Normal) {
|
|
return;
|
|
}
|
|
|
|
if (style.GetUnit() != eCSSUnit_Enumerated ||
|
|
style.GetIntValue() != NS_FONT_STYLE_NORMAL) {
|
|
style.AppendToString(eCSSProperty_font_style, aValue);
|
|
aValue.Append(PRUnichar(' '));
|
|
}
|
|
if (variant.GetUnit() != eCSSUnit_Enumerated ||
|
|
variant.GetIntValue() != NS_FONT_VARIANT_NORMAL) {
|
|
variant.AppendToString(eCSSProperty_font_variant, aValue);
|
|
aValue.Append(PRUnichar(' '));
|
|
}
|
|
if (weight.GetUnit() != eCSSUnit_Enumerated ||
|
|
weight.GetIntValue() != NS_FONT_WEIGHT_NORMAL) {
|
|
weight.AppendToString(eCSSProperty_font_weight, aValue);
|
|
aValue.Append(PRUnichar(' '));
|
|
}
|
|
size.AppendToString(eCSSProperty_font_size, aValue);
|
|
if (lh.GetUnit() != eCSSUnit_Normal) {
|
|
aValue.Append(PRUnichar('/'));
|
|
lh.AppendToString(eCSSProperty_line_height, aValue);
|
|
}
|
|
aValue.Append(PRUnichar(' '));
|
|
family.AppendToString(eCSSProperty_font_family, aValue);
|
|
}
|
|
break;
|
|
}
|
|
case eCSSProperty_list_style:
|
|
if (AppendValueToString(eCSSProperty_list_style_type, aValue))
|
|
aValue.Append(PRUnichar(' '));
|
|
if (AppendValueToString(eCSSProperty_list_style_position, aValue))
|
|
aValue.Append(PRUnichar(' '));
|
|
AppendValueToString(eCSSProperty_list_style_image, aValue);
|
|
break;
|
|
case eCSSProperty_overflow: {
|
|
const nsCSSValue &xValue =
|
|
*data->ValueFor(eCSSProperty_overflow_x);
|
|
const nsCSSValue &yValue =
|
|
*data->ValueFor(eCSSProperty_overflow_y);
|
|
if (xValue == yValue)
|
|
xValue.AppendToString(eCSSProperty_overflow_x, aValue);
|
|
break;
|
|
}
|
|
case eCSSProperty_text_decoration: {
|
|
// If text-decoration-color or text-decoration-style isn't initial value,
|
|
// we cannot serialize the text-decoration shorthand value.
|
|
const nsCSSValue &decorationColor =
|
|
*data->ValueFor(eCSSProperty_text_decoration_color);
|
|
const nsCSSValue &decorationStyle =
|
|
*data->ValueFor(eCSSProperty_text_decoration_style);
|
|
|
|
NS_ABORT_IF_FALSE(decorationStyle.GetUnit() == eCSSUnit_Enumerated,
|
|
nsPrintfCString("bad text-decoration-style unit %d",
|
|
decorationStyle.GetUnit()).get());
|
|
|
|
if (decorationColor.GetUnit() != eCSSUnit_Enumerated ||
|
|
decorationColor.GetIntValue() != NS_STYLE_COLOR_MOZ_USE_TEXT_COLOR ||
|
|
decorationStyle.GetIntValue() !=
|
|
NS_STYLE_TEXT_DECORATION_STYLE_SOLID) {
|
|
return;
|
|
}
|
|
|
|
const nsCSSValue &textBlink =
|
|
*data->ValueFor(eCSSProperty_text_blink);
|
|
const nsCSSValue &decorationLine =
|
|
*data->ValueFor(eCSSProperty_text_decoration_line);
|
|
|
|
NS_ABORT_IF_FALSE(textBlink.GetUnit() == eCSSUnit_Enumerated,
|
|
nsPrintfCString("bad text-blink unit %d",
|
|
textBlink.GetUnit()).get());
|
|
NS_ABORT_IF_FALSE(decorationLine.GetUnit() == eCSSUnit_Enumerated,
|
|
nsPrintfCString("bad text-decoration-line unit %d",
|
|
decorationLine.GetUnit()).get());
|
|
|
|
bool blinkNone = (textBlink.GetIntValue() == NS_STYLE_TEXT_BLINK_NONE);
|
|
bool lineNone =
|
|
(decorationLine.GetIntValue() == NS_STYLE_TEXT_DECORATION_LINE_NONE);
|
|
|
|
if (blinkNone && lineNone) {
|
|
AppendValueToString(eCSSProperty_text_decoration_line, aValue);
|
|
} else {
|
|
if (!blinkNone) {
|
|
AppendValueToString(eCSSProperty_text_blink, aValue);
|
|
}
|
|
if (!lineNone) {
|
|
if (!aValue.IsEmpty()) {
|
|
aValue.Append(PRUnichar(' '));
|
|
}
|
|
AppendValueToString(eCSSProperty_text_decoration_line, aValue);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case eCSSProperty_transition: {
|
|
const nsCSSValue &transProp =
|
|
*data->ValueFor(eCSSProperty_transition_property);
|
|
const nsCSSValue &transDuration =
|
|
*data->ValueFor(eCSSProperty_transition_duration);
|
|
const nsCSSValue &transTiming =
|
|
*data->ValueFor(eCSSProperty_transition_timing_function);
|
|
const nsCSSValue &transDelay =
|
|
*data->ValueFor(eCSSProperty_transition_delay);
|
|
|
|
NS_ABORT_IF_FALSE(transDuration.GetUnit() == eCSSUnit_List ||
|
|
transDuration.GetUnit() == eCSSUnit_ListDep,
|
|
nsPrintfCString("bad t-duration unit %d",
|
|
transDuration.GetUnit()).get());
|
|
NS_ABORT_IF_FALSE(transTiming.GetUnit() == eCSSUnit_List ||
|
|
transTiming.GetUnit() == eCSSUnit_ListDep,
|
|
nsPrintfCString("bad t-timing unit %d",
|
|
transTiming.GetUnit()).get());
|
|
NS_ABORT_IF_FALSE(transDelay.GetUnit() == eCSSUnit_List ||
|
|
transDelay.GetUnit() == eCSSUnit_ListDep,
|
|
nsPrintfCString("bad t-delay unit %d",
|
|
transDelay.GetUnit()).get());
|
|
|
|
const nsCSSValueList* dur = transDuration.GetListValue();
|
|
const nsCSSValueList* tim = transTiming.GetListValue();
|
|
const nsCSSValueList* del = transDelay.GetListValue();
|
|
|
|
if (transProp.GetUnit() == eCSSUnit_None ||
|
|
transProp.GetUnit() == eCSSUnit_All) {
|
|
// If any of the other three lists has more than one element,
|
|
// we can't use the shorthand.
|
|
if (!dur->mNext && !tim->mNext && !del->mNext) {
|
|
transProp.AppendToString(eCSSProperty_transition_property, aValue);
|
|
aValue.Append(PRUnichar(' '));
|
|
dur->mValue.AppendToString(eCSSProperty_transition_duration,aValue);
|
|
aValue.Append(PRUnichar(' '));
|
|
tim->mValue.AppendToString(eCSSProperty_transition_timing_function,
|
|
aValue);
|
|
aValue.Append(PRUnichar(' '));
|
|
del->mValue.AppendToString(eCSSProperty_transition_delay, aValue);
|
|
aValue.Append(PRUnichar(' '));
|
|
} else {
|
|
aValue.Truncate();
|
|
}
|
|
} else {
|
|
NS_ABORT_IF_FALSE(transProp.GetUnit() == eCSSUnit_List ||
|
|
transProp.GetUnit() == eCSSUnit_ListDep,
|
|
nsPrintfCString("bad t-prop unit %d",
|
|
transProp.GetUnit()).get());
|
|
const nsCSSValueList* pro = transProp.GetListValue();
|
|
for (;;) {
|
|
pro->mValue.AppendToString(eCSSProperty_transition_property,
|
|
aValue);
|
|
aValue.Append(PRUnichar(' '));
|
|
dur->mValue.AppendToString(eCSSProperty_transition_duration,
|
|
aValue);
|
|
aValue.Append(PRUnichar(' '));
|
|
tim->mValue.AppendToString(eCSSProperty_transition_timing_function,
|
|
aValue);
|
|
aValue.Append(PRUnichar(' '));
|
|
del->mValue.AppendToString(eCSSProperty_transition_delay,
|
|
aValue);
|
|
pro = pro->mNext;
|
|
dur = dur->mNext;
|
|
tim = tim->mNext;
|
|
del = del->mNext;
|
|
if (!pro || !dur || !tim || !del) {
|
|
break;
|
|
}
|
|
aValue.AppendLiteral(", ");
|
|
}
|
|
if (pro || dur || tim || del) {
|
|
// Lists not all the same length, can't use shorthand.
|
|
aValue.Truncate();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case eCSSProperty_animation: {
|
|
const nsCSSProperty* subprops =
|
|
nsCSSProps::SubpropertyEntryFor(eCSSProperty_animation);
|
|
static const size_t numProps = 7;
|
|
NS_ABORT_IF_FALSE(subprops[numProps] == eCSSProperty_UNKNOWN,
|
|
"unexpected number of subproperties");
|
|
const nsCSSValue* values[numProps];
|
|
const nsCSSValueList* lists[numProps];
|
|
|
|
for (PRUint32 i = 0; i < numProps; ++i) {
|
|
values[i] = data->ValueFor(subprops[i]);
|
|
NS_ABORT_IF_FALSE(values[i]->GetUnit() == eCSSUnit_List ||
|
|
values[i]->GetUnit() == eCSSUnit_ListDep,
|
|
nsPrintfCString("bad a-duration unit %d",
|
|
values[i]->GetUnit()).get());
|
|
lists[i] = values[i]->GetListValue();
|
|
}
|
|
|
|
for (;;) {
|
|
// We must serialize 'animation-name' last in case it has
|
|
// a value that conflicts with one of the other keyword properties.
|
|
NS_ABORT_IF_FALSE(subprops[numProps - 1] ==
|
|
eCSSProperty_animation_name,
|
|
"animation-name must be last");
|
|
bool done = false;
|
|
for (PRUint32 i = 0;;) {
|
|
lists[i]->mValue.AppendToString(subprops[i], aValue);
|
|
lists[i] = lists[i]->mNext;
|
|
if (!lists[i]) {
|
|
done = true;
|
|
}
|
|
if (++i == numProps) {
|
|
break;
|
|
}
|
|
aValue.Append(PRUnichar(' '));
|
|
}
|
|
if (done) {
|
|
break;
|
|
}
|
|
aValue.AppendLiteral(", ");
|
|
}
|
|
for (PRUint32 i = 0; i < numProps; ++i) {
|
|
if (lists[i]) {
|
|
// Lists not all the same length, can't use shorthand.
|
|
aValue.Truncate();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case eCSSProperty_marker: {
|
|
const nsCSSValue &endValue =
|
|
*data->ValueFor(eCSSProperty_marker_end);
|
|
const nsCSSValue &midValue =
|
|
*data->ValueFor(eCSSProperty_marker_mid);
|
|
const nsCSSValue &startValue =
|
|
*data->ValueFor(eCSSProperty_marker_start);
|
|
if (endValue == midValue && midValue == startValue)
|
|
AppendValueToString(eCSSProperty_marker_end, aValue);
|
|
break;
|
|
}
|
|
case eCSSProperty__moz_columns: {
|
|
// Two values, column-count and column-width, separated by a space.
|
|
const nsCSSProperty* subprops =
|
|
nsCSSProps::SubpropertyEntryFor(aProperty);
|
|
AppendValueToString(subprops[0], aValue);
|
|
aValue.Append(PRUnichar(' '));
|
|
AppendValueToString(subprops[1], aValue);
|
|
break;
|
|
}
|
|
default:
|
|
NS_ABORT_IF_FALSE(false, "no other shorthands");
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool
|
|
Declaration::GetValueIsImportant(const nsAString& aProperty) const
|
|
{
|
|
nsCSSProperty propID = nsCSSProps::LookupProperty(aProperty);
|
|
if (propID == eCSSProperty_UNKNOWN) {
|
|
return false;
|
|
}
|
|
return GetValueIsImportant(propID);
|
|
}
|
|
|
|
bool
|
|
Declaration::GetValueIsImportant(nsCSSProperty aProperty) const
|
|
{
|
|
if (!mImportantData)
|
|
return false;
|
|
|
|
// Calling ValueFor is inefficient, but we can assume '!important' is rare.
|
|
|
|
if (!nsCSSProps::IsShorthand(aProperty)) {
|
|
return mImportantData->ValueFor(aProperty) != nsnull;
|
|
}
|
|
|
|
CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty) {
|
|
if (*p == eCSSProperty__x_system_font) {
|
|
// The system_font subproperty doesn't count.
|
|
continue;
|
|
}
|
|
if (!mImportantData->ValueFor(*p)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Declaration::AppendPropertyAndValueToString(nsCSSProperty aProperty,
|
|
nsAutoString& aValue,
|
|
nsAString& aResult) const
|
|
{
|
|
NS_ABORT_IF_FALSE(0 <= aProperty && aProperty < eCSSProperty_COUNT,
|
|
"property enum out of range");
|
|
NS_ABORT_IF_FALSE((aProperty < eCSSProperty_COUNT_no_shorthands) ==
|
|
aValue.IsEmpty(),
|
|
"aValue should be given for shorthands but not longhands");
|
|
AppendASCIItoUTF16(nsCSSProps::GetStringValue(aProperty), aResult);
|
|
aResult.AppendLiteral(": ");
|
|
if (aValue.IsEmpty())
|
|
AppendValueToString(aProperty, aResult);
|
|
else
|
|
aResult.Append(aValue);
|
|
if (GetValueIsImportant(aProperty)) {
|
|
aResult.AppendLiteral(" ! important");
|
|
}
|
|
aResult.AppendLiteral("; ");
|
|
}
|
|
|
|
void
|
|
Declaration::ToString(nsAString& aString) const
|
|
{
|
|
// Someone cares about this declaration's contents, so don't let it
|
|
// change from under them. See e.g. bug 338679.
|
|
SetImmutable();
|
|
|
|
nsCSSCompressedDataBlock *systemFontData =
|
|
GetValueIsImportant(eCSSProperty__x_system_font) ? mImportantData : mData;
|
|
const nsCSSValue *systemFont =
|
|
systemFontData->ValueFor(eCSSProperty__x_system_font);
|
|
const bool haveSystemFont = systemFont &&
|
|
systemFont->GetUnit() != eCSSUnit_None &&
|
|
systemFont->GetUnit() != eCSSUnit_Null;
|
|
bool didSystemFont = false;
|
|
|
|
PRInt32 count = mOrder.Length();
|
|
PRInt32 index;
|
|
nsAutoTArray<nsCSSProperty, 16> shorthandsUsed;
|
|
for (index = 0; index < count; index++) {
|
|
nsCSSProperty property = OrderValueAt(index);
|
|
bool doneProperty = false;
|
|
|
|
// If we already used this property in a shorthand, skip it.
|
|
if (shorthandsUsed.Length() > 0) {
|
|
for (const nsCSSProperty *shorthands =
|
|
nsCSSProps::ShorthandsContaining(property);
|
|
*shorthands != eCSSProperty_UNKNOWN; ++shorthands) {
|
|
if (shorthandsUsed.Contains(*shorthands)) {
|
|
doneProperty = true;
|
|
break;
|
|
}
|
|
}
|
|
if (doneProperty)
|
|
continue;
|
|
}
|
|
|
|
// Try to use this property in a shorthand.
|
|
nsAutoString value;
|
|
for (const nsCSSProperty *shorthands =
|
|
nsCSSProps::ShorthandsContaining(property);
|
|
*shorthands != eCSSProperty_UNKNOWN; ++shorthands) {
|
|
// ShorthandsContaining returns the shorthands in order from those
|
|
// that contain the most subproperties to those that contain the
|
|
// least, which is exactly the order we want to test them.
|
|
nsCSSProperty shorthand = *shorthands;
|
|
|
|
// If GetValue gives us a non-empty string back, we can use that
|
|
// value; otherwise it's not possible to use this shorthand.
|
|
GetValue(shorthand, value);
|
|
if (!value.IsEmpty()) {
|
|
AppendPropertyAndValueToString(shorthand, value, aString);
|
|
shorthandsUsed.AppendElement(shorthand);
|
|
doneProperty = true;
|
|
break;
|
|
}
|
|
|
|
NS_ABORT_IF_FALSE(shorthand != eCSSProperty_font ||
|
|
*(shorthands + 1) == eCSSProperty_UNKNOWN,
|
|
"font should always be the only containing shorthand");
|
|
if (shorthand == eCSSProperty_font) {
|
|
if (haveSystemFont && !didSystemFont) {
|
|
// Output the shorthand font declaration that we will
|
|
// partially override later. But don't add it to
|
|
// |shorthandsUsed|, since we will have to override it.
|
|
systemFont->AppendToString(eCSSProperty__x_system_font, value);
|
|
AppendPropertyAndValueToString(eCSSProperty_font, value, aString);
|
|
value.Truncate();
|
|
didSystemFont = true;
|
|
}
|
|
|
|
// That we output the system font is enough for this property if:
|
|
// (1) it's the hidden system font subproperty (which either
|
|
// means we output it or we don't have it), or
|
|
// (2) its value is the hidden system font value and it matches
|
|
// the hidden system font subproperty in importance, and
|
|
// we output the system font subproperty.
|
|
const nsCSSValue *val = systemFontData->ValueFor(property);
|
|
if (property == eCSSProperty__x_system_font ||
|
|
(haveSystemFont && val && val->GetUnit() == eCSSUnit_System_Font)) {
|
|
doneProperty = true;
|
|
}
|
|
}
|
|
}
|
|
if (doneProperty)
|
|
continue;
|
|
|
|
NS_ABORT_IF_FALSE(value.IsEmpty(), "value should be empty now");
|
|
AppendPropertyAndValueToString(property, value, aString);
|
|
}
|
|
if (! aString.IsEmpty()) {
|
|
// if the string is not empty, we have trailing whitespace we
|
|
// should remove
|
|
aString.Truncate(aString.Length() - 1);
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void
|
|
Declaration::List(FILE* out, PRInt32 aIndent) const
|
|
{
|
|
for (PRInt32 index = aIndent; --index >= 0; ) fputs(" ", out);
|
|
|
|
fputs("{ ", out);
|
|
nsAutoString s;
|
|
ToString(s);
|
|
fputs(NS_ConvertUTF16toUTF8(s).get(), out);
|
|
fputs("}", out);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
Declaration::GetNthProperty(PRUint32 aIndex, nsAString& aReturn) const
|
|
{
|
|
aReturn.Truncate();
|
|
if (aIndex < mOrder.Length()) {
|
|
nsCSSProperty property = OrderValueAt(aIndex);
|
|
if (0 <= property) {
|
|
AppendASCIItoUTF16(nsCSSProps::GetStringValue(property), aReturn);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Declaration::InitializeEmpty()
|
|
{
|
|
NS_ABORT_IF_FALSE(!mData && !mImportantData, "already initialized");
|
|
mData = nsCSSCompressedDataBlock::CreateEmptyBlock();
|
|
}
|
|
|
|
Declaration*
|
|
Declaration::EnsureMutable()
|
|
{
|
|
NS_ABORT_IF_FALSE(mData, "should only be called when not expanded");
|
|
if (!IsMutable()) {
|
|
return new Declaration(*this);
|
|
} else {
|
|
return this;
|
|
}
|
|
}
|
|
|
|
size_t
|
|
Declaration::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const
|
|
{
|
|
size_t n = aMallocSizeOf(this);
|
|
n += mOrder.SizeOfExcludingThis(aMallocSizeOf);
|
|
n += mData ? mData ->SizeOfIncludingThis(aMallocSizeOf) : 0;
|
|
n += mImportantData ? mImportantData->SizeOfIncludingThis(aMallocSizeOf) : 0;
|
|
return n;
|
|
}
|
|
|
|
} // namespace mozilla::css
|
|
} // namespace mozilla
|