From f0f957b679107213711564ab800567cf3d56a042 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Sat, 24 Dec 2011 14:26:03 +0100 Subject: [PATCH] Bug 698237 - Invalidate affected frames when a range in a selection is modified. r=smaug --- content/base/src/nsRange.cpp | 70 +++++++++++++++++++ content/base/src/nsRange.h | 34 +++++++++ .../reftests/selection/modify-range-ref.html | 68 ++++++++++++++++++ layout/reftests/selection/modify-range.html | 68 ++++++++++++++++++ layout/reftests/selection/reftest.list | 1 + 5 files changed, 241 insertions(+) create mode 100644 layout/reftests/selection/modify-range-ref.html create mode 100644 layout/reftests/selection/modify-range.html diff --git a/content/base/src/nsRange.cpp b/content/base/src/nsRange.cpp index 1e47761d6c0b..5733d172d297 100644 --- a/content/base/src/nsRange.cpp +++ b/content/base/src/nsRange.cpp @@ -84,6 +84,32 @@ nsresult NS_NewContentSubtreeIterator(nsIContentIterator** aInstancePtrResult); } \ PR_END_MACRO +static void InvalidateAllFrames(nsINode* aNode) +{ + NS_PRECONDITION(aNode, "bad arg"); + + nsIFrame* frame = nsnull; + switch (aNode->NodeType()) { + case nsIDOMNode::TEXT_NODE: + case nsIDOMNode::ELEMENT_NODE: + { + nsIContent* content = static_cast(aNode); + frame = content->GetPrimaryFrame(); + break; + } + case nsIDOMNode::DOCUMENT_NODE: + { + nsIDocument* doc = static_cast(aNode); + nsIPresShell* shell = doc ? doc->GetShell() : nsnull; + frame = shell ? shell->GetRootFrame() : nsnull; + break; + } + } + for (nsIFrame* f = frame; f; f = f->GetNextContinuation()) { + f->InvalidateFrameSubtree(); + } +} + // Utility routine to detect if a content node is completely contained in a range // If outNodeBefore is returned true, then the node starts before the range does. // If outNodeAfter is returned true, then the node ends after the range does. @@ -939,6 +965,7 @@ nsRange::SetStart(nsIDOMNode* aParent, PRInt32 aOffset) VALIDATE_ACCESS(aParent); nsCOMPtr parent = do_QueryInterface(aParent); + AutoInvalidateSelection atEndOfBlock(this); return SetStart(parent, aOffset); } @@ -1000,6 +1027,7 @@ nsRange::SetEnd(nsIDOMNode* aParent, PRInt32 aOffset) { VALIDATE_ACCESS(aParent); + AutoInvalidateSelection atEndOfBlock(this); nsCOMPtr parent = do_QueryInterface(aParent); return SetEnd(parent, aOffset); } @@ -1067,6 +1095,7 @@ nsRange::Collapse(bool aToStart) if (!mIsPositioned) return NS_ERROR_NOT_INITIALIZED; + AutoInvalidateSelection atEndOfBlock(this); if (aToStart) DoSetRange(mStartParent, mStartOffset, mStartParent, mStartOffset, mRoot); else @@ -1092,6 +1121,7 @@ nsRange::SelectNode(nsIDOMNode* aN) return NS_ERROR_DOM_RANGE_INVALID_NODE_TYPE_ERR; } + AutoInvalidateSelection atEndOfBlock(this); DoSetRange(parent, index, parent, index + 1, newRoot); return NS_OK; @@ -1106,6 +1136,7 @@ nsRange::SelectNodeContents(nsIDOMNode* aN) nsINode* newRoot = IsValidBoundary(node); NS_ENSURE_TRUE(newRoot, NS_ERROR_DOM_RANGE_INVALID_NODE_TYPE_ERR); + AutoInvalidateSelection atEndOfBlock(this); DoSetRange(node, 0, node, GetNodeLength(node), newRoot); return NS_OK; @@ -2335,6 +2366,10 @@ nsRange::Detach() if(mIsDetached) return NS_ERROR_DOM_INVALID_STATE_ERR; + if (IsInSelection()) { + ::InvalidateAllFrames(GetRegisteredCommonAncestor()); + } + mIsDetached = true; DoSetRange(nsnull, 0, nsnull, 0, nsnull); @@ -2591,3 +2626,38 @@ nsRange::GetUsedFontFaces(nsIDOMFontFaceList** aResult) fontFaceList.forget(aResult); return NS_OK; } + +nsINode* +nsRange::GetRegisteredCommonAncestor() +{ + NS_ASSERTION(IsInSelection(), + "GetRegisteredCommonAncestor only valid for range in selection"); + nsINode* ancestor = GetNextRangeCommonAncestor(mStartParent); + while (ancestor) { + RangeHashTable* ranges = + static_cast(ancestor->GetProperty(nsGkAtoms::range)); + if (ranges->GetEntry(this)) { + break; + } + ancestor = GetNextRangeCommonAncestor(ancestor->GetNodeParent()); + } + NS_ASSERTION(ancestor, "can't find common ancestor for selected range"); + return ancestor; +} + +/* static */ bool nsRange::AutoInvalidateSelection::mIsNested; + +nsRange::AutoInvalidateSelection::~AutoInvalidateSelection() +{ + NS_ASSERTION(mWasInSelection == mRange->IsInSelection(), + "Range got unselected in AutoInvalidateSelection block"); + if (!mCommonAncestor) { + return; + } + mIsNested = false; + ::InvalidateAllFrames(mCommonAncestor); + nsINode* commonAncestor = mRange->GetRegisteredCommonAncestor(); + if (commonAncestor != mCommonAncestor) { + ::InvalidateAllFrames(commonAncestor); + } +} diff --git a/content/base/src/nsRange.h b/content/base/src/nsRange.h index 7472f1dd0f84..a2750d6bc060 100644 --- a/content/base/src/nsRange.h +++ b/content/base/src/nsRange.h @@ -165,6 +165,40 @@ protected: void DoSetRange(nsINode* aStartN, PRInt32 aStartOffset, nsINode* aEndN, PRInt32 aEndOffset, nsINode* aRoot, bool aNotInsertedYet = false); + + /** + * For a range for which IsInSelection() is true, return the common + * ancestor for the range. This method uses the selection bits and + * nsGkAtoms::range property on the nodes to quickly find the ancestor. + * That is, it's a faster version of GetCommonAncestor that only works + * for ranges in a Selection. The method will assert and the behavior + * is undefined if called on a range where IsInSelection() is false. + */ + nsINode* GetRegisteredCommonAncestor(); + + struct NS_STACK_CLASS AutoInvalidateSelection + { + AutoInvalidateSelection(nsRange* aRange) : mRange(aRange) + { +#ifdef DEBUG + mWasInSelection = mRange->IsInSelection(); +#endif + if (!mRange->IsInSelection() || mIsNested) { + return; + } + mIsNested = true; + NS_ASSERTION(!mRange->IsDetached(), "detached range in selection"); + mCommonAncestor = mRange->GetRegisteredCommonAncestor(); + } + ~AutoInvalidateSelection(); + nsRange* mRange; + nsRefPtr mCommonAncestor; +#ifdef DEBUG + bool mWasInSelection; +#endif + static bool mIsNested; + }; + }; // Make a new nsIDOMRange object diff --git a/layout/reftests/selection/modify-range-ref.html b/layout/reftests/selection/modify-range-ref.html new file mode 100644 index 000000000000..5086fcef8aa8 --- /dev/null +++ b/layout/reftests/selection/modify-range-ref.html @@ -0,0 +1,68 @@ + + + + Testcase for bug + + + + + diff --git a/layout/reftests/selection/modify-range.html b/layout/reftests/selection/modify-range.html new file mode 100644 index 000000000000..2595511ed0da --- /dev/null +++ b/layout/reftests/selection/modify-range.html @@ -0,0 +1,68 @@ + + + + Testcase for bug + + + + + diff --git a/layout/reftests/selection/reftest.list b/layout/reftests/selection/reftest.list index cbc127b881b4..65f5df86520a 100644 --- a/layout/reftests/selection/reftest.list +++ b/layout/reftests/selection/reftest.list @@ -31,4 +31,5 @@ fails-if(cocoaWidget) == themed-widget.html themed-widget-ref.html == addrange-1.html addrange-ref.html == addrange-2.html addrange-ref.html == splitText-normalize.html splitText-normalize-ref.html +== modify-range.html modify-range-ref.html == dom-mutations.html dom-mutations-ref.html