Bug 371839. Simplify SetSelected signature and semantics, and reimplement it in nsTextFrame much more efficiently. r=bzbarsky

--HG--
extra : rebase_source : 18c390e3ebd09de82b1a23699c56b741f928ca37
This commit is contained in:
Robert O'Callahan 2009-07-27 10:05:41 +12:00
parent 36956c156b
commit 8bab94a96a
13 changed files with 196 additions and 243 deletions

View File

@ -43,15 +43,7 @@
#ifndef nsGenericDOMDataNode_h___
#define nsGenericDOMDataNode_h___
// This bit is set to indicate that if the text node changes to
// non-whitespace, we may need to create a frame for it. This bit must
// not be set on nodes that already have a frame.
#define NS_CREATE_FRAME_IF_NON_WHITESPACE (1 << NODE_TYPE_SPECIFIC_BITS_OFFSET)
// This bit is set to indicate that if the text node changes to
// whitespace, we may need to reframe it (or its ancestors).
#define NS_REFRAME_IF_WHITESPACE (1 << (NODE_TYPE_SPECIFIC_BITS_OFFSET + 1))
#include "nsIContent.h"
#include "nsIDOMCharacterData.h"
#include "nsIDOMEventTarget.h"
#include "nsIDOM3Text.h"
@ -66,6 +58,18 @@
#include "nsISMILAttr.h"
#endif // MOZ_SMIL
// This bit is set to indicate that if the text node changes to
// non-whitespace, we may need to create a frame for it. This bit must
// not be set on nodes that already have a frame.
#define NS_CREATE_FRAME_IF_NON_WHITESPACE (1 << NODE_TYPE_SPECIFIC_BITS_OFFSET)
// This bit is set to indicate that if the text node changes to
// whitespace, we may need to reframe it (or its ancestors).
#define NS_REFRAME_IF_WHITESPACE (1 << (NODE_TYPE_SPECIFIC_BITS_OFFSET + 1))
// This bit is set to indicate that the text may be part of a selection.
#define NS_TEXT_IN_SELECTION (1 << (NODE_TYPE_SPECIFIC_BITS_OFFSET + 2))
class nsIDOMAttr;
class nsIDOMEventListener;
class nsIDOMNodeList;

View File

