Bug 594730 - Make the content attribute in <meta> act as an encoding declaration only if http-equiv="Content-Type" is present. rs=jonas, a=blocking2.0-betaN.
authorHenri Sivonen <hsivonen@iki.fi>
Wed, 08 Dec 2010 14:37:19 +0200
changeset 58890 16847ac492b39d6321cc3d5183c1365beb945eeb
parent 58889 465ac4717f88f8cc249881652eeafee2fe4f28b3
child 58891 22f53d50851adf2a0585d2d4624ec9f5697a99c9
push idunknown
push userunknown
push dateunknown
reviewersjonas, blocking2.0-betaN
bugs594730
milestone2.0b8pre
Bug 594730 - Make the content attribute in <meta> act as an encoding declaration only if http-equiv="Content-Type" is present. rs=jonas, a=blocking2.0-betaN.
parser/html/javasrc/MetaScanner.java
parser/html/javasrc/Tokenizer.java
parser/html/javasrc/TreeBuilder.java
parser/html/nsHtml5MetaScanner.cpp
parser/html/nsHtml5MetaScanner.h
parser/html/nsHtml5MetaScannerCppSupplement.h
parser/html/nsHtml5MetaScannerHSupplement.h
parser/html/nsHtml5StreamParser.cpp
parser/html/nsHtml5StreamParser.h
parser/html/nsHtml5Tokenizer.cpp
parser/html/nsHtml5Tokenizer.h
parser/html/nsHtml5TreeBuilder.cpp
parser/htmlparser/tests/mochitest/Makefile.in
parser/htmlparser/tests/mochitest/file_bug594730-1.html
parser/htmlparser/tests/mochitest/file_bug594730-2.html
parser/htmlparser/tests/mochitest/file_bug594730-3.html
parser/htmlparser/tests/mochitest/file_bug594730-4.html
parser/htmlparser/tests/mochitest/file_bug594730-5.html
parser/htmlparser/tests/mochitest/file_bug594730-6.html
parser/htmlparser/tests/mochitest/file_bug594730-7.html
parser/htmlparser/tests/mochitest/file_bug594730-8.html
parser/htmlparser/tests/mochitest/test_bug594730.html
--- a/parser/html/javasrc/MetaScanner.java
+++ b/parser/html/javasrc/MetaScanner.java
@@ -21,32 +21,42 @@
  * DEALINGS IN THE SOFTWARE.
  */
 
 package nu.validator.htmlparser.impl;
 
 import java.io.IOException;
 
 import nu.validator.htmlparser.annotation.Auto;
