Bug 1052924 - Implement basic line breaking for ruby. r=dbaron
authorXidorn Quan <quanxunzhen@gmail.com>
Wed, 26 Nov 2014 15:52:50 +1100
changeset 217542 96c4dd714d56909ca3ee5ea5791c162cf3bf72f5
parent 217541 0ea4763c76f3e9e8509713082495bcc4487e8c18
child 217543 3928aae210c33dea09016c49e6a688233ee923de
push id52308
push userxquan@mozilla.com
push dateWed, 26 Nov 2014 04:53:51 +0000
treeherdermozilla-inbound@b657497010c2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs1052924, 1098272
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1052924 - Implement basic line breaking for ruby. r=dbaron Known problem: It would cause infinite loop if there is any line break happens inside ruby base or annotation, or the width of container is not enough for the widest pair/span. This might be fixed in bug 1098272.
layout/base/nsCSSFrameConstructor.cpp
layout/generic/nsContainerFrame.cpp
layout/generic/nsContainerFrame.h
layout/generic/nsRubyBaseContainerFrame.cpp
layout/generic/nsRubyBaseContainerFrame.h
layout/generic/nsRubyFrame.cpp
layout/generic/nsRubyFrame.h
layout/generic/nsRubyTextContainerFrame.cpp
layout/generic/nsRubyTextContainerFrame.h
layout/reftests/css-ruby/reftest.list
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -8711,19 +8711,22 @@ nsCSSFrameConstructor::CreateContinuingF
     }
     newFrame = fieldset;
   } else if (nsGkAtoms::legendFrame == frameType) {
     newFrame = NS_NewLegendFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
   } else if (nsGkAtoms::flexContainerFrame == frameType) {
     newFrame = NS_NewFlexContainerFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
-    //TODO: Add conditionals for rubyFrame and rubyBaseContainerFrame
-    // once their reflow methods are advanced enough to return
-    // non-complete statuses
+  } else if (nsGkAtoms::rubyFrame == frameType) {
+    newFrame = NS_NewRubyFrame(shell, styleContext);
+    newFrame->Init(content, aParentFrame, aFrame);
+  } else if (nsGkAtoms::rubyBaseContainerFrame == frameType) {
+    newFrame = NS_NewRubyBaseContainerFrame(shell, styleContext);
+    newFrame->Init(content, aParentFrame, aFrame);
   } else if (nsGkAtoms::rubyTextContainerFrame == frameType) {
     newFrame = NS_NewRubyTextContainerFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
   } else {
     NS_RUNTIMEABORT("unexpected frame type");
   }
 
   // Init() set newFrame to be a fluid continuation of aFrame.
--- a/layout/generic/nsContainerFrame.cpp
+++ b/layout/generic/nsContainerFrame.cpp
@@ -1512,16 +1512,69 @@ nsContainerFrame::DrainSelfOverflowList(
   if (overflowFrames) {
     NS_ASSERTION(mFrames.NotEmpty(), "overflow list w/o frames");
     mFrames.AppendFrames(nullptr, *overflowFrames);
     return true;
   }
   return false;
 }
 
+nsIFrame*
+nsContainerFrame::GetNextInFlowChild(ContinuationTraversingState& aState,
+                                     bool* aIsInOverflow)
+{
+  nsContainerFrame*& nextInFlow = aState.mNextInFlow;
+  while (nextInFlow) {
+    // See if there is any frame in the container
+    nsIFrame* frame = nextInFlow->mFrames.FirstChild();
+    if (frame) {
+      if (aIsInOverflow) {
+        *aIsInOverflow = false;
+      }
+      return frame;
+    }
+    // No frames in the principal list, try its overflow list
+    nsFrameList* overflowFrames = nextInFlow->GetOverflowFrames();
+    if (overflowFrames) {
+      if (aIsInOverflow) {
+        *aIsInOverflow = true;
+      }
+      return overflowFrames->FirstChild();
+    }
+    nextInFlow = static_cast<nsContainerFrame*>(nextInFlow->GetNextInFlow());
+  }
+  return nullptr;
+}
+
+nsIFrame*
+nsContainerFrame::PullNextInFlowChild(ContinuationTraversingState& aState)
+{
+  bool isInOverflow;
+  nsIFrame* frame = GetNextInFlowChild(aState, &isInOverflow);
+  if (frame) {
+    nsContainerFrame* nextInFlow = aState.mNextInFlow;
+    if (isInOverflow) {
+      nsFrameList* overflowFrames = nextInFlow->GetOverflowFrames();
+      overflowFrames->RemoveFirstChild();
+      if (overflowFrames->IsEmpty()) {
+        nextInFlow->DestroyOverflowList();
+      }
+    } else {
+      nextInFlow->mFrames.RemoveFirstChild();
+    }
+
+    // Move the frame to the principal frame list of this container
+    mFrames.AppendFrame(this, frame);
+    // AppendFrame has reparented the frame, we need
+    // to reparent the frame view then.
+    nsContainerFrame::ReparentFrameView(frame, nextInFlow, this);
+  }
+  return frame;
+}
+
 nsOverflowContinuationTracker::nsOverflowContinuationTracker(nsContainerFrame* aFrame,
                                                              bool              aWalkOOFFrames,
                                                              bool              aSkipOverflowContainerChildren)
   : mOverflowContList(nullptr),
     mPrevOverflowCont(nullptr),
     mSentry(nullptr),
     mParent(aFrame),
     mSkipOverflowContainerChildren(aSkipOverflowContainerChildren),
