Bug 1099807 part 2 - Implement intra-level whitespace pairing. r=dholbert
authorXidorn Quan <quanxunzhen@gmail.com>
Thu, 08 Jan 2015 18:28:09 +1100
changeset 222600 9e82574ea4f2b6e6f6d16b6b768ec850c739d75e
parent 222599 b936ab379c755646f876d3120c3e105c4e8128eb
child 222601 fb6b0a95a55509375ba5718ee5245fe85790dd01
push id53667
push userxquan@mozilla.com
push dateThu, 08 Jan 2015 07:28:35 +0000
treeherdermozilla-inbound@2238390e5de4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1099807
milestone37.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 1099807 part 2 - Implement intra-level whitespace pairing. r=dholbert Line breaking is not handled properly in this patch. It would be fixed in the part 4.
layout/generic/nsRubyBaseContainerFrame.cpp
layout/generic/nsRubyContentFrame.cpp
layout/generic/nsRubyContentFrame.h
layout/reftests/css-ruby/ruby-whitespace-1-ref.html
--- a/layout/generic/nsRubyBaseContainerFrame.cpp
+++ b/layout/generic/nsRubyBaseContainerFrame.cpp
@@ -72,81 +72,138 @@ class MOZ_STACK_CLASS RubyColumnEnumerat
 public:
   RubyColumnEnumerator(nsRubyBaseContainerFrame* aRBCFrame,
                        const nsTArray<nsRubyTextContainerFrame*>& aRTCFrames);
 
   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); }
+  nsRubyContentFrame* GetFrameAtLevel(uint32_t aIndex) const;
   void GetColumn(RubyColumn& aColumn) const;
 
 private:
-  nsAutoTArray<nsIFrame*, RTC_ARRAY_SIZE + 1> mFrames;
+  // Frames in this array are NOT necessary part of the current column.
+  // When in doubt, use GetFrameAtLevel to access it.
+  // See GetFrameAtLevel() and Next() for more info.
+  nsAutoTArray<nsRubyContentFrame*, RTC_ARRAY_SIZE + 1> mFrames;
+  // Whether we are on a column for intra-level whitespaces
+  bool mAtIntraLevelWhitespace;
 };
 
 RubyColumnEnumerator::RubyColumnEnumerator(
   nsRubyBaseContainerFrame* aBaseContainer,
   const nsTArray<nsRubyTextContainerFrame*>& aTextContainers)
