Bug 605466 - Implement new spec-based limits for formatting element proliferation in the HTML5 parsing algorithm. rs=jonas, a=blocking2.0-betaN.
authorHenri Sivonen <hsivonen@iki.fi>
Fri, 15 Oct 2010 12:23:42 +0300
changeset 57327 308dfe76b260df92dee89c968ec9db6dc26595b4
parent 57326 5e33141526517f2a7b3a18d0465d0828305099f6
child 57328 108a3a3878c1f3322c97afdd06d394462c1a5bf6
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonas, blocking2
bugs605466
milestone2.0b8pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 605466 - Implement new spec-based limits for formatting element proliferation in the HTML5 parsing algorithm. rs=jonas, a=blocking2.0-betaN.
parser/html/javasrc/HtmlAttributes.java
parser/html/javasrc/Portability.java
parser/html/javasrc/TreeBuilder.java
parser/html/nsHtml5HtmlAttributes.cpp
parser/html/nsHtml5HtmlAttributes.h
parser/html/nsHtml5Portability.cpp
parser/html/nsHtml5Portability.h
parser/html/nsHtml5TreeBuilder.cpp
parser/html/nsHtml5TreeBuilder.h
parser/htmlparser/tests/mochitest/html5_tree_construction_exceptions.js
parser/htmlparser/tests/mochitest/html5lib_tree_construction/tests1.dat
parser/htmlparser/tests/mochitest/html5lib_tree_construction/tests10.dat
--- a/parser/html/javasrc/HtmlAttributes.java
+++ b/parser/html/javasrc/HtmlAttributes.java
@@ -459,16 +459,43 @@ public final class HtmlAttributes implem
         for (int i = 0; i < xmlnsLength; i++) {
             clone.addAttribute(xmlnsNames[i],
                     xmlnsValues[i], XmlViolationPolicy.ALLOW);
         }
         // ]NOCPP]
         return clone; // XXX!!!
     }
     
+    public boolean equalsAnother(HtmlAttributes other) {
+        assert mode == 0 || mode == 3 : "Trying to compare attributes in foreign content.";
+        int otherLength = other.getLength();
+        if (length != otherLength) {
+            return false;
+        }
+        for (int i = 0; i < length; i++) {
+            // Work around the limitations of C++
+            boolean found = false;
+            // The comparing just the local names is OK, since these attribute
+            // holders are both supposed to belong to HTML formatting elements
+            @Local String ownLocal = names[i].getLocal(AttributeName.HTML);
+            for (int j = 0; j < otherLength; j++) {
+                if (ownLocal == other.names[j].getLocal(AttributeName.HTML)) {
+                    found = true;
+                    if (!Portability.stringEqualsString(values[i], other.values[j])) {
+                        return false;
+                    }
+                }
+            }
+            if (!found) {
+                return false;
+            }
+        }
+        return true;
+    }
+    
     // [NOCPP[
     
     void processNonNcNames(TreeBuilder<?> treeBuilder, XmlViolationPolicy namePolicy) throws SAXException {
         for (int i = 0; i < length; i++) {
             AttributeName attName = names[i];
             if (!attName.isNcName(mode)) {
                 String name = attName.getLocal(mode);
                 switch (namePolicy) {
--- a/parser/html/javasrc/Portability.java
+++ b/parser/html/javasrc/Portability.java
@@ -154,16 +154,20 @@ public final class Portability {
         }
         return true;
     }
     
     public static boolean literalEqualsString(@Literal String literal, String string) {
         return literal.equals(string);
     }
 
+    public static boolean stringEqualsString(String one, String other) {
+        return one.equals(other);
+    }
+    
     public static void delete(Object o) {
         
     }
 
     public static void deleteArray(Object o) {
         
     }
 }
--- a/parser/html/javasrc/TreeBuilder.java
+++ b/parser/html/javasrc/TreeBuilder.java
@@ -332,18 +332,16 @@ public abstract class TreeBuilder<T> imp
             "-//w3c//dtd html 4.0 transitional//",
             "-//w3c//dtd html experimental 19960712//",
             "-//w3c//dtd html experimental 970421//", "-//w3c//dtd w3 html//",
             "-//w3o//dtd w3 html 3.0//", "-//webtechs//dtd mozilla html 2.0//",
             "-//webtechs//dtd mozilla html//" };
 
     private static final int NOT_FOUND_ON_STACK = Integer.MAX_VALUE;
 
-    private static final int AAA_MAX_ITERATIONS = 10;
-
     // [NOCPP[
 
     private static final @Local String HTML_LOCAL = "html";
 
     // ]NOCPP]
 
     private int mode = INITIAL;
 
@@ -1941,16 +1939,17 @@ public abstract class TreeBuilder<T> imp
                                 appendToCurrentNodeAndPushFormattingElementMayFoster(
                                         "http://www.w3.org/1999/xhtml",
                                         elementName, attributes);
                                 attributes = null; // CPP
                                 break starttagloop;
                             case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U:
                             case FONT:
                                 reconstructTheActiveFormattingElements();
+                                maybeForgetEarlierDuplicateFormattingElement(elementName.name, attributes);
                                 appendToCurrentNodeAndPushFormattingElementMayFoster(
                                         "http://www.w3.org/1999/xhtml",
                                         elementName, attributes);
                                 attributes = null; // CPP
                                 break starttagloop;
                             case NOBR:
                                 reconstructTheActiveFormattingElements();
                                 if (TreeBuilder.NOT_FOUND_ON_STACK != findLastInScope("nobr")) {
@@ -3524,22 +3523,16 @@ public abstract class TreeBuilder<T> imp
                                             + name
                                             + "\u201D seen but there were unclosed elements.");
                                 }
                                 while (currentPtr >= eltPos) {
                                     pop();
                                 }
                             }
                             break endtagloop;
-                        case A:
-                        case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U:
-                        case FONT:
-                        case NOBR:
-                            adoptionAgencyEndTag(name);
-                            break endtagloop;
                         case OBJECT:
                         case MARQUEE_OR_APPLET:
                             eltPos = findLastInScope(name);
                             if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
                                 err("Stray end tag \u201C" + name + "\u201D.");
                             } else {
                                 generateImpliedEndTags();
                                 if (errorHandler != null && !isCurrent(name)) {
@@ -3588,16 +3581,24 @@ public abstract class TreeBuilder<T> imp
                             break endtagloop;
                         case NOSCRIPT:
                             if (scriptingEnabled) {
                                 err("Stray end tag \u201Cnoscript\u201D.");
                                 break endtagloop;
                             } else {
                                 // fall through
                             }
+                        case A:
+                        case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U:
+                        case FONT:
+                        case NOBR:
+                            if (adoptionAgencyEndTag(name)) {
+                                break endtagloop;
+                            }
+                            // else handle like any other tag
                         default:
                             if (isCurrent(name)) {
                                 pop();
                                 break endtagloop;
                             }
 
                             eltPos = currentPtr;
                             for (;;) {
@@ -4321,35 +4322,34 @@ public abstract class TreeBuilder<T> imp
         }
         assert pos < listPtr;
         System.arraycopy(listOfActiveFormattingElements, pos + 1,
                 listOfActiveFormattingElements, pos, listPtr - pos);
         assert clearLastListSlot();
         listPtr--;
     }
 
-    private void adoptionAgencyEndTag(@Local String name) throws SAXException {
+    private boolean adoptionAgencyEndTag(@Local String name) throws SAXException {
         // If you crash around here, perhaps some stack node variable claimed to
         // be a weak ref isn't.
-        for (int i = 0; i < AAA_MAX_ITERATIONS; ++i) {
+        for (int i = 0; i < 8; ++i) {
             int formattingEltListPos = listPtr;
             while (formattingEltListPos > -1) {
                 StackNode<T> listNode = listOfActiveFormattingElements[formattingEltListPos]; // weak
                                                                                               // ref
                 if (listNode == null) {
                     formattingEltListPos = -1;
                     break;
                 } else if (listNode.name == name) {
                     break;
                 }
                 formattingEltListPos--;
             }
             if (formattingEltListPos == -1) {
-                err("No element \u201C" + name + "\u201D to close.");
-                return;
+                return false;
             }
             StackNode<T> formattingElt = listOfActiveFormattingElements[formattingEltListPos]; // this
             // *looks*
             // like
             // a
             // weak
             // ref
             // to
@@ -4367,21 +4367,21 @@ public abstract class TreeBuilder<T> imp
                 } else if (node.scoping) {
                     inScope = false;
                 }
                 formattingEltStackPos--;
             }
             if (formattingEltStackPos == -1) {
                 err("No element \u201C" + name + "\u201D to close.");
                 removeFromListOfActiveFormattingElements(formattingEltListPos);
-                return;
+                return true;
             }
             if (!inScope) {
                 err("No element \u201C" + name + "\u201D to close.");
-                return;
+                return true;
             }
             // stackPos now points to the formatting element and it is in scope
             if (errorHandler != null && formattingEltStackPos != currentPtr) {
                 errNoCheck("End tag \u201C" + name + "\u201D violates nesting rules.");
             }
             int furthestBlockPos = formattingEltStackPos + 1;
             while (furthestBlockPos <= currentPtr) {
                 StackNode<T> node = stack[furthestBlockPos]; // weak ref
@@ -4391,26 +4391,26 @@ public abstract class TreeBuilder<T> imp
                 furthestBlockPos++;
             }
             if (furthestBlockPos > currentPtr) {
                 // no furthest block
                 while (currentPtr >= formattingEltStackPos) {
                     pop();
                 }
                 removeFromListOfActiveFormattingElements(formattingEltListPos);
-                return;
+                return true;
             }
             StackNode<T> commonAncestor = stack[formattingEltStackPos - 1]; // weak
             // ref
             StackNode<T> furthestBlock = stack[furthestBlockPos]; // weak ref
             // detachFromParent(furthestBlock.node); XXX AAA CHANGE
             int bookmark = formattingEltListPos;
             int nodePos = furthestBlockPos;
             StackNode<T> lastNode = furthestBlock; // weak ref
-            for (int j = 0; j < AAA_MAX_ITERATIONS; ++j) {
+            for (int j = 0; j < 3; ++j) {
                 nodePos--;
                 StackNode<T> node = stack[nodePos]; // weak ref
                 int nodeListPos = findInListOfActiveFormattingElements(node);
                 if (nodeListPos == -1) {
                     assert formattingEltStackPos < nodePos;
                     assert bookmark < nodePos;
                     assert furthestBlockPos > nodePos;
                     removeFromStack(nodePos); // node is now a bad pointer in
@@ -4478,16 +4478,17 @@ public abstract class TreeBuilder<T> imp
             insertIntoListOfActiveFormattingElements(formattingClone, bookmark);
             assert formattingEltStackPos < furthestBlockPos;
             removeFromStack(formattingEltStackPos);
             // furthestBlockPos is now off by one and points to the slot after
             // it
             insertIntoStack(formattingClone, furthestBlockPos);
             Portability.releaseElement(clone);
         }
+        return true;
     }
 
     private void insertIntoStack(StackNode<T> node, int position)
             throws SAXException {
         assert currentPtr + 1 < stack.length;
         assert position <= currentPtr + 1;
         if (position == currentPtr + 1) {
             push(node);
@@ -4529,16 +4530,36 @@ public abstract class TreeBuilder<T> imp
                 return -1;
             } else if (node.name == name) {
                 return i;
             }
         }
         return -1;
     }
 
+
+    private void maybeForgetEarlierDuplicateFormattingElement(
+            @Local String name, HtmlAttributes attributes) throws SAXException {
+        int candidate = -1;
+        int count = 0;
+        for (int i = listPtr; i >= 0; i--) {
+            StackNode<T> node = listOfActiveFormattingElements[i];
+            if (node == null) {
+                break;
+            }
+            if (node.name == name && node.attributes.equalsAnother(attributes)) {
+                candidate = i;
+                ++count;
+            }
+        }
+        if (count >= 3) {
+            removeFromListOfActiveFormattingElements(candidate);
+        }
+    }
+    
     private int findLastOrRoot(@Local String name) {
         for (int i = currentPtr; i > 0; i--) {
             if (stack[i].name == name) {
                 return i;
             }
         }
         return 0;
     }
--- a/parser/html/nsHtml5HtmlAttributes.cpp
+++ b/parser/html/nsHtml5HtmlAttributes.cpp
@@ -230,16 +230,42 @@ nsHtml5HtmlAttributes::cloneAttributes(n
 
   nsHtml5HtmlAttributes* clone = new nsHtml5HtmlAttributes(0);
   for (PRInt32 i = 0; i < length; i++) {
     clone->addAttribute(names[i]->cloneAttributeName(interner), nsHtml5Portability::newStringFromString(values[i]));
   }
   return clone;
 }
 
+PRBool 
+nsHtml5HtmlAttributes::equalsAnother(nsHtml5HtmlAttributes* other)
+{
+
+  PRInt32 otherLength = other->getLength();
+  if (length != otherLength) {
+    return PR_FALSE;
+  }
+  for (PRInt32 i = 0; i < length; i++) {
+    PRBool found = PR_FALSE;
+    nsIAtom* ownLocal = names[i]->getLocal(NS_HTML5ATTRIBUTE_NAME_HTML);
+    for (PRInt32 j = 0; j < otherLength; j++) {
+      if (ownLocal == other->names[j]->getLocal(NS_HTML5ATTRIBUTE_NAME_HTML)) {
+        found = PR_TRUE;
+        if (!nsHtml5Portability::stringEqualsString(values[i], other->values[j])) {
+          return PR_FALSE;
+        }
+      }
+    }
+    if (!found) {
+      return PR_FALSE;
+    }
+  }
+  return PR_TRUE;
+}
+
 void
 nsHtml5HtmlAttributes::initializeStatics()
 {
   EMPTY_ATTRIBUTES = new nsHtml5HtmlAttributes(NS_HTML5ATTRIBUTE_NAME_HTML);
 }
 
 void
 nsHtml5HtmlAttributes::releaseStatics()
--- a/parser/html/nsHtml5HtmlAttributes.h
+++ b/parser/html/nsHtml5HtmlAttributes.h
@@ -83,16 +83,17 @@ class nsHtml5HtmlAttributes
     void addAttribute(nsHtml5AttributeName* name, nsString* value);
     void clear(PRInt32 m);
     void releaseValue(PRInt32 i);
     void clearWithoutReleasingContents();
     PRBool contains(nsHtml5AttributeName* name);
     void adjustForMath();
     void adjustForSvg();
     nsHtml5HtmlAttributes* cloneAttributes(nsHtml5AtomTable* interner);
+    PRBool equalsAnother(nsHtml5HtmlAttributes* other);
     static void initializeStatics();
     static void releaseStatics();
 };
 
 
 
 #endif
 
--- a/parser/html/nsHtml5Portability.cpp
+++ b/parser/html/nsHtml5Portability.cpp
@@ -174,16 +174,22 @@ PRBool
 nsHtml5Portability::literalEqualsString(const char* literal, nsString* string)
 {
   if (!string) {
     return PR_FALSE;
   }
   return string->EqualsASCII(literal);
 }
 
+PRBool
+nsHtml5Portability::stringEqualsString(nsString* one, nsString* other)
+{
+  return one->Equals(*other);
+}
+
 void
 nsHtml5Portability::initializeStatics()
 {
 }
 
 void
 nsHtml5Portability::releaseStatics()
 {
--- a/parser/html/nsHtml5Portability.h
+++ b/parser/html/nsHtml5Portability.h
@@ -72,16 +72,17 @@ class nsHtml5Portability
     static nsIAtom* newLocalFromLocal(nsIAtom* local, nsHtml5AtomTable* interner);
     static void releaseString(nsString* str);
     static void retainLocal(nsIAtom* local);
     static void releaseLocal(nsIAtom* local);
     static PRBool localEqualsBuffer(nsIAtom* local, PRUnichar* buf, PRInt32 offset, PRInt32 length);
     static PRBool lowerCaseLiteralIsPrefixOfIgnoreAsciiCaseString(const char* lowerCaseLiteral, nsString* string);
     static PRBool lowerCaseLiteralEqualsIgnoreAsciiCaseString(const char* lowerCaseLiteral, nsString* string);
     static PRBool literalEqualsString(const char* literal, nsString* string);
+    static PRBool stringEqualsString(nsString* one, nsString* other);
     static void initializeStatics();
     static void releaseStatics();
 };
 
 
 
 #endif
 
--- a/parser/html/nsHtml5TreeBuilder.cpp
+++ b/parser/html/nsHtml5TreeBuilder.cpp
@@ -1021,16 +1021,17 @@ nsHtml5TreeBuilder::startTag(nsHtml5Elem
               reconstructTheActiveFormattingElements();
               appendToCurrentNodeAndPushFormattingElementMayFoster(kNameSpaceID_XHTML, elementName, attributes);
               attributes = nsnull;
               NS_HTML5_BREAK(starttagloop);
             }
             case NS_HTML5TREE_BUILDER_B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U:
             case NS_HTML5TREE_BUILDER_FONT: {
               reconstructTheActiveFormattingElements();
+              maybeForgetEarlierDuplicateFormattingElement(elementName->name, attributes);
               appendToCurrentNodeAndPushFormattingElementMayFoster(kNameSpaceID_XHTML, elementName, attributes);
               attributes = nsnull;
               NS_HTML5_BREAK(starttagloop);
             }
             case NS_HTML5TREE_BUILDER_NOBR: {
               reconstructTheActiveFormattingElements();
               if (NS_HTML5TREE_BUILDER_NOT_FOUND_ON_STACK != findLastInScope(nsHtml5Atoms::nobr)) {
 
@@ -2382,23 +2383,16 @@ nsHtml5TreeBuilder::endTag(nsHtml5Elemen
               generateImpliedEndTags();
 
               while (currentPtr >= eltPos) {
                 pop();
               }
             }
             NS_HTML5_BREAK(endtagloop);
           }
-          case NS_HTML5TREE_BUILDER_A:
-          case NS_HTML5TREE_BUILDER_B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U:
-          case NS_HTML5TREE_BUILDER_FONT:
-          case NS_HTML5TREE_BUILDER_NOBR: {
-            adoptionAgencyEndTag(name);
-            NS_HTML5_BREAK(endtagloop);
-          }
           case NS_HTML5TREE_BUILDER_OBJECT:
           case NS_HTML5TREE_BUILDER_MARQUEE_OR_APPLET: {
             eltPos = findLastInScope(name);
             if (eltPos != NS_HTML5TREE_BUILDER_NOT_FOUND_ON_STACK) {
               generateImpliedEndTags();
 
               while (currentPtr >= eltPos) {
                 pop();
@@ -2439,16 +2433,24 @@ nsHtml5TreeBuilder::endTag(nsHtml5Elemen
           }
           case NS_HTML5TREE_BUILDER_NOSCRIPT: {
             if (scriptingEnabled) {
 
               NS_HTML5_BREAK(endtagloop);
             } else {
             }
           }
+          case NS_HTML5TREE_BUILDER_A:
+          case NS_HTML5TREE_BUILDER_B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U:
+          case NS_HTML5TREE_BUILDER_FONT:
+          case NS_HTML5TREE_BUILDER_NOBR: {
+            if (adoptionAgencyEndTag(name)) {
+              NS_HTML5_BREAK(endtagloop);
+            }
+          }
           default: {
             if (isCurrent(name)) {
               pop();
               NS_HTML5_BREAK(endtagloop);
             }
             eltPos = currentPtr;
             for (; ; ) {
               nsHtml5StackNode* node = stack[eltPos];
@@ -3167,78 +3169,77 @@ nsHtml5TreeBuilder::removeFromListOfActi
     return;
   }
 
   nsHtml5ArrayCopy::arraycopy(listOfActiveFormattingElements, pos + 1, pos, listPtr - pos);
 
   listPtr--;
 }
 
-void 
+PRBool 
 nsHtml5TreeBuilder::adoptionAgencyEndTag(nsIAtom* name)
 {
-  for (PRInt32 i = 0; i < NS_HTML5TREE_BUILDER_AAA_MAX_ITERATIONS; ++i) {
+  for (PRInt32 i = 0; i < 8; ++i) {
     PRInt32 formattingEltListPos = listPtr;
     while (formattingEltListPos > -1) {
       nsHtml5StackNode* listNode = listOfActiveFormattingElements[formattingEltListPos];
       if (!listNode) {
         formattingEltListPos = -1;
         break;
       } else if (listNode->name == name) {
         break;
       }
       formattingEltListPos--;
     }
     if (formattingEltListPos == -1) {
-
-      return;
+      return PR_FALSE;
     }
     nsHtml5StackNode* formattingElt = listOfActiveFormattingElements[formattingEltListPos];
     PRInt32 formattingEltStackPos = currentPtr;
     PRBool inScope = PR_TRUE;
     while (formattingEltStackPos > -1) {
       nsHtml5StackNode* node = stack[formattingEltStackPos];
       if (node == formattingElt) {
         break;
       } else if (node->scoping) {
         inScope = PR_FALSE;
       }
       formattingEltStackPos--;
     }
     if (formattingEltStackPos == -1) {
 
       removeFromListOfActiveFormattingElements(formattingEltListPos);
-      return;
+      return PR_TRUE;
     }
     if (!inScope) {
 
-      return;
+      return PR_TRUE;
     }
 
     PRInt32 furthestBlockPos = formattingEltStackPos + 1;
     while (furthestBlockPos <= currentPtr) {
       nsHtml5StackNode* node = stack[furthestBlockPos];
       if (node->scoping || node->special) {
         break;
       }
       furthestBlockPos++;
     }
     if (furthestBlockPos > currentPtr) {
       while (currentPtr >= formattingEltStackPos) {
         pop();
       }
       removeFromListOfActiveFormattingElements(formattingEltListPos);
-      return;
+      return PR_TRUE;
     }
     nsHtml5StackNode* commonAncestor = stack[formattingEltStackPos - 1];
     nsHtml5StackNode* furthestBlock = stack[furthestBlockPos];
     PRInt32 bookmark = formattingEltListPos;
     PRInt32 nodePos = furthestBlockPos;
     nsHtml5StackNode* lastNode = furthestBlock;
-    for (PRInt32 j = 0; j < NS_HTML5TREE_BUILDER_AAA_MAX_ITERATIONS; ++j) {
+    for (PRInt32 j = 0; j < 3; ++j) {
       nodePos--;
       nsHtml5StackNode* node = stack[nodePos];
       PRInt32 nodeListPos = findInListOfActiveFormattingElements(node);
       if (nodeListPos == -1) {
 
 
 
         removeFromStack(nodePos);
@@ -3282,16 +3283,17 @@ nsHtml5TreeBuilder::adoptionAgencyEndTag
     appendElement(clone, furthestBlock->node);
     removeFromListOfActiveFormattingElements(formattingEltListPos);
     insertIntoListOfActiveFormattingElements(formattingClone, bookmark);
 
     removeFromStack(formattingEltStackPos);
     insertIntoStack(formattingClone, furthestBlockPos);
     ;
   }
+  return PR_TRUE;
 }
 
 void 
 nsHtml5TreeBuilder::insertIntoStack(nsHtml5StackNode* node, PRInt32 position)
 {
 
 
   if (position == currentPtr + 1) {
@@ -3335,16 +3337,36 @@ nsHtml5TreeBuilder::findInListOfActiveFo
       return -1;
     } else if (node->name == name) {
       return i;
     }
   }
   return -1;
 }
 
+void 
+nsHtml5TreeBuilder::maybeForgetEarlierDuplicateFormattingElement(nsIAtom* name, nsHtml5HtmlAttributes* attributes)
+{
+  PRInt32 candidate = -1;
+  PRInt32 count = 0;
+  for (PRInt32 i = listPtr; i >= 0; i--) {
+    nsHtml5StackNode* node = listOfActiveFormattingElements[i];
+    if (!node) {
+      break;
+    }
+    if (node->name == name && node->attributes->equalsAnother(attributes)) {
+      candidate = i;
+      ++count;
+    }
+  }
+  if (count >= 3) {
+    removeFromListOfActiveFormattingElements(candidate);
+  }
+}
+
 PRInt32 
 nsHtml5TreeBuilder::findLastOrRoot(nsIAtom* name)
 {
   for (PRInt32 i = currentPtr; i > 0; i--) {
     if (stack[i]->name == name) {
       return i;
     }
   }
--- a/parser/html/nsHtml5TreeBuilder.h
+++ b/parser/html/nsHtml5TreeBuilder.h
@@ -147,21 +147,22 @@ class nsHtml5TreeBuilder : public nsAHtm
     inline PRBool isCurrent(nsIAtom* name)
     {
       return name == stack[currentPtr]->name;
     }
 
     void removeFromStack(PRInt32 pos);
     void removeFromStack(nsHtml5StackNode* node);
     void removeFromListOfActiveFormattingElements(PRInt32 pos);
-    void adoptionAgencyEndTag(nsIAtom* name);
+    PRBool adoptionAgencyEndTag(nsIAtom* name);
     void insertIntoStack(nsHtml5StackNode* node, PRInt32 position);
     void insertIntoListOfActiveFormattingElements(nsHtml5StackNode* formattingClone, PRInt32 bookmark);
     PRInt32 findInListOfActiveFormattingElements(nsHtml5StackNode* node);
     PRInt32 findInListOfActiveFormattingElementsContainsBetweenEndAndLastMarker(nsIAtom* name);
+    void maybeForgetEarlierDuplicateFormattingElement(nsIAtom* name, nsHtml5HtmlAttributes* attributes);
     PRInt32 findLastOrRoot(nsIAtom* name);
     PRInt32 findLastOrRoot(PRInt32 group);
     PRBool addAttributesToBody(nsHtml5HtmlAttributes* attributes);
     void addAttributesToHtml(nsHtml5HtmlAttributes* attributes);
     void pushHeadPointerOntoStack();
     void reconstructTheActiveFormattingElements();
     void insertIntoFosterParent(nsIContent** child);
     PRBool isInStack(nsHtml5StackNode* node);
@@ -340,13 +341,12 @@ class nsHtml5TreeBuilder : public nsAHtm
 #define NS_HTML5TREE_BUILDER_CHARSET_S 5
 #define NS_HTML5TREE_BUILDER_CHARSET_E 6
 #define NS_HTML5TREE_BUILDER_CHARSET_T 7
 #define NS_HTML5TREE_BUILDER_CHARSET_EQUALS 8
 #define NS_HTML5TREE_BUILDER_CHARSET_SINGLE_QUOTED 9
 #define NS_HTML5TREE_BUILDER_CHARSET_DOUBLE_QUOTED 10
 #define NS_HTML5TREE_BUILDER_CHARSET_UNQUOTED 11
 #define NS_HTML5TREE_BUILDER_NOT_FOUND_ON_STACK PR_INT32_MAX
-#define NS_HTML5TREE_BUILDER_AAA_MAX_ITERATIONS 10
 
 
 #endif
 
--- a/parser/htmlparser/tests/mochitest/html5_tree_construction_exceptions.js
+++ b/parser/htmlparser/tests/mochitest/html5_tree_construction_exceptions.js
@@ -2,9 +2,10 @@
  * These are the tests we don't pass. The test data comes from the .dat
  * files under html5lib_tree_construction/. Please see
  * html5lib_tree_construction/html5lib_license.txt for the license for these
  * tests.
  */
 var html5Exceptions = {
   "<!doctype html><keygen><frameset>" : true, // Bug 101019
   "<select><keygen>" : true, // Bug 101019
+  "<math><mi><div><object><div><span></span></div></object></div></mi><mi>" : true, // Bug 606925
 }
--- a/parser/htmlparser/tests/mochitest/html5lib_tree_construction/tests1.dat
+++ b/parser/htmlparser/tests/mochitest/html5lib_tree_construction/tests1.dat
@@ -848,21 +848,60 @@ Line: 1 Col: 50 Expected closing tag. Un
 |         <cite>
 |           <i>
 |             <cite>
 |               <i>
 |                 <cite>
 |                   <i>
 |       <i>
 |         <i>
-|           <i>
-|             <div>
-|               <b>
+|           <div>
+|             <b>
+|               "X"
+|             "TEST"
+
+#data
+<p><font size=4><font color=red><font size=4><font size=4><font size=4><font size=4><font size=4><font color=red><p>X
+#errors
+3: Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
+116: Unclosed elements.
+117: End of file seen and there were open elements.
+#document
+| <html>
+|   <head>
+|   <body>
+|     <p>
+|       <font>
+|         size="4"
+|         <font>
+|           color="red"
+|           <font>
+|             size="4"
+|             <font>
+|               size="4"
+|               <font>
+|                 size="4"
+|                 <font>
+|                   size="4"
+|                   <font>
+|                     size="4"
+|                     <font>
+|                       color="red"
+|     <p>
+|       <font>
+|         color="red"
+|         <font>
+|           size="4"
+|           <font>
+|             size="4"
+|             <font>
+|               size="4"
+|               <font>
+|                 color="red"
 |                 "X"
-|               "TEST"
 
 #data
 
 #errors
 Line: 1 Col: 0 Unexpected End of file. Expected DOCTYPE.
 #document
 | <html>
 |   <head>
--- a/parser/htmlparser/tests/mochitest/html5lib_tree_construction/tests10.dat
+++ b/parser/htmlparser/tests/mochitest/html5lib_tree_construction/tests10.dat
@@ -568,17 +568,17 @@ 36: End of file in a foreign namespace c
 |   <head>
 |   <body>
 |     <math math>
 |       <math mi>
 |         <div>
 |           <object>
 |             <div>
 |               <span>
-|       <mi>
+|       <math mi>
 
 #data
 <math><mi><svg><foreignObject><div><div></div></div></foreignObject></svg></mi><mi>
 #errors
 #document
 | <html>
 |   <head>
 |   <body>