Mypal/layout/style/nsStyleContext.cpp

1855 lines
69 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- 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/. */
/* the interface (to internal code) for retrieving computed style data */
#include "CSSVariableImageTable.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Maybe.h"
#include "nsCSSAnonBoxes.h"
#include "nsCSSPseudoElements.h"
#include "nsStyleConsts.h"
#include "nsStyleStruct.h"
#include "nsStyleStructInlines.h"
#include "nsString.h"
#include "nsPresContext.h"
#include "nsIStyleRule.h"
#include "nsCOMPtr.h"
#include "nsStyleSet.h"
#include "nsIPresShell.h"
#include "nsRuleNode.h"
#include "nsStyleContext.h"
#include "mozilla/StyleAnimationValue.h"
#include "GeckoProfiler.h"
#include "nsIDocument.h"
#include "nsPrintfCString.h"
#include "RubyUtils.h"
#include "mozilla/Preferences.h"
#include "mozilla/ArenaObjectID.h"
#include "mozilla/StyleSetHandle.h"
#include "mozilla/StyleSetHandleInlines.h"
#include "mozilla/ReflowInput.h"
#include "nsLayoutUtils.h"
#include "nsCoord.h"
#include "nsFontMetrics.h"
// Ensure the binding function declarations in nsStyleContext.h matches
// those in ServoBindings.h.
#include "mozilla/ServoBindings.h"
using namespace mozilla;
//----------------------------------------------------------------------
#ifdef DEBUG
// Check that the style struct IDs are in the same order as they are
// in nsStyleStructList.h, since when we set up the IDs, we include
// the inherited and reset structs spearately from nsStyleStructList.h
enum DebugStyleStruct {
#define STYLE_STRUCT(name, checkdata_cb) eDebugStyleStruct_##name,
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
};
#define STYLE_STRUCT(name, checkdata_cb) \
static_assert(static_cast<int>(eDebugStyleStruct_##name) == \
static_cast<int>(eStyleStruct_##name), \
"Style struct IDs are not declared in order?");
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
const uint32_t nsStyleContext::sDependencyTable[] = {
#define STYLE_STRUCT(name, checkdata_cb)
#define STYLE_STRUCT_DEP(dep) NS_STYLE_INHERIT_BIT(dep) |
#define STYLE_STRUCT_END() 0,
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
#undef STYLE_STRUCT_DEP
#undef STYLE_STRUCT_END
};
// Whether to perform expensive assertions in the nsStyleContext destructor.
static bool sExpensiveStyleStructAssertionsEnabled;
#endif
nsStyleContext::nsStyleContext(nsStyleContext* aParent,
OwningStyleContextSource&& aSource,
nsIAtom* aPseudoTag,
CSSPseudoElementType aPseudoType)
: mParent(aParent)
, mChild(nullptr)
, mEmptyChild(nullptr)
, mPseudoTag(aPseudoTag)
, mSource(Move(aSource))
#ifdef MOZ_STYLO
, mPresContext(nullptr)
#endif
, mCachedResetData(nullptr)
, mBits(((uint64_t)aPseudoType) << NS_STYLE_CONTEXT_TYPE_SHIFT)
, mRefCnt(0)
#ifdef MOZ_STYLO
, mStoredChangeHint(nsChangeHint(0))
#ifdef DEBUG
, mConsumedChangeHint(false)
#endif
#endif
#ifdef DEBUG
, mFrameRefCnt(0)
, mComputingStruct(nsStyleStructID_None)
#endif
{
MOZ_COUNT_CTOR(nsStyleContext);
}
nsStyleContext::nsStyleContext(nsStyleContext* aParent,
nsIAtom* aPseudoTag,
CSSPseudoElementType aPseudoType,
already_AddRefed<nsRuleNode> aRuleNode,
bool aSkipParentDisplayBasedStyleFixup)
: nsStyleContext(aParent, OwningStyleContextSource(Move(aRuleNode)),
aPseudoTag, aPseudoType)
{
#ifdef MOZ_STYLO
mPresContext = mSource.AsGeckoRuleNode()->PresContext();
#endif
if (aParent) {
#ifdef DEBUG
nsRuleNode *r1 = mParent->RuleNode(), *r2 = mSource.AsGeckoRuleNode();
while (r1->GetParent())
r1 = r1->GetParent();
while (r2->GetParent())
r2 = r2->GetParent();
NS_ASSERTION(r1 == r2, "must be in the same rule tree as parent");
#endif
} else {
PresContext()->PresShell()->StyleSet()->RootStyleContextAdded();
}
mSource.AsGeckoRuleNode()->SetUsedDirectly(); // before ApplyStyleFixups()!
FinishConstruction(aSkipParentDisplayBasedStyleFixup);
}
nsStyleContext::nsStyleContext(nsStyleContext* aParent,
nsPresContext* aPresContext,
nsIAtom* aPseudoTag,
CSSPseudoElementType aPseudoType,
already_AddRefed<ServoComputedValues> aComputedValues,
bool aSkipParentDisplayBasedStyleFixup)
: nsStyleContext(aParent, OwningStyleContextSource(Move(aComputedValues)),
aPseudoTag, aPseudoType)
{
#ifdef MOZ_STYLO
mPresContext = aPresContext;
#endif
FinishConstruction(aSkipParentDisplayBasedStyleFixup);
}
void
nsStyleContext::FinishConstruction(bool aSkipParentDisplayBasedStyleFixup)
{
// This check has to be done "backward", because if it were written the
// more natural way it wouldn't fail even when it needed to.
static_assert((UINT64_MAX >> NS_STYLE_CONTEXT_TYPE_SHIFT) >=
static_cast<CSSPseudoElementTypeBase>(
CSSPseudoElementType::MAX),
"pseudo element bits no longer fit in a uint64_t");
MOZ_ASSERT(!mSource.IsNull());
#ifdef DEBUG
static_assert(MOZ_ARRAY_LENGTH(nsStyleContext::sDependencyTable)
== nsStyleStructID_Length,
"Number of items in dependency table doesn't match IDs");
#endif
mNextSibling = this;
mPrevSibling = this;
if (mParent) {
mParent->AddChild(this);
}
SetStyleBits();
if (!mSource.IsServoComputedValues()) {
ApplyStyleFixups(aSkipParentDisplayBasedStyleFixup);
}
#define eStyleStruct_LastItem (nsStyleStructID_Length - 1)
NS_ASSERTION(NS_STYLE_INHERIT_MASK & NS_STYLE_INHERIT_BIT(LastItem),
"NS_STYLE_INHERIT_MASK must be bigger, and other bits shifted");
#undef eStyleStruct_LastItem
}
nsStyleContext::~nsStyleContext()
{
MOZ_COUNT_DTOR(nsStyleContext);
NS_ASSERTION((nullptr == mChild) && (nullptr == mEmptyChild), "destructing context with children");
#ifdef DEBUG
if (sExpensiveStyleStructAssertionsEnabled) {
// Assert that the style structs we are about to destroy are not referenced
// anywhere else in the style context tree. These checks are expensive,
// which is why they are not enabled by default.
nsStyleContext* root = this;
while (root->mParent) {
root = root->mParent;
}
root->AssertStructsNotUsedElsewhere(this,
std::numeric_limits<int32_t>::max());
} else {
// In DEBUG builds when the pref is not enabled, we perform a more limited
// check just of the children of this style context.
AssertStructsNotUsedElsewhere(this, 2);
}
#endif
nsPresContext *presContext = PresContext();
DebugOnly<nsStyleSet*> geckoStyleSet = presContext->PresShell()->StyleSet()->GetAsGecko();
NS_ASSERTION(!geckoStyleSet ||
geckoStyleSet->GetRuleTree() == mSource.AsGeckoRuleNode()->RuleTree() ||
geckoStyleSet->IsInRuleTreeReconstruct(),
"destroying style context from old rule tree too late");
if (mParent) {
mParent->RemoveChild(this);
} else {
presContext->StyleSet()->RootStyleContextRemoved();
}
// Free up our data structs.
mCachedInheritedData.DestroyStructs(mBits, presContext);
if (mCachedResetData) {
mCachedResetData->Destroy(mBits, presContext);
}
// Free any ImageValues we were holding on to for CSS variable values.
CSSVariableImageTable::RemoveAll(this);
}
#ifdef DEBUG
void
nsStyleContext::AssertStructsNotUsedElsewhere(
nsStyleContext* aDestroyingContext,
int32_t aLevels) const
{
if (aLevels == 0) {
return;
}
void* data;
if (mBits & NS_STYLE_IS_GOING_AWAY) {
return;
}
if (this != aDestroyingContext) {
nsInheritedStyleData& destroyingInheritedData =
aDestroyingContext->mCachedInheritedData;
#define STYLE_STRUCT_INHERITED(name_, checkdata_cb) \
data = destroyingInheritedData.mStyleStructs[eStyleStruct_##name_]; \
if (data && \
!(aDestroyingContext->mBits & NS_STYLE_INHERIT_BIT(name_)) && \
(mCachedInheritedData.mStyleStructs[eStyleStruct_##name_] == data)) { \
printf_stderr("style struct %p found on style context %p\n", data, this);\
nsString url; \
nsresult rv = PresContext()->Document()->GetURL(url); \
if (NS_SUCCEEDED(rv)) { \
printf_stderr(" in %s\n", NS_ConvertUTF16toUTF8(url).get()); \
} \
MOZ_ASSERT(false, "destroying " #name_ " style struct still present " \
"in style context tree"); \
}
#define STYLE_STRUCT_RESET(name_, checkdata_cb)
#include "nsStyleStructList.h"
#undef STYLE_STRUCT_INHERITED
#undef STYLE_STRUCT_RESET
if (mCachedResetData) {
nsResetStyleData* destroyingResetData =
aDestroyingContext->mCachedResetData;
if (destroyingResetData) {
#define STYLE_STRUCT_INHERITED(name_, checkdata_cb_)
#define STYLE_STRUCT_RESET(name_, checkdata_cb) \
data = destroyingResetData->mStyleStructs[eStyleStruct_##name_]; \
if (data && \
!(aDestroyingContext->mBits & NS_STYLE_INHERIT_BIT(name_)) && \
(mCachedResetData->mStyleStructs[eStyleStruct_##name_] == data)) { \
printf_stderr("style struct %p found on style context %p\n", data, \
this); \
nsString url; \
nsresult rv = PresContext()->Document()->GetURL(url); \
if (NS_SUCCEEDED(rv)) { \
printf_stderr(" in %s\n", NS_ConvertUTF16toUTF8(url).get()); \
} \
MOZ_ASSERT(false, "destroying " #name_ " style struct still present "\
"in style context tree"); \
}
#include "nsStyleStructList.h"
#undef STYLE_STRUCT_INHERITED
#undef STYLE_STRUCT_RESET
}
}
}
if (mChild) {
const nsStyleContext* child = mChild;
do {
child->AssertStructsNotUsedElsewhere(aDestroyingContext, aLevels - 1);
child = child->mNextSibling;
} while (child != mChild);
}
if (mEmptyChild) {
const nsStyleContext* child = mEmptyChild;
do {
child->AssertStructsNotUsedElsewhere(aDestroyingContext, aLevels - 1);
child = child->mNextSibling;
} while (child != mEmptyChild);
}
}
#endif
void nsStyleContext::AddChild(nsStyleContext* aChild)
{
NS_ASSERTION(aChild->mPrevSibling == aChild &&
aChild->mNextSibling == aChild,
"child already in a child list");
nsStyleContext **listPtr = aChild->mSource.MatchesNoRules() ? &mEmptyChild : &mChild;
// Explicitly dereference listPtr so that compiler doesn't have to know that mNextSibling
// etc. don't alias with what ever listPtr points at.
nsStyleContext *list = *listPtr;
// Insert at the beginning of the list. See also FindChildWithRules.
if (list) {
// Link into existing elements, if there are any.
aChild->mNextSibling = list;
aChild->mPrevSibling = list->mPrevSibling;
list->mPrevSibling->mNextSibling = aChild;
list->mPrevSibling = aChild;
}
(*listPtr) = aChild;
}
void nsStyleContext::RemoveChild(nsStyleContext* aChild)
{
NS_PRECONDITION(nullptr != aChild && this == aChild->mParent, "bad argument");
nsStyleContext **list = aChild->mSource.MatchesNoRules() ? &mEmptyChild : &mChild;
if (aChild->mPrevSibling != aChild) { // has siblings
if ((*list) == aChild) {
(*list) = (*list)->mNextSibling;
}
}
else {
NS_ASSERTION((*list) == aChild, "bad sibling pointers");
(*list) = nullptr;
}
aChild->mPrevSibling->mNextSibling = aChild->mNextSibling;
aChild->mNextSibling->mPrevSibling = aChild->mPrevSibling;
aChild->mNextSibling = aChild;
aChild->mPrevSibling = aChild;
}
void
nsStyleContext::MoveTo(nsStyleContext* aNewParent)
{
MOZ_ASSERT(aNewParent != mParent);
// This function shouldn't be getting called if the parents have different
// values for some flags in mBits (unless the flag is also set on this style
// context) because if that were the case we would need to recompute those
// bits for |this|.
#define CHECK_FLAG(bit_) \
MOZ_ASSERT((mParent->mBits & (bit_)) == (aNewParent->mBits & (bit_)) || \
(mBits & (bit_)), \
"MoveTo cannot be called if " #bit_ " value on old and new " \
"style context parents do not match, unless the flag is set " \
"on this style context");
CHECK_FLAG(NS_STYLE_HAS_PSEUDO_ELEMENT_DATA)
CHECK_FLAG(NS_STYLE_IN_DISPLAY_NONE_SUBTREE)
CHECK_FLAG(NS_STYLE_HAS_TEXT_DECORATION_LINES)
CHECK_FLAG(NS_STYLE_RELEVANT_LINK_VISITED)
#undef CHECK_FLAG
// Assertions checking for visited style are just to avoid some tricky
// cases we can't be bothered handling at the moment.
MOZ_ASSERT(!IsStyleIfVisited());
MOZ_ASSERT(!mParent->IsStyleIfVisited());
MOZ_ASSERT(!aNewParent->IsStyleIfVisited());
MOZ_ASSERT(!mStyleIfVisited || mStyleIfVisited->mParent == mParent);
if (mParent->HasChildThatUsesResetStyle()) {
aNewParent->AddStyleBit(NS_STYLE_HAS_CHILD_THAT_USES_RESET_STYLE);
}
mParent->RemoveChild(this);
mParent = aNewParent;
mParent->AddChild(this);
if (mStyleIfVisited) {
mStyleIfVisited->mParent->RemoveChild(mStyleIfVisited);
mStyleIfVisited->mParent = aNewParent;
mStyleIfVisited->mParent->AddChild(mStyleIfVisited);
}
}
already_AddRefed<nsStyleContext>
nsStyleContext::FindChildWithRules(const nsIAtom* aPseudoTag,
NonOwningStyleContextSource aSource,
NonOwningStyleContextSource aSourceIfVisited,
bool aRelevantLinkVisited)
{
uint32_t threshold = 10; // The # of siblings we're willing to examine
// before just giving this whole thing up.
RefPtr<nsStyleContext> result;
nsStyleContext *list = aSource.MatchesNoRules() ? mEmptyChild : mChild;
if (list) {
nsStyleContext *child = list;
do {
if (child->mSource.AsRaw() == aSource &&
child->mPseudoTag == aPseudoTag &&
!child->IsStyleIfVisited() &&
child->RelevantLinkVisited() == aRelevantLinkVisited) {
bool match = false;
if (!aSourceIfVisited.IsNull()) {
match = child->GetStyleIfVisited() &&
child->GetStyleIfVisited()->mSource.AsRaw() == aSourceIfVisited;
} else {
match = !child->GetStyleIfVisited();
}
if (match && !(child->mBits & NS_STYLE_INELIGIBLE_FOR_SHARING)) {
result = child;
break;
}
}
child = child->mNextSibling;
threshold--;
if (threshold == 0)
break;
} while (child != list);
}
if (result) {
if (result != list) {
// Move result to the front of the list.
RemoveChild(result);
AddChild(result);
}
result->mBits |= NS_STYLE_IS_SHARED;
}
return result.forget();
}
const void* nsStyleContext::StyleData(nsStyleStructID aSID)
{
const void* cachedData = GetCachedStyleData(aSID);
if (cachedData)
return cachedData; // We have computed data stored on this node in the context tree.
// Our style source will take care of it for us.
const void* newData;
if (mSource.IsGeckoRuleNode()) {
newData = mSource.AsGeckoRuleNode()->GetStyleData(aSID, this, true);
if (!nsCachedStyleData::IsReset(aSID)) {
// always cache inherited data on the style context; the rule
// node set the bit in mBits for us if needed.
mCachedInheritedData.mStyleStructs[aSID] = const_cast<void*>(newData);
}
} else {
newData = StyleStructFromServoComputedValues(aSID);
// perform any remaining main thread work on the struct
switch (aSID) {
#define STYLE_STRUCT(name_, checkdata_cb_) \
case eStyleStruct_##name_: { \
auto data = static_cast<const nsStyle##name_*>(newData); \
const_cast<nsStyle##name_*>(data)->FinishStyle(PresContext()); \
break; \
}
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
default:
MOZ_ASSERT_UNREACHABLE("unexpected nsStyleStructID value");
break;
}
// The Servo-backed StyleContextSource owns the struct.
AddStyleBit(nsCachedStyleData::GetBitForSID(aSID));
// XXXbholley: Unconditionally caching reset structs here defeats the memory
// optimization where we lazily allocate mCachedResetData, so that we can avoid
// performing an FFI call each time we want to get the style structs. We should
// measure the tradeoffs at some point. If the FFI overhead is low and the memory
// win significant, we should consider _always_ grabbing the struct over FFI, and
// potentially giving mCachedInheritedData the same treatment.
//
// Note that there is a similar comment in the struct getters in nsStyleContext.h.
SetStyle(aSID, const_cast<void*>(newData));
}
return newData;
}
// This is an evil evil function, since it forces you to alloc your own separate copy of
// style data! Do not use this function unless you absolutely have to! You should avoid
// this at all costs! -dwh
void*
nsStyleContext::GetUniqueStyleData(const nsStyleStructID& aSID)
{
MOZ_ASSERT(!mSource.IsServoComputedValues(),
"Can't COW-mutate servo values from Gecko!");
// If we already own the struct and no kids could depend on it, then
// just return it. (We leak in this case if there are kids -- and this
// function really shouldn't be called for style contexts that could
// have kids depending on the data. ClearStyleData would be OK, but
// this test for no mChild or mEmptyChild doesn't catch that case.)
const void *current = StyleData(aSID);
if (!mChild && !mEmptyChild &&
!(mBits & nsCachedStyleData::GetBitForSID(aSID)) &&
GetCachedStyleData(aSID))
return const_cast<void*>(current);
void* result;
nsPresContext *presContext = PresContext();
switch (aSID) {
#define UNIQUE_CASE(c_) \
case eStyleStruct_##c_: \
result = new (presContext) nsStyle##c_( \
* static_cast<const nsStyle##c_ *>(current)); \
break;
UNIQUE_CASE(Font)
UNIQUE_CASE(Display)
UNIQUE_CASE(Text)
UNIQUE_CASE(TextReset)
UNIQUE_CASE(Visibility)
#undef UNIQUE_CASE
default:
NS_ERROR("Struct type not supported. Please find another way to do this if you can!");
return nullptr;
}
SetStyle(aSID, result);
mBits &= ~static_cast<uint64_t>(nsCachedStyleData::GetBitForSID(aSID));
return result;
}
// This is an evil function, but less evil than GetUniqueStyleData. It
// creates an empty style struct for this nsStyleContext.
void*
nsStyleContext::CreateEmptyStyleData(const nsStyleStructID& aSID)
{
MOZ_ASSERT(!mChild && !mEmptyChild &&
!(mBits & nsCachedStyleData::GetBitForSID(aSID)) &&
!GetCachedStyleData(aSID),
"This style should not have been computed");
void* result;
nsPresContext* presContext = PresContext();
switch (aSID) {
#define UNIQUE_CASE(c_) \
case eStyleStruct_##c_: \
result = new (presContext) nsStyle##c_(presContext); \
break;
UNIQUE_CASE(Border)
UNIQUE_CASE(Padding)
#undef UNIQUE_CASE
default:
NS_ERROR("Struct type not supported.");
return nullptr;
}
// The new struct is owned by this style context, but that we don't
// need to clear the bit in mBits because we've asserted that at the
// top of this function.
SetStyle(aSID, result);
return result;
}
void
nsStyleContext::SetStyle(nsStyleStructID aSID, void* aStruct)
{
// This method should only be called from nsRuleNode! It is not a public
// method!
NS_ASSERTION(aSID >= 0 && aSID < nsStyleStructID_Length, "out of bounds");
// NOTE: nsCachedStyleData::GetStyleData works roughly the same way.
// See the comments there (in nsRuleNode.h) for more details about
// what this is doing and why.
void** dataSlot;
if (nsCachedStyleData::IsReset(aSID)) {
if (!mCachedResetData) {
mCachedResetData = new (PresContext()) nsResetStyleData;
}
dataSlot = &mCachedResetData->mStyleStructs[aSID];
} else {
dataSlot = &mCachedInheritedData.mStyleStructs[aSID];
}
NS_ASSERTION(!*dataSlot || (mBits & nsCachedStyleData::GetBitForSID(aSID)),
"Going to leak style data");
*dataSlot = aStruct;
}
static bool
ShouldSuppressLineBreak(const nsStyleContext* aContext,
const nsStyleDisplay* aDisplay,
const nsStyleContext* aParentContext,
const nsStyleDisplay* aParentDisplay)
{
// The display change should only occur for "in-flow" children
if (aDisplay->IsOutOfFlowStyle()) {
return false;
}
// Display value of any anonymous box should not be touched. In most
// cases, anonymous boxes are actually not in ruby frame, but instead,
// some other frame with a ruby display value. Non-element pseudos
// which represents text frames, as well as ruby pseudos are excluded
// because we still want to set the flag for them.
if (aContext->GetPseudoType() == CSSPseudoElementType::AnonBox &&
!nsCSSAnonBoxes::IsNonElement(aContext->GetPseudo()) &&
!RubyUtils::IsRubyPseudo(aContext->GetPseudo())) {
return false;
}
if (aParentContext->ShouldSuppressLineBreak()) {
// Line break suppressing bit is propagated to any children of
// line participants, which include inline, contents, and inline
// ruby boxes.
if (aParentDisplay->mDisplay == mozilla::StyleDisplay::Inline ||
aParentDisplay->mDisplay == mozilla::StyleDisplay::Contents ||
aParentDisplay->mDisplay == mozilla::StyleDisplay::Ruby ||
aParentDisplay->mDisplay == mozilla::StyleDisplay::RubyBaseContainer) {
return true;
}
}
// Any descendant of ruby level containers is non-breakable, but
// the level containers themselves are breakable. We have to check
// the container display type against all ruby display type here
// because any of the ruby boxes could be anonymous.
// Note that, when certain HTML tags, e.g. form controls, have ruby
// level container display type, they could also escape from this flag
// while they shouldn't. However, it is generally fine since they
// won't usually break the assertion that there is no line break
// inside ruby, because:
// 1. their display types, the ruby level container types, are inline-
// outside, which means they won't cause any forced line break; and
// 2. they never start an inline span, which means their children, if
// any, won't be able to break the line its ruby ancestor lays; and
// 3. their parent frame is always a ruby content frame (due to
// anonymous ruby box generation), which makes line layout suppress
// any optional line break around this frame.
// However, there is one special case which is BR tag, because it
// directly affects the line layout. This case is handled by the BR
// frame which checks the flag of its parent frame instead of itself.
if ((aParentDisplay->IsRubyDisplayType() &&
aDisplay->mDisplay != mozilla::StyleDisplay::RubyBaseContainer &&
aDisplay->mDisplay != mozilla::StyleDisplay::RubyTextContainer) ||
// Since ruby base and ruby text may exist themselves without any
// non-anonymous frame outside, we should also check them.
aDisplay->mDisplay == mozilla::StyleDisplay::RubyBase ||
aDisplay->mDisplay == mozilla::StyleDisplay::RubyText) {
return true;
}
return false;
}
// Flex & grid containers blockify their children.
// "The display value of a flex item is blockified"
// https://drafts.csswg.org/css-flexbox-1/#flex-items
// "The display value of a grid item is blockified"
// https://drafts.csswg.org/css-grid/#grid-items
static bool
ShouldBlockifyChildren(const nsStyleDisplay* aStyleDisp)
{
auto displayVal = aStyleDisp->mDisplay;
return mozilla::StyleDisplay::Flex == displayVal ||
mozilla::StyleDisplay::InlineFlex == displayVal ||
mozilla::StyleDisplay::Grid == displayVal ||
mozilla::StyleDisplay::InlineGrid == displayVal;
}
void
nsStyleContext::SetStyleBits()
{
// XXXbholley: We should get this information directly from the
// ServoComputedValues rather than computing it here. This setup for
// ServoComputedValues-backed nsStyleContexts is probably not something
// we should ship.
//
// For example, NS_STYLE_IS_TEXT_COMBINED is still set in ApplyStyleFixups,
// which isn't called for ServoComputedValues.
// See if we have any text decorations.
// First see if our parent has text decorations. If our parent does, then we inherit the bit.
if (mParent && mParent->HasTextDecorationLines()) {
mBits |= NS_STYLE_HAS_TEXT_DECORATION_LINES;
} else {
// We might have defined a decoration.
if (StyleTextReset()->HasTextDecorationLines()) {
mBits |= NS_STYLE_HAS_TEXT_DECORATION_LINES;
}
}
if ((mParent && mParent->HasPseudoElementData()) || IsPseudoElement()) {
mBits |= NS_STYLE_HAS_PSEUDO_ELEMENT_DATA;
}
// Set the NS_STYLE_IN_DISPLAY_NONE_SUBTREE bit
const nsStyleDisplay* disp = StyleDisplay();
if ((mParent && mParent->IsInDisplayNoneSubtree()) ||
disp->mDisplay == mozilla::StyleDisplay::None) {
mBits |= NS_STYLE_IN_DISPLAY_NONE_SUBTREE;
}
}
void
nsStyleContext::ApplyStyleFixups(bool aSkipParentDisplayBasedStyleFixup)
{
MOZ_ASSERT(!mSource.IsServoComputedValues(),
"Can't do Gecko style fixups on Servo values");
#define GET_UNIQUE_STYLE_DATA(name_) \
static_cast<nsStyle##name_*>(GetUniqueStyleData(eStyleStruct_##name_))
// CSS Inline Layout Level 3 - 3.5 Sizing Initial Letters:
// For an N-line drop initial in a Western script, the cap-height of the
// letter needs to be (N 1) times the line-height, plus the cap-height
// of the surrounding text.
if (mPseudoTag == nsCSSPseudoElements::firstLetter) {
const nsStyleTextReset* textReset = StyleTextReset();
if (textReset->mInitialLetterSize != 0.0f) {
nsStyleContext* containerSC = mParent;
const nsStyleDisplay* containerDisp = containerSC->StyleDisplay();
while (containerDisp->mDisplay == mozilla::StyleDisplay::Contents) {
if (!containerSC->GetParent()) {
break;
}
containerSC = containerSC->GetParent();
containerDisp = containerSC->StyleDisplay();
}
nscoord containerLH =
ReflowInput::CalcLineHeight(nullptr, containerSC, NS_AUTOHEIGHT, 1.0f);
RefPtr<nsFontMetrics> containerFM =
nsLayoutUtils::GetFontMetricsForStyleContext(containerSC);
MOZ_ASSERT(containerFM, "Should have fontMetrics!!");
nscoord containerCH = containerFM->CapHeight();
RefPtr<nsFontMetrics> firstLetterFM =
nsLayoutUtils::GetFontMetricsForStyleContext(this);
MOZ_ASSERT(firstLetterFM, "Should have fontMetrics!!");
nscoord firstLetterCH = firstLetterFM->CapHeight();
nsStyleFont* mutableStyleFont = GET_UNIQUE_STYLE_DATA(Font);
float invCapHeightRatio =
mutableStyleFont->mFont.size / NSCoordToFloat(firstLetterCH);
mutableStyleFont->mFont.size =
NSToCoordRound(((textReset->mInitialLetterSize - 1) * containerLH +
containerCH) *
invCapHeightRatio);
}
}
// Change writing mode of text frame for text-combine-upright. We use
// style structs of the parent to avoid triggering computation before
// we change the writing mode.
// It is safe to look at the parent's style because we are looking at
// inherited properties, and ::-moz-text never matches any rules.
if (mPseudoTag == nsCSSAnonBoxes::mozText && mParent &&
mParent->StyleVisibility()->mWritingMode !=
NS_STYLE_WRITING_MODE_HORIZONTAL_TB &&
mParent->StyleText()->mTextCombineUpright ==
NS_STYLE_TEXT_COMBINE_UPRIGHT_ALL) {
MOZ_ASSERT(!PeekStyleVisibility(), "If StyleVisibility was already "
"computed, some properties may have been computed "
"incorrectly based on the old writing mode value");
nsStyleVisibility* mutableVis = GET_UNIQUE_STYLE_DATA(Visibility);
mutableVis->mWritingMode = NS_STYLE_WRITING_MODE_HORIZONTAL_TB;
AddStyleBit(NS_STYLE_IS_TEXT_COMBINED);
}
// CSS 2.1 10.1: Propagate the root element's 'direction' to the ICB.
// (PageContentFrame/CanvasFrame etc will inherit 'direction')
if (mPseudoTag == nsCSSAnonBoxes::viewport) {
nsPresContext* presContext = PresContext();
mozilla::dom::Element* docElement = presContext->Document()->GetRootElement();
if (docElement) {
RefPtr<nsStyleContext> rootStyle =
presContext->StyleSet()->ResolveStyleFor(docElement, nullptr);
auto dir = rootStyle->StyleVisibility()->mDirection;
if (dir != StyleVisibility()->mDirection) {
nsStyleVisibility* uniqueVisibility = GET_UNIQUE_STYLE_DATA(Visibility);
uniqueVisibility->mDirection = dir;
}
}
}
// Correct tables.
const nsStyleDisplay* disp = StyleDisplay();
if (disp->mDisplay == mozilla::StyleDisplay::Table) {
// -moz-center and -moz-right are used for HTML's alignment
// This is covering the <div align="right"><table>...</table></div> case.
// In this case, we don't want to inherit the text alignment into the table.
const nsStyleText* text = StyleText();
if (text->mTextAlign == NS_STYLE_TEXT_ALIGN_MOZ_LEFT ||
text->mTextAlign == NS_STYLE_TEXT_ALIGN_MOZ_CENTER ||
text->mTextAlign == NS_STYLE_TEXT_ALIGN_MOZ_RIGHT)
{
nsStyleText* uniqueText = GET_UNIQUE_STYLE_DATA(Text);
uniqueText->mTextAlign = NS_STYLE_TEXT_ALIGN_START;
}
}
// CSS2.1 section 9.2.4 specifies fixups for the 'display' property of
// the root element. We can't implement them in nsRuleNode because we
// don't want to store all display structs that aren't 'block',
// 'inline', or 'table' in the style context tree on the off chance
// that the root element has its style reresolved later. So do them
// here if needed, by changing the style data, so that other code
// doesn't get confused by looking at the style data.
if (!mParent) {
auto displayVal = disp->mDisplay;
if (displayVal != mozilla::StyleDisplay::Contents) {
nsRuleNode::EnsureBlockDisplay(displayVal, true);
} else {
// http://dev.w3.org/csswg/css-display/#transformations
// "... a display-outside of 'contents' computes to block-level
// on the root element."
displayVal = mozilla::StyleDisplay::Block;
}
if (displayVal != disp->mDisplay) {
nsStyleDisplay* mutable_display = GET_UNIQUE_STYLE_DATA(Display);
disp = mutable_display;
// If we're in this code, then mOriginalDisplay doesn't matter
// for purposes of the cascade (because this nsStyleDisplay
// isn't living in the ruletree anyway), and for determining
// hypothetical boxes it's better to have mOriginalDisplay
// matching mDisplay here.
mutable_display->mOriginalDisplay = mutable_display->mDisplay =
displayVal;
}
}
// Adjust the "display" values of flex and grid items (but not for raw text
// or placeholders). CSS3 Flexbox section 4 says:
// # The computed 'display' of a flex item is determined
// # by applying the table in CSS 2.1 Chapter 9.7.
// ...which converts inline-level elements to their block-level equivalents.
// Any block-level element directly contained by elements with ruby display
// values are converted to their inline-level equivalents.
if (!aSkipParentDisplayBasedStyleFixup && mParent) {
// Skip display:contents ancestors to reach the potential container.
// (If there are only display:contents ancestors between this node and
// a flex/grid container ancestor, then this node is a flex/grid item, since
// its parent *in the frame tree* will be the flex/grid container. So we treat
// it like a flex/grid item here.)
nsStyleContext* containerContext = mParent;
const nsStyleDisplay* containerDisp = containerContext->StyleDisplay();
while (containerDisp->mDisplay == mozilla::StyleDisplay::Contents) {
if (!containerContext->GetParent()) {
break;
}
containerContext = containerContext->GetParent();
containerDisp = containerContext->StyleDisplay();
}
if (ShouldBlockifyChildren(containerDisp) &&
!nsCSSAnonBoxes::IsNonElement(GetPseudo())) {
// NOTE: Technically, we shouldn't modify the 'display' value of
// positioned elements, since they aren't flex/grid items. However,
// we don't need to worry about checking for that, because if we're
// positioned, we'll have already been through a call to
// EnsureBlockDisplay() in nsRuleNode, so this call here won't change
// anything. So we're OK.
auto displayVal = disp->mDisplay;
nsRuleNode::EnsureBlockDisplay(displayVal);
if (displayVal != disp->mDisplay) {
NS_ASSERTION(!disp->IsAbsolutelyPositionedStyle(),
"We shouldn't be changing the display value of "
"positioned content (and we should have already "
"converted its display value to be block-level...)");
nsStyleDisplay* mutable_display = GET_UNIQUE_STYLE_DATA(Display);
disp = mutable_display;
mutable_display->mDisplay = displayVal;
}
}
}
// Note: This must come after the blockification above, otherwise we fail
// the grid-item-blockifying-001.html reftest.
if (mParent && ::ShouldSuppressLineBreak(this, disp, mParent,
mParent->StyleDisplay())) {
mBits |= NS_STYLE_SUPPRESS_LINEBREAK;
auto displayVal = disp->mDisplay;
nsRuleNode::EnsureInlineDisplay(displayVal);
if (displayVal != disp->mDisplay) {
nsStyleDisplay* mutable_display = GET_UNIQUE_STYLE_DATA(Display);
disp = mutable_display;
mutable_display->mDisplay = displayVal;
}
}
// Suppress border/padding of ruby level containers
if (disp->mDisplay == mozilla::StyleDisplay::RubyBaseContainer ||
disp->mDisplay == mozilla::StyleDisplay::RubyTextContainer) {
CreateEmptyStyleData(eStyleStruct_Border);
CreateEmptyStyleData(eStyleStruct_Padding);
}
if (disp->IsRubyDisplayType()) {
// Per CSS Ruby spec section Bidi Reordering, for all ruby boxes,
// the 'normal' and 'embed' values of 'unicode-bidi' should compute to
// 'isolate', and 'bidi-override' should compute to 'isolate-override'.
const nsStyleTextReset* textReset = StyleTextReset();
uint8_t unicodeBidi = textReset->mUnicodeBidi;
if (unicodeBidi == NS_STYLE_UNICODE_BIDI_NORMAL ||
unicodeBidi == NS_STYLE_UNICODE_BIDI_EMBED) {
unicodeBidi = NS_STYLE_UNICODE_BIDI_ISOLATE;
} else if (unicodeBidi == NS_STYLE_UNICODE_BIDI_BIDI_OVERRIDE) {
unicodeBidi = NS_STYLE_UNICODE_BIDI_ISOLATE_OVERRIDE;
}
if (unicodeBidi != textReset->mUnicodeBidi) {
nsStyleTextReset* mutableTextReset = GET_UNIQUE_STYLE_DATA(TextReset);
mutableTextReset->mUnicodeBidi = unicodeBidi;
}
}
/*
* According to https://drafts.csswg.org/css-writing-modes-3/#block-flow:
*
* If a box has a different block flow direction than its containing block:
* * If the box has a specified display of inline, its display computes
* to inline-block. [CSS21]
* ...etc.
*/
if (disp->mDisplay == mozilla::StyleDisplay::Inline &&
!nsCSSAnonBoxes::IsNonElement(mPseudoTag) &&
mParent) {
auto cbContext = mParent;
while (cbContext->StyleDisplay()->mDisplay == mozilla::StyleDisplay::Contents) {
cbContext = cbContext->mParent;
}
MOZ_ASSERT(cbContext, "the root context can't have display:contents");
// We don't need the full mozilla::WritingMode value (incorporating dir
// and text-orientation) here; just the writing-mode property is enough.
if (StyleVisibility()->mWritingMode !=
cbContext->StyleVisibility()->mWritingMode) {
nsStyleDisplay* mutable_display = GET_UNIQUE_STYLE_DATA(Display);
disp = mutable_display;
mutable_display->mOriginalDisplay = mutable_display->mDisplay =
mozilla::StyleDisplay::InlineBlock;
}
}
// Compute User Interface style, to trigger loads of cursors
StyleUserInterface();
#undef GET_UNIQUE_STYLE_DATA
}
template<class StyleContextLike>
nsChangeHint
nsStyleContext::CalcStyleDifferenceInternal(StyleContextLike* aNewContext,
nsChangeHint aParentHintsNotHandledForDescendants,
uint32_t* aEqualStructs,
uint32_t* aSamePointerStructs)
{
PROFILER_LABEL("nsStyleContext", "CalcStyleDifference",
js::ProfileEntry::Category::CSS);
MOZ_ASSERT(NS_IsHintSubset(aParentHintsNotHandledForDescendants,
nsChangeHint_Hints_NotHandledForDescendants),
"caller is passing inherited hints, but shouldn't be");
static_assert(nsStyleStructID_Length <= 32,
"aEqualStructs is not big enough");
*aEqualStructs = 0;
nsChangeHint hint = nsChangeHint(0);
NS_ENSURE_TRUE(aNewContext, hint);
// We must always ensure that we populate the structs on the new style
// context that are filled in on the old context, so that if we get
// two style changes in succession, the second of which causes a real
// style change, the PeekStyleData doesn't return null (implying that
// nobody ever looked at that struct's data). In other words, we
// can't skip later structs if we get a big change up front, because
// we could later get a small change in one of those structs that we
// don't want to miss.
// If our sources are the same, then any differences in style data
// are already accounted for by differences on ancestors. We know
// this because CalcStyleDifference is always called on two style
// contexts that point to the same element, so we know that our
// position in the style context tree is the same and our position in
// the rule node tree (if applicable) is also the same.
// However, if there were noninherited style change hints on the
// parent, we might produce these same noninherited hints on this
// style context's frame due to 'inherit' values, so we do need to
// compare.
// (Things like 'em' units are handled by the change hint produced
// by font-size changing, so we don't need to worry about them like
// we worry about 'inherit' values.)
bool compare = StyleSource() != aNewContext->StyleSource();
DebugOnly<uint32_t> structsFound = 0;
// If we had any change in variable values, then we'll need to examine
// all of the other style structs too, even if the new style context has
// the same source as the old one.
const nsStyleVariables* thisVariables = PeekStyleVariables();
if (thisVariables) {
structsFound |= NS_STYLE_INHERIT_BIT(Variables);
const nsStyleVariables* otherVariables = aNewContext->StyleVariables();
if (thisVariables->mVariables == otherVariables->mVariables) {
*aEqualStructs |= NS_STYLE_INHERIT_BIT(Variables);
} else {
compare = true;
}
} else {
*aEqualStructs |= NS_STYLE_INHERIT_BIT(Variables);
}
DebugOnly<int> styleStructCount = 1; // count Variables already
#define DO_STRUCT_DIFFERENCE(struct_) \
PR_BEGIN_MACRO \
const nsStyle##struct_* this##struct_ = PeekStyle##struct_(); \
if (this##struct_) { \
structsFound |= NS_STYLE_INHERIT_BIT(struct_); \
const nsStyle##struct_* other##struct_ = aNewContext->Style##struct_(); \
nsChangeHint maxDifference = nsStyle##struct_::MaxDifference(); \
nsChangeHint differenceAlwaysHandledForDescendants = \
nsStyle##struct_::DifferenceAlwaysHandledForDescendants(); \
if (this##struct_ == other##struct_) { \
/* The very same struct, so we know that there will be no */ \
/* differences. */ \
*aEqualStructs |= NS_STYLE_INHERIT_BIT(struct_); \
} else if (compare || \
((maxDifference & ~differenceAlwaysHandledForDescendants) & \
aParentHintsNotHandledForDescendants)) { \
nsChangeHint difference = \
this##struct_->CalcDifference(*other##struct_ EXTRA_DIFF_ARGS); \
NS_ASSERTION(NS_IsHintSubset(difference, maxDifference), \
"CalcDifference() returned bigger hint than " \
"MaxDifference()"); \
hint |= difference; \
if (!difference) { \
*aEqualStructs |= NS_STYLE_INHERIT_BIT(struct_); \
} \
} else { \
/* We still must call CalcDifference to see if there were any */ \
/* changes so that we can set *aEqualStructs appropriately. */ \
nsChangeHint difference = \
this##struct_->CalcDifference(*other##struct_ EXTRA_DIFF_ARGS); \
NS_ASSERTION(NS_IsHintSubset(difference, maxDifference), \
"CalcDifference() returned bigger hint than " \
"MaxDifference()"); \
if (!difference) { \
*aEqualStructs |= NS_STYLE_INHERIT_BIT(struct_); \
} \
} \
} else { \
*aEqualStructs |= NS_STYLE_INHERIT_BIT(struct_); \
} \
styleStructCount++; \
PR_END_MACRO
// In general, we want to examine structs starting with those that can
// cause the largest style change, down to those that can cause the
// smallest. This lets us skip later ones if we already have a hint
// that subsumes their MaxDifference. (As the hints get
// finer-grained, this optimization is becoming less useful, though.)
#define EXTRA_DIFF_ARGS /* nothing */
DO_STRUCT_DIFFERENCE(Display);
DO_STRUCT_DIFFERENCE(XUL);
DO_STRUCT_DIFFERENCE(Column);
DO_STRUCT_DIFFERENCE(Content);
DO_STRUCT_DIFFERENCE(UserInterface);
DO_STRUCT_DIFFERENCE(Visibility);
DO_STRUCT_DIFFERENCE(Outline);
DO_STRUCT_DIFFERENCE(TableBorder);
DO_STRUCT_DIFFERENCE(Table);
DO_STRUCT_DIFFERENCE(UIReset);
DO_STRUCT_DIFFERENCE(Text);
DO_STRUCT_DIFFERENCE(List);
DO_STRUCT_DIFFERENCE(SVGReset);
DO_STRUCT_DIFFERENCE(SVG);
#undef EXTRA_DIFF_ARGS
#define EXTRA_DIFF_ARGS , PeekStyleVisibility()
DO_STRUCT_DIFFERENCE(Position);
#undef EXTRA_DIFF_ARGS
#define EXTRA_DIFF_ARGS /* nothing */
DO_STRUCT_DIFFERENCE(Font);
DO_STRUCT_DIFFERENCE(Margin);
DO_STRUCT_DIFFERENCE(Padding);
DO_STRUCT_DIFFERENCE(Border);
DO_STRUCT_DIFFERENCE(TextReset);
DO_STRUCT_DIFFERENCE(Effects);
DO_STRUCT_DIFFERENCE(Background);
DO_STRUCT_DIFFERENCE(Color);
#undef EXTRA_DIFF_ARGS
#undef DO_STRUCT_DIFFERENCE
MOZ_ASSERT(styleStructCount == nsStyleStructID_Length,
"missing a call to DO_STRUCT_DIFFERENCE");
#ifdef DEBUG
#define STYLE_STRUCT(name_, callback_) \
MOZ_ASSERT(!!(structsFound & NS_STYLE_INHERIT_BIT(name_)) == \
!!PeekStyle##name_(), \
"PeekStyleData results must not change in the middle of " \
"difference calculation.");
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
#endif
// We check for struct pointer equality here rather than as part of the
// DO_STRUCT_DIFFERENCE calls, since those calls can result in structs
// we previously examined and found to be null on this style context
// getting computed by later DO_STRUCT_DIFFERENCE calls (which can
// happen when the nsRuleNode::ComputeXXXData method looks up another
// struct.) This is important for callers in RestyleManager that
// need to know the equality or not of the final set of cached struct
// pointers.
*aSamePointerStructs = 0;
#define STYLE_STRUCT(name_, callback_) \
{ \
const nsStyle##name_* data = PeekStyle##name_(); \
if (!data || data == aNewContext->Style##name_()) { \
*aSamePointerStructs |= NS_STYLE_INHERIT_BIT(name_); \
} \
}
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
// Note that we do not check whether this->RelevantLinkVisited() !=
// aNewContext->RelevantLinkVisited(); we don't need to since
// nsCSSFrameConstructor::DoContentStateChanged always adds
// nsChangeHint_RepaintFrame for NS_EVENT_STATE_VISITED changes (and
// needs to, since HasStateDependentStyle probably doesn't work right
// for NS_EVENT_STATE_VISITED). Hopefully this doesn't actually
// expose whether links are visited to performance tests since all
// link coloring happens asynchronously at a time when it's hard for
// the page to measure.
// However, we do need to compute the larger of the changes that can
// happen depending on whether the link is visited or unvisited, since
// doing only the one that's currently appropriate would expose which
// links are in history to easy performance measurement. Therefore,
// here, we add nsChangeHint_RepaintFrame hints (the maximum for
// things that can depend on :visited) for the properties on which we
// call GetVisitedDependentColor.
nsStyleContext *thisVis = GetStyleIfVisited(),
*otherVis = aNewContext->GetStyleIfVisited();
if (!thisVis != !otherVis) {
// One style context has a style-if-visited and the other doesn't.
// Presume a difference.
hint |= nsChangeHint_RepaintFrame;
} else if (thisVis && !NS_IsHintSubset(nsChangeHint_RepaintFrame, hint)) {
// Both style contexts have a style-if-visited.
bool change = false;
// NB: Calling Peek on |this|, not |thisVis|, since callers may look
// at a struct on |this| without looking at the same struct on
// |thisVis| (including this function if we skip one of these checks
// due to change being true already or due to the old style context
// not having a style-if-visited), but not the other way around.
if (PeekStyleColor()) {
if (thisVis->StyleColor()->mColor !=
otherVis->StyleColor()->mColor) {
change = true;
}
}
// NB: Calling Peek on |this|, not |thisVis| (see above).
if (!change && PeekStyleBackground()) {
if (thisVis->StyleBackground()->mBackgroundColor !=
otherVis->StyleBackground()->mBackgroundColor) {
change = true;
}
}
// NB: Calling Peek on |this|, not |thisVis| (see above).
if (!change && PeekStyleBorder()) {
const nsStyleBorder *thisVisBorder = thisVis->StyleBorder();
const nsStyleBorder *otherVisBorder = otherVis->StyleBorder();
NS_FOR_CSS_SIDES(side) {
if (thisVisBorder->mBorderColor[side] !=
otherVisBorder->mBorderColor[side]) {
change = true;
break;
}
}
}
// NB: Calling Peek on |this|, not |thisVis| (see above).
if (!change && PeekStyleOutline()) {
const nsStyleOutline *thisVisOutline = thisVis->StyleOutline();
const nsStyleOutline *otherVisOutline = otherVis->StyleOutline();
if (thisVisOutline->mOutlineColor != otherVisOutline->mOutlineColor) {
change = true;
}
}
// NB: Calling Peek on |this|, not |thisVis| (see above).
if (!change && PeekStyleColumn()) {
const nsStyleColumn *thisVisColumn = thisVis->StyleColumn();
const nsStyleColumn *otherVisColumn = otherVis->StyleColumn();
if (thisVisColumn->mColumnRuleColor != otherVisColumn->mColumnRuleColor) {
change = true;
}
}
// NB: Calling Peek on |this|, not |thisVis| (see above).
if (!change && PeekStyleText()) {
const nsStyleText* thisVisText = thisVis->StyleText();
const nsStyleText* otherVisText = otherVis->StyleText();
if (thisVisText->mTextEmphasisColor != otherVisText->mTextEmphasisColor ||
thisVisText->mWebkitTextFillColor != otherVisText->mWebkitTextFillColor ||
thisVisText->mWebkitTextStrokeColor != otherVisText->mWebkitTextStrokeColor) {
change = true;
}
}
// NB: Calling Peek on |this|, not |thisVis| (see above).
if (!change && PeekStyleTextReset()) {
const nsStyleTextReset *thisVisTextReset = thisVis->StyleTextReset();
const nsStyleTextReset *otherVisTextReset = otherVis->StyleTextReset();
if (thisVisTextReset->mTextDecorationColor !=
otherVisTextReset->mTextDecorationColor) {
change = true;
}
}
// NB: Calling Peek on |this|, not |thisVis| (see above).
if (!change && PeekStyleSVG()) {
const nsStyleSVG *thisVisSVG = thisVis->StyleSVG();
const nsStyleSVG *otherVisSVG = otherVis->StyleSVG();
if (thisVisSVG->mFill != otherVisSVG->mFill ||
thisVisSVG->mStroke != otherVisSVG->mStroke) {
change = true;
}
}
// NB: Calling Peek on |this|, not |thisVis| (see above).
if (!change && PeekStyleUserInterface()) {
const nsStyleUserInterface *thisVisUserInterface = thisVis->StyleUserInterface();
const nsStyleUserInterface *otherVisUserInterface = otherVis->StyleUserInterface();
if (thisVisUserInterface->mCaretColor !=
otherVisUserInterface->mCaretColor) {
change = true;
}
}
if (change) {
hint |= nsChangeHint_RepaintFrame;
}
}
if (hint & nsChangeHint_UpdateContainingBlock) {
// If a struct returned nsChangeHint_UpdateContainingBlock, that
// means that one property's influence on whether we're a containing
// block for abs-pos or fixed-pos elements has changed. However, we
// only need to return the hint if the overall computation of
// whether we establish a containing block has changed.
// This depends on data in nsStyleDisplay and nsStyleEffects, so we
// do it here.
// Note that it's perhaps good for this test to be last because it
// doesn't use Peek* functions to get the structs on the old
// context. But this isn't a big concern because these struct
// getters should be called during frame construction anyway.
if (StyleDisplay()->IsAbsPosContainingBlockForAppropriateFrame(this) ==
aNewContext->StyleDisplay()->
IsAbsPosContainingBlockForAppropriateFrame(aNewContext) &&
StyleDisplay()->IsFixedPosContainingBlockForAppropriateFrame(this) ==
aNewContext->StyleDisplay()->
IsFixedPosContainingBlockForAppropriateFrame(aNewContext)) {
// While some styles that cause the frame to be a containing block
// has changed, the overall result hasn't.
hint &= ~nsChangeHint_UpdateContainingBlock;
}
}
MOZ_ASSERT(NS_IsHintSubset(hint, nsChangeHint_AllHints),
"Added a new hint without bumping AllHints?");
return hint & ~nsChangeHint_NeutralChange;
}
nsChangeHint
nsStyleContext::CalcStyleDifference(nsStyleContext* aNewContext,
nsChangeHint aParentHintsNotHandledForDescendants,
uint32_t* aEqualStructs,
uint32_t* aSamePointerStructs)
{
return CalcStyleDifferenceInternal(aNewContext, aParentHintsNotHandledForDescendants,
aEqualStructs, aSamePointerStructs);
}
class MOZ_STACK_CLASS FakeStyleContext
{
public:
explicit FakeStyleContext(const ServoComputedValues* aComputedValues)
: mComputedValues(aComputedValues) {}
mozilla::NonOwningStyleContextSource StyleSource() const {
return mozilla::NonOwningStyleContextSource(mComputedValues);
}
nsStyleContext* GetStyleIfVisited() {
// XXXbholley: This is wrong. Need to implement to get visited handling
// corrrect!
return nullptr;
}
#define STYLE_STRUCT(name_, checkdata_cb_) \
const nsStyle##name_ * Style##name_() { \
return Servo_GetStyle##name_(mComputedValues); \
}
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
private:
const ServoComputedValues* MOZ_NON_OWNING_REF mComputedValues;
};
nsChangeHint
nsStyleContext::CalcStyleDifference(const ServoComputedValues* aNewComputedValues,
nsChangeHint aParentHintsNotHandledForDescendants,
uint32_t* aEqualStructs,
uint32_t* aSamePointerStructs)
{
FakeStyleContext newContext(aNewComputedValues);
return CalcStyleDifferenceInternal(&newContext, aParentHintsNotHandledForDescendants,
aEqualStructs, aSamePointerStructs);
}
#ifdef DEBUG
void nsStyleContext::List(FILE* out, int32_t aIndent, bool aListDescendants)
{
nsAutoCString str;
// Indent
int32_t ix;
for (ix = aIndent; --ix >= 0; ) {
str.AppendLiteral(" ");
}
str.Append(nsPrintfCString("%p(%d) parent=%p ",
(void*)this, mRefCnt, (void *)mParent));
if (mPseudoTag) {
nsAutoString buffer;
mPseudoTag->ToString(buffer);
AppendUTF16toUTF8(buffer, str);
str.Append(' ');
}
if (mSource.IsServoComputedValues()) {
fprintf_stderr(out, "%s{ServoComputedValues}\n", str.get());
} else if (mSource.IsGeckoRuleNode()) {
fprintf_stderr(out, "%s{\n", str.get());
str.Truncate();
nsRuleNode* ruleNode = mSource.AsGeckoRuleNode();
while (ruleNode) {
nsIStyleRule *styleRule = ruleNode->GetRule();
if (styleRule) {
styleRule->List(out, aIndent + 1);
}
ruleNode = ruleNode->GetParent();
}
for (ix = aIndent; --ix >= 0; ) {
str.AppendLiteral(" ");
}
fprintf_stderr(out, "%s}\n", str.get());
}
else {
fprintf_stderr(out, "%s{}\n", str.get());
}
if (aListDescendants) {
if (nullptr != mChild) {
nsStyleContext* child = mChild;
do {
child->List(out, aIndent + 1, aListDescendants);
child = child->mNextSibling;
} while (mChild != child);
}
if (nullptr != mEmptyChild) {
nsStyleContext* child = mEmptyChild;
do {
child->List(out, aIndent + 1, aListDescendants);
child = child->mNextSibling;
} while (mEmptyChild != child);
}
}
}
#endif
// Overloaded new operator. Initializes the memory to 0 and relies on an arena
// (which comes from the presShell) to perform the allocation.
void*
nsStyleContext::operator new(size_t sz, nsPresContext* aPresContext)
{
// Check the recycle list first.
return aPresContext->PresShell()->
AllocateByObjectID(eArenaObjectID_nsStyleContext, sz);
}
// Overridden to prevent the global delete from being called, since the memory
// came out of an nsIArena instead of the global delete operator's heap.
void
nsStyleContext::Destroy()
{
// Get the pres context.
RefPtr<nsPresContext> presContext = PresContext();
// Call our destructor.
this->~nsStyleContext();
// Don't let the memory be freed, since it will be recycled
// instead. Don't call the global operator delete.
presContext->PresShell()->
FreeByObjectID(eArenaObjectID_nsStyleContext, this);
}
already_AddRefed<nsStyleContext>
NS_NewStyleContext(nsStyleContext* aParentContext,
nsIAtom* aPseudoTag,
CSSPseudoElementType aPseudoType,
nsRuleNode* aRuleNode,
bool aSkipParentDisplayBasedStyleFixup)
{
RefPtr<nsRuleNode> node = aRuleNode;
RefPtr<nsStyleContext> context =
new (aRuleNode->PresContext())
nsStyleContext(aParentContext, aPseudoTag, aPseudoType, node.forget(),
aSkipParentDisplayBasedStyleFixup);
return context.forget();
}
already_AddRefed<nsStyleContext>
NS_NewStyleContext(nsStyleContext* aParentContext,
nsPresContext* aPresContext,
nsIAtom* aPseudoTag,
CSSPseudoElementType aPseudoType,
already_AddRefed<ServoComputedValues> aComputedValues,
bool aSkipParentDisplayBasedStyleFixup)
{
RefPtr<nsStyleContext> context =
new (aPresContext)
nsStyleContext(aParentContext, aPresContext, aPseudoTag, aPseudoType,
Move(aComputedValues), aSkipParentDisplayBasedStyleFixup);
return context.forget();
}
nsIPresShell*
nsStyleContext::Arena()
{
return PresContext()->PresShell();
}
static inline void
ExtractAnimationValue(nsCSSPropertyID aProperty,
nsStyleContext* aStyleContext,
StyleAnimationValue& aResult)
{
DebugOnly<bool> success =
StyleAnimationValue::ExtractComputedValue(aProperty, aStyleContext,
aResult);
MOZ_ASSERT(success,
"aProperty must be extractable by StyleAnimationValue");
}
static Maybe<nscolor>
ExtractColor(nsCSSPropertyID aProperty,
nsStyleContext *aStyleContext)
{
StyleAnimationValue val;
ExtractAnimationValue(aProperty, aStyleContext, val);
switch (val.GetUnit()) {
case StyleAnimationValue::eUnit_Color:
return Some(val.GetCSSValueValue()->GetColorValue());
case StyleAnimationValue::eUnit_CurrentColor:
return Some(aStyleContext->StyleColor()->mColor);
case StyleAnimationValue::eUnit_ComplexColor:
return Some(aStyleContext->StyleColor()->
CalcComplexColor(val.GetStyleComplexColorValue()));
case StyleAnimationValue::eUnit_Auto:
return Some(aStyleContext->StyleColor()->
CalcComplexColor(StyleComplexColor::Auto()));
default:
return Nothing();
}
}
static nscolor
ExtractColorLenient(nsCSSPropertyID aProperty,
nsStyleContext *aStyleContext)
{
return ExtractColor(aProperty, aStyleContext).valueOr(NS_RGBA(0, 0, 0, 0));
}
struct ColorIndexSet {
uint8_t colorIndex, alphaIndex;
};
static const ColorIndexSet gVisitedIndices[2] = { { 0, 0 }, { 1, 0 } };
nscolor
nsStyleContext::GetVisitedDependentColor(nsCSSPropertyID aProperty)
{
NS_ASSERTION(aProperty == eCSSProperty_caret_color ||
aProperty == eCSSProperty_color ||
aProperty == eCSSProperty_background_color ||
aProperty == eCSSProperty_border_top_color ||
aProperty == eCSSProperty_border_right_color ||
aProperty == eCSSProperty_border_bottom_color ||
aProperty == eCSSProperty_border_left_color ||
aProperty == eCSSProperty_outline_color ||
aProperty == eCSSProperty_column_rule_color ||
aProperty == eCSSProperty_text_decoration_color ||
aProperty == eCSSProperty_text_emphasis_color ||
aProperty == eCSSProperty__webkit_text_fill_color ||
aProperty == eCSSProperty__webkit_text_stroke_color ||
aProperty == eCSSProperty_fill ||
aProperty == eCSSProperty_stroke,
"we need to add to nsStyleContext::CalcStyleDifference");
bool isPaintProperty = aProperty == eCSSProperty_fill ||
aProperty == eCSSProperty_stroke;
nscolor colors[2];
colors[0] = isPaintProperty ? ExtractColorLenient(aProperty, this)
: ExtractColor(aProperty, this).value();
nsStyleContext *visitedStyle = this->GetStyleIfVisited();
if (!visitedStyle) {
return colors[0];
}
colors[1] = isPaintProperty ? ExtractColorLenient(aProperty, visitedStyle)
: ExtractColor(aProperty, visitedStyle).value();
return nsStyleContext::CombineVisitedColors(colors,
this->RelevantLinkVisited());
}
/* static */ nscolor
nsStyleContext::CombineVisitedColors(nscolor *aColors, bool aLinkIsVisited)
{
if (NS_GET_A(aColors[1]) == 0) {
// If the style-if-visited is transparent, then just use the
// unvisited style rather than using the (meaningless) color
// components of the visited style along with a potentially
// non-transparent alpha value.
aLinkIsVisited = false;
}
// NOTE: We want this code to have as little timing dependence as
// possible on whether this->RelevantLinkVisited() is true.
const ColorIndexSet &set =
gVisitedIndices[aLinkIsVisited ? 1 : 0];
nscolor colorColor = aColors[set.colorIndex];
nscolor alphaColor = aColors[set.alphaIndex];
return NS_RGBA(NS_GET_R(colorColor), NS_GET_G(colorColor),
NS_GET_B(colorColor), NS_GET_A(alphaColor));
}
#ifdef DEBUG
/* static */ void
nsStyleContext::AssertStyleStructMaxDifferenceValid()
{
#define STYLE_STRUCT(name, checkdata_cb) \
MOZ_ASSERT(NS_IsHintSubset(nsStyle##name::DifferenceAlwaysHandledForDescendants(), \
nsStyle##name::MaxDifference()));
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
}
/* static */ const char*
nsStyleContext::StructName(nsStyleStructID aSID)
{
switch (aSID) {
#define STYLE_STRUCT(name_, checkdata_cb) \
case eStyleStruct_##name_: \
return #name_;
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
default:
return "Unknown";
}
}
/* static */ bool
nsStyleContext::LookupStruct(const nsACString& aName, nsStyleStructID& aResult)
{
if (false)
;
#define STYLE_STRUCT(name_, checkdata_cb_) \
else if (aName.EqualsLiteral(#name_)) \
aResult = eStyleStruct_##name_;
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
else
return false;
return true;
}
#endif
void
nsStyleContext::SwapStyleData(nsStyleContext* aNewContext, uint32_t aStructs)
{
static_assert(nsStyleStructID_Length <= 32, "aStructs is not big enough");
for (nsStyleStructID i = nsStyleStructID_Inherited_Start;
i < nsStyleStructID_Inherited_Start + nsStyleStructID_Inherited_Count;
i = nsStyleStructID(i + 1)) {
uint32_t bit = nsCachedStyleData::GetBitForSID(i);
if (!(aStructs & bit)) {
continue;
}
void*& thisData = mCachedInheritedData.mStyleStructs[i];
void*& otherData = aNewContext->mCachedInheritedData.mStyleStructs[i];
if (mBits & bit) {
if (thisData == otherData) {
thisData = nullptr;
}
} else if (!(aNewContext->mBits & bit) && thisData && otherData) {
std::swap(thisData, otherData);
}
}
for (nsStyleStructID i = nsStyleStructID_Reset_Start;
i < nsStyleStructID_Reset_Start + nsStyleStructID_Reset_Count;
i = nsStyleStructID(i + 1)) {
uint32_t bit = nsCachedStyleData::GetBitForSID(i);
if (!(aStructs & bit)) {
continue;
}
if (!mCachedResetData) {
mCachedResetData = new (PresContext()) nsResetStyleData;
}
if (!aNewContext->mCachedResetData) {
aNewContext->mCachedResetData = new (PresContext()) nsResetStyleData;
}
void*& thisData = mCachedResetData->mStyleStructs[i];
void*& otherData = aNewContext->mCachedResetData->mStyleStructs[i];
if (mBits & bit) {
if (thisData == otherData) {
thisData = nullptr;
}
} else if (!(aNewContext->mBits & bit) && thisData && otherData) {
std::swap(thisData, otherData);
}
}
}
void
nsStyleContext::ClearCachedInheritedStyleDataOnDescendants(uint32_t aStructs)
{
if (mChild) {
nsStyleContext* child = mChild;
do {
child->DoClearCachedInheritedStyleDataOnDescendants(aStructs);
child = child->mNextSibling;
} while (mChild != child);
}
if (mEmptyChild) {
nsStyleContext* child = mEmptyChild;
do {
child->DoClearCachedInheritedStyleDataOnDescendants(aStructs);
child = child->mNextSibling;
} while (mEmptyChild != child);
}
}
void
nsStyleContext::DoClearCachedInheritedStyleDataOnDescendants(uint32_t aStructs)
{
NS_ASSERTION(mFrameRefCnt == 0, "frame still referencing style context");
for (nsStyleStructID i = nsStyleStructID_Inherited_Start;
i < nsStyleStructID_Inherited_Start + nsStyleStructID_Inherited_Count;
i = nsStyleStructID(i + 1)) {
uint32_t bit = nsCachedStyleData::GetBitForSID(i);
if (aStructs & bit) {
if (!(mBits & bit) && mCachedInheritedData.mStyleStructs[i]) {
aStructs &= ~bit;
} else {
mCachedInheritedData.mStyleStructs[i] = nullptr;
}
}
}
if (mCachedResetData) {
for (nsStyleStructID i = nsStyleStructID_Reset_Start;
i < nsStyleStructID_Reset_Start + nsStyleStructID_Reset_Count;
i = nsStyleStructID(i + 1)) {
uint32_t bit = nsCachedStyleData::GetBitForSID(i);
if (aStructs & bit) {
if (!(mBits & bit) && mCachedResetData->mStyleStructs[i]) {
aStructs &= ~bit;
} else {
mCachedResetData->mStyleStructs[i] = nullptr;
}
}
}
}
if (aStructs == 0) {
return;
}
ClearCachedInheritedStyleDataOnDescendants(aStructs);
}
void
nsStyleContext::SetIneligibleForSharing()
{
if (mBits & NS_STYLE_INELIGIBLE_FOR_SHARING) {
return;
}
mBits |= NS_STYLE_INELIGIBLE_FOR_SHARING;
if (mChild) {
nsStyleContext* child = mChild;
do {
child->SetIneligibleForSharing();
child = child->mNextSibling;
} while (mChild != child);
}
if (mEmptyChild) {
nsStyleContext* child = mEmptyChild;
do {
child->SetIneligibleForSharing();
child = child->mNextSibling;
} while (mEmptyChild != child);
}
}
#ifdef RESTYLE_LOGGING
nsCString
nsStyleContext::GetCachedStyleDataAsString(uint32_t aStructs)
{
nsCString structs;
for (nsStyleStructID i = nsStyleStructID(0);
i < nsStyleStructID_Length;
i = nsStyleStructID(i + 1)) {
if (aStructs & nsCachedStyleData::GetBitForSID(i)) {
const void* data = GetCachedStyleData(i);
if (!structs.IsEmpty()) {
structs.Append(' ');
}
structs.AppendPrintf("%s=%p", StructName(i), data);
if (HasCachedDependentStyleData(i)) {
structs.AppendLiteral("(dependent)");
} else {
structs.AppendLiteral("(owned)");
}
}
}
return structs;
}
int32_t&
nsStyleContext::LoggingDepth()
{
static int32_t depth = 0;
return depth;
}
void
nsStyleContext::LogStyleContextTree(int32_t aLoggingDepth, uint32_t aStructs)
{
LoggingDepth() = aLoggingDepth;
LogStyleContextTree(true, aStructs);
}
void
nsStyleContext::LogStyleContextTree(bool aFirst, uint32_t aStructs)
{
nsCString structs = GetCachedStyleDataAsString(aStructs);
if (!structs.IsEmpty()) {
structs.Append(' ');
}
nsCString pseudo;
if (mPseudoTag) {
nsAutoString pseudoTag;
mPseudoTag->ToString(pseudoTag);
AppendUTF16toUTF8(pseudoTag, pseudo);
pseudo.Append(' ');
}
nsCString flags;
if (IsStyleIfVisited()) {
flags.AppendLiteral("IS_STYLE_IF_VISITED ");
}
if (HasChildThatUsesGrandancestorStyle()) {
flags.AppendLiteral("CHILD_USES_GRANDANCESTOR_STYLE ");
}
if (IsShared()) {
flags.AppendLiteral("IS_SHARED ");
}
nsCString parent;
if (aFirst) {
parent.AppendPrintf("parent=%p ", mParent.get());
}
LOG_RESTYLE("%p(%d) %s%s%s%s",
this, mRefCnt,
structs.get(), pseudo.get(), flags.get(), parent.get());
LOG_RESTYLE_INDENT();
if (nullptr != mChild) {
nsStyleContext* child = mChild;
do {
child->LogStyleContextTree(false, aStructs);
child = child->mNextSibling;
} while (mChild != child);
}
if (nullptr != mEmptyChild) {
nsStyleContext* child = mEmptyChild;
do {
child->LogStyleContextTree(false, aStructs);
child = child->mNextSibling;
} while (mEmptyChild != child);
}
}
#endif
#ifdef DEBUG
/* static */ void
nsStyleContext::Initialize()
{
Preferences::AddBoolVarCache(
&sExpensiveStyleStructAssertionsEnabled,
"layout.css.expensive-style-struct-assertions.enabled");
}
#endif