mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-05 23:38:38 +09:00
Bug 1404789 - Stop reconstructing frames for the whole shadow root each time content is inserted in a shadow tree
* Cleanup a bit the ShadowRoot code * Privatize ShadowRoot methods * When the shadow tree distribution changes, post a restyle + reframe * Simplify ShadowRoot::IsPooledNode * Be a bit better at detecting distribution changes Tag #1375
This commit is contained in:
parent
6b075994f5
commit
1846dde168
7 changed files with 243 additions and 137 deletions
|
|
@ -79,10 +79,10 @@ ShadowRoot::ShadowRoot(Element* aElement,
|
|||
|
||||
ShadowRoot::~ShadowRoot()
|
||||
{
|
||||
if (GetHost()) {
|
||||
// mPoolHost may have been unlinked or a new ShadowRoot may have been
|
||||
// creating, making this one obsolete.
|
||||
GetHost()->RemoveMutationObserver(this);
|
||||
if (auto* host = GetHost()) {
|
||||
// mHost may have been unlinked or a new ShadowRoot may have been
|
||||
// created, making this one obsolete.
|
||||
host->RemoveMutationObserver(this);
|
||||
}
|
||||
|
||||
UnsetFlags(NODE_IS_IN_SHADOW_TREE);
|
||||
|
|
@ -114,8 +114,7 @@ ShadowRoot::StyleSheetChanged()
|
|||
{
|
||||
mProtoBinding->FlushSkinSheets();
|
||||
|
||||
nsIPresShell* shell = OwnerDoc()->GetShell();
|
||||
if (shell) {
|
||||
if (nsIPresShell* shell = OwnerDoc()->GetShell()) {
|
||||
OwnerDoc()->BeginUpdate(UPDATE_STYLE);
|
||||
shell->RecordShadowStyleChange(this);
|
||||
OwnerDoc()->EndUpdate(UPDATE_STYLE);
|
||||
|
|
@ -253,96 +252,106 @@ ShadowRoot::RemoveDestInsertionPoint(nsIContent* aInsertionPoint,
|
|||
}
|
||||
|
||||
void
|
||||
ShadowRoot::DistributionChanged()
|
||||
{
|
||||
// FIXME(emilio): We could be more granular in a bunch of cases.
|
||||
auto* host = GetHost();
|
||||
if (!host || !host->IsInComposedDoc()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* shell = OwnerDoc()->GetShell();
|
||||
if (!shell) {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME(emilio): Rename this to DestroyFramesForAndRestyle?
|
||||
shell->DestroyFramesFor(host);
|
||||
}
|
||||
|
||||
const HTMLContentElement*
|
||||
ShadowRoot::DistributeSingleNode(nsIContent* aContent)
|
||||
{
|
||||
// Find the insertion point to which the content belongs.
|
||||
HTMLContentElement* insertionPoint = nullptr;
|
||||
for (uint32_t i = 0; i < mInsertionPoints.Length(); i++) {
|
||||
if (mInsertionPoints[i]->Match(aContent)) {
|
||||
if (mInsertionPoints[i]->MatchedNodes().Contains(aContent)) {
|
||||
HTMLContentElement* foundInsertionPoint = nullptr;
|
||||
for (HTMLContentElement* insertionPoint : mInsertionPoints) {
|
||||
if (insertionPoint->Match(aContent)) {
|
||||
if (insertionPoint->MatchedNodes().Contains(aContent)) {
|
||||
// Node is already matched into the insertion point. We are done.
|
||||
return;
|
||||
return insertionPoint;
|
||||
}
|
||||
|
||||
// Matching may cause the insertion point to drop fallback content.
|
||||
if (mInsertionPoints[i]->MatchedNodes().IsEmpty() &&
|
||||
static_cast<nsINode*>(mInsertionPoints[i])->GetFirstChild()) {
|
||||
if (insertionPoint->MatchedNodes().IsEmpty() &&
|
||||
insertionPoint->HasChildren()) {
|
||||
// This match will cause the insertion point to drop all fallback
|
||||
// content and used matched nodes instead. Give up on the optimization
|
||||
// and just distribute all nodes.
|
||||
DistributeAllNodes();
|
||||
return;
|
||||
MOZ_ASSERT(insertionPoint->MatchedNodes().Contains(aContent));
|
||||
return insertionPoint;
|
||||
}
|
||||
insertionPoint = mInsertionPoints[i];
|
||||
foundInsertionPoint = insertionPoint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundInsertionPoint) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Find the index into the insertion point.
|
||||
if (insertionPoint) {
|
||||
nsCOMArray<nsIContent>& matchedNodes = insertionPoint->MatchedNodes();
|
||||
// Find the appropriate position in the matched node list for the
|
||||
// newly distributed content.
|
||||
bool isIndexFound = false;
|
||||
ExplicitChildIterator childIterator(GetHost());
|
||||
for (uint32_t i = 0; i < matchedNodes.Length(); i++) {
|
||||
// Seek through the host's explicit children until the inserted content
|
||||
// is found or when the current matched node is reached.
|
||||
if (childIterator.Seek(aContent, matchedNodes[i])) {
|
||||
// aContent was found before the current matched node.
|
||||
insertionPoint->InsertMatchedNode(i, aContent);
|
||||
isIndexFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isIndexFound) {
|
||||
// We have still not found an index in the insertion point,
|
||||
// thus it must be at the end.
|
||||
MOZ_ASSERT(childIterator.Seek(aContent, nullptr),
|
||||
"Trying to match a node that is not a candidate to be matched");
|
||||
insertionPoint->AppendMatchedNode(aContent);
|
||||
}
|
||||
|
||||
// Handle the case where the parent of the insertion point has a ShadowRoot.
|
||||
// The node distributed into the insertion point must be reprojected to the
|
||||
// insertion points of the parent's ShadowRoot.
|
||||
ShadowRoot* parentShadow = insertionPoint->GetParent()->GetShadowRoot();
|
||||
if (parentShadow) {
|
||||
parentShadow->DistributeSingleNode(aContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ShadowRoot::RemoveDistributedNode(nsIContent* aContent)
|
||||
{
|
||||
// Find insertion point containing the content and remove the node.
|
||||
for (uint32_t i = 0; i < mInsertionPoints.Length(); i++) {
|
||||
if (mInsertionPoints[i]->MatchedNodes().Contains(aContent)) {
|
||||
// Removing the matched node may cause the insertion point to use
|
||||
// fallback content.
|
||||
if (mInsertionPoints[i]->MatchedNodes().Length() == 1 &&
|
||||
static_cast<nsINode*>(mInsertionPoints[i])->GetFirstChild()) {
|
||||
// Removing the matched node will cause fallback content to be
|
||||
// used instead. Give up optimization and distribute all nodes.
|
||||
DistributeAllNodes();
|
||||
return;
|
||||
}
|
||||
|
||||
mInsertionPoints[i]->RemoveMatchedNode(aContent);
|
||||
|
||||
// Handle the case where the parent of the insertion point has a ShadowRoot.
|
||||
// The removed node needs to be removed from the insertion points of the
|
||||
// parent's ShadowRoot.
|
||||
ShadowRoot* parentShadow = mInsertionPoints[i]->GetParent()->GetShadowRoot();
|
||||
if (parentShadow) {
|
||||
parentShadow->RemoveDistributedNode(aContent);
|
||||
}
|
||||
|
||||
nsCOMArray<nsIContent>& matchedNodes = foundInsertionPoint->MatchedNodes();
|
||||
// Find the appropriate position in the matched node list for the
|
||||
// newly distributed content.
|
||||
bool isIndexFound = false;
|
||||
ExplicitChildIterator childIterator(GetHost());
|
||||
for (uint32_t i = 0; i < matchedNodes.Length(); i++) {
|
||||
// Seek through the host's explicit children until the inserted content
|
||||
// is found or when the current matched node is reached.
|
||||
if (childIterator.Seek(aContent, matchedNodes[i])) {
|
||||
// aContent was found before the current matched node.
|
||||
foundInsertionPoint->InsertMatchedNode(i, aContent);
|
||||
isIndexFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isIndexFound) {
|
||||
// We have still not found an index in the insertion point,
|
||||
// thus it must be at the end.
|
||||
MOZ_ASSERT(childIterator.Seek(aContent, nullptr),
|
||||
"Trying to match a node that is not a candidate to be matched");
|
||||
foundInsertionPoint->AppendMatchedNode(aContent);
|
||||
}
|
||||
|
||||
return foundInsertionPoint;
|
||||
}
|
||||
|
||||
const HTMLContentElement*
|
||||
ShadowRoot::RemoveDistributedNode(nsIContent* aContent)
|
||||
{
|
||||
// Find insertion point containing the content and remove the node.
|
||||
for (HTMLContentElement* insertionPoint : mInsertionPoints) {
|
||||
if (!insertionPoint->MatchedNodes().Contains(aContent)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Removing the matched node may cause the insertion point to use
|
||||
// fallback content.
|
||||
if (insertionPoint->MatchedNodes().Length() == 1 &&
|
||||
insertionPoint->HasChildren()) {
|
||||
// Removing the matched node will cause fallback content to be
|
||||
// used instead. Give up optimization and distribute all nodes.
|
||||
DistributeAllNodes();
|
||||
return insertionPoint;
|
||||
}
|
||||
|
||||
insertionPoint->RemoveMatchedNode(aContent);
|
||||
return insertionPoint;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -358,19 +367,19 @@ ShadowRoot::DistributeAllNodes()
|
|||
|
||||
nsTArray<ShadowRoot*> shadowsToUpdate;
|
||||
|
||||
for (uint32_t i = 0; i < mInsertionPoints.Length(); i++) {
|
||||
mInsertionPoints[i]->ClearMatchedNodes();
|
||||
for (HTMLContentElement* insertionPoint : mInsertionPoints) {
|
||||
insertionPoint->ClearMatchedNodes();
|
||||
// Assign matching nodes from node pool.
|
||||
for (uint32_t j = 0; j < nodePool.Length(); j++) {
|
||||
if (mInsertionPoints[i]->Match(nodePool[j])) {
|
||||
mInsertionPoints[i]->AppendMatchedNode(nodePool[j]);
|
||||
if (insertionPoint->Match(nodePool[j])) {
|
||||
insertionPoint->AppendMatchedNode(nodePool[j]);
|
||||
nodePool.RemoveElementAt(j--);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep track of instances where the content insertion point is distributed
|
||||
// (parent of insertion point has a ShadowRoot).
|
||||
nsIContent* insertionParent = mInsertionPoints[i]->GetParent();
|
||||
nsIContent* insertionParent = insertionPoint->GetParent();
|
||||
MOZ_ASSERT(insertionParent, "The only way for an insertion point to be in the"
|
||||
"mInsertionPoints array is to be a descendant of a"
|
||||
"ShadowRoot, in which case, it should have a parent");
|
||||
|
|
@ -384,9 +393,11 @@ ShadowRoot::DistributeAllNodes()
|
|||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < shadowsToUpdate.Length(); i++) {
|
||||
shadowsToUpdate[i]->DistributeAllNodes();
|
||||
for (ShadowRoot* shadow : shadowsToUpdate) {
|
||||
shadow->DistributeAllNodes();
|
||||
}
|
||||
|
||||
DistributionChanged();
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -447,16 +458,16 @@ ShadowRoot::StyleSheets()
|
|||
* test nodes that have not yet been distributed.
|
||||
*/
|
||||
bool
|
||||
ShadowRoot::IsPooledNode(nsIContent* aContent, nsIContent* aContainer,
|
||||
nsIContent* aHost)
|
||||
ShadowRoot::IsPooledNode(nsIContent* aContent) const
|
||||
{
|
||||
if (nsContentUtils::IsContentInsertionPoint(aContent)) {
|
||||
// Insertion points never end up in the pool.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aContainer == aHost &&
|
||||
nsContentUtils::IsInSameAnonymousTree(aContainer, aContent)) {
|
||||
auto* host = GetHost();
|
||||
auto* container = aContent->GetParent();
|
||||
if (container == host && !aContent->IsRootOfAnonymousSubtree()) {
|
||||
// Children of the host will end up in the pool. We check to ensure
|
||||
// that the content is in the same anonymous tree as the container
|
||||
// because anonymous content may report its container as the host
|
||||
|
|
@ -464,12 +475,11 @@ ShadowRoot::IsPooledNode(nsIContent* aContent, nsIContent* aContainer,
|
|||
return true;
|
||||
}
|
||||
|
||||
if (aContainer) {
|
||||
if (auto* content = HTMLContentElement::FromContentOrNull(container)) {
|
||||
// Fallback content will end up in pool if its parent is a child of the host.
|
||||
HTMLContentElement* content = HTMLContentElement::FromContent(aContainer);
|
||||
return content && content->IsInsertionPoint() &&
|
||||
return content->IsInsertionPoint() &&
|
||||
content->MatchedNodes().IsEmpty() &&
|
||||
aContainer->GetParentNode() == aHost;
|
||||
container->GetParentNode() == host;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -483,13 +493,70 @@ ShadowRoot::AttributeChanged(nsIDocument* aDocument,
|
|||
int32_t aModType,
|
||||
const nsAttrValue* aOldValue)
|
||||
{
|
||||
if (!IsPooledNode(aElement, aElement->GetParent(), GetHost())) {
|
||||
if (!IsPooledNode(aElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Attributes may change insertion point matching, find its new distribution.
|
||||
RemoveDistributedNode(aElement);
|
||||
DistributeSingleNode(aElement);
|
||||
//
|
||||
// FIXME(emilio): What about state changes?
|
||||
if (!RedistributeElement(aElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aElement->IsInComposedDoc()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* shell = OwnerDoc()->GetShell();
|
||||
if (!shell) {
|
||||
return;
|
||||
}
|
||||
|
||||
shell->DestroyFramesFor(aElement);
|
||||
}
|
||||
|
||||
bool
|
||||
ShadowRoot::RedistributeElement(Element* aElement)
|
||||
{
|
||||
auto* oldInsertionPoint = RemoveDistributedNode(aElement);
|
||||
auto* newInsertionPoint = DistributeSingleNode(aElement);
|
||||
|
||||
if (oldInsertionPoint == newInsertionPoint) {
|
||||
if (oldInsertionPoint) {
|
||||
if (auto* shadow = oldInsertionPoint->GetParent()->GetShadowRoot()) {
|
||||
return shadow->RedistributeElement(aElement);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
while (oldInsertionPoint) {
|
||||
// Handle the case where the parent of the insertion point has a ShadowRoot.
|
||||
// The node distributed into the insertion point must be reprojected to the
|
||||
// insertion points of the parent's ShadowRoot.
|
||||
auto* shadow = oldInsertionPoint->GetParent()->GetShadowRoot();
|
||||
if (!shadow) {
|
||||
break;
|
||||
}
|
||||
|
||||
oldInsertionPoint = shadow->RemoveDistributedNode(aElement);
|
||||
}
|
||||
|
||||
while (newInsertionPoint) {
|
||||
// Handle the case where the parent of the insertion point has a ShadowRoot.
|
||||
// The node distributed into the insertion point must be reprojected to the
|
||||
// insertion points of the parent's ShadowRoot.
|
||||
auto* shadow = newInsertionPoint->GetParent()->GetShadowRoot();
|
||||
if (!shadow) {
|
||||
break;
|
||||
}
|
||||
|
||||
newInsertionPoint = shadow->DistributeSingleNode(aElement);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -498,29 +565,10 @@ ShadowRoot::ContentAppended(nsIDocument* aDocument,
|
|||
nsIContent* aFirstNewContent,
|
||||
int32_t aNewIndexInContainer)
|
||||
{
|
||||
if (mInsertionPointChanged) {
|
||||
DistributeAllNodes();
|
||||
mInsertionPointChanged = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Watch for new nodes added to the pool because the node
|
||||
// may need to be added to an insertion point.
|
||||
nsIContent* currentChild = aFirstNewContent;
|
||||
while (currentChild) {
|
||||
// Add insertion point to destination insertion points of fallback content.
|
||||
if (nsContentUtils::IsContentInsertionPoint(aContainer)) {
|
||||
HTMLContentElement* content = HTMLContentElement::FromContent(aContainer);
|
||||
if (content->MatchedNodes().IsEmpty()) {
|
||||
currentChild->DestInsertionPoints().AppendElement(aContainer);
|
||||
}
|
||||
}
|
||||
|
||||
if (IsPooledNode(currentChild, aContainer, GetHost())) {
|
||||
DistributeSingleNode(currentChild);
|
||||
}
|
||||
|
||||
currentChild = currentChild->GetNextSibling();
|
||||
for (nsIContent* content = aFirstNewContent;
|
||||
content;
|
||||
content = content->GetNextSibling()) {
|
||||
ContentInserted(aDocument, aContainer, aFirstNewContent, aNewIndexInContainer);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -536,18 +584,29 @@ ShadowRoot::ContentInserted(nsIDocument* aDocument,
|
|||
return;
|
||||
}
|
||||
|
||||
// Add insertion point to destination insertion points of fallback content.
|
||||
if (nsContentUtils::IsContentInsertionPoint(aContainer)) {
|
||||
HTMLContentElement* content = HTMLContentElement::FromContent(aContainer);
|
||||
if (content && content->MatchedNodes().IsEmpty()) {
|
||||
aChild->DestInsertionPoints().AppendElement(aContainer);
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for new nodes added to the pool because the node
|
||||
// may need to be added to an insertion point.
|
||||
if (IsPooledNode(aChild, aContainer, GetHost())) {
|
||||
// Add insertion point to destination insertion points of fallback content.
|
||||
if (nsContentUtils::IsContentInsertionPoint(aContainer)) {
|
||||
HTMLContentElement* content = HTMLContentElement::FromContent(aContainer);
|
||||
if (content->MatchedNodes().IsEmpty()) {
|
||||
aChild->DestInsertionPoints().AppendElement(aContainer);
|
||||
if (IsPooledNode(aChild)) {
|
||||
auto* insertionPoint = DistributeSingleNode(aChild);
|
||||
while (insertionPoint) {
|
||||
// Handle the case where the parent of the insertion point has a ShadowRoot.
|
||||
// The node distributed into the insertion point must be reprojected to the
|
||||
// insertion points of the parent's ShadowRoot.
|
||||
auto* parentShadow = insertionPoint->GetParent()->GetShadowRoot();
|
||||
if (!parentShadow) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DistributeSingleNode(aChild);
|
||||
insertionPoint = parentShadow->DistributeSingleNode(aChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -575,8 +634,21 @@ ShadowRoot::ContentRemoved(nsIDocument* aDocument,
|
|||
|
||||
// Watch for node that is removed from the pool because
|
||||
// it may need to be removed from an insertion point.
|
||||
if (IsPooledNode(aChild, aContainer, GetHost())) {
|
||||
RemoveDistributedNode(aChild);
|
||||
if (IsPooledNode(aChild)) {
|
||||
auto* insertionPoint = RemoveDistributedNode(aChild);
|
||||
while (insertionPoint) {
|
||||
// Handle the case where the parent of the insertion point has a
|
||||
// ShadowRoot.
|
||||
//
|
||||
// The removed node needs to be removed from the insertion points of the
|
||||
// parent's ShadowRoot.
|
||||
auto* parentShadow = insertionPoint->GetParent()->GetShadowRoot();
|
||||
if (!parentShadow) {
|
||||
break;
|
||||
}
|
||||
|
||||
insertionPoint = parentShadow->RemoveDistributedNode(aChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue