зеркало из https://github.com/mozilla/gecko-dev.git
848 строки
30 KiB
C++
848 строки
30 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/. */
|
|
|
|
/* the interface (to internal code) for retrieving computed style data */
|
|
|
|
#include "nsStyleConsts.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 "prlog.h"
|
|
#include "nsStyleAnimation.h"
|
|
#include "mozilla/Util.h"
|
|
#include "sampler.h"
|
|
|
|
#ifdef DEBUG
|
|
// #define NOISY_DEBUG
|
|
#endif
|
|
|
|
using namespace mozilla;
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
|
|
nsStyleContext::nsStyleContext(nsStyleContext* aParent,
|
|
nsIAtom* aPseudoTag,
|
|
nsCSSPseudoElements::Type aPseudoType,
|
|
nsRuleNode* aRuleNode)
|
|
: mParent(aParent),
|
|
mChild(nullptr),
|
|
mEmptyChild(nullptr),
|
|
mPseudoTag(aPseudoTag),
|
|
mRuleNode(aRuleNode),
|
|
mAllocations(nullptr),
|
|
mCachedResetData(nullptr),
|
|
mBits(((uint32_t)aPseudoType) << NS_STYLE_CONTEXT_TYPE_SHIFT),
|
|
mRefCnt(0)
|
|
{
|
|
// 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.
|
|
MOZ_STATIC_ASSERT((UINT32_MAX >> NS_STYLE_CONTEXT_TYPE_SHIFT) >=
|
|
nsCSSPseudoElements::ePseudo_MAX,
|
|
"pseudo element bits no longer fit in a uint32_t");
|
|
|
|
mNextSibling = this;
|
|
mPrevSibling = this;
|
|
if (mParent) {
|
|
mParent->AddRef();
|
|
mParent->AddChild(this);
|
|
#ifdef DEBUG
|
|
nsRuleNode *r1 = mParent->GetRuleNode(), *r2 = aRuleNode;
|
|
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
|
|
}
|
|
|
|
mRuleNode->AddRef();
|
|
mRuleNode->SetUsedDirectly(); // before ApplyStyleFixups()!
|
|
|
|
ApplyStyleFixups();
|
|
|
|
#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()
|
|
{
|
|
NS_ASSERTION((nullptr == mChild) && (nullptr == mEmptyChild), "destructing context with children");
|
|
|
|
nsPresContext *presContext = mRuleNode->GetPresContext();
|
|
|
|
mRuleNode->Release();
|
|
|
|
presContext->PresShell()->StyleSet()->
|
|
NotifyStyleContextDestroyed(presContext, this);
|
|
|
|
if (mParent) {
|
|
mParent->RemoveChild(this);
|
|
mParent->Release();
|
|
}
|
|
|
|
// Free up our data structs.
|
|
mCachedInheritedData.DestroyStructs(mBits, presContext);
|
|
if (mCachedResetData) {
|
|
mCachedResetData->Destroy(mBits, presContext);
|
|
}
|
|
|
|
FreeAllocations(presContext);
|
|
}
|
|
|
|
void nsStyleContext::AddChild(nsStyleContext* aChild)
|
|
{
|
|
NS_ASSERTION(aChild->mPrevSibling == aChild &&
|
|
aChild->mNextSibling == aChild,
|
|
"child already in a child list");
|
|
|
|
nsStyleContext **list = aChild->mRuleNode->IsRoot() ? &mEmptyChild : &mChild;
|
|
|
|
// 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;
|
|
}
|
|
(*list) = aChild;
|
|
}
|
|
|
|
void nsStyleContext::RemoveChild(nsStyleContext* aChild)
|
|
{
|
|
NS_PRECONDITION(nullptr != aChild && this == aChild->mParent, "bad argument");
|
|
|
|
nsStyleContext **list = aChild->mRuleNode->IsRoot() ? &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;
|
|
}
|
|
|
|
already_AddRefed<nsStyleContext>
|
|
nsStyleContext::FindChildWithRules(const nsIAtom* aPseudoTag,
|
|
nsRuleNode* aRuleNode,
|
|
nsRuleNode* aRulesIfVisited,
|
|
bool aRelevantLinkVisited)
|
|
{
|
|
NS_ABORT_IF_FALSE(aRulesIfVisited || !aRelevantLinkVisited,
|
|
"aRelevantLinkVisited should only be set when we have a separate style");
|
|
uint32_t threshold = 10; // The # of siblings we're willing to examine
|
|
// before just giving this whole thing up.
|
|
|
|
nsStyleContext* result = nullptr;
|
|
nsStyleContext *list = aRuleNode->IsRoot() ? mEmptyChild : mChild;
|
|
|
|
if (list) {
|
|
nsStyleContext *child = list;
|
|
do {
|
|
if (child->mRuleNode == aRuleNode &&
|
|
child->mPseudoTag == aPseudoTag &&
|
|
!child->IsStyleIfVisited() &&
|
|
child->RelevantLinkVisited() == aRelevantLinkVisited) {
|
|
bool match = false;
|
|
if (aRulesIfVisited) {
|
|
match = child->GetStyleIfVisited() &&
|
|
child->GetStyleIfVisited()->mRuleNode == aRulesIfVisited;
|
|
} else {
|
|
match = !child->GetStyleIfVisited();
|
|
}
|
|
if (match) {
|
|
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);
|
|
}
|
|
|
|
// Add reference for the caller.
|
|
result->AddRef();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
const void* nsStyleContext::GetCachedStyleData(nsStyleStructID aSID)
|
|
{
|
|
const void* cachedData;
|
|
if (nsCachedStyleData::IsReset(aSID)) {
|
|
if (mCachedResetData) {
|
|
cachedData = mCachedResetData->mStyleStructs[aSID];
|
|
} else {
|
|
cachedData = nullptr;
|
|
}
|
|
} else {
|
|
cachedData = mCachedInheritedData.mStyleStructs[aSID];
|
|
}
|
|
return cachedData;
|
|
}
|
|
|
|
const void* nsStyleContext::GetStyleData(nsStyleStructID aSID)
|
|
{
|
|
const void* cachedData = GetCachedStyleData(aSID);
|
|
if (cachedData)
|
|
return cachedData; // We have computed data stored on this node in the context tree.
|
|
return mRuleNode->GetStyleData(aSID, this, true); // Our rule node will take care of it for us.
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
// 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 = GetStyleData(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(Display)
|
|
UNIQUE_CASE(Background)
|
|
UNIQUE_CASE(Text)
|
|
UNIQUE_CASE(TextReset)
|
|
|
|
#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 &= ~nsCachedStyleData::GetBitForSID(aSID);
|
|
|
|
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 (mRuleNode->GetPresContext()) nsResetStyleData;
|
|
}
|
|
dataSlot = &mCachedResetData->mStyleStructs[aSID];
|
|
} else {
|
|
dataSlot = &mCachedInheritedData.mStyleStructs[aSID];
|
|
}
|
|
NS_ASSERTION(!*dataSlot || (mBits & nsCachedStyleData::GetBitForSID(aSID)),
|
|
"Going to leak style data");
|
|
*dataSlot = aStruct;
|
|
}
|
|
|
|
void
|
|
nsStyleContext::ApplyStyleFixups()
|
|
{
|
|
// 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.
|
|
const nsStyleTextReset* text = GetStyleTextReset();
|
|
uint8_t decorationLine = text->mTextDecorationLine;
|
|
if (decorationLine != NS_STYLE_TEXT_DECORATION_LINE_NONE &&
|
|
decorationLine != NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL) {
|
|
mBits |= NS_STYLE_HAS_TEXT_DECORATION_LINES;
|
|
}
|
|
}
|
|
|
|
if ((mParent && mParent->HasPseudoElementData()) || mPseudoTag) {
|
|
mBits |= NS_STYLE_HAS_PSEUDO_ELEMENT_DATA;
|
|
}
|
|
|
|
// Correct tables.
|
|
const nsStyleDisplay* disp = GetStyleDisplay();
|
|
if (disp->mDisplay == NS_STYLE_DISPLAY_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 = GetStyleText();
|
|
|
|
if (text->mTextAlign == NS_STYLE_TEXT_ALIGN_MOZ_CENTER ||
|
|
text->mTextAlign == NS_STYLE_TEXT_ALIGN_MOZ_RIGHT)
|
|
{
|
|
nsStyleText* uniqueText = (nsStyleText*)GetUniqueStyleData(eStyleStruct_Text);
|
|
uniqueText->mTextAlign = NS_STYLE_TEXT_ALIGN_DEFAULT;
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
if (disp->mDisplay != NS_STYLE_DISPLAY_NONE &&
|
|
disp->mDisplay != NS_STYLE_DISPLAY_BLOCK &&
|
|
disp->mDisplay != NS_STYLE_DISPLAY_TABLE) {
|
|
nsStyleDisplay *mutable_display = static_cast<nsStyleDisplay*>
|
|
(GetUniqueStyleData(eStyleStruct_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.
|
|
if (mutable_display->mDisplay == NS_STYLE_DISPLAY_INLINE_TABLE)
|
|
mutable_display->mOriginalDisplay = mutable_display->mDisplay =
|
|
NS_STYLE_DISPLAY_TABLE;
|
|
else
|
|
mutable_display->mOriginalDisplay = mutable_display->mDisplay =
|
|
NS_STYLE_DISPLAY_BLOCK;
|
|
}
|
|
}
|
|
|
|
// Adjust the "display" values of flex items (but not for raw text,
|
|
// placeholders, or table-parts). 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.
|
|
#ifdef MOZ_FLEXBOX
|
|
if (mParent) {
|
|
const nsStyleDisplay* parentDisp = mParent->GetStyleDisplay();
|
|
if ((parentDisp->mDisplay == NS_STYLE_DISPLAY_FLEX ||
|
|
parentDisp->mDisplay == NS_STYLE_DISPLAY_INLINE_FLEX) &&
|
|
GetPseudo() != nsCSSAnonBoxes::mozNonElement) {
|
|
uint8_t displayVal = disp->mDisplay;
|
|
// Skip table parts.
|
|
// NOTE: This list needs to be kept in sync with
|
|
// nsCSSFrameConstructor.cpp's "sDisplayData" array -- specifically,
|
|
// this should be the list of display-values that have
|
|
// FCDATA_DESIRED_PARENT_TYPE_TO_BITS specified in that array.
|
|
if (NS_STYLE_DISPLAY_TABLE_CAPTION != displayVal &&
|
|
NS_STYLE_DISPLAY_TABLE_ROW_GROUP != displayVal &&
|
|
NS_STYLE_DISPLAY_TABLE_HEADER_GROUP != displayVal &&
|
|
NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP != displayVal &&
|
|
NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP != displayVal &&
|
|
NS_STYLE_DISPLAY_TABLE_COLUMN != displayVal &&
|
|
NS_STYLE_DISPLAY_TABLE_ROW != displayVal &&
|
|
NS_STYLE_DISPLAY_TABLE_CELL != displayVal) {
|
|
|
|
// NOTE: Technically, we shouldn't modify the 'display' value of
|
|
// positioned elements, since they aren't flex 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.
|
|
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 =
|
|
static_cast<nsStyleDisplay*>(GetUniqueStyleData(eStyleStruct_Display));
|
|
mutable_display->mDisplay = displayVal;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // MOZ_FLEXBOX
|
|
|
|
// Computer User Interface style, to trigger loads of cursors
|
|
GetStyleUserInterface();
|
|
}
|
|
|
|
nsChangeHint
|
|
nsStyleContext::CalcStyleDifference(nsStyleContext* aOther,
|
|
nsChangeHint aParentHintsNotHandledForDescendants)
|
|
{
|
|
SAMPLE_LABEL("nsStyleContext", "CalcStyleDifference");
|
|
|
|
NS_ABORT_IF_FALSE(NS_IsHintSubset(aParentHintsNotHandledForDescendants,
|
|
nsChangeHint_Hints_NotHandledForDescendants),
|
|
"caller is passing inherited hints, but shouldn't be");
|
|
|
|
nsChangeHint hint = NS_STYLE_HINT_NONE;
|
|
NS_ENSURE_TRUE(aOther, 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 rule nodes 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 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 = mRuleNode != aOther->mRuleNode;
|
|
|
|
#define DO_STRUCT_DIFFERENCE(struct_) \
|
|
PR_BEGIN_MACRO \
|
|
const nsStyle##struct_* this##struct_ = PeekStyle##struct_(); \
|
|
if (this##struct_) { \
|
|
const nsStyle##struct_* other##struct_ = aOther->GetStyle##struct_(); \
|
|
nsChangeHint maxDifference = nsStyle##struct_::MaxDifference(); \
|
|
if ((compare || \
|
|
(maxDifference & aParentHintsNotHandledForDescendants)) && \
|
|
!NS_IsHintSubset(maxDifference, hint) && \
|
|
this##struct_ != other##struct_) { \
|
|
NS_ASSERTION(NS_IsHintSubset( \
|
|
this##struct_->CalcDifference(*other##struct_), \
|
|
nsStyle##struct_::MaxDifference()), \
|
|
"CalcDifference() returned bigger hint than MaxDifference()"); \
|
|
NS_UpdateHint(hint, this##struct_->CalcDifference(*other##struct_)); \
|
|
} \
|
|
} \
|
|
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.)
|
|
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(Quotes);
|
|
DO_STRUCT_DIFFERENCE(SVGReset);
|
|
DO_STRUCT_DIFFERENCE(SVG);
|
|
DO_STRUCT_DIFFERENCE(Position);
|
|
DO_STRUCT_DIFFERENCE(Font);
|
|
DO_STRUCT_DIFFERENCE(Margin);
|
|
DO_STRUCT_DIFFERENCE(Padding);
|
|
DO_STRUCT_DIFFERENCE(Border);
|
|
DO_STRUCT_DIFFERENCE(TextReset);
|
|
DO_STRUCT_DIFFERENCE(Background);
|
|
DO_STRUCT_DIFFERENCE(Color);
|
|
|
|
#undef DO_STRUCT_DIFFERENCE
|
|
|
|
// Note that we do not check whether this->RelevantLinkVisited() !=
|
|
// aOther->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 = aOther->GetStyleIfVisited();
|
|
if (!thisVis != !otherVis) {
|
|
// One style context has a style-if-visited and the other doesn't.
|
|
// Presume a difference.
|
|
NS_UpdateHint(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->GetStyleColor()->mColor !=
|
|
otherVis->GetStyleColor()->mColor) {
|
|
change = true;
|
|
}
|
|
}
|
|
|
|
// NB: Calling Peek on |this|, not |thisVis| (see above).
|
|
if (!change && PeekStyleBackground()) {
|
|
if (thisVis->GetStyleBackground()->mBackgroundColor !=
|
|
otherVis->GetStyleBackground()->mBackgroundColor) {
|
|
change = true;
|
|
}
|
|
}
|
|
|
|
// NB: Calling Peek on |this|, not |thisVis| (see above).
|
|
if (!change && PeekStyleBorder()) {
|
|
const nsStyleBorder *thisVisBorder = thisVis->GetStyleBorder();
|
|
const nsStyleBorder *otherVisBorder = otherVis->GetStyleBorder();
|
|
NS_FOR_CSS_SIDES(side) {
|
|
bool thisFG, otherFG;
|
|
nscolor thisColor, otherColor;
|
|
thisVisBorder->GetBorderColor(side, thisColor, thisFG);
|
|
otherVisBorder->GetBorderColor(side, otherColor, otherFG);
|
|
if (thisFG != otherFG || (!thisFG && thisColor != otherColor)) {
|
|
change = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// NB: Calling Peek on |this|, not |thisVis| (see above).
|
|
if (!change && PeekStyleOutline()) {
|
|
const nsStyleOutline *thisVisOutline = thisVis->GetStyleOutline();
|
|
const nsStyleOutline *otherVisOutline = otherVis->GetStyleOutline();
|
|
bool haveColor;
|
|
nscolor thisColor, otherColor;
|
|
if (thisVisOutline->GetOutlineInitialColor() !=
|
|
otherVisOutline->GetOutlineInitialColor() ||
|
|
(haveColor = thisVisOutline->GetOutlineColor(thisColor)) !=
|
|
otherVisOutline->GetOutlineColor(otherColor) ||
|
|
(haveColor && thisColor != otherColor)) {
|
|
change = true;
|
|
}
|
|
}
|
|
|
|
// NB: Calling Peek on |this|, not |thisVis| (see above).
|
|
if (!change && PeekStyleColumn()) {
|
|
const nsStyleColumn *thisVisColumn = thisVis->GetStyleColumn();
|
|
const nsStyleColumn *otherVisColumn = otherVis->GetStyleColumn();
|
|
if (thisVisColumn->mColumnRuleColor != otherVisColumn->mColumnRuleColor ||
|
|
thisVisColumn->mColumnRuleColorIsForeground !=
|
|
otherVisColumn->mColumnRuleColorIsForeground) {
|
|
change = true;
|
|
}
|
|
}
|
|
|
|
// NB: Calling Peek on |this|, not |thisVis| (see above).
|
|
if (!change && PeekStyleTextReset()) {
|
|
const nsStyleTextReset *thisVisTextReset = thisVis->GetStyleTextReset();
|
|
const nsStyleTextReset *otherVisTextReset = otherVis->GetStyleTextReset();
|
|
nscolor thisVisDecColor, otherVisDecColor;
|
|
bool thisVisDecColorIsFG, otherVisDecColorIsFG;
|
|
thisVisTextReset->GetDecorationColor(thisVisDecColor,
|
|
thisVisDecColorIsFG);
|
|
otherVisTextReset->GetDecorationColor(otherVisDecColor,
|
|
otherVisDecColorIsFG);
|
|
if (thisVisDecColorIsFG != otherVisDecColorIsFG ||
|
|
(!thisVisDecColorIsFG && thisVisDecColor != otherVisDecColor)) {
|
|
change = true;
|
|
}
|
|
}
|
|
|
|
// NB: Calling Peek on |this|, not |thisVis| (see above).
|
|
if (!change && PeekStyleSVG()) {
|
|
const nsStyleSVG *thisVisSVG = thisVis->GetStyleSVG();
|
|
const nsStyleSVG *otherVisSVG = otherVis->GetStyleSVG();
|
|
if (thisVisSVG->mFill != otherVisSVG->mFill ||
|
|
thisVisSVG->mStroke != otherVisSVG->mStroke) {
|
|
change = true;
|
|
}
|
|
}
|
|
|
|
if (change) {
|
|
NS_UpdateHint(hint, nsChangeHint_RepaintFrame);
|
|
}
|
|
}
|
|
|
|
return hint;
|
|
}
|
|
|
|
void
|
|
nsStyleContext::Mark()
|
|
{
|
|
// Mark our rule node.
|
|
mRuleNode->Mark();
|
|
|
|
// Mark our children (i.e., tell them to mark their rule nodes, etc.).
|
|
if (mChild) {
|
|
nsStyleContext* child = mChild;
|
|
do {
|
|
child->Mark();
|
|
child = child->mNextSibling;
|
|
} while (mChild != child);
|
|
}
|
|
|
|
if (mEmptyChild) {
|
|
nsStyleContext* child = mEmptyChild;
|
|
do {
|
|
child->Mark();
|
|
child = child->mNextSibling;
|
|
} while (mEmptyChild != child);
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void nsStyleContext::List(FILE* out, int32_t aIndent)
|
|
{
|
|
// Indent
|
|
int32_t ix;
|
|
for (ix = aIndent; --ix >= 0; ) fputs(" ", out);
|
|
fprintf(out, "%p(%d) parent=%p ",
|
|
(void*)this, mRefCnt, (void *)mParent);
|
|
if (mPseudoTag) {
|
|
nsAutoString buffer;
|
|
mPseudoTag->ToString(buffer);
|
|
fputs(NS_LossyConvertUTF16toASCII(buffer).get(), out);
|
|
fputs(" ", out);
|
|
}
|
|
|
|
if (mRuleNode) {
|
|
fputs("{\n", out);
|
|
nsRuleNode* ruleNode = mRuleNode;
|
|
while (ruleNode) {
|
|
nsIStyleRule *styleRule = ruleNode->GetRule();
|
|
if (styleRule) {
|
|
styleRule->List(out, aIndent + 1);
|
|
}
|
|
ruleNode = ruleNode->GetParent();
|
|
}
|
|
for (ix = aIndent; --ix >= 0; ) fputs(" ", out);
|
|
fputs("}\n", out);
|
|
}
|
|
else {
|
|
fputs("{}\n", out);
|
|
}
|
|
|
|
if (nullptr != mChild) {
|
|
nsStyleContext* child = mChild;
|
|
do {
|
|
child->List(out, aIndent + 1);
|
|
child = child->mNextSibling;
|
|
} while (mChild != child);
|
|
}
|
|
if (nullptr != mEmptyChild) {
|
|
nsStyleContext* child = mEmptyChild;
|
|
do {
|
|
child->List(out, aIndent + 1);
|
|
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) CPP_THROW_NEW
|
|
{
|
|
// Check the recycle list first.
|
|
return aPresContext->PresShell()->AllocateByObjectID(nsPresArena::nsStyleContext_id, 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 from our rule node.
|
|
nsRefPtr<nsPresContext> presContext = mRuleNode->GetPresContext();
|
|
|
|
// 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(nsPresArena::nsStyleContext_id, this);
|
|
}
|
|
|
|
already_AddRefed<nsStyleContext>
|
|
NS_NewStyleContext(nsStyleContext* aParentContext,
|
|
nsIAtom* aPseudoTag,
|
|
nsCSSPseudoElements::Type aPseudoType,
|
|
nsRuleNode* aRuleNode)
|
|
{
|
|
nsStyleContext* context =
|
|
new (aRuleNode->GetPresContext())
|
|
nsStyleContext(aParentContext, aPseudoTag, aPseudoType, aRuleNode);
|
|
context->AddRef();
|
|
return context;
|
|
}
|
|
|
|
static inline void
|
|
ExtractAnimationValue(nsCSSProperty aProperty,
|
|
nsStyleContext* aStyleContext,
|
|
nsStyleAnimation::Value& aResult)
|
|
{
|
|
DebugOnly<bool> success =
|
|
nsStyleAnimation::ExtractComputedValue(aProperty, aStyleContext, aResult);
|
|
NS_ABORT_IF_FALSE(success,
|
|
"aProperty must be extractable by nsStyleAnimation");
|
|
}
|
|
|
|
static nscolor
|
|
ExtractColor(nsCSSProperty aProperty,
|
|
nsStyleContext *aStyleContext)
|
|
{
|
|
nsStyleAnimation::Value val;
|
|
ExtractAnimationValue(aProperty, aStyleContext, val);
|
|
return val.GetColorValue();
|
|
}
|
|
|
|
static nscolor
|
|
ExtractColorLenient(nsCSSProperty aProperty,
|
|
nsStyleContext *aStyleContext)
|
|
{
|
|
nsStyleAnimation::Value val;
|
|
ExtractAnimationValue(aProperty, aStyleContext, val);
|
|
if (val.GetUnit() == nsStyleAnimation::eUnit_Color) {
|
|
return val.GetColorValue();
|
|
}
|
|
return NS_RGBA(0, 0, 0, 0);
|
|
}
|
|
|
|
struct ColorIndexSet {
|
|
uint8_t colorIndex, alphaIndex;
|
|
};
|
|
|
|
static const ColorIndexSet gVisitedIndices[2] = { { 0, 0 }, { 1, 0 } };
|
|
|
|
nscolor
|
|
nsStyleContext::GetVisitedDependentColor(nsCSSProperty aProperty)
|
|
{
|
|
NS_ASSERTION(aProperty == eCSSProperty_color ||
|
|
aProperty == eCSSProperty_background_color ||
|
|
aProperty == eCSSProperty_border_top_color ||
|
|
aProperty == eCSSProperty_border_right_color_value ||
|
|
aProperty == eCSSProperty_border_bottom_color ||
|
|
aProperty == eCSSProperty_border_left_color_value ||
|
|
aProperty == eCSSProperty_outline_color ||
|
|
aProperty == eCSSProperty__moz_column_rule_color ||
|
|
aProperty == eCSSProperty_text_decoration_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);
|
|
|
|
nsStyleContext *visitedStyle = this->GetStyleIfVisited();
|
|
if (!visitedStyle) {
|
|
return colors[0];
|
|
}
|
|
|
|
colors[1] = isPaintProperty ? ExtractColorLenient(aProperty, visitedStyle)
|
|
: ExtractColor(aProperty, visitedStyle);
|
|
|
|
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));
|
|
}
|
|
|
|
void*
|
|
nsStyleContext::Alloc(size_t aSize)
|
|
{
|
|
nsIPresShell *shell = PresContext()->PresShell();
|
|
|
|
aSize += offsetof(AllocationHeader, mStorageStart);
|
|
AllocationHeader *alloc =
|
|
static_cast<AllocationHeader*>(shell->AllocateMisc(aSize));
|
|
|
|
alloc->mSize = aSize; // NOTE: inflated by header
|
|
|
|
alloc->mNext = mAllocations;
|
|
mAllocations = alloc;
|
|
|
|
return static_cast<void*>(&alloc->mStorageStart);
|
|
}
|
|
|
|
void
|
|
nsStyleContext::FreeAllocations(nsPresContext *aPresContext)
|
|
{
|
|
nsIPresShell *shell = aPresContext->PresShell();
|
|
|
|
for (AllocationHeader *alloc = mAllocations, *next; alloc; alloc = next) {
|
|
next = alloc->mNext;
|
|
shell->FreeMisc(alloc->mSize, alloc);
|
|
}
|
|
}
|