Bug 698237 - Invalidate affected frames when a range in a selection is modified. r=smaug

This commit is contained in:
Mats Palmgren 2011-12-24 14:26:03 +01:00
parent b2adf684c7
commit f0f957b679
5 changed files with 241 additions and 0 deletions

View File

@ -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<nsIContent*>(aNode);
frame = content->GetPrimaryFrame();
break;
}
case nsIDOMNode::DOCUMENT_NODE:
{
nsIDocument* doc = static_cast<nsIDocument*>(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<nsINode> 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<nsINode> 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<RangeHashTable*>(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);
}
}

View File

@ -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<nsINode> mCommonAncestor;
#ifdef DEBUG
bool mWasInSelection;
#endif
static bool mIsNested;
};
};
// Make a new nsIDOMRange object

View File

@ -0,0 +1,68 @@
<!DOCTYPE HTML>
<html class="reftest-wait"><head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Testcase for bug </title>
<script>
var tests_done = 0;
var tests = [
'window.getSelection().getRangeAt(0).setEnd(document.getElementsByTagName("pre")[0].firstChild,9)',
'window.getSelection().getRangeAt(0).setEndAfter(document.getElementsByTagName("pre")[0].firstChild)',
'window.getSelection().getRangeAt(0).setEndBefore(document.getElementsByTagName("pre")[0].childNodes[1])',
'pre=document.getElementsByTagName("pre")[0]; r=window.getSelection().getRangeAt(0); r.setEnd(pre.childNodes[1],3); r.setStartAfter(pre.firstChild)',
'window.getSelection().getRangeAt(0).setStartBefore(document.getElementsByTagName("pre")[0].firstChild)',
'window.getSelection().getRangeAt(0).selectNode(document.getElementsByTagName("pre")[0].firstChild)',
'window.getSelection().getRangeAt(0).selectNodeContents(document.getElementsByTagName("pre")[0])',
'window.getSelection().getRangeAt(0).collapse(true)',
'window.getSelection().getRangeAt(0).surroundContents(document.createElement("span"))',
'window.getSelection().getRangeAt(0).setStart(document,0)',
'window.getSelection().getRangeAt(0).detach()',
'window.getSelection().getRangeAt(0).extractContents()',
'window.getSelection().getRangeAt(0).deleteContents()'
];
function init_iframe(d) {
var pre = d.createElement('pre');
pre.appendChild(d.createTextNode('first\nfirst\n'));
pre.appendChild(d.createTextNode('second'));
d.documentElement.appendChild(pre);
var text = pre.firstChild;
var sel = d.defaultView.getSelection();
var r = d.createRange();
r.setStart(text,0)
r.setEnd(text,3)
sel.addRange(r);
d.documentElement.offsetHeight;
}
function test_iframe(iframe, i) {
iframe.contentDocument.write(
'<'+'style>span { text-decoration:underline; } <'+'/style>' +
'<'+'script>' +
'window.parent.init_iframe(document);' +
'setTimeout(function(){' + window.parent.tests[i] + '; sel=window.getSelection(); try{r=sel.getRangeAt(0); sel.removeRange(r); sel.addRange(r);}catch(e){}; ++window.parent.tests_done; },0)' +
'<'+'/script>'
);
}
function create_iframe(i) {
var div = document.createElement('div');
document.body.appendChild(div);
div.innerHTML = "<iframe src='about:blank' style='height:6em; width:12em; float:left;' frameborder='0' onload='test_iframe(this,"+i+")'><iframe>"
}
var id;
function check_if_done() {
if (tests_done == tests.length) {
clearInterval(id);
document.documentElement.className = "";
}
}
function test() {
for (i = 0; i < tests.length; ++i) {
create_iframe(i);
}
id = setInterval(check_if_done,500);
}
</script>
</head>
<body onload="test()"></body>
</html>

View File

@ -0,0 +1,68 @@
<!DOCTYPE HTML>
<html class="reftest-wait"><head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Testcase for bug </title>
<script>
var tests_done = 0;
var tests = [
'window.getSelection().getRangeAt(0).setEnd(document.getElementsByTagName("pre")[0].firstChild,9)',
'window.getSelection().getRangeAt(0).setEndAfter(document.getElementsByTagName("pre")[0].firstChild)',
'window.getSelection().getRangeAt(0).setEndBefore(document.getElementsByTagName("pre")[0].childNodes[1])',
'pre=document.getElementsByTagName("pre")[0]; r=window.getSelection().getRangeAt(0); r.setEnd(pre.childNodes[1],3); r.setStartAfter(pre.firstChild)',
'window.getSelection().getRangeAt(0).setStartBefore(document.getElementsByTagName("pre")[0].firstChild)',
'window.getSelection().getRangeAt(0).selectNode(document.getElementsByTagName("pre")[0].firstChild)',
'window.getSelection().getRangeAt(0).selectNodeContents(document.getElementsByTagName("pre")[0])',
'window.getSelection().getRangeAt(0).collapse(true)',
'window.getSelection().getRangeAt(0).surroundContents(document.createElement("span"))',
'window.getSelection().getRangeAt(0).setStart(document,0)',
'window.getSelection().getRangeAt(0).detach()',
'window.getSelection().getRangeAt(0).extractContents()',
'window.getSelection().getRangeAt(0).deleteContents()'
];
function init_iframe(d) {
var pre = d.createElement('pre');
pre.appendChild(d.createTextNode('first\nfirst\n'));
pre.appendChild(d.createTextNode('second'));
d.documentElement.appendChild(pre);
var text = pre.firstChild;
var sel = d.defaultView.getSelection();
var r = d.createRange();
r.setStart(text,0)
r.setEnd(text,3)
sel.addRange(r);
d.documentElement.offsetHeight;
}
function test_iframe(iframe, i) {
iframe.contentDocument.write(
'<'+'style>span { text-decoration:underline; } <'+'/style>' +
'<'+'script>' +
'window.parent.init_iframe(document);' +
'setTimeout(function(){' + window.parent.tests[i] + '; ++window.parent.tests_done; },0)' +
'<'+'/script>'
);
}
function create_iframe(i) {
var div = document.createElement('div');
document.body.appendChild(div);
div.innerHTML = "<iframe src='about:blank' style='height:6em; width:12em; float:left;' frameborder='0' onload='test_iframe(this,"+i+")'><iframe>"
}
var id;
function check_if_done() {
if (tests_done == tests.length) {
clearInterval(id);
document.documentElement.className = "";
}
}
function test() {
for (i = 0; i < tests.length; ++i) {
create_iframe(i);
}
id = setInterval(check_if_done,500);
}
</script>
</head>
<body onload="test()"></body>
</html>

View File

@ -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