mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 07:42:04 +00:00
Bug 698237 - Invalidate affected frames when a range in a selection is modified. r=smaug
This commit is contained in:
parent
b2adf684c7
commit
f0f957b679
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
68
layout/reftests/selection/modify-range-ref.html
Normal file
68
layout/reftests/selection/modify-range-ref.html
Normal 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>
|
68
layout/reftests/selection/modify-range.html
Normal file
68
layout/reftests/selection/modify-range.html
Normal 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>
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user