Bug 655877 - Part 42: Construct new SVG text frames if the pref is set. r=bz
authorCameron McCormack <cam@mcc.id.au>
Mon, 11 Feb 2013 17:22:18 +1100
changeset 121499 d11ce45846b5db6977cc6c99d07434a6f3b1676a
parent 121498 204b51a6c6458a30be158a3d1bdca8e9b4edfd67
child 121500 230087dd7d3b8a25b4293a61493dac2bb26e1df0
push id24291
push userryanvm@gmail.com
push dateMon, 11 Feb 2013 19:12:51 +0000
treeherdermozilla-central@93ba23f414ff [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs655877
milestone21.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 655877 - Part 42: Construct new SVG text frames if the pref is set. r=bz
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsCSSFrameConstructor.h
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -139,16 +139,17 @@ nsIFrame*
 NS_NewHTMLCanvasFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
 
 #if defined(MOZ_MEDIA)
 nsIFrame*
 NS_NewHTMLVideoFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
 #endif
 
 #include "nsSVGTextContainerFrame.h"
+#include "nsSVGTextFrame2.h"
 
 nsIFrame*
 NS_NewSVGOuterSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewSVGOuterSVGAnonChildFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewSVGInnerSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
@@ -163,16 +164,18 @@ nsIFrame*
 NS_NewSVGAFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewSVGGlyphFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewSVGSwitchFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewSVGTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
+NS_NewSVGTextFrame2(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
 NS_NewSVGTSpanFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewSVGContainerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewSVGUseFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewSVGViewFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 extern nsIFrame*
@@ -3206,25 +3209,34 @@ FindAncestorWithGeneratedContentPseudo(n
 #define SIMPLE_FCDATA(_func) FCDATA_DECL(0, _func)
 #define FULL_CTOR_FCDATA(_flags, _func)                             \
   { _flags | FCDATA_FUNC_IS_FULL_CTOR, { nullptr }, _func, nullptr }
 
 /* static */
 const nsCSSFrameConstructor::FrameConstructionData*
 nsCSSFrameConstructor::FindTextData(nsIFrame* aParentFrame)
 {
-  if (aParentFrame && aParentFrame->IsFrameOfType(nsIFrame::eSVG)) {
+  if (aParentFrame && IsFrameForSVG(aParentFrame)) {
     nsIFrame *ancestorFrame =
       nsSVGUtils::GetFirstNonAAncestorFrame(aParentFrame);
     if (ancestorFrame) {
-      nsSVGTextContainerFrame* metrics = do_QueryFrame(ancestorFrame);
-      if (metrics) {
+      if (NS_SVGTextCSSFramesEnabled()) {
+        static const FrameConstructionData sSVGTextData =
+          FCDATA_DECL(FCDATA_IS_LINE_PARTICIPANT | FCDATA_IS_SVG_TEXT,
+                      NS_NewTextFrame);
+        if (ancestorFrame->IsSVGText()) {
+          return &sSVGTextData;
+        }
+      } else {
         static const FrameConstructionData sSVGGlyphData =
           SIMPLE_FCDATA(NS_NewSVGGlyphFrame);
-        return &sSVGGlyphData;
+        nsSVGTextContainerFrame* metrics = do_QueryFrame(ancestorFrame);
+        if (metrics) {
+          return &sSVGGlyphData;
+        }
       }
     }
     return nullptr;
   }
 
   static const FrameConstructionData sTextData =
     FCDATA_DECL(FCDATA_IS_LINE_PARTICIPANT, NS_NewTextFrame);
   return &sTextData;
@@ -4823,27 +4835,29 @@ IsFilterPrimitiveChildTag(const nsIAtom*
 }
 
 /* static */
 const nsCSSFrameConstructor::FrameConstructionData*
 nsCSSFrameConstructor::FindSVGData(Element* aElement,
                                    nsIAtom* aTag,
                                    int32_t aNameSpaceID,
                                    nsIFrame* aParentFrame,
+                                   bool aIsWithinSVGText,
+                                   bool aAllowsTextPathChild,
                                    nsStyleContext* aStyleContext)
 {
   if (aNameSpaceID != kNameSpaceID_SVG) {
     return nullptr;
   }
 
   static const FrameConstructionData sSuppressData = SUPPRESS_FCDATA();
   static const FrameConstructionData sContainerData =
     SIMPLE_SVG_FCDATA(NS_NewSVGContainerFrame);
 
-  bool parentIsSVG = false;
+  bool parentIsSVG = aIsWithinSVGText;
   nsIContent* parentContent =
     aParentFrame ? aParentFrame->GetContent() : nullptr;
   // XXXbz should this really be based on the XBL-resolved tag of the parent
   // frame's content?  Should it not be based on the type of the parent frame
   // (e.g. whether it's an SVG frame)?
   if (parentContent) {
     int32_t parentNSID;
     nsIAtom* parentTag =
@@ -4895,58 +4909,100 @@ nsCSSFrameConstructor::FindSVGData(Eleme
   if (tests && !tests->PassesConditionalProcessingTests()) {
     // Elements with failing conditional processing attributes never get
     // rendered.  Note that this is not where we select which frame in a
     // <switch> to render!  That happens in nsSVGSwitchFrame::PaintSVG.
     return &sContainerData;
   }
 
   // Prevent bad frame types being children of filters or parents of filter
-  // primitives:
-  bool parentIsFilter = aParentFrame->GetType() == nsGkAtoms::svgFilterFrame;
+  // primitives.  If aParentFrame is null, we know that the frame that will
+  // be created will be an nsInlineFrame, so it can never be a filter.
+  bool parentIsFilter = aParentFrame &&
+    aParentFrame->GetType() == nsGkAtoms::svgFilterFrame;
   nsCOMPtr<nsIDOMSVGFilterPrimitiveStandardAttributes> filterPrimitive =
     do_QueryInterface(aElement);
   if ((parentIsFilter && !filterPrimitive) ||
       (!parentIsFilter && filterPrimitive)) {
     return &sSuppressData;
   }
 
   // Prevent bad frame types being children of filter primitives or parents of
-  // filter primitive children:
-  bool parentIsFEContainerFrame =
+  // filter primitive children.  If aParentFrame is null, we know that the frame
+  // that will be created will be an nsInlineFrame, so it can never be a filter
+  // primitive.
+  bool parentIsFEContainerFrame = aParentFrame &&
     aParentFrame->GetType() == nsGkAtoms::svgFEContainerFrame;
   if ((parentIsFEContainerFrame && !IsFilterPrimitiveChildTag(aTag)) ||
       (!parentIsFEContainerFrame && IsFilterPrimitiveChildTag(aTag))) {
     return &sSuppressData;
   }
 
   // Special cases for text/tspan/textPath, because the kind of frame
   // they get depends on the parent frame.  We ignore 'a' elements when
   // determining the parent, however.
-  nsIFrame *ancestorFrame =
-    nsSVGUtils::GetFirstNonAAncestorFrame(aParentFrame);
-  if (ancestorFrame) {
-    if (aTag == nsGkAtoms::tspan || aTag == nsGkAtoms::altGlyph) {
-      // tspan and altGlyph must be children of another text content element.
-      nsSVGTextContainerFrame* metrics = do_QueryFrame(ancestorFrame);
-      if (!metrics) {
+  if (NS_SVGTextCSSFramesEnabled()) {
+    if (aIsWithinSVGText) {
+      // We don't use ConstructInline because we want different behavior
+      // for generated content.
+      static const FrameConstructionData sTSpanData =
+        FCDATA_DECL(FCDATA_DISALLOW_OUT_OF_FLOW |
+                    FCDATA_SKIP_ABSPOS_PUSH |
+                    FCDATA_DISALLOW_GENERATED_CONTENT |
+                    FCDATA_IS_LINE_PARTICIPANT |
+                    FCDATA_IS_INLINE |
+                    FCDATA_USE_CHILD_ITEMS,
+                    NS_NewInlineFrame);
+      if (aTag == nsGkAtoms::textPath) {
+        if (aAllowsTextPathChild) {
+          return &sTSpanData;
+        }
+      } else if (aTag == nsGkAtoms::tspan ||
+                 aTag == nsGkAtoms::altGlyph ||
+                 aTag == nsGkAtoms::a) {
+        return &sTSpanData;
+      }
+      return &sSuppressData;
+    } else {
+      if (aTag == nsGkAtoms::text) {
+        static const FrameConstructionData sTextData =
+          FCDATA_WITH_WRAPPING_BLOCK(FCDATA_DISALLOW_OUT_OF_FLOW |
+                                     FCDATA_ALLOW_BLOCK_STYLES,
+                                     NS_NewSVGTextFrame2,
+                                     nsCSSAnonBoxes::mozSVGText);
+        return &sTextData;
+      } else if (aTag == nsGkAtoms::tspan ||
+                 aTag == nsGkAtoms::altGlyph ||
+                 aTag == nsGkAtoms::textPath) {
         return &sSuppressData;
       }
-    } else if (aTag == nsGkAtoms::textPath) {
-      // textPath must be a child of text.
-      nsIAtom* ancestorFrameType = ancestorFrame->GetType();
-      if (ancestorFrameType != nsGkAtoms::svgTextFrame) {
-        return &sSuppressData;
-      }
-    } else if (aTag != nsGkAtoms::a) {
-      // Every other element except 'a' must not be a child of a text content
-      // element.
-      nsSVGTextContainerFrame* metrics = do_QueryFrame(ancestorFrame);
-      if (metrics) {
-        return &sSuppressData;
+    }
+  } else {
+    nsIFrame *ancestorFrame =
+      nsSVGUtils::GetFirstNonAAncestorFrame(aParentFrame);
+    if (ancestorFrame) {
+      if (aTag == nsGkAtoms::tspan || aTag == nsGkAtoms::altGlyph) {
+        // tspan and altGlyph must be children of another text content element.
+        nsSVGTextContainerFrame* metrics = do_QueryFrame(ancestorFrame);
+        if (!metrics) {
+          return &sSuppressData;
+        }
+      } else if (aTag == nsGkAtoms::textPath) {
+        // textPath must be a child of text.
+        nsIAtom* ancestorFrameType = ancestorFrame->GetType();
+        if (ancestorFrameType != nsGkAtoms::svgTextFrame) {
+          return &sSuppressData;
+        }
+      } else if (aTag != nsGkAtoms::a) {
+        // Every other element except 'a' must not be a child of a text content
+        // element.
+        nsSVGTextContainerFrame* metrics = do_QueryFrame(ancestorFrame);
+        if (metrics) {
+          return &sSuppressData;
+        }
       }
     }
   }
 
   static const FrameConstructionDataByTag sSVGData[] = {
     SIMPLE_SVG_CREATE(svg, NS_NewSVGInnerSVGFrame),
     SIMPLE_SVG_CREATE(g, NS_NewSVGGFrame),
     SIMPLE_SVG_CREATE(svgSwitch, NS_NewSVGSwitchFrame),
@@ -5103,21 +5159,30 @@ nsCSSFrameConstructor::AddFrameConstruct
   // never create frames for comments or PIs
   if (aContent->IsNodeOfType(nsINode::eCOMMENT) ||
       aContent->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION))
     return;
 
   nsRefPtr<nsStyleContext> styleContext;
   styleContext = ResolveStyleContext(aParentFrame, aContent, &aState);
 
+  uint32_t flags = ITEM_ALLOW_XBL_BASE | ITEM_ALLOW_PAGE_BREAK;
+  if (aParentFrame->IsSVGText()) {
+    flags |= ITEM_IS_WITHIN_SVG_TEXT;
+  }
+  if (aParentFrame->GetType() == nsGkAtoms::blockFrame &&
+      aParentFrame->GetParent() &&
+      aParentFrame->GetParent()->GetType() == nsGkAtoms::svgTextFrame2) {
+    flags |= ITEM_ALLOWS_TEXT_PATH_CHILD;
+  }
   AddFrameConstructionItemsInternal(aState, aContent, aParentFrame,
                                     aContent->Tag(), aContent->GetNameSpaceID(),
                                     aSuppressWhiteSpaceOptimizations,
                                     styleContext,
-                                    ITEM_ALLOW_XBL_BASE | ITEM_ALLOW_PAGE_BREAK,
+                                    flags,
                                     aItems);
 }
 
 /* static */ void
 nsCSSFrameConstructor::SetAsUndisplayedContent(FrameConstructionItemList& aList,
                                                nsIContent* aContent,
                                                nsStyleContext* aStyleContext,
                                                bool aIsGeneratedContent)
@@ -5253,16 +5318,18 @@ nsCSSFrameConstructor::AddFrameConstruct
     if (!data) {
       data = FindXULTagData(element, aTag, aNameSpaceID, styleContext);
     }
     if (!data) {
       data = FindMathMLData(element, aTag, aNameSpaceID, styleContext);
     }
     if (!data) {
       data = FindSVGData(element, aTag, aNameSpaceID, aParentFrame,
+                         aFlags & ITEM_IS_WITHIN_SVG_TEXT,
+                         aFlags & ITEM_ALLOWS_TEXT_PATH_CHILD,
                          styleContext);
     }
 
     // Now check for XUL display types
     if (!data) {
       data = FindXULDisplayData(display, element, styleContext);
     }
 
@@ -5334,25 +5401,29 @@ nsCSSFrameConstructor::AddFrameConstruct
   }
   item->mIsRootPopupgroup =
     aNameSpaceID == kNameSpaceID_XUL && aTag == nsGkAtoms::popupgroup &&
     aContent->IsRootOfNativeAnonymousSubtree();
   if (item->mIsRootPopupgroup) {
     aState.mHavePendingPopupgroup = true;
   }
   item->mIsPopup = isPopup;
+  item->mIsForSVGAElement = aNameSpaceID == kNameSpaceID_SVG &&
+                            aTag == nsGkAtoms::a;
 
   if (canHavePageBreak && display->mBreakAfter) {
     AddPageBreakItem(aContent, aStyleContext, aItems);
   }
 
   if (bits & FCDATA_IS_INLINE) {
     // To correctly set item->mIsAllInline we need to build up our child items
     // right now.
-    BuildInlineChildItems(aState, *item);
+    BuildInlineChildItems(aState, *item,
+                          aFlags & ITEM_IS_WITHIN_SVG_TEXT,
+                          aFlags & ITEM_ALLOWS_TEXT_PATH_CHILD);
     item->mHasInlineEnds = true;
     item->mIsBlock = false;
   } else {
     // Compute a boolean isInline which is guaranteed to be false for blocks
     // (but may also be false for some inlines).
     bool isInline =
       // Table-internal things are inline-outside if and only if they're kids of
       // inlines, since they'll trigger construction of inline-table
@@ -11328,35 +11399,39 @@ nsCSSFrameConstructor::CreateIBSiblings(
     lastNewInline = inlineFrame;
   } while (aChildItems.NotEmpty());
 
   SetFrameIsSpecial(lastNewInline, nullptr);
 }
 
 void
 nsCSSFrameConstructor::BuildInlineChildItems(nsFrameConstructorState& aState,
-                                             FrameConstructionItem& aParentItem)
+                                             FrameConstructionItem& aParentItem,
+                                             bool aItemIsWithinSVGText,
+                                             bool aItemAllowsTextPathChild)
 {
   // XXXbz should we preallocate aParentItem.mChildItems to some sane
   // length?  Maybe even to parentContent->GetChildCount()?
   nsFrameConstructorState::PendingBindingAutoPusher
     pusher(aState, aParentItem.mPendingBinding);
 
-  // Probe for generated content before
   nsStyleContext* const parentStyleContext = aParentItem.mStyleContext;
   nsIContent* const parentContent = aParentItem.mContent;
 
   TreeMatchContext::AutoAncestorPusher
     ancestorPusher(aState.mTreeMatchContext.mAncestorFilter.HasFilter(),
                    aState.mTreeMatchContext,
                    parentContent->AsElement());
   
-  CreateGeneratedContentItem(aState, nullptr, parentContent, parentStyleContext,
-                             nsCSSPseudoElements::ePseudo_before,
-                             aParentItem.mChildItems);
+  if (!aItemIsWithinSVGText) {
+    // Probe for generated content before
+    CreateGeneratedContentItem(aState, nullptr, parentContent, parentStyleContext,
+                               nsCSSPseudoElements::ePseudo_before,
+                               aParentItem.mChildItems);
+  }
 
   ChildIterator iter, last;
   for (ChildIterator::Init(parentContent, &iter, &last);
        iter != last;
        ++iter) {
     // Manually check for comments/PIs, since we don't have a frame to pass to
     // AddFrameConstructionItems.  We know our parent is a non-replaced inline,
     // so there is no need to do the NeedFrameFor check.
@@ -11372,27 +11447,36 @@ nsCSSFrameConstructor::BuildInlineChildI
       // that we can remove all restyle flags, just like in
       // ProcessChildren and for the same reason.
       content->UnsetFlags(ELEMENT_ALL_RESTYLE_FLAGS);
     }
 
     nsRefPtr<nsStyleContext> childContext =
       ResolveStyleContext(parentStyleContext, content, &aState);
 
+    uint32_t flags = ITEM_ALLOW_XBL_BASE | ITEM_ALLOW_PAGE_BREAK;
+    if (aItemIsWithinSVGText) {
+      flags |= ITEM_IS_WITHIN_SVG_TEXT;
+    }
+    if (aItemAllowsTextPathChild && aParentItem.mIsForSVGAElement) {
+      flags |= ITEM_ALLOWS_TEXT_PATH_CHILD;
+    }
     AddFrameConstructionItemsInternal(aState, content, nullptr, content->Tag(),
                                       content->GetNameSpaceID(),
                                       iter.XBLInvolved(), childContext,
-                                      ITEM_ALLOW_XBL_BASE | ITEM_ALLOW_PAGE_BREAK,
+                                      flags,
                                       aParentItem.mChildItems);
   }
 
-  // Probe for generated content after
-  CreateGeneratedContentItem(aState, nullptr, parentContent, parentStyleContext,
-                             nsCSSPseudoElements::ePseudo_after,
-                             aParentItem.mChildItems);
+  if (!aItemIsWithinSVGText) {
+    // Probe for generated content after
+    CreateGeneratedContentItem(aState, nullptr, parentContent, parentStyleContext,
+                               nsCSSPseudoElements::ePseudo_after,
+                               aParentItem.mChildItems);
+  }
 
   aParentItem.mIsAllInline = aParentItem.mChildItems.AreAllItemsInline();
 }
 
 // return whether it's ok to append (in the AppendFrames sense) to
 // aParentFrame if our nextSibling is aNextSibling.  aParentFrame must
 // be an {ib} special inline.
 static bool
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -1051,17 +1051,17 @@ private:
                           bool aSuppressWhiteSpaceOptimizations) :
       mFCData(aFCData), mContent(aContent), mTag(aTag),
       mNameSpaceID(aNameSpaceID),
       mPendingBinding(aPendingBinding), mStyleContext(aStyleContext),
       mSuppressWhiteSpaceOptimizations(aSuppressWhiteSpaceOptimizations),
       mIsText(false), mIsGeneratedContent(false),
       mIsRootPopupgroup(false), mIsAllInline(false), mIsBlock(false),
       mHasInlineEnds(false), mIsPopup(false),
-      mIsLineParticipant(false)
+      mIsLineParticipant(false), mIsForSVGAElement(false)
     {}
     ~FrameConstructionItem() {
       if (mIsGeneratedContent) {
         mContent->UnbindFromTree();
         NS_RELEASE(mContent);
       }
     }
 
@@ -1129,16 +1129,18 @@ private:
     // items, where it's always true, whereas mIsAllInline might be false due
     // to {ib} splits.
     bool mHasInlineEnds;
     // Whether construction from this item will create a popup that needs to
     // go into the global popup items.
     bool mIsPopup;
     // Whether this item should be treated as a line participant
     bool mIsLineParticipant;
+    // Whether this item is for an SVG <a> element
+    bool mIsForSVGAElement;
 
     // Child frame construction items.
     FrameConstructionItemList mChildItems;
 
   private:
     FrameConstructionItem(const FrameConstructionItem& aOther) MOZ_DELETE; /* not implemented */
   };
 
@@ -1278,16 +1280,20 @@ private:
   // possible flags for AddFrameConstructionItemInternal's aFlags argument
   /* Allow xbl:base to affect the tag/namespace used. */
 #define ITEM_ALLOW_XBL_BASE 0x1
   /* Allow page-break before and after items to be created if the
      style asks for them. */
 #define ITEM_ALLOW_PAGE_BREAK 0x2
   /* The item is a generated content item. */
 #define ITEM_IS_GENERATED_CONTENT 0x4
+  /* The item is within an SVG text block frame. */
+#define ITEM_IS_WITHIN_SVG_TEXT 0x8
+  /* The item allows items to be created for SVG <textPath> children. */
+#define ITEM_ALLOWS_TEXT_PATH_CHILD 0x10
   // The guts of AddFrameConstructionItems
   // aParentFrame might be null.  If it is, that means it was an
   // inline frame.
   void AddFrameConstructionItemsInternal(nsFrameConstructorState& aState,
                                          nsIContent*              aContent,
                                          nsIFrame*                aParentFrame,
                                          nsIAtom*                 aTag,
                                          int32_t                  aNameSpaceID,
@@ -1387,16 +1393,18 @@ private:
                              const nsStyleDisplay*    aDisplay,
                              nsFrameItems&            aFrameItems,
                              nsIFrame**               aNewFrame);
 
   static const FrameConstructionData* FindSVGData(Element* aElement,
                                                   nsIAtom* aTag,
                                                   int32_t aNameSpaceID,
                                                   nsIFrame* aParentFrame,
+                                                  bool aIsWithinSVGText,
+                                                  bool aAllowsTextPathChild,
                                                   nsStyleContext* aStyleContext);
 
   /* Not static because it does PropagateScrollToViewport.  If this
      changes, make this static */
   const FrameConstructionData*
     FindDisplayData(const nsStyleDisplay* aDisplay, Element* aElement,
                     nsIFrame* aParentFrame, nsStyleContext* aStyleContext);
 
@@ -1640,17 +1648,19 @@ private:
                         nsFrameItems& aChildItems,
                         nsFrameItems& aSiblings);
 
   /**
    * For an inline aParentItem, construct its list of child
    * FrameConstructionItems and set its mIsAllInline flag appropriately.
    */
   void BuildInlineChildItems(nsFrameConstructorState& aState,
-                             FrameConstructionItem& aParentItem);
+                             FrameConstructionItem& aParentItem,
+                             bool aItemIsWithinSVGText,
+                             bool aItemAllowsTextPathChild);
 
   // Determine whether we need to wipe out what we just did and start over
   // because we're doing something like adding block kids to an inline frame
   // (and therefore need an {ib} split).  aPrevSibling must be correct, even in
   // aIsAppend cases.  Passing aIsAppend false even when an append is happening
   // is ok in terms of correctness, but can lead to unnecessary reframing.  If
   // aIsAppend is true, then the caller MUST call
   // nsCSSFrameConstructor::AppendFrames (as opposed to