Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsBidiPresUtils.h"
#include "mozilla/intl/Bidi.h"
#include "mozilla/Casting.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/Maybe.h"
#include "mozilla/PresShell.h"
#include "mozilla/dom/Text.h"
#include "gfxContext.h"
#include "nsFontMetrics.h"
#include "nsGkAtoms.h"
#include "nsPresContext.h"
#include "nsBidiUtils.h"
#include "nsCSSFrameConstructor.h"
#include "nsContainerFrame.h"
#include "nsInlineFrame.h"
#include "nsPlaceholderFrame.h"
#include "nsPointerHashKeys.h"
#include "nsFirstLetterFrame.h"
#include "nsUnicodeProperties.h"
#include "nsTextFrame.h"
#include "nsBlockFrame.h"
#include "nsIFrameInlines.h"
#include "nsStyleStructInlines.h"
#include "RubyUtils.h"
#include "nsRubyFrame.h"
#include "nsRubyBaseFrame.h"
#include "nsRubyTextFrame.h"
#include "nsRubyBaseContainerFrame.h"
#include "nsRubyTextContainerFrame.h"
#include <algorithm>
#undef NOISY_BIDI
#undef REALLY_NOISY_BIDI
using namespace mozilla;
using BidiEngine = intl::Bidi;
using BidiClass = intl::BidiClass;
using BidiDirection = intl::BidiDirection;
using BidiEmbeddingLevel = intl::BidiEmbeddingLevel;
static const char16_t kNextLine = 0x0085;
static const char16_t kZWSP = 0x200B;
static const char16_t kLineSeparator = 0x2028;
static const char16_t kParagraphSeparator = 0x2029;
static const char16_t kObjectSubstitute = 0xFFFC;
static const char16_t kLRE = 0x202A;
static const char16_t kRLE = 0x202B;
static const char16_t kLRO = 0x202D;
static const char16_t kRLO = 0x202E;
static const char16_t kPDF = 0x202C;
static const char16_t kLRI = 0x2066;
static const char16_t kRLI = 0x2067;
static const char16_t kFSI = 0x2068;
static const char16_t kPDI = 0x2069;
// All characters with Bidi type Segment Separator or Block Separator.
// This should be kept in sync with the table in ReplaceSeparators.
static const char16_t kSeparators[] = {
char16_t('\t'), char16_t('\r'), char16_t('\n'), char16_t(0xb),
char16_t(0x1c), char16_t(0x1d), char16_t(0x1e), char16_t(0x1f),
kNextLine, kParagraphSeparator, char16_t(0)};
#define NS_BIDI_CONTROL_FRAME ((nsIFrame*)0xfffb1d1)
// This exists just to be a type; the value doesn't matter.
enum class BidiControlFrameType { Value };
static bool IsIsolateControl(char16_t aChar) {
return aChar == kLRI || aChar == kRLI || aChar == kFSI;
}
// Given a ComputedStyle, return any bidi control character necessary to
// implement style properties that override directionality (i.e. if it has
// unicode-bidi:bidi-override, or text-orientation:upright in vertical
// writing mode) when applying the bidi algorithm.
//
// Returns 0 if no override control character is implied by this style.
static char16_t GetBidiOverride(ComputedStyle* aComputedStyle) {
const nsStyleVisibility* vis = aComputedStyle->StyleVisibility();
if ((vis->mWritingMode == StyleWritingModeProperty::VerticalRl ||
vis->mWritingMode == StyleWritingModeProperty::VerticalLr) &&
vis->mTextOrientation == StyleTextOrientation::Upright) {
return kLRO;
}
const nsStyleTextReset* text = aComputedStyle->StyleTextReset();
if (text->mUnicodeBidi == StyleUnicodeBidi::BidiOverride ||
text->mUnicodeBidi == StyleUnicodeBidi::IsolateOverride) {
return StyleDirection::Rtl == vis->mDirection ? kRLO : kLRO;
}
return 0;
}
// Given a ComputedStyle, return any bidi control character necessary to
// implement style properties that affect bidi resolution (i.e. if it
// has unicode-bidiembed, isolate, or plaintext) when applying the bidi
// algorithm.
//
// Returns 0 if no control character is implied by the style.
//
// Note that GetBidiOverride and GetBidiControl need to be separate
// because in the case of unicode-bidi:isolate-override we need both
// FSI and LRO/RLO.
static char16_t GetBidiControl(ComputedStyle* aComputedStyle) {
const nsStyleVisibility* vis = aComputedStyle->StyleVisibility();
const nsStyleTextReset* text = aComputedStyle->StyleTextReset();
switch (text->mUnicodeBidi) {
case StyleUnicodeBidi::Embed:
return StyleDirection::Rtl == vis->mDirection ? kRLE : kLRE;
case StyleUnicodeBidi::Isolate:
// <bdi> element already has its directionality set from content so
// we never need to return kFSI.
return StyleDirection::Rtl == vis->mDirection ? kRLI : kLRI;
case StyleUnicodeBidi::IsolateOverride:
case StyleUnicodeBidi::Plaintext:
return kFSI;
case StyleUnicodeBidi::Normal:
case StyleUnicodeBidi::BidiOverride:
break;
}
return 0;
}
#ifdef DEBUG
static inline bool AreContinuationsInOrder(nsIFrame* aFrame1,
nsIFrame* aFrame2) {
nsIFrame* f = aFrame1;
do {
f = f->GetNextContinuation();
} while (f && f != aFrame2);
return !!f;
}
#endif
struct MOZ_STACK_CLASS BidiParagraphData {
struct FrameInfo {
FrameInfo(nsIFrame* aFrame, nsBlockInFlowLineIterator& aLineIter)
: mFrame(aFrame),
mBlockContainer(aLineIter.GetContainer()),
mInOverflow(aLineIter.GetInOverflow()) {}
explicit FrameInfo(BidiControlFrameType aValue)
: mFrame(NS_BIDI_CONTROL_FRAME),
mBlockContainer(nullptr),
mInOverflow(false) {}
FrameInfo()
: mFrame(nullptr), mBlockContainer(nullptr), mInOverflow(false) {}
nsIFrame* mFrame;
// The block containing mFrame (i.e., which continuation).
nsBlockFrame* mBlockContainer;
// true if mFrame is in mBlockContainer's overflow lines, false if
// in primary lines
bool mInOverflow;
};
nsAutoString mBuffer;
AutoTArray<char16_t, 16> mEmbeddingStack;
AutoTArray<FrameInfo, 16> mLogicalFrames;
nsTHashMap<nsPtrHashKey<const nsIContent>, int32_t> mContentToFrameIndex;
// Cached presentation context for the frames we're processing.
nsPresContext* mPresContext;
bool mIsVisual;
bool mRequiresBidi;
BidiEmbeddingLevel mParaLevel;
nsIContent* mPrevContent;
/**
* This class is designed to manage the process of mapping a frame to
* the line that it's in, when we know that (a) the frames we ask it
* about are always in the block's lines and (b) each successive frame
* we ask it about is the same as or after (in depth-first search
* order) the previous.
*
* Since we move through the lines at a different pace in Traverse and
* ResolveParagraph, we use one of these for each.
*
* The state of the mapping is also different between TraverseFrames
* and ResolveParagraph since since resolving can call functions
* (EnsureBidiContinuation or SplitInlineAncestors) that can create
* new frames and thus break lines.
*
* The TraverseFrames iterator is only used in some edge cases.
*/
struct FastLineIterator {
FastLineIterator() : mPrevFrame(nullptr), mNextLineStart(nullptr) {}
// These iterators *and* mPrevFrame track the line list that we're
// iterating over.
//
// mPrevFrame, if non-null, should be either the frame we're currently
// handling (in ResolveParagraph or TraverseFrames, depending on the
// iterator) or a frame before it, and is also guaranteed to either be in
// mCurrentLine or have been in mCurrentLine until recently.
//
// In case the splitting causes block frames to break lines, however, we
// also track the first frame of the next line. If that changes, it means
// we've broken lines and we have to invalidate mPrevFrame.
nsBlockInFlowLineIterator mLineIterator;
nsIFrame* mPrevFrame;
nsIFrame* mNextLineStart;
nsLineList::iterator GetLine() { return mLineIterator.GetLine(); }
static bool IsFrameInCurrentLine(nsBlockInFlowLineIterator* aLineIter,
nsIFrame* aPrevFrame, nsIFrame* aFrame) {
MOZ_ASSERT(!aPrevFrame || aLineIter->GetLine()->Contains(aPrevFrame),
"aPrevFrame must be in aLineIter's current line");
nsIFrame* endFrame = aLineIter->IsLastLineInList()
? nullptr
: aLineIter->GetLine().next()->mFirstChild;
nsIFrame* startFrame =
aPrevFrame ? aPrevFrame : aLineIter->GetLine()->mFirstChild;
for (nsIFrame* frame = startFrame; frame && frame != endFrame;
frame = frame->GetNextSibling()) {
if (frame == aFrame) return true;
}
return false;
}
static nsIFrame* FirstChildOfNextLine(
nsBlockInFlowLineIterator& aIterator) {
const nsLineList::iterator line = aIterator.GetLine();
const nsLineList::iterator lineEnd = aIterator.End();
MOZ_ASSERT(line != lineEnd, "iterator should start off valid");
const nsLineList::iterator nextLine = line.next();
return nextLine != lineEnd ? nextLine->mFirstChild : nullptr;
}
// Advance line iterator to the line containing aFrame, assuming
// that aFrame is already in the line list our iterator is iterating
// over.
void AdvanceToFrame(nsIFrame* aFrame) {
if (mPrevFrame && FirstChildOfNextLine(mLineIterator) != mNextLineStart) {
// Something has caused a line to split. We need to invalidate
// mPrevFrame since it may now be in a *later* line, though it may
// still be in this line, so we need to start searching for it from
// the start of this line.
mPrevFrame = nullptr;
}
nsIFrame* child = aFrame;
nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(child);
while (parent && !parent->IsBlockFrameOrSubclass()) {
child = parent;
parent = nsLayoutUtils::GetParentOrPlaceholderFor(child);
}
MOZ_ASSERT(parent, "aFrame is not a descendent of a block frame");
while (!IsFrameInCurrentLine(&mLineIterator, mPrevFrame, child)) {
#ifdef DEBUG
bool hasNext =
#endif
mLineIterator.Next();
MOZ_ASSERT(hasNext, "Can't find frame in lines!");
mPrevFrame = nullptr;
}
mPrevFrame = child;
mNextLineStart = FirstChildOfNextLine(mLineIterator);
}
// Advance line iterator to the line containing aFrame, which may
// require moving forward into overflow lines or into a later
// continuation (or both).
void AdvanceToLinesAndFrame(const FrameInfo& aFrameInfo) {
if (mLineIterator.GetContainer() != aFrameInfo.mBlockContainer ||
mLineIterator.GetInOverflow() != aFrameInfo.mInOverflow) {
MOZ_ASSERT(
mLineIterator.GetContainer() == aFrameInfo.mBlockContainer
? (!mLineIterator.GetInOverflow() && aFrameInfo.mInOverflow)
: (!mLineIterator.GetContainer() ||
AreContinuationsInOrder(mLineIterator.GetContainer(),
aFrameInfo.mBlockContainer)),
"must move forwards");
nsBlockFrame* block = aFrameInfo.mBlockContainer;
nsLineList::iterator lines =
aFrameInfo.mInOverflow ? block->GetOverflowLines()->mLines.begin()
: block->LinesBegin();
mLineIterator =
nsBlockInFlowLineIterator(block, lines, aFrameInfo.mInOverflow);
mPrevFrame = nullptr;
}
AdvanceToFrame(aFrameInfo.mFrame);
}
};
FastLineIterator mCurrentTraverseLine, mCurrentResolveLine;
#ifdef DEBUG
// Only used for NOISY debug output.
// Matches the current TraverseFrames state, not the ResolveParagraph
// state.
nsBlockFrame* mCurrentBlock;
#endif
explicit BidiParagraphData(nsBlockFrame* aBlockFrame)
: mPresContext(aBlockFrame->PresContext()),
mIsVisual(mPresContext->IsVisualMode()),
mRequiresBidi(false),
mParaLevel(nsBidiPresUtils::BidiLevelFromStyle(aBlockFrame->Style())),
mPrevContent(nullptr)
#ifdef DEBUG
,
mCurrentBlock(aBlockFrame)
#endif
{
if (mParaLevel > 0) {
mRequiresBidi = true;
}
if (mIsVisual) {
/**
* Drill up in content to detect whether this is an element that needs to
* be rendered with logical order even on visual pages.
*
* We always use logical order on form controls, firstly so that text
* entry will be in logical order, but also because visual pages were
* written with the assumption that even if the browser had no support
* for right-to-left text rendering, it would use native widgets with
* bidi support to display form controls.
*
* We also use logical order in XUL elements, since we expect that if a
* XUL element appears in a visual page, it will be generated by an XBL
* binding and contain localized text which will be in logical order.
*/
for (nsIContent* content = aBlockFrame->GetContent(); content;
content = content->GetParent()) {
if (content->IsXULElement() || content->IsHTMLFormControlElement()) {
mIsVisual = false;
break;
}
}
}
}
nsresult SetPara() {
if (mPresContext->BidiEngine().SetParagraph(mBuffer, mParaLevel).isErr()) {
return NS_ERROR_FAILURE;
};
return NS_OK;
}
/**
* mParaLevel can be BidiDirection::LTR as well as
* BidiDirection::LTR or BidiDirection::RTL.
* GetParagraphEmbeddingLevel() returns the actual (resolved) paragraph level
* which is always either BidiDirection::LTR or
* BidiDirection::RTL
*/
BidiEmbeddingLevel GetParagraphEmbeddingLevel() {
BidiEmbeddingLevel paraLevel = mParaLevel;
if (paraLevel == BidiEmbeddingLevel::DefaultLTR() ||
paraLevel == BidiEmbeddingLevel::DefaultRTL()) {
paraLevel = mPresContext->BidiEngine().GetParagraphEmbeddingLevel();
}
return paraLevel;
}
BidiEngine::ParagraphDirection GetParagraphDirection() {
return mPresContext->BidiEngine().GetParagraphDirection();
}
nsresult CountRuns(int32_t* runCount) {
auto result = mPresContext->BidiEngine().CountRuns();
if (result.isErr()) {
return NS_ERROR_FAILURE;
}
*runCount = result.unwrap();
return NS_OK;
}
void GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit,
BidiEmbeddingLevel* aLevel) {
mPresContext->BidiEngine().GetLogicalRun(aLogicalStart, aLogicalLimit,
aLevel);
if (mIsVisual) {
*aLevel = GetParagraphEmbeddingLevel();
}
}
void ResetData() {
mLogicalFrames.Clear();
mContentToFrameIndex.Clear();
mBuffer.SetLength(0);
mPrevContent = nullptr;
for (uint32_t i = 0; i < mEmbeddingStack.Length(); ++i) {
mBuffer.Append(mEmbeddingStack[i]);
mLogicalFrames.AppendElement(FrameInfo(BidiControlFrameType::Value));
}
}
void AppendFrame(nsIFrame* aFrame, FastLineIterator& aLineIter,
nsIContent* aContent = nullptr) {
if (aContent) {
mContentToFrameIndex.InsertOrUpdate(aContent, FrameCount());
}
// We don't actually need to advance aLineIter to aFrame, since all we use
// from it is the block and is-overflow state, which are correct already.
mLogicalFrames.AppendElement(FrameInfo(aFrame, aLineIter.mLineIterator));
}
void AdvanceAndAppendFrame(nsIFrame** aFrame, FastLineIterator& aLineIter,
nsIFrame** aNextSibling) {
nsIFrame* frame = *aFrame;
nsIFrame* nextSibling = *aNextSibling;
frame = frame->GetNextContinuation();
if (frame) {
AppendFrame(frame, aLineIter, nullptr);
/*
* If we have already overshot the saved next-sibling while
* scanning the frame's continuations, advance it.
*/
if (frame == nextSibling) {
nextSibling = frame->GetNextSibling();
}
}
*aFrame = frame;
*aNextSibling = nextSibling;
}
int32_t GetLastFrameForContent(nsIContent* aContent) {
return mContentToFrameIndex.Get(aContent);
}
int32_t FrameCount() { return mLogicalFrames.Length(); }
int32_t BufferLength() { return mBuffer.Length(); }
nsIFrame* FrameAt(int32_t aIndex) { return mLogicalFrames[aIndex].mFrame; }
const FrameInfo& FrameInfoAt(int32_t aIndex) {
return mLogicalFrames[aIndex];
}
void AppendUnichar(char16_t aCh) { mBuffer.Append(aCh); }
void AppendString(const nsDependentSubstring& aString) {
mBuffer.Append(aString);
}
void AppendControlChar(char16_t aCh) {
mLogicalFrames.AppendElement(FrameInfo(BidiControlFrameType::Value));
AppendUnichar(aCh);
}
void PushBidiControl(char16_t aCh) {
AppendControlChar(aCh);
mEmbeddingStack.AppendElement(aCh);
}
void AppendPopChar(char16_t aCh) {
AppendControlChar(IsIsolateControl(aCh) ? kPDI : kPDF);
}
void PopBidiControl(char16_t aCh) {
MOZ_ASSERT(mEmbeddingStack.Length(), "embedding/override underflow");
MOZ_ASSERT(aCh == mEmbeddingStack.LastElement());
AppendPopChar(aCh);
mEmbeddingStack.RemoveLastElement();
}
void ClearBidiControls() {
for (char16_t c : Reversed(mEmbeddingStack)) {
AppendPopChar(c);
}
}
};
class MOZ_STACK_CLASS BidiLineData {
public:
BidiLineData(nsIFrame* aFirstFrameOnLine, int32_t aNumFramesOnLine) {
// Initialize the logically-ordered array of frames using the top-level
// frames of a single line
auto appendFrame = [&](nsIFrame* frame, BidiEmbeddingLevel level) {
mLogicalFrames.AppendElement(frame);
mLevels.AppendElement(level);
mIndexMap.AppendElement(0);
};
for (nsIFrame* frame = aFirstFrameOnLine; frame && aNumFramesOnLine--;
frame = frame->GetNextSibling()) {
FrameBidiData bidiData = nsBidiPresUtils::GetFrameBidiData(frame);
if (bidiData.precedingControl != kBidiLevelNone) {
appendFrame(NS_BIDI_CONTROL_FRAME, bidiData.precedingControl);
}
appendFrame(frame, bidiData.embeddingLevel);
}
// Reorder the line
BidiEngine::ReorderVisual(mLevels.Elements(), mLevels.Length(),
mIndexMap.Elements());
// Collect the frames in visual order, omitting virtual controls
// and noting whether frames are reordered.
for (uint32_t i = 0; i < mIndexMap.Length(); i++) {
nsIFrame* frame = mLogicalFrames[mIndexMap[i]];
if (frame == NS_BIDI_CONTROL_FRAME) {
continue;
}
mVisualFrameIndex.AppendElement(mIndexMap[i]);
if (int32_t(i) != mIndexMap[i]) {
mIsReordered = true;
}
}
}
uint32_t LogicalFrameCount() const { return mLogicalFrames.Length(); }
uint32_t VisualFrameCount() const { return mVisualFrameIndex.Length(); }
nsIFrame* LogicalFrameAt(uint32_t aIndex) const {
return mLogicalFrames[aIndex];
}
nsIFrame* VisualFrameAt(uint32_t aIndex) const {
return mLogicalFrames[mVisualFrameIndex[aIndex]];
}
std::pair<nsIFrame*, BidiEmbeddingLevel> VisualFrameAndLevelAt(
uint32_t aIndex) const {
int32_t index = mVisualFrameIndex[aIndex];
return std::pair(mLogicalFrames[index], mLevels[index]);
}
bool IsReordered() const { return mIsReordered; }
void InitContinuationStates(nsContinuationStates* aContinuationStates) const {
for (auto* frame : mLogicalFrames) {
if (frame != NS_BIDI_CONTROL_FRAME) {
nsBidiPresUtils::InitContinuationStates(frame, aContinuationStates);
}
}
}
private:
AutoTArray<nsIFrame*, 16> mLogicalFrames;
AutoTArray<int32_t, 16> mVisualFrameIndex;
AutoTArray<int32_t, 16> mIndexMap;
AutoTArray<BidiEmbeddingLevel, 16> mLevels;
bool mIsReordered = false;
};
#ifdef DEBUG
extern "C" {
void MOZ_EXPORT DumpBidiLine(BidiLineData* aData, bool aVisualOrder) {
auto dump = [](nsIFrame* frame) {
if (frame == NS_BIDI_CONTROL_FRAME) {
fprintf_stderr(stderr, "(Bidi control frame)\n");
} else {
frame->List();
}
};
if (aVisualOrder) {
for (uint32_t i = 0; i < aData->VisualFrameCount(); i++) {
dump(aData->VisualFrameAt(i));
}
} else {
for (uint32_t i = 0; i < aData->LogicalFrameCount(); i++) {
dump(aData->LogicalFrameAt(i));
}
}
}
}
#endif
/* Some helper methods for Resolve() */
// Should this frame be split between text runs?
static bool IsBidiSplittable(nsIFrame* aFrame) {
MOZ_ASSERT(aFrame);
// Bidi inline containers should be split, unless they're line frames.
LayoutFrameType frameType = aFrame->Type();
return (aFrame->IsBidiInlineContainer() &&
frameType != LayoutFrameType::Line) ||
frameType == LayoutFrameType::Text;
}
// Should this frame be treated as a leaf (e.g. when building mLogicalFrames)?
static bool IsBidiLeaf(const nsIFrame* aFrame) {
nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
if (kid) {
if (aFrame->IsBidiInlineContainer() ||
RubyUtils::IsRubyBox(aFrame->Type())) {
return false;
}
}
return true;
}
/**
* Create non-fluid continuations for the ancestors of a given frame all the way
* up the frame tree until we hit a non-splittable frame (a line or a block).
*
* @param aParent the first parent frame to be split
* @param aFrame the child frames after this frame are reparented to the
* newly-created continuation of aParent.
* If aFrame is null, all the children of aParent are reparented.
*/
static void SplitInlineAncestors(nsContainerFrame* aParent,
nsLineList::iterator aLine, nsIFrame* aFrame) {
PresShell* presShell = aParent->PresShell();
nsIFrame* frame = aFrame;
nsContainerFrame* parent = aParent;
nsContainerFrame* newParent;
while (IsBidiSplittable(parent)) {
nsContainerFrame* grandparent = parent->GetParent();
NS_ASSERTION(grandparent,
"Couldn't get parent's parent in "
"nsBidiPresUtils::SplitInlineAncestors");
// Split the child list after |frame|, unless it is the last child.
if (!frame || frame->GetNextSibling()) {
newParent = static_cast<nsContainerFrame*>(
presShell->FrameConstructor()->CreateContinuingFrame(
parent, grandparent, false));
nsFrameList tail = parent->StealFramesAfter(frame);
// Reparent views as necessary
nsContainerFrame::ReparentFrameViewList(tail, parent, newParent);
// The parent's continuation adopts the siblings after the split.
MOZ_ASSERT(!newParent->IsBlockFrameOrSubclass(),
"blocks should not be IsBidiSplittable");
newParent->InsertFrames(FrameChildListID::NoReflowPrincipal, nullptr,
nullptr, std::move(tail));
// While passing &aLine to InsertFrames for a non-block isn't harmful
// because it's a no-op, it doesn't really make sense. However, the
// MOZ_ASSERT() we need to guarantee that it's safe only works if the
// parent is actually the block.
const nsLineList::iterator* parentLine;
if (grandparent->IsBlockFrameOrSubclass()) {
MOZ_ASSERT(aLine->Contains(parent));
parentLine = &aLine;
} else {
parentLine = nullptr;
}
// The list name FrameChildListID::NoReflowPrincipal would indicate we
// don't want reflow
grandparent->InsertFrames(FrameChildListID::NoReflowPrincipal, parent,
parentLine, nsFrameList(newParent, newParent));
}
frame = parent;
parent = grandparent;
}
}
static void MakeContinuationFluid(nsIFrame* aFrame, nsIFrame* aNext) {
NS_ASSERTION(!aFrame->GetNextInFlow() || aFrame->GetNextInFlow() == aNext,
"next-in-flow is not next continuation!");
aFrame->SetNextInFlow(aNext);
NS_ASSERTION(!aNext->GetPrevInFlow() || aNext->GetPrevInFlow() == aFrame,
"prev-in-flow is not prev continuation!");
aNext->SetPrevInFlow(aFrame);
}
static void MakeContinuationsNonFluidUpParentChain(nsIFrame* aFrame,
nsIFrame* aNext) {
nsIFrame* frame;
nsIFrame* next;
for (frame = aFrame, next = aNext;
frame && next && next != frame && next == frame->GetNextInFlow() &&
IsBidiSplittable(frame);
frame = frame->GetParent(), next = next->GetParent()) {
frame->SetNextContinuation(next);
next->SetPrevContinuation(frame);
}
}
// If aFrame is the last child of its parent, convert bidi continuations to
// fluid continuations for all of its inline ancestors.
// If it isn't the last child, make sure that its continuation is fluid.
static void JoinInlineAncestors(nsIFrame* aFrame) {
nsIFrame* frame = aFrame;
while (frame && IsBidiSplittable(frame)) {
nsIFrame* next = frame->GetNextContinuation();
if (next) {
MakeContinuationFluid(frame, next);
}
// Join the parent only as long as we're its last child.
if (frame->GetNextSibling()) break;
frame = frame->GetParent();
}
}
static void CreateContinuation(nsIFrame* aFrame,
const nsLineList::iterator aLine,
nsIFrame** aNewFrame, bool aIsFluid) {
MOZ_ASSERT(aNewFrame, "null OUT ptr");
MOZ_ASSERT(aFrame, "null ptr");
*aNewFrame = nullptr;
nsPresContext* presContext = aFrame->PresContext();
PresShell* presShell = presContext->PresShell();
NS_ASSERTION(presShell,
"PresShell must be set on PresContext before calling "
"nsBidiPresUtils::CreateContinuation");
nsContainerFrame* parent = aFrame->GetParent();
NS_ASSERTION(
parent,
"Couldn't get frame parent in nsBidiPresUtils::CreateContinuation");
// While passing &aLine to InsertFrames for a non-block isn't harmful
// because it's a no-op, it doesn't really make sense. However, the
// MOZ_ASSERT() we need to guarantee that it's safe only works if the
// parent is actually the block.
const nsLineList::iterator* parentLine;
if (parent->IsBlockFrameOrSubclass()) {
MOZ_ASSERT(aLine->Contains(aFrame));
parentLine = &aLine;
} else {
parentLine = nullptr;
}
// Have to special case floating first letter frames because the continuation
// doesn't go in the first letter frame. The continuation goes with the rest
// of the text that the first letter frame was made out of.
if (parent->IsLetterFrame() && parent->IsFloating()) {
nsFirstLetterFrame* letterFrame = do_QueryFrame(parent);
letterFrame->CreateContinuationForFloatingParent(aFrame, aNewFrame,
aIsFluid);
return;
}
*aNewFrame = presShell->FrameConstructor()->CreateContinuingFrame(
aFrame, parent, aIsFluid);
// The list name FrameChildListID::NoReflowPrincipal would indicate we don't
// want reflow
// XXXbz this needs higher-level framelist love
parent->InsertFrames(FrameChildListID::NoReflowPrincipal, aFrame, parentLine,
nsFrameList(*aNewFrame, *aNewFrame));
if (!aIsFluid) {
// Split inline ancestor frames
SplitInlineAncestors(parent, aLine, aFrame);
}
}
/*
* Overview of the implementation of Resolve():
*
* Walk through the descendants of aBlockFrame and build:
* * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order
* * mBuffer: an nsString containing a representation of
* the content of the frames.
* In the case of text frames, this is the actual text context of the
* frames, but some other elements are represented in a symbolic form which
* will make the Unicode Bidi Algorithm give the correct results.
* Bidi isolates, embeddings, and overrides set by CSS, <bdi>, or <bdo>
* elements are represented by the corresponding Unicode control characters.
* <br> elements are represented by U+2028 LINE SEPARATOR
* Other inline elements are represented by U+FFFC OBJECT REPLACEMENT
* CHARACTER
*
* Then pass mBuffer to the Bidi engine for resolving of embedding levels
* by nsBidi::SetPara() and division into directional runs by
* nsBidi::CountRuns().
*
* Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and
* correlate them with the frames indexed in mLogicalFrames, setting the
* baseLevel and embeddingLevel properties according to the results returned
* by the Bidi engine.
*
* The rendering layer requires each text frame to contain text in only one
* direction, so we may need to call EnsureBidiContinuation() to split frames.
* We may also need to call RemoveBidiContinuation() to convert frames created
* by EnsureBidiContinuation() in previous reflows into fluid continuations.
*/
nsresult nsBidiPresUtils::Resolve(nsBlockFrame* aBlockFrame) {
BidiParagraphData bpd(aBlockFrame);
// Handle bidi-override being set on the block itself before calling
// TraverseFrames.
// No need to call GetBidiControl as well, because isolate and embed
// values of unicode-bidi property are redundant on block elements.
// unicode-bidi:plaintext on a block element is handled by block frame
// via using nsIFrame::GetWritingMode(nsIFrame*).
char16_t ch = GetBidiOverride(aBlockFrame->Style());
if (ch != 0) {
bpd.PushBidiControl(ch);
bpd.mRequiresBidi = true;
} else {
// If there are no unicode-bidi properties and no RTL characters in the
// block's content, then it is pure LTR and we can skip the rest of bidi
// resolution.
nsIContent* currContent = nullptr;
for (nsBlockFrame* block = aBlockFrame; block;
block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
if (!bpd.mRequiresBidi &&
ChildListMayRequireBidi(block->PrincipalChildList().FirstChild(),
&currContent)) {
bpd.mRequiresBidi = true;
}
if (!bpd.mRequiresBidi) {
nsBlockFrame::FrameLines* overflowLines = block->GetOverflowLines();
if (overflowLines) {
if (ChildListMayRequireBidi(overflowLines->mFrames.FirstChild(),
&currContent)) {
bpd.mRequiresBidi = true;
}
}
}
}
if (!bpd.mRequiresBidi) {
return NS_OK;
}
}
for (nsBlockFrame* block = aBlockFrame; block;
block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
#ifdef DEBUG
bpd.mCurrentBlock = block;
#endif
block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
bpd.mCurrentTraverseLine.mLineIterator =
nsBlockInFlowLineIterator(block, block->LinesBegin());
bpd.mCurrentTraverseLine.mPrevFrame = nullptr;
TraverseFrames(block->PrincipalChildList().FirstChild(), &bpd);
nsBlockFrame::FrameLines* overflowLines = block->GetOverflowLines();
if (overflowLines) {
bpd.mCurrentTraverseLine.mLineIterator =
nsBlockInFlowLineIterator(block, overflowLines->mLines.begin(), true);
bpd.mCurrentTraverseLine.mPrevFrame = nullptr;
TraverseFrames(overflowLines->mFrames.FirstChild(), &bpd);
}
}
if (ch != 0) {
bpd.PopBidiControl(ch);
}
return ResolveParagraph(&bpd);
}
// In ResolveParagraph, we previously used ReplaceChar(kSeparators, kSpace)
// to convert separators to spaces, but this hard-coded implementation is
// substantially faster than the general-purpose ReplaceChar function.
// This must be kept in sync with the definition of kSeparators.
static inline void ReplaceSeparators(nsString& aText, size_t aStartIndex = 0) {
for (char16_t* cp = aText.BeginWriting() + aStartIndex;
cp < aText.EndWriting(); cp++) {
if (MOZ_UNLIKELY(*cp < char16_t(' '))) {
static constexpr char16_t SeparatorToSpace[32] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, ' ', ' ',
' ', 0x0c, ' ', 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, ' ', ' ', ' ', ' ',
};
*cp = SeparatorToSpace[*cp];
} else if (MOZ_UNLIKELY(*cp == kNextLine || *cp == kParagraphSeparator)) {
*cp = ' ';
}
}
}
nsresult nsBidiPresUtils::ResolveParagraph(BidiParagraphData* aBpd) {
if (aBpd->BufferLength() < 1) {
return NS_OK;
}
ReplaceSeparators(aBpd->mBuffer);
int32_t runCount;
nsresult rv = aBpd->SetPara();
NS_ENSURE_SUCCESS(rv, rv);
BidiEmbeddingLevel embeddingLevel = aBpd->GetParagraphEmbeddingLevel();
rv = aBpd->CountRuns(&runCount);
NS_ENSURE_SUCCESS(rv, rv);
int32_t runLength = 0; // the length of the current run of text
int32_t logicalLimit = 0; // the end of the current run + 1
int32_t numRun = -1;
int32_t fragmentLength = 0; // the length of the current text frame
int32_t frameIndex = -1; // index to the frames in mLogicalFrames
int32_t frameCount = aBpd->FrameCount();
int32_t contentOffset = 0; // offset of current frame in its content node
bool isTextFrame = false;
nsIFrame* frame = nullptr;
BidiParagraphData::FrameInfo frameInfo;
nsIContent* content = nullptr;
int32_t contentTextLength = 0;
#ifdef DEBUG
# ifdef NOISY_BIDI
printf(
"Before Resolve(), mCurrentBlock=%p, mBuffer='%s', frameCount=%d, "
"runCount=%d\n",
(void*)aBpd->mCurrentBlock, NS_ConvertUTF16toUTF8(aBpd->mBuffer).get(),
frameCount, runCount);
# ifdef REALLY_NOISY_BIDI
printf(" block frame tree=:\n");
aBpd->mCurrentBlock->List(stdout);
# endif
# endif
#endif
if (runCount == 1 && frameCount == 1 &&
aBpd->GetParagraphDirection() == BidiEngine::ParagraphDirection::LTR &&
aBpd->GetParagraphEmbeddingLevel() == 0) {
// We have a single left-to-right frame in a left-to-right paragraph,
// without bidi isolation from the surrounding text.
// Make sure that the embedding level and base level frame properties aren't
// set (because if they are this frame used to have some other direction,
// so we can't do this optimization), and we're done.
nsIFrame* frame = aBpd->FrameAt(0);
if (frame != NS_BIDI_CONTROL_FRAME) {
FrameBidiData bidiData = frame->GetBidiData();
if (!bidiData.embeddingLevel && !bidiData.baseLevel) {
#ifdef DEBUG
# ifdef NOISY_BIDI
printf("early return for single direction frame %p\n", (void*)frame);
# endif
#endif
frame->AddStateBits(NS_FRAME_IS_BIDI);
return NS_OK;
}
}
}
BidiParagraphData::FrameInfo lastRealFrame;
BidiEmbeddingLevel lastEmbeddingLevel = kBidiLevelNone;
BidiEmbeddingLevel precedingControl = kBidiLevelNone;
auto storeBidiDataToFrame = [&]() {
FrameBidiData bidiData;
bidiData.embeddingLevel = embeddingLevel;
bidiData.baseLevel = aBpd->GetParagraphEmbeddingLevel();
// If a control character doesn't have a lower embedding level than
// both the preceding and the following frame, it isn't something
// needed for getting the correct result. This optimization should
// remove almost all of embeds and overrides, and some of isolates.
if (precedingControl >= embeddingLevel ||
precedingControl >= lastEmbeddingLevel) {
bidiData.precedingControl = kBidiLevelNone;
} else {
bidiData.precedingControl = precedingControl;
}
precedingControl = kBidiLevelNone;
lastEmbeddingLevel = embeddingLevel;
frame->SetProperty(nsIFrame::BidiDataProperty(), bidiData);
};
for (;;) {
if (fragmentLength <= 0) {
// Get the next frame from mLogicalFrames
if (++frameIndex >= frameCount) {
break;
}
frameInfo = aBpd->FrameInfoAt(frameIndex);
frame = frameInfo.mFrame;
if (frame == NS_BIDI_CONTROL_FRAME || !frame->IsTextFrame()) {
/*
* Any non-text frame corresponds to a single character in the text
* buffer (a bidi control character, LINE SEPARATOR, or OBJECT
* SUBSTITUTE)
*/
isTextFrame = false;
fragmentLength = 1;
} else {
aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(frameInfo);
content = frame->GetContent();
if (!content) {
rv = NS_OK;
break;
}
contentTextLength = content->TextLength();
auto [start, end] = frame->GetOffsets();
NS_ASSERTION(!(contentTextLength < end - start),
"Frame offsets don't fit in content");
fragmentLength = std::min(contentTextLength, end - start);
contentOffset = start;
isTextFrame = true;
}
} // if (fragmentLength <= 0)
if (runLength <= 0) {
// Get the next run of text from the Bidi engine
if (++numRun >= runCount) {
// We've run out of runs of text; but don't forget to store bidi data
// to the frame before breaking out of the loop (bug 1426042).
if (frame != NS_BIDI_CONTROL_FRAME) {
storeBidiDataToFrame();
if (isTextFrame) {
frame->AdjustOffsetsForBidi(contentOffset,
contentOffset + fragmentLength);
}
}
break;
}
int32_t lineOffset = logicalLimit;
aBpd->GetLogicalRun(lineOffset, &logicalLimit, &embeddingLevel);
runLength = logicalLimit - lineOffset;
} // if (runLength <= 0)
if (frame == NS_BIDI_CONTROL_FRAME) {
// In theory, we only need to do this for isolates. However, it is
// easier to do this for all here because we do not maintain the
// index to get corresponding character from buffer. Since we do
// have proper embedding level for all those characters, including
// them wouldn't affect the final result.
precedingControl = std::min(precedingControl, embeddingLevel);
} else {
storeBidiDataToFrame();
if (isTextFrame) {
if (contentTextLength == 0) {
// Set the base level and embedding level of the current run even
// on an empty frame. Otherwise frame reordering will not be correct.
frame->AdjustOffsetsForBidi(0, 0);
// Nothing more to do for an empty frame, except update
// lastRealFrame like we do below.
lastRealFrame = frameInfo;
continue;
}
nsLineList::iterator currentLine = aBpd->mCurrentResolveLine.GetLine();
if ((runLength > 0) && (runLength < fragmentLength)) {
/*
* The text in this frame continues beyond the end of this directional
* run. Create a non-fluid continuation frame for the next directional
* run.
*/
currentLine->MarkDirty();
nsIFrame* nextBidi;
int32_t runEnd = contentOffset + runLength;
EnsureBidiContinuation(frame, currentLine, &nextBidi, contentOffset,
runEnd);
nextBidi->AdjustOffsetsForBidi(runEnd,
contentOffset + fragmentLength);
frame = nextBidi;
frameInfo.mFrame = frame;
contentOffset = runEnd;
aBpd->mCurrentResolveLine.AdvanceToFrame(frame);
} // if (runLength < fragmentLength)
else {
if (contentOffset + fragmentLength == contentTextLength) {
/*
* We have finished all the text in this content node. Convert any
* further non-fluid continuations to fluid continuations and
* advance frameIndex to the last frame in the content node
*/
int32_t newIndex = aBpd->GetLastFrameForContent(content);
if (newIndex > frameIndex) {
currentLine->MarkDirty();
RemoveBidiContinuation(aBpd, frame, frameIndex, newIndex);
frameIndex = newIndex;
frameInfo = aBpd->FrameInfoAt(frameIndex);
frame = frameInfo.mFrame;
}
} else if (fragmentLength > 0 && runLength > fragmentLength) {
/*
* There is more text that belongs to this directional run in the
* next text frame: make sure it is a fluid continuation of the
* current frame. Do not advance frameIndex, because the next frame
* may contain multi-directional text and need to be split
*/
int32_t newIndex = frameIndex;
do {
} while (++newIndex < frameCount &&
aBpd->FrameAt(newIndex) == NS_BIDI_CONTROL_FRAME);
if (newIndex < frameCount) {
currentLine->MarkDirty();
RemoveBidiContinuation(aBpd, frame, frameIndex, newIndex);
}
} else if (runLength == fragmentLength) {
/*
* If the directional run ends at the end of the frame, make sure
* that any continuation is non-fluid, and do the same up the
* parent chain
*/
nsIFrame* next = frame->GetNextInFlow();
if (next) {
currentLine->MarkDirty();
MakeContinuationsNonFluidUpParentChain(frame, next);
}
}
frame->AdjustOffsetsForBidi(contentOffset,
contentOffset + fragmentLength);
}
} // isTextFrame
} // not bidi control frame
int32_t temp = runLength;
runLength -= fragmentLength;
fragmentLength -= temp;
// Record last real frame so that we can do splitting properly even
// if a run ends after a virtual bidi control frame.
if (frame != NS_BIDI_CONTROL_FRAME) {
lastRealFrame = frameInfo;
}
if (lastRealFrame.mFrame && fragmentLength <= 0) {
// If the frame is at the end of a run, and this is not the end of our
// paragraph, split all ancestor inlines that need splitting.
// To determine whether we're at the end of the run, we check that we've
// finished processing the current run, and that the current frame
// doesn't have a fluid continuation (it could have a fluid continuation
// of zero length, so testing runLength alone is not sufficient).
if (runLength <= 0 && !lastRealFrame.mFrame->GetNextInFlow()) {
if (numRun + 1 < runCount) {
nsIFrame* child = lastRealFrame.mFrame;
nsContainerFrame* parent = child->GetParent();
// As long as we're on the last sibling, the parent doesn't have to
// be split.
// However, if the parent has a fluid continuation, we do have to make
// it non-fluid. This can happen e.g. when we have a first-letter
// frame and the end of the first-letter coincides with the end of a
// directional run.
while (parent && IsBidiSplittable(parent) &&
!child->GetNextSibling()) {
nsIFrame* next = parent->GetNextInFlow();
if (next) {
parent->SetNextContinuation(next);
next->SetPrevContinuation(parent);
}
child = parent;
parent = child->GetParent();
}
if (parent && IsBidiSplittable(parent)) {
aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(lastRealFrame);
SplitInlineAncestors(parent, aBpd->mCurrentResolveLine.GetLine(),
child);
aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(lastRealFrame);
}
}
} else if (frame != NS_BIDI_CONTROL_FRAME) {
// We're not at an end of a run. If |frame| is the last child of its
// parent, and its ancestors happen to have bidi continuations, convert
// them into fluid continuations.
JoinInlineAncestors(frame);
}
}
} // for
#ifdef DEBUG
# ifdef REALLY_NOISY_BIDI
printf("---\nAfter Resolve(), frameTree =:\n");
aBpd->mCurrentBlock->List(stdout);
printf("===\n");
# endif
#endif
return rv;
}
void nsBidiPresUtils::TraverseFrames(nsIFrame* aCurrentFrame,
BidiParagraphData* aBpd) {
if (!aCurrentFrame) return;
#ifdef DEBUG
nsBlockFrame* initialLineContainer =
aBpd->mCurrentTraverseLine.mLineIterator.GetContainer();
#endif
nsIFrame* childFrame = aCurrentFrame;
do {
/*
* It's important to get the next sibling and next continuation *before*
* handling the frame: If we encounter a forced paragraph break and call
* ResolveParagraph within this loop, doing GetNextSibling and
* GetNextContinuation after that could return a bidi continuation that had
* just been split from the original childFrame and we would process it
* twice.
*/
nsIFrame* nextSibling = childFrame->GetNextSibling();
// If the real frame for a placeholder is a first letter frame, we need to
// drill down into it and include its contents in Bidi resolution.
// If not, we just use the placeholder.
nsIFrame* frame = childFrame;
if (childFrame->IsPlaceholderFrame()) {
nsIFrame* realFrame =
nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame);
if (realFrame->IsLetterFrame()) {
frame = realFrame;
}
}
auto DifferentBidiValues = [](ComputedStyle* aSC1, nsIFrame* aFrame2) {
ComputedStyle* sc2 = aFrame2->Style();
return GetBidiControl(aSC1) != GetBidiControl(sc2) ||
GetBidiOverride(aSC1) != GetBidiOverride(sc2);
};
ComputedStyle* sc = frame->Style();
nsIFrame* nextContinuation = frame->GetNextContinuation();
nsIFrame* prevContinuation = frame->GetPrevContinuation();
bool isLastFrame =
!nextContinuation || DifferentBidiValues(sc, nextContinuation);
bool isFirstFrame =
!prevContinuation || DifferentBidiValues(sc, prevContinuation);
char16_t controlChar = 0;
char16_t overrideChar = 0;
LayoutFrameType frameType = frame->Type();
if (frame->IsBidiInlineContainer() || RubyUtils::IsRubyBox(frameType)) {
if (!frame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
nsContainerFrame* c = static_cast<nsContainerFrame*>(frame);
MOZ_ASSERT(c == do_QueryFrame(frame),
"eBidiInlineContainer and ruby frame must be"
" a nsContainerFrame subclass");
c->DrainSelfOverflowList();
}
controlChar = GetBidiControl(sc);
overrideChar = GetBidiOverride(sc);
// Add dummy frame pointers representing bidi control codes before
// the first frames of elements specifying override, isolation, or
// plaintext.
if (isFirstFrame) {
if (controlChar != 0) {
aBpd->PushBidiControl(controlChar);
}
if (overrideChar != 0) {
aBpd->PushBidiControl(overrideChar);
}
}
}
if (IsBidiLeaf(frame)) {
/* Bidi leaf frame: add the frame to the mLogicalFrames array,
* and add its index to the mContentToFrameIndex hashtable. This
* will be used in RemoveBidiContinuation() to identify the last
* frame in the array with a given content.
*/
nsIContent* content = frame->GetContent();
aBpd->AppendFrame(frame, aBpd->mCurrentTraverseLine, content);
// Append the content of the frame to the paragraph buffer
if (LayoutFrameType::Text == frameType) {
if (content != aBpd->mPrevContent) {
aBpd->mPrevContent = content;
if (!frame->StyleText()->NewlineIsSignificant(
static_cast<nsTextFrame*>(frame))) {
content->GetAsText()->AppendTextTo(aBpd->mBuffer);
} else {
/*
* For preformatted text we have to do bidi resolution on each line
* separately.
*/
nsAutoString text;
content->GetAsText()->AppendTextTo(text);
nsIFrame* next;
do {
next = nullptr;
auto [start, end] = frame->GetOffsets();
int32_t endLine = text.FindChar('\n', start);
if (endLine == -1) {
/*
* If there is no newline in the text content, just save the
* text from this frame and its continuations, and do bidi
* resolution later
*/
aBpd->AppendString(Substring(text, start));
while (frame && nextSibling) {
aBpd->AdvanceAndAppendFrame(
&frame, aBpd->mCurrentTraverseLine, &nextSibling);
}
break;
}
/*
* If there is a newline in the frame, break the frame after the
* newline, do bidi resolution and repeat until the last sibling
*/
++endLine;
/*
* If the frame ends before the new line, save the text and move
* into the next continuation
*/
aBpd->AppendString(
Substring(text, start, std::min(end, endLine) - start));
while (end < endLine && nextSibling) {
aBpd->AdvanceAndAppendFrame(&frame, aBpd->mCurrentTraverseLine,
&nextSibling);
NS_ASSERTION(frame, "Premature end of continuation chain");
std::tie(start, end) = frame->GetOffsets();
aBpd->AppendString(
Substring(text, start, std::min(end, endLine) - start));
}
if (end < endLine) {
aBpd->mPrevContent = nullptr;
break;
}
bool createdContinuation = false;
if (uint32_t(endLine) < text.Length()) {
/*
* Timing is everything here: if the frame already has a bidi
* continuation, we need to make the continuation fluid *before*
* resetting the length of the current frame. Otherwise
* nsTextFrame::SetLength won't set the continuation frame's
* text offsets correctly.
*
* On the other hand, if the frame doesn't have a continuation,
* we need to create one *after* resetting the length, or
* CreateContinuingFrame will complain that there is no more
* content for the continuation.
*/
next = frame->GetNextInFlow();
if (!next) {
// If the frame already has a bidi continuation, make it fluid
next = frame->GetNextContinuation();
if (next) {
MakeContinuationFluid(frame, next);
JoinInlineAncestors(frame);
}
}
nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
textFrame->SetLength(endLine - start, nullptr);
// If it weren't for CreateContinuation needing this to
// be current, we could restructure the marking dirty
// below to use mCurrentResolveLine and eliminate
// mCurrentTraverseLine entirely.
aBpd->mCurrentTraverseLine.AdvanceToFrame(frame);
if (!next) {
// If the frame has no next in flow, create one.
CreateContinuation(
frame, aBpd->mCurrentTraverseLine.GetLine(), &next, true);
createdContinuation = true;
}
// Mark the line before the newline as dirty.
aBpd->mCurrentTraverseLine.GetLine()->MarkDirty();
}
ResolveParagraphWithinBlock(aBpd);
if (!nextSibling && !createdContinuation) {
break;
}
if (next) {
frame = next;
aBpd->AppendFrame(frame, aBpd->mCurrentTraverseLine);
// Mark the line after the newline as dirty.
aBpd->mCurrentTraverseLine.AdvanceToFrame(frame);
aBpd->mCurrentTraverseLine.GetLine()->MarkDirty();
}
/*
* If we have already overshot the saved next-sibling while
* scanning the frame's continuations, advance it.
*/
if (frame && frame == nextSibling) {
nextSibling = frame->GetNextSibling();
}
} while (next);
}
}
} else if (LayoutFrameType::Br == frameType) {
// break frame -- append line separator
aBpd->AppendUnichar(kLineSeparator);
ResolveParagraphWithinBlock(aBpd);
} else {
// other frame type -- see the Unicode Bidi Algorithm:
// "...inline objects (such as graphics) are treated as if they are ...
// U+FFFC"
// <wbr>, however, is treated as U+200B ZERO WIDTH SPACE. See
aBpd->AppendUnichar(
content->IsHTMLElement(nsGkAtoms::wbr) ? kZWSP : kObjectSubstitute);
if (!frame->IsInlineOutside()) {
// if it is not inline, end the paragraph
ResolveParagraphWithinBlock(aBpd);
}
}
} else {
// For a non-leaf frame, recurse into TraverseFrames
nsIFrame* kid = frame->PrincipalChildList().FirstChild();
MOZ_ASSERT(!frame->GetChildList(FrameChildListID::Overflow).FirstChild(),
"should have drained the overflow list above");
if (kid) {
TraverseFrames(kid, aBpd);
}
}
// If the element is attributed by dir, indicate direction pop (add PDF
// frame)
if (isLastFrame) {
// Add a dummy frame pointer representing a bidi control code after the
// last frame of an element specifying embedding or override
if (overrideChar != 0) {
aBpd->PopBidiControl(overrideChar);
}
if (controlChar != 0) {
aBpd->PopBidiControl(controlChar);
}
}
childFrame = nextSibling;
} while (childFrame);
MOZ_ASSERT(initialLineContainer ==
aBpd->mCurrentTraverseLine.mLineIterator.GetContainer());
}
bool nsBidiPresUtils::ChildListMayRequireBidi(nsIFrame* aFirstChild,
nsIContent** aCurrContent) {
MOZ_ASSERT(!aFirstChild || !aFirstChild->GetPrevSibling(),
"Expecting to traverse from the start of a child list");
for (nsIFrame* childFrame = aFirstChild; childFrame;
childFrame = childFrame->GetNextSibling()) {
nsIFrame* frame = childFrame;
// If the real frame for a placeholder is a first-letter frame, we need to
// consider its contents for potential Bidi resolution.
if (childFrame->IsPlaceholderFrame()) {
nsIFrame* realFrame =
nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame);
if (realFrame->IsLetterFrame()) {
frame = realFrame;
}
}
// If unicode-bidi properties are present, we should do bidi resolution.
ComputedStyle* sc = frame->Style();
if (GetBidiControl(sc) || GetBidiOverride(sc)) {
return true;