--- a/layout/generic/nsContainerFrame.h
+++ b/layout/generic/nsContainerFrame.h
@@ -523,16 +523,42 @@ protected:
    *            aPrevSibling
    * @param   aPrevSibling aFromChild's previous sibling. Must not be null.
    *            It's an error to push a parent's first child frame
    */
   void PushChildren(nsIFrame* aFromChild, nsIFrame* aPrevSibling);
 
   // ==========================================================================
   /*
+   * Convenience methods for traversing continuations
+   */
+
+  struct ContinuationTraversingState
+  {
+    nsContainerFrame* mNextInFlow;
+    ContinuationTraversingState(nsContainerFrame* aFrame)
+      : mNextInFlow(static_cast<nsContainerFrame*>(aFrame->GetNextInFlow()))
+    { }
+  };
+
+  /**
+   * Find the first frame that is a child of this frame's next-in-flows,
+   * considering both their principal child lists and overflow lists.
+   */
+  nsIFrame* GetNextInFlowChild(ContinuationTraversingState& aState,
+                               bool* aIsInOverflow = nullptr);
+
+  /**
+   * Remove the result of GetNextInFlowChild from its current parent and
+   * append it to this frame's principal child list.
+   */
+  nsIFrame* PullNextInFlowChild(ContinuationTraversingState& aState);
+
+  // ==========================================================================
+  /*
    * Convenience methods for nsFrameLists stored in the
    * PresContext's proptable
    */
 
   /**
    * Get the PresContext-stored nsFrameList named aPropID for this frame.
    * May return null.
    */
--- a/layout/generic/nsRubyBaseContainerFrame.cpp
+++ b/layout/generic/nsRubyBaseContainerFrame.cpp
@@ -9,18 +9,16 @@
 #include "nsRubyBaseContainerFrame.h"
 #include "nsLineLayout.h"
 #include "nsPresContext.h"
 #include "nsStyleContext.h"
 #include "WritingModes.h"
 
 using namespace mozilla;
 
-#define RTC_ARRAY_SIZE 1
-
 //----------------------------------------------------------------------
 
 // Frame class boilerplate
 // =======================
 
 NS_QUERYFRAME_HEAD(nsRubyBaseContainerFrame)
   NS_QUERYFRAME_ENTRY(nsRubyBaseContainerFrame)
 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
@@ -62,17 +60,17 @@ public:
 
   void Next();
   bool AtEnd() const;
 
   uint32_t GetLevelCount() const { return mFrames.Length(); }
   nsIFrame* GetFrame(uint32_t aIndex) const { return mFrames[aIndex]; }
   nsIFrame* GetBaseFrame() const { return GetFrame(0); }
   nsIFrame* GetTextFrame(uint32_t aIndex) const { return GetFrame(aIndex + 1); }
-  void GetTextFrames(nsTArray<nsIFrame*>& aFrames) const;
+  void GetFrames(nsIFrame*& aBaseFrame, nsTArray<nsIFrame*>& aTextFrames) const;
 
 private:
   nsAutoTArray<nsIFrame*, RTC_ARRAY_SIZE + 1> mFrames;
 };
 
 PairEnumerator::PairEnumerator(
     nsRubyBaseContainerFrame* aBaseContainer,
     const nsTArray<nsRubyTextContainerFrame*>& aTextContainers)
@@ -103,21 +101,23 @@ PairEnumerator::AtEnd() const
     if (mFrames[i]) {
       return false;
     }
   }
   return true;
 }
 
 void
