зеркало из https://github.com/mozilla/gecko-dev.git
1817 строки
68 KiB
C++
1817 строки
68 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 "CSSVariableImageTable.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/Maybe.h"
|
|
|
|
#include "nsCSSAnonBoxes.h"
|
|
#include "nsCSSPseudoElements.h"
|
|
#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 "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"
|
|
|
|
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 {
|
|
// The Servo-backed StyleContextSource owns the struct.
|
|
newData = StyleStructFromServoComputedValues(aSID);
|
|
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;
|
|
}
|
|
}
|
|
|
|
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(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:
|
|
ServoComputedValues* MOZ_NON_OWNING_REF mComputedValues;
|
|
};
|
|
|
|
nsChangeHint
|
|
nsStyleContext::CalcStyleDifference(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()));
|
|
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_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
|