+  : mAtIntraLevelWhitespace(false)
 {
   const uint32_t rtcCount = aTextContainers.Length();
   mFrames.SetCapacity(rtcCount + 1);
-  mFrames.AppendElement(aBaseContainer->GetFirstPrincipalChild());
+
+  nsIFrame* rbFrame = aBaseContainer->GetFirstPrincipalChild();
+  MOZ_ASSERT(!rbFrame || rbFrame->GetType() == nsGkAtoms::rubyBaseFrame);
+  mFrames.AppendElement(static_cast<nsRubyContentFrame*>(rbFrame));
   for (uint32_t i = 0; i < rtcCount; i++) {
     nsRubyTextContainerFrame* container = aTextContainers[i];
     // If the container is for span, leave a nullptr here.
     // Spans do not take part in pairing.
     nsIFrame* rtFrame = !container->IsSpanContainer() ?
-      aTextContainers[i]->GetFirstPrincipalChild() : nullptr;
-    mFrames.AppendElement(rtFrame);
+      container->GetFirstPrincipalChild() : nullptr;
+    MOZ_ASSERT(!rtFrame || rtFrame->GetType() == nsGkAtoms::rubyTextFrame);
+    mFrames.AppendElement(static_cast<nsRubyContentFrame*>(rtFrame));
+  }
+
+  // We have to init mAtIntraLevelWhitespace to be correct for the
+  // first column. There are two ways we could end up with intra-level
+  // whitespace in our first colum:
+  // 1. The current segment itself is an inter-segment whitespace;
+  // 2. If our ruby segment is split across multiple lines, and some
+  //    intra-level whitespace happens to fall right after a line-break.
+  //    Each line will get its own nsRubyBaseContainerFrame, and the
+  //    container right after the line-break will end up with its first
+  //    column containing that intra-level whitespace.
+  for (uint32_t i = 0, iend = mFrames.Length(); i < iend; i++) {
+    nsRubyContentFrame* frame = mFrames[i];
+    if (frame && frame->IsIntraLevelWhitespace()) {
+      mAtIntraLevelWhitespace = true;
+      break;
+    }
   }
 }
 
 void
 RubyColumnEnumerator::Next()
 {
+  bool advancingToIntraLevelWhitespace = false;
   for (uint32_t i = 0, iend = mFrames.Length(); i < iend; i++) {
-    if (mFrames[i]) {
-      mFrames[i] = mFrames[i]->GetNextSibling();
+    nsRubyContentFrame* frame = mFrames[i];
+    // If we've got intra-level whitespace frames at some levels in the
+    // current ruby column, we "faked" an anonymous box for all other
+    // levels for this column. So when we advance off this column, we
+    // don't advance any of the frames in those levels, because we're
+    // just advancing across the "fake" frames.
+    if (frame && (!mAtIntraLevelWhitespace ||
+                  frame->IsIntraLevelWhitespace())) {
+      nsIFrame* nextSibling = frame->GetNextSibling();
+      MOZ_ASSERT(!nextSibling || nextSibling->GetType() == frame->GetType(),
+                 "Frame type should be identical among a level");
+      mFrames[i] = frame = static_cast<nsRubyContentFrame*>(nextSibling);
+      if (!advancingToIntraLevelWhitespace &&
+          frame && frame->IsIntraLevelWhitespace()) {
+        advancingToIntraLevelWhitespace = true;
+      }
     }
   }
+  MOZ_ASSERT(!advancingToIntraLevelWhitespace || !mAtIntraLevelWhitespace,
+             "Should never have adjacent intra-level whitespace columns");
+  mAtIntraLevelWhitespace = advancingToIntraLevelWhitespace;
 }
 
 bool
 RubyColumnEnumerator::AtEnd() const
 {
   for (uint32_t i = 0, iend = mFrames.Length(); i < iend; i++) {
     if (mFrames[i]) {
       return false;
     }
   }
   return true;
 }
 
+nsRubyContentFrame*
+RubyColumnEnumerator::GetFrameAtLevel(uint32_t aIndex) const
+{
+  // If the current ruby column is for intra-level whitespaces, we
+  // return nullptr for any levels that do not have an actual intra-
+  // level whitespace frame in this column.  This nullptr represents
+  // an anonymous empty intra-level whitespace box.  (In this case,
+  // it's important that we NOT return mFrames[aIndex], because it's
+  // really part of the next column, not the current one.)
+  nsRubyContentFrame* frame = mFrames[aIndex];
+  return !mAtIntraLevelWhitespace ||
+         (frame && frame->IsIntraLevelWhitespace()) ? frame : nullptr;
+}
+
 void
 RubyColumnEnumerator::GetColumn(RubyColumn& aColumn) const
 {
-  aColumn.mBaseFrame = mFrames[0];
+  aColumn.mBaseFrame = GetFrameAtLevel(0);
   aColumn.mTextFrames.ClearAndRetainStorage();
   for (uint32_t i = 1, iend = mFrames.Length(); i < iend; i++) {
-    aColumn.mTextFrames.AppendElement(mFrames[i]);
+    aColumn.mTextFrames.AppendElement(GetFrameAtLevel(i));
   }
 }
 
 static nscoord
 CalculateColumnPrefISize(nsRenderingContext* aRenderingContext,
                          const RubyColumnEnumerator& aEnumerator)
 {
   nscoord max = 0;
   uint32_t levelCount = aEnumerator.GetLevelCount();
   for (uint32_t i = 0; i < levelCount; i++) {
-    nsIFrame* frame = aEnumerator.GetFrame(i);
+    nsIFrame* frame = aEnumerator.GetFrameAtLevel(i);
     if (frame) {
       max = std::max(max, frame->GetPrefISize(aRenderingContext));
     }
   }
   return max;
 }
 
 /* virtual */ void
--- a/layout/generic/nsRubyContentFrame.cpp
+++ b/layout/generic/nsRubyContentFrame.cpp
@@ -4,16 +4,17 @@
  * version 2.0 (the "License"). You can obtain a copy of the License at
  * http://mozilla.org/MPL/2.0/. */
 
 /* base class for ruby rendering objects that directly contain content */
 
 #include "nsRubyContentFrame.h"
 #include "nsPresContext.h"
 #include "nsStyleContext.h"
+#include "nsCSSAnonBoxes.h"
 
 using namespace mozilla;
 
 //----------------------------------------------------------------------
 
 // Frame class boilerplate
 // =======================
 
@@ -27,8 +28,21 @@ NS_IMPL_FRAMEARENA_HELPERS(nsRubyContent
 /* virtual */ bool
 nsRubyContentFrame::IsFrameOfType(uint32_t aFlags) const
 {
   if (aFlags & eBidiInlineContainer) {
     return false;
   }
   return nsRubyContentFrameSuper::IsFrameOfType(aFlags);
 }
+
+bool
+nsRubyContentFrame::IsIntraLevelWhitespace() const
+{
+  nsIAtom* pseudoType = StyleContext()->GetPseudo();
+  if (pseudoType != nsCSSAnonBoxes::rubyBase &&
+      pseudoType != nsCSSAnonBoxes::rubyText) {
+    return false;
+  }
+
+  nsIFrame* child = mFrames.OnlyChild();
+  return child && child->GetContent()->TextIsOnlyWhitespace();
+}
--- a/layout/generic/nsRubyContentFrame.h
+++ b/layout/generic/nsRubyContentFrame.h
@@ -16,14 +16,21 @@ typedef nsInlineFrame nsRubyContentFrame
 class nsRubyContentFrame : public nsRubyContentFrameSuper
 {
 public:
   NS_DECL_FRAMEARENA_HELPERS
 
   // nsIFrame overrides
   virtual bool IsFrameOfType(uint32_t aFlags) const MOZ_OVERRIDE;
 
+  // Indicates whether this is an "intra-level whitespace" frame, i.e.
+  // an anonymous frame that was created to contain non-droppable
+  // whitespaces directly inside a ruby level container. This impacts
+  // ruby pairing behavior.
+  // See http://dev.w3.org/csswg/css-ruby/#anon-gen-interpret-space
+  bool IsIntraLevelWhitespace() const;
+
 protected:
   explicit nsRubyContentFrame(nsStyleContext* aContext)
     : nsRubyContentFrameSuper(aContext) {}
 };
 
 #endif /* nsRubyContentFrame_h___ */
--- a/layout/reftests/css-ruby/ruby-whitespace-1-ref.html
+++ b/layout/reftests/css-ruby/ruby-whitespace-1-ref.html
@@ -24,13 +24,13 @@ body { line-height: 5em; }
 
 <p>
   <ruby><rbc><rb>Segment one</rb></rbc
     ><rbc><rb><span>  </span></rb></rbc
     ><rbc><rb><span>  </span></rb><rb><span>    </span></rb><rb>Base three</rb></rbc
     ><rtc><rt><span>  </span></rt><rt><span>     </span></rt><rt>Text three</rt></rtc
     ><rbc><rb><span>  </span></rb></rbc
     ><rbc><rb>Base one</rb><rb><span>   </span></rb><rb>Base three</rb></rbc
-    ><rtc><rt>Text one</rt><rt>Text two/three</rt></rtc></ruby>
+    ><rtc><rt>Text one</rt><rt></rt><rt>Text two/three</rt></rtc></ruby>
 </p>
 
 </body>
 </html>