-PairEnumerator::GetTextFrames(nsTArray<nsIFrame*>& aFrames) const
+PairEnumerator::GetFrames(nsIFrame*& aBaseFrame,
+                          nsTArray<nsIFrame*>& aTextFrames) const
 {
-  aFrames.ClearAndRetainStorage();
+  aBaseFrame = mFrames[0];
+  aTextFrames.ClearAndRetainStorage();
   for (uint32_t i = 1, iend = mFrames.Length(); i < iend; i++) {
-    aFrames.AppendElement(mFrames[i]);
+    aTextFrames.AppendElement(mFrames[i]);
   }
 }
 
 nscoord
 nsRubyBaseContainerFrame::CalculateMaxSpanISize(
     nsRenderingContext* aRenderingContext)
 {
   nscoord max = 0;
@@ -186,24 +186,26 @@ nsRubyBaseContainerFrame::IsFrameOfType(
          ~(nsIFrame::eLineParticipant));
 }
 
 void nsRubyBaseContainerFrame::AppendTextContainer(nsIFrame* aFrame)
 {
   nsRubyTextContainerFrame* rtcFrame = do_QueryFrame(aFrame);
   MOZ_ASSERT(rtcFrame, "Must provide a ruby text container.");
 
-  nsIFrame* onlyChild = rtcFrame->PrincipalChildList().OnlyChild();
-  if (onlyChild && onlyChild->IsPseudoFrame(rtcFrame->GetContent())) {
-    // Per CSS Ruby spec, if the only child of an rtc frame is
-    // a pseudo rt frame, it spans all bases in the segment.
-    mSpanContainers.AppendElement(rtcFrame);
-  } else {
-    mTextContainers.AppendElement(rtcFrame);
+  nsTArray<nsRubyTextContainerFrame*>* containers = &mTextContainers;
+  if (!GetPrevContinuation() && !GetNextContinuation()) {
+    nsIFrame* onlyChild = rtcFrame->PrincipalChildList().OnlyChild();
+    if (onlyChild && onlyChild->IsPseudoFrame(rtcFrame->GetContent())) {
+      // Per CSS Ruby spec, if the only child of an rtc frame is
+      // a pseudo rt frame, it spans all bases in the segment.
+      containers = &mSpanContainers;
+    }
   }
+  containers->AppendElement(rtcFrame);
 }
 
 void nsRubyBaseContainerFrame::ClearTextContainers() {
   mSpanContainers.Clear();
   mTextContainers.Clear();
 }
 
 /* virtual */ bool
@@ -228,16 +230,28 @@ nsRubyBaseContainerFrame::ComputeSize(ns
 }
 
 /* virtual */ nscoord
 nsRubyBaseContainerFrame::GetLogicalBaseline(WritingMode aWritingMode) const
 {
   return mBaseline;
 }
 
+// Check whether the given extra isize can fit in the line in base level.
+static bool
+ShouldBreakBefore(const nsHTMLReflowState& aReflowState, nscoord aExtraISize)
+{
+  nsLineLayout* lineLayout = aReflowState.mLineLayout;
+  int32_t offset;
+  gfxBreakPriority priority;
+  nscoord icoord = lineLayout->GetCurrentICoord();
+  return icoord + aExtraISize > aReflowState.AvailableISize() &&
+         lineLayout->GetLastOptionalBreakPosition(&offset, &priority);
+}
+
 /* virtual */ void
 nsRubyBaseContainerFrame::Reflow(nsPresContext* aPresContext,
                                  nsHTMLReflowMetrics& aDesiredSize,
                                  const nsHTMLReflowState& aReflowState,
                                  nsReflowStatus& aStatus)
 {
   DO_GLOBAL_REFLOW_COUNT("nsRubyBaseContainerFrame");
   DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
@@ -245,30 +259,36 @@ nsRubyBaseContainerFrame::Reflow(nsPresC
   if (!aReflowState.mLineLayout) {
     NS_ASSERTION(
       aReflowState.mLineLayout,
       "No line layout provided to RubyBaseContainerFrame reflow method.");
     aStatus = NS_FRAME_COMPLETE;
     return;
   }
 
+  MoveOverflowToChildList();
+  // Ask text containers to drain overflows
+  const uint32_t rtcCount = mTextContainers.Length();
+  for (uint32_t i = 0; i < rtcCount; i++) {
+    mTextContainers[i]->MoveOverflowToChildList();
+  }
+
   aStatus = NS_FRAME_COMPLETE;
   WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
   WritingMode frameWM = aReflowState.GetWritingMode();
   LogicalMargin borderPadding = aReflowState.ComputedLogicalBorderPadding();
   nscoord startEdge = borderPadding.IStart(frameWM);
   nscoord endEdge = aReflowState.AvailableISize() - borderPadding.IEnd(frameWM);
 
   aReflowState.mLineLayout->BeginSpan(this, &aReflowState,
                                       startEdge, endEdge, &mBaseline);
 
   LogicalSize availSize(lineWM, aReflowState.AvailableWidth(),
                         aReflowState.AvailableHeight());
 
-  const uint32_t rtcCount = mTextContainers.Length();
   const uint32_t spanCount = mSpanContainers.Length();
   const uint32_t totalCount = rtcCount + spanCount;
   // We have a reflow state and a line layout for each RTC.
   // They are conceptually the state of the RTCs, but we don't actually
   // reflow those RTCs in this code. These two arrays are holders of
   // the reflow states and line layouts.
   nsAutoTArray<UniquePtr<nsHTMLReflowState>, RTC_ARRAY_SIZE> reflowStates;
   nsAutoTArray<UniquePtr<nsLineLayout>, RTC_ARRAY_SIZE> lineLayouts;
@@ -311,124 +331,304 @@ nsRubyBaseContainerFrame::Reflow(nsPresC
 
     lineLayout->BeginLineReflow(borderPadding.IStart(lineWM),
                                 borderPadding.BStart(lineWM),
                                 reflowState->ComputedISize(),
                                 NS_UNCONSTRAINEDSIZE,
                                 false, false, lineWM, containerWidth);
   }
 
-  // Reflow non-span annotations and bases
+  // Reflow pairs excluding any span
   nscoord pairsISize = ReflowPairs(aPresContext, aReflowState,
                                    rtcReflowStates, aStatus);
   nscoord isize = pairsISize;
 
-  // Reflow spans
-  nscoord spanISize = ReflowSpans(aPresContext, aReflowState,
-                                  spanReflowStates, aStatus);
-  if (isize < spanISize) {
-    aReflowState.mLineLayout->AdvanceICoord(spanISize - isize);
-    isize = spanISize;
+  // If there exists any span, the pairs must either be completely
+  // reflowed, or be not reflowed at all.
+  MOZ_ASSERT(NS_INLINE_IS_BREAK_BEFORE(aStatus) ||
+             NS_FRAME_IS_COMPLETE(aStatus) || mSpanContainers.IsEmpty());
+  if (!NS_INLINE_IS_BREAK_BEFORE(aStatus) &&
+      NS_FRAME_IS_COMPLETE(aStatus) && !mSpanContainers.IsEmpty()) {
+    // Reflow spans
+    nscoord spanISize = ReflowSpans(aPresContext, aReflowState,
+                                    spanReflowStates, aStatus);
+    // ReflowSpans never reports break or incomplete, but we still need
+    // to check if it exceed the line.
+    MOZ_ASSERT(aStatus == NS_FRAME_COMPLETE);
+    if (isize < spanISize) {
+      nscoord delta = spanISize - isize;
+      if (ShouldBreakBefore(aReflowState, delta)) {
+        aStatus = NS_INLINE_LINE_BREAK_BEFORE();
+      } else {
+        aReflowState.mLineLayout->AdvanceICoord(delta);
+        isize = spanISize;
+      }
+    }
   }
 
   DebugOnly<nscoord> lineSpanSize = aReflowState.mLineLayout->EndSpan(this);
   // When there are no frames inside the ruby base container, EndSpan
   // will return 0. However, in this case, the actual width of the
   // container could be non-zero because of non-empty ruby annotations.
-  MOZ_ASSERT(isize == lineSpanSize || mFrames.IsEmpty());
+  MOZ_ASSERT(NS_INLINE_IS_BREAK_BEFORE(aStatus) ||
+             isize == lineSpanSize || mFrames.IsEmpty());
   for (uint32_t i = 0; i < totalCount; i++) {
     // It happens before the ruby text container is reflowed, and that
     // when it is reflowed, it will just use this size.
     nsRubyTextContainerFrame* textContainer = i < rtcCount ?
       mTextContainers[i] : mSpanContainers[i - rtcCount];
     textContainer->SetISize(isize);
     lineLayouts[i]->EndLineReflow();
   }
 
   aDesiredSize.ISize(lineWM) = isize;
   nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, aReflowState,
                                          borderPadding, lineWM, frameWM);
 }
 
+/**
+ * This struct stores the continuations after this frame and
+ * corresponding text containers. It is used to speed up looking
+ * ahead for nonempty continuations.
+ */
+struct MOZ_STACK_CLASS nsRubyBaseContainerFrame::PullFrameState
+{
+  ContinuationTraversingState mBase;
+  nsAutoTArray<ContinuationTraversingState, RTC_ARRAY_SIZE> mTexts;
+
+  PullFrameState(nsRubyBaseContainerFrame* aFrame);
+};
+
 nscoord
 nsRubyBaseContainerFrame::ReflowPairs(nsPresContext* aPresContext,
                                       const nsHTMLReflowState& aReflowState,
                                       nsTArray<nsHTMLReflowState*>& aReflowStates,
                                       nsReflowStatus& aStatus)
 {
-  nscoord istart = aReflowState.mLineLayout->GetCurrentICoord();
+  nsLineLayout* lineLayout = aReflowState.mLineLayout;
+  if (!lineLayout->LineIsEmpty()) {
+    // Record break position only if the line is not empty.
+    if (lineLayout->NotifyOptionalBreakPosition(
+          this, 0, true, gfxBreakPriority::eNormalBreak)) {
+      aStatus = NS_INLINE_LINE_BREAK_BEFORE();
+      return 0;
+    }
+  }
+
+  const uint32_t rtcCount = mTextContainers.Length();
+  nscoord istart = lineLayout->GetCurrentICoord();
   nscoord icoord = istart;
+  nsReflowStatus reflowStatus = NS_FRAME_COMPLETE;
+  aStatus = NS_FRAME_COMPLETE;
 
+  mPairCount = 0;
+  nsIFrame* baseFrame = nullptr;
   nsAutoTArray<nsIFrame*, RTC_ARRAY_SIZE> textFrames;
-  textFrames.SetCapacity(mTextContainers.Length());
-  for (PairEnumerator e(this, mTextContainers); !e.AtEnd(); e.Next()) {
-    e.GetTextFrames(textFrames);
+  textFrames.SetCapacity(rtcCount);
+  PairEnumerator e(this, mTextContainers);
+  for (; !e.AtEnd(); e.Next()) {
+    e.GetFrames(baseFrame, textFrames);
+    icoord += ReflowOnePair(aPresContext, aReflowState, aReflowStates,
+                            baseFrame, textFrames, reflowStatus);
+    if (NS_INLINE_IS_BREAK(reflowStatus)) {
+      break;
+    }
+    // We are not handling overflow here.
+    MOZ_ASSERT(reflowStatus == NS_FRAME_COMPLETE);
+  }
+
+  bool isComplete = false;
+  PullFrameState pullFrameState(this);
+  while (!NS_INLINE_IS_BREAK(reflowStatus)) {
+    // We are not handling overflow here.
+    MOZ_ASSERT(reflowStatus == NS_FRAME_COMPLETE);
+
+    // Try pull some frames from next continuations. This call replaces
+    // |baseFrame| and |textFrames| with the frame pulled in each level.
+    PullOnePair(lineLayout, pullFrameState, baseFrame, textFrames, isComplete);
+    if (isComplete) {
+      // No more frames can be pulled.
+      break;
+    }
     icoord += ReflowOnePair(aPresContext, aReflowState, aReflowStates,
-                            e.GetBaseFrame(), textFrames, aStatus);
+                            baseFrame, textFrames, reflowStatus);
+  }
+
+  if (!e.AtEnd() && NS_INLINE_IS_BREAK_AFTER(reflowStatus)) {
+    // The current pair has been successfully placed.
+    // Skip to the next pair and mark break before.
+    e.Next();
+    e.GetFrames(baseFrame, textFrames);
+    reflowStatus = NS_INLINE_LINE_BREAK_BEFORE();
+  }
+  if (!e.AtEnd() || (GetNextInFlow() && !isComplete)) {
+    NS_FRAME_SET_INCOMPLETE(aStatus);
+  } else {
+    // Completely reflow the whole segment, record a break position.
+    // We record an extra break position here because ReflowOnePair
+    // won't record any break position if there exist spans.
+    if (lineLayout->NotifyOptionalBreakPosition(
+          this, INT32_MAX, true, gfxBreakPriority::eNormalBreak)) {
+      reflowStatus = NS_INLINE_LINE_BREAK_AFTER(reflowStatus);
+    }
+  }
+
+  if (NS_INLINE_IS_BREAK_BEFORE(reflowStatus)) {
+    if (!mPairCount || !mSpanContainers.IsEmpty()) {
+      // If no pair has been placed yet, or we have any span,
+      // the whole container should be in the next line.
+      aStatus = NS_INLINE_LINE_BREAK_BEFORE();
+      return 0;
+    }
+    aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
+    MOZ_ASSERT(NS_FRAME_IS_COMPLETE(aStatus) || mSpanContainers.IsEmpty());
+
+    if (baseFrame) {
+      PushChildren(baseFrame, baseFrame->GetPrevSibling());
+    }
+    for (uint32_t i = 0; i < rtcCount; i++) {
+      nsIFrame* textFrame = textFrames[i];
+      if (textFrame) {
+        mTextContainers[i]->PushChildren(textFrame,
+                                         textFrame->GetPrevSibling());
+      }
+    }
+  } else if (NS_INLINE_IS_BREAK_AFTER(reflowStatus)) {
+    // |reflowStatus| being break after here may only happen when
+    // there is a break after the pair just pulled, or the whole
+    // segment has been completely reflowed. In those cases, we do
+    // not need to push anything.
+    MOZ_ASSERT(e.AtEnd());
+    aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
   }
 
   return icoord - istart;
 }
 
-/* static */ nscoord
+nscoord
 nsRubyBaseContainerFrame::ReflowOnePair(nsPresContext* aPresContext,
                                         const nsHTMLReflowState& aReflowState,
                                         nsTArray<nsHTMLReflowState*>& aReflowStates,
                                         nsIFrame* aBaseFrame,
                                         const nsTArray<nsIFrame*>& aTextFrames,
                                         nsReflowStatus& aStatus)
 {
   WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
-  const uint32_t rtcCount = aTextFrames.Length();
+  const uint32_t rtcCount = mTextContainers.Length();
+  MOZ_ASSERT(aTextFrames.Length() == rtcCount);
   MOZ_ASSERT(aReflowStates.Length() == rtcCount);
   nscoord istart = aReflowState.mLineLayout->GetCurrentICoord();
   nscoord pairISize = 0;
 
+  // Reflow text frames
   for (uint32_t i = 0; i < rtcCount; i++) {
     if (aTextFrames[i]) {
       MOZ_ASSERT(aTextFrames[i]->GetType() == nsGkAtoms::rubyTextFrame);
       nsReflowStatus reflowStatus;
       nsHTMLReflowMetrics metrics(*aReflowStates[i]);
 
       bool pushedFrame;
       aReflowStates[i]->mLineLayout->ReflowFrame(aTextFrames[i], reflowStatus,
                                                  &metrics, pushedFrame);
-      NS_ASSERTION(!NS_INLINE_IS_BREAK(reflowStatus),
-                   "Ruby line breaking is not yet implemented");
-      NS_ASSERTION(!pushedFrame, "Ruby line breaking is not yet implemented");
+      if (NS_INLINE_IS_BREAK(reflowStatus)) {
+        // If any breaking occurs when reflowing a ruby text frame,
+        // we should abandon reflowing this pair.
+        aStatus = NS_INLINE_LINE_BREAK_BEFORE();
+        return 0;
+      }
+      MOZ_ASSERT(!pushedFrame, "Shouldn't push frame if there is no break");
       pairISize = std::max(pairISize, metrics.ISize(lineWM));
     }
   }
+  if (ShouldBreakBefore(aReflowState, pairISize)) {
+    // Since ruby text container uses an independent line layout, it
+    // may successfully place a frame because the line is empty while
+    // the line of base container is not.
+    aStatus = NS_INLINE_LINE_BREAK_BEFORE();
+    return 0;
+  }
 
+  // Reflow the base frame
   if (aBaseFrame) {
     MOZ_ASSERT(aBaseFrame->GetType() == nsGkAtoms::rubyBaseFrame);
     nsReflowStatus reflowStatus;
     nsHTMLReflowMetrics metrics(aReflowState);
 
     bool pushedFrame;
     aReflowState.mLineLayout->ReflowFrame(aBaseFrame, reflowStatus,
                                           &metrics, pushedFrame);
-    NS_ASSERTION(!NS_INLINE_IS_BREAK(reflowStatus),
-                 "Ruby line breaking is not yet implemented");
-    NS_ASSERTION(!pushedFrame, "Ruby line breaking is not yet implemented");
+    if (NS_INLINE_IS_BREAK(reflowStatus)) {
+      // We cannot place the ruby base frame. Abandon this pair.
+      aStatus = NS_INLINE_LINE_BREAK_BEFORE();
+      return 0;
+    }
+    MOZ_ASSERT(!pushedFrame, "Shouldn't push frame if there is no break");
     pairISize = std::max(pairISize, metrics.ISize(lineWM));
   }
 
   // Align all the line layout to the new coordinate.
   nscoord icoord = istart + pairISize;
   aReflowState.mLineLayout->AdvanceICoord(
     icoord - aReflowState.mLineLayout->GetCurrentICoord());
   for (uint32_t i = 0; i < rtcCount; i++) {
     nsLineLayout* lineLayout = aReflowStates[i]->mLineLayout;
     lineLayout->AdvanceICoord(icoord - lineLayout->GetCurrentICoord());
   }
 
+  mPairCount++;
+  // We only break between bases if there is no span.
+  if (mSpanContainers.IsEmpty()) {
+    if (aReflowState.mLineLayout->NotifyOptionalBreakPosition(
+          this, mPairCount, icoord <= aReflowState.AvailableISize(),
+          gfxBreakPriority::eNormalBreak)) {
+      aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
+    }
+  }
+
   return pairISize;
 }
 
+nsRubyBaseContainerFrame::PullFrameState::PullFrameState(
+    nsRubyBaseContainerFrame* aFrame)
+  : mBase(aFrame)
+{
+  const uint32_t rtcCount = aFrame->mTextContainers.Length();
+  for (uint32_t i = 0; i < rtcCount; i++) {
+    mTexts.AppendElement(aFrame->mTextContainers[i]);
+  }
+}
+
+void
+nsRubyBaseContainerFrame::PullOnePair(nsLineLayout* aLineLayout,
+                                      PullFrameState& aPullFrameState,
+                                      nsIFrame*& aBaseFrame,
+                                      nsTArray<nsIFrame*>& aTextFrames,
+                                      bool& aIsComplete)
+{
+  const uint32_t rtcCount = mTextContainers.Length();
+
+  aBaseFrame = PullNextInFlowChild(aPullFrameState.mBase);
+  aIsComplete = !aBaseFrame;
+
+  aTextFrames.ClearAndRetainStorage();
+  for (uint32_t i = 0; i < rtcCount; i++) {
+    nsIFrame* nextText =
+      mTextContainers[i]->PullNextInFlowChild(aPullFrameState.mTexts[i]);
+    aTextFrames.AppendElement(nextText);
+    // If there exists any frame in continations, we haven't
+    // completed the reflow process.
+    aIsComplete = aIsComplete && !nextText;
+  }
+
+  if (!aIsComplete) {
+    // We pulled frames from the next line, hence mark it dirty.
+    aLineLayout->SetDirtyNextLine();
+  }
+}
+
 nscoord
 nsRubyBaseContainerFrame::ReflowSpans(nsPresContext* aPresContext,
                                       const nsHTMLReflowState& aReflowState,
                                       nsTArray<nsHTMLReflowState*>& aReflowStates,
                                       nsReflowStatus& aStatus)
 {
   WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
   const uint32_t spanCount = mSpanContainers.Length();
@@ -437,16 +637,16 @@ nsRubyBaseContainerFrame::ReflowSpans(ns
   for (uint32_t i = 0; i < spanCount; i++) {
     nsRubyTextContainerFrame* container = mSpanContainers[i];
     nsIFrame* rtFrame = container->GetFirstPrincipalChild();
     nsReflowStatus reflowStatus;
     nsHTMLReflowMetrics metrics(*aReflowStates[i]);
     bool pushedFrame;
     aReflowStates[i]->mLineLayout->ReflowFrame(rtFrame, reflowStatus,
                                                &metrics, pushedFrame);
-    NS_ASSERTION(!NS_INLINE_IS_BREAK(reflowStatus),
-                 "Ruby line breaking is not yet implemented");
-    NS_ASSERTION(!pushedFrame, "Ruby line breaking is not yet implemented");
+    // It should never cause break, since it is always
+    // at the beginning in its line layout.
+    MOZ_ASSERT(!NS_INLINE_IS_BREAK(reflowStatus) && !pushedFrame);
     spanISize = std::max(spanISize, metrics.ISize(lineWM));
   }
 
   return spanISize;
 }
--- a/layout/generic/nsRubyBaseContainerFrame.h
+++ b/layout/generic/nsRubyBaseContainerFrame.h
@@ -9,16 +9,18 @@
 #ifndef nsRubyBaseContainerFrame_h___
 #define nsRubyBaseContainerFrame_h___
 
 #include "nsContainerFrame.h"
 #include "nsRubyTextContainerFrame.h"
 #include "nsRubyBaseFrame.h"
 #include "nsRubyTextFrame.h"
 
+#define RTC_ARRAY_SIZE 1
+
 /**
  * Factory function.
  * @return a newly allocated nsRubyBaseContainerFrame (infallible)
  */
 nsContainerFrame* NS_NewRubyBaseContainerFrame(nsIPresShell* aPresShell,
                                                nsStyleContext* aContext);
 
 class nsRubyBaseContainerFrame MOZ_FINAL : public nsContainerFrame
@@ -76,36 +78,47 @@ protected:
 
   nscoord CalculateMaxSpanISize(nsRenderingContext* aRenderingContext);
 
   nscoord ReflowPairs(nsPresContext* aPresContext,
                       const nsHTMLReflowState& aReflowState,
                       nsTArray<nsHTMLReflowState*>& aReflowStates,
                       nsReflowStatus& aStatus);
 
-  static nscoord ReflowOnePair(nsPresContext* aPresContext,
-                               const nsHTMLReflowState& aReflowState,
-                               nsTArray<nsHTMLReflowState*>& aReflowStates,
-                               nsIFrame* aBaseFrame,
-                               const nsTArray<nsIFrame*>& aTextFrames,
-                               nsReflowStatus& aStatus);
+  nscoord ReflowOnePair(nsPresContext* aPresContext,
+                        const nsHTMLReflowState& aReflowState,
+                        nsTArray<nsHTMLReflowState*>& aReflowStates,
+                        nsIFrame* aBaseFrame,
+                        const nsTArray<nsIFrame*>& aTextFrames,
+                        nsReflowStatus& aStatus);
 
   nscoord ReflowSpans(nsPresContext* aPresContext,
                       const nsHTMLReflowState& aReflowState,
                       nsTArray<nsHTMLReflowState*>& aReflowStates,
                       nsReflowStatus& aStatus);
 
+  struct PullFrameState;
+
+  // Pull ruby base and corresponding ruby text frames from
+  // continuations after them.
+  void PullOnePair(nsLineLayout* aLineLayout,
+                   PullFrameState& aPullFrameState,
+                   nsIFrame*& aBaseFrame,
+                   nsTArray<nsIFrame*>& aTextFrames,
+                   bool& aIsComplete);
+
   /**
    * The arrays of ruby text containers below are filled before the ruby
    * frame (parent) starts reflowing this ruby segment, and cleared when
    * the reflow finishes.
    */
 
   // The text containers that contain a span, which spans all ruby
   // pairs in the ruby segment.
   nsTArray<nsRubyTextContainerFrame*> mSpanContainers;
   // Normal text containers that do not contain spans.
   nsTArray<nsRubyTextContainerFrame*> mTextContainers;
 
   nscoord mBaseline;
+  uint32_t mPairCount;
 };
 
 #endif /* nsRubyBaseContainerFrame_h___ */
--- a/layout/generic/nsRubyFrame.cpp
+++ b/layout/generic/nsRubyFrame.cpp
@@ -224,74 +224,190 @@ nsRubyFrame::Reflow(nsPresContext* aPres
   
   if (!aReflowState.mLineLayout) {
     NS_ASSERTION(aReflowState.mLineLayout,
                  "No line layout provided to RubyFrame reflow method.");
     aStatus = NS_FRAME_COMPLETE;
     return;
   }
 
+  // Grab overflow frames from prev-in-flow and its own.
+  MoveOverflowToChildList();
+
   // Begin the span for the ruby frame
   WritingMode frameWM = aReflowState.GetWritingMode();
   WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
   LogicalMargin borderPadding = aReflowState.ComputedLogicalBorderPadding();
   nscoord startEdge = borderPadding.IStart(frameWM);
   nscoord endEdge = aReflowState.AvailableISize() - borderPadding.IEnd(frameWM);
   NS_ASSERTION(aReflowState.AvailableISize() != NS_UNCONSTRAINEDSIZE,
                "should no longer use available widths");
   aReflowState.mLineLayout->BeginSpan(this, &aReflowState,
                                       startEdge, endEdge, &mBaseline);
 
-  // FIXME: line breaking / continuations not yet implemented
   aStatus = NS_FRAME_COMPLETE;
   for (SegmentEnumerator e(this); !e.AtEnd(); e.Next()) {
     ReflowSegment(aPresContext, aReflowState, e.GetBaseContainer(), aStatus);
+
+    if (NS_INLINE_IS_BREAK(aStatus)) {
+      // A break occurs when reflowing the segment.
+      // Don't continue reflowing more segments.
+      break;
+    }
   }
 
+  ContinuationTraversingState pullState(this);
+  while (aStatus == NS_FRAME_COMPLETE) {
+    nsRubyBaseContainerFrame* baseContainer = PullOneSegment(pullState);
+    if (!baseContainer) {
+      // No more continuations after, finish now.
+      break;
+    }
+    ReflowSegment(aPresContext, aReflowState, baseContainer, aStatus);
+  }
+  // We never handle overflow in ruby.
+  MOZ_ASSERT(!NS_FRAME_OVERFLOW_IS_INCOMPLETE(aStatus));
+
   aDesiredSize.ISize(lineWM) = aReflowState.mLineLayout->EndSpan(this);
   nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, aReflowState,
                                          borderPadding, lineWM, frameWM);
 }
 
 void
 nsRubyFrame::ReflowSegment(nsPresContext* aPresContext,
                            const nsHTMLReflowState& aReflowState,
                            nsRubyBaseContainerFrame* aBaseContainer,
                            nsReflowStatus& aStatus)
 {
   AutoSetTextContainers holder(aBaseContainer);
   WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
   LogicalSize availSize(lineWM, aReflowState.AvailableISize(),
                         aReflowState.AvailableBSize());
 
-  nsReflowStatus baseReflowStatus;
+  nsAutoTArray<nsRubyTextContainerFrame*, RTC_ARRAY_SIZE> textContainers;
+  for (TextContainerIterator iter(aBaseContainer); !iter.AtEnd(); iter.Next()) {
+    textContainers.AppendElement(iter.GetTextContainer());
+  }
+  const uint32_t rtcCount = textContainers.Length();
+
   nsHTMLReflowMetrics baseMetrics(aReflowState);
   bool pushedFrame;
-  aReflowState.mLineLayout->ReflowFrame(aBaseContainer, baseReflowStatus,
+  aReflowState.mLineLayout->ReflowFrame(aBaseContainer, aStatus,
                                         &baseMetrics, pushedFrame);
 
+  if (NS_INLINE_IS_BREAK_BEFORE(aStatus)) {
+    if (aBaseContainer != mFrames.FirstChild()) {
+      // Some segments may have been reflowed before, hence it is not
+      // a break-before for the ruby container.
+      aStatus = NS_INLINE_LINE_BREAK_AFTER(NS_FRAME_NOT_COMPLETE);
+      PushChildren(aBaseContainer, aBaseContainer->GetPrevSibling());
+      aReflowState.mLineLayout->SetDirtyNextLine();
+    }
+    // This base container is not placed at all, we can skip all
+    // text containers paired with it.
+    return;
+  }
+  if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) {
+    // It always promise that if the status is incomplete, there is a
+    // break occurs. Break before has been processed above. However,
+    // it is possible that break after happens with the frame reflow
+    // completed. It happens if there is a force break at the end.
+    MOZ_ASSERT(NS_INLINE_IS_BREAK_AFTER(aStatus));
+    // Find the previous sibling which we will
+    // insert new continuations after.
+    nsIFrame* lastChild;
+    if (rtcCount > 0) {
+      lastChild = textContainers.LastElement();
+    } else {
+      lastChild = aBaseContainer;
+    }
+
+    // Create continuations for the base container
+    nsIFrame* newBaseContainer;
+    CreateNextInFlow(aBaseContainer, newBaseContainer);
+    // newBaseContainer is null if there are existing next-in-flows.
+    // We only need to move and push if there were not.
+    if (newBaseContainer) {
+      // Move the new frame after all the text containers
+      mFrames.RemoveFrame(newBaseContainer);
+      mFrames.InsertFrame(nullptr, lastChild, newBaseContainer);
+
+      // Create continuations for text containers
+      nsIFrame* newLastChild = newBaseContainer;
+      for (uint32_t i = 0; i < rtcCount; i++) {
+        nsIFrame* newTextContainer;
+        CreateNextInFlow(textContainers[i], newTextContainer);
+        MOZ_ASSERT(newTextContainer, "Next-in-flow of rtc should not exist "
+                   "if the corresponding rbc does not");
+        mFrames.RemoveFrame(newTextContainer);
+        mFrames.InsertFrame(nullptr, newLastChild, newTextContainer);
+        newLastChild = newTextContainer;
+      }
+      PushChildren(newBaseContainer, lastChild);
+      aReflowState.mLineLayout->SetDirtyNextLine();
+    }
+  } else {
+    // If the ruby base container is reflowed completely, the line
+    // layout will remove the next-in-flows of that frame. But the
+    // line layout is not aware of the ruby text containers, hence
+    // it is necessary to remove them here.
+    for (uint32_t i = 0; i < rtcCount; i++) {
+      nsIFrame* nextRTC = textContainers[i]->GetNextInFlow();
+      if (nextRTC) {
+        nextRTC->GetParent()->DeleteNextInFlowChild(nextRTC, true);
+      }
+    }
+  }
+
   nsRect baseRect = aBaseContainer->GetRect();
-  for (TextContainerIterator iter(aBaseContainer); !iter.AtEnd(); iter.Next()) {
-    nsRubyTextContainerFrame* textContainer = iter.GetTextContainer();
+  for (uint32_t i = 0; i < rtcCount; i++) {
+    nsRubyTextContainerFrame* textContainer = textContainers[i];
     nsReflowStatus textReflowStatus;
     nsHTMLReflowMetrics textMetrics(aReflowState);
     nsHTMLReflowState textReflowState(aPresContext, aReflowState,
                                       textContainer, availSize);
+    // FIXME We probably shouldn't be using the same nsLineLayout for
+    //       the text containers. But it should be fine now as we are
+    //       not actually using this line layout to reflow something,
+    //       but just read the writing mode from it.
     textReflowState.mLineLayout = aReflowState.mLineLayout;
     textContainer->Reflow(aPresContext, textMetrics,
                           textReflowState, textReflowStatus);
+    // Ruby text containers always return NS_FRAME_COMPLETE even when
+    // they have continuations, because the breaking has already been
+    // handled when reflowing the base containers.
     NS_ASSERTION(textReflowStatus == NS_FRAME_COMPLETE,
-                 "Ruby line breaking is not yet implemented");
+                 "Ruby text container must not break itself inside");
     textContainer->SetSize(LogicalSize(lineWM, textMetrics.ISize(lineWM),
                                        textMetrics.BSize(lineWM)));
     nscoord x, y;
     nscoord bsize = textMetrics.BSize(lineWM);
     if (lineWM.IsVertical()) {
       x = lineWM.IsVerticalLR() ? -bsize : baseRect.XMost();
       y = baseRect.Y();
     } else {
       x = baseRect.X();
       y = -bsize;
     }
     FinishReflowChild(textContainer, aPresContext, textMetrics,
                       &textReflowState, x, y, 0);
   }
 }
+
+nsRubyBaseContainerFrame*
+nsRubyFrame::PullOneSegment(ContinuationTraversingState& aState)
+{
+  // Pull a ruby base container
+  nsIFrame* baseFrame = PullNextInFlowChild(aState);
+  if (!baseFrame) {
+    return nullptr;
+  }
+  MOZ_ASSERT(baseFrame->GetType() == nsGkAtoms::rubyBaseContainerFrame);
+
+  // Pull all ruby text containers following the base container
+  nsIFrame* nextFrame;
+  while ((nextFrame = GetNextInFlowChild(aState)) != nullptr &&
+         nextFrame->GetType() == nsGkAtoms::rubyTextContainerFrame) {
+    PullNextInFlowChild(aState);
+  }
+
+  return static_cast<nsRubyBaseContainerFrame*>(baseFrame);
+}
--- a/layout/generic/nsRubyFrame.h
+++ b/layout/generic/nsRubyFrame.h
@@ -7,16 +7,17 @@
 /* rendering object for CSS "display: ruby" */
 
 #ifndef nsRubyFrame_h___
 #define nsRubyFrame_h___
 
 #include "nsContainerFrame.h"
 
 class nsRubyBaseContainerFrame;
+class nsRubyTextContainerFrame;
 
 /**
  * Factory function.
  * @return a newly allocated nsRubyFrame (infallible)
  */
 nsContainerFrame* NS_NewRubyFrame(nsIPresShell* aPresShell,
                                   nsStyleContext* aContext);
 
@@ -60,12 +61,14 @@ protected:
                                            nsStyleContext* aContext);
   explicit nsRubyFrame(nsStyleContext* aContext) : nsContainerFrame(aContext) {}
 
   void ReflowSegment(nsPresContext* aPresContext,
                      const nsHTMLReflowState& aReflowState,
                      nsRubyBaseContainerFrame* aBaseContainer,
                      nsReflowStatus& aStatus);
 
