Bug 918185. When Range.getBoundingClientRect/getClientRects needs a textframe for a node, create one if the textframe was optimized away. r=bz

This commit is contained in:
Robert O'Callahan 2013-09-19 16:23:48 +12:00
parent 9b8ce9a9f5
commit 81d19c4037
6 changed files with 79 additions and 7 deletions

View File

@ -50,7 +50,11 @@ enum {
// This bit is only meaningful if the NS_CACHED_TEXT_IS_ONLY_WHITESPACE
// bit is set, and if so it indicates whether we're only whitespace or
// not.
NS_TEXT_IS_ONLY_WHITESPACE = DATA_NODE_FLAG_BIT(3)
NS_TEXT_IS_ONLY_WHITESPACE = DATA_NODE_FLAG_BIT(3),
// This bit is set to indicate that we must create frames for this text node
// even if it's only whitespace next to a block boundary.
NS_CREATE_FRAME_FOR_IGNORABLE_WHITESPACE = DATA_NODE_FLAG_BIT(4)
};
// Make sure we have enough space for those bits

View File

@ -32,6 +32,7 @@
#include "mozilla/dom/RangeBinding.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Likely.h"
#include "nsCSSFrameConstructor.h"
using namespace mozilla;
using namespace mozilla::dom;
@ -2747,12 +2748,25 @@ static void ExtractRectFromOffset(nsIFrame* aFrame,
}
}
static nsTextFrame*
GetTextFrameForContent(nsIContent* aContent)
{
nsIPresShell* presShell = aContent->OwnerDoc()->GetShell();
if (presShell) {
nsIFrame* frame = presShell->FrameConstructor()->EnsureFrameForTextNode(
static_cast<nsGenericDOMDataNode*>(aContent));
if (frame && frame->GetType() == nsGkAtoms::textFrame) {
return static_cast<nsTextFrame*>(frame);
}
}
return nullptr;
}
static nsresult GetPartialTextRect(nsLayoutUtils::RectCallback* aCallback,
nsIContent* aContent, int32_t aStartOffset, int32_t aEndOffset)
{
nsIFrame* frame = aContent->GetPrimaryFrame();
if (frame && frame->GetType() == nsGkAtoms::textFrame) {
nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
nsTextFrame* textFrame = GetTextFrameForContent(aContent);
if (textFrame) {
nsIFrame* relativeTo = nsLayoutUtils::GetContainingBlockForClientRect(textFrame);
for (nsTextFrame* f = textFrame; f; f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
int32_t fstart = f->GetContentOffset(), fend = f->GetContentEnd();
@ -2808,9 +2822,8 @@ static void CollectClientRects(nsLayoutUtils::RectCallback* aCollector,
// the range is collapsed, only continue if the cursor is in a text node
nsCOMPtr<nsIContent> content = do_QueryInterface(aStartParent);
if (content && content->IsNodeOfType(nsINode::eTEXT)) {
nsIFrame* frame = content->GetPrimaryFrame();
if (frame && frame->GetType() == nsGkAtoms::textFrame) {
nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
nsTextFrame* textFrame = GetTextFrameForContent(content);
if (textFrame) {
int32_t outOffset;
nsIFrame* outFrame;
textFrame->GetChildFrameContainingOffset(aStartOffset, false,

View File

@ -5433,12 +5433,16 @@ nsCSSFrameConstructor::ConstructFramesFromItem(nsFrameConstructorState& aState,
// due to dynamic changes.
// We don't do it for SVG text, since we might need to position and
// measure the white space glyphs due to x/y/dx/dy attributes.
// We check that NS_CREATE_FRAME_FOR_IGNORABLE_WHITESPACE is not set on
// the node. This lets us disable this optimization for specific nodes
// (e.g. nodes whose geometry is being queried via DOM APIs).
if (AtLineBoundary(aIter) &&
!styleContext->StyleText()->WhiteSpaceOrNewlineIsSignificant() &&
aIter.List()->ParentHasNoXBLChildren() &&
!(aState.mAdditionalStateBits & NS_FRAME_GENERATED_CONTENT) &&
(item.mFCData->mBits & FCDATA_IS_LINE_PARTICIPANT) &&
!(item.mFCData->mBits & FCDATA_IS_SVG_TEXT) &&
!item.mContent->HasFlag(NS_CREATE_FRAME_FOR_IGNORABLE_WHITESPACE) &&
item.IsWhitespace(aState))
return;
@ -7613,6 +7617,22 @@ InvalidateCanvasIfNeeded(nsIPresShell* presShell, nsIContent* node)
rootFrame->InvalidateFrameSubtree();
}
nsIFrame*
nsCSSFrameConstructor::EnsureFrameForTextNode(nsGenericDOMDataNode* aContent)
{
if (aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE) &&
!aContent->HasFlag(NS_CREATE_FRAME_FOR_IGNORABLE_WHITESPACE)) {
// Text frame may have been suppressed. Disable suppression and signal
// that a flush should be performed.
aContent->SetFlags(NS_CREATE_FRAME_FOR_IGNORABLE_WHITESPACE);
nsAutoScriptBlocker blocker;
BeginUpdate();
RecreateFramesForContent(aContent, false);
EndUpdate();
}
return aContent->GetPrimaryFrame();
}
nsresult
nsCSSFrameConstructor::CharacterDataChanged(nsIContent* aContent,
CharacterDataChangeInfo* aInfo)

View File

@ -31,6 +31,7 @@ struct nsGenConInitializer;
class nsICSSAnonBoxPseudo;
class nsPageContentFrame;
struct PendingBinding;
class nsGenericDOMDataNode;
class nsFrameConstructorState;
class nsFrameConstructorSaveState;
@ -198,6 +199,13 @@ public:
nsresult CharacterDataChanged(nsIContent* aContent,
CharacterDataChangeInfo* aInfo);
// If aContent is a text node that has been optimized away due to being
// whitespace next to a block boundary (or for some other reason), stop
// doing that and create a frame for it if it should have one. This recreates
// frames so be careful (although this should not change actual layout).
// Returns the frame for aContent if there is one.
nsIFrame* EnsureFrameForTextNode(nsGenericDOMDataNode* aContent);
// generate the child frames and process bindings
nsresult GenerateChildFrames(nsIFrame* aFrame);

View File

@ -143,6 +143,7 @@ MOCHITEST_FILES = \
bug851445_helper.html \
test_emulateMedium.html \
enableTestPlugin.js \
test_getClientRects_emptytext.html \
$(NULL)
ifeq (,$(filter gonk,$(MOZ_WIDGET_TOOLKIT)))

View File

@ -0,0 +1,26 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<div id="testDiv"> </div>
<script>
var textNode = testDiv.firstChild;
var range = new Range();
range.selectNodeContents(textNode);
is(range.getClientRects().length, 1, "Text node should have a rectangle");
var rect = range.getClientRects()[0];
ok(rect.left > 0, "Rectangle x should be greater than zero");
ok(rect.top > 0, "Rectangle y should be greater than zero");
is(rect.width, 0, "Rectangle should be zero width");
is(rect.height, 0, "Rectangle should be zero height");
</script>
</body>
</html>