diff --git a/dom/base/nsRange.cpp b/dom/base/nsRange.cpp index ecc6c43b365b..df1c14f4a19e 100644 --- a/dom/base/nsRange.cpp +++ b/dom/base/nsRange.cpp @@ -2647,10 +2647,40 @@ static nsresult GetPartialTextRect(RectCallback* aCallback, if (textFrame) { nsIFrame* relativeTo = nsLayoutUtils::GetContainingBlockForClientRect(textFrame); - for (nsTextFrame* f = textFrame; f; - f = static_cast(f->GetNextContinuation())) { + + // Try to binary-search the list of continuations for the starting point. + // (GetContinuations is fallible; if it returns nullptr, we'll just start + // from the beginning.) + nsTArray* continuations = textFrame->GetContinuations(); + nsTextFrame* f = textFrame; + if (continuations) { + size_t index; + if (BinarySearchIf( + *continuations, 0, continuations->Length(), + [=](nsTextFrame* aFrame) -> int { + if (aStartOffset < aFrame->GetContentOffset()) { + return -1; + } + if (aStartOffset > aFrame->GetContentOffset()) { + return 1; + } + return 0; + }, + &index)) { + f = (*continuations)[index]; + } else { + f = (*continuations)[index ? index - 1 : 0]; + } + } + + for (; f; f = static_cast(f->GetNextContinuation())) { int32_t fstart = f->GetContentOffset(), fend = f->GetContentEnd(); - if (fend <= aStartOffset || fstart >= aEndOffset) continue; + if (fend <= aStartOffset) { + continue; + } + if (fstart >= aEndOffset) { + break; + } // Calculate the text content offsets we'll need if text is requested. int32_t textContentStart = fstart; diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index 99d6d015cd76..7f6f3c6d2a8f 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -4384,6 +4384,35 @@ void nsTextFrame::DestroyFrom(nsIFrame* aDestructRoot, nsIFrame::DestroyFrom(aDestructRoot, aPostDestroyData); } +nsTArray* nsTextFrame::GetContinuations() { + MOZ_ASSERT(NS_IsMainThread()); + // Only for use on the primary frame, which has no prev-continuation. + MOZ_ASSERT(!GetPrevContinuation()); + if (!mNextContinuation) { + return nullptr; + } + if (mHasContinuationsProperty) { + return GetProperty(ContinuationsProperty()); + } + size_t count = 0; + for (nsIFrame* f = this; f; f = f->GetNextContinuation()) { + ++count; + } + auto* continuations = new nsTArray; + if (continuations->SetCapacity(count, fallible)) { + for (nsTextFrame* f = this; f; + f = static_cast(f->GetNextContinuation())) { + continuations->AppendElement(f); + } + } else { + delete continuations; + continuations = nullptr; + } + AddProperty(ContinuationsProperty(), continuations); + mHasContinuationsProperty = true; + return continuations; +} + class nsContinuingTextFrame final : public nsTextFrame { public: NS_DECL_FRAMEARENA_HELPERS(nsContinuingTextFrame) @@ -4410,10 +4439,16 @@ class nsContinuingTextFrame final : public nsTextFrame { nsTextFrame* prevFirst = mFirstContinuation; if (mPrevContinuation) { mFirstContinuation = mPrevContinuation->FirstContinuation(); + if (mFirstContinuation) { + mFirstContinuation->ClearCachedContinuations(); + } } else { mFirstContinuation = nullptr; } if (mFirstContinuation != prevFirst) { + if (prevFirst) { + prevFirst->ClearCachedContinuations(); + } auto* f = static_cast(mNextContinuation); while (f) { f->mFirstContinuation = mFirstContinuation; @@ -4438,10 +4473,16 @@ class nsContinuingTextFrame final : public nsTextFrame { nsTextFrame* prevFirst = mFirstContinuation; if (mPrevContinuation) { mFirstContinuation = mPrevContinuation->FirstContinuation(); + if (mFirstContinuation) { + mFirstContinuation->ClearCachedContinuations(); + } } else { mFirstContinuation = nullptr; } if (mFirstContinuation != prevFirst) { + if (prevFirst) { + prevFirst->ClearCachedContinuations(); + } auto* f = static_cast(mNextContinuation); while (f) { f->mFirstContinuation = mFirstContinuation; @@ -4457,6 +4498,8 @@ class nsContinuingTextFrame final : public nsTextFrame { return mFirstContinuation; }; + nsTArray* GetContinuations() final { return nullptr; } + void AddInlineMinISize(gfxContext* aRenderingContext, InlineMinISizeData* aData) final; void AddInlinePrefISize(gfxContext* aRenderingContext, @@ -9121,6 +9164,12 @@ void nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS); mReflowRequestedForCharDataChange = false; RemoveProperty(WebRenderTextBounds()); + + // Discard cached continuations array that will be invalidated by the reflow. + if (nsTextFrame* first = FirstContinuation()) { + first->ClearCachedContinuations(); + } + // Temporarily map all possible content while we construct our new textrun. // so that when doing reflow our styles prevail over any part of the // textrun we look at. Note that next-in-flows may be mapping the same diff --git a/layout/generic/nsTextFrame.h b/layout/generic/nsTextFrame.h index 6b429845a388..ea7c3f312908 100644 --- a/layout/generic/nsTextFrame.h +++ b/layout/generic/nsTextFrame.h @@ -214,6 +214,9 @@ class nsTextFrame : public nsIFrame { // nsQueryFrame NS_DECL_QUERYFRAME + NS_DECLARE_FRAME_PROPERTY_DELETABLE(ContinuationsProperty, + nsTArray) + // nsIFrame void BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) final; @@ -781,6 +784,11 @@ class nsTextFrame : public nsIFrame { nsRect WebRenderBounds(); + // Return pointer to an array of all frames in the continuation chain, or + // null if we're too short of memory. (This is only meant to be called on the + // first text frame in the chain; continuations will always return null.) + virtual nsTArray* GetContinuations(); + protected: virtual ~nsTextFrame(); @@ -815,6 +823,9 @@ class nsTextFrame : public nsIFrame { }; mutable SelectionState mIsSelected; + // Whether a cached continuations array is present. + bool mHasContinuationsProperty = false; + /** * Return true if the frame is part of a Selection. * Helper method to implement the public IsSelected() API. @@ -999,6 +1010,16 @@ class nsTextFrame : public nsIFrame { void ClearMetrics(ReflowOutput& aMetrics); + // Clear any cached continuations array; this should be called whenever the + // chain is modified. + void ClearCachedContinuations() { + MOZ_ASSERT(NS_IsMainThread()); + if (mHasContinuationsProperty) { + RemoveProperty(ContinuationsProperty()); + mHasContinuationsProperty = false; + } + } + /** * UpdateIteratorFromOffset() updates the iterator from a given offset. * Also, aInOffset may be updated to cluster start if aInOffset isn't @@ -1010,8 +1031,6 @@ class nsTextFrame : public nsIFrame { nsPoint GetPointFromIterator(const gfxSkipCharsIterator& aIter, PropertyProvider& aProperties); - - public: }; MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsTextFrame::TrimmedOffsetFlags)