tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsBidiPresUtils.cpp (95887B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "nsBidiPresUtils.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "RubyUtils.h"
     12 #include "gfxContext.h"
     13 #include "mozilla/Casting.h"
     14 #include "mozilla/IntegerRange.h"
     15 #include "mozilla/Maybe.h"
     16 #include "mozilla/PresShell.h"
     17 #include "mozilla/dom/Text.h"
     18 #include "mozilla/intl/Bidi.h"
     19 #include "nsBidiUtils.h"
     20 #include "nsBlockFrame.h"
     21 #include "nsCSSFrameConstructor.h"
     22 #include "nsContainerFrame.h"
     23 #include "nsFirstLetterFrame.h"
     24 #include "nsFontMetrics.h"
     25 #include "nsGkAtoms.h"
     26 #include "nsIFrameInlines.h"
     27 #include "nsInlineFrame.h"
     28 #include "nsPlaceholderFrame.h"
     29 #include "nsPointerHashKeys.h"
     30 #include "nsPresContext.h"
     31 #include "nsRubyBaseContainerFrame.h"
     32 #include "nsRubyBaseFrame.h"
     33 #include "nsRubyFrame.h"
     34 #include "nsRubyTextContainerFrame.h"
     35 #include "nsRubyTextFrame.h"
     36 #include "nsStyleStructInlines.h"
     37 #include "nsTextFrame.h"
     38 #include "nsUnicodeProperties.h"
     39 
     40 #undef NOISY_BIDI
     41 #undef REALLY_NOISY_BIDI
     42 
     43 using namespace mozilla;
     44 
     45 using BidiEngine = intl::Bidi;
     46 using BidiClass = intl::BidiClass;
     47 using BidiDirection = intl::BidiDirection;
     48 using BidiEmbeddingLevel = intl::BidiEmbeddingLevel;
     49 
     50 static const char16_t kNextLine = 0x0085;
     51 static const char16_t kZWSP = 0x200B;
     52 static const char16_t kLineSeparator = 0x2028;
     53 static const char16_t kParagraphSeparator = 0x2029;
     54 static const char16_t kObjectSubstitute = 0xFFFC;
     55 static const char16_t kLRE = 0x202A;
     56 static const char16_t kRLE = 0x202B;
     57 static const char16_t kLRO = 0x202D;
     58 static const char16_t kRLO = 0x202E;
     59 static const char16_t kPDF = 0x202C;
     60 static const char16_t kLRI = 0x2066;
     61 static const char16_t kRLI = 0x2067;
     62 static const char16_t kFSI = 0x2068;
     63 static const char16_t kPDI = 0x2069;
     64 // All characters with Bidi type Segment Separator or Block Separator.
     65 // This should be kept in sync with the table in ReplaceSeparators.
     66 static const char16_t kSeparators[] = {
     67    char16_t('\t'), char16_t('\r'),      char16_t('\n'), char16_t(0xb),
     68    char16_t(0x1c), char16_t(0x1d),      char16_t(0x1e), char16_t(0x1f),
     69    kNextLine,      kParagraphSeparator, char16_t(0)};
     70 
     71 #define NS_BIDI_CONTROL_FRAME ((nsIFrame*)0xfffb1d1)
     72 
     73 // This exists just to be a type; the value doesn't matter.
     74 enum class BidiControlFrameType { Value };
     75 
     76 static bool IsIsolateControl(char16_t aChar) {
     77  return aChar == kLRI || aChar == kRLI || aChar == kFSI;
     78 }
     79 
     80 // Given a ComputedStyle, return any bidi control character necessary to
     81 // implement style properties that override directionality (i.e. if it has
     82 // unicode-bidi:bidi-override, or text-orientation:upright in vertical
     83 // writing mode) when applying the bidi algorithm.
     84 //
     85 // Returns 0 if no override control character is implied by this style.
     86 static char16_t GetBidiOverride(ComputedStyle* aComputedStyle) {
     87  const nsStyleVisibility* vis = aComputedStyle->StyleVisibility();
     88  if ((vis->mWritingMode == StyleWritingModeProperty::VerticalRl ||
     89       vis->mWritingMode == StyleWritingModeProperty::VerticalLr) &&
     90      vis->mTextOrientation == StyleTextOrientation::Upright) {
     91    return kLRO;
     92  }
     93  const nsStyleTextReset* text = aComputedStyle->StyleTextReset();
     94  if (text->mUnicodeBidi == StyleUnicodeBidi::BidiOverride ||
     95      text->mUnicodeBidi == StyleUnicodeBidi::IsolateOverride) {
     96    return StyleDirection::Rtl == vis->mDirection ? kRLO : kLRO;
     97  }
     98  return 0;
     99 }
    100 
    101 // Given a ComputedStyle, return any bidi control character necessary to
    102 // implement style properties that affect bidi resolution (i.e. if it
    103 // has unicode-bidiembed, isolate, or plaintext) when applying the bidi
    104 // algorithm.
    105 //
    106 // Returns 0 if no control character is implied by the style.
    107 //
    108 // Note that GetBidiOverride and GetBidiControl need to be separate
    109 // because in the case of unicode-bidi:isolate-override we need both
    110 // FSI and LRO/RLO.
    111 static char16_t GetBidiControl(ComputedStyle* aComputedStyle) {
    112  const nsStyleVisibility* vis = aComputedStyle->StyleVisibility();
    113  const nsStyleTextReset* text = aComputedStyle->StyleTextReset();
    114  switch (text->mUnicodeBidi) {
    115    case StyleUnicodeBidi::Embed:
    116      return StyleDirection::Rtl == vis->mDirection ? kRLE : kLRE;
    117    case StyleUnicodeBidi::Isolate:
    118      // <bdi> element already has its directionality set from content so
    119      // we never need to return kFSI.
    120      return StyleDirection::Rtl == vis->mDirection ? kRLI : kLRI;
    121    case StyleUnicodeBidi::IsolateOverride:
    122    case StyleUnicodeBidi::Plaintext:
    123      return kFSI;
    124    case StyleUnicodeBidi::Normal:
    125    case StyleUnicodeBidi::BidiOverride:
    126      break;
    127  }
    128 
    129  return 0;
    130 }
    131 
    132 #ifdef DEBUG
    133 static inline bool AreContinuationsInOrder(nsIFrame* aFrame1,
    134                                           nsIFrame* aFrame2) {
    135  nsIFrame* f = aFrame1;
    136  do {
    137    f = f->GetNextContinuation();
    138  } while (f && f != aFrame2);
    139  return !!f;
    140 }
    141 #endif
    142 
    143 struct MOZ_STACK_CLASS BidiParagraphData {
    144  struct FrameInfo {
    145    FrameInfo(nsIFrame* aFrame, nsBlockInFlowLineIterator& aLineIter)
    146        : mFrame(aFrame),
    147          mBlockContainer(aLineIter.GetContainer()),
    148          mInOverflow(aLineIter.GetInOverflow()) {}
    149 
    150    explicit FrameInfo(BidiControlFrameType aValue)
    151        : mFrame(NS_BIDI_CONTROL_FRAME),
    152          mBlockContainer(nullptr),
    153          mInOverflow(false) {}
    154 
    155    FrameInfo()
    156        : mFrame(nullptr), mBlockContainer(nullptr), mInOverflow(false) {}
    157 
    158    nsIFrame* mFrame;
    159 
    160    // The block containing mFrame (i.e., which continuation).
    161    nsBlockFrame* mBlockContainer;
    162 
    163    // true if mFrame is in mBlockContainer's overflow lines, false if
    164    // in primary lines
    165    bool mInOverflow;
    166  };
    167 
    168  nsAutoString mBuffer;
    169  AutoTArray<char16_t, 16> mEmbeddingStack;
    170  AutoTArray<FrameInfo, 16> mLogicalFrames;
    171  nsTHashMap<nsPtrHashKey<const nsIContent>, int32_t> mContentToFrameIndex;
    172  // Cached presentation context for the frames we're processing.
    173  nsPresContext* mPresContext;
    174  bool mIsVisual;
    175  bool mRequiresBidi;
    176  BidiEmbeddingLevel mParaLevel;
    177  nsIContent* mPrevContent;
    178 
    179  /**
    180   * This class is designed to manage the process of mapping a frame to
    181   * the line that it's in, when we know that (a) the frames we ask it
    182   * about are always in the block's lines and (b) each successive frame
    183   * we ask it about is the same as or after (in depth-first search
    184   * order) the previous.
    185   *
    186   * Since we move through the lines at a different pace in Traverse and
    187   * ResolveParagraph, we use one of these for each.
    188   *
    189   * The state of the mapping is also different between TraverseFrames
    190   * and ResolveParagraph since since resolving can call functions
    191   * (EnsureBidiContinuation or SplitInlineAncestors) that can create
    192   * new frames and thus break lines.
    193   *
    194   * The TraverseFrames iterator is only used in some edge cases.
    195   */
    196  struct FastLineIterator {
    197    FastLineIterator() : mPrevFrame(nullptr), mNextLineStart(nullptr) {}
    198 
    199    // These iterators *and* mPrevFrame track the line list that we're
    200    // iterating over.
    201    //
    202    // mPrevFrame, if non-null, should be either the frame we're currently
    203    // handling (in ResolveParagraph or TraverseFrames, depending on the
    204    // iterator) or a frame before it, and is also guaranteed to either be in
    205    // mCurrentLine or have been in mCurrentLine until recently.
    206    //
    207    // In case the splitting causes block frames to break lines, however, we
    208    // also track the first frame of the next line.  If that changes, it means
    209    // we've broken lines and we have to invalidate mPrevFrame.
    210    nsBlockInFlowLineIterator mLineIterator;
    211    nsIFrame* mPrevFrame;
    212    nsIFrame* mNextLineStart;
    213 
    214    nsLineList::iterator GetLine() { return mLineIterator.GetLine(); }
    215 
    216    static bool IsFrameInCurrentLine(nsBlockInFlowLineIterator* aLineIter,
    217                                     nsIFrame* aPrevFrame, nsIFrame* aFrame) {
    218      MOZ_ASSERT(!aPrevFrame || aLineIter->GetLine()->Contains(aPrevFrame),
    219                 "aPrevFrame must be in aLineIter's current line");
    220      nsIFrame* endFrame = aLineIter->IsLastLineInList()
    221                               ? nullptr
    222                               : aLineIter->GetLine().next()->mFirstChild;
    223      nsIFrame* startFrame =
    224          aPrevFrame ? aPrevFrame : aLineIter->GetLine()->mFirstChild;
    225      for (nsIFrame* frame = startFrame; frame && frame != endFrame;
    226           frame = frame->GetNextSibling()) {
    227        if (frame == aFrame) {
    228          return true;
    229        }
    230      }
    231      return false;
    232    }
    233 
    234    static nsIFrame* FirstChildOfNextLine(
    235        nsBlockInFlowLineIterator& aIterator) {
    236      const nsLineList::iterator line = aIterator.GetLine();
    237      const nsLineList::iterator lineEnd = aIterator.End();
    238      MOZ_ASSERT(line != lineEnd, "iterator should start off valid");
    239      const nsLineList::iterator nextLine = line.next();
    240 
    241      return nextLine != lineEnd ? nextLine->mFirstChild : nullptr;
    242    }
    243 
    244    // Advance line iterator to the line containing aFrame, assuming
    245    // that aFrame is already in the line list our iterator is iterating
    246    // over.
    247    void AdvanceToFrame(nsIFrame* aFrame) {
    248      if (mPrevFrame && FirstChildOfNextLine(mLineIterator) != mNextLineStart) {
    249        // Something has caused a line to split.  We need to invalidate
    250        // mPrevFrame since it may now be in a *later* line, though it may
    251        // still be in this line, so we need to start searching for it from
    252        // the start of this line.
    253        mPrevFrame = nullptr;
    254      }
    255      nsIFrame* child = aFrame;
    256      nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(child);
    257      while (parent && !parent->IsBlockFrameOrSubclass()) {
    258        child = parent;
    259        parent = nsLayoutUtils::GetParentOrPlaceholderFor(child);
    260      }
    261      MOZ_ASSERT(parent, "aFrame is not a descendent of a block frame");
    262      while (!IsFrameInCurrentLine(&mLineIterator, mPrevFrame, child)) {
    263 #ifdef DEBUG
    264        bool hasNext =
    265 #endif
    266            mLineIterator.Next();
    267        MOZ_ASSERT(hasNext, "Can't find frame in lines!");
    268        mPrevFrame = nullptr;
    269      }
    270      mPrevFrame = child;
    271      mNextLineStart = FirstChildOfNextLine(mLineIterator);
    272    }
    273 
    274    // Advance line iterator to the line containing aFrame, which may
    275    // require moving forward into overflow lines or into a later
    276    // continuation (or both).
    277    void AdvanceToLinesAndFrame(const FrameInfo& aFrameInfo) {
    278      if (mLineIterator.GetContainer() != aFrameInfo.mBlockContainer ||
    279          mLineIterator.GetInOverflow() != aFrameInfo.mInOverflow) {
    280        MOZ_ASSERT(
    281            mLineIterator.GetContainer() == aFrameInfo.mBlockContainer
    282                ? (!mLineIterator.GetInOverflow() && aFrameInfo.mInOverflow)
    283                : (!mLineIterator.GetContainer() ||
    284                   AreContinuationsInOrder(mLineIterator.GetContainer(),
    285                                           aFrameInfo.mBlockContainer)),
    286            "must move forwards");
    287        nsBlockFrame* block = aFrameInfo.mBlockContainer;
    288        nsLineList::iterator lines =
    289            aFrameInfo.mInOverflow ? block->GetOverflowLines()->mLines.begin()
    290                                   : block->LinesBegin();
    291        mLineIterator =
    292            nsBlockInFlowLineIterator(block, lines, aFrameInfo.mInOverflow);
    293        mPrevFrame = nullptr;
    294      }
    295      AdvanceToFrame(aFrameInfo.mFrame);
    296    }
    297  };
    298 
    299  FastLineIterator mCurrentTraverseLine, mCurrentResolveLine;
    300 
    301 #ifdef DEBUG
    302  // Only used for NOISY debug output.
    303  // Matches the current TraverseFrames state, not the ResolveParagraph
    304  // state.
    305  nsBlockFrame* mCurrentBlock;
    306 #endif
    307 
    308  explicit BidiParagraphData(nsBlockFrame* aBlockFrame)
    309      : mPresContext(aBlockFrame->PresContext()),
    310        mIsVisual(mPresContext->IsVisualMode()),
    311        mRequiresBidi(false),
    312        mParaLevel(nsBidiPresUtils::BidiLevelFromStyle(aBlockFrame->Style())),
    313        mPrevContent(nullptr)
    314 #ifdef DEBUG
    315        ,
    316        mCurrentBlock(aBlockFrame)
    317 #endif
    318  {
    319    if (mParaLevel > 0) {
    320      mRequiresBidi = true;
    321    }
    322 
    323    if (mIsVisual) {
    324      /**
    325       * Drill up in content to detect whether this is an element that needs to
    326       * be rendered with logical order even on visual pages.
    327       *
    328       * We always use logical order on form controls, firstly so that text
    329       * entry will be in logical order, but also because visual pages were
    330       * written with the assumption that even if the browser had no support
    331       * for right-to-left text rendering, it would use native widgets with
    332       * bidi support to display form controls.
    333       *
    334       * We also use logical order in XUL elements, since we expect that if a
    335       * XUL element appears in a visual page, it will be generated by an XBL
    336       * binding and contain localized text which will be in logical order.
    337       */
    338      for (nsIContent* content = aBlockFrame->GetContent(); content;
    339           content = content->GetParent()) {
    340        if (content->IsXULElement() || content->IsHTMLFormControlElement()) {
    341          mIsVisual = false;
    342          break;
    343        }
    344      }
    345    }
    346  }
    347 
    348  nsresult SetPara() {
    349    if (mPresContext->BidiEngine().SetParagraph(mBuffer, mParaLevel).isErr()) {
    350      return NS_ERROR_FAILURE;
    351    };
    352    return NS_OK;
    353  }
    354 
    355  /**
    356   * mParaLevel can be BidiDirection::LTR as well as
    357   * BidiDirection::LTR or BidiDirection::RTL.
    358   * GetParagraphEmbeddingLevel() returns the actual (resolved) paragraph level
    359   * which is always either BidiDirection::LTR or
    360   * BidiDirection::RTL
    361   */
    362  BidiEmbeddingLevel GetParagraphEmbeddingLevel() {
    363    BidiEmbeddingLevel paraLevel = mParaLevel;
    364    if (paraLevel == BidiEmbeddingLevel::DefaultLTR() ||
    365        paraLevel == BidiEmbeddingLevel::DefaultRTL()) {
    366      paraLevel = mPresContext->BidiEngine().GetParagraphEmbeddingLevel();
    367    }
    368    return paraLevel;
    369  }
    370 
    371  BidiEngine::ParagraphDirection GetParagraphDirection() {
    372    return mPresContext->BidiEngine().GetParagraphDirection();
    373  }
    374 
    375  nsresult CountRuns(int32_t* runCount) {
    376    auto result = mPresContext->BidiEngine().CountRuns();
    377    if (result.isErr()) {
    378      return NS_ERROR_FAILURE;
    379    }
    380    *runCount = result.unwrap();
    381    return NS_OK;
    382  }
    383 
    384  void GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit,
    385                     BidiEmbeddingLevel* aLevel) {
    386    mPresContext->BidiEngine().GetLogicalRun(aLogicalStart, aLogicalLimit,
    387                                             aLevel);
    388    if (mIsVisual) {
    389      *aLevel = GetParagraphEmbeddingLevel();
    390    }
    391  }
    392 
    393  void ResetData() {
    394    mLogicalFrames.Clear();
    395    mContentToFrameIndex.Clear();
    396    mBuffer.SetLength(0);
    397    mPrevContent = nullptr;
    398    for (uint32_t i = 0; i < mEmbeddingStack.Length(); ++i) {
    399      mBuffer.Append(mEmbeddingStack[i]);
    400      mLogicalFrames.AppendElement(FrameInfo(BidiControlFrameType::Value));
    401    }
    402  }
    403 
    404  void AppendFrame(nsIFrame* aFrame, FastLineIterator& aLineIter,
    405                   nsIContent* aContent = nullptr) {
    406    if (aContent) {
    407      mContentToFrameIndex.InsertOrUpdate(aContent, FrameCount());
    408    }
    409 
    410    // We don't actually need to advance aLineIter to aFrame, since all we use
    411    // from it is the block and is-overflow state, which are correct already.
    412    mLogicalFrames.AppendElement(FrameInfo(aFrame, aLineIter.mLineIterator));
    413  }
    414 
    415  void AdvanceAndAppendFrame(nsIFrame** aFrame, FastLineIterator& aLineIter,
    416                             nsIFrame** aNextSibling) {
    417    nsIFrame* frame = *aFrame;
    418    nsIFrame* nextSibling = *aNextSibling;
    419 
    420    frame = frame->GetNextContinuation();
    421    if (frame) {
    422      AppendFrame(frame, aLineIter, nullptr);
    423 
    424      /*
    425       * If we have already overshot the saved next-sibling while
    426       * scanning the frame's continuations, advance it.
    427       */
    428      if (frame == nextSibling) {
    429        nextSibling = frame->GetNextSibling();
    430      }
    431    }
    432 
    433    *aFrame = frame;
    434    *aNextSibling = nextSibling;
    435  }
    436 
    437  int32_t GetLastFrameForContent(nsIContent* aContent) {
    438    return mContentToFrameIndex.Get(aContent);
    439  }
    440 
    441  int32_t FrameCount() { return mLogicalFrames.Length(); }
    442 
    443  int32_t BufferLength() { return mBuffer.Length(); }
    444 
    445  nsIFrame* FrameAt(int32_t aIndex) { return mLogicalFrames[aIndex].mFrame; }
    446 
    447  const FrameInfo& FrameInfoAt(int32_t aIndex) {
    448    return mLogicalFrames[aIndex];
    449  }
    450 
    451  void AppendUnichar(char16_t aCh) { mBuffer.Append(aCh); }
    452 
    453  void AppendString(const nsDependentSubstring& aString) {
    454    mBuffer.Append(aString);
    455  }
    456 
    457  void AppendControlChar(char16_t aCh) {
    458    mLogicalFrames.AppendElement(FrameInfo(BidiControlFrameType::Value));
    459    AppendUnichar(aCh);
    460  }
    461 
    462  void PushBidiControl(char16_t aCh) {
    463    AppendControlChar(aCh);
    464    mEmbeddingStack.AppendElement(aCh);
    465  }
    466 
    467  void AppendPopChar(char16_t aCh) {
    468    AppendControlChar(IsIsolateControl(aCh) ? kPDI : kPDF);
    469  }
    470 
    471  void PopBidiControl(char16_t aCh) {
    472    MOZ_ASSERT(mEmbeddingStack.Length(), "embedding/override underflow");
    473    MOZ_ASSERT(aCh == mEmbeddingStack.LastElement());
    474    AppendPopChar(aCh);
    475    mEmbeddingStack.RemoveLastElement();
    476  }
    477 
    478  void ClearBidiControls() {
    479    for (char16_t c : Reversed(mEmbeddingStack)) {
    480      AppendPopChar(c);
    481    }
    482  }
    483 };
    484 
    485 class MOZ_STACK_CLASS BidiLineData {
    486 public:
    487  BidiLineData(nsIFrame* aFirstFrameOnLine, int32_t aNumFramesOnLine) {
    488    // Initialize the logically-ordered array of frames using the top-level
    489    // frames of a single line
    490    auto appendFrame = [&](nsIFrame* frame, BidiEmbeddingLevel level) {
    491      mLogicalFrames.AppendElement(frame);
    492      mLevels.AppendElement(level);
    493      mIndexMap.AppendElement(0);
    494    };
    495 
    496    for (nsIFrame* frame = aFirstFrameOnLine; frame && aNumFramesOnLine--;
    497         frame = frame->GetNextSibling()) {
    498      FrameBidiData bidiData = nsBidiPresUtils::GetFrameBidiData(frame);
    499      if (bidiData.precedingControl != kBidiLevelNone) {
    500        appendFrame(NS_BIDI_CONTROL_FRAME, bidiData.precedingControl);
    501      }
    502      appendFrame(frame, bidiData.embeddingLevel);
    503    }
    504 
    505    // Reorder the line
    506    BidiEngine::ReorderVisual(mLevels.Elements(), mLevels.Length(),
    507                              mIndexMap.Elements());
    508 
    509    // Collect the frames in visual order, omitting virtual controls
    510    // and noting whether frames are reordered.
    511    for (uint32_t i = 0; i < mIndexMap.Length(); i++) {
    512      nsIFrame* frame = mLogicalFrames[mIndexMap[i]];
    513      if (frame == NS_BIDI_CONTROL_FRAME) {
    514        continue;
    515      }
    516      mVisualFrameIndex.AppendElement(mIndexMap[i]);
    517      if (int32_t(i) != mIndexMap[i]) {
    518        mIsReordered = true;
    519      }
    520    }
    521  }
    522 
    523  uint32_t LogicalFrameCount() const { return mLogicalFrames.Length(); }
    524  uint32_t VisualFrameCount() const { return mVisualFrameIndex.Length(); }
    525 
    526  nsIFrame* LogicalFrameAt(uint32_t aIndex) const {
    527    return mLogicalFrames[aIndex];
    528  }
    529 
    530  nsIFrame* VisualFrameAt(uint32_t aIndex) const {
    531    return mLogicalFrames[mVisualFrameIndex[aIndex]];
    532  }
    533 
    534  std::pair<nsIFrame*, BidiEmbeddingLevel> VisualFrameAndLevelAt(
    535      uint32_t aIndex) const {
    536    int32_t index = mVisualFrameIndex[aIndex];
    537    return std::pair(mLogicalFrames[index], mLevels[index]);
    538  }
    539 
    540  bool IsReordered() const { return mIsReordered; }
    541 
    542  void InitContinuationStates(nsContinuationStates* aContinuationStates) const {
    543    for (auto* frame : mLogicalFrames) {
    544      if (frame != NS_BIDI_CONTROL_FRAME) {
    545        nsBidiPresUtils::InitContinuationStates(frame, aContinuationStates);
    546      }
    547    }
    548  }
    549 
    550 private:
    551  AutoTArray<nsIFrame*, 16> mLogicalFrames;
    552  AutoTArray<int32_t, 16> mVisualFrameIndex;
    553  AutoTArray<int32_t, 16> mIndexMap;
    554  AutoTArray<BidiEmbeddingLevel, 16> mLevels;
    555  bool mIsReordered = false;
    556 };
    557 
    558 #ifdef DEBUG
    559 extern "C" {
    560 void MOZ_EXPORT DumpBidiLine(BidiLineData* aData, bool aVisualOrder) {
    561  auto dump = [](nsIFrame* frame) {
    562    if (frame == NS_BIDI_CONTROL_FRAME) {
    563      fprintf_stderr(stderr, "(Bidi control frame)\n");
    564    } else {
    565      frame->List();
    566    }
    567  };
    568 
    569  if (aVisualOrder) {
    570    for (uint32_t i = 0; i < aData->VisualFrameCount(); i++) {
    571      dump(aData->VisualFrameAt(i));
    572    }
    573  } else {
    574    for (uint32_t i = 0; i < aData->LogicalFrameCount(); i++) {
    575      dump(aData->LogicalFrameAt(i));
    576    }
    577  }
    578 }
    579 }
    580 #endif
    581 
    582 /* Some helper methods for Resolve() */
    583 
    584 // Should this frame be split between text runs?
    585 static bool IsBidiSplittable(nsIFrame* aFrame) {
    586  MOZ_ASSERT(aFrame);
    587  // Bidi inline containers should be split, unless they're line frames.
    588  LayoutFrameType frameType = aFrame->Type();
    589  return (aFrame->IsBidiInlineContainer() &&
    590          frameType != LayoutFrameType::Line) ||
    591         frameType == LayoutFrameType::Text;
    592 }
    593 
    594 // Should this frame be treated as a leaf (e.g. when building mLogicalFrames)?
    595 static bool IsBidiLeaf(const nsIFrame* aFrame) {
    596  nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
    597  if (kid) {
    598    if (aFrame->IsBidiInlineContainer() ||
    599        RubyUtils::IsRubyBox(aFrame->Type())) {
    600      return false;
    601    }
    602  }
    603  return true;
    604 }
    605 
    606 /**
    607 * Create non-fluid continuations for the ancestors of a given frame all the way
    608 * up the frame tree until we hit a non-splittable frame (a line or a block).
    609 *
    610 * @param aParent the first parent frame to be split
    611 * @param aFrame the child frames after this frame are reparented to the
    612 *        newly-created continuation of aParent.
    613 *        If aFrame is null, all the children of aParent are reparented.
    614 */
    615 static void SplitInlineAncestors(nsContainerFrame* aParent,
    616                                 nsLineList::iterator aLine, nsIFrame* aFrame) {
    617  PresShell* presShell = aParent->PresShell();
    618  nsIFrame* frame = aFrame;
    619  nsContainerFrame* parent = aParent;
    620  nsContainerFrame* newParent;
    621 
    622  while (IsBidiSplittable(parent)) {
    623    nsContainerFrame* grandparent = parent->GetParent();
    624    NS_ASSERTION(grandparent,
    625                 "Couldn't get parent's parent in "
    626                 "nsBidiPresUtils::SplitInlineAncestors");
    627 
    628    // Split the child list after |frame|, unless it is the last child.
    629    if (!frame || frame->GetNextSibling()) {
    630      newParent = static_cast<nsContainerFrame*>(
    631          presShell->FrameConstructor()->CreateContinuingFrame(
    632              parent, grandparent, false));
    633 
    634      nsFrameList tail = parent->StealFramesAfter(frame);
    635 
    636      // The parent's continuation adopts the siblings after the split.
    637      MOZ_ASSERT(!newParent->IsBlockFrameOrSubclass(),
    638                 "blocks should not be IsBidiSplittable");
    639      newParent->InsertFrames(FrameChildListID::NoReflowPrincipal, nullptr,
    640                              nullptr, std::move(tail));
    641 
    642      // While passing &aLine to InsertFrames for a non-block isn't harmful
    643      // because it's a no-op, it doesn't really make sense.  However, the
    644      // MOZ_ASSERT() we need to guarantee that it's safe only works if the
    645      // parent is actually the block.
    646      const nsLineList::iterator* parentLine;
    647      if (grandparent->IsBlockFrameOrSubclass()) {
    648        MOZ_ASSERT(aLine->Contains(parent));
    649        parentLine = &aLine;
    650      } else {
    651        parentLine = nullptr;
    652      }
    653 
    654      // The list name FrameChildListID::NoReflowPrincipal would indicate we
    655      // don't want reflow
    656      grandparent->InsertFrames(FrameChildListID::NoReflowPrincipal, parent,
    657                                parentLine, nsFrameList(newParent, newParent));
    658    }
    659 
    660    frame = parent;
    661    parent = grandparent;
    662  }
    663 }
    664 
    665 static void MakeContinuationFluid(nsIFrame* aFrame, nsIFrame* aNext) {
    666  NS_ASSERTION(!aFrame->GetNextInFlow() || aFrame->GetNextInFlow() == aNext,
    667               "next-in-flow is not next continuation!");
    668  aFrame->SetNextInFlow(aNext);
    669 
    670  NS_ASSERTION(!aNext->GetPrevInFlow() || aNext->GetPrevInFlow() == aFrame,
    671               "prev-in-flow is not prev continuation!");
    672  aNext->SetPrevInFlow(aFrame);
    673 }
    674 
    675 static void MakeContinuationsNonFluidUpParentChain(nsIFrame* aFrame,
    676                                                   nsIFrame* aNext) {
    677  nsIFrame* frame;
    678  nsIFrame* next;
    679 
    680  for (frame = aFrame, next = aNext;
    681       frame && next && next != frame && next == frame->GetNextInFlow() &&
    682       IsBidiSplittable(frame);
    683       frame = frame->GetParent(), next = next->GetParent()) {
    684    frame->SetNextContinuation(next);
    685    next->SetPrevContinuation(frame);
    686  }
    687 }
    688 
    689 // If aFrame is the last child of its parent, convert bidi continuations to
    690 // fluid continuations for all of its inline ancestors.
    691 // If it isn't the last child, make sure that its continuation is fluid.
    692 static void JoinInlineAncestors(nsIFrame* aFrame) {
    693  nsIFrame* frame = aFrame;
    694  while (frame && IsBidiSplittable(frame)) {
    695    nsIFrame* next = frame->GetNextContinuation();
    696    if (next) {
    697      MakeContinuationFluid(frame, next);
    698    }
    699    // Join the parent only as long as we're its last child.
    700    if (frame->GetNextSibling()) {
    701      break;
    702    }
    703    frame = frame->GetParent();
    704  }
    705 }
    706 
    707 static void CreateContinuation(nsIFrame* aFrame,
    708                               const nsLineList::iterator aLine,
    709                               nsIFrame** aNewFrame, bool aIsFluid) {
    710  MOZ_ASSERT(aNewFrame, "null OUT ptr");
    711  MOZ_ASSERT(aFrame, "null ptr");
    712 
    713  *aNewFrame = nullptr;
    714 
    715  nsPresContext* presContext = aFrame->PresContext();
    716  PresShell* presShell = presContext->PresShell();
    717  NS_ASSERTION(presShell,
    718               "PresShell must be set on PresContext before calling "
    719               "nsBidiPresUtils::CreateContinuation");
    720 
    721  nsContainerFrame* parent = aFrame->GetParent();
    722  NS_ASSERTION(
    723      parent,
    724      "Couldn't get frame parent in nsBidiPresUtils::CreateContinuation");
    725 
    726  // While passing &aLine to InsertFrames for a non-block isn't harmful
    727  // because it's a no-op, it doesn't really make sense.  However, the
    728  // MOZ_ASSERT() we need to guarantee that it's safe only works if the
    729  // parent is actually the block.
    730  const nsLineList::iterator* parentLine;
    731  if (parent->IsBlockFrameOrSubclass()) {
    732    MOZ_ASSERT(aLine->Contains(aFrame));
    733    parentLine = &aLine;
    734  } else {
    735    parentLine = nullptr;
    736  }
    737 
    738  // Have to special case floating first letter frames because the continuation
    739  // doesn't go in the first letter frame. The continuation goes with the rest
    740  // of the text that the first letter frame was made out of.
    741  if (parent->IsLetterFrame() && parent->IsFloating()) {
    742    nsFirstLetterFrame* letterFrame = do_QueryFrame(parent);
    743    letterFrame->CreateContinuationForFloatingParent(aFrame, aNewFrame,
    744                                                     aIsFluid);
    745    return;
    746  }
    747 
    748  *aNewFrame = presShell->FrameConstructor()->CreateContinuingFrame(
    749      aFrame, parent, aIsFluid);
    750 
    751  // The list name FrameChildListID::NoReflowPrincipal would indicate we don't
    752  // want reflow
    753  // XXXbz this needs higher-level framelist love
    754  parent->InsertFrames(FrameChildListID::NoReflowPrincipal, aFrame, parentLine,
    755                       nsFrameList(*aNewFrame, *aNewFrame));
    756 
    757  if (!aIsFluid) {
    758    // Split inline ancestor frames
    759    SplitInlineAncestors(parent, aLine, aFrame);
    760  }
    761 }
    762 
    763 /*
    764 * Overview of the implementation of Resolve():
    765 *
    766 *  Walk through the descendants of aBlockFrame and build:
    767 *   * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order
    768 *   * mBuffer: an nsString containing a representation of
    769 *     the content of the frames.
    770 *     In the case of text frames, this is the actual text context of the
    771 *     frames, but some other elements are represented in a symbolic form which
    772 *     will make the Unicode Bidi Algorithm give the correct results.
    773 *     Bidi isolates, embeddings, and overrides set by CSS, <bdi>, or <bdo>
    774 *     elements are represented by the corresponding Unicode control characters.
    775 *     <br> elements are represented by U+2028 LINE SEPARATOR
    776 *     Other inline elements are represented by U+FFFC OBJECT REPLACEMENT
    777 *     CHARACTER
    778 *
    779 *  Then pass mBuffer to the Bidi engine for resolving of embedding levels
    780 *  by nsBidi::SetPara() and division into directional runs by
    781 *  nsBidi::CountRuns().
    782 *
    783 *  Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and
    784 *  correlate them with the frames indexed in mLogicalFrames, setting the
    785 *  baseLevel and embeddingLevel properties according to the results returned
    786 *  by the Bidi engine.
    787 *
    788 *  The rendering layer requires each text frame to contain text in only one
    789 *  direction, so we may need to call EnsureBidiContinuation() to split frames.
    790 *  We may also need to call RemoveBidiContinuation() to convert frames created
    791 *  by EnsureBidiContinuation() in previous reflows into fluid continuations.
    792 */
    793 nsresult nsBidiPresUtils::Resolve(nsBlockFrame* aBlockFrame) {
    794  BidiParagraphData bpd(aBlockFrame);
    795 
    796  // Handle bidi-override being set on the block itself before calling
    797  // TraverseFrames.
    798  // No need to call GetBidiControl as well, because isolate and embed
    799  // values of unicode-bidi property are redundant on block elements.
    800  // unicode-bidi:plaintext on a block element is handled by block frame
    801  // via using nsIFrame::GetWritingMode(nsIFrame*).
    802  char16_t ch = GetBidiOverride(aBlockFrame->Style());
    803  if (ch != 0) {
    804    bpd.PushBidiControl(ch);
    805    bpd.mRequiresBidi = true;
    806  } else {
    807    // If there are no unicode-bidi properties and no RTL characters in the
    808    // block's content, then it is pure LTR and we can skip the rest of bidi
    809    // resolution.
    810    nsIContent* currContent = nullptr;
    811    for (nsBlockFrame* block = aBlockFrame; block;
    812         block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
    813      block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
    814      if (!bpd.mRequiresBidi &&
    815          ChildListMayRequireBidi(block->PrincipalChildList().FirstChild(),
    816                                  &currContent)) {
    817        bpd.mRequiresBidi = true;
    818      }
    819      if (!bpd.mRequiresBidi) {
    820        nsBlockFrame::FrameLines* overflowLines = block->GetOverflowLines();
    821        if (overflowLines) {
    822          if (ChildListMayRequireBidi(overflowLines->mFrames.FirstChild(),
    823                                      &currContent)) {
    824            bpd.mRequiresBidi = true;
    825          }
    826        }
    827      }
    828    }
    829    if (!bpd.mRequiresBidi) {
    830      return NS_OK;
    831    }
    832  }
    833 
    834  for (nsBlockFrame* block = aBlockFrame; block;
    835       block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
    836 #ifdef DEBUG
    837    bpd.mCurrentBlock = block;
    838 #endif
    839    block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
    840    bpd.mCurrentTraverseLine.mLineIterator =
    841        nsBlockInFlowLineIterator(block, block->LinesBegin());
    842    bpd.mCurrentTraverseLine.mPrevFrame = nullptr;
    843    TraverseFrames(block->PrincipalChildList().FirstChild(), &bpd);
    844    nsBlockFrame::FrameLines* overflowLines = block->GetOverflowLines();
    845    if (overflowLines) {
    846      bpd.mCurrentTraverseLine.mLineIterator =
    847          nsBlockInFlowLineIterator(block, overflowLines->mLines.begin(), true);
    848      bpd.mCurrentTraverseLine.mPrevFrame = nullptr;
    849      TraverseFrames(overflowLines->mFrames.FirstChild(), &bpd);
    850    }
    851  }
    852 
    853  if (ch != 0) {
    854    bpd.PopBidiControl(ch);
    855  }
    856 
    857  return ResolveParagraph(&bpd);
    858 }
    859 
    860 // In ResolveParagraph, we previously used ReplaceChar(kSeparators, kSpace)
    861 // to convert separators to spaces, but this hard-coded implementation is
    862 // substantially faster than the general-purpose ReplaceChar function.
    863 // This must be kept in sync with the definition of kSeparators.
    864 static inline void ReplaceSeparators(nsString& aText, size_t aStartIndex = 0) {
    865  for (char16_t* cp = aText.BeginWriting() + aStartIndex;
    866       cp < aText.EndWriting(); cp++) {
    867    if (MOZ_UNLIKELY(*cp < char16_t(' '))) {
    868      static constexpr char16_t SeparatorToSpace[32] = {
    869          0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, ' ',  ' ',
    870          ' ',  0x0c, ' ',  0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
    871          0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, ' ',  ' ',  ' ',  ' ',
    872      };
    873      *cp = SeparatorToSpace[*cp];
    874    } else if (MOZ_UNLIKELY(*cp == kNextLine || *cp == kParagraphSeparator)) {
    875      *cp = ' ';
    876    }
    877  }
    878 }
    879 
    880 nsresult nsBidiPresUtils::ResolveParagraph(BidiParagraphData* aBpd) {
    881  if (aBpd->BufferLength() < 1) {
    882    return NS_OK;
    883  }
    884 
    885  ReplaceSeparators(aBpd->mBuffer);
    886 
    887  int32_t runCount;
    888 
    889  nsresult rv = aBpd->SetPara();
    890  NS_ENSURE_SUCCESS(rv, rv);
    891 
    892  BidiEmbeddingLevel embeddingLevel = aBpd->GetParagraphEmbeddingLevel();
    893 
    894  rv = aBpd->CountRuns(&runCount);
    895  NS_ENSURE_SUCCESS(rv, rv);
    896 
    897  int32_t runLength = 0;     // the length of the current run of text
    898  int32_t logicalLimit = 0;  // the end of the current run + 1
    899  int32_t numRun = -1;
    900  int32_t fragmentLength = 0;  // the length of the current text frame
    901  int32_t frameIndex = -1;     // index to the frames in mLogicalFrames
    902  int32_t frameCount = aBpd->FrameCount();
    903  int32_t contentOffset = 0;  // offset of current frame in its content node
    904  bool isTextFrame = false;
    905  nsIFrame* frame = nullptr;
    906  BidiParagraphData::FrameInfo frameInfo;
    907  nsIContent* content = nullptr;
    908  int32_t contentTextLength = 0;
    909 
    910 #ifdef DEBUG
    911 #  ifdef NOISY_BIDI
    912  printf(
    913      "Before Resolve(), mCurrentBlock=%p, mBuffer='%s', frameCount=%d, "
    914      "runCount=%d\n",
    915      (void*)aBpd->mCurrentBlock, NS_ConvertUTF16toUTF8(aBpd->mBuffer).get(),
    916      frameCount, runCount);
    917 #    ifdef REALLY_NOISY_BIDI
    918  printf(" block frame tree=:\n");
    919  aBpd->mCurrentBlock->List(stdout);
    920 #    endif
    921 #  endif
    922 #endif
    923 
    924  if (runCount == 1 && frameCount == 1 &&
    925      aBpd->GetParagraphDirection() == BidiEngine::ParagraphDirection::LTR &&
    926      aBpd->GetParagraphEmbeddingLevel() == 0) {
    927    // We have a single left-to-right frame in a left-to-right paragraph,
    928    // without bidi isolation from the surrounding text.
    929    // Make sure that the embedding level and base level frame properties aren't
    930    // set (because if they are this frame used to have some other direction,
    931    // so we can't do this optimization), and we're done.
    932    nsIFrame* frame = aBpd->FrameAt(0);
    933    if (frame != NS_BIDI_CONTROL_FRAME) {
    934      FrameBidiData bidiData = frame->GetBidiData();
    935      if (!bidiData.embeddingLevel && !bidiData.baseLevel) {
    936 #ifdef DEBUG
    937 #  ifdef NOISY_BIDI
    938        printf("early return for single direction frame %p\n", (void*)frame);
    939 #  endif
    940 #endif
    941        frame->AddStateBits(NS_FRAME_IS_BIDI);
    942        return NS_OK;
    943      }
    944    }
    945  }
    946 
    947  BidiParagraphData::FrameInfo lastRealFrame;
    948  BidiEmbeddingLevel lastEmbeddingLevel = kBidiLevelNone;
    949  BidiEmbeddingLevel precedingControl = kBidiLevelNone;
    950 
    951  auto storeBidiDataToFrame = [&]() {
    952    FrameBidiData bidiData;
    953    bidiData.embeddingLevel = embeddingLevel;
    954    bidiData.baseLevel = aBpd->GetParagraphEmbeddingLevel();
    955    // If a control character doesn't have a lower embedding level than
    956    // both the preceding and the following frame, it isn't something
    957    // needed for getting the correct result. This optimization should
    958    // remove almost all of embeds and overrides, and some of isolates.
    959    if (precedingControl >= embeddingLevel ||
    960        precedingControl >= lastEmbeddingLevel) {
    961      bidiData.precedingControl = kBidiLevelNone;
    962    } else {
    963      bidiData.precedingControl = precedingControl;
    964    }
    965    precedingControl = kBidiLevelNone;
    966    lastEmbeddingLevel = embeddingLevel;
    967    frame->SetProperty(nsIFrame::BidiDataProperty(), bidiData);
    968  };
    969 
    970  for (;;) {
    971    if (fragmentLength <= 0) {
    972      // Get the next frame from mLogicalFrames
    973      if (++frameIndex >= frameCount) {
    974        break;
    975      }
    976      frameInfo = aBpd->FrameInfoAt(frameIndex);
    977      frame = frameInfo.mFrame;
    978      if (frame == NS_BIDI_CONTROL_FRAME || !frame->IsTextFrame()) {
    979        /*
    980         * Any non-text frame corresponds to a single character in the text
    981         * buffer (a bidi control character, LINE SEPARATOR, or OBJECT
    982         * SUBSTITUTE)
    983         */
    984        isTextFrame = false;
    985        fragmentLength = 1;
    986      } else {
    987        aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(frameInfo);
    988        content = frame->GetContent();
    989        if (!content) {
    990          rv = NS_OK;
    991          break;
    992        }
    993        contentTextLength = content->TextLength();
    994        auto [start, end] = frame->GetOffsets();
    995        NS_ASSERTION(!(contentTextLength < end - start),
    996                     "Frame offsets don't fit in content");
    997        fragmentLength = std::min(contentTextLength, end - start);
    998        contentOffset = start;
    999        isTextFrame = true;
   1000      }
   1001    }  // if (fragmentLength <= 0)
   1002 
   1003    if (runLength <= 0) {
   1004      // Get the next run of text from the Bidi engine
   1005      if (++numRun >= runCount) {
   1006        // We've run out of runs of text; but don't forget to store bidi data
   1007        // to the frame before breaking out of the loop (bug 1426042).
   1008        if (frame != NS_BIDI_CONTROL_FRAME) {
   1009          storeBidiDataToFrame();
   1010          if (isTextFrame) {
   1011            frame->AdjustOffsetsForBidi(contentOffset,
   1012                                        contentOffset + fragmentLength);
   1013          }
   1014        }
   1015        break;
   1016      }
   1017      int32_t lineOffset = logicalLimit;
   1018      aBpd->GetLogicalRun(lineOffset, &logicalLimit, &embeddingLevel);
   1019      runLength = logicalLimit - lineOffset;
   1020    }  // if (runLength <= 0)
   1021 
   1022    if (frame == NS_BIDI_CONTROL_FRAME) {
   1023      // In theory, we only need to do this for isolates. However, it is
   1024      // easier to do this for all here because we do not maintain the
   1025      // index to get corresponding character from buffer. Since we do
   1026      // have proper embedding level for all those characters, including
   1027      // them wouldn't affect the final result.
   1028      precedingControl = std::min(precedingControl, embeddingLevel);
   1029    } else {
   1030      storeBidiDataToFrame();
   1031      if (isTextFrame) {
   1032        if (contentTextLength == 0) {
   1033          // Set the base level and embedding level of the current run even
   1034          // on an empty frame. Otherwise frame reordering will not be correct.
   1035          frame->AdjustOffsetsForBidi(0, 0);
   1036          // Nothing more to do for an empty frame, except update
   1037          // lastRealFrame like we do below.
   1038          lastRealFrame = frameInfo;
   1039          continue;
   1040        }
   1041        nsLineList::iterator currentLine = aBpd->mCurrentResolveLine.GetLine();
   1042        if ((runLength > 0) && (runLength < fragmentLength)) {
   1043          /*
   1044           * The text in this frame continues beyond the end of this directional
   1045           * run. Create a non-fluid continuation frame for the next directional
   1046           * run.
   1047           */
   1048          currentLine->MarkDirty();
   1049          nsIFrame* nextBidi;
   1050          int32_t runEnd = contentOffset + runLength;
   1051          EnsureBidiContinuation(frame, currentLine, &nextBidi, contentOffset,
   1052                                 runEnd);
   1053          nextBidi->AdjustOffsetsForBidi(runEnd,
   1054                                         contentOffset + fragmentLength);
   1055          frame = nextBidi;
   1056          frameInfo.mFrame = frame;
   1057          contentOffset = runEnd;
   1058 
   1059          aBpd->mCurrentResolveLine.AdvanceToFrame(frame);
   1060        }  // if (runLength < fragmentLength)
   1061        else {
   1062          if (contentOffset + fragmentLength == contentTextLength) {
   1063            /*
   1064             * We have finished all the text in this content node. Convert any
   1065             * further non-fluid continuations to fluid continuations and
   1066             * advance frameIndex to the last frame in the content node
   1067             */
   1068            int32_t newIndex = aBpd->GetLastFrameForContent(content);
   1069            if (newIndex > frameIndex) {
   1070              currentLine->MarkDirty();
   1071              RemoveBidiContinuation(aBpd, frame, frameIndex, newIndex);
   1072              frameIndex = newIndex;
   1073              frameInfo = aBpd->FrameInfoAt(frameIndex);
   1074              frame = frameInfo.mFrame;
   1075            }
   1076          } else if (fragmentLength > 0 && runLength > fragmentLength) {
   1077            /*
   1078             * There is more text that belongs to this directional run in the
   1079             * next text frame: make sure it is a fluid continuation of the
   1080             * current frame. Do not advance frameIndex, because the next frame
   1081             * may contain multi-directional text and need to be split
   1082             */
   1083            int32_t newIndex = frameIndex;
   1084            do {
   1085            } while (++newIndex < frameCount &&
   1086                     aBpd->FrameAt(newIndex) == NS_BIDI_CONTROL_FRAME);
   1087            if (newIndex < frameCount) {
   1088              currentLine->MarkDirty();
   1089              RemoveBidiContinuation(aBpd, frame, frameIndex, newIndex);
   1090            }
   1091          } else if (runLength == fragmentLength) {
   1092            /*
   1093             * If the directional run ends at the end of the frame, make sure
   1094             * that any continuation is non-fluid, and do the same up the
   1095             * parent chain
   1096             */
   1097            nsIFrame* next = frame->GetNextInFlow();
   1098            if (next) {
   1099              currentLine->MarkDirty();
   1100              MakeContinuationsNonFluidUpParentChain(frame, next);
   1101            }
   1102          }
   1103          frame->AdjustOffsetsForBidi(contentOffset,
   1104                                      contentOffset + fragmentLength);
   1105        }
   1106      }  // isTextFrame
   1107    }  // not bidi control frame
   1108    int32_t temp = runLength;
   1109    runLength -= fragmentLength;
   1110    fragmentLength -= temp;
   1111 
   1112    // Record last real frame so that we can do splitting properly even
   1113    // if a run ends after a virtual bidi control frame.
   1114    if (frame != NS_BIDI_CONTROL_FRAME) {
   1115      lastRealFrame = frameInfo;
   1116    }
   1117    if (lastRealFrame.mFrame && fragmentLength <= 0) {
   1118      // If the frame is at the end of a run, and this is not the end of our
   1119      // paragraph, split all ancestor inlines that need splitting.
   1120      // To determine whether we're at the end of the run, we check that we've
   1121      // finished processing the current run, and that the current frame
   1122      // doesn't have a fluid continuation (it could have a fluid continuation
   1123      // of zero length, so testing runLength alone is not sufficient).
   1124      if (runLength <= 0 && !lastRealFrame.mFrame->GetNextInFlow()) {
   1125        if (numRun + 1 < runCount) {
   1126          nsIFrame* child = lastRealFrame.mFrame;
   1127          nsContainerFrame* parent = child->GetParent();
   1128          // As long as we're on the last sibling, the parent doesn't have to
   1129          // be split.
   1130          // However, if the parent has a fluid continuation, we do have to make
   1131          // it non-fluid. This can happen e.g. when we have a first-letter
   1132          // frame and the end of the first-letter coincides with the end of a
   1133          // directional run.
   1134          while (parent && IsBidiSplittable(parent) &&
   1135                 !child->GetNextSibling()) {
   1136            nsIFrame* next = parent->GetNextInFlow();
   1137            if (next) {
   1138              parent->SetNextContinuation(next);
   1139              next->SetPrevContinuation(parent);
   1140            }
   1141            child = parent;
   1142            parent = child->GetParent();
   1143          }
   1144          if (parent && IsBidiSplittable(parent)) {
   1145            aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(lastRealFrame);
   1146            SplitInlineAncestors(parent, aBpd->mCurrentResolveLine.GetLine(),
   1147                                 child);
   1148 
   1149            aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(lastRealFrame);
   1150          }
   1151        }
   1152      } else if (frame != NS_BIDI_CONTROL_FRAME) {
   1153        // We're not at an end of a run. If |frame| is the last child of its
   1154        // parent, and its ancestors happen to have bidi continuations, convert
   1155        // them into fluid continuations.
   1156        JoinInlineAncestors(frame);
   1157      }
   1158    }
   1159  }  // for
   1160 
   1161 #ifdef DEBUG
   1162 #  ifdef REALLY_NOISY_BIDI
   1163  printf("---\nAfter Resolve(), frameTree =:\n");
   1164  aBpd->mCurrentBlock->List(stdout);
   1165  printf("===\n");
   1166 #  endif
   1167 #endif
   1168 
   1169  return rv;
   1170 }
   1171 
   1172 void nsBidiPresUtils::TraverseFrames(nsIFrame* aCurrentFrame,
   1173                                     BidiParagraphData* aBpd) {
   1174  if (!aCurrentFrame) {
   1175    return;
   1176  }
   1177 
   1178 #ifdef DEBUG
   1179  nsBlockFrame* initialLineContainer =
   1180      aBpd->mCurrentTraverseLine.mLineIterator.GetContainer();
   1181 #endif
   1182 
   1183  nsIFrame* childFrame = aCurrentFrame;
   1184  do {
   1185    /*
   1186     * It's important to get the next sibling and next continuation *before*
   1187     * handling the frame: If we encounter a forced paragraph break and call
   1188     * ResolveParagraph within this loop, doing GetNextSibling and
   1189     * GetNextContinuation after that could return a bidi continuation that had
   1190     * just been split from the original childFrame and we would process it
   1191     * twice.
   1192     */
   1193    nsIFrame* nextSibling = childFrame->GetNextSibling();
   1194 
   1195    // If the real frame for a placeholder is a first letter frame, we need to
   1196    // drill down into it and include its contents in Bidi resolution.
   1197    // If not, we just use the placeholder.
   1198    nsIFrame* frame = childFrame;
   1199    if (childFrame->IsPlaceholderFrame()) {
   1200      nsIFrame* realFrame =
   1201          nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame);
   1202      if (realFrame->IsLetterFrame()) {
   1203        frame = realFrame;
   1204      }
   1205    }
   1206 
   1207    auto DifferentBidiValues = [](ComputedStyle* aSC1, nsIFrame* aFrame2) {
   1208      ComputedStyle* sc2 = aFrame2->Style();
   1209      return GetBidiControl(aSC1) != GetBidiControl(sc2) ||
   1210             GetBidiOverride(aSC1) != GetBidiOverride(sc2);
   1211    };
   1212 
   1213    ComputedStyle* sc = frame->Style();
   1214    nsIFrame* nextContinuation = frame->GetNextContinuation();
   1215    nsIFrame* prevContinuation = frame->GetPrevContinuation();
   1216    bool isLastFrame =
   1217        !nextContinuation || DifferentBidiValues(sc, nextContinuation);
   1218    bool isFirstFrame =
   1219        !prevContinuation || DifferentBidiValues(sc, prevContinuation);
   1220 
   1221    char16_t controlChar = 0;
   1222    char16_t overrideChar = 0;
   1223    LayoutFrameType frameType = frame->Type();
   1224    if (frame->IsBidiInlineContainer() || RubyUtils::IsRubyBox(frameType)) {
   1225      if (!frame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
   1226        nsContainerFrame* c = static_cast<nsContainerFrame*>(frame);
   1227        MOZ_ASSERT(c == do_QueryFrame(frame),
   1228                   "eBidiInlineContainer and ruby frame must be"
   1229                   " a nsContainerFrame subclass");
   1230        c->DrainSelfOverflowList();
   1231      }
   1232 
   1233      controlChar = GetBidiControl(sc);
   1234      overrideChar = GetBidiOverride(sc);
   1235 
   1236      // Add dummy frame pointers representing bidi control codes before
   1237      // the first frames of elements specifying override, isolation, or
   1238      // plaintext.
   1239      if (isFirstFrame) {
   1240        if (controlChar != 0) {
   1241          aBpd->PushBidiControl(controlChar);
   1242        }
   1243        if (overrideChar != 0) {
   1244          aBpd->PushBidiControl(overrideChar);
   1245        }
   1246      }
   1247    }
   1248 
   1249    if (IsBidiLeaf(frame)) {
   1250      /* Bidi leaf frame: add the frame to the mLogicalFrames array,
   1251       * and add its index to the mContentToFrameIndex hashtable. This
   1252       * will be used in RemoveBidiContinuation() to identify the last
   1253       * frame in the array with a given content.
   1254       */
   1255      nsIContent* content = frame->GetContent();
   1256      aBpd->AppendFrame(frame, aBpd->mCurrentTraverseLine, content);
   1257 
   1258      // Append the content of the frame to the paragraph buffer
   1259      if (LayoutFrameType::Text == frameType) {
   1260        if (content != aBpd->mPrevContent) {
   1261          aBpd->mPrevContent = content;
   1262          if (!frame->StyleText()->NewlineIsSignificant(
   1263                  static_cast<nsTextFrame*>(frame))) {
   1264            content->GetAsText()->AppendTextTo(aBpd->mBuffer);
   1265          } else {
   1266            /*
   1267             * For preformatted text we have to do bidi resolution on each line
   1268             * separately.
   1269             */
   1270            nsAutoString text;
   1271            content->GetAsText()->AppendTextTo(text);
   1272            nsIFrame* next;
   1273            do {
   1274              next = nullptr;
   1275 
   1276              auto [start, end] = frame->GetOffsets();
   1277              int32_t endLine = text.FindChar('\n', start);
   1278              if (endLine == -1) {
   1279                /*
   1280                 * If there is no newline in the text content, just save the
   1281                 * text from this frame and its continuations, and do bidi
   1282                 * resolution later
   1283                 */
   1284                aBpd->AppendString(Substring(text, start));
   1285                while (frame && nextSibling) {
   1286                  aBpd->AdvanceAndAppendFrame(
   1287                      &frame, aBpd->mCurrentTraverseLine, &nextSibling);
   1288                }
   1289                break;
   1290              }
   1291 
   1292              /*
   1293               * If there is a newline in the frame, break the frame after the
   1294               * newline, do bidi resolution and repeat until the last sibling
   1295               */
   1296              ++endLine;
   1297 
   1298              /*
   1299               * If the frame ends before the new line, save the text and move
   1300               * into the next continuation
   1301               */
   1302              aBpd->AppendString(
   1303                  Substring(text, start, std::min(end, endLine) - start));
   1304              while (end < endLine && nextSibling) {
   1305                aBpd->AdvanceAndAppendFrame(&frame, aBpd->mCurrentTraverseLine,
   1306                                            &nextSibling);
   1307                NS_ASSERTION(frame, "Premature end of continuation chain");
   1308                std::tie(start, end) = frame->GetOffsets();
   1309                aBpd->AppendString(
   1310                    Substring(text, start, std::min(end, endLine) - start));
   1311              }
   1312 
   1313              if (end < endLine) {
   1314                aBpd->mPrevContent = nullptr;
   1315                break;
   1316              }
   1317 
   1318              bool createdContinuation = false;
   1319              if (uint32_t(endLine) < text.Length()) {
   1320                /*
   1321                 * Timing is everything here: if the frame already has a bidi
   1322                 * continuation, we need to make the continuation fluid *before*
   1323                 * resetting the length of the current frame. Otherwise
   1324                 * nsTextFrame::SetLength won't set the continuation frame's
   1325                 * text offsets correctly.
   1326                 *
   1327                 * On the other hand, if the frame doesn't have a continuation,
   1328                 * we need to create one *after* resetting the length, or
   1329                 * CreateContinuingFrame will complain that there is no more
   1330                 * content for the continuation.
   1331                 */
   1332                next = frame->GetNextInFlow();
   1333                if (!next) {
   1334                  // If the frame already has a bidi continuation, make it fluid
   1335                  next = frame->GetNextContinuation();
   1336                  if (next) {
   1337                    MakeContinuationFluid(frame, next);
   1338                    JoinInlineAncestors(frame);
   1339                  }
   1340                }
   1341 
   1342                nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
   1343                textFrame->SetLength(endLine - start, nullptr);
   1344 
   1345                // If it weren't for CreateContinuation needing this to
   1346                // be current, we could restructure the marking dirty
   1347                // below to use mCurrentResolveLine and eliminate
   1348                // mCurrentTraverseLine entirely.
   1349                aBpd->mCurrentTraverseLine.AdvanceToFrame(frame);
   1350 
   1351                if (!next) {
   1352                  // If the frame has no next in flow, create one.
   1353                  CreateContinuation(
   1354                      frame, aBpd->mCurrentTraverseLine.GetLine(), &next, true);
   1355                  createdContinuation = true;
   1356                }
   1357                // Mark the line before the newline as dirty.
   1358                aBpd->mCurrentTraverseLine.GetLine()->MarkDirty();
   1359              }
   1360              ResolveParagraphWithinBlock(aBpd);
   1361 
   1362              if (!nextSibling && !createdContinuation) {
   1363                break;
   1364              }
   1365              if (next) {
   1366                frame = next;
   1367                aBpd->AppendFrame(frame, aBpd->mCurrentTraverseLine);
   1368                // Mark the line after the newline as dirty.
   1369                aBpd->mCurrentTraverseLine.AdvanceToFrame(frame);
   1370                aBpd->mCurrentTraverseLine.GetLine()->MarkDirty();
   1371              }
   1372 
   1373              /*
   1374               * If we have already overshot the saved next-sibling while
   1375               * scanning the frame's continuations, advance it.
   1376               */
   1377              if (frame && frame == nextSibling) {
   1378                nextSibling = frame->GetNextSibling();
   1379              }
   1380 
   1381            } while (next);
   1382          }
   1383        }
   1384      } else if (LayoutFrameType::Br == frameType) {
   1385        // break frame -- append line separator
   1386        aBpd->AppendUnichar(kLineSeparator);
   1387        ResolveParagraphWithinBlock(aBpd);
   1388      } else {
   1389        // other frame type -- see the Unicode Bidi Algorithm:
   1390        // "...inline objects (such as graphics) are treated as if they are ...
   1391        // U+FFFC"
   1392        // <wbr>, however, is treated as U+200B ZERO WIDTH SPACE. See
   1393        // http://dev.w3.org/html5/spec/Overview.html#phrasing-content-1.
   1394        // Empty inline frames are also treated as kZWSP, to avoid unexpected
   1395        // bidi reordering of the surrounding content.
   1396        aBpd->AppendUnichar(content->IsHTMLElement(nsGkAtoms::wbr) ||
   1397                                    (frame->IsInlineFrame() && frame->IsEmpty())
   1398                                ? kZWSP
   1399                                : kObjectSubstitute);
   1400        if (!frame->IsInlineOutside()) {
   1401          // if it is not inline, end the paragraph
   1402          ResolveParagraphWithinBlock(aBpd);
   1403        }
   1404      }
   1405    } else {
   1406      // For a non-leaf frame, recurse into TraverseFrames
   1407      nsIFrame* kid = frame->PrincipalChildList().FirstChild();
   1408      MOZ_ASSERT(!frame->GetChildList(FrameChildListID::Overflow).FirstChild(),
   1409                 "should have drained the overflow list above");
   1410      if (kid) {
   1411        TraverseFrames(kid, aBpd);
   1412      }
   1413    }
   1414 
   1415    // If the element is attributed by dir, indicate direction pop (add PDF
   1416    // frame)
   1417    if (isLastFrame) {
   1418      // Add a dummy frame pointer representing a bidi control code after the
   1419      // last frame of an element specifying embedding or override
   1420      if (overrideChar != 0) {
   1421        aBpd->PopBidiControl(overrideChar);
   1422      }
   1423      if (controlChar != 0) {
   1424        aBpd->PopBidiControl(controlChar);
   1425      }
   1426    }
   1427    childFrame = nextSibling;
   1428  } while (childFrame);
   1429 
   1430  MOZ_ASSERT(initialLineContainer ==
   1431             aBpd->mCurrentTraverseLine.mLineIterator.GetContainer());
   1432 }
   1433 
   1434 bool nsBidiPresUtils::ChildListMayRequireBidi(nsIFrame* aFirstChild,
   1435                                              nsIContent** aCurrContent) {
   1436  MOZ_ASSERT(!aFirstChild || !aFirstChild->GetPrevSibling(),
   1437             "Expecting to traverse from the start of a child list");
   1438 
   1439  for (nsIFrame* childFrame = aFirstChild; childFrame;
   1440       childFrame = childFrame->GetNextSibling()) {
   1441    nsIFrame* frame = childFrame;
   1442 
   1443    // If the real frame for a placeholder is a first-letter frame, we need to
   1444    // consider its contents for potential Bidi resolution.
   1445    if (childFrame->IsPlaceholderFrame()) {
   1446      nsIFrame* realFrame =
   1447          nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame);
   1448      if (realFrame->IsLetterFrame()) {
   1449        frame = realFrame;
   1450      }
   1451    }
   1452 
   1453    // If unicode-bidi properties are present, we should do bidi resolution.
   1454    ComputedStyle* sc = frame->Style();
   1455    if (GetBidiControl(sc) || GetBidiOverride(sc)) {
   1456      return true;
   1457    }
   1458 
   1459    if (IsBidiLeaf(frame)) {
   1460      if (frame->IsTextFrame()) {
   1461        // If the frame already has a BidiDataProperty, we know we need to
   1462        // perform bidi resolution (even if no bidi content is NOW present --
   1463        // we might need to remove the property set by a previous reflow, if
   1464        // content has changed; see bug 1366623).
   1465        if (frame->HasProperty(nsIFrame::BidiDataProperty())) {
   1466          return true;
   1467        }
   1468 
   1469        // Check whether the text frame has any RTL characters; if so, bidi
   1470        // resolution will be needed.
   1471        dom::Text* content = frame->GetContent()->AsText();
   1472        if (content != *aCurrContent) {
   1473          *aCurrContent = content;
   1474          const dom::CharacterDataBuffer* txt = &content->DataBuffer();
   1475          if (txt->Is2b() &&
   1476              HasRTLChars(Span(txt->Get2b(), txt->GetLength()))) {
   1477            return true;
   1478          }
   1479        }
   1480      }
   1481    } else if (ChildListMayRequireBidi(frame->PrincipalChildList().FirstChild(),
   1482                                       aCurrContent)) {
   1483      return true;
   1484    }
   1485  }
   1486 
   1487  return false;
   1488 }
   1489 
   1490 void nsBidiPresUtils::ResolveParagraphWithinBlock(BidiParagraphData* aBpd) {
   1491  aBpd->ClearBidiControls();
   1492  ResolveParagraph(aBpd);
   1493  aBpd->ResetData();
   1494 }
   1495 
   1496 /* static */
   1497 nscoord nsBidiPresUtils::ReorderFrames(nsIFrame* aFirstFrameOnLine,
   1498                                       int32_t aNumFramesOnLine,
   1499                                       WritingMode aLineWM,
   1500                                       const nsSize& aContainerSize,
   1501                                       nscoord aStart) {
   1502  nsSize containerSize(aContainerSize);
   1503 
   1504  // If this line consists of a line frame, reorder the line frame's children.
   1505  if (aFirstFrameOnLine->IsLineFrame()) {
   1506    // The line frame is positioned at the start-edge, so use its size
   1507    // as the container size.
   1508    containerSize = aFirstFrameOnLine->GetSize();
   1509 
   1510    aFirstFrameOnLine = aFirstFrameOnLine->PrincipalChildList().FirstChild();
   1511    if (!aFirstFrameOnLine) {
   1512      return 0;
   1513    }
   1514    // All children of the line frame are on the first line. Setting
   1515    // aNumFramesOnLine to -1 makes InitLogicalArrayFromLine look at all of
   1516    // them.
   1517    aNumFramesOnLine = -1;
   1518    // As the line frame itself has been adjusted at its inline-start position
   1519    // by the caller, we do not want to apply this to its children.
   1520    aStart = 0;
   1521  }
   1522 
   1523  // No need to bidi-reorder the line if there's only a single frame.
   1524  if (aNumFramesOnLine == 1) {
   1525    auto bidiData = nsBidiPresUtils::GetFrameBidiData(aFirstFrameOnLine);
   1526    nsContinuationStates continuationStates;
   1527    InitContinuationStates(aFirstFrameOnLine, &continuationStates);
   1528    return aStart + RepositionFrame(aFirstFrameOnLine,
   1529                                    bidiData.embeddingLevel.IsLTR(), aStart,
   1530                                    &continuationStates, aLineWM, false,
   1531                                    containerSize);
   1532  }
   1533 
   1534  BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
   1535  return RepositionInlineFrames(bld, aLineWM, containerSize, aStart);
   1536 }
   1537 
   1538 nsIFrame* nsBidiPresUtils::GetFirstLeaf(nsIFrame* aFrame) {
   1539  nsIFrame* firstLeaf = aFrame;
   1540  while (!IsBidiLeaf(firstLeaf)) {
   1541    nsIFrame* firstChild = firstLeaf->PrincipalChildList().FirstChild();
   1542    nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameFor(firstChild);
   1543    firstLeaf = (realFrame->IsLetterFrame()) ? realFrame : firstChild;
   1544  }
   1545  return firstLeaf;
   1546 }
   1547 
   1548 FrameBidiData nsBidiPresUtils::GetFrameBidiData(nsIFrame* aFrame) {
   1549  return GetFirstLeaf(aFrame)->GetBidiData();
   1550 }
   1551 
   1552 BidiEmbeddingLevel nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame) {
   1553  return GetFirstLeaf(aFrame)->GetEmbeddingLevel();
   1554 }
   1555 
   1556 BidiEmbeddingLevel nsBidiPresUtils::GetFrameBaseLevel(const nsIFrame* aFrame) {
   1557  const nsIFrame* firstLeaf = aFrame;
   1558  while (!IsBidiLeaf(firstLeaf)) {
   1559    firstLeaf = firstLeaf->PrincipalChildList().FirstChild();
   1560  }
   1561  return firstLeaf->GetBaseLevel();
   1562 }
   1563 
   1564 void nsBidiPresUtils::IsFirstOrLast(nsIFrame* aFrame,
   1565                                    nsContinuationStates* aContinuationStates,
   1566                                    bool aSpanDirMatchesLineDir,
   1567                                    bool& aIsFirst /* out */,
   1568                                    bool& aIsLast /* out */) {
   1569  /*
   1570   * Since we lay out frames in the line's direction, visiting a frame with
   1571   * 'mFirstVisualFrame == nullptr', means it's the first appearance of one
   1572   * of its continuation chain frames on the line.
   1573   * To determine if it's the last visual frame of its continuation chain on
   1574   * the line or not, we count the number of frames of the chain on the line,
   1575   * and then reduce it when we lay out a frame of the chain. If this value
   1576   * becomes 1 it means that it's the last visual frame of its continuation
   1577   * chain on this line.
   1578   */
   1579 
   1580  bool firstInLineOrder, lastInLineOrder;
   1581  nsFrameContinuationState* frameState = aContinuationStates->Get(aFrame);
   1582  nsFrameContinuationState* firstFrameState;
   1583 
   1584  if (!frameState->mFirstVisualFrame) {
   1585    // aFrame is the first visual frame of its continuation chain
   1586    nsFrameContinuationState* contState;
   1587    nsIFrame* frame;
   1588 
   1589    frameState->mFrameCount = 1;
   1590    frameState->mFirstVisualFrame = aFrame;
   1591 
   1592    /**
   1593     * Traverse continuation chain of aFrame in both backward and forward
   1594     * directions while the frames are on this line. Count the frames and
   1595     * set their mFirstVisualFrame to aFrame.
   1596     */
   1597    // Traverse continuation chain backward
   1598    for (frame = aFrame->GetPrevContinuation();
   1599         frame && (contState = aContinuationStates->Get(frame));
   1600         frame = frame->GetPrevContinuation()) {
   1601      frameState->mFrameCount++;
   1602      contState->mFirstVisualFrame = aFrame;
   1603    }
   1604    frameState->mHasContOnPrevLines = (frame != nullptr);
   1605 
   1606    // Traverse continuation chain forward
   1607    for (frame = aFrame->GetNextContinuation();
   1608         frame && (contState = aContinuationStates->Get(frame));
   1609         frame = frame->GetNextContinuation()) {
   1610      frameState->mFrameCount++;
   1611      contState->mFirstVisualFrame = aFrame;
   1612    }
   1613    frameState->mHasContOnNextLines = (frame != nullptr);
   1614 
   1615    firstInLineOrder = true;
   1616    firstFrameState = frameState;
   1617  } else {
   1618    // aFrame is not the first visual frame of its continuation chain
   1619    firstInLineOrder = false;
   1620    firstFrameState = aContinuationStates->Get(frameState->mFirstVisualFrame);
   1621  }
   1622 
   1623  lastInLineOrder = (firstFrameState->mFrameCount == 1);
   1624 
   1625  if (aSpanDirMatchesLineDir) {
   1626    aIsFirst = firstInLineOrder;
   1627    aIsLast = lastInLineOrder;
   1628  } else {
   1629    aIsFirst = lastInLineOrder;
   1630    aIsLast = firstInLineOrder;
   1631  }
   1632 
   1633  if (frameState->mHasContOnPrevLines) {
   1634    aIsFirst = false;
   1635  }
   1636  if (firstFrameState->mHasContOnNextLines) {
   1637    aIsLast = false;
   1638  }
   1639 
   1640  if ((aIsFirst || aIsLast) &&
   1641      aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
   1642    // For ib splits, don't treat anything except the last part as
   1643    // endmost or anything except the first part as startmost.
   1644    // As an optimization, only get the first continuation once.
   1645    nsIFrame* firstContinuation = aFrame->FirstContinuation();
   1646    if (firstContinuation->FrameIsNonLastInIBSplit()) {
   1647      // We are not endmost
   1648      aIsLast = false;
   1649    }
   1650    if (firstContinuation->FrameIsNonFirstInIBSplit()) {
   1651      // We are not startmost
   1652      aIsFirst = false;
   1653    }
   1654  }
   1655 
   1656  // Reduce number of remaining frames of the continuation chain on the line.
   1657  firstFrameState->mFrameCount--;
   1658 
   1659  nsInlineFrame* testFrame = do_QueryFrame(aFrame);
   1660 
   1661  if (testFrame) {
   1662    aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET);
   1663 
   1664    if (aIsFirst) {
   1665      aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
   1666    } else {
   1667      aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
   1668    }
   1669 
   1670    if (aIsLast) {
   1671      aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
   1672    } else {
   1673      aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
   1674    }
   1675  }
   1676 }
   1677 
   1678 /* static */
   1679 void nsBidiPresUtils::RepositionRubyContentFrame(
   1680    nsIFrame* aFrame, WritingMode aFrameWM,
   1681    const LogicalMargin& aBorderPadding) {
   1682  const nsFrameList& childList = aFrame->PrincipalChildList();
   1683  if (childList.IsEmpty()) {
   1684    return;
   1685  }
   1686 
   1687  // Reorder the children.
   1688  nscoord isize =
   1689      ReorderFrames(childList.FirstChild(), childList.GetLength(), aFrameWM,
   1690                    aFrame->GetSize(), aBorderPadding.IStart(aFrameWM));
   1691  isize += aBorderPadding.IEnd(aFrameWM);
   1692 
   1693  if (aFrame->StyleText()->mRubyAlign == StyleRubyAlign::Start) {
   1694    return;
   1695  }
   1696  nscoord residualISize = aFrame->ISize(aFrameWM) - isize;
   1697  if (residualISize <= 0) {
   1698    return;
   1699  }
   1700 
   1701  // When ruby-align is not "start", if the content does not fill this
   1702  // frame, we need to center the children.
   1703  const nsSize dummyContainerSize;
   1704  for (nsIFrame* child : childList) {
   1705    LogicalRect rect = child->GetLogicalRect(aFrameWM, dummyContainerSize);
   1706    rect.IStart(aFrameWM) += residualISize / 2;
   1707    child->SetRect(aFrameWM, rect, dummyContainerSize);
   1708  }
   1709 }
   1710 
   1711 /* static */
   1712 nscoord nsBidiPresUtils::RepositionRubyFrame(
   1713    nsIFrame* aFrame, nsContinuationStates* aContinuationStates,
   1714    const WritingMode aContainerWM, const LogicalMargin& aBorderPadding) {
   1715  LayoutFrameType frameType = aFrame->Type();
   1716  MOZ_ASSERT(RubyUtils::IsRubyBox(frameType));
   1717 
   1718  nscoord icoord = 0;
   1719  WritingMode frameWM = aFrame->GetWritingMode();
   1720  bool isLTR = frameWM.IsBidiLTR();
   1721  nsSize frameSize = aFrame->GetSize();
   1722  if (frameType == LayoutFrameType::Ruby) {
   1723    icoord += aBorderPadding.IStart(frameWM);
   1724    // Reposition ruby segments in a ruby container
   1725    for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(aFrame)); !e.AtEnd();
   1726         e.Next()) {
   1727      nsRubyBaseContainerFrame* rbc = e.GetBaseContainer();
   1728      AutoRubyTextContainerArray textContainers(rbc);
   1729 
   1730      nscoord segmentISize = RepositionFrame(
   1731          rbc, isLTR, icoord, aContinuationStates, frameWM, false, frameSize);
   1732      for (nsRubyTextContainerFrame* rtc : textContainers) {
   1733        nscoord isize = RepositionFrame(rtc, isLTR, icoord, aContinuationStates,
   1734                                        frameWM, false, frameSize);
   1735        segmentISize = std::max(segmentISize, isize);
   1736      }
   1737      icoord += segmentISize;
   1738    }
   1739    icoord += aBorderPadding.IEnd(frameWM);
   1740  } else if (frameType == LayoutFrameType::RubyBaseContainer) {
   1741    // Reposition ruby columns in a ruby segment
   1742    auto rbc = static_cast<nsRubyBaseContainerFrame*>(aFrame);
   1743    AutoRubyTextContainerArray textContainers(rbc);
   1744 
   1745    for (RubyColumnEnumerator e(rbc, textContainers); !e.AtEnd(); e.Next()) {
   1746      RubyColumn column;
   1747      e.GetColumn(column);
   1748 
   1749      nscoord columnISize =
   1750          RepositionFrame(column.mBaseFrame, isLTR, icoord, aContinuationStates,
   1751                          frameWM, false, frameSize);
   1752      for (nsRubyTextFrame* rt : column.mTextFrames) {
   1753        nscoord isize = RepositionFrame(rt, isLTR, icoord, aContinuationStates,
   1754                                        frameWM, false, frameSize);
   1755        columnISize = std::max(columnISize, isize);
   1756      }
   1757      icoord += columnISize;
   1758    }
   1759  } else {
   1760    if (frameType == LayoutFrameType::RubyBase ||
   1761        frameType == LayoutFrameType::RubyText) {
   1762      RepositionRubyContentFrame(aFrame, frameWM, aBorderPadding);
   1763    }
   1764    // Note that, ruby text container is not present in all conditions
   1765    // above. It is intended, because the children of rtc are reordered
   1766    // with the children of ruby base container simultaneously. We only
   1767    // need to return its isize here, as it should not be changed.
   1768    icoord += aFrame->ISize(aContainerWM);
   1769  }
   1770  return icoord;
   1771 }
   1772 
   1773 /* static */
   1774 nscoord nsBidiPresUtils::RepositionFrame(
   1775    nsIFrame* aFrame, bool aIsEvenLevel, nscoord aStartOrEnd,
   1776    nsContinuationStates* aContinuationStates, WritingMode aContainerWM,
   1777    bool aContainerReverseDir, const nsSize& aContainerSize) {
   1778  nscoord lineSize =
   1779      aContainerWM.IsVertical() ? aContainerSize.height : aContainerSize.width;
   1780  NS_ASSERTION(lineSize != NS_UNCONSTRAINEDSIZE,
   1781               "Unconstrained inline line size in bidi frame reordering");
   1782  if (!aFrame) {
   1783    return 0;
   1784  }
   1785 
   1786  bool isFirst, isLast;
   1787  WritingMode frameWM = aFrame->GetWritingMode();
   1788  IsFirstOrLast(aFrame, aContinuationStates,
   1789                aContainerWM.IsBidiLTR() == frameWM.IsBidiLTR(),
   1790                isFirst /* out */, isLast /* out */);
   1791 
   1792  // We only need the margin if the frame is first or last in its own
   1793  // writing mode, but we're traversing the frames in the order of the
   1794  // container's writing mode. To get the right values, we set start and
   1795  // end margins on a logical margin in the frame's writing mode, and
   1796  // then convert the margin to the container's writing mode to set the
   1797  // coordinates.
   1798 
   1799  // This method is called from nsBlockFrame::PlaceLine via the call to
   1800  // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
   1801  // have been reflowed, which is required for GetUsedMargin/Border/Padding
   1802  nscoord frameISize = aFrame->ISize();
   1803  LogicalMargin frameMargin = aFrame->GetLogicalUsedMargin(frameWM);
   1804  LogicalMargin borderPadding = aFrame->GetLogicalUsedBorderAndPadding(frameWM);
   1805  // Since the visual order of frame could be different from the continuation
   1806  // order, we need to remove any inline border/padding [that is already applied
   1807  // based on continuation order] and then add it back based on the visual order
   1808  // (i.e. isFirst/isLast) to get the correct isize for the current frame.
   1809  // We don't need to do that for 'box-decoration-break:clone' because then all
   1810  // continuations have border/padding/margin applied.
   1811  if (aFrame->StyleBorder()->mBoxDecorationBreak ==
   1812      StyleBoxDecorationBreak::Slice) {
   1813    // First remove the border/padding that was applied based on logical order.
   1814    if (!aFrame->GetPrevContinuation()) {
   1815      frameISize -= borderPadding.IStart(frameWM);
   1816    }
   1817    if (!aFrame->GetNextContinuation()) {
   1818      frameISize -= borderPadding.IEnd(frameWM);
   1819    }
   1820    // Set margin/border/padding based on visual order.
   1821    if (!isFirst) {
   1822      frameMargin.IStart(frameWM) = 0;
   1823      borderPadding.IStart(frameWM) = 0;
   1824    }
   1825    if (!isLast) {
   1826      frameMargin.IEnd(frameWM) = 0;
   1827      borderPadding.IEnd(frameWM) = 0;
   1828    }
   1829    // Add the border/padding which is now based on visual order.
   1830    frameISize += borderPadding.IStartEnd(frameWM);
   1831  }
   1832 
   1833  nscoord icoord = 0;
   1834  if (IsBidiLeaf(aFrame)) {
   1835    icoord +=
   1836        frameWM.IsOrthogonalTo(aContainerWM) ? aFrame->BSize() : frameISize;
   1837  } else if (RubyUtils::IsRubyBox(aFrame->Type())) {
   1838    icoord += RepositionRubyFrame(aFrame, aContinuationStates, aContainerWM,
   1839                                  borderPadding);
   1840  } else {
   1841    bool reverseDir = aIsEvenLevel != frameWM.IsBidiLTR();
   1842    icoord += reverseDir ? borderPadding.IEnd(frameWM)
   1843                         : borderPadding.IStart(frameWM);
   1844    LogicalSize logicalSize(frameWM, frameISize, aFrame->BSize());
   1845    nsSize frameSize = logicalSize.GetPhysicalSize(frameWM);
   1846    // Reposition the child frames
   1847    for (nsIFrame* f : aFrame->PrincipalChildList()) {
   1848      icoord += RepositionFrame(f, aIsEvenLevel, icoord, aContinuationStates,
   1849                                frameWM, reverseDir, frameSize);
   1850    }
   1851    icoord += reverseDir ? borderPadding.IStart(frameWM)
   1852                         : borderPadding.IEnd(frameWM);
   1853  }
   1854 
   1855  // In the following variables, if aContainerReverseDir is true, i.e.
   1856  // the container is positioning its children in reverse of its logical
   1857  // direction, the "StartOrEnd" refers to the distance from the frame
   1858  // to the inline end edge of the container, elsewise, it refers to the
   1859  // distance to the inline start edge.
   1860  const LogicalMargin margin = frameMargin.ConvertTo(aContainerWM, frameWM);
   1861  nscoord marginStartOrEnd = aContainerReverseDir ? margin.IEnd(aContainerWM)
   1862                                                  : margin.IStart(aContainerWM);
   1863  nscoord frameStartOrEnd = aStartOrEnd + marginStartOrEnd;
   1864 
   1865  LogicalRect rect = aFrame->GetLogicalRect(aContainerWM, aContainerSize);
   1866  rect.ISize(aContainerWM) = icoord;
   1867  rect.IStart(aContainerWM) = aContainerReverseDir
   1868                                  ? lineSize - frameStartOrEnd - icoord
   1869                                  : frameStartOrEnd;
   1870  aFrame->SetRect(aContainerWM, rect, aContainerSize);
   1871 
   1872  return icoord + margin.IStartEnd(aContainerWM);
   1873 }
   1874 
   1875 void nsBidiPresUtils::InitContinuationStates(
   1876    nsIFrame* aFrame, nsContinuationStates* aContinuationStates) {
   1877  aContinuationStates->Insert(aFrame);
   1878  if (!IsBidiLeaf(aFrame)) {
   1879    // Continue for child frames
   1880    for (nsIFrame* frame : aFrame->PrincipalChildList()) {
   1881      InitContinuationStates(frame, aContinuationStates);
   1882    }
   1883  }
   1884 }
   1885 
   1886 /* static */
   1887 nscoord nsBidiPresUtils::RepositionInlineFrames(const BidiLineData& aBld,
   1888                                                WritingMode aLineWM,
   1889                                                const nsSize& aContainerSize,
   1890                                                nscoord aStart) {
   1891  nsContinuationStates continuationStates;
   1892  aBld.InitContinuationStates(&continuationStates);
   1893 
   1894  if (aLineWM.IsBidiLTR()) {
   1895    for (auto index : IntegerRange(aBld.VisualFrameCount())) {
   1896      auto [frame, level] = aBld.VisualFrameAndLevelAt(index);
   1897      aStart +=
   1898          RepositionFrame(frame, level.IsLTR(), aStart, &continuationStates,
   1899                          aLineWM, false, aContainerSize);
   1900    }
   1901  } else {
   1902    for (auto index : Reversed(IntegerRange(aBld.VisualFrameCount()))) {
   1903      auto [frame, level] = aBld.VisualFrameAndLevelAt(index);
   1904      aStart +=
   1905          RepositionFrame(frame, level.IsLTR(), aStart, &continuationStates,
   1906                          aLineWM, false, aContainerSize);
   1907    }
   1908  }
   1909 
   1910  return aStart;
   1911 }
   1912 
   1913 bool nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine,
   1914                                     int32_t aNumFramesOnLine,
   1915                                     nsIFrame** aFirstVisual,
   1916                                     nsIFrame** aLastVisual) {
   1917  BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
   1918 
   1919  if (aFirstVisual) {
   1920    *aFirstVisual = bld.VisualFrameAt(0);
   1921  }
   1922  if (aLastVisual) {
   1923    *aLastVisual = bld.VisualFrameAt(bld.VisualFrameCount() - 1);
   1924  }
   1925 
   1926  return bld.IsReordered();
   1927 }
   1928 
   1929 nsIFrame* nsBidiPresUtils::GetFrameToRightOf(const nsIFrame* aFrame,
   1930                                             nsIFrame* aFirstFrameOnLine,
   1931                                             int32_t aNumFramesOnLine) {
   1932  BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
   1933 
   1934  int32_t count = bld.VisualFrameCount();
   1935 
   1936  if (!aFrame && count) {
   1937    return bld.VisualFrameAt(0);
   1938  }
   1939 
   1940  for (int32_t i = 0; i < count - 1; i++) {
   1941    if (bld.VisualFrameAt(i) == aFrame) {
   1942      return bld.VisualFrameAt(i + 1);
   1943    }
   1944  }
   1945 
   1946  return nullptr;
   1947 }
   1948 
   1949 nsIFrame* nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame* aFrame,
   1950                                            nsIFrame* aFirstFrameOnLine,
   1951                                            int32_t aNumFramesOnLine) {
   1952  BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
   1953 
   1954  int32_t count = bld.VisualFrameCount();
   1955 
   1956  if (!aFrame && count) {
   1957    return bld.VisualFrameAt(count - 1);
   1958  }
   1959 
   1960  for (int32_t i = 1; i < count; i++) {
   1961    if (bld.VisualFrameAt(i) == aFrame) {
   1962      return bld.VisualFrameAt(i - 1);
   1963    }
   1964  }
   1965 
   1966  return nullptr;
   1967 }
   1968 
   1969 inline void nsBidiPresUtils::EnsureBidiContinuation(
   1970    nsIFrame* aFrame, const nsLineList::iterator aLine, nsIFrame** aNewFrame,
   1971    int32_t aStart, int32_t aEnd) {
   1972  MOZ_ASSERT(aNewFrame, "null OUT ptr");
   1973  MOZ_ASSERT(aFrame, "aFrame is null");
   1974 
   1975  aFrame->AdjustOffsetsForBidi(aStart, aEnd);
   1976  CreateContinuation(aFrame, aLine, aNewFrame, false);
   1977 }
   1978 
   1979 void nsBidiPresUtils::RemoveBidiContinuation(BidiParagraphData* aBpd,
   1980                                             nsIFrame* aFrame,
   1981                                             int32_t aFirstIndex,
   1982                                             int32_t aLastIndex) {
   1983  // If we're only processing one frame, and it is already a fluid continuation
   1984  // (next-in-flow), there's nothing to do.
   1985  if (aLastIndex == aFirstIndex + 1 &&
   1986      aFrame->GetNextInFlow() == aFrame->GetNextContinuation()) {
   1987    return;
   1988  }
   1989  FrameBidiData bidiData = aFrame->GetBidiData();
   1990  bidiData.precedingControl = kBidiLevelNone;
   1991  for (int32_t index = aFirstIndex + 1; index <= aLastIndex; index++) {
   1992    nsIFrame* frame = aBpd->FrameAt(index);
   1993    if (frame != NS_BIDI_CONTROL_FRAME) {
   1994      // Make the frame and its continuation ancestors fluid,
   1995      // so they can be reused or deleted by normal reflow code
   1996      frame->SetProperty(nsIFrame::BidiDataProperty(), bidiData);
   1997      frame->AddStateBits(NS_FRAME_IS_BIDI);
   1998      while (frame && IsBidiSplittable(frame)) {
   1999        nsIFrame* prev = frame->GetPrevContinuation();
   2000        if (prev) {
   2001          MakeContinuationFluid(prev, frame);
   2002          frame = frame->GetParent();
   2003        } else {
   2004          break;
   2005        }
   2006      }
   2007    }
   2008  }
   2009 
   2010  // Make sure that the last continuation we made fluid does not itself have a
   2011  // fluid continuation (this can happen when re-resolving after dynamic changes
   2012  // to content)
   2013  nsIFrame* lastFrame = aBpd->FrameAt(aLastIndex);
   2014  MakeContinuationsNonFluidUpParentChain(lastFrame, lastFrame->GetNextInFlow());
   2015 }
   2016 
   2017 nsresult nsBidiPresUtils::FormatUnicodeText(nsPresContext* aPresContext,
   2018                                            char16_t* aText,
   2019                                            int32_t& aTextLength,
   2020                                            BidiClass aBidiClass) {
   2021  nsresult rv = NS_OK;
   2022  // ahmed
   2023  // adjusted for correct numeral shaping
   2024  uint32_t bidiOptions = aPresContext->GetBidi();
   2025  switch (GET_BIDI_OPTION_NUMERAL(bidiOptions)) {
   2026    case IBMBIDI_NUMERAL_HINDI:
   2027      HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
   2028      break;
   2029 
   2030    case IBMBIDI_NUMERAL_ARABIC:
   2031      HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
   2032      break;
   2033 
   2034    case IBMBIDI_NUMERAL_PERSIAN:
   2035      HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_PERSIAN);
   2036      break;
   2037 
   2038    case IBMBIDI_NUMERAL_REGULAR:
   2039 
   2040      switch (aBidiClass) {
   2041        case BidiClass::EuropeanNumber:
   2042          HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
   2043          break;
   2044 
   2045        case BidiClass::ArabicNumber:
   2046          HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
   2047          break;
   2048 
   2049        default:
   2050          break;
   2051      }
   2052      break;
   2053 
   2054    case IBMBIDI_NUMERAL_HINDICONTEXT:
   2055      if (((GET_BIDI_OPTION_DIRECTION(bidiOptions) ==
   2056            IBMBIDI_TEXTDIRECTION_RTL) &&
   2057           (IS_ARABIC_DIGIT(aText[0]))) ||
   2058          (BidiClass::ArabicNumber == aBidiClass)) {
   2059        HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
   2060      } else if (BidiClass::EuropeanNumber == aBidiClass) {
   2061        HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
   2062      }
   2063      break;
   2064 
   2065    case IBMBIDI_NUMERAL_PERSIANCONTEXT:
   2066      if (((GET_BIDI_OPTION_DIRECTION(bidiOptions) ==
   2067            IBMBIDI_TEXTDIRECTION_RTL) &&
   2068           (IS_ARABIC_DIGIT(aText[0]))) ||
   2069          (BidiClass::ArabicNumber == aBidiClass)) {
   2070        HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_PERSIAN);
   2071      } else if (BidiClass::EuropeanNumber == aBidiClass) {
   2072        HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
   2073      }
   2074      break;
   2075 
   2076    case IBMBIDI_NUMERAL_NOMINAL:
   2077    default:
   2078      break;
   2079  }
   2080 
   2081  StripBidiControlCharacters(aText, aTextLength);
   2082  return rv;
   2083 }
   2084 
   2085 void nsBidiPresUtils::StripBidiControlCharacters(char16_t* aText,
   2086                                                 int32_t& aTextLength) {
   2087  if ((nullptr == aText) || (aTextLength < 1)) {
   2088    return;
   2089  }
   2090 
   2091  int32_t stripLen = 0;
   2092 
   2093  for (int32_t i = 0; i < aTextLength; i++) {
   2094    // XXX: This silently ignores surrogate characters.
   2095    //      As of Unicode 4.0, all Bidi control characters are within the BMP.
   2096    if (IsBidiControl((uint32_t)aText[i])) {
   2097      ++stripLen;
   2098    } else {
   2099      aText[i - stripLen] = aText[i];
   2100    }
   2101  }
   2102  aTextLength -= stripLen;
   2103 }
   2104 
   2105 void nsBidiPresUtils::CalculateBidiClass(
   2106    const char16_t* aText, int32_t& aOffset, int32_t aBidiClassLimit,
   2107    int32_t& aRunLimit, int32_t& aRunLength, int32_t& aRunCount,
   2108    BidiClass& aBidiClass, BidiClass& aPrevBidiClass) {
   2109  bool strongTypeFound = false;
   2110  int32_t offset;
   2111  BidiClass bidiClass;
   2112 
   2113  aBidiClass = BidiClass::OtherNeutral;
   2114 
   2115  int32_t charLen;
   2116  for (offset = aOffset; offset < aBidiClassLimit; offset += charLen) {
   2117    // Make sure we give RTL chartype to all characters that would be classified
   2118    // as Right-To-Left by a bidi platform.
   2119    // (May differ from the UnicodeData, eg we set RTL chartype to some NSMs.)
   2120    charLen = 1;
   2121    uint32_t ch = aText[offset];
   2122    if (IS_HEBREW_CHAR(ch)) {
   2123      bidiClass = BidiClass::RightToLeft;
   2124    } else if (IS_ARABIC_ALPHABETIC(ch)) {
   2125      bidiClass = BidiClass::RightToLeftArabic;
   2126    } else {
   2127      if (offset + 1 < aBidiClassLimit &&
   2128          NS_IS_SURROGATE_PAIR(ch, aText[offset + 1])) {
   2129        ch = SURROGATE_TO_UCS4(ch, aText[offset + 1]);
   2130        charLen = 2;
   2131      }
   2132      bidiClass = intl::UnicodeProperties::GetBidiClass(ch);
   2133    }
   2134 
   2135    if (!BIDICLASS_IS_WEAK(bidiClass)) {
   2136      if (strongTypeFound && (bidiClass != aPrevBidiClass) &&
   2137          (BIDICLASS_IS_RTL(bidiClass) || BIDICLASS_IS_RTL(aPrevBidiClass))) {
   2138        // Stop at this point to ensure uni-directionality of the text
   2139        // (from platform's point of view).
   2140        // Also, don't mix Arabic and Hebrew content (since platform may
   2141        // provide BIDI support to one of them only).
   2142        aRunLength = offset - aOffset;
   2143        aRunLimit = offset;
   2144        ++aRunCount;
   2145        break;
   2146      }
   2147 
   2148      if ((BidiClass::RightToLeftArabic == aPrevBidiClass ||
   2149           BidiClass::ArabicNumber == aPrevBidiClass) &&
   2150          BidiClass::EuropeanNumber == bidiClass) {
   2151        bidiClass = BidiClass::ArabicNumber;
   2152      }
   2153 
   2154      // Set PrevBidiClass to the last strong type in this frame
   2155      // (for correct numeric shaping)
   2156      aPrevBidiClass = bidiClass;
   2157 
   2158      strongTypeFound = true;
   2159      aBidiClass = bidiClass;
   2160    }
   2161  }
   2162  aOffset = offset;
   2163 }
   2164 
   2165 nsresult nsBidiPresUtils::ProcessText(const char16_t* aText, size_t aLength,
   2166                                      BidiEmbeddingLevel aBaseLevel,
   2167                                      nsPresContext* aPresContext,
   2168                                      BidiProcessor& aprocessor, Mode aMode,
   2169                                      nsBidiPositionResolve* aPosResolve,
   2170                                      int32_t aPosResolveCount, nscoord* aWidth,
   2171                                      BidiEngine& aBidiEngine) {
   2172  MOZ_ASSERT((aPosResolve == nullptr) != (aPosResolveCount > 0),
   2173             "Incorrect aPosResolve / aPosResolveCount arguments");
   2174 
   2175  // Caller should have already replaced any separators in the original text
   2176  // with <space> characters.
   2177  MOZ_ASSERT(nsDependentSubstring(aText, aLength).FindCharInSet(kSeparators) ==
   2178             kNotFound);
   2179 
   2180  for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) {
   2181    aPosResolve[nPosResolve].visualIndex = kNotFound;
   2182    aPosResolve[nPosResolve].visualLeftTwips = kNotFound;
   2183    aPosResolve[nPosResolve].visualWidth = kNotFound;
   2184  }
   2185 
   2186  // For a single-char string, or a string that is purely LTR, use a simplified
   2187  // path as it cannot have multiple direction or bidi-class runs.
   2188  if (aLength == 1 ||
   2189      (aLength == 2 && NS_IS_SURROGATE_PAIR(aText[0], aText[1])) ||
   2190      (aBaseLevel.Direction() == BidiDirection::LTR &&
   2191       !encoding_mem_is_utf16_bidi(aText, aLength))) {
   2192    ProcessSimpleRun(aText, aLength, aBaseLevel, aPresContext, aprocessor,
   2193                     aMode, aPosResolve, aPosResolveCount, aWidth);
   2194    return NS_OK;
   2195  }
   2196 
   2197  if (aBidiEngine.SetParagraph(Span(aText, aLength), aBaseLevel).isErr()) {
   2198    return NS_ERROR_FAILURE;
   2199  }
   2200 
   2201  auto result = aBidiEngine.CountRuns();
   2202  if (result.isErr()) {
   2203    return NS_ERROR_FAILURE;
   2204  }
   2205  int32_t runCount = result.unwrap();
   2206 
   2207  nscoord xOffset = 0;
   2208  nscoord width, xEndRun = 0;
   2209  nscoord totalWidth = 0;
   2210  int32_t i, start, limit, length;
   2211  uint32_t visualStart = 0;
   2212  BidiClass bidiClass;
   2213  BidiClass prevClass = BidiClass::LeftToRight;
   2214 
   2215  for (i = 0; i < runCount; i++) {
   2216    aBidiEngine.GetVisualRun(i, &start, &length);
   2217 
   2218    BidiEmbeddingLevel level;
   2219    aBidiEngine.GetLogicalRun(start, &limit, &level);
   2220 
   2221    BidiDirection dir = level.Direction();
   2222    int32_t subRunLength = limit - start;
   2223    int32_t lineOffset = start;
   2224    int32_t typeLimit = std::min(limit, AssertedCast<int32_t>(aLength));
   2225    int32_t subRunCount = 1;
   2226    int32_t subRunLimit = typeLimit;
   2227 
   2228    /*
   2229     * If |level| is even, i.e. the direction of the run is left-to-right, we
   2230     * render the subruns from left to right and increment the x-coordinate
   2231     * |xOffset| by the width of each subrun after rendering.
   2232     *
   2233     * If |level| is odd, i.e. the direction of the run is right-to-left, we
   2234     * render the subruns from right to left. We begin by incrementing |xOffset|
   2235     * by the width of the whole run, and then decrement it by the width of each
   2236     * subrun before rendering. After rendering all the subruns, we restore the
   2237     * x-coordinate of the end of the run for the start of the next run.
   2238     */
   2239 
   2240    if (dir == BidiDirection::RTL) {
   2241      aprocessor.SetText(aText + start, subRunLength, BidiDirection::RTL);
   2242      width = aprocessor.GetWidth();
   2243      xOffset += width;
   2244      xEndRun = xOffset;
   2245    }
   2246 
   2247    while (subRunCount > 0) {
   2248      // CalculateBidiClass can increment subRunCount if the run
   2249      // contains mixed character types
   2250      CalculateBidiClass(aText, lineOffset, typeLimit, subRunLimit,
   2251                         subRunLength, subRunCount, bidiClass, prevClass);
   2252 
   2253      nsAutoString runVisualText(aText + start, subRunLength);
   2254      if (aPresContext) {
   2255        FormatUnicodeText(aPresContext, runVisualText.BeginWriting(),
   2256                          subRunLength, bidiClass);
   2257      }
   2258 
   2259      aprocessor.SetText(runVisualText.get(), subRunLength, dir);
   2260      width = aprocessor.GetWidth();
   2261      totalWidth += width;
   2262      if (dir == BidiDirection::RTL) {
   2263        xOffset -= width;
   2264      }
   2265      if (aMode == MODE_DRAW) {
   2266        aprocessor.DrawText(xOffset);
   2267      }
   2268 
   2269      /*
   2270       * The caller may request to calculate the visual position of one
   2271       * or more characters.
   2272       */
   2273      for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) {
   2274        nsBidiPositionResolve* posResolve = &aPosResolve[nPosResolve];
   2275        /*
   2276         * Did we already resolve this position's visual metric? If so, skip.
   2277         */
   2278        if (posResolve->visualLeftTwips != kNotFound) {
   2279          continue;
   2280        }
   2281 
   2282        /*
   2283         * First find out if the logical position is within this run.
   2284         */
   2285        if (start <= posResolve->logicalIndex &&
   2286            start + subRunLength > posResolve->logicalIndex) {
   2287          /*
   2288           * If this run is only one character long, we have an easy case:
   2289           * the visual position is the x-coord of the start of the run
   2290           * less the x-coord of the start of the whole text.
   2291           */
   2292          if (subRunLength == 1) {
   2293            posResolve->visualIndex = visualStart;
   2294            posResolve->visualLeftTwips = xOffset;
   2295            posResolve->visualWidth = width;
   2296          }
   2297          /*
   2298           * Otherwise, we need to measure the width of the run's part
   2299           * which is to the visual left of the index.
   2300           * In other words, the run is broken in two, around the logical index,
   2301           * and we measure the part which is visually left.
   2302           * If the run is right-to-left, this part will span from after the
   2303           * index up to the end of the run; if it is left-to-right, this part
   2304           * will span from the start of the run up to (and inclduing) the
   2305           * character before the index.
   2306           */
   2307          else {
   2308            /*
   2309             * Here is a description of how the width of the current character
   2310             * (posResolve->visualWidth) is calculated:
   2311             *
   2312             * LTR (current char: "P"):
   2313             *    S A M P L E          (logical index: 3, visual index: 3)
   2314             *    ^ (visualLeftPart)
   2315             *    ^ (visualRightSide)
   2316             *    visualLeftLength == 3
   2317             *    ^^^^^^ (subWidth)
   2318             *    ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
   2319             *          ^^ (posResolve->visualWidth)
   2320             *
   2321             * RTL (current char: "M"):
   2322             *    E L P M A S          (logical index: 2, visual index: 3)
   2323             *        ^ (visualLeftPart)
   2324             *          ^ (visualRightSide)
   2325             *    visualLeftLength == 3
   2326             *    ^^^^^^ (subWidth)
   2327             *    ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
   2328             *          ^^ (posResolve->visualWidth)
   2329             */
   2330            nscoord subWidth;
   2331            // The position in the text where this run's "left part" begins.
   2332            const char16_t* visualLeftPart;
   2333            const char16_t* visualRightSide;
   2334            if (dir == BidiDirection::RTL) {
   2335              // One day, son, this could all be replaced with
   2336              // mPresContext->BidiEngine().GetVisualIndex() ...
   2337              posResolve->visualIndex =
   2338                  visualStart +
   2339                  (subRunLength - (posResolve->logicalIndex + 1 - start));
   2340              // Skipping to the "left part".
   2341              visualLeftPart = aText + posResolve->logicalIndex + 1;
   2342              // Skipping to the right side of the current character
   2343              visualRightSide = visualLeftPart - 1;
   2344            } else {
   2345              posResolve->visualIndex =
   2346                  visualStart + (posResolve->logicalIndex - start);
   2347              // Skipping to the "left part".
   2348              visualLeftPart = aText + start;
   2349              // In LTR mode this is the same as visualLeftPart
   2350              visualRightSide = visualLeftPart;
   2351            }
   2352            // The delta between the start of the run and the left part's end.
   2353            int32_t visualLeftLength = posResolve->visualIndex - visualStart;
   2354            aprocessor.SetText(visualLeftPart, visualLeftLength, dir);
   2355            subWidth = aprocessor.GetWidth();
   2356            aprocessor.SetText(visualRightSide, visualLeftLength + 1, dir);
   2357            posResolve->visualLeftTwips = xOffset + subWidth;
   2358            posResolve->visualWidth = aprocessor.GetWidth() - subWidth;
   2359          }
   2360        }
   2361      }
   2362 
   2363      if (dir == BidiDirection::LTR) {
   2364        xOffset += width;
   2365      }
   2366 
   2367      --subRunCount;
   2368      start = lineOffset;
   2369      subRunLimit = typeLimit;
   2370      subRunLength = typeLimit - lineOffset;
   2371    }  // while
   2372    if (dir == BidiDirection::RTL) {
   2373      xOffset = xEndRun;
   2374    }
   2375 
   2376    visualStart += length;
   2377  }  // for
   2378 
   2379  if (aWidth) {
   2380    *aWidth = totalWidth;
   2381  }
   2382  return NS_OK;
   2383 }
   2384 
   2385 // This is called either for a single character (one code unit, or a surrogate
   2386 // pair), or for a run that is known to be purely LTR.
   2387 void nsBidiPresUtils::ProcessSimpleRun(const char16_t* aText, size_t aLength,
   2388                                       BidiEmbeddingLevel aBaseLevel,
   2389                                       nsPresContext* aPresContext,
   2390                                       BidiProcessor& aprocessor, Mode aMode,
   2391                                       nsBidiPositionResolve* aPosResolve,
   2392                                       int32_t aPosResolveCount,
   2393                                       nscoord* aWidth) {
   2394  if (!aLength) {
   2395    if (aWidth) {
   2396      *aWidth = 0;
   2397    }
   2398    return;
   2399  }
   2400  // Get bidi class from the first (or only) character.
   2401  uint32_t ch = aText[0];
   2402  if (aLength > 1 && NS_IS_HIGH_SURROGATE(ch) &&
   2403      NS_IS_LOW_SURROGATE(aText[1])) {
   2404    ch = SURROGATE_TO_UCS4(aText[0], aText[1]);
   2405  }
   2406  BidiClass bidiClass = intl::UnicodeProperties::GetBidiClass(ch);
   2407 
   2408  nsAutoString runVisualText(aText, aLength);
   2409  int32_t length = aLength;
   2410  if (aPresContext) {
   2411    FormatUnicodeText(aPresContext, runVisualText.BeginWriting(), length,
   2412                      bidiClass);
   2413  }
   2414 
   2415  BidiDirection dir = bidiClass == BidiClass::RightToLeft ||
   2416                              bidiClass == BidiClass::RightToLeftArabic
   2417                          ? BidiDirection::RTL
   2418                          : BidiDirection::LTR;
   2419  aprocessor.SetText(runVisualText.get(), length, dir);
   2420 
   2421  if (aMode == MODE_DRAW) {
   2422    aprocessor.DrawText(0);
   2423  }
   2424 
   2425  if (!aWidth && !aPosResolve) {
   2426    return;
   2427  }
   2428 
   2429  nscoord width = aprocessor.GetWidth();
   2430 
   2431  for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) {
   2432    nsBidiPositionResolve* posResolve = &aPosResolve[nPosResolve];
   2433    if (posResolve->visualLeftTwips != kNotFound) {
   2434      continue;
   2435    }
   2436    if (0 <= posResolve->logicalIndex && length > posResolve->logicalIndex) {
   2437      posResolve->visualIndex = 0;
   2438      posResolve->visualLeftTwips = 0;
   2439      posResolve->visualWidth = width;
   2440    }
   2441  }
   2442 
   2443  if (aWidth) {
   2444    *aWidth = width;
   2445  }
   2446 }
   2447 
   2448 class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor final
   2449    : public nsBidiPresUtils::BidiProcessor {
   2450 public:
   2451  typedef gfx::DrawTarget DrawTarget;
   2452 
   2453  nsIRenderingContextBidiProcessor(gfxContext* aCtx,
   2454                                   DrawTarget* aTextRunConstructionDrawTarget,
   2455                                   nsFontMetrics* aFontMetrics,
   2456                                   const nsPoint& aPt)
   2457      : mCtx(aCtx),
   2458        mTextRunConstructionDrawTarget(aTextRunConstructionDrawTarget),
   2459        mFontMetrics(aFontMetrics),
   2460        mPt(aPt),
   2461        mText(nullptr),
   2462        mLength(0) {}
   2463 
   2464  ~nsIRenderingContextBidiProcessor() { mFontMetrics->SetTextRunRTL(false); }
   2465 
   2466  virtual void SetText(const char16_t* aText, int32_t aLength,
   2467                       BidiDirection aDirection) override {
   2468    mFontMetrics->SetTextRunRTL(aDirection == BidiDirection::RTL);
   2469    mText = aText;
   2470    mLength = aLength;
   2471  }
   2472 
   2473  virtual nscoord GetWidth() override {
   2474    return nsLayoutUtils::AppUnitWidthOfString(mText, mLength, *mFontMetrics,
   2475                                               mTextRunConstructionDrawTarget);
   2476  }
   2477 
   2478  virtual void DrawText(nscoord aIOffset) override {
   2479    nsPoint pt(mPt);
   2480    if (mFontMetrics->GetVertical()) {
   2481      pt.y += aIOffset;
   2482    } else {
   2483      pt.x += aIOffset;
   2484    }
   2485    mFontMetrics->DrawString(mText, mLength, pt.x, pt.y, mCtx,
   2486                             mTextRunConstructionDrawTarget);
   2487  }
   2488 
   2489 private:
   2490  gfxContext* mCtx;
   2491  DrawTarget* mTextRunConstructionDrawTarget;
   2492  nsFontMetrics* mFontMetrics;
   2493  nsPoint mPt;
   2494  const char16_t* mText;
   2495  int32_t mLength;
   2496 };
   2497 
   2498 nsresult nsBidiPresUtils::ProcessTextForRenderingContext(
   2499    const char16_t* aText, int32_t aLength, BidiEmbeddingLevel aBaseLevel,
   2500    nsPresContext* aPresContext, gfxContext& aRenderingContext,
   2501    DrawTarget* aTextRunConstructionDrawTarget, nsFontMetrics& aFontMetrics,
   2502    Mode aMode, nscoord aX, nscoord aY, nsBidiPositionResolve* aPosResolve,
   2503    int32_t aPosResolveCount, nscoord* aWidth) {
   2504  nsIRenderingContextBidiProcessor processor(&aRenderingContext,
   2505                                             aTextRunConstructionDrawTarget,
   2506                                             &aFontMetrics, nsPoint(aX, aY));
   2507  nsDependentSubstring text(aText, aLength);
   2508  auto separatorIndex = text.FindCharInSet(kSeparators);
   2509  if (separatorIndex == kNotFound) {
   2510    return ProcessText(text.BeginReading(), text.Length(), aBaseLevel,
   2511                       aPresContext, processor, aMode, aPosResolve,
   2512                       aPosResolveCount, aWidth, aPresContext->BidiEngine());
   2513  }
   2514 
   2515  // We need to replace any block or segment separators with space for bidi
   2516  // processing, so make a local copy.
   2517  nsAutoString localText(text);
   2518  ReplaceSeparators(localText, separatorIndex);
   2519  return ProcessText(localText.BeginReading(), localText.Length(), aBaseLevel,
   2520                     aPresContext, processor, aMode, aPosResolve,
   2521                     aPosResolveCount, aWidth, aPresContext->BidiEngine());
   2522 }
   2523 
   2524 /* static */
   2525 BidiEmbeddingLevel nsBidiPresUtils::BidiLevelFromStyle(
   2526    ComputedStyle* aComputedStyle) {
   2527  if (aComputedStyle->StyleTextReset()->mUnicodeBidi ==
   2528      StyleUnicodeBidi::Plaintext) {
   2529    return BidiEmbeddingLevel::DefaultLTR();
   2530  }
   2531 
   2532  if (aComputedStyle->StyleVisibility()->mDirection == StyleDirection::Rtl) {
   2533    return BidiEmbeddingLevel::RTL();
   2534  }
   2535 
   2536  return BidiEmbeddingLevel::LTR();
   2537 }