-import nu.validator.htmlparser.annotation.NoLength;
+import nu.validator.htmlparser.annotation.Inline;
 import nu.validator.htmlparser.common.ByteReadable;
 
 import org.xml.sax.SAXException;
 
 public abstract class MetaScanner {
 
     /**
      * Constant for "charset".
      */
-    private static final @NoLength char[] CHARSET = "charset".toCharArray();
+    private static final char[] CHARSET = "harset".toCharArray();
     
     /**
      * Constant for "content".
      */
-    private static final @NoLength char[] CONTENT = "content".toCharArray();
+    private static final char[] CONTENT = "ontent".toCharArray();
+
+    /**
+     * Constant for "http-equiv".
+     */
+    private static final char[] HTTP_EQUIV = "ttp-equiv".toCharArray();
+
+    /**
+     * Constant for "content-type".
+     */
+    private static final char[] CONTENT_TYPE = "content-type".toCharArray();
 
     private static final int NO = 0;
 
     private static final int M = 1;
     
     private static final int E = 2;
     
     private static final int T = 3;
@@ -88,69 +98,95 @@ public abstract class MetaScanner {
     private static final int COMMENT = 17;
 
     private static final int COMMENT_END_DASH = 18;
 
     private static final int COMMENT_END = 19;
     
     private static final int SELF_CLOSING_START_TAG = 20;
     
+    private static final int HTTP_EQUIV_NOT_SEEN = 0;
+    
+    private static final int HTTP_EQUIV_CONTENT_TYPE = 1;
+
+    private static final int HTTP_EQUIV_OTHER = 2;
+
     /**
      * The data source.
      */
     protected ByteReadable readable;
     
     /**
      * The state of the state machine that recognizes the tag name "meta".
      */
     private int metaState = NO;
 
     /**
      * The current position in recognizing the attribute name "content".
      */
-    private int contentIndex = -1;
+    private int contentIndex = Integer.MAX_VALUE;
     
     /**
      * The current position in recognizing the attribute name "charset".
      */
-    private int charsetIndex = -1;
+    private int charsetIndex = Integer.MAX_VALUE;
+
+    /**
+     * The current position in recognizing the attribute name "http-equive".
+     */
+    private int httpEquivIndex = Integer.MAX_VALUE;
+
+    /**
+     * The current position in recognizing the attribute value "content-type".
+     */
+    private int contentTypeIndex = Integer.MAX_VALUE;
 
     /**
      * The tokenizer state.
      */
     protected int stateSave = DATA;
 
     /**
      * The currently filled length of strBuf.
      */
     private int strBufLen;
 
     /**
      * Accumulation buffer for attribute values.
      */
     private @Auto char[] strBuf;
     
-    // [NOCPP[
+    private String content;
     
-    /**
-     * @param source
-     * @param errorHandler
-     * @param publicId
-     * @param systemId
-     */
+    private String charset;
+    
+    private int httpEquivState;
+    
     public MetaScanner() {
         this.readable = null;
         this.metaState = NO;
-        this.contentIndex = -1;
-        this.charsetIndex = -1;
+        this.contentIndex = Integer.MAX_VALUE;
+        this.charsetIndex = Integer.MAX_VALUE;
+        this.httpEquivIndex = Integer.MAX_VALUE;
+        this.contentTypeIndex = Integer.MAX_VALUE;
         this.stateSave = DATA;
-        strBufLen = 0;
-        strBuf = new char[36];
+        this.strBufLen = 0;
+        this.strBuf = new char[36];
+        this.content = null;
+        this.charset = null;
+        this.httpEquivState = HTTP_EQUIV_NOT_SEEN;
     }
     
+    @SuppressWarnings("unused") private void destructor() {
+        Portability.releaseString(content);
+        Portability.releaseString(charset);
+    }
+
+    // [NOCPP[
+    
     /**
      * Reads a byte from the data source.
      * 
      * -1 means end.
      * @return
      * @throws IOException
      */
     protected int read() throws IOException {
@@ -290,27 +326,42 @@ public abstract class MetaScanner {
                             case '\t':
                             case '\n':
                             case '\u000C':
                                 continue;
                             case '/':
                                 state = MetaScanner.SELF_CLOSING_START_TAG;
                                 continue stateloop;
                             case '>':
+                                if (handleTag()) {
+                                    break stateloop;
+                                }
                                 state = DATA;
                                 continue stateloop;
                             case 'c':
                             case 'C':
                                 contentIndex = 0;
                                 charsetIndex = 0;
+                                httpEquivIndex = Integer.MAX_VALUE;
+                                contentTypeIndex = Integer.MAX_VALUE;
+                                state = MetaScanner.ATTRIBUTE_NAME;
+                                break beforeattributenameloop;                                
+                            case 'h':
+                            case 'H':
+                                contentIndex = Integer.MAX_VALUE;
+                                charsetIndex = Integer.MAX_VALUE;
+                                httpEquivIndex = 0;
+                                contentTypeIndex = Integer.MAX_VALUE;
                                 state = MetaScanner.ATTRIBUTE_NAME;
                                 break beforeattributenameloop;                                
                             default:
-                                contentIndex = -1;
-                                charsetIndex = -1;
+                                contentIndex = Integer.MAX_VALUE;
+                                charsetIndex = Integer.MAX_VALUE;
+                                httpEquivIndex = Integer.MAX_VALUE;
+                                contentTypeIndex = Integer.MAX_VALUE;
                                 state = MetaScanner.ATTRIBUTE_NAME;
                                 break beforeattributenameloop;
                             // continue stateloop;
                         }
                     }
                     // FALLTHRU DON'T REORDER
                 case ATTRIBUTE_NAME:
                     attributenameloop: for (;;) {
@@ -324,41 +375,46 @@ public abstract class MetaScanner {
                             case '\u000C':
                                 state = MetaScanner.AFTER_ATTRIBUTE_NAME;
                                 continue stateloop;
                             case '/':
                                 state = MetaScanner.SELF_CLOSING_START_TAG;
                                 continue stateloop;
                             case '=':
                                 strBufLen = 0;
+                                contentTypeIndex = 0;
                                 state = MetaScanner.BEFORE_ATTRIBUTE_VALUE;
                                 break attributenameloop;
                             // continue stateloop;
                             case '>':
+                                if (handleTag()) {
+                                    break stateloop;
+                                }
                                 state = MetaScanner.DATA;
                                 continue stateloop;
                             default:
                                 if (metaState == A) {
                                     if (c >= 'A' && c <= 'Z') {
                                         c += 0x20;
                                     }
-                                    if (contentIndex == 6) {
-                                        contentIndex = -1;
-                                    } else if (contentIndex > -1
-                                            && contentIndex < 6
-                                            && (c == CONTENT[contentIndex + 1])) {
-                                        contentIndex++;
+                                    if (contentIndex < CONTENT.length && c == CONTENT[contentIndex]) {
+                                        ++contentIndex;
+                                    } else {
+                                        contentIndex = Integer.MAX_VALUE;
                                     }
-                                    if (charsetIndex == 6) {
-                                        charsetIndex = -1;
-                                    } else if (charsetIndex > -1
-                                            && charsetIndex < 6
-                                            && (c == CHARSET[charsetIndex + 1])) {
-                                        charsetIndex++;
+                                    if (charsetIndex < CHARSET.length && c == CHARSET[charsetIndex]) {
+                                        ++charsetIndex;
+                                    } else {
+                                        charsetIndex = Integer.MAX_VALUE;
                                     }
+                                    if (httpEquivIndex < HTTP_EQUIV.length && c == HTTP_EQUIV[httpEquivIndex]) {
+                                        ++httpEquivIndex;
+                                    } else {
+                                        httpEquivIndex = Integer.MAX_VALUE;
+                                    }                                    
                                 }
                                 continue;
                         }
                     }
                     // FALLTHRU DON'T REORDER
                 case BEFORE_ATTRIBUTE_VALUE:
                     beforeattributevalueloop: for (;;) {
                         c = read();
@@ -373,48 +429,45 @@ public abstract class MetaScanner {
                             case '"':
                                 state = MetaScanner.ATTRIBUTE_VALUE_DOUBLE_QUOTED;
                                 break beforeattributevalueloop;
                             // continue stateloop;
                             case '\'':
                                 state = MetaScanner.ATTRIBUTE_VALUE_SINGLE_QUOTED;
                                 continue stateloop;
                             case '>':
+                                if (handleTag()) {
+                                    break stateloop;
+                                }
                                 state = MetaScanner.DATA;
                                 continue stateloop;
                             default:
-                                if (charsetIndex == 6 || contentIndex == 6) {
-                                    addToBuffer(c);
-                                }
+                                handleCharInAttributeValue(c);
                                 state = MetaScanner.ATTRIBUTE_VALUE_UNQUOTED;
                                 continue stateloop;
                         }
                     }
                     // FALLTHRU DON'T REORDER
                 case ATTRIBUTE_VALUE_DOUBLE_QUOTED:
                     attributevaluedoublequotedloop: for (;;) {
                         if (reconsume) {
                             reconsume = false;
                         } else {
                             c = read();
                         }
                         switch (c) {
                             case -1:
                                 break stateloop;
                             case '"':
-                                if (tryCharset()) {
-                                    break stateloop;
-                                }
+                                handleAttributeValue();
                                 state = MetaScanner.AFTER_ATTRIBUTE_VALUE_QUOTED;
                                 break attributevaluedoublequotedloop;
                             // continue stateloop;
                             default:
-                                if (metaState == A && (contentIndex == 6 || charsetIndex == 6)) {
-                                    addToBuffer(c);
-                                }
+                                handleCharInAttributeValue(c);
                                 continue;
                         }
                     }
                     // FALLTHRU DON'T REORDER
                 case AFTER_ATTRIBUTE_VALUE_QUOTED:
                     afterattributevaluequotedloop: for (;;) {
                         c = read();
                         switch (c) {
@@ -426,31 +479,37 @@ public abstract class MetaScanner {
                             case '\u000C':
                                 state = MetaScanner.BEFORE_ATTRIBUTE_NAME;
                                 continue stateloop;
                             case '/':
                                 state = MetaScanner.SELF_CLOSING_START_TAG;
                                 break afterattributevaluequotedloop;
                             // continue stateloop;
                             case '>':
+                                if (handleTag()) {
+                                    break stateloop;
+                                }
                                 state = MetaScanner.DATA;
                                 continue stateloop;
                             default:
                                 state = MetaScanner.BEFORE_ATTRIBUTE_NAME;
                                 reconsume = true;
                                 continue stateloop;
                         }
                     }
                     // FALLTHRU DON'T REORDER
                 case SELF_CLOSING_START_TAG:
                     c = read();
                     switch (c) {
                         case -1:
                             break stateloop;
                         case '>':
+                            if (handleTag()) {
+                                break stateloop;
+                            }
                             state = MetaScanner.DATA;
                             continue stateloop;
                         default:
                             state = MetaScanner.BEFORE_ATTRIBUTE_NAME;
                             reconsume = true;
                             continue stateloop;
                     }
                     // XXX reorder point
@@ -464,57 +523,55 @@ public abstract class MetaScanner {
                         switch (c) {
                             case -1:
                                 break stateloop;
                             case ' ':
                             case '\t':
                             case '\n':
 
                             case '\u000C':
-                                if (tryCharset()) {
-                                    break stateloop;
-                                }
+                                handleAttributeValue();
                                 state = MetaScanner.BEFORE_ATTRIBUTE_NAME;
                                 continue stateloop;
                             case '>':
-                                if (tryCharset()) {
+                                handleAttributeValue();
+                                if (handleTag()) {
                                     break stateloop;
                                 }
                                 state = MetaScanner.DATA;
                                 continue stateloop;
                             default:
-                                if (metaState == A && (contentIndex == 6 || charsetIndex == 6)) {
-                                    addToBuffer(c);
-                                }
+                                handleCharInAttributeValue(c);
                                 continue;
                         }
                     }
                     // XXX reorder point
                 case AFTER_ATTRIBUTE_NAME:
                     for (;;) {
                         c = read();
                         switch (c) {
                             case -1:
                                 break stateloop;
                             case ' ':
                             case '\t':
                             case '\n':
                             case '\u000C':
                                 continue;
                             case '/':
-                                if (tryCharset()) {
-                                    break stateloop;
-                                }
+                                handleAttributeValue();
                                 state = MetaScanner.SELF_CLOSING_START_TAG;
                                 continue stateloop;
                             case '=':
+                                strBufLen = 0;
+                                contentTypeIndex = 0;
                                 state = MetaScanner.BEFORE_ATTRIBUTE_VALUE;
                                 continue stateloop;
                             case '>':
-                                if (tryCharset()) {
+                                handleAttributeValue();
+                                if (handleTag()) {
                                     break stateloop;
                                 }
                                 state = MetaScanner.DATA;
                                 continue stateloop;
                             case 'c':
                             case 'C':
                                 contentIndex = 0;
                                 charsetIndex = 0;
@@ -651,25 +708,21 @@ public abstract class MetaScanner {
                             reconsume = false;
                         } else {
                             c = read();
                         }
                         switch (c) {
                             case -1:
                                 break stateloop;
                             case '\'':
-                                if (tryCharset()) {
-                                    break stateloop;
-                                }
+                                handleAttributeValue();
                                 state = MetaScanner.AFTER_ATTRIBUTE_VALUE_QUOTED;
                                 continue stateloop;
                             default:
-                                if (metaState == A && (contentIndex == 6 || charsetIndex == 6)) {
-                                    addToBuffer(c);
-                                }
+                                handleCharInAttributeValue(c);
                                 continue;
                         }
                     }
                     // XXX reorder point
                 case SCAN_UNTIL_GT:
                     for (;;) {
                         if (reconsume) {
                             reconsume = false;
@@ -686,16 +739,37 @@ public abstract class MetaScanner {
                                 continue;
                         }
                     }
             }
         }
         stateSave  = state;
     }
 
+    private void handleCharInAttributeValue(int c) {
+        if (metaState == A) {
+            if (contentIndex == CONTENT.length || charsetIndex == CHARSET.length) {
+                addToBuffer(c);
+            } else if (httpEquivIndex == HTTP_EQUIV.length) {
+                if (contentTypeIndex < CONTENT_TYPE.length && toAsciiLowerCase(c) == CONTENT_TYPE[contentTypeIndex]) {
+                    ++contentTypeIndex;
+                } else {
+                    contentTypeIndex = Integer.MAX_VALUE;
+                }
+            }
+        }
+    }
+
+    @Inline private int toAsciiLowerCase(int c) {
+        if (c >= 'A' && c <= 'Z') {
+            return c + 0x20;
+        }
+        return c;
+    }
+
     /**
      * Adds a character to the accumulation buffer.
      * @param c the character to add
      */
     private void addToBuffer(int c) {
         if (strBufLen == strBuf.length) {
             char[] newBuf = new char[strBuf.length + (strBuf.length << 1)];
             System.arraycopy(strBuf, 0, newBuf, 0, strBuf.length);
@@ -704,41 +778,64 @@ public abstract class MetaScanner {
         strBuf[strBufLen++] = (char)c;
     }
 
     /**
      * Attempts to extract a charset name from the accumulation buffer.
      * @return <code>true</code> if successful
      * @throws SAXException
      */
-    private boolean tryCharset() throws SAXException {
-        if (metaState != A || !(contentIndex == 6 || charsetIndex == 6)) {
-            return false;
+    private void handleAttributeValue() throws SAXException {
+        if (metaState != A) {
+            return;
+        }
+        if (contentIndex == CONTENT.length && content == null) {
+            content = Portability.newStringFromBuffer(strBuf, 0, strBufLen);
+            return;
+        }
+        if (charsetIndex == CHARSET.length && charset == null) {
+            charset = Portability.newStringFromBuffer(strBuf, 0, strBufLen);            
+            return;
         }
-        String attVal = Portability.newStringFromBuffer(strBuf, 0, strBufLen);
-        String candidateEncoding;
-        if (contentIndex == 6) {
-            candidateEncoding = TreeBuilder.extractCharsetFromContent(attVal);
-            Portability.releaseString(attVal);
-        } else {
-            candidateEncoding = attVal;
+        if (httpEquivIndex == HTTP_EQUIV.length
+                && httpEquivState == HTTP_EQUIV_NOT_SEEN) {
+            httpEquivState = (contentTypeIndex == CONTENT_TYPE.length) ? HTTP_EQUIV_CONTENT_TYPE
+                    : HTTP_EQUIV_OTHER;
+            return;
         }
-        if (candidateEncoding == null) {
-            return false;
-        }
-        boolean success = tryCharset(candidateEncoding);
-        Portability.releaseString(candidateEncoding);
-        contentIndex = -1;
-        charsetIndex = -1;
-        return success;
+    }
+
+    private boolean handleTag() throws SAXException {
+        boolean stop = handleTagInner();
+        Portability.releaseString(content);
+        content = null;
+        Portability.releaseString(charset);
+        charset = null;
+        httpEquivState = HTTP_EQUIV_NOT_SEEN;
+        return stop;
     }
     
+    private boolean handleTagInner() throws SAXException {
+        if (charset != null && tryCharset(charset)) {
+                return true;
+        }
+        if (content != null && httpEquivState == HTTP_EQUIV_CONTENT_TYPE) {
+            String extract = TreeBuilder.extractCharsetFromContent(content);
+            if (extract == null) {
+                return false;
+            }
+            boolean success = tryCharset(extract);
+            Portability.releaseString(extract);
+            return success;
+        }
+        return false;
+    }
+
     /**
      * Tries to switch to an encoding.
      * 
      * @param encoding
      * @return <code>true</code> if successful
      * @throws SAXException
      */
     protected abstract boolean tryCharset(String encoding) throws SAXException;
-
     
 }
--- a/parser/html/javasrc/Tokenizer.java
+++ b/parser/html/javasrc/Tokenizer.java
@@ -6455,21 +6455,22 @@ public class Tokenizer implements Locato
      * @return the alreadyComplainedAboutNonAscii
      */
     public boolean isAlreadyComplainedAboutNonAscii() {
         return true;
     }
 
     // ]NOCPP]
 
-    public void internalEncodingDeclaration(String internalCharset)
+    public boolean internalEncodingDeclaration(String internalCharset)
             throws SAXException {
         if (encodingDeclarationHandler != null) {
-            encodingDeclarationHandler.internalEncodingDeclaration(internalCharset);
+            return encodingDeclarationHandler.internalEncodingDeclaration(internalCharset);
         }
+        return false;
     }
 
     /**
      * @param val
      * @throws SAXException
      */
     private void emitOrAppendTwo(@Const @NoLength char[] val, int returnState)
             throws SAXException {
--- a/parser/html/javasrc/TreeBuilder.java
+++ b/parser/html/javasrc/TreeBuilder.java
@@ -3098,40 +3098,39 @@ public abstract class TreeBuilder<T> imp
             charset = Portability.newStringFromBuffer(buffer, start, end
                     - start);
         }
         return charset;
     }
 
     private void checkMetaCharset(HtmlAttributes attributes)
             throws SAXException {
+        String charset = attributes.getValue(AttributeName.CHARSET);
+        if (charset != null) {
+            if (tokenizer.internalEncodingDeclaration(charset)) {
+                requestSuspension();
+                return;
+            }            
+            return;
+        }
+        if (!Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+                "content-type",
+                attributes.getValue(AttributeName.HTTP_EQUIV))) {
+            return;
+        }
         String content = attributes.getValue(AttributeName.CONTENT);
-        String internalCharsetLegacy = null;
         if (content != null) {
-            internalCharsetLegacy = TreeBuilder.extractCharsetFromContent(content);
-            // [NOCPP[
-            if (errorHandler != null
-                    && internalCharsetLegacy != null
-                    && !Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
-                            "content-type",
-                            attributes.getValue(AttributeName.HTTP_EQUIV))) {
-                warn("Attribute \u201Ccontent\u201D would be sniffed as an internal character encoding declaration but there was no matching \u201Chttp-equiv='Content-Type'\u201D attribute.");
+            String extract = TreeBuilder.extractCharsetFromContent(content);
+            // remember not to return early without releasing the string
+            if (extract != null) {
+                if (tokenizer.internalEncodingDeclaration(extract)) {
+                    requestSuspension();
+                }                
             }
-            // ]NOCPP]
-        }
-        if (internalCharsetLegacy == null) {
-            String internalCharsetHtml5 = attributes.getValue(AttributeName.CHARSET);
-            if (internalCharsetHtml5 != null) {
-                tokenizer.internalEncodingDeclaration(internalCharsetHtml5);
-                requestSuspension();
-            }
-        } else {
-            tokenizer.internalEncodingDeclaration(internalCharsetLegacy);
-            Portability.releaseString(internalCharsetLegacy);
-            requestSuspension();
+            Portability.releaseString(extract);
         }
     }
 
     public final void endTag(ElementName elementName) throws SAXException {
         flushCharacters();
         needToDropLF = false;
         int eltPos;
         int group = elementName.group;
--- a/parser/html/nsHtml5MetaScanner.cpp
+++ b/parser/html/nsHtml5MetaScanner.cpp
@@ -54,18 +54,50 @@
 #include "nsHtml5HtmlAttributes.h"
 #include "nsHtml5StackNode.h"
 #include "nsHtml5UTF16Buffer.h"
 #include "nsHtml5StateSnapshot.h"
 #include "nsHtml5Portability.h"
 
 #include "nsHtml5MetaScanner.h"
 
-PRUnichar nsHtml5MetaScanner::CHARSET[] = { 'c', 'h', 'a', 'r', 's', 'e', 't' };
-PRUnichar nsHtml5MetaScanner::CONTENT[] = { 'c', 'o', 'n', 't', 'e', 'n', 't' };
+static PRUnichar const CHARSET_DATA[] = { 'h', 'a', 'r', 's', 'e', 't' };
+staticJArray<PRUnichar,PRInt32> nsHtml5MetaScanner::CHARSET = { CHARSET_DATA, NS_ARRAY_LENGTH(CHARSET_DATA) };
+static PRUnichar const CONTENT_DATA[] = { 'o', 'n', 't', 'e', 'n', 't' };
+staticJArray<PRUnichar,PRInt32> nsHtml5MetaScanner::CONTENT = { CONTENT_DATA, NS_ARRAY_LENGTH(CONTENT_DATA) };
+static PRUnichar const HTTP_EQUIV_DATA[] = { 't', 't', 'p', '-', 'e', 'q', 'u', 'i', 'v' };
+staticJArray<PRUnichar,PRInt32> nsHtml5MetaScanner::HTTP_EQUIV = { HTTP_EQUIV_DATA, NS_ARRAY_LENGTH(HTTP_EQUIV_DATA) };
+static PRUnichar const CONTENT_TYPE_DATA[] = { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 't', 'y', 'p', 'e' };
+staticJArray<PRUnichar,PRInt32> nsHtml5MetaScanner::CONTENT_TYPE = { CONTENT_TYPE_DATA, NS_ARRAY_LENGTH(CONTENT_TYPE_DATA) };
+
+nsHtml5MetaScanner::nsHtml5MetaScanner()
+  : readable(nsnull),
+    metaState(NS_HTML5META_SCANNER_NO),
+    contentIndex(PR_INT32_MAX),
+    charsetIndex(PR_INT32_MAX),
+    httpEquivIndex(PR_INT32_MAX),
+    contentTypeIndex(PR_INT32_MAX),
+    stateSave(NS_HTML5META_SCANNER_DATA),
+    strBufLen(0),
+    strBuf(jArray<PRUnichar,PRInt32>::newJArray(36)),
+    content(nsnull),
+    charset(nsnull),
+    httpEquivState(NS_HTML5META_SCANNER_HTTP_EQUIV_NOT_SEEN)
+{
+  MOZ_COUNT_CTOR(nsHtml5MetaScanner);
+}
+
+
+nsHtml5MetaScanner::~nsHtml5MetaScanner()
+{
+  MOZ_COUNT_DTOR(nsHtml5MetaScanner);
+  nsHtml5Portability::releaseString(content);
+  nsHtml5Portability::releaseString(charset);
+}
+
 void 
 nsHtml5MetaScanner::stateLoop(PRInt32 state)
 {
   PRInt32 c = -1;
   PRBool reconsume = PR_FALSE;
   stateloop: for (; ; ) {
     switch(state) {
       case NS_HTML5META_SCANNER_DATA: {
@@ -204,29 +236,45 @@ nsHtml5MetaScanner::stateLoop(PRInt32 st
             case '\f': {
               continue;
             }
             case '/': {
               state = NS_HTML5META_SCANNER_SELF_CLOSING_START_TAG;
               NS_HTML5_CONTINUE(stateloop);
             }
             case '>': {
+              if (handleTag()) {
+                NS_HTML5_BREAK(stateloop);
+              }
               state = NS_HTML5META_SCANNER_DATA;
               NS_HTML5_CONTINUE(stateloop);
             }
             case 'c':
             case 'C': {
               contentIndex = 0;
               charsetIndex = 0;
+              httpEquivIndex = PR_INT32_MAX;
+              contentTypeIndex = PR_INT32_MAX;
+              state = NS_HTML5META_SCANNER_ATTRIBUTE_NAME;
+              NS_HTML5_BREAK(beforeattributenameloop);
+            }
+            case 'h':
+            case 'H': {
+              contentIndex = PR_INT32_MAX;
+              charsetIndex = PR_INT32_MAX;
+              httpEquivIndex = 0;
+              contentTypeIndex = PR_INT32_MAX;
               state = NS_HTML5META_SCANNER_ATTRIBUTE_NAME;
               NS_HTML5_BREAK(beforeattributenameloop);
             }
             default: {
-              contentIndex = -1;
-              charsetIndex = -1;
+              contentIndex = PR_INT32_MAX;
+              charsetIndex = PR_INT32_MAX;
+              httpEquivIndex = PR_INT32_MAX;
+              contentTypeIndex = PR_INT32_MAX;
               state = NS_HTML5META_SCANNER_ATTRIBUTE_NAME;
               NS_HTML5_BREAK(beforeattributenameloop);
             }
           }
         }
         beforeattributenameloop_end: ;
       }
       case NS_HTML5META_SCANNER_ATTRIBUTE_NAME: {
@@ -244,37 +292,46 @@ nsHtml5MetaScanner::stateLoop(PRInt32 st
               NS_HTML5_CONTINUE(stateloop);
             }
             case '/': {
               state = NS_HTML5META_SCANNER_SELF_CLOSING_START_TAG;
               NS_HTML5_CONTINUE(stateloop);
             }
             case '=': {
               strBufLen = 0;
+              contentTypeIndex = 0;
               state = NS_HTML5META_SCANNER_BEFORE_ATTRIBUTE_VALUE;
               NS_HTML5_BREAK(attributenameloop);
             }
             case '>': {
+              if (handleTag()) {
+                NS_HTML5_BREAK(stateloop);
+              }
               state = NS_HTML5META_SCANNER_DATA;
               NS_HTML5_CONTINUE(stateloop);
             }
             default: {
               if (metaState == NS_HTML5META_SCANNER_A) {
                 if (c >= 'A' && c <= 'Z') {
                   c += 0x20;
                 }
-                if (contentIndex == 6) {
-                  contentIndex = -1;
-                } else if (contentIndex > -1 && contentIndex < 6 && (c == CONTENT[contentIndex + 1])) {
-                  contentIndex++;
+                if (contentIndex < CONTENT.length && c == CONTENT[contentIndex]) {
+                  ++contentIndex;
+                } else {
+                  contentIndex = PR_INT32_MAX;
                 }
-                if (charsetIndex == 6) {
-                  charsetIndex = -1;
-                } else if (charsetIndex > -1 && charsetIndex < 6 && (c == CHARSET[charsetIndex + 1])) {
-                  charsetIndex++;
+                if (charsetIndex < CHARSET.length && c == CHARSET[charsetIndex]) {
+                  ++charsetIndex;
+                } else {
+                  charsetIndex = PR_INT32_MAX;
+                }
+                if (httpEquivIndex < HTTP_EQUIV.length && c == HTTP_EQUIV[httpEquivIndex]) {
+                  ++httpEquivIndex;
+                } else {
+                  httpEquivIndex = PR_INT32_MAX;
                 }
               }
               continue;
             }
           }
         }
         attributenameloop_end: ;
       }
@@ -295,23 +352,24 @@ nsHtml5MetaScanner::stateLoop(PRInt32 st
               state = NS_HTML5META_SCANNER_ATTRIBUTE_VALUE_DOUBLE_QUOTED;
               NS_HTML5_BREAK(beforeattributevalueloop);
             }
             case '\'': {
               state = NS_HTML5META_SCANNER_ATTRIBUTE_VALUE_SINGLE_QUOTED;
               NS_HTML5_CONTINUE(stateloop);
             }
             case '>': {
+              if (handleTag()) {
+                NS_HTML5_BREAK(stateloop);
+              }
               state = NS_HTML5META_SCANNER_DATA;
               NS_HTML5_CONTINUE(stateloop);
             }
             default: {
-              if (charsetIndex == 6 || contentIndex == 6) {
-                addToBuffer(c);
-              }
+              handleCharInAttributeValue(c);
               state = NS_HTML5META_SCANNER_ATTRIBUTE_VALUE_UNQUOTED;
               NS_HTML5_CONTINUE(stateloop);
             }
           }
         }
         beforeattributevalueloop_end: ;
       }
       case NS_HTML5META_SCANNER_ATTRIBUTE_VALUE_DOUBLE_QUOTED: {
@@ -321,26 +379,22 @@ nsHtml5MetaScanner::stateLoop(PRInt32 st
           } else {
             c = read();
           }
           switch(c) {
             case -1: {
               NS_HTML5_BREAK(stateloop);
             }
             case '\"': {
-              if (tryCharset()) {
-                NS_HTML5_BREAK(stateloop);
-              }
+              handleAttributeValue();
               state = NS_HTML5META_SCANNER_AFTER_ATTRIBUTE_VALUE_QUOTED;
               NS_HTML5_BREAK(attributevaluedoublequotedloop);
             }
             default: {
-              if (metaState == NS_HTML5META_SCANNER_A && (contentIndex == 6 || charsetIndex == 6)) {
-                addToBuffer(c);
-              }
+              handleCharInAttributeValue(c);
               continue;
             }
           }
         }
         attributevaluedoublequotedloop_end: ;
       }
       case NS_HTML5META_SCANNER_AFTER_ATTRIBUTE_VALUE_QUOTED: {
         for (; ; ) {
@@ -356,16 +410,19 @@ nsHtml5MetaScanner::stateLoop(PRInt32 st
               state = NS_HTML5META_SCANNER_BEFORE_ATTRIBUTE_NAME;
               NS_HTML5_CONTINUE(stateloop);
             }
             case '/': {
               state = NS_HTML5META_SCANNER_SELF_CLOSING_START_TAG;
               NS_HTML5_BREAK(afterattributevaluequotedloop);
             }
             case '>': {
+              if (handleTag()) {
+                NS_HTML5_BREAK(stateloop);
+              }
               state = NS_HTML5META_SCANNER_DATA;
               NS_HTML5_CONTINUE(stateloop);
             }
             default: {
               state = NS_HTML5META_SCANNER_BEFORE_ATTRIBUTE_NAME;
               reconsume = PR_TRUE;
               NS_HTML5_CONTINUE(stateloop);
             }
@@ -375,16 +432,19 @@ nsHtml5MetaScanner::stateLoop(PRInt32 st
       }
       case NS_HTML5META_SCANNER_SELF_CLOSING_START_TAG: {
         c = read();
         switch(c) {
           case -1: {
             NS_HTML5_BREAK(stateloop);
           }
           case '>': {
+            if (handleTag()) {
+              NS_HTML5_BREAK(stateloop);
+            }
             state = NS_HTML5META_SCANNER_DATA;
             NS_HTML5_CONTINUE(stateloop);
           }
           default: {
             state = NS_HTML5META_SCANNER_BEFORE_ATTRIBUTE_NAME;
             reconsume = PR_TRUE;
             NS_HTML5_CONTINUE(stateloop);
           }
@@ -400,33 +460,30 @@ nsHtml5MetaScanner::stateLoop(PRInt32 st
           switch(c) {
             case -1: {
               NS_HTML5_BREAK(stateloop);
             }
             case ' ':
             case '\t':
             case '\n':
             case '\f': {
-              if (tryCharset()) {
-                NS_HTML5_BREAK(stateloop);
-              }
+              handleAttributeValue();
               state = NS_HTML5META_SCANNER_BEFORE_ATTRIBUTE_NAME;
               NS_HTML5_CONTINUE(stateloop);
             }
             case '>': {
-              if (tryCharset()) {
+              handleAttributeValue();
+              if (handleTag()) {
                 NS_HTML5_BREAK(stateloop);
               }
               state = NS_HTML5META_SCANNER_DATA;
               NS_HTML5_CONTINUE(stateloop);
             }
             default: {
-              if (metaState == NS_HTML5META_SCANNER_A && (contentIndex == 6 || charsetIndex == 6)) {
-                addToBuffer(c);
-              }
+              handleCharInAttributeValue(c);
               continue;
             }
           }
         }
       }
       case NS_HTML5META_SCANNER_AFTER_ATTRIBUTE_NAME: {
         for (; ; ) {
           c = read();
@@ -436,28 +493,29 @@ nsHtml5MetaScanner::stateLoop(PRInt32 st
             }
             case ' ':
             case '\t':
             case '\n':
             case '\f': {
               continue;
             }
             case '/': {
-              if (tryCharset()) {
-                NS_HTML5_BREAK(stateloop);
-              }
+              handleAttributeValue();
               state = NS_HTML5META_SCANNER_SELF_CLOSING_START_TAG;
               NS_HTML5_CONTINUE(stateloop);
             }
             case '=': {
+              strBufLen = 0;
+              contentTypeIndex = 0;
               state = NS_HTML5META_SCANNER_BEFORE_ATTRIBUTE_VALUE;
               NS_HTML5_CONTINUE(stateloop);
             }
             case '>': {
-              if (tryCharset()) {
+              handleAttributeValue();
+              if (handleTag()) {
                 NS_HTML5_BREAK(stateloop);
               }
               state = NS_HTML5META_SCANNER_DATA;
               NS_HTML5_CONTINUE(stateloop);
             }
             case 'c':
             case 'C': {
               contentIndex = 0;
@@ -622,26 +680,22 @@ nsHtml5MetaScanner::stateLoop(PRInt32 st
           } else {
             c = read();
           }
           switch(c) {
             case -1: {
               NS_HTML5_BREAK(stateloop);
             }
             case '\'': {
-              if (tryCharset()) {
-                NS_HTML5_BREAK(stateloop);
-              }
+              handleAttributeValue();
               state = NS_HTML5META_SCANNER_AFTER_ATTRIBUTE_VALUE_QUOTED;
               NS_HTML5_CONTINUE(stateloop);
             }
             default: {
-              if (metaState == NS_HTML5META_SCANNER_A && (contentIndex == 6 || charsetIndex == 6)) {
-                addToBuffer(c);
-              }
+              handleCharInAttributeValue(c);
               continue;
             }
           }
         }
       }
       case NS_HTML5META_SCANNER_SCAN_UNTIL_GT: {
         for (; ; ) {
           if (reconsume) {
@@ -665,48 +719,90 @@ nsHtml5MetaScanner::stateLoop(PRInt32 st
       }
     }
   }
   stateloop_end: ;
   stateSave = state;
 }
 
 void 
+nsHtml5MetaScanner::handleCharInAttributeValue(PRInt32 c)
+{
+  if (metaState == NS_HTML5META_SCANNER_A) {
+    if (contentIndex == CONTENT.length || charsetIndex == CHARSET.length) {
+      addToBuffer(c);
+    } else if (httpEquivIndex == HTTP_EQUIV.length) {
+      if (contentTypeIndex < CONTENT_TYPE.length && toAsciiLowerCase(c) == CONTENT_TYPE[contentTypeIndex]) {
+        ++contentTypeIndex;
+      } else {
+        contentTypeIndex = PR_INT32_MAX;
+      }
+    }
+  }
+}
+
+void 
 nsHtml5MetaScanner::addToBuffer(PRInt32 c)
 {
   if (strBufLen == strBuf.length) {
     jArray<PRUnichar,PRInt32> newBuf = jArray<PRUnichar,PRInt32>::newJArray(strBuf.length + (strBuf.length << 1));
     nsHtml5ArrayCopy::arraycopy(strBuf, newBuf, strBuf.length);
     strBuf = newBuf;
   }
   strBuf[strBufLen++] = (PRUnichar) c;
 }
 
-PRBool 
-nsHtml5MetaScanner::tryCharset()
+void 
+nsHtml5MetaScanner::handleAttributeValue()
 {
-  if (metaState != NS_HTML5META_SCANNER_A || !(contentIndex == 6 || charsetIndex == 6)) {
-    return PR_FALSE;
+  if (metaState != NS_HTML5META_SCANNER_A) {
+    return;
+  }
+  if (contentIndex == CONTENT.length && !content) {
+    content = nsHtml5Portability::newStringFromBuffer(strBuf, 0, strBufLen);
+    return;
+  }
+  if (charsetIndex == CHARSET.length && !charset) {
+    charset = nsHtml5Portability::newStringFromBuffer(strBuf, 0, strBufLen);
+    return;
+  }
+  if (httpEquivIndex == HTTP_EQUIV.length && httpEquivState == NS_HTML5META_SCANNER_HTTP_EQUIV_NOT_SEEN) {
+    httpEquivState = (contentTypeIndex == CONTENT_TYPE.length) ? NS_HTML5META_SCANNER_HTTP_EQUIV_CONTENT_TYPE : NS_HTML5META_SCANNER_HTTP_EQUIV_OTHER;
+    return;
   }
-  nsString* attVal = nsHtml5Portability::newStringFromBuffer(strBuf, 0, strBufLen);
-  nsString* candidateEncoding;
-  if (contentIndex == 6) {
-    candidateEncoding = nsHtml5TreeBuilder::extractCharsetFromContent(attVal);
-    nsHtml5Portability::releaseString(attVal);
-  } else {
-    candidateEncoding = attVal;
+}
+
+PRBool 
+nsHtml5MetaScanner::handleTag()
+{
+  PRBool stop = handleTagInner();
+  nsHtml5Portability::releaseString(content);
+  content = nsnull;
+  nsHtml5Portability::releaseString(charset);
+  charset = nsnull;
+  httpEquivState = NS_HTML5META_SCANNER_HTTP_EQUIV_NOT_SEEN;
+  return stop;
+}
+
+PRBool 
+nsHtml5MetaScanner::handleTagInner()
+{
+  if (!!charset && tryCharset(charset)) {
+    return PR_TRUE;
   }
-  if (!candidateEncoding) {
-    return PR_FALSE;
+  if (!!content && httpEquivState == NS_HTML5META_SCANNER_HTTP_EQUIV_CONTENT_TYPE) {
+    nsString* extract = nsHtml5TreeBuilder::extractCharsetFromContent(content);
+    if (!extract) {
+      return PR_FALSE;
+    }
+    PRBool success = tryCharset(extract);
+    nsHtml5Portability::releaseString(extract);
+    return success;
   }
-  PRBool success = tryCharset(candidateEncoding);
-  nsHtml5Portability::releaseString(candidateEncoding);
-  contentIndex = -1;
-  charsetIndex = -1;
-  return success;
+  return PR_FALSE;
 }
 
 void
 nsHtml5MetaScanner::initializeStatics()
 {
 }
 
 void
--- a/parser/html/nsHtml5MetaScanner.h
+++ b/parser/html/nsHtml5MetaScanner.h
@@ -58,34 +58,55 @@ class nsHtml5HtmlAttributes;
 class nsHtml5UTF16Buffer;
 class nsHtml5StateSnapshot;
 class nsHtml5Portability;
 
 
 class nsHtml5MetaScanner
 {
   private:
-    static PRUnichar CHARSET[];
-    static PRUnichar CONTENT[];
+    static staticJArray<PRUnichar,PRInt32> CHARSET;
+    static staticJArray<PRUnichar,PRInt32> CONTENT;
+    static staticJArray<PRUnichar,PRInt32> HTTP_EQUIV;
+    static staticJArray<PRUnichar,PRInt32> CONTENT_TYPE;
   protected:
     nsHtml5ByteReadable* readable;
   private:
     PRInt32 metaState;
     PRInt32 contentIndex;
     PRInt32 charsetIndex;
+    PRInt32 httpEquivIndex;
+    PRInt32 contentTypeIndex;
   protected:
     PRInt32 stateSave;
   private:
     PRInt32 strBufLen;
     autoJArray<PRUnichar,PRInt32> strBuf;
+    nsString* content;
+    nsString* charset;
+    PRInt32 httpEquivState;
+  public:
+    nsHtml5MetaScanner();
+    ~nsHtml5MetaScanner();
   protected:
     void stateLoop(PRInt32 state);
   private:
+    void handleCharInAttributeValue(PRInt32 c);
+    inline PRInt32 toAsciiLowerCase(PRInt32 c)
+    {
+      if (c >= 'A' && c <= 'Z') {
+        return c + 0x20;
+      }
+      return c;
+    }
+
     void addToBuffer(PRInt32 c);
-    PRBool tryCharset();
+    void handleAttributeValue();
+    PRBool handleTag();
+    PRBool handleTagInner();
   protected:
     PRBool tryCharset(nsString* encoding);
   public:
     static void initializeStatics();
     static void releaseStatics();
 
 #include "nsHtml5MetaScannerHSupplement.h"
 };
@@ -110,12 +131,15 @@ class nsHtml5MetaScanner
 #define NS_HTML5META_SCANNER_MARKUP_DECLARATION_OPEN 13
 #define NS_HTML5META_SCANNER_MARKUP_DECLARATION_HYPHEN 14
 #define NS_HTML5META_SCANNER_COMMENT_START 15
 #define NS_HTML5META_SCANNER_COMMENT_START_DASH 16
 #define NS_HTML5META_SCANNER_COMMENT 17
 #define NS_HTML5META_SCANNER_COMMENT_END_DASH 18
 #define NS_HTML5META_SCANNER_COMMENT_END 19
 #define NS_HTML5META_SCANNER_SELF_CLOSING_START_TAG 20
+#define NS_HTML5META_SCANNER_HTTP_EQUIV_NOT_SEEN 0
+#define NS_HTML5META_SCANNER_HTTP_EQUIV_CONTENT_TYPE 1
+#define NS_HTML5META_SCANNER_HTTP_EQUIV_OTHER 2
 
 
 #endif
 
--- a/parser/html/nsHtml5MetaScannerCppSupplement.h
+++ b/parser/html/nsHtml5MetaScannerCppSupplement.h
@@ -38,33 +38,16 @@
 #include "nsICharsetConverterManager.h"
 #include "nsServiceManagerUtils.h"
 #include "nsICharsetAlias.h"
 #include "nsEncoderDecoderUtils.h"
 #include "nsTraceRefcnt.h"
 
 static NS_DEFINE_CID(kCharsetAliasCID, NS_CHARSETALIAS_CID);
 
-nsHtml5MetaScanner::nsHtml5MetaScanner()
- : readable(nsnull),
-   metaState(NS_HTML5META_SCANNER_NO),
-   contentIndex(-1),
-   charsetIndex(-1),
-   stateSave(NS_HTML5META_SCANNER_DATA),
-   strBufLen(0),
-   strBuf(jArray<PRUnichar,PRInt32>::newJArray(36))
-{
-  MOZ_COUNT_CTOR(nsHtml5MetaScanner);
-}
-
-nsHtml5MetaScanner::~nsHtml5MetaScanner()
-{
-  MOZ_COUNT_DTOR(nsHtml5MetaScanner);
-}
-
 void
 nsHtml5MetaScanner::sniff(nsHtml5ByteReadable* bytes, nsIUnicodeDecoder** decoder, nsACString& charset)
 {
   readable = bytes;
   stateLoop(stateSave);
   readable = nsnull;
   if (mUnicodeDecoder) {
     mUnicodeDecoder.forget(decoder);
@@ -81,17 +64,16 @@ nsHtml5MetaScanner::tryCharset(nsString*
   nsresult res = NS_OK;
   nsCOMPtr<nsICharsetConverterManager> convManager = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &res);
   if (NS_FAILED(res)) {
     NS_ERROR("Could not get CharsetConverterManager service.");
     return PR_FALSE;
   }
   nsCAutoString encoding;
   CopyUTF16toUTF8(*charset, encoding);
-  // XXX spec says only UTF-16
   if (encoding.LowerCaseEqualsLiteral("utf-16") ||
       encoding.LowerCaseEqualsLiteral("utf-16be") ||
       encoding.LowerCaseEqualsLiteral("utf-16le")) {
     mCharset.Assign("UTF-8");
     res = convManager->GetUnicodeDecoderRaw(mCharset.get(), getter_AddRefs(mUnicodeDecoder));
     if (NS_FAILED(res)) {
       NS_ERROR("Could not get decoder for UTF-8.");
       return PR_FALSE;
--- a/parser/html/nsHtml5MetaScannerHSupplement.h
+++ b/parser/html/nsHtml5MetaScannerHSupplement.h
@@ -38,10 +38,8 @@
 private:
   nsCOMPtr<nsIUnicodeDecoder>  mUnicodeDecoder;
   nsCString mCharset;
   inline PRInt32 read() {
     return readable->read();
   }
 public:
   void sniff(nsHtml5ByteReadable* bytes, nsIUnicodeDecoder** decoder, nsACString& charset);
-  nsHtml5MetaScanner();
-  ~nsHtml5MetaScanner();
--- a/parser/html/nsHtml5StreamParser.cpp
+++ b/parser/html/nsHtml5StreamParser.cpp
@@ -584,17 +584,17 @@ nsHtml5StreamParser::OnStartRequest(nsIR
     // the network?
     if (!method.EqualsLiteral("GET")) {
       // This is the old Gecko behavior but the HTML5 spec disagrees.
       // Don't reparse on POST.
       mReparseForbidden = PR_TRUE;
     }
   }
   
-  if (mCharsetSource < kCharsetFromChannel) {
+  if (mCharsetSource <= kCharsetFromMetaPrescan) {
     // we aren't ready to commit to an encoding yet
     // leave converter uninstantiated for now
     return NS_OK;
   }
   
   nsCOMPtr<nsICharsetConverterManager> convManager = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = convManager->GetUnicodeDecoder(mCharset.get(), getter_AddRefs(mUnicodeDecoder));
@@ -746,88 +746,89 @@ nsHtml5StreamParser::OnDataAvailable(nsI
                                                                 data.forget(),
                                                                 totalRead);
   if (NS_FAILED(mThread->Dispatch(dataAvailable, nsIThread::DISPATCH_NORMAL))) {
     NS_WARNING("Dispatching DataAvailable event failed.");
   }
   return rv;
 }
 
-void
+PRBool
 nsHtml5StreamParser::internalEncodingDeclaration(nsString* aEncoding)
 {
   // This code needs to stay in sync with
   // nsHtml5MetaScanner::tryCharset. Unfortunately, the
   // trickery with member fields there leads to some copy-paste reuse. :-(
   NS_ASSERTION(IsParserThread(), "Wrong thread!");
   if (mCharsetSource >= kCharsetFromMetaTag) { // this threshold corresponds to "confident" in the HTML5 spec
-    return;
+    return PR_FALSE;
   }
 
   if (mReparseForbidden) {
-    return; // not reparsing even if we wanted to
+    return PR_FALSE; // not reparsing even if we wanted to
   }
 
   nsCAutoString newEncoding;
   CopyUTF16toUTF8(*aEncoding, newEncoding);
   // XXX spec says only UTF-16
   if (newEncoding.LowerCaseEqualsLiteral("utf-16") ||
       newEncoding.LowerCaseEqualsLiteral("utf-16be") ||
       newEncoding.LowerCaseEqualsLiteral("utf-16le")) {
     newEncoding.Assign("UTF-8");
   }
 
   nsresult rv = NS_OK;
   nsCOMPtr<nsICharsetAlias> calias(do_GetService(kCharsetAliasCID, &rv));
   if (NS_FAILED(rv)) {
     NS_NOTREACHED("Charset alias service not available.");
-    return;
+    return PR_FALSE;
   }
   PRBool eq;
   rv = calias->Equals(newEncoding, mCharset, &eq);
   if (NS_FAILED(rv)) {
     NS_NOTREACHED("Charset name equality check failed.");
-    return;
+    return PR_FALSE;
   }
   if (eq) {
     mCharsetSource = kCharsetFromMetaTag; // become confident
-    return;
+    return PR_FALSE;
   }
   
   // XXX check HTML5 non-IANA aliases here
   
   nsCAutoString preferred;
   
   rv = calias->GetPreferred(newEncoding, preferred);
   if (NS_FAILED(rv)) {
     // the encoding name is bogus
-    return;
+    return PR_FALSE;
   }
   
   if (preferred.LowerCaseEqualsLiteral("utf-16") ||
       preferred.LowerCaseEqualsLiteral("utf-16be") ||
       preferred.LowerCaseEqualsLiteral("utf-16le") ||
       preferred.LowerCaseEqualsLiteral("utf-32") ||
       preferred.LowerCaseEqualsLiteral("utf-32be") ||
       preferred.LowerCaseEqualsLiteral("utf-32le") ||
       preferred.LowerCaseEqualsLiteral("utf-7") ||
       preferred.LowerCaseEqualsLiteral("jis_x0212-1990") ||
       preferred.LowerCaseEqualsLiteral("x-jis0208") ||
       preferred.LowerCaseEqualsLiteral("x-imap4-modified-utf7") ||
       preferred.LowerCaseEqualsLiteral("x-user-defined")) {
     // Not a rough ASCII superset
-    return;
+    return PR_FALSE;
   }
 
   mTreeBuilder->NeedsCharsetSwitchTo(preferred);
   FlushTreeOpsAndDisarmTimer();
   Interrupt();
   // the tree op executor will cause the stream parser to terminate
   // if the charset switch request is accepted or it'll uninterrupt 
   // if the request failed.
+  return PR_TRUE;
 }
 
 void
 nsHtml5StreamParser::FlushTreeOpsAndDisarmTimer()
 {
   NS_ASSERTION(IsParserThread(), "Wrong thread!");
   if (mFlushTimerArmed) {
     // avoid calling Cancel if the flush timer isn't armed to avoid acquiring
--- a/parser/html/nsHtml5StreamParser.h
+++ b/parser/html/nsHtml5StreamParser.h
@@ -133,17 +133,17 @@ class nsHtml5StreamParser : public nsISt
      */
     NS_IMETHOD Notify(const char* aCharset, nsDetectionConfident aConf);
 
     // EncodingDeclarationHandler
     // http://hg.mozilla.org/projects/htmlparser/file/tip/src/nu/validator/htmlparser/common/EncodingDeclarationHandler.java
     /**
      * Tree builder uses this to report a late <meta charset>
      */
-    void internalEncodingDeclaration(nsString* aEncoding);
+    PRBool internalEncodingDeclaration(nsString* aEncoding);
 
     // Not from an external interface
 
     /**
      *  Call this method once you've created a parser, and want to instruct it
      *  about what charset to load
      *
      *  @param   aCharset the charset of a document
--- a/parser/html/nsHtml5Tokenizer.cpp
+++ b/parser/html/nsHtml5Tokenizer.cpp
@@ -3675,22 +3675,23 @@ nsHtml5Tokenizer::emitDoctypeToken(PRInt
   nsHtml5Portability::releaseLocal(doctypeName);
   doctypeName = nsnull;
   nsHtml5Portability::releaseString(publicIdentifier);
   publicIdentifier = nsnull;
   nsHtml5Portability::releaseString(systemIdentifier);
   systemIdentifier = nsnull;
 }
 
-void 
+PRBool 
 nsHtml5Tokenizer::internalEncodingDeclaration(nsString* internalCharset)
 {
   if (encodingDeclarationHandler) {
-    encodingDeclarationHandler->internalEncodingDeclaration(internalCharset);
+    return encodingDeclarationHandler->internalEncodingDeclaration(internalCharset);
   }
+  return PR_FALSE;
 }
 
 void 
 nsHtml5Tokenizer::emitOrAppendTwo(const PRUnichar* val, PRInt32 returnState)
 {
   if ((returnState & NS_HTML5TOKENIZER_DATA_AND_RCDATA_MASK)) {
     appendLongStrBuf(val[0]);
     appendLongStrBuf(val[1]);
--- a/parser/html/nsHtml5Tokenizer.h
+++ b/parser/html/nsHtml5Tokenizer.h
@@ -270,17 +270,17 @@ class nsHtml5Tokenizer
     void emitDoctypeToken(PRInt32 pos);
   protected:
     inline PRUnichar checkChar(PRUnichar* buf, PRInt32 pos)
     {
       return buf[pos];
     }
 
   public:
-    void internalEncodingDeclaration(nsString* internalCharset);
+    PRBool internalEncodingDeclaration(nsString* internalCharset);
   private:
     void emitOrAppendTwo(const PRUnichar* val, PRInt32 returnState);
     void emitOrAppendOne(const PRUnichar* val, PRInt32 returnState);
   public:
     void end();
     void requestSuspension();
     PRBool isInDataState();
     void resetToDataState();
--- a/parser/html/nsHtml5TreeBuilder.cpp
+++ b/parser/html/nsHtml5TreeBuilder.cpp
@@ -2019,31 +2019,36 @@ nsHtml5TreeBuilder::extractCharsetFromCo
     charset = nsHtml5Portability::newStringFromBuffer(buffer, start, end - start);
   }
   return charset;
 }
 
 void 
 nsHtml5TreeBuilder::checkMetaCharset(nsHtml5HtmlAttributes* attributes)
 {
-  nsString* content = attributes->getValue(nsHtml5AttributeName::ATTR_CONTENT);
-  nsString* internalCharsetLegacy = nsnull;
-  if (content) {
-    internalCharsetLegacy = nsHtml5TreeBuilder::extractCharsetFromContent(content);
+  nsString* charset = attributes->getValue(nsHtml5AttributeName::ATTR_CHARSET);
+  if (charset) {
+    if (tokenizer->internalEncodingDeclaration(charset)) {
+      requestSuspension();
+      return;
+    }
+    return;
   }
-  if (!internalCharsetLegacy) {
-    nsString* internalCharsetHtml5 = attributes->getValue(nsHtml5AttributeName::ATTR_CHARSET);
-    if (internalCharsetHtml5) {
-      tokenizer->internalEncodingDeclaration(internalCharsetHtml5);
-      requestSuspension();
+  if (!nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString("content-type", attributes->getValue(nsHtml5AttributeName::ATTR_HTTP_EQUIV))) {
+    return;
+  }
+  nsString* content = attributes->getValue(nsHtml5AttributeName::ATTR_CONTENT);
+  if (content) {
+    nsString* extract = nsHtml5TreeBuilder::extractCharsetFromContent(content);
+    if (extract) {
+      if (tokenizer->internalEncodingDeclaration(extract)) {
+        requestSuspension();
+      }
     }
-  } else {
-    tokenizer->internalEncodingDeclaration(internalCharsetLegacy);
-    nsHtml5Portability::releaseString(internalCharsetLegacy);
-    requestSuspension();
+    nsHtml5Portability::releaseString(extract);
   }
 }
 
 void 
 nsHtml5TreeBuilder::endTag(nsHtml5ElementName* elementName)
 {
   flushCharacters();
   needToDropLF = PR_FALSE;
--- a/parser/htmlparser/tests/mochitest/Makefile.in
+++ b/parser/htmlparser/tests/mochitest/Makefile.in
@@ -67,16 +67,25 @@ include $(topsrcdir)/config/rules.mk
 		file_bug543062.sjs \
 		test_bug552938.html \
 		test_bug552938-2.html \
 		test_bug566879.html \
 		test_compatmode.html \
 		invalidchar.xml \
 		file_bug534293.sjs \
 		file_bug534293-slow.sjs \
+		test_bug594730.html \
+		file_bug594730-1.html \
+		file_bug594730-2.html \
+		file_bug594730-3.html \
+		file_bug594730-4.html \
+		file_bug594730-5.html \
+		file_bug594730-6.html \
+		file_bug594730-7.html \
+		file_bug594730-8.html \
 		test_bug599584.html \
 		iframe_bug599584.html \
 		$(NULL)
 
 # Disabled test due to orange on Linux
 #		test_bug568470.html \
 #		file_bug568470.sjs \
 #		file_bug568470-script.sjs \
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/mochitest/file_bug594730-1.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="Windows-1250">
+<script>parent.is("", "\u0159", "Decoded bytes should have matched the Unicode escape.");</script>
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/mochitest/file_bug594730-2.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta http-equiv="content-TYPE" content="text/html; charset=Windows-1250">
+<script>parent.is("", "\u0159", "Decoded bytes should have matched the Unicode escape.");</script>
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/mochitest/file_bug594730-3.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta content="text/html; charset=Windows-1250" http-equiv="content-TYPE">
+<script>parent.is("", "\u0159", "Decoded bytes should have matched the Unicode escape.");</script>
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/mochitest/file_bug594730-4.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta content="text/html; charset=Windows-1250">
+<script>parent.is_not("", "\u0159", "Decoded bytes should not have matched the Unicode escape.");</script>
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/mochitest/file_bug594730-5.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="Windows-1250" content="text/html; charset=Windows-1252">
+<script>parent.is("", "\u0159", "Decoded bytes should have matched the Unicode escape.");</script>
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/mochitest/file_bug594730-6.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="Windows-1250" http-equiv="Content-Type" content="text/html; charset=Windows-1252">
+<script>parent.is("", "\u0159", "Decoded bytes should have matched the Unicode escape.");</script>
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/mochitest/file_bug594730-7.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta content="text/html; charset=Windows-1250" http-equiv="Content-Type" content="text/html; charset=Windows-1252">
+<script>parent.is("", "\u0159", "Decoded bytes should have matched the Unicode escape.");</script>
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/mochitest/file_bug594730-8.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="Windows-1250" charset="Windows-1252">
+<script>parent.is("", "\u0159", "Decoded bytes should have matched the Unicode escape.");</script>
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/mochitest/test_bug594730.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=594730
+-->
+<head>
+  <title>Test for Bug 594730</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=594730">Mozilla Bug 594730</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<iframe src=file_bug594730-1.html></iframe>
+<iframe src=file_bug594730-2.html></iframe>
+<iframe src=file_bug594730-3.html></iframe>
+<iframe src=file_bug594730-4.html></iframe>
+<iframe src=file_bug594730-5.html></iframe>
+<iframe src=file_bug594730-6.html></iframe>
+<iframe src=file_bug594730-7.html></iframe>
+<iframe src=file_bug594730-8.html></iframe>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+</script>
+</pre>
+</body>
+</html>
+