Bug 238072. Rework generated content support to match CSS 2.1, making generated content take the normal frame construction path so supporting any style applied to it (including 'position', 'float', etc). r+sr=bz,r=dbaron
authorRobert O'Callahan <robert@ocallahan.org>
Thu, 07 Aug 2008 13:18:24 +1200
changeset 16461 dcbcc3d9070c3bbbe228599813216560389df7d6
parent 16460 49992e48b298b3ff02d132b6c90ecc44af6755b0
child 16462 2837d3e7971503277910fca92a024ad55b5ac116
push id1050
push userrocallahan@mozilla.com
push dateThu, 07 Aug 2008 01:18:48 +0000
treeherdermozilla-central@dcbcc3d9070c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs238072
milestone1.9.1a2pre
Bug 238072. Rework generated content support to match CSS 2.1, making generated content take the normal frame construction path so supporting any style applied to it (including 'position', 'float', etc). r+sr=bz,r=dbaron
layout/base/crashtests/374193-1.xhtml
layout/base/crashtests/crashtests.list
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsCSSFrameConstructor.h
layout/base/nsCounterManager.cpp
layout/base/nsCounterManager.h
layout/base/nsGenConList.cpp
layout/base/nsGenConList.h
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/base/nsQuoteList.cpp
layout/base/nsQuoteList.h
layout/generic/nsHTMLParts.h
layout/generic/nsTextFrameThebes.cpp
layout/reftests/bugs/380842-1-ref.html
layout/reftests/bugs/reftest.list
layout/reftests/generated-content/display-types-01-ref.html
layout/reftests/generated-content/display-types-01.html
layout/reftests/generated-content/dynamic-attr-01-ref.html
layout/reftests/generated-content/dynamic-attr-01.html
layout/reftests/generated-content/dynamic-restyle-01-ref.html
layout/reftests/generated-content/dynamic-restyle-01.html
layout/reftests/generated-content/floated-01-ref.html
layout/reftests/generated-content/floated-01.html
layout/reftests/generated-content/images-01-ref.html
layout/reftests/generated-content/images-01.html
layout/reftests/generated-content/positioned-01-ref.html
layout/reftests/generated-content/positioned-01.html
layout/reftests/generated-content/reftest.list
layout/reftests/generated-content/table-ignoring-whitespace-01-ref.html
layout/reftests/generated-content/table-ignoring-whitespace-01.html
layout/reftests/generated-content/table-parts-01-ref.html
layout/reftests/generated-content/table-parts-01.html
layout/reftests/reftest.list
layout/style/nsRuleNode.cpp
new file mode 100644
--- /dev/null
+++ b/layout/base/crashtests/374193-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+  ><mtd xmlns="http://www.w3.org/1998/Math/MathML"
+  ><th xmlns="http://www.w3.org/1999/xhtml"
+  /><mtable xmlns="http://www.w3.org/1998/Math/MathML"
+  ><th xmlns="http://www.w3.org/1999/xhtml" style="-moz-binding: url(374193-1xbl.xml);" id="mw_th20"></th></mtable></mtd><style>
+mtable::after { content:"anonymous text"; }
+</style></html>
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -51,16 +51,17 @@ load 366271-1.html
 load 366967-1.html
 load 367015-1.html
 load 367243-1.html
 load 369945-1.xhtml
 load 371681-1.xhtml
 load 372237-1.html
 load 372475-1.xhtml
 load 372550-1.html
+load 374193-1.html
 load 374297-1.html
 load 374297-2.html
 load 376223-1.xhtml
 load 379105-1.xhtml
 load 379419-1.xhtml
 load 379768-1.html
 load 379799-1.html
 load 379920-1.svg
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -1111,16 +1111,19 @@ public:
   // Containing block information for out-of-flow frames.
   nsAbsoluteItems           mFixedItems;
   nsAbsoluteItems           mAbsoluteItems;
   nsAbsoluteItems           mFloatedItems;
   PRBool                    mFirstLetterStyle;
   PRBool                    mFirstLineStyle;
   nsCOMPtr<nsILayoutHistoryState> mFrameState;
   nsPseudoFrames            mPseudoFrames;
+  // These bits will be added to the state bits of any frame we construct
+  // using this state.
+  nsFrameState              mAdditionalStateBits; 
 
   // Constructor
   // Use the passed-in history state.
   nsFrameConstructorState(nsIPresShell*          aPresShell,
                           nsIFrame*              aFixedContainingBlock,
                           nsIFrame*              aAbsoluteContainingBlock,
                           nsIFrame*              aFloatContainingBlock,
                           nsILayoutHistoryState* aHistoryState);
@@ -1215,17 +1218,18 @@ nsFrameConstructorState::nsFrameConstruc
     mPopupItems(mRootBox ? mRootBox->GetPopupSetFrame() : nsnull),
 #endif
     mFixedItems(aFixedContainingBlock),
     mAbsoluteItems(aAbsoluteContainingBlock),
     mFloatedItems(aFloatContainingBlock),
     mFirstLetterStyle(PR_FALSE),
     mFirstLineStyle(PR_FALSE),
     mFrameState(aHistoryState),
-    mPseudoFrames()
+    mPseudoFrames(),
+    mAdditionalStateBits(0)
 {
   MOZ_COUNT_CTOR(nsFrameConstructorState);
 }
 
 nsFrameConstructorState::nsFrameConstructorState(nsIPresShell* aPresShell,
                                                  nsIFrame*     aFixedContainingBlock,
                                                  nsIFrame*     aAbsoluteContainingBlock,
                                                  nsIFrame*     aFloatContainingBlock)
@@ -1236,17 +1240,18 @@ nsFrameConstructorState::nsFrameConstruc
     mRootBox(nsIRootBox::GetRootBox(aPresShell)),
     mPopupItems(mRootBox ? mRootBox->GetPopupSetFrame() : nsnull),
 #endif
     mFixedItems(aFixedContainingBlock),
     mAbsoluteItems(aAbsoluteContainingBlock),
     mFloatedItems(aFloatContainingBlock),
     mFirstLetterStyle(PR_FALSE),
     mFirstLineStyle(PR_FALSE),
-    mPseudoFrames()
+    mPseudoFrames(),
+    mAdditionalStateBits(0)
 {
   MOZ_COUNT_CTOR(nsFrameConstructorState);
   mFrameState = aPresShell->GetDocument()->GetLayoutHistoryState();
 }
 
 nsFrameConstructorState::~nsFrameConstructorState()
 {
   // Frame order comparison functions only work properly when the placeholders
@@ -1427,16 +1432,17 @@ nsFrameConstructorState::AddChild(nsIFra
       // hence already set as the primary frame.  So we have to clean up here.
       // But it shouldn't have any out-of-flow kids.
       // XXXbz Maybe add a utility function to assert that?
       CleanupFrameReferences(mFrameManager, aNewFrame);
       aNewFrame->Destroy();
       return rv;
     }
 
+    placeholderFrame->AddStateBits(mAdditionalStateBits);
     // Add the placeholder frame to the flow
     aFrameItems.AddChild(placeholderFrame);
   }
 #ifdef DEBUG
   else {
     NS_ASSERTION(aNewFrame->GetParent() == aParentFrame,
                  "In-flow frame has wrong parent");
   }
@@ -1874,449 +1880,350 @@ nsIXBLService * nsCSSFrameConstructor::G
     nsresult rv = CallGetService("@mozilla.org/xbl;1", &gXBLService);
     if (NS_FAILED(rv))
       gXBLService = nsnull;
   }
   
   return gXBLService;
 }
 
+// XXX it would be great if we could use a state bit for this ... or a fast
+// property
+static PRBool
+HasCounterIncrementOrReset(nsIFrame* aFrame)
+{
+  const nsStyleContent *styleContent = aFrame->GetStyleContent();
+  return styleContent->CounterIncrementCount() ||
+         styleContent->CounterResetCount();
+}
+
 void
 nsCSSFrameConstructor::NotifyDestroyingFrame(nsIFrame* aFrame)
 {
   NS_PRECONDITION(mUpdateCount != 0,
                   "Should be in an update while destroying frames");
 
-  if (aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT) {
-    if (mQuoteList.DestroyNodesFor(aFrame))
+  nsIFrame* parentFrame = aFrame->GetParent();
+  if (!parentFrame) {
+    NS_ASSERTION(!aFrame->IsGeneratedContentFrame(),
+                 "Root frame was generated?");
+    // The root frame can't be generated...
+    return;
+  }
+
+  if ((aFrame->IsGeneratedContentFrame() &&
+       !parentFrame->IsGeneratedContentFrame()) ||
+      (!mCounterManager.IsEmpty() && HasCounterIncrementOrReset(aFrame))) {
+    // Destroying outermost generated content frame OR a frame with
+    // counter increments/resets
+    nsIContent* content;
+    nsIAtom* pseudo = aFrame->GetStyleContext()->GetPseudoType();
+    // The frame may be a pseudo that's not 'before' or 'after'; if so,
+    // treat it as no pseudo since the frame may be non-generated content
+    // associated with a counter, and the counter pseudo is always only
+    // 'before', 'after' or null.
+    if (pseudo == nsCSSPseudoElements::before ||
+        pseudo == nsCSSPseudoElements::after) {
+      content = parentFrame->GetContent();
+    } else {
+      pseudo = nsnull;
+      content = aFrame->GetContent();
+    }
+
+    if (mQuoteList.DestroyNodesFor(content, pseudo)) {
       QuotesDirty();
-  }
-
-  if (mCounterManager.DestroyNodesFor(aFrame)) {
-    // Technically we don't need to update anything if we destroyed only
-    // USE nodes.  However, this is unlikely to happen in the real world
-    // since USE nodes generally go along with INCREMENT nodes.
-    CountersDirty();
-  }
-}
-
-nsresult
-nsCSSFrameConstructor::CreateAttributeContent(nsIContent* aParentContent,
-                                              nsIFrame* aParentFrame,
-                                              PRInt32 aAttrNamespace,
-                                              nsIAtom* aAttrName,
-                                              nsStyleContext* aStyleContext,
-                                              nsCOMArray<nsIContent>& aGeneratedContent,
-                                              nsIContent** aNewContent,
-                                              nsIFrame** aNewFrame)
-{
-  *aNewFrame = nsnull;
-  *aNewContent = nsnull;
+    }
+    // Note that we might call DestroyNodesFor multiple times for the same
+    // content+pseudo pair: once for the outermost generated content frame
+    // and once for an inner frame that actually has the content
+    // increments/resets on it (e.g. an inner table frame). That's OK though.
+    if (mCounterManager.DestroyNodesFor(content, pseudo)) {
+      // Technically we don't need to update anything if we destroyed only
+      // USE nodes.  However, this is unlikely to happen in the real world
+      // since USE nodes generally go along with INCREMENT nodes.
+      CountersDirty();
+    }
+  }
+}
+
+already_AddRefed<nsIContent>
+nsCSSFrameConstructor::CreateTextNode(const nsString& aString,
+                                      nsCOMPtr<nsIDOMCharacterData>* aText)
+{
   nsCOMPtr<nsIContent> content;
-  nsresult rv = NS_NewAttributeContent(mDocument->NodeInfoManager(),
-                                       aAttrNamespace, aAttrName,
-                                       getter_AddRefs(content));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  content->SetNativeAnonymous();
-
-  // Set aParentContent as the parent content so that event handling works.
-  // It is also the binding parent.
-  rv = content->BindToTree(mDocument, aParentContent, aParentContent, PR_TRUE);
-  if (NS_FAILED(rv)) {
-    content->UnbindFromTree();
-    return rv;
-  }
-
-  // Create a text frame and initialize it
-  nsIFrame* textFrame = NS_NewTextFrame(mPresShell, aStyleContext);
-  rv = textFrame->Init(content, aParentFrame, nsnull);
-  if (NS_SUCCEEDED(rv)) {
-    if (NS_UNLIKELY(!aGeneratedContent.AppendObject(content))) {
-      rv = NS_ERROR_OUT_OF_MEMORY;
-    }
-  }
-
-  if (NS_FAILED(rv)) {
-    content->UnbindFromTree();
-    textFrame->Destroy();
-    textFrame = nsnull;
-    content = nsnull;
-  }
-
-  *aNewFrame = textFrame;
-  content.swap(*aNewContent);
-  return rv;
-}
-
-nsresult
-nsCSSFrameConstructor::CreateGeneratedFrameFor(nsIFrame*             aParentFrame,
-                                               nsIContent*           aContent,
-                                               nsStyleContext*       aStyleContext,
-                                               const nsStyleContent* aStyleContent,
-                                               PRUint32              aContentIndex,
-                                               nsCOMArray<nsIContent>& aGeneratedContent,
-                                               nsIFrame**            aFrame)
-{
-  *aFrame = nsnull;  // initialize OUT parameter
-
-  // The QuoteList needs the content attached to the frame.
-  nsCOMPtr<nsIDOMCharacterData>* textPtr = nsnull;
-
+  NS_NewTextNode(getter_AddRefs(content), mDocument->NodeInfoManager());
+  if (!content) {
+    // XXX The quotes/counters code doesn't like the text pointer
+    // being null in case of dynamic changes!
+    NS_ASSERTION(!aText, "this OOM case isn't handled very well");
+    return nsnull;
+  }
+  content->SetText(aString, PR_FALSE);
+  if (aText) {
+    *aText = do_QueryInterface(content);
+  }
+  return content.forget();
+}
+
+already_AddRefed<nsIContent>
+nsCSSFrameConstructor::CreateGeneratedContent(nsIContent*     aParentContent,
+                                              nsStyleContext* aStyleContext,
+                                              PRUint32        aContentIndex)
+{
   // Get the content value
-  const nsStyleContentData &data = aStyleContent->ContentAt(aContentIndex);
-  nsStyleContentType  type = data.mType;
-
-  nsCOMPtr<nsIContent> content;
+  const nsStyleContentData &data =
+    aStyleContext->GetStyleContent()->ContentAt(aContentIndex);
+  nsStyleContentType type = data.mType;
 
   if (eStyleContentType_Image == type) {
     if (!data.mContent.mImage) {
       // CSS had something specified that couldn't be converted to an
       // image object
-      return NS_ERROR_FAILURE;
+      return nsnull;
     }
     
     // Create an image content object and pass it the image request.
     // XXX Check if it's an image type we can handle...
 
     nsCOMPtr<nsINodeInfo> nodeInfo;
     mDocument->NodeInfoManager()->GetNodeInfo(nsGkAtoms::img, nsnull,
-                                              kNameSpaceID_None,
+                                              kNameSpaceID_XHTML,
                                               getter_AddRefs(nodeInfo));
 
-    nsresult rv = NS_NewGenConImageContent(getter_AddRefs(content), nodeInfo,
-                                           data.mContent.mImage);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    content->SetNativeAnonymous();
-  
-    // Set aContent as the parent content and set the document object. This
-    // way event handling works.  It is also the binding parent.
-    rv = content->BindToTree(mDocument, aContent, aContent, PR_TRUE);
-    if (NS_FAILED(rv)) {
-      content->UnbindFromTree();
-      return rv;
-    }
-    
-    // Create an image frame and initialize it
-    nsIFrame* imageFrame = NS_NewImageFrame(mPresShell, aStyleContext);
-    if (NS_UNLIKELY(!imageFrame)) {
-      content->UnbindFromTree();
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    rv = imageFrame->Init(content, aParentFrame, nsnull);
-    if (NS_FAILED(rv) || NS_UNLIKELY(!aGeneratedContent.AppendObject(content))) {
-      content->UnbindFromTree();
-      imageFrame->Destroy();
-      return NS_FAILED(rv) ? rv : NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    // Return the image frame
-    *aFrame = imageFrame;
-
-  } else {
-
-    nsAutoString contentString;
-
-    switch (type) {
-    case eStyleContentType_String:
-      contentString = data.mContent.mString;
-      break;
-  
-    case eStyleContentType_Attr:
-      {
-        nsCOMPtr<nsIAtom> attrName;
-        PRInt32 attrNameSpace = kNameSpaceID_None;
-        contentString = data.mContent.mString;
-        PRInt32 barIndex = contentString.FindChar('|'); // CSS namespace delimiter
-        if (-1 != barIndex) {
-          nsAutoString  nameSpaceVal;
-          contentString.Left(nameSpaceVal, barIndex);
-          PRInt32 error;
-          attrNameSpace = nameSpaceVal.ToInteger(&error, 10);
-          contentString.Cut(0, barIndex + 1);
-          if (contentString.Length()) {
-            attrName = do_GetAtom(contentString);
-          }
-        }
-        else {
+    nsCOMPtr<nsIContent> content;
+    NS_NewGenConImageContent(getter_AddRefs(content), nodeInfo,
+                             data.mContent.mImage);
+    return content.forget();
+  }
+
+  switch (type) {
+  case eStyleContentType_String:
+    return CreateTextNode(nsDependentString(data.mContent.mString), nsnull);
+
+  case eStyleContentType_Attr:
+    {
+      nsCOMPtr<nsIAtom> attrName;
+      PRInt32 attrNameSpace = kNameSpaceID_None;
+      nsAutoString contentString(data.mContent.mString);
+      PRInt32 barIndex = contentString.FindChar('|'); // CSS namespace delimiter
+      if (-1 != barIndex) {
+        nsAutoString  nameSpaceVal;
+        contentString.Left(nameSpaceVal, barIndex);
+        PRInt32 error;
+        attrNameSpace = nameSpaceVal.ToInteger(&error, 10);
+        contentString.Cut(0, barIndex + 1);
+        if (contentString.Length()) {
           attrName = do_GetAtom(contentString);
         }
-
-        if (!attrName) {
-          return NS_ERROR_OUT_OF_MEMORY;
+      }
+      else {
+        attrName = do_GetAtom(contentString);
+      }
+
+      if (!attrName) {
+        return nsnull;
+      }
+
+      nsCOMPtr<nsIContent> content;
+      NS_NewAttributeContent(mDocument->NodeInfoManager(),
+                             attrNameSpace, attrName, getter_AddRefs(content));
+      return content.forget();
+    }
+  
+  case eStyleContentType_Counter:
+  case eStyleContentType_Counters:
+    {
+      nsCSSValue::Array *counters = data.mContent.mCounters;
+      nsCounterList *counterList = mCounterManager.CounterListFor(
+          nsDependentString(counters->Item(0).GetStringBufferValue()));
+      if (!counterList)
+        return nsnull;
+
+      nsCounterUseNode* node =
+        new nsCounterUseNode(counters, aParentContent, aStyleContext,
+                             aContentIndex, type == eStyleContentType_Counters);
+      if (!node)
+        return nsnull;
+
+      nsAutoString contentString;
+
+      // Note that if there are increments or resets for this generated
+      // content, they will be added *later* when we create the frame subtree
+      // for the generated content. That's OK correctness-wise since
+      // the counter manager will insert them before this "use" node. It's
+      // not so great for performance though. This !dirty optimization here
+      // isn't going to be much use since adding the increments and resets
+      // later will almost certainly dirty everything.
+      counterList->Insert(node);
+      PRBool dirty = counterList->IsDirty();
+      if (!dirty) {
+        if (counterList->IsLast(node)) {
+          node->Calc(counterList);
+          node->GetText(contentString);
+        }
+        // In all other cases (list already dirty or node not at the end),
+        // just start with an empty string for now and when we recalculate
+        // the list we'll change the value to the right one.
+        else {
+          counterList->SetDirty();
+          CountersDirty();
         }
-
-        nsresult rv =
-          CreateAttributeContent(aContent, aParentFrame, attrNameSpace,
-                                 attrName, aStyleContext, aGeneratedContent,
-                                 getter_AddRefs(content), aFrame);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
-      break;
-  
-    case eStyleContentType_Counter:
-    case eStyleContentType_Counters:
-      {
-        nsCSSValue::Array *counters = data.mContent.mCounters;
-        nsCounterList *counterList = mCounterManager.CounterListFor(
-            nsDependentString(counters->Item(0).GetStringBufferValue()));
-        if (!counterList)
-            return NS_ERROR_OUT_OF_MEMORY;
-
-        nsCounterUseNode* node =
-          new nsCounterUseNode(counters, aParentFrame, aContentIndex,
-                               type == eStyleContentType_Counters);
-        if (!node)
-          return NS_ERROR_OUT_OF_MEMORY;
-
-        counterList->Insert(node);
-        PRBool dirty = counterList->IsDirty();
-        if (!dirty) {
-          if (counterList->IsLast(node)) {
-            node->Calc(counterList);
-            node->GetText(contentString);
-          }
-          // In all other cases (list already dirty or node not at the end),
-          // just start with an empty string for now and when we recalculate
-          // the list we'll change the value to the right one.
-          else {
-            counterList->SetDirty();
-            CountersDirty();
-          }
+      }
+
+      return CreateTextNode(contentString, &node->mText);
+    }
+
+  case eStyleContentType_Image:
+    NS_NOTREACHED("handled by if above");
+    return nsnull;
+
+  case eStyleContentType_OpenQuote:
+  case eStyleContentType_CloseQuote:
+  case eStyleContentType_NoOpenQuote:
+  case eStyleContentType_NoCloseQuote:
+    {
+      nsQuoteNode* node =
+        new nsQuoteNode(type, aParentContent, aContentIndex, aStyleContext);
+      if (!node)
+        return nsnull;
+      mQuoteList.Insert(node);
+      if (mQuoteList.IsLast(node))
+        mQuoteList.Calc(node);
+      else
+        QuotesDirty();
+
+      // Don't generate a text node or any text for 'no-open-quote' and
+      // 'no-close-quote'.
+      if (node->IsHiddenQuote())
+        return nsnull;
+
+      return CreateTextNode(*node->Text(), &node->mText);
+    }
+  
+  case eStyleContentType_AltContent:
+    {
+      // Use the "alt" attribute; if that fails and the node is an HTML
+      // <input>, try the value attribute and then fall back to some default
+      // localized text we have.
+      // XXX what if the 'alt' attribute is added later, how will we
+      // detect that and do the right thing here?
+      if (aParentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) {
+        nsCOMPtr<nsIContent> content;
+        NS_NewAttributeContent(mDocument->NodeInfoManager(),
+                               kNameSpaceID_None, nsGkAtoms::alt, getter_AddRefs(content));
+        return content.forget();
+      }
+
+      if (aParentContent->IsNodeOfType(nsINode::eHTML) &&
+          aParentContent->NodeInfo()->Equals(nsGkAtoms::input)) {
+        if (aParentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::value)) {
+          nsCOMPtr<nsIContent> content;
+          NS_NewAttributeContent(mDocument->NodeInfoManager(),
+                                 kNameSpaceID_None, nsGkAtoms::value, getter_AddRefs(content));
+          return content.forget();
         }
 
-        textPtr = &node->mText; // text node assigned below
-      }
-      break;
-
-    case eStyleContentType_Image:
-      NS_NOTREACHED("handled by if above");
-      return NS_ERROR_UNEXPECTED;
-  
-    case eStyleContentType_OpenQuote:
-    case eStyleContentType_CloseQuote:
-    case eStyleContentType_NoOpenQuote:
-    case eStyleContentType_NoCloseQuote:
-      {
-        nsQuoteNode* node = new nsQuoteNode(type, aParentFrame, aContentIndex);
-        if (!node)
-          return NS_ERROR_OUT_OF_MEMORY;
-        mQuoteList.Insert(node);
-        if (mQuoteList.IsLast(node))
-          mQuoteList.Calc(node);
-        else
-          QuotesDirty();
-
-        // Don't generate a text node or any text for 'no-open-quote' and
-        // 'no-close-quote'.
-        if (node->IsHiddenQuote())
-          return NS_OK;
-
-        textPtr = &node->mText; // text node assigned below
-        contentString = *node->Text();
-      }
+        nsXPIDLString temp;
+        nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+                                           "Submit", temp);
+        return CreateTextNode(temp, nsnull);
+      }
+
       break;
-  
-    case eStyleContentType_AltContent:
-      {
-        // Use the "alt" attribute; if that fails and the node is an HTML
-        // <input>, try the value attribute and then fall back to some default
-        // localized text we have.
-        nsresult rv = NS_OK;
-        if (aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) {
-          rv = CreateAttributeContent(aContent, aParentFrame,
-                                      kNameSpaceID_None, nsGkAtoms::alt,
-                                      aStyleContext, aGeneratedContent,
-                                      getter_AddRefs(content), aFrame);
-        } else if (aContent->IsNodeOfType(nsINode::eHTML) &&
-                   aContent->NodeInfo()->Equals(nsGkAtoms::input)) {
-          if (aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::value)) {
-            rv = CreateAttributeContent(aContent, aParentFrame,
-                                        kNameSpaceID_None, nsGkAtoms::value,
-                                        aStyleContext, aGeneratedContent,
-                                        getter_AddRefs(content), aFrame);
-          } else {
-            nsXPIDLString temp;
-            rv = nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
-                                                    "Submit", temp);
-            contentString = temp;
-          }
-        } else {
-          *aFrame = nsnull;
-          rv = NS_ERROR_NOT_AVAILABLE;
-          return rv; // Don't fall through to the warning below.
-        }
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
-      break;
-    } // switch
-  
-
-    if (!content) {
-      // Create a text content node
-      nsIFrame* textFrame = nsnull;
-      nsCOMPtr<nsIContent> textContent;
-      NS_NewTextNode(getter_AddRefs(textContent),
-                     mDocument->NodeInfoManager());
-      if (textContent) {
-        // Set the text
-        textContent->SetText(contentString, PR_TRUE);
-
-        if (textPtr) {
-          *textPtr = do_QueryInterface(textContent);
-          NS_ASSERTION(*textPtr, "must implement nsIDOMCharacterData");
-        }
-
-        textContent->SetNativeAnonymous();
-
-        // Set aContent as the parent content so that event handling works.
-        nsresult rv = textContent->BindToTree(mDocument, aContent, aContent,
-                                              PR_TRUE);
-        if (NS_FAILED(rv)) {
-          textContent->UnbindFromTree();
-          return rv;
-        }
-
-        // Create a text frame and initialize it
-        textFrame = NS_NewTextFrame(mPresShell, aStyleContext);
-        if (!textFrame) {
-          // XXX The quotes/counters code doesn't like the text pointer
-          // being null in case of dynamic changes!
-          NS_NOTREACHED("this OOM case isn't handled very well");
-          return NS_ERROR_OUT_OF_MEMORY;
-        }
-
-        textFrame->Init(textContent, aParentFrame, nsnull);
-
-        content = textContent;
-        if (NS_UNLIKELY(!aGeneratedContent.AppendObject(content))) {
-          NS_NOTREACHED("this OOM case isn't handled very well");
-          return NS_ERROR_OUT_OF_MEMORY;
-        }
-      } else {
-        // XXX The quotes/counters code doesn't like the text pointer
-        // being null in case of dynamic changes!
-        NS_NOTREACHED("this OOM case isn't handled very well");
-      }
-
-      // Return the text frame
-      *aFrame = textFrame;
-    }
-  }
-
-  return NS_OK;
+    }
+  } // switch
+
+  return nsnull;
+}
+
+static void DestroyContent(void *aObject,
+                           nsIAtom *aPropertyName,
+                           void *aPropertyValue,
+                           void *aData)
+{
+  nsIContent* content = static_cast<nsIContent*>(aPropertyValue);
+  content->UnbindFromTree();
+  NS_RELEASE(content);
 }
 
 /*
- *
- * aFrame - the frame that should be the parent of the generated
+ * aParentFrame - the frame that should be the parent of the generated
  *   content.  This is the frame for the corresponding content node,
  *   which must not be a leaf frame.
+ * 
+ * Any frames created are added to aFrameItems (or possibly left
+ * in the table pseudoframe state in aState).
+ * 
+ * We create an XML element (tag _moz_generated_content_before or
+ * _moz_generated_content_after) representing the pseudoelement. We
+ * create a DOM node for each 'content' item and make those nodes the
+ * children of the XML element. Then we create a frame subtree for
+ * the XML element as if it were a regular child of
+ * aParentFrame/aParentContent, giving the XML element the ::before or
+ * ::after style.
  */
-PRBool
+void
 nsCSSFrameConstructor::CreateGeneratedContentFrame(nsFrameConstructorState& aState,
-                                                   nsIFrame*        aFrame,
-                                                   nsIContent*      aContent,
+                                                   nsIFrame*        aParentFrame,
+                                                   nsIContent*      aParentContent,
                                                    nsStyleContext*  aStyleContext,
                                                    nsIAtom*         aPseudoElement,
-                                                   nsIFrame**       aResult)
-{
-  *aResult = nsnull; // initialize OUT parameter
-
-  if (!aContent->IsNodeOfType(nsINode::eELEMENT))
-    return PR_FALSE;
+                                                   nsFrameItems&    aFrameItems)
+{
+  if (!aParentContent->IsNodeOfType(nsINode::eELEMENT))
+    return;
 
   nsStyleSet *styleSet = mPresShell->StyleSet();
 
   // Probe for the existence of the pseudo-element
   nsRefPtr<nsStyleContext> pseudoStyleContext;
-  pseudoStyleContext = styleSet->ProbePseudoStyleFor(aContent,
+  pseudoStyleContext = styleSet->ProbePseudoStyleFor(aParentContent,
                                                      aPseudoElement,
                                                      aStyleContext);
-
-  if (pseudoStyleContext) {
-    // |ProbePseudoStyleContext| checks the 'display' property and the
-    // |ContentCount()| of the 'content' property for us.
-
-    // Create a block box or an inline box depending on the value of
-    // the 'display' property
-    nsIFrame*     containerFrame;
-    nsFrameItems  childFrames;
-    nsresult rv;
-
-    const PRUint8 disp = pseudoStyleContext->GetStyleDisplay()->mDisplay;
-    if (disp == NS_STYLE_DISPLAY_BLOCK ||
-        disp == NS_STYLE_DISPLAY_INLINE_BLOCK) {
-      PRUint32 flags = 0;
-      if (disp == NS_STYLE_DISPLAY_INLINE_BLOCK) {
-        flags = NS_BLOCK_SPACE_MGR | NS_BLOCK_MARGIN_ROOT;
-      }
-      containerFrame = NS_NewBlockFrame(mPresShell, pseudoStyleContext, flags);
-    } else {
-      containerFrame = NS_NewInlineFrame(mPresShell, pseudoStyleContext);
-    }
-
-    if (NS_UNLIKELY(!containerFrame)) {
-      return PR_FALSE;
-    }
-    InitAndRestoreFrame(aState, aContent, aFrame, nsnull, containerFrame);
-    // XXXbz should we be passing in a non-null aContentParentFrame?
-    nsHTMLContainerFrame::CreateViewForFrame(containerFrame, nsnull, PR_FALSE);
-
-    // Mark the frame as being associated with generated content
-    containerFrame->AddStateBits(NS_FRAME_GENERATED_CONTENT);
-
-    // Create an array to hold all the generated content created for this
-    // frame below in CreateGeneratedFrameFor. No destructor function is
-    // specified because the property is only set here and is removed in
-    // a single place - nsContainerFrame::Destroy.
-    nsCOMArray<nsIContent>* generatedContent = new nsCOMArray<nsIContent>;
-    rv = containerFrame->SetProperty(nsGkAtoms::generatedContent,
-                                     generatedContent);
-    if (NS_UNLIKELY(!generatedContent) || NS_FAILED(rv)) {
-      containerFrame->Destroy(); // this also destroys the created view
-      delete generatedContent;
-      return PR_FALSE;
-    }
-
-    // Create another pseudo style context to use for all the generated child
-    // frames
-    nsRefPtr<nsStyleContext> textStyleContext;
-    textStyleContext = styleSet->ResolveStyleForNonElement(pseudoStyleContext);
-
-    // Now create content objects (and child frames) for each value of the
-    // 'content' property
-
-    const nsStyleContent* styleContent = pseudoStyleContext->GetStyleContent();
-    PRUint32 contentCount = styleContent->ContentCount();
-    for (PRUint32 contentIndex = 0; contentIndex < contentCount; contentIndex++) {
-      nsIFrame* frame;
-
-      // Create a frame
-      rv = CreateGeneratedFrameFor(containerFrame,
-                                   aContent, textStyleContext,
-                                   styleContent, contentIndex,
-                                   *generatedContent, &frame);
-      // Non-elements can't possibly have a view, so don't bother checking
-      if (NS_SUCCEEDED(rv) && frame) {
-        // Add it to the list of child frames
-        childFrames.AddChild(frame);
-      }
-    }
-
-    if (childFrames.childList) {
-      containerFrame->SetInitialChildList(nsnull, childFrames.childList);
-    }
-    *aResult = containerFrame;
-    return PR_TRUE;
-  }
-
-  return PR_FALSE;
+  if (!pseudoStyleContext)
+    return;
+  // |ProbePseudoStyleFor| checked the 'display' property and the
+  // |ContentCount()| of the 'content' property for us.
+  nsCOMPtr<nsINodeInfo> nodeInfo;
+  nsIAtom* elemName = aPseudoElement == nsCSSPseudoElements::before ?
+    nsGkAtoms::mozgeneratedcontentbefore : nsGkAtoms::mozgeneratedcontentafter;
+  mDocument->NodeInfoManager()->GetNodeInfo(elemName, nsnull,
+                                            kNameSpaceID_None,
+                                            getter_AddRefs(nodeInfo));
+  nsIContent* container;
+  nsresult rv = NS_NewXMLElement(&container, nodeInfo);
+  if (NS_FAILED(rv))
+    return;
+  container->SetNativeAnonymous();
+  // Transfer ownership to the frame
+  aParentFrame->SetProperty(aPseudoElement, container, DestroyContent);
+
+  rv = container->BindToTree(mDocument, aParentContent, aParentContent, PR_TRUE);
+  if (NS_FAILED(rv)) {
+    container->UnbindFromTree();
+    return;
+  }
+
+  PRUint32 contentCount = pseudoStyleContext->GetStyleContent()->ContentCount();
+  for (PRUint32 contentIndex = 0; contentIndex < contentCount; contentIndex++) {
+    nsCOMPtr<nsIContent> content =
+      CreateGeneratedContent(aParentContent, pseudoStyleContext, contentIndex);
+    if (content) {
+      container->AppendChildTo(content, PR_FALSE);
+    }
+  }
+
+  nsFrameState savedStateBits = aState.mAdditionalStateBits;
+  // Ensure that frames created here are all tagged with
+  // NS_FRAME_GENERATED_CONTENT.
+  aState.mAdditionalStateBits |= NS_FRAME_GENERATED_CONTENT;
+
+  ConstructFrameInternal(aState, container, aParentFrame,
+    elemName, kNameSpaceID_None, pseudoStyleContext, aFrameItems, PR_TRUE);
+  aState.mAdditionalStateBits = savedStateBits;
 }
 
 nsresult
 nsCSSFrameConstructor::CreateInputFrame(nsFrameConstructorState& aState,
                                         nsIContent*              aContent,
                                         nsIFrame*                aParentFrame,
                                         nsIAtom*                 aTag,
                                         nsStyleContext*          aStyleContext,
@@ -3630,17 +3537,17 @@ nsCSSFrameConstructor::ConstructTableFra
 
     rv = aState.AddChild(aNewOuterFrame, *frameItems, aContent,
                          aStyleContext, parentFrame);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     nsFrameItems childItems;
-    rv = ProcessChildren(aState, aContent, aNewInnerFrame, PR_FALSE, childItems,
+    rv = ProcessChildren(aState, aContent, aNewInnerFrame, PR_TRUE, childItems,
                          PR_FALSE);
     // XXXbz what about cleaning up?
     if (NS_FAILED(rv)) return rv;
 
     // if there are any anonymous children for the table, create frames for them
     CreateAnonymousFrames(nsnull, aState, aContent, aNewInnerFrame,
                           PR_FALSE, childItems);
 
@@ -3759,17 +3666,17 @@ nsCSSFrameConstructor::ConstructTableRow
     }
     InitAndRestoreFrame(aState, aContent, parentFrame, nsnull, aNewFrame);
     // XXXbz should we be passing in a non-null aContentParentFrame?
     nsHTMLContainerFrame::CreateViewForFrame(aNewFrame, nsnull, PR_FALSE);
   }
 
   if (!aIsPseudo) {
     nsFrameItems childItems;
-    rv = ProcessChildren(aState, aContent, aNewFrame, PR_FALSE, childItems,
+    rv = ProcessChildren(aState, aContent, aNewFrame, PR_TRUE, childItems,
                          PR_FALSE);
     
     if (NS_FAILED(rv)) return rv;
 
     // if there are any anonymous children for the table, create frames for them
     CreateAnonymousFrames(nsnull, aState, aContent, aNewFrame,
                           PR_FALSE, childItems);
 
@@ -3820,17 +3727,17 @@ nsCSSFrameConstructor::ConstructTableCol
   aNewFrame = NS_NewTableColGroupFrame(mPresShell, aStyleContext);
   if (NS_UNLIKELY(!aNewFrame)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   InitAndRestoreFrame(aState, aContent, parentFrame, nsnull, aNewFrame);
 
   if (!aIsPseudo) {
     nsFrameItems childItems;
-    rv = ProcessChildren(aState, aContent, aNewFrame, PR_FALSE, childItems,
+    rv = ProcessChildren(aState, aContent, aNewFrame, PR_TRUE, childItems,
                          PR_FALSE);
     if (NS_FAILED(rv)) return rv;
     aNewFrame->SetInitialChildList(nsnull, childItems.childList);
     if (aIsPseudoParent) {
       aState.mPseudoFrames.mTableInner.mChildList.AddChild(aNewFrame);
     }
   }
 
@@ -3876,17 +3783,17 @@ nsCSSFrameConstructor::ConstructTableRow
   if (NS_UNLIKELY(!aNewFrame)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   InitAndRestoreFrame(aState, aContent, parentFrame, nsnull, aNewFrame);
   // XXXbz should we be passing in a non-null aContentParentFrame?
   nsHTMLContainerFrame::CreateViewForFrame(aNewFrame, nsnull, PR_FALSE);
   if (!aIsPseudo) {
     nsFrameItems childItems;
-    rv = ProcessChildren(aState, aContent, aNewFrame, PR_FALSE, childItems,
+    rv = ProcessChildren(aState, aContent, aNewFrame, PR_TRUE, childItems,
                          PR_FALSE);
     if (NS_FAILED(rv)) return rv;
     // if there are any anonymous children for the table, create frames for them
     CreateAnonymousFrames(nsnull, aState, aContent, aNewFrame,
                           PR_FALSE, childItems);
 
     aNewFrame->SetInitialChildList(nsnull, childItems.childList);
     if (aIsPseudoParent) {
@@ -4080,19 +3987,25 @@ nsCSSFrameConstructor::ConstructTableCel
 
   return rv;
 }
 
 static PRBool 
 NeedFrameFor(nsIFrame*   aParentFrame,
              nsIContent* aChildContent) 
 {
-  // don't create a whitespace frame if aParentFrame doesn't want it
-  return !aParentFrame->IsFrameOfType(nsIFrame::eExcludesIgnorableWhitespace) ||
-         !TextIsOnlyWhitespace(aChildContent);
+  // don't create a whitespace frame if aParentFrame doesn't want it.
+  // always create frames for children in generated content. counter(),
+  // quotes, and attr() content can easily change dynamically and we don't
+  // want to be reconstructing frames. It's not even clear that these
+  // should be considered ignorable just because they evaluate to
+  // whitespace.
+  return !aParentFrame->IsFrameOfType(nsIFrame::eExcludesIgnorableWhitespace)
+    || !TextIsOnlyWhitespace(aChildContent)
+    || aParentFrame->IsGeneratedContentFrame();
 }
 
 const nsStyleDisplay* 
 nsCSSFrameConstructor::GetDisplay(nsIFrame* aFrame)
 {
   if (nsnull == aFrame) {
     return nsnull;
   }
@@ -6754,16 +6667,17 @@ nsCSSFrameConstructor::InitAndRestoreFra
   nsresult rv = NS_OK;
   
   NS_ASSERTION(aNewFrame, "Null frame cannot be initialized");
   if (!aNewFrame)
     return NS_ERROR_NULL_POINTER;
 
   // Initialize the frame
   rv = aNewFrame->Init(aContent, aParentFrame, aPrevInFlow);
+  aNewFrame->AddStateBits(aState.mAdditionalStateBits);
 
   if (aState.mFrameState && aState.mFrameManager) {
     // Restore frame state for just the newly created frame.
     aState.mFrameManager->RestoreFrameStateFor(aNewFrame, aState.mFrameState);
   }
 
   if (aAllowCounters && !aPrevInFlow &&
       mCounterManager.AddCounterResetsAndIncrements(aNewFrame)) {
@@ -8582,23 +8496,20 @@ nsCSSFrameConstructor::ContentAppended(n
   nsresult result = NS_OK;
 
   // Notify the parent frame passing it the list of new frames
   if (NS_SUCCEEDED(result) &&
       (frameItems.childList || captionItems.childList)) {
     // Perform special check for diddling around with the frames in
     // a special inline frame.
 
-    // We can't have a block ::after inside an inline, so it's safe to ignore
-    // the fact that we're not really appending if there's ::after content.
-    // Indeed, if we're inserting before the ::after content that means the
-    // ::after content is not the last child of the block in the {ib} split,
-    // which is the only case in which we care whether we're appending.
+    // If we're appending before :after content, then we're not really
+    // appending, so let WipeContainingBlock know that.
     if (WipeContainingBlock(state, containingBlock, parentFrame, frameItems,
-                            PR_TRUE, nsnull)) {
+                            !parentAfterFrame, nsnull)) {
       return NS_OK;
     }
 
     // Append the flowed frames to the principal child list, tables need special treatment
     if (nsGkAtoms::tableFrame == frameType) {
       if (captionItems.childList) { // append the caption to the outer table
         nsIFrame* outerTable = parentFrame->GetParent();
         if (outerTable) { 
@@ -8988,23 +8899,21 @@ nsCSSFrameConstructor::ContentInserted(n
       ::AdjustAppendParentForAfterContent(mPresShell->GetPresContext(),
                                           aContainer,
                                           frameItems.childList->GetParent(),
                                           &appendAfterFrame);
   }
 
   // Perform special check for diddling around with the frames in
   // a special inline frame.
-  // We can't have a block ::after inside an inline, so it's safe to ignore
-  // the fact that we're not really appending if there's ::after content.
-  // Indeed, if we're inserting before the ::after content that means the
-  // ::after content is not the last child of the block in the {ib} split,
-  // which is the only case in which we care whether we're appending.
+
+  // If we're appending before :after content, then we're not really
+  // appending, so let WipeContainingBlock know that.
   if (WipeContainingBlock(state, containingBlock, parentFrame, frameItems,
-                          isAppend, prevSibling))
+                          isAppend && !appendAfterFrame, prevSibling))
     return NS_OK;
 
   if (haveFirstLineStyle && parentFrame == containingBlock) {
     // It's possible that the new frame goes into a first-line
     // frame. Look at it and see...
     if (isAppend) {
       // Use append logic when appending
       AppendFirstLineFrames(state, containingBlock->GetContent(),
@@ -10227,17 +10136,17 @@ nsCSSFrameConstructor::CreateContinuingT
                                         GetAbsoluteContainingBlock(newFrame),
                                         nsnull);
 
           headerFooterFrame = static_cast<nsTableRowGroupFrame*>
                                          (NS_NewTableRowGroupFrame(aPresShell, rowGroupFrame->GetStyleContext()));
           nsIContent* headerFooter = rowGroupFrame->GetContent();
           headerFooterFrame->Init(headerFooter, newFrame, nsnull);
           ProcessChildren(state, headerFooter, headerFooterFrame,
-                          PR_FALSE, childItems, PR_FALSE);
+                          PR_TRUE, childItems, PR_FALSE);
           NS_ASSERTION(!state.mFloatedItems.childList, "unexpected floated element");
           headerFooterFrame->SetInitialChildList(nsnull, childItems.childList);
           headerFooterFrame->SetRepeatable(PR_TRUE);
 
           // Table specific initialization
           headerFooterFrame->InitRepeatedFrame(aPresContext, rowGroupFrame);
 
           // XXX Deal with absolute and fixed frames...
@@ -11219,61 +11128,52 @@ nsCSSFrameConstructor::ProcessChildren(n
   // XXXbz ideally, this would do all the pushing of various
   // containing blocks as needed, so callers don't have to do it...
   nsresult rv = NS_OK;
   // :before/:after content should have the same style context parent
   // as normal kids.
   nsStyleContext* styleContext =
     nsFrame::CorrectStyleParentFrame(aFrame, nsnull)->GetStyleContext();
     
-  if (aCanHaveGeneratedContent) {
-    // Probe for generated content before
-    nsIFrame* generatedFrame;
-    if (CreateGeneratedContentFrame(aState, aFrame, aContent,
-                                    styleContext, nsCSSPseudoElements::before,
-                                    &generatedFrame)) {
-      // Add the generated frame to the child list
-      aFrameItems.AddChild(generatedFrame);
-    }
-  }
-
- 
   // save the incoming pseudo frame state
   nsPseudoFrames priorPseudoFrames;
   aState.mPseudoFrames.Reset(&priorPseudoFrames);
 
+  if (aCanHaveGeneratedContent) {
+    // Probe for generated content before
+    CreateGeneratedContentFrame(aState, aFrame, aContent,
+                                styleContext, nsCSSPseudoElements::before,
+                                aFrameItems);
+  }
+
   ChildIterator iter, last;
   for (ChildIterator::Init(aContent, &iter, &last);
        iter != last;
        ++iter) {
     rv = ConstructFrame(aState, nsCOMPtr<nsIContent>(*iter),
                         aFrame, aFrameItems);
     if (NS_FAILED(rv))
       return rv;
   }
 
+  if (aCanHaveGeneratedContent) {
+    // Probe for generated content after
+    CreateGeneratedContentFrame(aState, aFrame, aContent,
+                                styleContext, nsCSSPseudoElements::after,
+                                aFrameItems);
+  }
+
   // process the current pseudo frame state
   if (!aState.mPseudoFrames.IsEmpty()) {
     ProcessPseudoFrames(aState, aFrameItems);
   }
 
   // restore the incoming pseudo frame state
   aState.mPseudoFrames = priorPseudoFrames;
 
-  if (aCanHaveGeneratedContent) {
-    // Probe for generated content after
-    nsIFrame* generatedFrame;
-    if (CreateGeneratedContentFrame(aState, aFrame, aContent,
-                                    styleContext, nsCSSPseudoElements::after,
-                                    &generatedFrame)) {
-      // Add the generated frame to the child list
-      aFrameItems.AddChild(generatedFrame);
-    }
-  }
-
   if (aParentIsBlock) {
     if (aState.mFirstLetterStyle) {
       rv = WrapFramesInFirstLetterFrame(aState, aContent, aFrame, aFrameItems);
     }
     if (aState.mFirstLineStyle) {
       rv = WrapFramesInFirstLineFrame(aState, aContent, aFrame, aFrameItems);
     }
   }
@@ -12601,24 +12501,20 @@ nsCSSFrameConstructor::ProcessInlineChil
   nsStyleContext* styleContext = nsnull;
 
   // save the pseudo frame state 
   nsPseudoFrames prevPseudoFrames; 
   aState.mPseudoFrames.Reset(&prevPseudoFrames);
 
   if (aCanHaveGeneratedContent) {
     // Probe for generated content before
-    nsIFrame* generatedFrame;
     styleContext = aFrame->GetStyleContext();
-    if (CreateGeneratedContentFrame(aState, aFrame, aContent,
-                                    styleContext, nsCSSPseudoElements::before,
-                                    &generatedFrame)) {
-      // Add the generated frame to the child list
-      aFrameItems.AddChild(generatedFrame);
-    }
+    CreateGeneratedContentFrame(aState, aFrame, aContent,
+                                styleContext, nsCSSPseudoElements::before,
+                                aFrameItems);
   }
 
   // Iterate the child content objects and construct frames
   PRBool allKidsInline = PR_TRUE;
   ChildIterator iter, last;
   for (ChildIterator::Init(aContent, &iter, &last);
        iter != last;
        ++iter) {
@@ -12649,23 +12545,19 @@ nsCSSFrameConstructor::ProcessInlineChil
         }
         kid = kid->GetNextSibling();
       }
     }
   }
 
   if (aCanHaveGeneratedContent) {
     // Probe for generated content after
-    nsIFrame* generatedFrame;
-    if (CreateGeneratedContentFrame(aState, aFrame, aContent,
-                                    styleContext, nsCSSPseudoElements::after,
-                                    &generatedFrame)) {
-      // Add the generated frame to the child list
-      aFrameItems.AddChild(generatedFrame);
-    }
+    CreateGeneratedContentFrame(aState, aFrame, aContent,
+                                styleContext, nsCSSPseudoElements::after,
+                                aFrameItems);
   }
 
   // process the current pseudo frame state
   if (!aState.mPseudoFrames.IsEmpty()) {
     ProcessPseudoFrames(aState, aFrameItems);
     // recompute allKidsInline to take into account new child frames
     // XXX we DON'T do this yet because anonymous table children should
     // be accepted as inline children, until we turn on inline-table.
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -318,31 +318,43 @@ private:
   nsresult CreateAttributeContent(nsIContent* aParentContent,
                                   nsIFrame* aParentFrame,
                                   PRInt32 aAttrNamespace,
                                   nsIAtom* aAttrName,
                                   nsStyleContext* aStyleContext,
                                   nsCOMArray<nsIContent>& aGeneratedContent,
                                   nsIContent** aNewContent,
                                   nsIFrame** aNewFrame);
-  
-  nsresult CreateGeneratedFrameFor(nsIFrame*             aParentFrame,
-                                   nsIContent*           aContent,
-                                   nsStyleContext*       aStyleContext,
-                                   const nsStyleContent* aStyleContent,
-                                   PRUint32              aContentIndex,
-                                   nsCOMArray<nsIContent>& aGeneratedContent,
-                                   nsIFrame**            aFrame);
+
+  /**
+   * Create a text node containing the given string. If aText is non-null
+   * then we also set aText to the returned node.
+   */
+  already_AddRefed<nsIContent> CreateTextNode(const nsString& aString,  
+                                              nsCOMPtr<nsIDOMCharacterData>* aText);
 
-  PRBool CreateGeneratedContentFrame(nsFrameConstructorState& aState,
-                                     nsIFrame*                aFrame,
-                                     nsIContent*              aContent,
-                                     nsStyleContext*          aStyleContext,
-                                     nsIAtom*                 aPseudoElement,
-                                     nsIFrame**               aResult);
+  /**
+   * Create a content node for the given generated content style.
+   * The caller takes care of making it SetNativeAnonymous, binding it
+   * to the document, and creating frames for it.
+   * @param aParentContent is the node that has the before/after style
+   * @param aStyleContext is the 'before' or 'after' pseudo-element
+   * style context
+   * @param aContentIndex is the index of the content item to create
+   */
+  already_AddRefed<nsIContent> CreateGeneratedContent(nsIContent*     aParentContent,
+                                                      nsStyleContext* aStyleContext,
+                                                      PRUint32        aContentIndex);
+
+  void CreateGeneratedContentFrame(nsFrameConstructorState& aState,
+                                   nsIFrame*                aFrame,
+                                   nsIContent*              aContent,
+                                   nsStyleContext*          aStyleContext,
+                                   nsIAtom*                 aPseudoElement,
+                                   nsFrameItems&            aFrameItems);
 
   // This method can change aFrameList: it can chop off the end and
   // put it in a special sibling of aParentFrame.  It can also change
   // aState by moving some floats out of it.
   nsresult AppendFrames(nsFrameConstructorState&       aState,
                         nsIContent*                    aContainer,
                         nsIFrame*                      aParentFrame,
                         nsFrameItems&                  aFrameList,
--- a/layout/base/nsCounterManager.cpp
+++ b/layout/base/nsCounterManager.cpp
@@ -34,18 +34,21 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 /* implementation of CSS counters (for numbering things) */
 
 #include "nsCounterManager.h"
+
+#include "nsIFrame.h"
 #include "nsBulletFrame.h" // legacy location for list style type to text code
 #include "nsContentUtils.h"
+#include "nsCSSPseudoElements.h"
 
 // assign the correct |mValueAfter| value to a node that has been inserted
 // Should be called immediately after calling |Insert|.
 void nsCounterUseNode::Calc(nsCounterList *aList)
 {
     NS_ASSERTION(!aList->IsDirty(),
                  "Why are we calculating with a dirty list?");
     mValueAfter = aList->ValueBefore(this);
@@ -88,16 +91,29 @@ nsCounterUseNode::GetText(nsString& aRes
         nsBulletFrame::AppendCounterText(style, n->mValueAfter, aResult);
         if (i == 0)
             break;
         NS_ASSERTION(mAllCounters, "yikes, separator is uninitialized");
         aResult.Append(separator);
     }
 }
 
+// Get the content that determines the scope for aNode. For non-generated
+// content, this is the parent of the element associated with aNode,
+// otherwise it's the mParentContent stored in aNode.
+static nsIContent *
+GetScopeContent(nsCounterNode *aNode)
+{
+  nsIContent *content = aNode->mParentContent;
+  if (!aNode->mPseudoType) {
+    content = content->GetParent();
+  }
+  return content;
+}
+
 void
 nsCounterList::SetScope(nsCounterNode *aNode)
 {
     // This function is responsible for setting |mScopeStart| and
     // |mScopePrev| (whose purpose is described in nsCounterManager.h).
     // We do this by starting from the node immediately preceding
     // |aNode| in content tree order, which is reasonably likely to be
     // the previous element in our scope (or, for a reset, the previous
@@ -107,42 +123,30 @@ nsCounterList::SetScope(nsCounterNode *a
     // appropriate.
 
     if (aNode == First()) {
         aNode->mScopeStart = nsnull;
         aNode->mScopePrev = nsnull;
         return;
     }
 
-    // Get the content node for aNode's rendering object's *parent*,
-    // since scope includes siblings, so we want a descendant check on
-    // parents.  If aNode is for a pseudo-element, then the parent
-    // rendering object is the frame's content; if aNode is for an
-    // element, then the parent rendering object is the frame's
-    // content's parent.
-    nsIContent *nodeContent = aNode->mPseudoFrame->GetContent();
-    if (!aNode->mPseudoFrame->GetStyleContext()->GetPseudoType()) {
-        nodeContent = nodeContent->GetParent();
-    }
+    nsIContent *nodeContent = GetScopeContent(aNode);
 
     for (nsCounterNode *prev = Prev(aNode), *start;
          prev; prev = start->mScopePrev) {
         // If |prev| starts a scope (because it's a real or implied
         // reset), we want it as the scope start rather than the start
         // of its enclosing scope.  Otherwise, there's no enclosing
         // scope, so the next thing in prev's scope shares its scope
         // start.
         start = (prev->mType == nsCounterNode::RESET || !prev->mScopeStart)
                   ? prev : prev->mScopeStart;
 
         // |startContent| is analogous to |nodeContent| (see above).
-        nsIContent *startContent = start->mPseudoFrame->GetContent();
-        if (!start->mPseudoFrame->GetStyleContext()->GetPseudoType()) {
-            startContent = startContent->GetParent();
-        }
+        nsIContent *startContent = GetScopeContent(start);
         NS_ASSERTION(nodeContent || !startContent,
                      "null check on startContent should be sufficient to "
                      "null check nodeContent as well, since if nodeContent "
                      "is for the root, startContent (which is before it) "
                      "must be too");
 
              // A reset's outer scope can't be a scope created by a sibling.
         if (!(aNode->mType == nsCounterNode::RESET &&
@@ -192,43 +196,63 @@ nsCounterList::RecalcAll()
 nsCounterManager::nsCounterManager()
 {
     mNames.Init(16);
 }
 
 PRBool
 nsCounterManager::AddCounterResetsAndIncrements(nsIFrame *aFrame)
 {
-    const nsStyleContent *styleContent = aFrame->GetStyleContent();
+    nsStyleContext *styleContext = aFrame->GetStyleContext();
+    const nsStyleContent *styleContent = styleContext->GetStyleContent();
     if (!styleContent->CounterIncrementCount() &&
         !styleContent->CounterResetCount())
         return PR_FALSE;
 
+    nsIContent *content = aFrame->GetContent();
+    nsIAtom *pseudo =
+      nsGenConNode::ToGeneratedContentType(styleContext->GetPseudoType());
+    if (pseudo) {
+        // The frame with counter increments/resets on it should be either
+        // non-generated content or else a frame for the anonymous
+        // generated content container
+        NS_ASSERTION(content->Tag() ==
+                     (pseudo == nsCSSPseudoElements::before
+                         ? nsGkAtoms::mozgeneratedcontentbefore 
+                         : nsGkAtoms::mozgeneratedcontentafter),
+                     "Not a generated content node");
+        // The parent of the container is the real content node
+        content = content->GetParent();
+    }
+
     // Add in order, resets first, so all the comparisons will be optimized
     // for addition at the end of the list.
     PRInt32 i, i_end;
     PRBool dirty = PR_FALSE;
     for (i = 0, i_end = styleContent->CounterResetCount(); i != i_end; ++i)
-        dirty |= AddResetOrIncrement(aFrame, i,
+        dirty |= AddResetOrIncrement(content, styleContext, i,
                                      styleContent->GetCounterResetAt(i),
                                      nsCounterChangeNode::RESET);
     for (i = 0, i_end = styleContent->CounterIncrementCount(); i != i_end; ++i)
-        dirty |= AddResetOrIncrement(aFrame, i,
+        dirty |= AddResetOrIncrement(content, styleContext, i,
                                      styleContent->GetCounterIncrementAt(i),
                                      nsCounterChangeNode::INCREMENT);
     return dirty;
 }
 
 PRBool
-nsCounterManager::AddResetOrIncrement(nsIFrame *aFrame, PRInt32 aIndex,
+nsCounterManager::AddResetOrIncrement(nsIContent *aContent,
+                                      nsStyleContext *aStyleContext,
+                                      PRInt32 aIndex,
                                       const nsStyleCounterData *aCounterData,
                                       nsCounterNode::Type aType)
 {
     nsCounterChangeNode *node =
-        new nsCounterChangeNode(aFrame, aType, aCounterData->mValue, aIndex);
+        new nsCounterChangeNode(aContent, aStyleContext, aType,
+                                aCounterData->mValue, aIndex);
     if (!node)
         return PR_FALSE;
 
     nsCounterList *counterList = CounterListFor(aCounterData->mCounter);
     if (!counterList) {
         NS_NOTREACHED("CounterListFor failed (should only happen on OOM)");
         return PR_FALSE;
     }
@@ -277,59 +301,65 @@ RecalcDirtyLists(const nsAString& aKey, 
 
 void
 nsCounterManager::RecalcAll()
 {
     mNames.EnumerateRead(RecalcDirtyLists, nsnull);
 }
 
 struct DestroyNodesData {
-    DestroyNodesData(nsIFrame *aFrame)
-        : mFrame(aFrame)
+    DestroyNodesData(nsIContent* aParentContent, nsIAtom *aPseudo)
+        : mParentContent(aParentContent)
+        , mPseudo(aPseudo)
         , mDestroyedAny(PR_FALSE)
     {
     }
 
-    nsIFrame *mFrame;
+    nsIContent *mParentContent;
+    nsIAtom *mPseudo;
     PRBool mDestroyedAny;
 };
 
 PR_STATIC_CALLBACK(PLDHashOperator)
 DestroyNodesInList(const nsAString& aKey, nsCounterList* aList, void* aClosure)
 {
     DestroyNodesData *data = static_cast<DestroyNodesData*>(aClosure);
-    if (aList->DestroyNodesFor(data->mFrame)) {
+    if (aList->DestroyNodesFor(data->mParentContent, data->mPseudo)) {
         data->mDestroyedAny = PR_TRUE;
         aList->SetDirty();
     }
     return PL_DHASH_NEXT;
 }
 
 PRBool
-nsCounterManager::DestroyNodesFor(nsIFrame *aFrame)
+nsCounterManager::DestroyNodesFor(nsIContent* aParentContent, nsIAtom *aPseudo)
 {
-    DestroyNodesData data(aFrame);
+    DestroyNodesData data(aParentContent, aPseudo);
     mNames.EnumerateRead(DestroyNodesInList, &data);
     return data.mDestroyedAny;
 }
 
 #ifdef DEBUG
 PR_STATIC_CALLBACK(PLDHashOperator)
 DumpList(const nsAString& aKey, nsCounterList* aList, void* aClosure)
 {
     printf("Counter named \"%s\":\n", NS_ConvertUTF16toUTF8(aKey).get());
     nsCounterNode *node = aList->First();
 
     if (node) {
         PRInt32 i = 0;
         do {
+            nsCAutoString pseudo;
+            if (node->mPseudoType) {
+              node->mPseudoType->ToUTF8String(pseudo);
+            }
             const char *types[] = { "RESET", "INCREMENT", "USE" };
-            printf("  Node #%d @%p frame=%p index=%d type=%s valAfter=%d\n"
+            printf("  Node #%d @%p content=%p pseudo=%s index=%d type=%s valAfter=%d\n"
                    "       scope-start=%p scope-prev=%p",
-                   i++, (void*)node, (void*)node->mPseudoFrame,
+                   i++, (void*)node, (void*)node->mParentContent, pseudo.get(),
                    node->mContentIndex, types[node->mType], node->mValueAfter,
                    (void*)node->mScopeStart, (void*)node->mScopePrev);
             if (node->mType == nsCounterNode::USE) {
                 nsAutoString text;
                 node->UseNode()->GetText(text);
                 printf(" text=%s", NS_ConvertUTF16toUTF8(text).get());
             }
             printf("\n");
--- a/layout/base/nsCounterManager.h
+++ b/layout/base/nsCounterManager.h
@@ -40,16 +40,17 @@
 
 #ifndef nsCounterManager_h_
 #define nsCounterManager_h_
 
 #include "nsGenConList.h"
 #include "nsAutoPtr.h"
 #include "nsClassHashtable.h"
 
+class nsIFrame;
 class nsCounterList;
 struct nsCounterUseNode;
 struct nsCounterChangeNode;
 
 struct nsCounterNode : public nsGenConNode {
     enum Type {
         RESET,     // a "counter number" pair in 'counter-reset'
         INCREMENT, // a "counter number" pair in 'counter-increment'
@@ -81,24 +82,25 @@ struct nsCounterNode : public nsGenConNo
     // mScopeStart.  Being null for a non-RESET means that it is an
     // implied 'counter-reset'.  Being null for a RESET means it has no
     // outer scope.
     nsCounterNode *mScopePrev;
 
     inline nsCounterUseNode* UseNode();
     inline nsCounterChangeNode* ChangeNode();
 
-    // For RESET and INCREMENT nodes, aPseudoFrame need not be a
-    // pseudo-element, and aContentIndex represents the index within the
+    // For RESET and INCREMENT nodes, aStyleContext may have a null
+    // psuedo type, and aContentIndex represents the index within the
     // 'counter-reset' or 'counter-increment' property instead of within
     // the 'content' property but offset to ensure that (reset,
     // increment, use) sort in that order.  (This slight weirdness
     // allows sharing a lot of code with 'quotes'.)
-    nsCounterNode(nsIFrame* aPseudoFrame, PRInt32 aContentIndex, Type aType)
-        : nsGenConNode(aPseudoFrame, aContentIndex)
+    nsCounterNode(nsIContent* aParentContent, nsStyleContext* aStyleContext,
+                  PRInt32 aContentIndex, Type aType)
+        : nsGenConNode(aParentContent, aStyleContext, aContentIndex)
         , mType(aType)
         , mValueAfter(0)
         , mScopeStart(nsnull)
         , mScopePrev(nsnull)
     {
     }
 
     // to avoid virtual function calls in the common case
@@ -110,19 +112,20 @@ struct nsCounterUseNode : public nsCount
     // containing the values in the counter() or counters() in the order
     // given in the CSS spec.
     nsRefPtr<nsCSSValue::Array> mCounterStyle;
 
     // false for counter(), true for counters()
     PRBool mAllCounters;
 
     // args go directly to member variables here and of nsGenConNode
-    nsCounterUseNode(nsCSSValue::Array* aCounterStyle, nsIFrame* aPseudoFrame,
+    nsCounterUseNode(nsCSSValue::Array* aCounterStyle,
+                     nsIContent* aParentContent, nsStyleContext* aStyleContext,
                      PRUint32 aContentIndex, PRBool aAllCounters)
-        : nsCounterNode(aPseudoFrame, aContentIndex, USE)
+        : nsCounterNode(aParentContent, aStyleContext, aContentIndex, USE)
         , mCounterStyle(aCounterStyle)
         , mAllCounters(aAllCounters)
     {
         NS_ASSERTION(aContentIndex >= 0, "out of range");
     }
 
     // assign the correct |mValueAfter| value to a node that has been inserted
     // Should be called immediately after calling |Insert|.
@@ -130,26 +133,23 @@ struct nsCounterUseNode : public nsCount
 
     // The text that should be displayed for this counter.
     void GetText(nsString& aResult);
 };
 
 struct nsCounterChangeNode : public nsCounterNode {
     PRInt32 mChangeValue; // the numeric value of the increment or reset
 
-    // |aPseudoFrame| is not necessarily a pseudo-element's frame, but
-    // since it is for every other subclass of nsGenConNode, we follow
-    // the naming convention here.
     // |aPropIndex| is the index of the value within the list in the
     // 'counter-increment' or 'counter-reset' property.
-    nsCounterChangeNode(nsIFrame* aPseudoFrame,
+    nsCounterChangeNode(nsIContent* aContent, nsStyleContext* aStyleContext,
                         nsCounterNode::Type aChangeType,
                         PRInt32 aChangeValue,
                         PRInt32 aPropIndex)
-        : nsCounterNode(aPseudoFrame,
+        : nsCounterNode(aContent, aStyleContext,
                         // Fake a content index for resets and increments
                         // that comes before all the real content, with
                         // the resets first, in order, and then the increments.
                         aPropIndex + (aChangeType == RESET
                                         ? (PR_INT32_MIN) 
                                         : (PR_INT32_MIN / 2)),
                         aChangeType)
         , mChangeValue(aChangeValue)
@@ -240,29 +240,35 @@ public:
 
     // Gets the appropriate counter list, creating it if necessary.
     // Returns null only on out-of-memory.
     nsCounterList* CounterListFor(const nsSubstring& aCounterName);
 
     // Clean up data in any dirty counter lists.
     void RecalcAll();
 
-    // Destroy nodes for the frame in any lists, and return whether any
-    // nodes were destroyed.
-    PRBool DestroyNodesFor(nsIFrame *aFrame);
+    // Destroy nodes for the given content and pseudo in any lists, and
+    // return whether any nodes were destroyed. aPseudo may be null to
+    // indicate that the counter is an increment or reset directly
+    // associated with the content.
+    PRBool DestroyNodesFor(nsIContent* aParentContent, nsIAtom* aPseudo);
 
     // Clear all data.
     void Clear() { mNames.Clear(); }
 
 #ifdef DEBUG
     void Dump();
 #endif
+    
+    PRBool IsEmpty() { return mNames.Count() == 0; }
 
 private:
     // for |AddCounterResetsAndIncrements| only
-    PRBool AddResetOrIncrement(nsIFrame *aFrame, PRInt32 aIndex,
+    PRBool AddResetOrIncrement(nsIContent *aContent,
+                               nsStyleContext *aStyleContext,
+                               PRInt32 aIndex,
                                const nsStyleCounterData *aCounterData,
                                nsCounterNode::Type aType);
 
     nsClassHashtable<nsStringHashKey, nsCounterList> mNames;
 };
 
 #endif /* nsCounterManager_h_ */
--- a/layout/base/nsGenConList.cpp
+++ b/layout/base/nsGenConList.cpp
@@ -55,91 +55,90 @@ nsGenConList::Clear()
   }
   delete mFirstNode;
 
   mFirstNode = nsnull;
   mSize = 0;
 }
 
 PRBool
-nsGenConList::DestroyNodesFor(nsIFrame* aFrame)
+nsGenConList::DestroyNodesFor(nsIContent* aParentContent, nsIAtom* aPseudo)
 {
   if (!mFirstNode)
     return PR_FALSE; // list empty
   nsGenConNode* node;
   PRBool destroyed = PR_FALSE;
-  while (mFirstNode->mPseudoFrame == aFrame) {
+  while (mFirstNode->mParentContent == aParentContent &&
+         mFirstNode->mPseudoType == aPseudo) {
     destroyed = PR_TRUE;
     node = Next(mFirstNode);
     PRBool isLastNode = node == mFirstNode; // before they're dangling
     Remove(mFirstNode);
     delete mFirstNode;
     if (isLastNode) {
       mFirstNode = nsnull;
       return PR_TRUE;
     }
     else {
       mFirstNode = node;
     }
   }
   node = Next(mFirstNode);
   while (node != mFirstNode) {
-    if (node->mPseudoFrame == aFrame) {
+    if (node->mParentContent == aParentContent &&
+        node->mPseudoType == aPseudo) {
       destroyed = PR_TRUE;
       nsGenConNode *nextNode = Next(node);
       Remove(node);
       delete node;
       node = nextNode;
     } else {
       node = Next(node);
     }
   }
   return destroyed;
 }
 
 // return -1 for ::before, +1 for ::after, and 0 otherwise.
-inline PRInt32 PseudoCompareType(nsIFrame *aFrame)
+inline PRInt32 PseudoCompareType(nsIAtom *aPseudo)
 {
-  nsIAtom *pseudo = aFrame->GetStyleContext()->GetPseudoType();
-  if (pseudo == nsCSSPseudoElements::before)
+  if (aPseudo == nsCSSPseudoElements::before)
     return -1;
-  if (pseudo == nsCSSPseudoElements::after)
+  if (aPseudo == nsCSSPseudoElements::after)
     return 1;
   return 0;
 }
 
 /* static */ PRBool
 nsGenConList::NodeAfter(const nsGenConNode* aNode1, const nsGenConNode* aNode2)
 {
-  nsIFrame *frame1 = aNode1->mPseudoFrame;
-  nsIFrame *frame2 = aNode2->mPseudoFrame;
-  if (frame1 == frame2) {
+  nsIContent *content1 = aNode1->mParentContent;
+  nsIContent *content2 = aNode2->mParentContent;
+  PRInt32 pseudoType1 = PseudoCompareType(aNode1->mPseudoType);
+  PRInt32 pseudoType2 = PseudoCompareType(aNode2->mPseudoType);
+  if (content1 == content2 && pseudoType1 == pseudoType2) {
     NS_ASSERTION(aNode2->mContentIndex != aNode1->mContentIndex, "identical");
     return aNode1->mContentIndex > aNode2->mContentIndex;
   }
-  PRInt32 pseudoType1 = PseudoCompareType(frame1);
-  PRInt32 pseudoType2 = PseudoCompareType(frame2);
-  nsIContent *content1 = frame1->GetContent();
-  nsIContent *content2 = frame2->GetContent();
   if (pseudoType1 == 0 || pseudoType2 == 0) {
     if (content1 == content2) {
       NS_ASSERTION(pseudoType1 != pseudoType2, "identical");
       return pseudoType2 == 0;
     }
     // We want to treat an element as coming before its :before (preorder
     // traversal), so treating both as :before now works.
     if (pseudoType1 == 0) pseudoType1 = -1;
     if (pseudoType2 == 0) pseudoType2 = -1;
   } else {
     if (content1 == content2) {
       NS_ASSERTION(pseudoType1 != pseudoType2, "identical");
       return pseudoType1 == 1;
     }
   }
-  // XXX Switch to the frame version of DoCompareTreePosition?
+  // XXX This doesn't handle anonymous (XBL) content properly.
   PRInt32 cmp = nsLayoutUtils::DoCompareTreePosition(content1, content2,
                                                      pseudoType1, -pseudoType2);
   NS_ASSERTION(cmp != 0, "same content, different frames");
   return cmp > 0;
 }
 
 void
 nsGenConList::Insert(nsGenConNode* aNode)
--- a/layout/base/nsGenConList.h
+++ b/layout/base/nsGenConList.h
@@ -35,57 +35,62 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 /* base class for nsCounterList and nsQuoteList */
 
 #ifndef nsGenConList_h___
 #define nsGenConList_h___
 
-#include "nsIFrame.h"
+#include "nsIContent.h"
 #include "nsStyleStruct.h"
+#include "nsStyleContext.h"
 #include "prclist.h"
 #include "nsIDOMCharacterData.h"
 #include "nsCSSPseudoElements.h"
 
 struct nsGenConNode : public PRCList {
-  // The wrapper frame for all of the pseudo-element's content.  This
-  // frame generally has useful style data and has the
-  // NS_FRAME_GENERATED_CONTENT bit set (so we use it to track removal),
-  // but does not necessarily for |nsCounterChangeNode|s.
-  nsIFrame* const mPseudoFrame;
+  // The content associated with this node. This is never a generated
+  // content element. When mPseudoType is non-null, this is the element
+  // for which we generated the anonymous content. If mPseudoType is null,
+  // this is the element associated with a counter reset or increment.  
+  nsIContent* mParentContent;
+  // nsGkAtoms::before, nsGkAtoms::after, or null
+  nsIAtom*    mPseudoType;
 
   // Index within the list of things specified by the 'content' property,
   // which is needed to do 'content: open-quote open-quote' correctly,
   // and needed for similar cases for counters.
   const PRInt32 mContentIndex;
 
   // null for 'content:no-open-quote', 'content:no-close-quote' and for
   // counter nodes for increments and resets (rather than uses)
   nsCOMPtr<nsIDOMCharacterData> mText;
 
-  nsGenConNode(nsIFrame* aPseudoFrame, PRInt32 aContentIndex)
-    : mPseudoFrame(aPseudoFrame)
+  static nsIAtom* ToGeneratedContentType(nsIAtom* aPseudoType)
+  {
+    if (aPseudoType == nsCSSPseudoElements::before ||
+        aPseudoType == nsCSSPseudoElements::after)
+      return aPseudoType;
+    return nsnull;
+  }
+  
+  nsGenConNode(nsIContent* aParentContent, nsStyleContext* aStyleContext,
+               PRInt32 aContentIndex)
+    : mParentContent(aParentContent)
+    , mPseudoType(ToGeneratedContentType(aStyleContext->GetPseudoType()))
     , mContentIndex(aContentIndex)
   {
     NS_ASSERTION(aContentIndex <
-                   PRInt32(aPseudoFrame->GetStyleContent()->ContentCount()),
+                 PRInt32(aStyleContext->GetStyleContent()->ContentCount()),
                  "index out of range");
     // We allow negative values of mContentIndex for 'counter-reset' and
     // 'counter-increment'.
-
-    NS_ASSERTION(aContentIndex < 0 ||
-                 aPseudoFrame->GetStyleContext()->GetPseudoType() ==
-                   nsCSSPseudoElements::before ||
-                 aPseudoFrame->GetStyleContext()->GetPseudoType() ==
-                   nsCSSPseudoElements::after,
+    NS_ASSERTION(aContentIndex < 0 || mPseudoType,
                  "not :before/:after generated content and not counter change");
-    NS_ASSERTION(aContentIndex < 0 ||
-                 aPseudoFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT,
-                 "not generated content and not counter change");
   }
 
   virtual ~nsGenConNode() {} // XXX Avoid, perhaps?
 };
 
 class nsGenConList {
 protected:
   nsGenConNode* mFirstNode;
@@ -96,18 +101,21 @@ public:
   void Clear();
   static nsGenConNode* Next(nsGenConNode* aNode) {
     return static_cast<nsGenConNode*>(PR_NEXT_LINK(aNode));
   }
   static nsGenConNode* Prev(nsGenConNode* aNode) {
     return static_cast<nsGenConNode*>(PR_PREV_LINK(aNode));
   }
   void Insert(nsGenConNode* aNode);
-  // returns whether any nodes have been destroyed
-  PRBool DestroyNodesFor(nsIFrame* aFrame); //destroy all nodes with aFrame as parent
+  /**
+   * Destroy all nodes whose aContent/aPseudo match.
+   * @return true if some nodes were destroyed
+   */
+  PRBool DestroyNodesFor(nsIContent* aParentContent, nsIAtom* aPseudo);
 
   // Return true if |aNode1| is after |aNode2|.
   static PRBool NodeAfter(const nsGenConNode* aNode1,
                           const nsGenConNode* aNode2);
 
   void Remove(nsGenConNode* aNode) { PR_REMOVE_LINK(aNode); mSize--; }
   PRBool IsLast(nsGenConNode* aNode) { return (Next(aNode) == mFirstNode); }
 };
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -243,22 +243,29 @@ nsLayoutUtils::IsGeneratedContentFor(nsI
                                      nsIAtom* aPseudoElement)
 {
   NS_PRECONDITION(aFrame, "Must have a frame");
   NS_PRECONDITION(aPseudoElement, "Must have a pseudo name");
 
   if (!aFrame->IsGeneratedContentFrame()) {
     return PR_FALSE;
   }
+  nsIFrame* parent = aFrame->GetParent();
+  NS_ASSERTION(parent, "Generated content can't be root frame");
+  if (parent->IsGeneratedContentFrame()) {
+    // Not the root of the generated content
+    return PR_FALSE;
+  }
   
-  if (aContent && aFrame->GetContent() != aContent) {
+  if (aContent && parent->GetContent() != aContent) {
     return PR_FALSE;
   }
 
-  return aFrame->GetStyleContext()->GetPseudoType() == aPseudoElement;
+  return (aFrame->GetContent()->Tag() == nsGkAtoms::mozgeneratedcontentbefore) ==
+    (aPseudoElement == nsCSSPseudoElements::before);
 }
 
 // static
 nsIFrame*
 nsLayoutUtils::GetCrossDocParentFrame(nsIFrame* aFrame)
 {
   nsIFrame* p = aFrame->GetParent();
   if (p)
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -66,27 +66,27 @@ class nsBlockFrame;
  * nsLayoutUtils is a namespace class used for various helper
  * functions that are useful in multiple places in layout.  The goal
  * is not to define multiple copies of the same static helper.
  */
 class nsLayoutUtils
 {
 public:
   /**
-   * GetBeforeFrame returns the :before frame of the given frame, if
+   * GetBeforeFrame returns the outermost :before frame of the given frame, if
    * one exists.  This is typically O(1).  The frame passed in must be
    * the first-in-flow.   
    *
    * @param aFrame the frame whose :before is wanted
    * @return the :before frame or nsnull if there isn't one
    */
   static nsIFrame* GetBeforeFrame(nsIFrame* aFrame);
 
   /**
-   * GetAfterFrame returns the :after frame of the given frame, if one
+   * GetAfterFrame returns the outermost :after frame of the given frame, if one
    * exists.  This will walk the in-flow chain to the last-in-flow if
    * needed.  This function is typically O(N) in the number of child
    * frames, following in-flows, etc.
    *
    * @param aFrame the frame whose :after is wanted
    * @return the :after frame or nsnull if there isn't one
    */
   static nsIFrame* GetAfterFrame(nsIFrame* aFrame);
@@ -111,18 +111,21 @@ public:
    *         such ancestor exists
    */
   static nsIFrame* GetPageFrame(nsIFrame* aFrame)
   {
     return GetClosestFrameOfType(aFrame, nsGkAtoms::pageFrame);
   }
 
   /**
-   * IsGeneratedContentFor returns PR_TRUE if aFrame is generated
-   * content of type aPseudoElement for aContent
+   * IsGeneratedContentFor returns PR_TRUE if aFrame is the outermost
+   * frame for generated content of type aPseudoElement for aContent.
+   * aFrame *might not* have the aPseudoElement pseudo-style! For example
+   * it might be a table outer frame and the inner table frame might
+   * have the pseudo-style.
    *
    * @param aContent the content node we're looking at.  If this is
    *        null, then we just assume that aFrame has the right content
    *        pointer.
    * @param aFrame the frame we're looking at
    * @param aPseudoElement the pseudo type we're interested in
    * @return whether aFrame is the generated aPseudoElement frame for aContent
    */
--- a/layout/base/nsQuoteList.cpp
+++ b/layout/base/nsQuoteList.cpp
@@ -42,17 +42,17 @@
 #include "nsReadableUtils.h"
 
 const nsString*
 nsQuoteNode::Text()
 {
   NS_ASSERTION(mType == eStyleContentType_OpenQuote ||
                mType == eStyleContentType_CloseQuote,
                "should only be called when mText should be non-null");
-  const nsStyleQuotes* styleQuotes = mPseudoFrame->GetStyleQuotes();
+  const nsStyleQuotes* styleQuotes = mStyleContext->GetStyleQuotes();
   PRInt32 quotesCount = styleQuotes->QuotesCount(); // 0 if 'quotes:none'
   PRInt32 quoteDepth = Depth();
 
   // Reuse the last pair when the depth is greater than the number of
   // pairs of quotes.  (Also make 'quotes: none' and close-quote from
   // a depth of 0 equivalent for the next test.)
   if (quoteDepth >= quotesCount)
     quoteDepth = quotesCount - 1;
--- a/layout/base/nsQuoteList.h
+++ b/layout/base/nsQuoteList.h
@@ -36,30 +36,33 @@
  * ***** END LICENSE BLOCK ***** */
 
 /* implementation of quotes for the CSS 'content' property */
 
 #ifndef nsQuoteList_h___
 #define nsQuoteList_h___
 
 #include "nsGenConList.h"
+#include "nsStyleContext.h"
 
 struct nsQuoteNode : public nsGenConNode {
   // open-quote, close-quote, no-open-quote, or no-close-quote
   const nsStyleContentType mType;
 
   // Quote depth before this quote, which is always non-negative.
   PRInt32 mDepthBefore;
 
+  nsRefPtr<nsStyleContext> mStyleContext;
 
-  nsQuoteNode(nsStyleContentType& aType, nsIFrame* aPseudoFrame,
-              PRUint32 aContentIndex)
-    : nsGenConNode(aPseudoFrame, aContentIndex)
+  nsQuoteNode(nsStyleContentType& aType, nsIContent* aContentParent,
+              PRUint32 aContentIndex, nsStyleContext* aStyleContext)
+    : nsGenConNode(aContentParent, aStyleContext, aContentIndex)
     , mType(aType)
     , mDepthBefore(0)
+    , mStyleContext(aStyleContext)
   {
     NS_ASSERTION(aType == eStyleContentType_OpenQuote ||
                  aType == eStyleContentType_CloseQuote ||
                  aType == eStyleContentType_NoOpenQuote ||
                  aType == eStyleContentType_NoCloseQuote,
                  "incorrect type");
     NS_ASSERTION(aContentIndex >= 0, "out of range");
   }
--- a/layout/generic/nsHTMLParts.h
+++ b/layout/generic/nsHTMLParts.h
@@ -84,17 +84,18 @@ class nsTableColFrame;
 
 // These are variations on AreaFrame with slightly different layout
 // policies.
 
 // Create a frame that supports "display: block" layout behavior
 nsIFrame*
 NS_NewBlockFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRUint32 aFlags = 0);
 
-// Special Generated Content Frame
+// Special Generated Content Node. It contains text taken from an
+// attribute of its *grandparent* content node. 
 nsresult
 NS_NewAttributeContent(nsNodeInfoManager *aNodeInfoManager,
                        PRInt32 aNameSpaceID, nsIAtom* aAttrName,
                        nsIContent** aResult);
 
 // Create a basic area frame but the GetFrameForPoint is overridden to always
 // return the option frame 
 // By default, area frames will extend
--- a/layout/generic/nsTextFrameThebes.cpp
+++ b/layout/generic/nsTextFrameThebes.cpp
@@ -2920,17 +2920,17 @@ nsTextPaintStyle::InitCommonColors()
                   NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor,
                                            selectionBGColor));
 
   mInitCommonColors = PR_TRUE;
 }
 
 static nsIFrame* GetNonGeneratedAncestor(nsIFrame* f) {
   while (f->GetStateBits() & NS_FRAME_GENERATED_CONTENT) {
-    f = f->GetParent();
+    f = nsLayoutUtils::GetParentOrPlaceholderFor(f->PresContext()->FrameManager(), f);
   }
   return f;
 }
 
 static nsIContent*
 FindElementAncestor(nsINode* aNode)
 {
   while (aNode && !aNode->IsNodeOfType(nsINode::eELEMENT)) {
--- a/layout/reftests/bugs/380842-1-ref.html
+++ b/layout/reftests/bugs/380842-1-ref.html
@@ -26,19 +26,19 @@
     Text
     <div>After</div>
   </div>
   After inline-block
 </div>
 <div>
   Before span
   <span>
-    <span>Before</span>
+    <div>Before</div>
     Text
-    <span>After</span>
+    <div>After</div>
   </span>
   After span
 </div>
 <div>
   <div>Before</div>
   Text
   <div>After</div>
 </div>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -495,17 +495,16 @@ random-if(MOZ_WIDGET_TOOLKIT=="gtk2") ==
 == 373381-1.html 373381-1-ref.html
 == 375508-1.html 375508-1-ref.html
 == 373433-1.html 373433-1-ref.html
 == 372062-1.html 372062-1-ref.html
 == 372768-1.html 372768-1-ref.html
 == 373383-1.html 373383-1-ref.html
 == 374038-1.xul 374038-1-ref.xul
 == 374038-2.xul 374038-2-ref.xul
-== 374193-1.xhtml about:blank
 fails == 374927-1.html 374927-1-ref.html # Was broken by patch for bug 368600; fails until bug 400776 is fixed
 == 375716-1.html 375716-1-ref.html
 == 375827-1.html 375827-1-ref.html
 == 376375-1.html 376375-1-ref.html
 == 376484-1.html 376484-1-ref.html
 == 376532-1.html 376532-1-ref.html
 != 376532-2.html 376532-2-ref.html
 != 376532-3.html 376532-3-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/display-types-01-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+div { border:1px solid green; margin:5px; }
+</style>
+</head>
+<body>
+<table width="100%"><tr><td valign="top">
+  <div><span style="display:block">1<img src="square-outline-32x32.png">"Before block</span
+>Inner<span style="display:block">2<img src="square-outline-32x32.png">After block"</span
+></div>
+  <div><span style="display:inline">1<img src="square-outline-32x32.png">"Before inline</span
+>Inner<span style="display:inline">2<img src="square-outline-32x32.png">After inline"</span
+></div>
+  <div><span style="display:inline-block">1<img src="square-outline-32x32.png">"Before inline-block</span
+>Inner<span style="display:inline-block">2<img src="square-outline-32x32.png">After inline-block"</span
+></div>
+  <div><span style="display:table">1<img src="square-outline-32x32.png">"Before table</span
+>Inner<span style="display:table">2<img src="square-outline-32x32.png">After table"</span
+></div>
+  <div><span style="display:inline-table">1<img src="square-outline-32x32.png">"Before inline-table</span
+>Inner<span style="display:inline-table">2<img src="square-outline-32x32.png">After inline-table"</span
+></div>
+  <div><span style="display:table-row-group">1<img src="square-outline-32x32.png">"Before table-row-group</span
+>Inner<span style="display:table-row-group">2<img src="square-outline-32x32.png">After table-row-group"</span
+></div>
+</td><td valign="top">
+  <div><span style="display:table-row">1<img src="square-outline-32x32.png">"Before table-row</span
+>Inner<span style="display:table-row">2<img src="square-outline-32x32.png">After table-row"</span
+></div>
+  <div><span style="display:table-cell">1<img src="square-outline-32x32.png">"Before table-cell</span
+>Inner<span style="display:table-cell">2<img src="square-outline-32x32.png">After table-cell"</span
+></div>
+  <div><span style="display:table-caption">1<img src="square-outline-32x32.png">"Before table-caption</span
+>Inner<span style="display:table-caption">2<img src="square-outline-32x32.png">After table-caption"</span
+></div>
+  <div><span style="display:-moz-box">1<img src="square-outline-32x32.png">"Before flexbox</span
+>Inner<span style="display:-moz-box">2<img src="square-outline-32x32.png">After flexbox"</span
+></div>
+  <div><span style="display:-moz-inline-box">1<img src="square-outline-32x32.png">"Before inline-flexbox</span
+>Inner<span style="display:-moz-inline-box">2<img src="square-outline-32x32.png">After inline-flexbox"</span
+></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/display-types-01.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+div { counter-reset:ctr; quotes:"\0022" "\0022" "\0022" "\0022"}
+
+div::before {
+  content:counter(ctr) url(square-outline-32x32.png) open-quote "Before " attr(class);
+  counter-increment:ctr;
+}
+div::after {
+  content:counter(ctr) url(square-outline-32x32.png) "After " attr(class) close-quote;
+  counter-increment:ctr;
+}
+
+.block::before, .block::after { display:block; }
+.inline::before, .inline::after { display:inline; }
+.inline-block::before, .inline-block::after { display:inline-block; }
+.table::before, .table::after { display:table; }
+.inline-table::before, .inline-table::after { display:inline-table; }
+.table-row-group::before, .table-row-group::after { display:table-row-group; }
+.table-row::before, .table-row::after { display:table-row; }
+.table-cell::before, .table-cell::after { display:table-cell; }
+.table-caption::before, .table-caption::after { display:table-caption; }
+.flexbox::before, .flexbox::after { display:-moz-box; }
+.inline-flexbox::before, .inline-flexbox::after { display:-moz-inline-box; }
+
+div { border:1px solid green; margin:5px; }
+</style>
+</head>
+
+<body>
+<table width="100%"><tr><td valign="top">
+  <div class="block">Inner</div>
+  <div class="inline">Inner</div>
+  <div class="inline-block">Inner</div>
+  <div class="table">Inner</div>
+  <div class="inline-table">Inner</div>
+  <div class="table-row-group">Inner</div>
+</td><td valign="top">
+  <div class="table-row">Inner</div>
+  <div class="table-cell">Inner</div>
+  <div class="table-caption">Inner</div>
+  <div class="flexbox">Inner</div>
+  <div class="inline-flexbox">Inner</div>
+</td></tr></table>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/dynamic-attr-01-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+before after
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/dynamic-attr-01.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<style>
+body::before {
+  content:attr(my-attr);
+}
+body::after {
+  content:attr(my-attr-2);
+}
+</style>
+<script>
+function fixupDOM() {
+  document.body.setAttribute("my-attr", "before");
+  document.body.setAttribute("my-attr-2", "after");
+  document.documentElement.className = "";
+}
+</script>
+</head>
+<body my-attr-2="xyz" onload="fixupDOM()">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/dynamic-restyle-01-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<html>
+<body style="border:2px solid red;">
+<span style="border:2px solid red;">Before</span>
+<div>After</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/dynamic-restyle-01.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<style>
+body::before {
+  content:"Before";
+  border:inherit;
+}
+.cl::after {
+  display:block;
+  content:"After";
+}
+</style>
+<script>
+function fixupDOM() {
+  document.body.setAttribute("style", "border:2px solid red;");
+  document.body.className = "cl";
+  document.documentElement.className = "";
+}
+</script>
+</head>
+<body onload="fixupDOM()">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/floated-01-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+div { border:1px solid green; margin:5px; }
+div { overflow:auto; }
+</style>
+</head>
+<body>
+<div><span style="float:left">1<img src="square-outline-32x32.png">"Before beforeleft afterleft</span
+>Inner<span style="float:left">2<img src="square-outline-32x32.png">After beforeleft afterleft"</span
+></div>
+<div><span style="float:left">1<img src="square-outline-32x32.png">"Before beforeleft afterright</span
+>Inner<span style="float:right">2<img src="square-outline-32x32.png">After beforeleft afterright"</span
+></div>
+<div><span style="float:right">1<img src="square-outline-32x32.png">"Before beforeright afterleft</span
+>Inner<span style="float:left">2<img src="square-outline-32x32.png">After beforeright afterleft"</span
+></div>
+<div><span style="float:right">1<img src="square-outline-32x32.png">"Before beforeright afterright</span
+>Inner<span style="float:right">2<img src="square-outline-32x32.png">After beforeright afterright"</span
+></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/floated-01.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+div { counter-reset:ctr; quotes:"\0022" "\0022" "\0022" "\0022"; }
+
+div::before {
+  content:counter(ctr) url(square-outline-32x32.png) open-quote "Before " attr(class);
+  counter-increment:ctr;
+}
+div::after {
+  content:counter(ctr) url(square-outline-32x32.png) "After " attr(class) close-quote;
+  counter-increment:ctr;
+}
+
+.beforeleft::before {
+  float:left;
+}
+.beforeright::before {
+  float:right;
+}
+.afterleft::after {
+  float:left;
+}
+.afterright::after {
+  float:right;
+}
+
+div { border:1px solid green; margin:5px; }
+div { overflow:auto; }
+</style>
+</head>
+<body>
+<div class="beforeleft afterleft">Inner</div>
+<div class="beforeleft afterright">Inner</div>
+<div class="beforeright afterleft">Inner</div>
+<div class="beforeright afterright">Inner</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/images-01-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+div { border:1px solid green; margin:5px; }
+</style>
+</head>
+<body>
+<div>Inner</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/images-01.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+div::before {
+  content:url(missing-image.png);
+}
+div { border:1px solid green; margin:5px; }
+</style>
+</head>
+<div>Inner</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/positioned-01-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+div { border:1px solid green; margin:5px; height:100px; }
+</style>
+</head>
+<div><span style="position:absolute; left:0">1<img src="square-outline-32x32.png">"Before gen abs</span
+>Inner<span style="position:absolute; right:0">2<img src="square-outline-32x32.png">After gen abs"</span
+></div>
+<div style="position:relative"><span style="position:absolute; left:0">1<img src="square-outline-32x32.png">"Before gen abs</span
+>Inner<span style="position:absolute; right:0">2<img src="square-outline-32x32.png">After gen abs"</span
+></div>
+<div><span style="position:relative; top:-10px;">1<img src="square-outline-32x32.png">"Before gen rel</span
+>Inner<span style="position:relative; top:10px;">2<img src="square-outline-32x32.png">After gen rel"</span
+></div>
+<div>Begin <span style="position:relative; top:-10px;">1<img src="square-outline-32x32.png">"Before gen rel</span
+>Inner<span style="position:relative; top:10px;">2<img src="square-outline-32x32.png">After gen rel"</span
+> End</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/positioned-01.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+div { counter-reset:ctr; quotes:"\0022" "\0022" "\0022" "\0022"; }
+
+.gen::before {
+  content:counter(ctr) url(square-outline-32x32.png) open-quote "Before " attr(class);
+  counter-increment:ctr;
+}
+.gen::after {
+  content:counter(ctr) url(square-outline-32x32.png) "After " attr(class) close-quote;
+  counter-increment:ctr;
+}
+
+.abs::before {
+  position:absolute;
+  left:0;
+}
+.abs::after {
+  position:absolute;
+  right:0;
+}
+
+.rel::before {
+  position:relative;
+  top:-10px;
+}
+.rel::after {
+  position:relative;
+  top:10px;
+}
+
+div { border:1px solid green; margin:5px; height:100px; }
+</style>
+</head>
+<div class="gen abs">Inner</div>
+<!-- an element should be the containing block for its positioned content -->
+<div style="position:relative;" class="gen abs">Inner</div>
+<div class="gen rel">Inner</div>
+<div>Begin <span class="gen rel">Inner</span> End</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/reftest.list
@@ -0,0 +1,8 @@
+== display-types-01.html display-types-01-ref.html
+== dynamic-attr-01.html dynamic-attr-01-ref.html
+== dynamic-restyle-01.html dynamic-restyle-01-ref.html
+== floated-01.html floated-01-ref.html
+== images-01.html images-01-ref.html
+== positioned-01.html positioned-01-ref.html
+== table-ignoring-whitespace-01.html table-ignoring-whitespace-01-ref.html
+== table-parts-01.html table-parts-01-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/table-ignoring-whitespace-01-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+div { border:1px solid green; margin:5px; }
+</style>
+</head>
+
+<body>
+<div>
+  <table><tbody><tr><td>Cell0</td><td></td></tr>
+                <tr><td>Cell1</td><td>Cell2</td></tr></tbody></table>
+</div>
+<div>
+  <table><tbody><tr><td></td><td>Cell0</td></tr>
+                <tr><td>Cell1</td><td>Cell2</td></tr></tbody></table>
+</div>
+<div>
+  <table><tbody><tr><td></td><td>Cell0</td></tr>
+                <tr><td>Cell1</td><td>Cell2</td></tr></tbody></table>
+</div>
+<div>
+  <table><tbody><tr><td></td><td>Cell0</td></tr>
+                <tr><td>Cell1</td><td>Cell2</td></tr></tbody></table>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/table-ignoring-whitespace-01.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+.gen0::before {
+  padding:1px;
+}
+.gen1::before {
+  content: " ";
+}
+.gen2::before {
+  content: attr(missing);
+}
+.gen3::before {
+  content: url(missing-image.png);
+}
+
+div { border:1px solid green; margin:5px; }
+</style>
+</head>
+
+<!-- This tests that generated content items that evaluate to empty strings or
+     broken images are *not* treated as whitespace text and ignored by the table.
+     Altogether missing content should be ignored, though. (In fact it won't even be generated.) -->
+
+<body>
+<div>
+  <table><tbody><tr class="gen0"><td>Cell0</td></tr>
+                <tr><td>Cell1</td><td>Cell2</td></tr></tbody></table>
+</div>
+<div>
+  <table><tbody><tr class="gen1"><td>Cell0</td></tr>
+                <tr><td>Cell1</td><td>Cell2</td></tr></tbody></table>
+</div>
+<div>
+  <table><tbody><tr class="gen2"><td>Cell0</td></tr>
+                <tr><td>Cell1</td><td>Cell2</td></tr></tbody></table>
+</div>
+<div>
+  <table><tbody><tr class="gen3"><td>Cell0</td></tr>
+                <tr><td>Cell1</td><td>Cell2</td></tr></tbody></table>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/table-parts-01-ref.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+table, div.gen { counter-reset:ctr; quotes:"\0022" "\0022" "\0022" "\0022"; }
+
+.gen::before {
+  content:counter(ctr) url(square-outline-32x32.png) open-quote "Before " attr(class);
+  counter-increment:ctr;
+}
+.gen::after {
+  content:counter(ctr) url(square-outline-32x32.png) "After " attr(class) close-quote;
+  counter-increment:ctr;
+}
+
+table { border:1px solid blue; }
+td.real { border:1px solid cyan; }
+td { border-spacing:0; padding:0; }
+
+tr.gen::before, tr.gen::after { display:table-cell; }
+tbody.gen::before, tbody.gen::after { display:table-row; }
+table.gen::before, table.gen::after { display:table-row-group; }
+table.col::before, table.gen.col::after { display:table-column-group; }
+/* note reordering here! */
+table.headfoot::after { display:table-header-group; }
+table.headfoot::before { display:table-footer-group; }
+
+.row { display:table-row; }
+.rowgroup { display:table-row-group; }
+div.gencell::before, div.gencell::after { display:table-cell; }
+div.genrow::before, div.genrow::after { display:table-row; }
+div.genblock::before, div.genblock::after { display:block; }
+div.geninline::before, div.geninline::after { display:inline; }
+
+div { border:1px solid green; margin:5px; }
+div.cell { border:none; display:table-cell; }
+div.real { display:table-cell; }
+</style>
+</head>
+
+<body>
+<table style="border:none" width="100%"><tr><td style="border:none" valign="top">
+  <div><table><tbody><tr><td>1<img src="square-outline-32x32.png">"Before gen</td
+><td class="real">Inner</td><td>2<img src="square-outline-32x32.png">After gen"</td
+></tr></tbody></table></div>
+  <div><table><tbody><tr><td>1<img src="square-outline-32x32.png">"Before gen</td
+></tr><tr><td class="real">Inner</td></tr><tr><td>2<img src="square-outline-32x32.png">After gen"</td
+></tr></tbody></table></div>
+  <div><table><tbody><tr><td>1<img src="square-outline-32x32.png">"Before gen</td
+></tr><tr><td>2<img src="square-outline-32x32.png">After gen"</td
+></tr><tr><td class="real">Inner</td></tr></tbody></table></div>
+  <div><table><tbody><tr><td class="real">Inner</td></tr></tbody></table></div>
+  <div><table><tbody><tr><td>2<img src="square-outline-32x32.png">After gen headfoot"</td
+></tr><tr><td class="real">Inner</td></tr><tr><td>1<img src="square-outline-32x32.png">"Before gen headfoot</td
+></tr></tbody></table></div>
+</td><td style="border:none" valign="top">
+  <div><div class="row"><div class="cell">1<img src="square-outline-32x32.png">"Before gen row gencell</div
+><div class="real">Inner</div><div class="cell">2<img src="square-outline-32x32.png">After gen row gencell"</div
+></div></div>
+  <div><div class="row"><div class="cell">1<img src="square-outline-32x32.png">"Before gen row genblock</div
+><div class="real">Inner</div><div class="cell">2<img src="square-outline-32x32.png">After gen row genblock"</div
+></div></div>
+  <div><div class="row"><div class="cell">1<img src="square-outline-32x32.png">"Before gen row geninline</div
+><div class="real">Inner</div><div class="cell">2<img src="square-outline-32x32.png">After gen row geninline"</div
+></div></div>
+  <div><div class="rowgroup"><div class="row">1<img src="square-outline-32x32.png">"Before gen rowgroup genrow</div
+><div class="row"><div class="real">Inner</div></div><div class="row">2<img src="square-outline-32x32.png">After gen rowgroup genrow"</div
+></div></div>
+  <div><div class="rowgroup"><div class="row">1<img src="square-outline-32x32.png">"Before gen rowgroup gencell</div
+><div class="row"><div class="real">Inner</div></div><div class="row">2<img src="square-outline-32x32.png">After gen rowgroup gencell"</div
+></div></div>
+  <div><div class="rowgroup"><div class="row">1<img src="square-outline-32x32.png">"Before gen rowgroup genblock</div
+><div class="row"><div class="real">Inner</div></div><div class="row">2<img src="square-outline-32x32.png">After gen rowgroup genblock"</div
+></div></div>
+  <div><div class="rowgroup"><div class="row">1<img src="square-outline-32x32.png">"Before gen rowgroup geninline</div
+><div class="row"><div class="real">Inner</div></div><div class="row">2<img src="square-outline-32x32.png">After gen rowgroup geninline"</div
+></div></div>
+</tr></td></table>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/table-parts-01.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+table, div.gen { counter-reset:ctr; quotes:"\0022" "\0022" "\0022" "\0022"; }
+
+.gen::before {
+  content:counter(ctr) url(square-outline-32x32.png) open-quote "Before " attr(class);
+  counter-increment:ctr;
+}
+.gen::after {
+  content:counter(ctr) url(square-outline-32x32.png) "After " attr(class) close-quote;
+  counter-increment:ctr;
+}
+
+table { border:1px solid blue; }
+td { border:1px solid cyan; }
+td { border-spacing:0; padding:0; }
+
+tr.gen::before, tr.gen::after { display:table-cell; }
+tbody.gen::before, tbody.gen::after { display:table-row; }
+table.gen::before, table.gen::after { display:table-row-group; }
+table.col::before, table.gen.col::after { display:table-column-group; }
+/* note reordering here! */
+table.headfoot::after { display:table-header-group; }
+table.headfoot::before { display:table-footer-group; }
+
+.cell { display:table-cell; }
+.row { display:table-row; }
+.rowgroup { display:table-row-group; }
+div.gencell::before, div.gencell::after { display:table-cell; }
+div.genrow::before, div.genrow::after { display:table-row; }
+div.genblock::before, div.genblock::after { display:block; }
+div.geninline::before, div.geninline::after { display:inline; }
+
+div { border:1px solid green; margin:5px; }
+</style>
+</head>
+
+<body>
+<table style="border:none" width="100%"><tr><td style="border:none" valign="top">
+  <div><table><tbody><tr class="gen"><td>Inner</td></tr></tbody></table></div>
+  <div><table><tbody class="gen"><tr><td>Inner</td></tr></tbody></table></div>
+  <div><table class="gen"><tfoot><tr><td>Inner</td></tr></tfoot></table></div>
+  <div><table class="gen col"><tbody><tr><td>Inner</td></tr></tbody></table></div>
+  <div><table class="gen headfoot"><tbody><tr><td>Inner</td></tr></tbody></table></div>
+</td><td style="border:none" valign="top">
+  <div><div class="gen row gencell"><div class="cell">Inner</div></div></div>
+  <div><div class="gen row genblock"><div class="cell">Inner</div></div></div>
+  <div><div class="gen row geninline"><div class="cell">Inner</div></div></div>
+  <div><div class="gen rowgroup genrow"><div class="row"><div class="cell">Inner</div></div></div></div>
+  <div><div class="gen rowgroup gencell"><div class="row"><div class="cell">Inner</div></div></div></div>
+  <div><div class="gen rowgroup genblock"><div class="row"><div class="cell">Inner</div></div></div></div>
+  <div><div class="gen rowgroup geninline"><div class="row"><div class="cell">Inner</div></div></div></div>
+</tr></td></table>
+</body>
+</html>
--- a/layout/reftests/reftest.list
+++ b/layout/reftests/reftest.list
@@ -30,16 +30,19 @@ include bugs/reftest.list
 include canvas/reftest.list
 
 # columns/
 include columns/reftest.list
 
 # counters/
 include counters/reftest.list
 
+# generated-content/
+include generated-content/reftest.list
+
 # first-letter/
 include first-letter/reftest.list
 
 # first-line/
 include first-line/reftest.list
 
 # forms
 include forms/reftest.list
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -3302,60 +3302,17 @@ nsRuleNode::ComputeDisplayData(void* aSt
     if (fullAuto) {
       display->mClipFlags |= NS_STYLE_CLIP_AUTO;
     }
     else {
       display->mClipFlags |= NS_STYLE_CLIP_RECT;
     }
   }
 
-  // CSS2 specified fixups:
-  if (generatedContent) {
-    // According to CSS2 section 12.1, :before and :after
-    // pseudo-elements must not be positioned or floated (CSS2 12.1) and
-    // must be limited to certain display types (depending on the
-    // display type of the element to which they are attached).
-    // XXX These restrictions are no longer present in CSS2.1.  We
-    // should ensure that we support removing them before doing so,
-    // though.
-    // XXXbz For example, the calls to WipeContainingBlock in the
-    // frame constructor will need to be changedif we allow
-    // block-level generated content inside inlines.
-
-    if (display->mPosition != NS_STYLE_POSITION_STATIC)
-      display->mPosition = NS_STYLE_POSITION_STATIC;
-    if (display->mFloats != NS_STYLE_FLOAT_NONE)
-      display->mFloats = NS_STYLE_FLOAT_NONE;
-
-    PRUint8 displayValue = display->mDisplay;
-    if (displayValue != NS_STYLE_DISPLAY_NONE &&
-        displayValue != NS_STYLE_DISPLAY_INLINE &&
-        displayValue != NS_STYLE_DISPLAY_INLINE_BLOCK) {
-      inherited = PR_TRUE;
-      if (parentDisplay->IsBlockOutside() ||
-          parentDisplay->mDisplay == NS_STYLE_DISPLAY_INLINE_BLOCK ||
-          parentDisplay->mDisplay == NS_STYLE_DISPLAY_TABLE_CELL ||
-          parentDisplay->mDisplay == NS_STYLE_DISPLAY_TABLE_CAPTION) {
-        // If the subject of the selector is a block-level element,
-        // allowed values are 'none', 'inline', 'block', and 'marker'.
-        // If the value of the 'display' has any other value, the
-        // pseudo-element will behave as if the value were 'block'.
-        if (displayValue != NS_STYLE_DISPLAY_BLOCK &&
-            displayValue != NS_STYLE_DISPLAY_MARKER)
-          display->mDisplay = NS_STYLE_DISPLAY_BLOCK;
-      } else {
-        // If the subject of the selector is an inline-level element,
-        // allowed values are 'none' and 'inline'. If the value of the
-        // 'display' has any other value, the pseudo-element will behave
-        // as if the value were 'inline'.
-        display->mDisplay = NS_STYLE_DISPLAY_INLINE;
-      }
-    }
-  }
-  else if (display->mDisplay != NS_STYLE_DISPLAY_NONE) {
+  if (display->mDisplay != NS_STYLE_DISPLAY_NONE) {
     // CSS2 9.7 specifies display type corrections dealing with 'float'
     // and 'position'.  Since generated content can't be floated or
     // positioned, we can deal with it here.
 
     if (nsCSSPseudoElements::firstLetter == pseudoTag) {
       // a non-floating first-letter must be inline
       // XXX this fix can go away once bug 103189 is fixed correctly
       display->mDisplay = NS_STYLE_DISPLAY_INLINE;