+  nsRubyBaseContainerFrame* PullOneSegment(ContinuationTraversingState& aState);
+
   nscoord mBaseline;
 };
 
 #endif /* nsRubyFrame_h___ */
--- a/layout/generic/nsRubyTextContainerFrame.cpp
+++ b/layout/generic/nsRubyTextContainerFrame.cpp
@@ -65,20 +65,26 @@ nsRubyTextContainerFrame::IsFrameOfType(
 nsRubyTextContainerFrame::Reflow(nsPresContext* aPresContext,
                                  nsHTMLReflowMetrics& aDesiredSize,
                                  const nsHTMLReflowState& aReflowState,
                                  nsReflowStatus& aStatus)
 {
   DO_GLOBAL_REFLOW_COUNT("nsRubyTextContainerFrame");
   DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
 
-  // All rt children have already been reflowed. All we need to do is clean up
-  // the line layout.
+  // All rt children have already been reflowed. All we need to do is
+  // to report complete and return the desired size.
 
+  // Although a ruby text container may have continuations, returning
+  // NS_FRAME_COMPLETE here is still safe, since its parent, ruby frame,
+  // ignores the status, and continuations of the ruby base container
+  // will take care of our continuations.
   aStatus = NS_FRAME_COMPLETE;
   WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
   WritingMode frameWM = aReflowState.GetWritingMode();
   LogicalMargin borderPadding = aReflowState.ComputedLogicalBorderPadding();
 
+  // ISize is provided by the ruby base container
+  // during reflow of that container.
   aDesiredSize.ISize(lineWM) = mISize;
   nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, aReflowState,
                                          borderPadding, lineWM, frameWM);
 }
