mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-29 11:57:32 +09:00
Bug 1355351 - Make pseudo-elements return the correct style via getComputedStyle
* Add a node property to access the ::before and ::after pseudo-elements * Look for the frame for ::before and ::after pseudos * Clean up pseudo-element props * Simplify nsLayoutUtils callers, and make child iterators notice display: contents pseudos Tag #1375
This commit is contained in:
parent
6b48075b57
commit
d3ffda520f
16 changed files with 188 additions and 241 deletions
|
|
@ -33,7 +33,7 @@ function* testTopLeft(inspector, view) {
|
||||||
let afterElement = children.nodes[children.nodes.length - 1];
|
let afterElement = children.nodes[children.nodes.length - 1];
|
||||||
yield selectNode(afterElement, inspector);
|
yield selectNode(afterElement, inspector);
|
||||||
top = getComputedViewPropertyValue(view, "top");
|
top = getComputedViewPropertyValue(view, "top");
|
||||||
is(top, "50%", "The computed view shows the correct top");
|
is(top, "96px", "The computed view shows the correct top");
|
||||||
left = getComputedViewPropertyValue(view, "left");
|
left = getComputedViewPropertyValue(view, "left");
|
||||||
is(left, "50%", "The computed view shows the correct left");
|
is(left, "96px", "The computed view shows the correct left");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -356,25 +356,17 @@ EffectCompositor::GetElementToRestyle(dom::Element* aElement,
|
||||||
return aElement;
|
return aElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
|
|
||||||
if (!primaryFrame) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
nsIFrame* pseudoFrame;
|
|
||||||
if (aPseudoType == CSSPseudoElementType::before) {
|
if (aPseudoType == CSSPseudoElementType::before) {
|
||||||
pseudoFrame = nsLayoutUtils::GetBeforeFrame(primaryFrame);
|
return nsLayoutUtils::GetBeforePseudo(aElement);
|
||||||
} else if (aPseudoType == CSSPseudoElementType::after) {
|
}
|
||||||
pseudoFrame = nsLayoutUtils::GetAfterFrame(primaryFrame);
|
if (aPseudoType == CSSPseudoElementType::after) {
|
||||||
} else {
|
return nsLayoutUtils::GetAfterPseudo(aElement);
|
||||||
|
}
|
||||||
|
|
||||||
NS_NOTREACHED("Should not try to get the element to restyle for a pseudo "
|
NS_NOTREACHED("Should not try to get the element to restyle for a pseudo "
|
||||||
"other that :before or :after");
|
"other that :before or :after");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (!pseudoFrame) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return pseudoFrame->GetContent()->AsElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
EffectCompositor::HasPendingStyleUpdates() const
|
EffectCompositor::HasPendingStyleUpdates() const
|
||||||
|
|
|
||||||
|
|
@ -1105,19 +1105,17 @@ KeyframeEffectReadOnly::GetAnimationFrame() const
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsIFrame* frame = mTarget->mElement->GetPrimaryFrame();
|
nsIFrame* frame;
|
||||||
if (!frame) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mTarget->mPseudoType == CSSPseudoElementType::before) {
|
if (mTarget->mPseudoType == CSSPseudoElementType::before) {
|
||||||
frame = nsLayoutUtils::GetBeforeFrame(frame);
|
frame = nsLayoutUtils::GetBeforeFrame(mTarget->mElement);
|
||||||
} else if (mTarget->mPseudoType == CSSPseudoElementType::after) {
|
} else if (mTarget->mPseudoType == CSSPseudoElementType::after) {
|
||||||
frame = nsLayoutUtils::GetAfterFrame(frame);
|
frame = nsLayoutUtils::GetAfterFrame(mTarget->mElement);
|
||||||
} else {
|
} else {
|
||||||
|
frame = mTarget->mElement->GetPrimaryFrame();
|
||||||
MOZ_ASSERT(mTarget->mPseudoType == CSSPseudoElementType::NotPseudo,
|
MOZ_ASSERT(mTarget->mPseudoType == CSSPseudoElementType::NotPseudo,
|
||||||
"unknown mTarget->mPseudoType");
|
"unknown mTarget->mPseudoType");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!frame) {
|
if (!frame) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -317,11 +317,9 @@ AllChildrenIterator::Get() const
|
||||||
{
|
{
|
||||||
switch (mPhase) {
|
switch (mPhase) {
|
||||||
case eAtBeforeKid: {
|
case eAtBeforeKid: {
|
||||||
nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
|
Element* before = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
|
||||||
MOZ_ASSERT(frame, "No frame at eAtBeforeKid phase");
|
MOZ_ASSERT(before, "No content before frame at eAtBeforeKid phase");
|
||||||
nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame);
|
return before;
|
||||||
MOZ_ASSERT(beforeFrame, "No content before frame at eAtBeforeKid phase");
|
|
||||||
return beforeFrame->GetContent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case eAtExplicitKids:
|
case eAtExplicitKids:
|
||||||
|
|
@ -331,11 +329,9 @@ AllChildrenIterator::Get() const
|
||||||
return mAnonKids[mAnonKidsIdx];
|
return mAnonKids[mAnonKidsIdx];
|
||||||
|
|
||||||
case eAtAfterKid: {
|
case eAtAfterKid: {
|
||||||
nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
|
Element* after = nsLayoutUtils::GetAfterPseudo(mOriginalContent);
|
||||||
MOZ_ASSERT(frame, "No frame at eAtAfterKid phase");
|
MOZ_ASSERT(after, "No content after frame at eAtAfterKid phase");
|
||||||
nsIFrame* afterFrame = nsLayoutUtils::GetAfterFrame(frame);
|
return after;
|
||||||
MOZ_ASSERT(afterFrame, "No content before frame at eAtBeforeKid phase");
|
|
||||||
return afterFrame->GetContent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -349,17 +345,12 @@ AllChildrenIterator::Seek(nsIContent* aChildToFind)
|
||||||
{
|
{
|
||||||
if (mPhase == eAtBegin || mPhase == eAtBeforeKid) {
|
if (mPhase == eAtBegin || mPhase == eAtBeforeKid) {
|
||||||
mPhase = eAtExplicitKids;
|
mPhase = eAtExplicitKids;
|
||||||
nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
|
Element* beforePseudo = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
|
||||||
if (frame) {
|
if (beforePseudo && beforePseudo == aChildToFind) {
|
||||||
nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame);
|
|
||||||
if (beforeFrame) {
|
|
||||||
if (beforeFrame->GetContent() == aChildToFind) {
|
|
||||||
mPhase = eAtBeforeKid;
|
mPhase = eAtBeforeKid;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mPhase == eAtExplicitKids) {
|
if (mPhase == eAtExplicitKids) {
|
||||||
if (ExplicitChildIterator::Seek(aChildToFind)) {
|
if (ExplicitChildIterator::Seek(aChildToFind)) {
|
||||||
|
|
@ -404,13 +395,10 @@ AllChildrenIterator::GetNextChild()
|
||||||
{
|
{
|
||||||
if (mPhase == eAtBegin) {
|
if (mPhase == eAtBegin) {
|
||||||
mPhase = eAtExplicitKids;
|
mPhase = eAtExplicitKids;
|
||||||
nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
|
Element* beforeContent = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
|
||||||
if (frame) {
|
if (beforeContent) {
|
||||||
nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame);
|
|
||||||
if (beforeFrame) {
|
|
||||||
mPhase = eAtBeforeKid;
|
mPhase = eAtBeforeKid;
|
||||||
return beforeFrame->GetContent();
|
return beforeContent;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -446,13 +434,10 @@ AllChildrenIterator::GetNextChild()
|
||||||
return mAnonKids[mAnonKidsIdx];
|
return mAnonKids[mAnonKidsIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
|
Element* afterContent = nsLayoutUtils::GetAfterPseudo(mOriginalContent);
|
||||||
if (frame) {
|
if (afterContent) {
|
||||||
nsIFrame* afterFrame = nsLayoutUtils::GetAfterFrame(frame);
|
|
||||||
if (afterFrame) {
|
|
||||||
mPhase = eAtAfterKid;
|
mPhase = eAtAfterKid;
|
||||||
return afterFrame->GetContent();
|
return afterContent;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -466,13 +451,10 @@ AllChildrenIterator::GetPreviousChild()
|
||||||
if (mPhase == eAtEnd) {
|
if (mPhase == eAtEnd) {
|
||||||
MOZ_ASSERT(mAnonKidsIdx == mAnonKids.Length());
|
MOZ_ASSERT(mAnonKidsIdx == mAnonKids.Length());
|
||||||
mPhase = eAtAnonKids;
|
mPhase = eAtAnonKids;
|
||||||
nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
|
Element* afterContent = nsLayoutUtils::GetAfterPseudo(mOriginalContent);
|
||||||
if (frame) {
|
if (afterContent) {
|
||||||
nsIFrame* afterFrame = nsLayoutUtils::GetAfterFrame(frame);
|
|
||||||
if (afterFrame) {
|
|
||||||
mPhase = eAtAfterKid;
|
mPhase = eAtAfterKid;
|
||||||
return afterFrame->GetContent();
|
return afterContent;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -501,13 +483,10 @@ AllChildrenIterator::GetPreviousChild()
|
||||||
return kid;
|
return kid;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
|
Element* beforeContent = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
|
||||||
if (frame) {
|
if (beforeContent) {
|
||||||
nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame);
|
|
||||||
if (beforeFrame) {
|
|
||||||
mPhase = eAtBeforeKid;
|
mPhase = eAtBeforeKid;
|
||||||
return beforeFrame->GetContent();
|
return beforeContent;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3657,11 +3657,11 @@ nsDOMWindowUtils::GetOMTAStyle(nsIDOMElement* aElement,
|
||||||
|
|
||||||
RefPtr<nsROCSSPrimitiveValue> cssValue = nullptr;
|
RefPtr<nsROCSSPrimitiveValue> cssValue = nullptr;
|
||||||
nsIFrame* frame = element->GetPrimaryFrame();
|
nsIFrame* frame = element->GetPrimaryFrame();
|
||||||
if (frame && !aPseudoElement.IsEmpty()) {
|
if (!aPseudoElement.IsEmpty()) {
|
||||||
if (aPseudoElement.EqualsLiteral("::before")) {
|
if (aPseudoElement.EqualsLiteral("::before")) {
|
||||||
frame = nsLayoutUtils::GetBeforeFrame(frame);
|
frame = nsLayoutUtils::GetBeforeFrame(element);
|
||||||
} else if (aPseudoElement.EqualsLiteral("::after")) {
|
} else if (aPseudoElement.EqualsLiteral("::after")) {
|
||||||
frame = nsLayoutUtils::GetAfterFrame(frame);
|
frame = nsLayoutUtils::GetAfterFrame(element);
|
||||||
} else {
|
} else {
|
||||||
return NS_ERROR_INVALID_ARG;
|
return NS_ERROR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2144,12 +2144,14 @@ GK_ATOM(ongamepaddisconnected, "ongamepaddisconnected")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Content property names
|
// Content property names
|
||||||
|
GK_ATOM(afterPseudoProperty, "afterPseudoProperty") // nsXMLElement*
|
||||||
GK_ATOM(animationsProperty, "AnimationsProperty") // FrameAnimations*
|
GK_ATOM(animationsProperty, "AnimationsProperty") // FrameAnimations*
|
||||||
GK_ATOM(animationsOfBeforeProperty, "AnimationsOfBeforeProperty") // FrameAnimations*
|
GK_ATOM(animationsOfBeforeProperty, "AnimationsOfBeforeProperty") // FrameAnimations*
|
||||||
GK_ATOM(animationsOfAfterProperty, "AnimationsOfAfterProperty") // FrameAnimations*
|
GK_ATOM(animationsOfAfterProperty, "AnimationsOfAfterProperty") // FrameAnimations*
|
||||||
GK_ATOM(animationEffectsProperty, "AnimationEffectsProperty") // EffectSet*
|
GK_ATOM(animationEffectsProperty, "AnimationEffectsProperty") // EffectSet*
|
||||||
GK_ATOM(animationEffectsForBeforeProperty, "AnimationsEffectsForBeforeProperty") // EffectSet*
|
GK_ATOM(animationEffectsForBeforeProperty, "AnimationsEffectsForBeforeProperty") // EffectSet*
|
||||||
GK_ATOM(animationEffectsForAfterProperty, "AnimationsEffectsForAfterProperty") // EffectSet*
|
GK_ATOM(animationEffectsForAfterProperty, "AnimationsEffectsForAfterProperty") // EffectSet*
|
||||||
|
GK_ATOM(beforePseudoProperty, "beforePseudoProperty") // nsXMLElement*
|
||||||
GK_ATOM(cssPseudoElementBeforeProperty, "CSSPseudoElementBeforeProperty") // CSSPseudoElement*
|
GK_ATOM(cssPseudoElementBeforeProperty, "CSSPseudoElementBeforeProperty") // CSSPseudoElement*
|
||||||
GK_ATOM(cssPseudoElementAfterProperty, "CSSPseudoElementAfterProperty") // CSSPseudoElement*
|
GK_ATOM(cssPseudoElementAfterProperty, "CSSPseudoElementAfterProperty") // CSSPseudoElement*
|
||||||
GK_ATOM(transitionsProperty, "TransitionsProperty") // FrameTransitions*
|
GK_ATOM(transitionsProperty, "TransitionsProperty") // FrameTransitions*
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include "mozilla/dom/ElementInlines.h"
|
#include "mozilla/dom/ElementInlines.h"
|
||||||
#include "nsContentUtils.h" // nsAutoScriptBlocker
|
#include "nsContentUtils.h" // nsAutoScriptBlocker
|
||||||
|
|
||||||
|
using namespace mozilla;
|
||||||
using namespace mozilla::dom;
|
using namespace mozilla::dom;
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
|
|
@ -29,4 +30,26 @@ nsXMLElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
|
||||||
return ElementBinding::Wrap(aCx, this, aGivenProto);
|
return ElementBinding::Wrap(aCx, this, aGivenProto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsXMLElement::UnbindFromTree(bool aDeep, bool aNullParent)
|
||||||
|
{
|
||||||
|
CSSPseudoElementType pseudoType = GetPseudoElementType();
|
||||||
|
bool isBefore = pseudoType == CSSPseudoElementType::before;
|
||||||
|
nsIAtom* property = isBefore
|
||||||
|
? nsGkAtoms::beforePseudoProperty : nsGkAtoms::afterPseudoProperty;
|
||||||
|
|
||||||
|
switch (pseudoType) {
|
||||||
|
case CSSPseudoElementType::before:
|
||||||
|
case CSSPseudoElementType::after: {
|
||||||
|
MOZ_ASSERT(GetParent());
|
||||||
|
MOZ_ASSERT(GetParent()->IsElement());
|
||||||
|
GetParent()->DeleteProperty(property);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Element::UnbindFromTree(aDeep, aNullParent);
|
||||||
|
}
|
||||||
|
|
||||||
NS_IMPL_ELEMENT_CLONE(nsXMLElement)
|
NS_IMPL_ELEMENT_CLONE(nsXMLElement)
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ public:
|
||||||
|
|
||||||
virtual nsIDOMNode* AsDOMNode() override { return this; }
|
virtual nsIDOMNode* AsDOMNode() override { return this; }
|
||||||
|
|
||||||
|
virtual void UnbindFromTree(bool aDeep = true,
|
||||||
|
bool aNullParent = true) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual ~nsXMLElement() {}
|
virtual ~nsXMLElement() {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3626,14 +3626,14 @@ ElementRestyler::MustReframeForPseudo(CSSPseudoElementType aPseudoType,
|
||||||
// Check for a ::before pseudo style and the absence of a ::before content,
|
// Check for a ::before pseudo style and the absence of a ::before content,
|
||||||
// but only if aFrame is null or is the first continuation/ib-split.
|
// but only if aFrame is null or is the first continuation/ib-split.
|
||||||
if ((aFrame && !nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame)) ||
|
if ((aFrame && !nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame)) ||
|
||||||
nsLayoutUtils::GetBeforeFrameForContent(aGenConParentFrame, aContent)) {
|
nsLayoutUtils::GetBeforeFrame(aContent)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Similarly for ::after, but check for being the last continuation/
|
// Similarly for ::after, but check for being the last continuation/
|
||||||
// ib-split.
|
// ib-split.
|
||||||
if ((aFrame && nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) ||
|
if ((aFrame && nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) ||
|
||||||
nsLayoutUtils::GetAfterFrameForContent(aGenConParentFrame, aContent)) {
|
nsLayoutUtils::GetAfterFrame(aContent)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -277,24 +277,17 @@ ServoRestyleManager::FrameForPseudoElement(const nsIContent* aContent,
|
||||||
nsIAtom* aPseudoTagOrNull)
|
nsIAtom* aPseudoTagOrNull)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT_IF(aPseudoTagOrNull, aContent->IsElement());
|
MOZ_ASSERT_IF(aPseudoTagOrNull, aContent->IsElement());
|
||||||
nsIFrame* primaryFrame = aContent->GetPrimaryFrame();
|
|
||||||
|
|
||||||
if (!aPseudoTagOrNull) {
|
if (!aPseudoTagOrNull) {
|
||||||
return primaryFrame;
|
return aContent->GetPrimaryFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!primaryFrame) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: we probably need to special-case display: contents here. Gecko's
|
|
||||||
// RestyleManager passes the primary frame of the parent instead.
|
|
||||||
if (aPseudoTagOrNull == nsCSSPseudoElements::before) {
|
if (aPseudoTagOrNull == nsCSSPseudoElements::before) {
|
||||||
return nsLayoutUtils::GetBeforeFrameForContent(primaryFrame, aContent);
|
return nsLayoutUtils::GetBeforeFrame(aContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aPseudoTagOrNull == nsCSSPseudoElements::after) {
|
if (aPseudoTagOrNull == nsCSSPseudoElements::after) {
|
||||||
return nsLayoutUtils::GetAfterFrameForContent(primaryFrame, aContent);
|
return nsLayoutUtils::GetAfterFrame(aContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
MOZ_CRASH("Unkown pseudo-element given to "
|
MOZ_CRASH("Unkown pseudo-element given to "
|
||||||
|
|
|
||||||
|
|
@ -1832,11 +1832,7 @@ nsCSSFrameConstructor::CreateGeneratedContentItem(nsFrameConstructorState& aStat
|
||||||
aPseudoElement == CSSPseudoElementType::after,
|
aPseudoElement == CSSPseudoElementType::after,
|
||||||
"unexpected aPseudoElement");
|
"unexpected aPseudoElement");
|
||||||
|
|
||||||
// XXXbz is this ever true?
|
MOZ_ASSERT(aParentContent->IsElement());
|
||||||
if (!aParentContent->IsElement()) {
|
|
||||||
NS_ERROR("Bogus generated content parent");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
StyleSetHandle styleSet = mPresShell->StyleSet();
|
StyleSetHandle styleSet = mPresShell->StyleSet();
|
||||||
|
|
||||||
|
|
@ -1864,6 +1860,13 @@ nsCSSFrameConstructor::CreateGeneratedContentItem(nsFrameConstructorState& aStat
|
||||||
nsresult rv = NS_NewXMLElement(getter_AddRefs(container), nodeInfo.forget());
|
nsresult rv = NS_NewXMLElement(getter_AddRefs(container), nodeInfo.forget());
|
||||||
if (NS_FAILED(rv))
|
if (NS_FAILED(rv))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Cleared when the pseudo is unbound from the tree, so no need to store a
|
||||||
|
// strong reference, nor a destructor.
|
||||||
|
nsIAtom* property = isBefore
|
||||||
|
? nsGkAtoms::beforePseudoProperty : nsGkAtoms::afterPseudoProperty;
|
||||||
|
aParentContent->SetProperty(property, container.get());
|
||||||
|
|
||||||
container->SetIsNativeAnonymousRoot();
|
container->SetIsNativeAnonymousRoot();
|
||||||
container->SetPseudoElementType(aPseudoElement);
|
container->SetPseudoElementType(aPseudoElement);
|
||||||
|
|
||||||
|
|
@ -6079,6 +6082,9 @@ AddGenConPseudoToFrame(nsIFrame* aOwnerFrame, nsIContent* aContent)
|
||||||
NS_ASSERTION(nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aOwnerFrame),
|
NS_ASSERTION(nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aOwnerFrame),
|
||||||
"property should only be set on first continuation/ib-sibling");
|
"property should only be set on first continuation/ib-sibling");
|
||||||
|
|
||||||
|
// FIXME(emilio): Remove this property, and use the frame of the generated
|
||||||
|
// content itself to tear the content down? It should be quite simpler.
|
||||||
|
|
||||||
nsIFrame::ContentArray* value =
|
nsIFrame::ContentArray* value =
|
||||||
aOwnerFrame->GetProperty(nsIFrame::GenConProperty());
|
aOwnerFrame->GetProperty(nsIFrame::GenConProperty());
|
||||||
if (!value) {
|
if (!value) {
|
||||||
|
|
@ -6340,7 +6346,7 @@ AdjustAppendParentForAfterContent(nsFrameManager* aFrameManager,
|
||||||
// frames to find the first one that is either a ::after frame for an
|
// frames to find the first one that is either a ::after frame for an
|
||||||
// ancestor of aChild or a frame that is for a node later in the
|
// ancestor of aChild or a frame that is for a node later in the
|
||||||
// document than aChild and return that in aAfterFrame.
|
// document than aChild and return that in aAfterFrame.
|
||||||
if (aParentFrame->GetGenConPseudos() ||
|
if (aParentFrame->GetProperty(nsIFrame::GenConProperty()) ||
|
||||||
nsLayoutUtils::HasPseudoStyle(aContainer, aParentFrame->StyleContext(),
|
nsLayoutUtils::HasPseudoStyle(aContainer, aParentFrame->StyleContext(),
|
||||||
CSSPseudoElementType::after,
|
CSSPseudoElementType::after,
|
||||||
aParentFrame->PresContext()) ||
|
aParentFrame->PresContext()) ||
|
||||||
|
|
@ -6669,9 +6675,8 @@ nsCSSFrameConstructor::FindFrameForContentSibling(nsIContent* aContent,
|
||||||
nsIFrame* sibling = aContent->GetPrimaryFrame();
|
nsIFrame* sibling = aContent->GetPrimaryFrame();
|
||||||
if (!sibling && GetDisplayContentsStyleFor(aContent)) {
|
if (!sibling && GetDisplayContentsStyleFor(aContent)) {
|
||||||
// A display:contents node - check if it has a ::before / ::after frame...
|
// A display:contents node - check if it has a ::before / ::after frame...
|
||||||
sibling = aPrevSibling ?
|
sibling = aPrevSibling ? nsLayoutUtils::GetAfterFrame(aContent)
|
||||||
nsLayoutUtils::GetAfterFrameForContent(aParentFrame, aContent) :
|
: nsLayoutUtils::GetBeforeFrame(aContent);
|
||||||
nsLayoutUtils::GetBeforeFrameForContent(aParentFrame, aContent);
|
|
||||||
if (!sibling) {
|
if (!sibling) {
|
||||||
// ... then recurse into children ...
|
// ... then recurse into children ...
|
||||||
const bool forward = !aPrevSibling;
|
const bool forward = !aPrevSibling;
|
||||||
|
|
@ -6682,9 +6687,8 @@ nsCSSFrameConstructor::FindFrameForContentSibling(nsIContent* aContent,
|
||||||
}
|
}
|
||||||
if (!sibling) {
|
if (!sibling) {
|
||||||
// ... then ::after / ::before on the opposite end.
|
// ... then ::after / ::before on the opposite end.
|
||||||
sibling = aPrevSibling ?
|
sibling = aPrevSibling ? nsLayoutUtils::GetAfterFrame(aContent)
|
||||||
nsLayoutUtils::GetBeforeFrameForContent(aParentFrame, aContent) :
|
: nsLayoutUtils::GetBeforeFrame(aContent);
|
||||||
nsLayoutUtils::GetAfterFrameForContent(aParentFrame, aContent);
|
|
||||||
}
|
}
|
||||||
if (!sibling) {
|
if (!sibling) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
@ -8291,27 +8295,24 @@ nsCSSFrameConstructor::ContentRemoved(nsIContent* aContainer,
|
||||||
MOZ_ASSERT(!childFrame || !GetDisplayContentsStyleFor(aChild),
|
MOZ_ASSERT(!childFrame || !GetDisplayContentsStyleFor(aChild),
|
||||||
"display:contents nodes shouldn't have a frame");
|
"display:contents nodes shouldn't have a frame");
|
||||||
if (!childFrame && GetDisplayContentsStyleFor(aChild)) {
|
if (!childFrame && GetDisplayContentsStyleFor(aChild)) {
|
||||||
nsIFrame* ancestorFrame = nullptr;
|
|
||||||
nsIContent* ancestor = aContainer;
|
nsIContent* ancestor = aContainer;
|
||||||
for (; ancestor; ancestor = ancestor->GetParent()) {
|
while (!ancestor->GetPrimaryFrame()) {
|
||||||
ancestorFrame = ancestor->GetPrimaryFrame();
|
// FIXME(emilio): Should this use the flattened tree parent instead?
|
||||||
if (ancestorFrame) {
|
ancestor = ancestor->GetParent();
|
||||||
break;
|
MOZ_ASSERT(ancestor, "we can't have a display: contents subtree root!");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (ancestorFrame) {
|
nsIFrame* ancestorFrame = ancestor->GetPrimaryFrame();
|
||||||
nsIFrame* contentInsertion = ancestorFrame->GetContentInsertionFrame();
|
if (ancestorFrame->GetProperty(nsIFrame::GenConProperty())) {
|
||||||
if (ancestorFrame->GetGenConPseudos() ||
|
|
||||||
(contentInsertion && contentInsertion->GetGenConPseudos())) {
|
|
||||||
*aDidReconstruct = true;
|
*aDidReconstruct = true;
|
||||||
LAYOUT_PHASE_TEMP_EXIT();
|
LAYOUT_PHASE_TEMP_EXIT();
|
||||||
|
|
||||||
// XXXmats Can we recreate frames only for the ::after/::before content?
|
// XXXmats Can we recreate frames only for the ::after/::before content?
|
||||||
// XXX Perhaps even only those that belong to the aChild sub-tree?
|
// XXX Perhaps even only those that belong to the aChild sub-tree?
|
||||||
RecreateFramesForContent(ancestor, false, aFlags, aDestroyedFramesFor);
|
RecreateFramesForContent(ancestor, false, aFlags, aDestroyedFramesFor);
|
||||||
LAYOUT_PHASE_TEMP_REENTER();
|
LAYOUT_PHASE_TEMP_REENTER();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
FlattenedChildIterator iter(aChild);
|
FlattenedChildIterator iter(aChild);
|
||||||
for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) {
|
for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) {
|
||||||
|
|
|
||||||
|
|
@ -1486,90 +1486,38 @@ nsLayoutUtils::GetChildListNameFor(nsIFrame* aChildFrame)
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*static*/ nsIFrame*
|
static Element*
|
||||||
nsLayoutUtils::GetBeforeFrameForContent(nsIFrame* aFrame,
|
GetPseudo(const nsIContent* aContent, nsIAtom* aPseudoProperty)
|
||||||
const nsIContent* aContent)
|
|
||||||
{
|
{
|
||||||
// We need to call GetGenConPseudos() on the first continuation/ib-split.
|
MOZ_ASSERT(aPseudoProperty == nsGkAtoms::beforePseudoProperty ||
|
||||||
// Find it, for symmetry with GetAfterFrameForContent.
|
aPseudoProperty == nsGkAtoms::afterPseudoProperty);
|
||||||
nsContainerFrame* genConParentFrame =
|
return static_cast<Element*>(aContent->GetProperty(aPseudoProperty));
|
||||||
FirstContinuationOrIBSplitSibling(aFrame)->GetContentInsertionFrame();
|
|
||||||
if (!genConParentFrame) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
nsTArray<nsIContent*>* prop = genConParentFrame->GetGenConPseudos();
|
|
||||||
if (prop) {
|
/*static*/ Element*
|
||||||
const nsTArray<nsIContent*>& pseudos(*prop);
|
nsLayoutUtils::GetBeforePseudo(const nsIContent* aContent)
|
||||||
for (uint32_t i = 0; i < pseudos.Length(); ++i) {
|
{
|
||||||
if (pseudos[i]->GetParent() == aContent &&
|
return GetPseudo(aContent, nsGkAtoms::beforePseudoProperty);
|
||||||
pseudos[i]->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore) {
|
|
||||||
return pseudos[i]->GetPrimaryFrame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If the first child frame is a pseudo-frame, then try that.
|
|
||||||
// Note that the frame we create for the generated content is also a
|
|
||||||
// pseudo-frame and so don't drill down in that case.
|
|
||||||
nsIFrame* childFrame = genConParentFrame->PrincipalChildList().FirstChild();
|
|
||||||
if (childFrame &&
|
|
||||||
childFrame->IsPseudoFrame(aContent) &&
|
|
||||||
!childFrame->IsGeneratedContentFrame()) {
|
|
||||||
return GetBeforeFrameForContent(childFrame, aContent);
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*static*/ nsIFrame*
|
/*static*/ nsIFrame*
|
||||||
nsLayoutUtils::GetBeforeFrame(nsIFrame* aFrame)
|
nsLayoutUtils::GetBeforeFrame(const nsIContent* aContent)
|
||||||
{
|
{
|
||||||
return GetBeforeFrameForContent(aFrame, aFrame->GetContent());
|
Element* pseudo = GetBeforePseudo(aContent);
|
||||||
|
return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static*/ Element*
|
||||||
|
nsLayoutUtils::GetAfterPseudo(const nsIContent* aContent)
|
||||||
|
{
|
||||||
|
return GetPseudo(aContent, nsGkAtoms::afterPseudoProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*static*/ nsIFrame*
|
/*static*/ nsIFrame*
|
||||||
nsLayoutUtils::GetAfterFrameForContent(nsIFrame* aFrame,
|
nsLayoutUtils::GetAfterFrame(const nsIContent* aContent)
|
||||||
const nsIContent* aContent)
|
|
||||||
{
|
{
|
||||||
// We need to call GetGenConPseudos() on the first continuation,
|
Element* pseudo = GetAfterPseudo(aContent);
|
||||||
// but callers are likely to pass the last.
|
return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
|
||||||
nsContainerFrame* genConParentFrame =
|
|
||||||
FirstContinuationOrIBSplitSibling(aFrame)->GetContentInsertionFrame();
|
|
||||||
if (!genConParentFrame) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
nsTArray<nsIContent*>* prop = genConParentFrame->GetGenConPseudos();
|
|
||||||
if (prop) {
|
|
||||||
const nsTArray<nsIContent*>& pseudos(*prop);
|
|
||||||
for (uint32_t i = 0; i < pseudos.Length(); ++i) {
|
|
||||||
if (pseudos[i]->GetParent() == aContent &&
|
|
||||||
pseudos[i]->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentafter) {
|
|
||||||
return pseudos[i]->GetPrimaryFrame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If the last child frame is a pseudo-frame, then try that.
|
|
||||||
// Note that the frame we create for the generated content is also a
|
|
||||||
// pseudo-frame and so don't drill down in that case.
|
|
||||||
genConParentFrame = aFrame->GetContentInsertionFrame();
|
|
||||||
if (!genConParentFrame) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
nsIFrame* lastParentContinuation =
|
|
||||||
LastContinuationWithChild(static_cast<nsContainerFrame*>(
|
|
||||||
LastContinuationOrIBSplitSibling(genConParentFrame)));
|
|
||||||
nsIFrame* childFrame =
|
|
||||||
lastParentContinuation->GetChildList(nsIFrame::kPrincipalList).LastChild();
|
|
||||||
if (childFrame &&
|
|
||||||
childFrame->IsPseudoFrame(aContent) &&
|
|
||||||
!childFrame->IsGeneratedContentFrame()) {
|
|
||||||
return GetAfterFrameForContent(childFrame->FirstContinuation(), aContent);
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*static*/ nsIFrame*
|
|
||||||
nsLayoutUtils::GetAfterFrame(nsIFrame* aFrame)
|
|
||||||
{
|
|
||||||
return GetAfterFrameForContent(aFrame, aFrame->GetContent());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
|
|
|
||||||
|
|
@ -276,50 +276,26 @@ public:
|
||||||
static mozilla::layout::FrameChildListID GetChildListNameFor(nsIFrame* aChildFrame);
|
static mozilla::layout::FrameChildListID GetChildListNameFor(nsIFrame* aChildFrame);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetBeforeFrameForContent returns the ::before frame for aContent, if
|
* Returns the ::before pseudo-element for aContent, if any.
|
||||||
* one exists. This is typically O(1). The frame passed in must be
|
|
||||||
* the first-in-flow.
|
|
||||||
*
|
|
||||||
* @param aGenConParentFrame an ancestor of the ::before frame
|
|
||||||
* @param aContent the content whose ::before is wanted
|
|
||||||
* @return the ::before frame or nullptr if there isn't one
|
|
||||||
*/
|
*/
|
||||||
static nsIFrame* GetBeforeFrameForContent(nsIFrame* aGenConParentFrame,
|
static mozilla::dom::Element* GetBeforePseudo(const nsIContent* aContent);
|
||||||
const nsIContent* aContent);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetBeforeFrame returns the outermost ::before frame of the given frame, if
|
* Returns the frame corresponding to the ::before pseudo-element for
|
||||||
* one exists. This is typically O(1). The frame passed in must be
|
* aContent, if any.
|
||||||
* the first-in-flow.
|
|
||||||
*
|
|
||||||
* @param aFrame the frame whose ::before is wanted
|
|
||||||
* @return the :before frame or nullptr if there isn't one
|
|
||||||
*/
|
*/
|
||||||
static nsIFrame* GetBeforeFrame(nsIFrame* aFrame);
|
static nsIFrame* GetBeforeFrame(const nsIContent* aContent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetAfterFrameForContent returns the ::after frame for aContent, if one
|
* Returns the ::after pseudo-element for aContent, if any.
|
||||||
* exists. This will walk the in-flow chain of aGenConParentFrame to the
|
|
||||||
* last-in-flow if needed. This function is typically O(N) in the number
|
|
||||||
* of child frames, following in-flows, etc.
|
|
||||||
*
|
|
||||||
* @param aGenConParentFrame an ancestor of the ::after frame
|
|
||||||
* @param aContent the content whose ::after is wanted
|
|
||||||
* @return the ::after frame or nullptr if there isn't one
|
|
||||||
*/
|
*/
|
||||||
static nsIFrame* GetAfterFrameForContent(nsIFrame* aGenConParentFrame,
|
static mozilla::dom::Element* GetAfterPseudo(const nsIContent* aContent);
|
||||||
const nsIContent* aContent);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetAfterFrame returns the outermost ::after frame of the given frame, if one
|
* Returns the frame corresponding to the ::after pseudo-element for aContent,
|
||||||
* exists. This will walk the in-flow chain to the last-in-flow if
|
* if any.
|
||||||
* needed. This function is typically O(N) in the number of child
|
|
||||||
* frames, following in-flows, etc.
|
|
||||||
*
|
|
||||||
* @param aFrame the frame whose ::after is wanted
|
|
||||||
* @return the :after frame or nullptr if there isn't one
|
|
||||||
*/
|
*/
|
||||||
static nsIFrame* GetAfterFrame(nsIFrame* aFrame);
|
static nsIFrame* GetAfterFrame(const nsIContent* aContent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a frame, search up the frame tree until we find an
|
* Given a frame, search up the frame tree until we find an
|
||||||
|
|
|
||||||
|
|
@ -9995,19 +9995,16 @@ nsIFrame::IsPseudoStackingContextFromStyle() {
|
||||||
Element*
|
Element*
|
||||||
nsIFrame::GetPseudoElement(CSSPseudoElementType aType)
|
nsIFrame::GetPseudoElement(CSSPseudoElementType aType)
|
||||||
{
|
{
|
||||||
nsIFrame* frame = nullptr;
|
if (!mContent) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (aType == CSSPseudoElementType::before) {
|
if (aType == CSSPseudoElementType::before) {
|
||||||
frame = nsLayoutUtils::GetBeforeFrame(this);
|
return nsLayoutUtils::GetBeforePseudo(mContent);
|
||||||
} else if (aType == CSSPseudoElementType::after) {
|
|
||||||
frame = nsLayoutUtils::GetAfterFrame(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frame) {
|
if (aType == CSSPseudoElementType::after) {
|
||||||
nsIContent* content = frame->GetContent();
|
return nsLayoutUtils::GetAfterPseudo(mContent);
|
||||||
if (content->IsElement()) {
|
|
||||||
return content->AsElement();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
|
||||||
|
|
@ -1062,10 +1062,6 @@ public:
|
||||||
return GetBidiData().embeddingLevel;
|
return GetBidiData().embeddingLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsTArray<nsIContent*>* GetGenConPseudos() {
|
|
||||||
return GetProperty(GenConProperty());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the distance between the border edge of the frame and the
|
* Return the distance between the border edge of the frame and the
|
||||||
* margin edge of the frame. Like GetRect(), returns the dimensions
|
* margin edge of the frame. Like GetRect(), returns the dimensions
|
||||||
|
|
|
||||||
|
|
@ -638,6 +638,34 @@ nsComputedDOMStyle::SetFrameStyleContext(nsStyleContext* aContext)
|
||||||
mStyleContext = aContext;
|
mStyleContext = aContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The following function checks whether we need to explicitly resolve the style
|
||||||
|
* again, even though we have a style context coming from the frame.
|
||||||
|
*
|
||||||
|
* This basically checks whether the style is or may be under a ::first-line or
|
||||||
|
* ::first-letter frame, in which case we can't return the frame style, and we
|
||||||
|
* need to resolve it. See bug 505515.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
MustReresolveStyle(const nsStyleContext* aContext)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(aContext);
|
||||||
|
|
||||||
|
if (aContext->HasPseudoElementData()) {
|
||||||
|
if (!aContext->GetPseudo() ||
|
||||||
|
aContext->StyleSource().IsServoComputedValues()) {
|
||||||
|
// TODO(emilio): When ::first-line is supported in Servo, we may want to
|
||||||
|
// fix this to avoid re-resolving pseudo-element styles.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return aContext->GetParent() &&
|
||||||
|
aContext->GetParent()->HasPseudoElementData();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush)
|
nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush)
|
||||||
{
|
{
|
||||||
|
|
@ -690,9 +718,20 @@ nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush)
|
||||||
// XXX the !mElement->IsHTMLElement(nsGkAtoms::area)
|
// XXX the !mElement->IsHTMLElement(nsGkAtoms::area)
|
||||||
// check is needed due to bug 135040 (to avoid using
|
// check is needed due to bug 135040 (to avoid using
|
||||||
// mPrimaryFrame). Remove it once that's fixed.
|
// mPrimaryFrame). Remove it once that's fixed.
|
||||||
if (!mPseudo && mStyleType == eAll &&
|
if (mStyleType == eAll && !mElement->IsHTMLElement(nsGkAtoms::area)) {
|
||||||
!mElement->IsHTMLElement(nsGkAtoms::area)) {
|
mOuterFrame = nullptr;
|
||||||
|
|
||||||
|
if (!mPseudo) {
|
||||||
mOuterFrame = mElement->GetPrimaryFrame();
|
mOuterFrame = mElement->GetPrimaryFrame();
|
||||||
|
} else if (mPseudo == nsCSSPseudoElements::before ||
|
||||||
|
mPseudo == nsCSSPseudoElements::after) {
|
||||||
|
nsIAtom* property = mPseudo == nsCSSPseudoElements::before
|
||||||
|
? nsGkAtoms::beforePseudoProperty
|
||||||
|
: nsGkAtoms::afterPseudoProperty;
|
||||||
|
|
||||||
|
auto* pseudo = static_cast<Element*>(mElement->GetProperty(property));
|
||||||
|
mOuterFrame = pseudo ? pseudo->GetPrimaryFrame() : nullptr;
|
||||||
|
}
|
||||||
mInnerFrame = mOuterFrame;
|
mInnerFrame = mOuterFrame;
|
||||||
if (mOuterFrame) {
|
if (mOuterFrame) {
|
||||||
nsIAtom* type = mOuterFrame->GetType();
|
nsIAtom* type = mOuterFrame->GetType();
|
||||||
|
|
@ -711,7 +750,7 @@ nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mStyleContext || mStyleContext->HasPseudoElementData()) {
|
if (!mStyleContext || MustReresolveStyle(mStyleContext)) {
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
if (mStyleContext) {
|
if (mStyleContext) {
|
||||||
// We want to check that going through this path because of
|
// We want to check that going through this path because of
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue