Bug 264412. Refactor nsIFrame::GetRenderedText API to be more sane. r=mats,marcoz
The test changes here are to adjust for the fact that nsTextFrame::GetRenderedText can now trim whitespace from the end of lines that end in a hard line break.
This commit is contained in:
@@ -228,12 +228,11 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
|
|||||||
nsIContent* containerElm = containerNode->IsElement() ?
|
nsIContent* containerElm = containerNode->IsElement() ?
|
||||||
containerNode->AsElement() : nullptr;
|
containerNode->AsElement() : nullptr;
|
||||||
|
|
||||||
nsAutoString text;
|
nsIFrame::RenderedText text = textFrame->GetRenderedText();
|
||||||
textFrame->GetRenderedText(&text);
|
|
||||||
|
|
||||||
// Remove text accessible if rendered text is empty.
|
// Remove text accessible if rendered text is empty.
|
||||||
if (textAcc) {
|
if (textAcc) {
|
||||||
if (text.IsEmpty()) {
|
if (text.mString.IsEmpty()) {
|
||||||
#ifdef A11Y_LOG
|
#ifdef A11Y_LOG
|
||||||
if (logging::IsEnabled(logging::eTree | logging::eText)) {
|
if (logging::IsEnabled(logging::eTree | logging::eText)) {
|
||||||
logging::MsgBegin("TREE", "text node lost its content");
|
logging::MsgBegin("TREE", "text node lost its content");
|
||||||
@@ -256,17 +255,17 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
|
|||||||
logging::MsgEntry("old text '%s'",
|
logging::MsgEntry("old text '%s'",
|
||||||
NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get());
|
NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get());
|
||||||
logging::MsgEntry("new text: '%s'",
|
logging::MsgEntry("new text: '%s'",
|
||||||
NS_ConvertUTF16toUTF8(text).get());
|
NS_ConvertUTF16toUTF8(text.mString).get());
|
||||||
logging::MsgEnd();
|
logging::MsgEnd();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text);
|
TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text.mString);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append an accessible if rendered text is not empty.
|
// Append an accessible if rendered text is not empty.
|
||||||
if (!text.IsEmpty()) {
|
if (!text.mString.IsEmpty()) {
|
||||||
#ifdef A11Y_LOG
|
#ifdef A11Y_LOG
|
||||||
if (logging::IsEnabled(logging::eTree | logging::eText)) {
|
if (logging::IsEnabled(logging::eTree | logging::eText)) {
|
||||||
logging::MsgBegin("TREE", "text node gains new content");
|
logging::MsgBegin("TREE", "text node gains new content");
|
||||||
|
|||||||
@@ -1091,12 +1091,11 @@ nsAccessibilityService::GetOrCreateAccessible(nsINode* aNode,
|
|||||||
|
|
||||||
// Create accessible for visible text frames.
|
// Create accessible for visible text frames.
|
||||||
if (content->IsNodeOfType(nsINode::eTEXT)) {
|
if (content->IsNodeOfType(nsINode::eTEXT)) {
|
||||||
nsAutoString text;
|
nsIFrame::RenderedText text = frame->GetRenderedText();
|
||||||
frame->GetRenderedText(&text, nullptr, nullptr, 0, UINT32_MAX);
|
|
||||||
// Ignore not rendered text nodes and whitespace text nodes between table
|
// Ignore not rendered text nodes and whitespace text nodes between table
|
||||||
// cells.
|
// cells.
|
||||||
if (text.IsEmpty() ||
|
if (text.mString.IsEmpty() ||
|
||||||
(aContext->IsTableRow() && nsCoreUtils::IsWhitespaceString(text))) {
|
(aContext->IsTableRow() && nsCoreUtils::IsWhitespaceString(text.mString))) {
|
||||||
if (aIsSubtreeHidden)
|
if (aIsSubtreeHidden)
|
||||||
*aIsSubtreeHidden = true;
|
*aIsSubtreeHidden = true;
|
||||||
|
|
||||||
@@ -1108,7 +1107,7 @@ nsAccessibilityService::GetOrCreateAccessible(nsINode* aNode,
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
document->BindToDocument(newAcc, nullptr);
|
document->BindToDocument(newAcc, nullptr);
|
||||||
newAcc->AsTextLeaf()->SetText(text);
|
newAcc->AsTextLeaf()->SetText(text.mString);
|
||||||
return newAcc;
|
return newAcc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,8 +139,8 @@ nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent *aContent,
|
|||||||
if (aContent->TextLength() > 0) {
|
if (aContent->TextLength() > 0) {
|
||||||
nsIFrame *frame = aContent->GetPrimaryFrame();
|
nsIFrame *frame = aContent->GetPrimaryFrame();
|
||||||
if (frame) {
|
if (frame) {
|
||||||
nsresult rv = frame->GetRenderedText(aString);
|
nsIFrame::RenderedText text = frame->GetRenderedText();
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
aString->Append(text.mString);
|
||||||
} else {
|
} else {
|
||||||
// If aContent is an object that is display: none, we have no a frame.
|
// If aContent is an object that is display: none, we have no a frame.
|
||||||
aContent->AppendTextTo(*aString);
|
aContent->AppendTextTo(*aString);
|
||||||
|
|||||||
@@ -394,11 +394,11 @@ Accessible::VisibilityState()
|
|||||||
if (frame->GetType() == nsGkAtoms::textFrame &&
|
if (frame->GetType() == nsGkAtoms::textFrame &&
|
||||||
!(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
|
!(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
|
||||||
frame->GetRect().IsEmpty()) {
|
frame->GetRect().IsEmpty()) {
|
||||||
nsAutoString renderedText;
|
nsIFrame::RenderedText text = frame->GetRenderedText();
|
||||||
frame->GetRenderedText(&renderedText, nullptr, nullptr, 0, 1);
|
if (text.mString.IsEmpty()) {
|
||||||
if (renderedText.IsEmpty())
|
|
||||||
return states::INVISIBLE;
|
return states::INVISIBLE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1984,17 +1984,9 @@ HyperTextAccessible::ContentToRenderedOffset(nsIFrame* aFrame, int32_t aContentO
|
|||||||
NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
|
NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
|
||||||
"Call on primary frame only");
|
"Call on primary frame only");
|
||||||
|
|
||||||
gfxSkipChars skipChars;
|
nsIFrame::RenderedText text = aFrame->GetRenderedText(aContentOffset,
|
||||||
gfxSkipCharsIterator iter;
|
aContentOffset + 1);
|
||||||
// Only get info up to original offset, we know that will be larger than skipped offset
|
*aRenderedOffset = text.mOffsetWithinNodeRenderedText;
|
||||||
nsresult rv = aFrame->GetRenderedText(nullptr, &skipChars, &iter, 0, aContentOffset);
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
|
|
||||||
uint32_t ourRenderedStart = iter.GetSkippedOffset();
|
|
||||||
int32_t ourContentStart = iter.GetOriginalOffset();
|
|
||||||
|
|
||||||
*aRenderedOffset = iter.ConvertOriginalToSkipped(aContentOffset + ourContentStart) -
|
|
||||||
ourRenderedStart;
|
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
@@ -2016,16 +2008,9 @@ HyperTextAccessible::RenderedToContentOffset(nsIFrame* aFrame, uint32_t aRendere
|
|||||||
NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
|
NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
|
||||||
"Call on primary frame only");
|
"Call on primary frame only");
|
||||||
|
|
||||||
gfxSkipChars skipChars;
|
nsIFrame::RenderedText text = aFrame->GetRenderedText(aRenderedOffset,
|
||||||
gfxSkipCharsIterator iter;
|
aRenderedOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_RENDERED_TEXT);
|
||||||
// We only need info up to skipped offset -- that is what we're converting to original offset
|
*aContentOffset = text.mOffsetWithinNodeText;
|
||||||
nsresult rv = aFrame->GetRenderedText(nullptr, &skipChars, &iter, 0, aRenderedOffset);
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
|
|
||||||
uint32_t ourRenderedStart = iter.GetSkippedOffset();
|
|
||||||
int32_t ourContentStart = iter.GetOriginalOffset();
|
|
||||||
|
|
||||||
*aContentOffset = iter.ConvertSkippedToOriginal(aRenderedOffset + ourRenderedStart) - ourContentStart;
|
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
function doTest()
|
function doTest()
|
||||||
{
|
{
|
||||||
var iframeDoc = [ getNode("iframe").contentDocument ];
|
var iframeDoc = [ getNode("iframe").contentDocument ];
|
||||||
testCharacterCount(iframeDoc, 15);
|
testCharacterCount(iframeDoc, 13);
|
||||||
testText(iframeDoc, 0, 15, "outbody inbody ");
|
testText(iframeDoc, 0, 13, "outbodyinbody");
|
||||||
|
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
// getTextAtOffset line boundary
|
// getTextAtOffset line boundary
|
||||||
|
|
||||||
testTextAtOffset(0, BOUNDARY_LINE_START, "line ", 0, 5,
|
testTextAtOffset(0, BOUNDARY_LINE_START, "line", 0, 4,
|
||||||
"hypertext3", kOk, kOk, kOk);
|
"hypertext3", kOk, kOk, kOk);
|
||||||
|
|
||||||
// XXX: see bug 634202.
|
// XXX: see bug 634202.
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
|
|
||||||
<div id="nulltext"></div>
|
<div id="nulltext"></div>
|
||||||
|
|
||||||
<div id="hypertext">hello <a>friend</a> see <img></div>
|
<div id="hypertext">hello <a>friend</a> see <img src="about:blank"></div>
|
||||||
<div id="hypertext2">hello <a>friend</a> see <input></div>
|
<div id="hypertext2">hello <a>friend</a> see <input></div>
|
||||||
<ol id="list">
|
<ol id="list">
|
||||||
<li id="listitem">foo</li>
|
<li id="listitem">foo</li>
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
function doTest()
|
function doTest()
|
||||||
{
|
{
|
||||||
testTextAtOffset("line_test_1", BOUNDARY_LINE_START,
|
testTextAtOffset("line_test_1", BOUNDARY_LINE_START,
|
||||||
[[0, 6, "Line 1 ", 0, 7],
|
[[0, 5, "Line 1", 0, 6],
|
||||||
[7, 7, "", 7, 7],
|
[6, 6, "", 6, 6],
|
||||||
[8, 15, "Line 3 ", 8, 15]]);
|
[7, 13, "Line 3", 7, 13]]);
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
// __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__
|
// __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__
|
||||||
@@ -114,10 +114,10 @@
|
|||||||
[ [ 0, 3, "foo\n", 0, 4 ], [ 4, 4, "", 4, 4 ] ]);
|
[ [ 0, 3, "foo\n", 0, 4 ], [ 4, 4, "", 4, 4 ] ]);
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
// 'Hello world ' (\n is rendered as space)
|
// 'Hello world'
|
||||||
|
|
||||||
testTextAtOffset([ "ht_4" ], BOUNDARY_LINE_START,
|
testTextAtOffset([ "ht_4" ], BOUNDARY_LINE_START,
|
||||||
[ [ 0, 12, "Hello world ", 0, 12 ] ]);
|
[ [ 0, 11, "Hello world", 0, 11 ] ]);
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
// list items
|
// list items
|
||||||
|
|||||||
@@ -42,27 +42,20 @@
|
|||||||
[ [ 0, 6, "", 0, 0 ] ]);
|
[ [ 0, 6, "", 0, 0 ] ]);
|
||||||
testTextBeforeOffset(ids, BOUNDARY_WORD_END,
|
testTextBeforeOffset(ids, BOUNDARY_WORD_END,
|
||||||
[ [ 0, 5, "", 0, 0 ],
|
[ [ 0, 5, "", 0, 0 ],
|
||||||
[ 6, 6, "hello", 0, 5,
|
[ 6, 6, "hello", 0, 5 ]
|
||||||
[ [6, "e2", kTodo, kOk, kTodo ] ]
|
|
||||||
]
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
testTextAtOffset(ids, BOUNDARY_WORD_START,
|
testTextAtOffset(ids, BOUNDARY_WORD_START,
|
||||||
[ [ 0, 6, "hello ", 0, 6 ] ]);
|
[ [ 0, 6, "hello ", 0, 6 ] ]);
|
||||||
testTextAtOffset(ids, BOUNDARY_WORD_END,
|
testTextAtOffset(ids, BOUNDARY_WORD_END,
|
||||||
[ [ 0, 4, "hello", 0, 5 ],
|
[ [ 0, 4, "hello", 0, 5 ],
|
||||||
[ 5, 6, " ", 5, 6,
|
[ 5, 6, " ", 5, 6 ]
|
||||||
[ [ 5, "e2", kTodo, kTodo, kOk ],
|
|
||||||
[ 6, "e2", kTodo, kTodo, kOk ] ]
|
|
||||||
]
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
testTextAfterOffset(ids, BOUNDARY_WORD_START,
|
testTextAfterOffset(ids, BOUNDARY_WORD_START,
|
||||||
[ [ 0, 6, "", 6, 6 ] ]);
|
[ [ 0, 6, "", 6, 6 ] ]);
|
||||||
testTextAfterOffset(ids, BOUNDARY_WORD_END,
|
testTextAfterOffset(ids, BOUNDARY_WORD_END,
|
||||||
[ [ 0, 5, " ", 5, 6,
|
[ [ 0, 5, " ", 5, 6 ],
|
||||||
[ [ 5, "e2", kTodo, kTodo, kOk ] ]
|
|
||||||
],
|
|
||||||
[ 6, 6, "", 6, 6 ]
|
[ 6, 6, "", 6, 6 ]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -256,7 +249,7 @@
|
|||||||
|
|
||||||
<input id="i2" value="hello "/>
|
<input id="i2" value="hello "/>
|
||||||
<pre><div id="d2">hello </div></pre>
|
<pre><div id="d2">hello </div></pre>
|
||||||
<div id="e2" contenteditable="true">hello </div>
|
<div id="e2" contenteditable="true" style='white-space:pre'>hello </div>
|
||||||
<textarea id="t2">hello </textarea>
|
<textarea id="t2">hello </textarea>
|
||||||
|
|
||||||
<input id="i6" value="hello all"/>
|
<input id="i6" value="hello all"/>
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ TestIterator()
|
|||||||
"[4] Check mapping of original to skipped for " << i;
|
"[4] Check mapping of original to skipped for " << i;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t expectOriginal1[] =
|
int32_t expectOriginal1[] =
|
||||||
{ 0, 1, 2, 3, 4, 5, 6, 7, 8,
|
{ 0, 1, 2, 3, 4, 5, 6, 7, 8,
|
||||||
10, 11, 12, 13, 14, 15, 16, 17, 18,
|
10, 11, 12, 13, 14, 15, 16, 17, 18,
|
||||||
20, 21, 22, 23, 24, 25, 26, 27, 28 };
|
20, 21, 22, 23, 24, 25, 26, 27, 28 };
|
||||||
@@ -136,7 +136,7 @@ TestIterator()
|
|||||||
"[9] Check mapping of original to skipped for " << i;
|
"[9] Check mapping of original to skipped for " << i;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t expectOriginal2[] = { 9, 19, 29 };
|
int32_t expectOriginal2[] = { 9, 19, 29 };
|
||||||
|
|
||||||
for (uint32_t i = 0; i < mozilla::ArrayLength(expectOriginal2); i++) {
|
for (uint32_t i = 0; i < mozilla::ArrayLength(expectOriginal2); i++) {
|
||||||
EXPECT_TRUE(iter2.ConvertSkippedToOriginal(i) == expectOriginal2[i]) <<
|
EXPECT_TRUE(iter2.ConvertSkippedToOriginal(i) == expectOriginal2[i]) <<
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ public:
|
|||||||
return GetSkippedOffset();
|
return GetSkippedOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t ConvertSkippedToOriginal(int32_t aSkippedStringOffset)
|
int32_t ConvertSkippedToOriginal(uint32_t aSkippedStringOffset)
|
||||||
{
|
{
|
||||||
SetSkippedOffset(aSkippedStringOffset);
|
SetSkippedOffset(aSkippedStringOffset);
|
||||||
return GetOriginalOffset();
|
return GetOriginalOffset();
|
||||||
|
|||||||
@@ -1893,26 +1893,36 @@ public:
|
|||||||
virtual bool CanContinueTextRun() const = 0;
|
virtual bool CanContinueTextRun() const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Append the rendered text to the passed-in string.
|
* Computes an approximation of the rendered text of the frame and its
|
||||||
|
* continuations. Returns nothing for non-text frames.
|
||||||
* The appended text will often not contain all the whitespace from source,
|
* The appended text will often not contain all the whitespace from source,
|
||||||
* depending on whether the CSS rule "white-space: pre" is active for this frame.
|
* depending on CSS white-space processing.
|
||||||
* if aStartOffset + aLength goes past end, or if aLength is not specified
|
* if aEndOffset goes past end, use the text up to the string's end.
|
||||||
* then use the text up to the string's end.
|
|
||||||
* Call this on the primary frame for a text node.
|
* Call this on the primary frame for a text node.
|
||||||
* @param aAppendToString String to append text to, or null if text should not be returned
|
* aStartOffset and aEndOffset can be content offsets or offsets in the
|
||||||
* @param aSkipChars if aSkipIter is non-null, this must also be non-null.
|
* rendered text, depending on aOffsetType.
|
||||||
* This gets used as backing data for the iterator so it should outlive the iterator.
|
* Returns a string, as well as offsets identifying the start of the text
|
||||||
* @param aSkipIter Where to fill in the gfxSkipCharsIterator info, or null if not needed by caller
|
* within the rendered text for the whole node, and within the text content
|
||||||
* @param aStartOffset Skipped (rendered text) start offset
|
* of the node.
|
||||||
* @param aSkippedMaxLength Maximum number of characters to return
|
|
||||||
* The iterator can be used to map content offsets to offsets in the returned string, or vice versa.
|
|
||||||
*/
|
*/
|
||||||
virtual nsresult GetRenderedText(nsAString* aAppendToString = nullptr,
|
struct RenderedText {
|
||||||
gfxSkipChars* aSkipChars = nullptr,
|
nsString mString;
|
||||||
gfxSkipCharsIterator* aSkipIter = nullptr,
|
uint32_t mOffsetWithinNodeRenderedText;
|
||||||
uint32_t aSkippedStartOffset = 0,
|
int32_t mOffsetWithinNodeText;
|
||||||
uint32_t aSkippedMaxLength = UINT32_MAX)
|
RenderedText() : mOffsetWithinNodeRenderedText(0),
|
||||||
{ return NS_ERROR_NOT_IMPLEMENTED; }
|
mOffsetWithinNodeText(0) {}
|
||||||
|
};
|
||||||
|
enum class TextOffsetType {
|
||||||
|
// Passed-in start and end offsets are within the content text.
|
||||||
|
OFFSETS_IN_CONTENT_TEXT,
|
||||||
|
// Passed-in start and end offsets are within the rendered text.
|
||||||
|
OFFSETS_IN_RENDERED_TEXT
|
||||||
|
};
|
||||||
|
virtual RenderedText GetRenderedText(uint32_t aStartOffset = 0,
|
||||||
|
uint32_t aEndOffset = UINT32_MAX,
|
||||||
|
TextOffsetType aOffsetType =
|
||||||
|
TextOffsetType::OFFSETS_IN_CONTENT_TEXT)
|
||||||
|
{ return RenderedText(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the frame contains any non-collapsed characters.
|
* Returns true if the frame contains any non-collapsed characters.
|
||||||
|
|||||||
@@ -3975,9 +3975,8 @@ a11y::AccType
|
|||||||
nsTextFrame::AccessibleType()
|
nsTextFrame::AccessibleType()
|
||||||
{
|
{
|
||||||
if (IsEmpty()) {
|
if (IsEmpty()) {
|
||||||
nsAutoString renderedWhitespace;
|
RenderedText text = GetRenderedText();
|
||||||
GetRenderedText(&renderedWhitespace, nullptr, nullptr, 0, 1);
|
if (text.mString.IsEmpty()) {
|
||||||
if (renderedWhitespace.IsEmpty()) {
|
|
||||||
return a11y::eNoType;
|
return a11y::eNoType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4090,13 +4089,6 @@ public:
|
|||||||
virtual void AddInlinePrefISize(nsRenderingContext *aRenderingContext,
|
virtual void AddInlinePrefISize(nsRenderingContext *aRenderingContext,
|
||||||
InlinePrefISizeData *aData) override;
|
InlinePrefISizeData *aData) override;
|
||||||
|
|
||||||
virtual nsresult GetRenderedText(nsAString* aString = nullptr,
|
|
||||||
gfxSkipChars* aSkipChars = nullptr,
|
|
||||||
gfxSkipCharsIterator* aSkipIter = nullptr,
|
|
||||||
uint32_t aSkippedStartOffset = 0,
|
|
||||||
uint32_t aSkippedMaxLength = UINT32_MAX) override
|
|
||||||
{ return NS_ERROR_NOT_IMPLEMENTED; } // Call on a primary text frame only
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit nsContinuingTextFrame(nsStyleContext* aContext) : nsTextFrame(aContext) {}
|
explicit nsContinuingTextFrame(nsStyleContext* aContext) : nsTextFrame(aContext) {}
|
||||||
nsIFrame* mPrevContinuation;
|
nsIFrame* mPrevContinuation;
|
||||||
@@ -8882,80 +8874,151 @@ static char16_t TransformChar(nsTextFrame* aFrame, const nsStyleText* aStyle,
|
|||||||
return aChar;
|
return aChar;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult nsTextFrame::GetRenderedText(nsAString* aAppendToString,
|
static bool
|
||||||
gfxSkipChars* aSkipChars,
|
LineEndsInHardLineBreak(nsTextFrame* aFrame)
|
||||||
gfxSkipCharsIterator* aSkipIter,
|
|
||||||
uint32_t aSkippedStartOffset,
|
|
||||||
uint32_t aSkippedMaxLength)
|
|
||||||
{
|
{
|
||||||
// The handling of aSkippedStartOffset and aSkippedMaxLength could be more efficient...
|
nsIFrame* lineContainer = FindLineContainer(aFrame);
|
||||||
gfxSkipChars skipChars;
|
nsBlockFrame* block = do_QueryFrame(lineContainer);
|
||||||
|
if (!block) {
|
||||||
|
// Weird situation where we have a line layout without a block.
|
||||||
|
// No soft breaks occur in this situation.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool foundValidLine;
|
||||||
|
nsBlockInFlowLineIterator iter(block, aFrame, &foundValidLine);
|
||||||
|
if (!foundValidLine) {
|
||||||
|
NS_ERROR("Invalid line!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !iter.GetLine()->IsLineWrapped();
|
||||||
|
}
|
||||||
|
|
||||||
|
nsIFrame::RenderedText
|
||||||
|
nsTextFrame::GetRenderedText(uint32_t aStartOffset,
|
||||||
|
uint32_t aEndOffset,
|
||||||
|
TextOffsetType aOffsetType)
|
||||||
|
{
|
||||||
|
NS_ASSERTION(!GetPrevContinuation(), "Must be called on first-in-flow");
|
||||||
|
|
||||||
|
// The handling of offsets could be more efficient...
|
||||||
|
RenderedText result;
|
||||||
nsTextFrame* textFrame;
|
nsTextFrame* textFrame;
|
||||||
const nsTextFragment* textFrag = mContent->GetText();
|
const nsTextFragment* textFrag = mContent->GetText();
|
||||||
uint32_t keptCharsLength = 0;
|
uint32_t offsetInRenderedString = 0;
|
||||||
uint32_t validCharsLength = 0;
|
bool haveOffsets = false;
|
||||||
|
|
||||||
// Build skipChars and copy text, for each text frame in this continuation block
|
|
||||||
for (textFrame = this; textFrame;
|
for (textFrame = this; textFrame;
|
||||||
textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation())) {
|
textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation())) {
|
||||||
// For each text frame continuation in this block ...
|
|
||||||
|
|
||||||
if (textFrame->GetStateBits() & NS_FRAME_IS_DIRTY) {
|
if (textFrame->GetStateBits() & NS_FRAME_IS_DIRTY) {
|
||||||
// We don't trust dirty frames, expecially when computing rendered text.
|
// We don't trust dirty frames, especially when computing rendered text.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the text run and grab the gfxSkipCharsIterator for it
|
// Ensure the text run and grab the gfxSkipCharsIterator for it
|
||||||
gfxSkipCharsIterator iter =
|
gfxSkipCharsIterator iter =
|
||||||
textFrame->EnsureTextRun(nsTextFrame::eInflated);
|
textFrame->EnsureTextRun(nsTextFrame::eInflated);
|
||||||
if (!textFrame->mTextRun)
|
if (!textFrame->mTextRun) {
|
||||||
return NS_ERROR_FAILURE;
|
break;
|
||||||
|
}
|
||||||
|
gfxSkipCharsIterator tmpIter = iter;
|
||||||
|
|
||||||
// Skip to the start of the text run, past ignored chars at start of line
|
// Skip to the start of the text run, past ignored chars at start of line
|
||||||
// XXX In the future we may decide to trim extra spaces before a hard line
|
TrimmedOffsets trimmedOffsets = textFrame->GetTrimmedOffsets(textFrag,
|
||||||
// break, in which case we need to accurately detect those sitations and
|
textFrame->IsAtEndOfLine() && LineEndsInHardLineBreak(textFrame));
|
||||||
// call GetTrimmedOffsets() with true to trim whitespace at the line's end
|
bool trimmedSignificantNewline =
|
||||||
TrimmedOffsets trimmedContentOffsets = textFrame->GetTrimmedOffsets(textFrag, false);
|
trimmedOffsets.GetEnd() < GetContentEnd() &&
|
||||||
int32_t startOfLineSkipChars = trimmedContentOffsets.mStart - textFrame->mContentOffset;
|
HasSignificantTerminalNewline();
|
||||||
if (startOfLineSkipChars > 0) {
|
uint32_t skippedToRenderedStringOffset = offsetInRenderedString -
|
||||||
skipChars.SkipChars(startOfLineSkipChars);
|
tmpIter.ConvertOriginalToSkipped(trimmedOffsets.mStart);
|
||||||
iter.SetOriginalOffset(trimmedContentOffsets.mStart);
|
uint32_t nextOffsetInRenderedString =
|
||||||
|
tmpIter.ConvertOriginalToSkipped(trimmedOffsets.GetEnd()) +
|
||||||
|
(trimmedSignificantNewline ? 1 : 0) + skippedToRenderedStringOffset;
|
||||||
|
|
||||||
|
if (aOffsetType == TextOffsetType::OFFSETS_IN_RENDERED_TEXT) {
|
||||||
|
if (nextOffsetInRenderedString <= aStartOffset) {
|
||||||
|
offsetInRenderedString = nextOffsetInRenderedString;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!haveOffsets) {
|
||||||
|
result.mOffsetWithinNodeText =
|
||||||
|
tmpIter.ConvertSkippedToOriginal(aStartOffset - skippedToRenderedStringOffset);
|
||||||
|
result.mOffsetWithinNodeRenderedText = aStartOffset;
|
||||||
|
haveOffsets = true;
|
||||||
|
}
|
||||||
|
if (offsetInRenderedString >= aEndOffset) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (uint32_t(textFrame->GetContentEnd()) <= aStartOffset) {
|
||||||
|
offsetInRenderedString = nextOffsetInRenderedString;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!haveOffsets) {
|
||||||
|
result.mOffsetWithinNodeText = aStartOffset;
|
||||||
|
// Skip trimmed space when computed the rendered text offset.
|
||||||
|
int32_t clamped = std::max<int32_t>(aStartOffset, trimmedOffsets.mStart);
|
||||||
|
result.mOffsetWithinNodeRenderedText =
|
||||||
|
tmpIter.ConvertOriginalToSkipped(clamped) + skippedToRenderedStringOffset;
|
||||||
|
NS_ASSERTION(result.mOffsetWithinNodeRenderedText >= offsetInRenderedString &&
|
||||||
|
result.mOffsetWithinNodeRenderedText <= INT32_MAX,
|
||||||
|
"Bad offset within rendered text");
|
||||||
|
haveOffsets = true;
|
||||||
|
}
|
||||||
|
if (uint32_t(textFrame->mContentOffset) >= aEndOffset) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep and copy the appropriate chars withing the caller's requested range
|
int32_t startOffset;
|
||||||
const nsStyleText* textStyle = textFrame->StyleText();
|
int32_t endOffset;
|
||||||
while (iter.GetOriginalOffset() < trimmedContentOffsets.GetEnd() &&
|
if (aOffsetType == TextOffsetType::OFFSETS_IN_RENDERED_TEXT) {
|
||||||
keptCharsLength < aSkippedMaxLength) {
|
startOffset =
|
||||||
// For each original char from content text
|
tmpIter.ConvertSkippedToOriginal(aStartOffset - skippedToRenderedStringOffset);
|
||||||
if (iter.IsOriginalCharSkipped() || ++validCharsLength <= aSkippedStartOffset) {
|
endOffset =
|
||||||
skipChars.SkipChar();
|
tmpIter.ConvertSkippedToOriginal(aEndOffset - skippedToRenderedStringOffset);
|
||||||
} else {
|
} else {
|
||||||
++keptCharsLength;
|
startOffset = aStartOffset;
|
||||||
skipChars.KeepChar();
|
endOffset = std::min<uint32_t>(INT32_MAX, aEndOffset);
|
||||||
if (aAppendToString) {
|
|
||||||
aAppendToString->Append(
|
|
||||||
TransformChar(textFrame, textStyle, textFrame->mTextRun,
|
|
||||||
iter.GetSkippedOffset(),
|
|
||||||
textFrag->CharAt(iter.GetOriginalOffset())));
|
|
||||||
}
|
}
|
||||||
|
trimmedOffsets.mStart = std::max<uint32_t>(trimmedOffsets.mStart,
|
||||||
|
startOffset);
|
||||||
|
trimmedOffsets.mLength = std::min<uint32_t>(trimmedOffsets.GetEnd(),
|
||||||
|
endOffset) - trimmedOffsets.mStart;
|
||||||
|
if (trimmedOffsets.mLength <= 0) {
|
||||||
|
offsetInRenderedString = nextOffsetInRenderedString;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nsStyleText* textStyle = textFrame->StyleText();
|
||||||
|
iter.SetOriginalOffset(trimmedOffsets.mStart);
|
||||||
|
while (iter.GetOriginalOffset() < trimmedOffsets.GetEnd()) {
|
||||||
|
char16_t ch = textFrag->CharAt(iter.GetOriginalOffset());
|
||||||
|
if (iter.IsOriginalCharSkipped()) {
|
||||||
|
if (ch == CH_SHY) {
|
||||||
|
// We should preserve soft hyphens. They can't be transformed.
|
||||||
|
result.mString.Append(ch);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.mString.Append(
|
||||||
|
TransformChar(textFrame, textStyle, textFrame->mTextRun,
|
||||||
|
iter.GetSkippedOffset(), ch));
|
||||||
}
|
}
|
||||||
iter.AdvanceOriginal(1);
|
iter.AdvanceOriginal(1);
|
||||||
}
|
}
|
||||||
if (keptCharsLength >= aSkippedMaxLength) {
|
|
||||||
break; // Already past the end, don't build string or gfxSkipCharsIter anymore
|
if (trimmedSignificantNewline && GetContentEnd() <= endOffset) {
|
||||||
|
// A significant newline was trimmed off (we must be
|
||||||
|
// white-space:pre-line). Put it back.
|
||||||
|
result.mString.Append('\n');
|
||||||
}
|
}
|
||||||
|
offsetInRenderedString = nextOffsetInRenderedString;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aSkipChars) {
|
if (!haveOffsets) {
|
||||||
aSkipChars->TakeFrom(&skipChars); // Copy skipChars into aSkipChars
|
result.mOffsetWithinNodeText = textFrag->GetLength();
|
||||||
if (aSkipIter) {
|
result.mOffsetWithinNodeRenderedText = offsetInRenderedString;
|
||||||
// Caller must provide both pointers in order to retrieve a gfxSkipCharsIterator,
|
|
||||||
// because the gfxSkipCharsIterator holds a weak pointer to the gfxSkipChars.
|
|
||||||
*aSkipIter = gfxSkipCharsIterator(*aSkipChars, GetContentLength());
|
|
||||||
}
|
}
|
||||||
}
|
return result;
|
||||||
|
|
||||||
return NS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nsIAtom*
|
nsIAtom*
|
||||||
|
|||||||
@@ -261,11 +261,10 @@ public:
|
|||||||
nscoord mDeltaWidth;
|
nscoord mDeltaWidth;
|
||||||
};
|
};
|
||||||
TrimOutput TrimTrailingWhiteSpace(nsRenderingContext* aRC);
|
TrimOutput TrimTrailingWhiteSpace(nsRenderingContext* aRC);
|
||||||
virtual nsresult GetRenderedText(nsAString* aString = nullptr,
|
virtual RenderedText GetRenderedText(uint32_t aStartOffset = 0,
|
||||||
gfxSkipChars* aSkipChars = nullptr,
|
uint32_t aEndOffset = UINT32_MAX,
|
||||||
gfxSkipCharsIterator* aSkipIter = nullptr,
|
TextOffsetType aOffsetType =
|
||||||
uint32_t aSkippedStartOffset = 0,
|
TextOffsetType::OFFSETS_IN_CONTENT_TEXT) override;
|
||||||
uint32_t aSkippedMaxLength = UINT32_MAX) override;
|
|
||||||
|
|
||||||
nsOverflowAreas RecomputeOverflow(nsIFrame* aBlockFrame);
|
nsOverflowAreas RecomputeOverflow(nsIFrame* aBlockFrame);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user