--- a/layout/generic/nsRubyTextContainerFrame.h
+++ b/layout/generic/nsRubyTextContainerFrame.h
@@ -36,24 +36,26 @@ public:
   virtual void Reflow(nsPresContext* aPresContext,
                       nsHTMLReflowMetrics& aDesiredSize,
                       const nsHTMLReflowState& aReflowState,
                       nsReflowStatus& aStatus) MOZ_OVERRIDE;
 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const MOZ_OVERRIDE;
 #endif
-  void SetISize(nscoord aISize) { mISize = aISize; }
 
 protected:
   friend nsContainerFrame*
     NS_NewRubyTextContainerFrame(nsIPresShell* aPresShell,
                                  nsStyleContext* aContext);
   explicit nsRubyTextContainerFrame(nsStyleContext* aContext)
     : nsRubyTextContainerFrameSuper(aContext) {}
 
+  friend class nsRubyBaseContainerFrame;
+  void SetISize(nscoord aISize) { mISize = aISize; }
+
   // The intended dimensions of the ruby text container. These are modified
   // whenever a ruby text box is reflowed and used when the ruby text container
   // is reflowed.
   nscoord mISize;
 };
 
 #endif /* nsRubyTextContainerFrame_h___ */
--- a/layout/reftests/css-ruby/reftest.list
+++ b/layout/reftests/css-ruby/reftest.list
@@ -1,6 +1,6 @@
 default-preferences pref(layout.css.ruby.enabled,true)
 
-asserts(3-20) == ruby-whitespace-1.html ruby-whitespace-1-ref.html # bug 1052924
+== ruby-whitespace-1.html ruby-whitespace-1-ref.html
 == ruby-whitespace-2.html ruby-whitespace-2-ref.html
-asserts(0-1) != ruby-reflow-1-opaqueruby.html ruby-reflow-1-noruby.html # bug 1052924
-asserts(0-1) == ruby-reflow-1-transparentruby.html ruby-reflow-1-noruby.html # bug 1052924
+!= ruby-reflow-1-opaqueruby.html ruby-reflow-1-noruby.html
+== ruby-reflow-1-transparentruby.html ruby-reflow-1-noruby.html