@ -110,21 +110,6 @@ nsFirstLetterFrame::SetInitialChildList(nsIAtom* aListName,
return NS_OK;
}
NS_IMETHODIMP
nsFirstLetterFrame::SetSelected(nsPresContext* aPresContext, nsIDOMRange *aRange,PRBool aSelected, nsSpread aSpread, SelectionType aType)
{
if (aSelected && ParentDisablesSelection())
return NS_OK;
nsIFrame *child = GetFirstChild(nsnull);
while (child)
{
child->SetSelected(aPresContext, aRange, aSelected, aSpread, aType);
// don't worry about result. there are more frames to come
child = child->GetNextSibling();
}
return NS_OK;
}
NS_IMETHODIMP
nsFirstLetterFrame::GetChildFrameContainingOffset(PRInt32 inContentOffset,
PRBool inHint,

View File

@ -83,8 +83,6 @@ public:
virtual PRBool CanContinueTextRun() const;
NS_IMETHOD SetSelected(nsPresContext* aPresContext, nsIDOMRange *aRange,PRBool aSelected, nsSpread aSpread, SelectionType aType);
//override of nsFrame method
NS_IMETHOD GetChildFrameContainingOffset(PRInt32 inContentOffset,
PRBool inHint,

View File

@ -4487,43 +4487,30 @@ nsFrame::DumpBaseRegressionData(nsPresContext* aPresContext, FILE* out, PRInt32
}
#endif
/*this method may.. invalidate if the state was changed or if aForceRedraw is PR_TRUE
it will not update immediately.*/
NS_IMETHODIMP
nsFrame::SetSelected(nsPresContext* aPresContext, nsIDOMRange *aRange, PRBool aSelected, nsSpread aSpread, SelectionType aType)
void
nsIFrame::SetSelected(PRBool aSelected, SelectionType aType)
{
/*
if (aSelected && ParentDisablesSelection())
return NS_OK;
*/
NS_ASSERTION(!GetPrevContinuation(),
"Should only be called on first in flow");
if (aType != nsISelectionController::SELECTION_NORMAL)
return;
if (aType == nsISelectionController::SELECTION_NORMAL) {
// check whether style allows selection
PRBool selectable;
IsSelectable(&selectable, nsnull);
if (!selectable)
return NS_OK;
}
// check whether style allows selection
PRBool selectable;
IsSelectable(&selectable, nsnull);
if (!selectable)
return;
/*
if (eSpreadDown == aSpread){
nsIFrame* kid = GetFirstChild(nsnull);
while (nsnull != kid) {
kid->SetSelected(nsnull,aSelected,aSpread);
kid = kid->GetNextSibling();
for (nsIFrame* f = this; f; f = f->GetNextContinuation()) {
if (aSelected) {
AddStateBits(NS_FRAME_SELECTED_CONTENT);
} else {
RemoveStateBits(NS_FRAME_SELECTED_CONTENT);
}
}
*/
if ( aSelected ){
AddStateBits(NS_FRAME_SELECTED_CONTENT);
}
else
RemoveStateBits(NS_FRAME_SELECTED_CONTENT);
// Repaint this frame subtree's entire area
InvalidateOverflowRect();
return NS_OK;
// Repaint this frame subtree's entire area
InvalidateOverflowRect();
}
}
NS_IMETHODIMP

View File

@ -237,7 +237,6 @@ public:
NS_IMETHOD DumpRegressionData(nsPresContext* aPresContext, FILE* out, PRInt32 aIndent);
#endif
NS_IMETHOD SetSelected(nsPresContext* aPresContext, nsIDOMRange *aRange,PRBool aSelected, nsSpread aSpread, SelectionType aType);
NS_IMETHOD GetSelected(PRBool *aSelected) const;
NS_IMETHOD IsSelectable(PRBool* aIsSelectable, PRUint8* aSelectStyle) const;

View File

@ -1887,19 +1887,20 @@ public:
/** Selection related calls
*/
/**
* Called to set the selection of the frame based on frame offsets. you can FORCE the frame
* to redraw event if aSelected == the frame selection with the last parameter.
* data in struct may be changed when passed in.
* @param aRange is the range that will dictate if the frames need to be redrawn null means the whole content needs to be redrawn
* Called to set the selection status of the frame.
*
* This must be called on the primary frame, but all continuations
* will be affected the same way.
*
* This sets or clears NS_FRAME_SELECTED_CONTENT for each frame in the
* continuation chain, if the frames are currently selectable.
* The frames are unconditionally invalidated, if this selection type
* is supported at all.
* @param aSelected is it selected?
* @param aSpread should it spread the selection to flow elements around it? or go down to its children?
* @param aType the selection type of the selection that you are setting on the frame
*/
NS_IMETHOD SetSelected(nsPresContext* aPresContext,
nsIDOMRange* aRange,
PRBool aSelected,
nsSpread aSpread,
SelectionType aType) = 0;
virtual void SetSelected(PRBool aSelected,
SelectionType aType);
NS_IMETHOD GetSelected(PRBool *aSelected) const = 0;

View File

@ -79,6 +79,7 @@
#include "nsLayoutCID.h"
#include "nsBidiPresUtils.h"
static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID);
#include "nsTextFrame.h"
#include "nsIDOMText.h"
@ -301,7 +302,9 @@ private:
void setAnchorFocusRange(PRInt32 aIndex); // pass in index into mRanges;
// negative value clears
// mAnchorFocusRange
nsresult selectFrames(nsPresContext* aPresContext, nsIContentIterator *aInnerIter, nsIContent *aContent, nsIPresShell *aPresShell, PRBool aFlags);
nsresult SelectAllFramesForContent(nsIContentIterator *aInnerIter,
nsIContent *aContent,
PRBool aSelected);
nsresult selectFrames(nsPresContext* aPresContext, nsIRange *aRange, PRBool aSelect);
nsresult getTableCellLocationFromRange(nsIRange *aRange, PRInt32 *aSelectionType, PRInt32 *aRow, PRInt32 *aCol);
nsresult addTableCellRange(nsIRange *aRange, PRBool *aDidAddRange, PRInt32 *aOutIndex);
@ -4177,15 +4180,11 @@ nsTypedSelection::GetPrimaryFrameForFocusNode(nsIFrame **aReturnFrame, PRInt32 *
return NS_OK;
}
//select all content children of aContent
nsresult
nsTypedSelection::selectFrames(nsPresContext* aPresContext,
nsIContentIterator *aInnerIter,
nsIContent *aContent,
nsIPresShell *aPresShell,
PRBool aFlags)
nsTypedSelection::SelectAllFramesForContent(nsIContentIterator *aInnerIter,
nsIContent *aContent,
PRBool aSelected)
{
if (!mFrameSelection)
return NS_OK;//nothing to do
@ -4200,8 +4199,7 @@ nsTypedSelection::selectFrames(nsPresContext* aPresContext,
frame = mFrameSelection->GetShell()->GetPrimaryFrameFor(aContent);
if (frame)
{
//NOTE: eSpreadDown is now IGNORED. Selected state is set only for given frame
frame->SetSelected(aPresContext, nsnull, aFlags, eSpreadDown, mType);
frame->SetSelected(aSelected, mType);
if (mFrameSelection->GetTableCellSelection())
{
nsITableCellLayout *tcl = do_QueryFrame(frame);
@ -4220,29 +4218,7 @@ nsTypedSelection::selectFrames(nsPresContext* aPresContext,
frame = mFrameSelection->GetShell()->GetPrimaryFrameFor(innercontent);
if (frame)
{
//NOTE: eSpreadDown is now IGNORED. Selected state is set only
//for given frame
//spread from here to hit all frames in flow
frame->SetSelected(aPresContext, nsnull, aFlags, eSpreadDown, mType);
nsRect frameRect = frame->GetRect();
//if a rect is 0 height/width then try to notify next
//available in flow of selection status.
while (!frameRect.width || !frameRect.height)
{
//try to notify next in flow that its content is selected.
frame = frame->GetNextInFlow();
if (frame)
{
frameRect = frame->GetRect();
frame->SetSelected(aPresContext, nsnull, aFlags, eSpreadDown, mType);
}
else
break;
}
//if the frame is splittable and this frame is 0,0 then set
//the next in flow frame to be selected also
frame->SetSelected(aSelected, mType);
}
aInnerIter->Next();
@ -4292,11 +4268,23 @@ nsTypedSelection::selectFrames(nsPresContext* aPresContext, nsIRange *aRange, PR
return NS_ERROR_UNEXPECTED;
nsIFrame *frame;
if (!content->IsNodeOfType(nsINode::eELEMENT))
if (content->IsNodeOfType(nsINode::eTEXT))
{
frame = mFrameSelection->GetShell()->GetPrimaryFrameFor(content);
if (frame)
frame->SetSelected(aPresContext, domRange, aFlags, eSpreadDown, mType);//spread from here to hit all frames in flow
// The frame could be an SVG text frame, in which case we'll ignore
// it.
if (frame && frame->GetType() == nsGkAtoms::textFrame)
{
nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
PRUint32 startOffset = aRange->StartOffset();
PRUint32 endOffset;
if (aRange->GetEndParent() == content) {
endOffset = aRange->EndOffset();
} else {
endOffset = content->GetText()->GetLength();
}
textFrame->SetSelectedRange(startOffset, endOffset, aFlags, mType);
}
}
iter->First();
@ -4305,7 +4293,7 @@ nsTypedSelection::selectFrames(nsPresContext* aPresContext, nsIRange *aRange, PR
{
content = do_QueryInterface(iter->GetCurrentNode());
selectFrames(aPresContext, inneriter, content, presShell,aFlags);
SelectAllFramesForContent(inneriter, content, aFlags);
iter->Next();
}
@ -4317,11 +4305,16 @@ nsTypedSelection::selectFrames(nsPresContext* aPresContext, nsIRange *aRange, PR
if (NS_FAILED(result) || !content)
return result;
if (!content->IsNodeOfType(nsINode::eELEMENT))
if (content->IsNodeOfType(nsINode::eTEXT))
{
frame = mFrameSelection->GetShell()->GetPrimaryFrameFor(content);
if (frame)
frame->SetSelected(aPresContext, domRange, aFlags, eSpreadDown, mType);//spread from here to hit all frames in flow
// The frame could be an SVG text frame, in which case we'll
// ignore it.
if (frame && frame->GetType() == nsGkAtoms::textFrame)
{
nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
textFrame->SetSelectedRange(0, aRange->EndOffset(), aFlags, mType);
}
}
}
}

View File

@ -155,12 +155,22 @@ public:
virtual ContentOffsets CalcContentOffsetsFromFramePoint(nsPoint aPoint);
ContentOffsets GetCharacterOffsetAtFramePoint(const nsPoint &aPoint);
NS_IMETHOD SetSelected(nsPresContext* aPresContext,
nsIDOMRange *aRange,
PRBool aSelected,
nsSpread aSpread,
SelectionType aType);
/**
* This is called only on the primary text frame. It indicates that
* the selection state of the given character range has changed.
* Text in the range is unconditionally invalidated
* (nsTypedSelection::Repaint depends on this).
* @param aSelected true if the selection has been added to the range,
* false otherwise
* @param aType the type of selection added or removed
*/
virtual void SetSelected(PRBool aSelected,
SelectionType aType);
void SetSelectedRange(PRUint32 aStart,
PRUint32 aEnd,
PRBool aSelected,
SelectionType aType);
virtual PRBool PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,

View File

@ -4969,120 +4969,98 @@ nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext,
return !aRect.IsEmpty() && !givenRect.Contains(aRect);
}
//null range means the whole thing
NS_IMETHODIMP
nsTextFrame::SetSelected(nsPresContext* aPresContext,
nsIDOMRange *aRange,
PRBool aSelected,
nsSpread aSpread,
void
nsTextFrame::SetSelected(PRBool aSelected,
SelectionType aType)
{
DEBUG_VERIFY_NOT_DIRTY(mState);
#if 0 //XXXrbs disable due to bug 310318
if (mState & NS_FRAME_IS_DIRTY)
return NS_ERROR_UNEXPECTED;
#endif
SetSelectedRange(0, mContent->GetText()->GetLength(), aSelected, aType);
}
void
nsTextFrame::SetSelectedRange(PRUint32 aStart,
PRUint32 aEnd,
PRBool aSelected,
SelectionType aType)
{
NS_ASSERTION(!GetPrevContinuation(), "Should only be called for primary frame");
DEBUG_VERIFY_NOT_DIRTY(mState);
// Selection is collapsed, which can't affect text frame rendering
if (aStart == aEnd)
return;
// XXXroc This is stupid, ParentDisablesSelection just returns true. Let's
// kill it.
if (aSelected && ParentDisablesSelection())
return NS_OK;
return;
if (aType == nsISelectionController::SELECTION_NORMAL) {
// check whether style allows selection
PRBool selectable;
IsSelectable(&selectable, nsnull);
if (!selectable)
return NS_OK;//do not continue no selection for this frame.
return;
}
PRBool found = PR_FALSE;
if (aRange) {
//lets see if the range contains us, if so we must redraw!
nsCOMPtr<nsIDOMNode> endNode;
PRInt32 endOffset;
nsCOMPtr<nsIDOMNode> startNode;
PRInt32 startOffset;
aRange->GetEndContainer(getter_AddRefs(endNode));
aRange->GetEndOffset(&endOffset);
aRange->GetStartContainer(getter_AddRefs(startNode));
aRange->GetStartOffset(&startOffset);
nsCOMPtr<nsIDOMNode> thisNode = do_QueryInterface(GetContent());
PRBool anySelected = PR_FALSE;
if (thisNode == startNode)
{
if (GetContentEnd() >= startOffset)
{
found = PR_TRUE;
if (thisNode == endNode)
{ //special case
if (endOffset == startOffset) //no need to redraw since drawing takes place with cursor
found = PR_FALSE;
nsTextFrame* f = this;
while (f && f->GetContentEnd() <= aStart) {
if (f->GetStateBits() & NS_FRAME_SELECTED_CONTENT) {
anySelected = PR_TRUE;
}
f = static_cast<nsTextFrame*>(f->GetNextContinuation());
}
if (mContentOffset > endOffset)
found = PR_FALSE;
}
nsPresContext* presContext = PresContext();
while (f && f->GetContentOffset() < aEnd) {
if (aSelected) {
f->AddStateBits(NS_FRAME_SELECTED_CONTENT);
anySelected = PR_TRUE;
} else { // we need to see if any other selection is available.
SelectionDetails *details = f->GetSelectionDetails();
if (details) {
anySelected = PR_TRUE;
DestroySelectionDetails(details);
} else {
f->RemoveStateBits(NS_FRAME_SELECTED_CONTENT);
}
}
else if (thisNode == endNode)
{
if (mContentOffset < endOffset)
found = PR_TRUE;
else
{
found = PR_FALSE;
}
}
else
{
found = PR_TRUE;
}
}
else {
// null range means the whole thing
found = PR_TRUE;
}
if ( aSelected )
AddStateBits(NS_FRAME_SELECTED_CONTENT);
else
{ //we need to see if any other selection is available.
SelectionDetails *details = GetSelectionDetails();
if (!details) {
RemoveStateBits(NS_FRAME_SELECTED_CONTENT);
} else {
DestroySelectionDetails(details);
}
}
if (found) {
// If the selection state is changed in this content, we need to reflow
// to recompute the overflow area for underline of spellchecking or IME if
// their underline is thicker than normal decoration line.
// We may need to reflow to recompute the overflow area for
// spellchecking or IME underline if their underline is thicker than
// the normal decoration line.
PRBool didHaveOverflowingSelection =
(mState & TEXT_SELECTION_UNDERLINE_OVERFLOWED) != 0;
(f->GetStateBits() & TEXT_SELECTION_UNDERLINE_OVERFLOWED) != 0;
nsRect r(nsPoint(0, 0), GetSize());
PRBool willHaveOverflowingSelection =
aSelected && CombineSelectionUnderlineRect(PresContext(), r);
aSelected && f->CombineSelectionUnderlineRect(presContext, r);
if (didHaveOverflowingSelection || willHaveOverflowingSelection) {
PresContext()->PresShell()->FrameNeedsReflow(this,
nsIPresShell::eStyleChange,
NS_FRAME_IS_DIRTY);
presContext->PresShell()->FrameNeedsReflow(f,
nsIPresShell::eStyleChange,
NS_FRAME_IS_DIRTY);
}
// Selection might change anything. Invalidate the overflow area.
InvalidateOverflowRect();
f->InvalidateOverflowRect();
f = static_cast<nsTextFrame*>(f->GetNextContinuation());
}
if (aSpread == eSpreadDown)
{
nsIFrame* frame = GetPrevContinuation();
while(frame){
frame->SetSelected(aPresContext, aRange,aSelected,eSpreadNone, aType);
frame = frame->GetPrevContinuation();
}
frame = GetNextContinuation();
while (frame){
frame->SetSelected(aPresContext, aRange,aSelected,eSpreadNone, aType);
frame = frame->GetNextContinuation();
// Scan remaining continuations to see if any are selected
while (f && !anySelected) {
if (f->GetStateBits() & NS_FRAME_SELECTED_CONTENT) {
anySelected = PR_TRUE;
}
f = static_cast<nsTextFrame*>(f->GetNextContinuation());
}
if (anySelected) {
mContent->SetFlags(NS_TEXT_IN_SELECTION);
} else {
// This is only legal because there is only one presentation for the
// content with a selection
mContent->UnsetFlags(NS_TEXT_IN_SELECTION);
}
return NS_OK;
}
NS_IMETHODIMP
@ -6421,7 +6399,18 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
SetLength(contentLength);
Invalidate(nsRect(nsPoint(0, 0), GetSize()));
if (mContent->HasFlag(NS_TEXT_IN_SELECTION)) {
// XXXroc Watch out, this could be slow!!! Speed up GetSelectionDetails?
SelectionDetails* details = GetSelectionDetails();
if (details) {
AddStateBits(NS_FRAME_SELECTED_CONTENT);
DestroySelectionDetails(details);
} else {
RemoveStateBits(NS_FRAME_SELECTED_CONTENT);
}
}
Invalidate(aMetrics.mOverflowArea);
#ifdef NOISY_REFLOW
ListTag(stdout);

View File

@ -229,35 +229,30 @@ nsSVGGlyphFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
}
}
NS_IMETHODIMP
nsSVGGlyphFrame::SetSelected(nsPresContext* aPresContext,
nsIDOMRange* aRange,
PRBool aSelected,
nsSpread aSpread,
SelectionType aType)
void
nsSVGGlyphFrame::SetSelected(PRBool aSelected,
SelectionType aType)
{
#if defined(DEBUG) && defined(SVG_DEBUG_SELECTION)
printf("nsSVGGlyphFrame(%p)::SetSelected()\n", this);
#endif
// return nsSVGGlyphFrameBase::SetSelected(aPresContext, aRange, aSelected, aSpread, aType);
if (aType == nsISelectionController::SELECTION_NORMAL) {
// check whether style allows selection
PRBool selectable;
IsSelectable(&selectable, nsnull);
if (!selectable)
return NS_OK;
}
if (aType != nsISelectionController::SELECTION_NORMAL)
return;
if ( aSelected ){
mState |= NS_FRAME_SELECTED_CONTENT;
// check whether style allows selection
PRBool selectable;
IsSelectable(&selectable, nsnull);
if (!selectable)
return;
if (aSelected) {
AddStateBits(NS_FRAME_SELECTED_CONTENT);
} else {
RemoveStateBits(NS_FRAME_SELECTED_CONTENT);
}
else
mState &= ~NS_FRAME_SELECTED_CONTENT;
nsSVGUtils::UpdateGraphic(this);
return NS_OK;
}
NS_IMETHODIMP

View File

@ -82,11 +82,8 @@ public:
virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext);
NS_IMETHOD SetSelected(nsPresContext* aPresContext,
nsIDOMRange* aRange,
PRBool aSelected,
nsSpread aSpread,
SelectionType aType);
virtual void SetSelected(PRBool aSelected,
SelectionType aType);
NS_IMETHOD GetSelected(PRBool *aSelected) const;
NS_IMETHOD IsSelectable(PRBool* aIsSelectable, PRUint8* aSelectStyle) const;

View File

@ -393,16 +393,14 @@ nsTableOuterFrame::BuildDisplayListForInnerTable(nsDisplayListBuilder* aBuilde
return NS_OK;
}
NS_IMETHODIMP nsTableOuterFrame::SetSelected(nsPresContext* aPresContext,
nsIDOMRange *aRange,
PRBool aSelected,
nsSpread aSpread,
SelectionType aType)
void
nsTableOuterFrame::SetSelected(PRBool aSelected,
SelectionType aType)
{
nsresult result = nsFrame::SetSelected(aPresContext, aRange,aSelected, aSpread, aType);
if (NS_SUCCEEDED(result) && mInnerTableFrame)
return mInnerTableFrame->SetSelected(aPresContext, aRange,aSelected, aSpread, aType);
return result;
nsFrame::SetSelected(aSelected, aType);
if (mInnerTableFrame) {
mInnerTableFrame->SetSelected(aSelected, aType);
}
}
NS_IMETHODIMP

View File

@ -165,11 +165,8 @@ public:
/** SetSelected needs to be overridden to talk to inner tableframe
*/
NS_IMETHOD SetSelected(nsPresContext* aPresContext,
nsIDOMRange *aRange,
PRBool aSelected,
nsSpread aSpread,
SelectionType aType);
void SetSelected(PRBool aSelected,
SelectionType aType);
NS_IMETHOD GetParentStyleContextFrame(nsPresContext* aPresContext,
nsIFrame** aProviderFrame,