Bug 1302460 - Expose a line number for each attribute. r=wchen, a=ritu FIREFOX_50_0b3_BUILD1 FIREFOX_50_0b3_RELEASE
authorHenri Sivonen <hsivonen@hsivonen.fi>
Tue, 20 Sep 2016 16:31:57 +0200
changeset 422425 6a7c1c8db5548d077c7fa36bce41af629ba52bd8
parent 422424 330d3ee35628ee7348073bfe94a64a34325c7622
child 422426 f0f71ac0db6d59e1b54b6b36779df4da3e39e78e
push id31738
push userbmo:giles@thaumas.net
push dateFri, 07 Oct 2016 20:30:41 +0000
reviewerswchen, ritu
bugs1302460
milestone50.0
Bug 1302460 - Expose a line number for each attribute. r=wchen, a=ritu
js/src/vm/Debugger.cpp
parser/html/javasrc/HtmlAttributes.java
parser/html/javasrc/Tokenizer.java
parser/html/javasrc/TreeBuilder.java
parser/html/nsHtml5Highlighter.cpp
parser/html/nsHtml5HtmlAttributes.cpp
parser/html/nsHtml5HtmlAttributes.h
parser/html/nsHtml5PlainTextUtils.cpp
parser/html/nsHtml5Tokenizer.cpp
parser/html/nsHtml5Tokenizer.h
parser/html/nsHtml5TreeBuilder.cpp
parser/html/nsHtml5ViewSourceUtils.cpp
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -4351,17 +4351,17 @@ class MOZ_STACK_CLASS Debugger::ScriptQu
             if (!script->scriptSource() || !script->scriptSource()->hasDisplayURL())
                 return;
 
             const char16_t* s = script->scriptSource()->displayURL();
             if (CompareChars(s, js_strlen(s), displayURLString) != 0)
                 return;
         }
         if (hasSource && !(source.is<ScriptSourceObject*>() &&
-                           source.as<ScriptSourceObject*>() == script->sourceObject()))
+                           source.as<ScriptSourceObject*>()->source() == script->scriptSource()))
         {
             return;
         }
 
         if (innermost) {
             /*
              * For 'innermost' queries, we don't place scripts in |vector| right
              * away; we may later find another script that is nested inside this
--- a/parser/html/javasrc/HtmlAttributes.java
+++ b/parser/html/javasrc/HtmlAttributes.java
@@ -58,16 +58,18 @@ public final class HtmlAttributes implem
     private int mode;
 
     private int length;
 
     private @Auto AttributeName[] names;
 
     private @Auto String[] values; // XXX perhaps make this @NoLength?
 
+    // CPPONLY: private @Auto int[] lines; // XXX perhaps make this @NoLength?
+
     // [NOCPP[
 
     private String idValue;
 
     private int xmlnsLength;
 
     private AttributeName[] xmlnsNames;
 
@@ -75,20 +77,22 @@ public final class HtmlAttributes implem
 
     // ]NOCPP]
 
     public HtmlAttributes(int mode) {
         this.mode = mode;
         this.length = 0;
         /*
          * The length of 5 covers covers 98.3% of elements
-         * according to Hixie
+         * according to Hixie, but lets round to the next power of two for
+         * jemalloc.
          */
-        this.names = new AttributeName[5];
-        this.values = new String[5];
+        this.names = new AttributeName[8];
+        this.values = new String[8];
+        // CPPONLY: this.lines = new int[8];
 
         // [NOCPP[
 
         this.idValue = null;
 
         this.xmlnsLength = 0;
 
         this.xmlnsNames = HtmlAttributes.EMPTY_ATTRIBUTENAMES;
@@ -194,16 +198,26 @@ public final class HtmlAttributes implem
      * @param index a valid attribute index
      * @return the attribute name at index
      */
     public AttributeName getAttributeNameNoBoundsCheck(int index) {
         // CPPONLY: assert index < length && index >= 0: "Index out of bounds";
         return names[index];
     }
 
+    // CPPONLY: /**
+    // CPPONLY: * Obtains a line number without bounds check.
+    // CPPONLY: * @param index a valid attribute index
+    // CPPONLY: * @return the line number at index or -1 if unknown
+    // CPPONLY: */
+    // CPPONLY: public int getLineNoBoundsCheck(int index) {
+    // CPPONLY: assert index < length && index >= 0: "Index out of bounds";
+    // CPPONLY: return lines[index];
+    // CPPONLY: }
+
     // [NOCPP[
     
     /**
      * Variant of <code>getQName(int index)</code> without bounds check.
      * @param index a valid attribute index
      * @return the QName at index
      */
     public @QName String getQNameNoBoundsCheck(int index) {
@@ -388,17 +402,18 @@ public final class HtmlAttributes implem
         }
     }
 
     // ]NOCPP]
 
     void addAttribute(AttributeName name, String value
             // [NOCPP[
             , XmlViolationPolicy xmlnsPolicy
-    // ]NOCPP]        
+            // ]NOCPP]
+            // CPPONLY: , int line
     ) throws SAXException {
         // [NOCPP[
         if (name == AttributeName.ID) {
             idValue = value;
         }
 
         if (name.isXmlns()) {
             if (xmlnsNames.length == xmlnsLength) {
@@ -431,19 +446,23 @@ public final class HtmlAttributes implem
             // 100% of elements according to
             // Hixie
             AttributeName[] newNames = new AttributeName[newLen];
             System.arraycopy(names, 0, newNames, 0, names.length);
             names = newNames;
             String[] newValues = new String[newLen];
             System.arraycopy(values, 0, newValues, 0, values.length);
             values = newValues;
+            // CPPONLY: int[] newLines = new int[newLen];
+            // CPPONLY: System.arraycopy(lines, 0, newLines, 0, lines.length);
+            // CPPONLY: lines = newLines;
         }
         names[length] = name;
         values[length] = value;
+        // CPPONLY: lines[length] = line;
         length++;
     }
 
     void clear(int m) {
         for (int i = 0; i < length; i++) {
             names[i].release();
             names[i] = null;
             Portability.releaseString(values[i]);
@@ -515,16 +534,17 @@ public final class HtmlAttributes implem
                 || mode == 0 || mode == 3;
         HtmlAttributes clone = new HtmlAttributes(0);
         for (int i = 0; i < length; i++) {
             clone.addAttribute(names[i].cloneAttributeName(interner),
                     Portability.newStringFromString(values[i])
                     // [NOCPP[
                     , XmlViolationPolicy.ALLOW
                     // ]NOCPP]
+                    // CPPONLY: , lines[i]
             );
         }
         // [NOCPP[
         for (int i = 0; i < xmlnsLength; i++) {
             clone.addAttribute(xmlnsNames[i], xmlnsValues[i],
                     XmlViolationPolicy.ALLOW);
         }
         // ]NOCPP]
--- a/parser/html/javasrc/Tokenizer.java
+++ b/parser/html/javasrc/Tokenizer.java
@@ -488,16 +488,23 @@ public class Tokenizer implements Locato
     private final boolean newAttributesEachTime;
 
     private boolean shouldSuspend;
 
     protected boolean confident;
 
     private int line;
 
+    /*
+     * The line number of the current attribute. First set to the line of the
+     * attribute name and if there is a value, set to the line the value
+     * started on.
+     */
+    // CPPONLY: private int attributeLine;
+
     private Interner interner;
 
     // CPPONLY: private boolean viewingXmlSource;
 
     // [NOCPP[
 
     protected LocatorImpl ampersandLocation;
 
@@ -742,16 +749,17 @@ public class Tokenizer implements Locato
                 return;
         }
     }
 
     /**
      * For C++ use only.
      */
     public void setLineNumber(int line) {
+        // CPPONLY: this.attributeLine = line; // XXX is this needed?
         this.line = line;
     }
 
     // start Locator impl
 
     /**
      * @see org.xml.sax.Locator#getLineNumber()
      */
@@ -1170,16 +1178,17 @@ public class Tokenizer implements Locato
                             + "\u201D without an explicit value seen. The attribute may be dropped by IE7.");
                 }
                 // ]NOCPP]
                 attributes.addAttribute(attributeName,
                         Portability.newEmptyString()
                         // [NOCPP[
                         , xmlnsPolicy
                 // ]NOCPP]
+                // CPPONLY: , attributeLine
                 );
                 // [NOCPP[
             }
             // ]NOCPP]
             attributeName = null; // attributeName has been adopted by the
             // |attributes| object
         }
     }
@@ -1202,16 +1211,17 @@ public class Tokenizer implements Locato
                     && attributeName.isCaseFolded()) {
                 val = newAsciiLowerCaseStringFromString(val);
             }
             // ]NOCPP]
             attributes.addAttribute(attributeName, val
             // [NOCPP[
                     , xmlnsPolicy
             // ]NOCPP]
+            // CPPONLY: , attributeLine
             );
             attributeName = null; // attributeName has been adopted by the
             // |attributes| object
         }
     }
 
     // [NOCPP[
 
@@ -1748,16 +1758,17 @@ public class Tokenizer implements Locato
                                      * U+0041 LATIN CAPITAL LETTER A through to
                                      * U+005A LATIN CAPITAL LETTER Z Set that
                                      * attribute's name to the lowercase version
                                      * of the current input character (add
                                      * 0x0020 to the character's code point)
                                      */
                                     c += 0x20;
                                 }
+                                // CPPONLY: attributeLine = line;
                                 /*
                                  * Set that attribute's name to the current
                                  * input character,
                                  */
                                 clearStrBufAndAppend(c);
                                 /*
                                  * and its value to the empty string.
                                  */
@@ -1897,36 +1908,39 @@ public class Tokenizer implements Locato
                                  * in the before attribute value state.
                                  */
                                 continue;
                             case '"':
                                 /*
                                  * U+0022 QUOTATION MARK (") Switch to the
                                  * attribute value (double-quoted) state.
                                  */
+                                // CPPONLY: attributeLine = line;
                                 clearStrBuf();
                                 state = transition(state, Tokenizer.ATTRIBUTE_VALUE_DOUBLE_QUOTED, reconsume, pos);
                                 break beforeattributevalueloop;
                             // continue stateloop;
                             case '&':
                                 /*
                                  * U+0026 AMPERSAND (&) Switch to the attribute
                                  * value (unquoted) state and reconsume this
                                  * input character.
                                  */
+                                // CPPONLY: attributeLine = line;
                                 clearStrBuf();
                                 reconsume = true;
                                 state = transition(state, Tokenizer.ATTRIBUTE_VALUE_UNQUOTED, reconsume, pos);
                                 noteUnquotedAttributeValue();
                                 continue stateloop;
                             case '\'':
                                 /*
                                  * U+0027 APOSTROPHE (') Switch to the attribute
                                  * value (single-quoted) state.
                                  */
+                                // CPPONLY: attributeLine = line;
                                 clearStrBuf();
                                 state = transition(state, Tokenizer.ATTRIBUTE_VALUE_SINGLE_QUOTED, reconsume, pos);
                                 continue stateloop;
                             case '>':
                                 /*
                                  * U+003E GREATER-THAN SIGN (>) Parse error.
                                  */
                                 errAttributeValueMissing();
@@ -1960,16 +1974,17 @@ public class Tokenizer implements Locato
                             default:
                                 // [NOCPP[
                                 errHtml4NonNameInUnquotedAttribute(c);
                                 // ]NOCPP]
                                 /*
                                  * Anything else Append the current input
                                  * character to the current attribute's value.
                                  */
+                                // CPPONLY: attributeLine = line;
                                 clearStrBufAndAppend(c);
                                 /*
                                  * Switch to the attribute value (unquoted)
                                  * state.
                                  */
 
                                 state = transition(state, Tokenizer.ATTRIBUTE_VALUE_UNQUOTED, reconsume, pos);
                                 noteUnquotedAttributeValue();
@@ -6732,16 +6747,17 @@ public class Tokenizer implements Locato
             attributes = other.attributes.cloneAttributes(interner);
         }
     }
 
     public void initializeWithoutStarting() throws SAXException {
         confident = false;
         strBuf = null;
         line = 1;
+        // CPPONLY: attributeLine = 1;
         // [NOCPP[
         html4 = false;
         metaBoundaryPassed = false;
         wantsComments = tokenHandler.wantsComments();
         if (!newAttributesEachTime) {
             attributes = new HtmlAttributes(mappingLangToXmlLang);
         }
         // ]NOCPP]
--- a/parser/html/javasrc/TreeBuilder.java
+++ b/parser/html/javasrc/TreeBuilder.java
@@ -2341,16 +2341,17 @@ public abstract class TreeBuilder<T> imp
                                 int actionIndex = attributes.getIndex(AttributeName.ACTION);
                                 if (actionIndex > -1) {
                                     formAttrs.addAttribute(
                                             AttributeName.ACTION,
                                             attributes.getValueNoBoundsCheck(actionIndex)
                                             // [NOCPP[
                                             , XmlViolationPolicy.ALLOW
                                     // ]NOCPP]
+                                            // CPPONLY: , attributes.getLineNoBoundsCheck(actionIndex)
                                     );
                                 }
                                 appendToCurrentNodeAndPushFormElementMayFoster(formAttrs);
                                 appendVoidElementToCurrentMayFoster(
                                         ElementName.HR,
                                         HtmlAttributes.EMPTY_ATTRIBUTES);
                                 appendToCurrentNodeAndPushElementMayFoster(
                                         ElementName.LABEL,
@@ -2366,30 +2367,31 @@ public abstract class TreeBuilder<T> imp
                                 HtmlAttributes inputAttributes = new HtmlAttributes(
                                         0);
                                 inputAttributes.addAttribute(
                                         AttributeName.NAME,
                                         Portability.newStringFromLiteral("isindex")
                                         // [NOCPP[
                                         , XmlViolationPolicy.ALLOW
                                 // ]NOCPP]
+                                // CPPONLY: , tokenizer.getLineNumber()
                                 );
                                 for (int i = 0; i < attributes.getLength(); i++) {
                                     AttributeName attributeQName = attributes.getAttributeNameNoBoundsCheck(i);
                                     if (AttributeName.NAME == attributeQName
                                             || AttributeName.PROMPT == attributeQName) {
                                         attributes.releaseValue(i);
                                     } else if (AttributeName.ACTION != attributeQName) {
                                         inputAttributes.addAttribute(
                                                 attributeQName,
                                                 attributes.getValueNoBoundsCheck(i)
                                                 // [NOCPP[
                                                 , XmlViolationPolicy.ALLOW
                                         // ]NOCPP]
-
+                                        // CPPONLY: , attributes.getLineNoBoundsCheck(i)
                                         );
                                     }
                                 }
                                 attributes.clearWithoutReleasingContents();
                                 appendVoidElementToCurrentMayFoster(
                                         "input",
                                         inputAttributes, formPointer);
                                 pop(); // label
--- a/parser/html/nsHtml5Highlighter.cpp
+++ b/parser/html/nsHtml5Highlighter.cpp
@@ -101,17 +101,17 @@ nsHtml5Highlighter::Start(const nsAutoSt
   Pop(); // link
 
   Pop(); // head
 
   Push(nsGkAtoms::body, nsHtml5ViewSourceUtils::NewBodyAttributes());
 
   nsHtml5HtmlAttributes* preAttrs = new nsHtml5HtmlAttributes(0);
   nsString* preId = new nsString(NS_LITERAL_STRING("line1"));
-  preAttrs->addAttribute(nsHtml5AttributeName::ATTR_ID, preId);
+  preAttrs->addAttribute(nsHtml5AttributeName::ATTR_ID, preId, -1);
   Push(nsGkAtoms::pre, preAttrs);
 
   StartCharacters();
 
   mOpQueue.AppendElement()->Init(eTreeOpStartLayout);
 }
 
 int32_t
--- a/parser/html/nsHtml5HtmlAttributes.cpp
+++ b/parser/html/nsHtml5HtmlAttributes.cpp
@@ -55,18 +55,19 @@
 
 #include "nsHtml5HtmlAttributes.h"
 
 nsHtml5HtmlAttributes* nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES = nullptr;
 
 nsHtml5HtmlAttributes::nsHtml5HtmlAttributes(int32_t mode)
   : mode(mode),
     length(0),
-    names(jArray<nsHtml5AttributeName*,int32_t>::newJArray(5)),
-    values(jArray<nsString*,int32_t>::newJArray(5))
+    names(jArray<nsHtml5AttributeName*,int32_t>::newJArray(8)),
+    values(jArray<nsString*,int32_t>::newJArray(8)),
+    lines(jArray<int32_t,int32_t>::newJArray(8))
 {
   MOZ_COUNT_CTOR(nsHtml5HtmlAttributes);
 }
 
 
 nsHtml5HtmlAttributes::~nsHtml5HtmlAttributes()
 {
   MOZ_COUNT_DTOR(nsHtml5HtmlAttributes);
@@ -131,30 +132,41 @@ nsHtml5HtmlAttributes::getValueNoBoundsC
 
 nsHtml5AttributeName* 
 nsHtml5HtmlAttributes::getAttributeNameNoBoundsCheck(int32_t index)
 {
   MOZ_ASSERT(index < length && index >= 0, "Index out of bounds");
   return names[index];
 }
 
+int32_t 
+nsHtml5HtmlAttributes::getLineNoBoundsCheck(int32_t index)
+{
+  MOZ_ASSERT(index < length && index >= 0, "Index out of bounds");
+  return lines[index];
+}
+
 void 
-nsHtml5HtmlAttributes::addAttribute(nsHtml5AttributeName* name, nsString* value)
+nsHtml5HtmlAttributes::addAttribute(nsHtml5AttributeName* name, nsString* value, int32_t line)
 {
   if (names.length == length) {
     int32_t newLen = length << 1;
     jArray<nsHtml5AttributeName*,int32_t> newNames = jArray<nsHtml5AttributeName*,int32_t>::newJArray(newLen);
     nsHtml5ArrayCopy::arraycopy(names, newNames, names.length);
     names = newNames;
     jArray<nsString*,int32_t> newValues = jArray<nsString*,int32_t>::newJArray(newLen);
     nsHtml5ArrayCopy::arraycopy(values, newValues, values.length);
     values = newValues;
+    jArray<int32_t,int32_t> newLines = jArray<int32_t,int32_t>::newJArray(newLen);
+    nsHtml5ArrayCopy::arraycopy(lines, newLines, lines.length);
+    lines = newLines;
   }
   names[length] = name;
   values[length] = value;
+  lines[length] = line;
   length++;
 }
 
 void 
 nsHtml5HtmlAttributes::clear(int32_t m)
 {
   for (int32_t i = 0; i < length; i++) {
     names[i]->release();
@@ -206,17 +218,17 @@ nsHtml5HtmlAttributes::adjustForSvg()
 }
 
 nsHtml5HtmlAttributes* 
 nsHtml5HtmlAttributes::cloneAttributes(nsHtml5AtomTable* interner)
 {
   MOZ_ASSERT((!length) || !mode || mode == 3);
   nsHtml5HtmlAttributes* clone = new nsHtml5HtmlAttributes(0);
   for (int32_t i = 0; i < length; i++) {
-    clone->addAttribute(names[i]->cloneAttributeName(interner), nsHtml5Portability::newStringFromString(values[i]));
+    clone->addAttribute(names[i]->cloneAttributeName(interner), nsHtml5Portability::newStringFromString(values[i]), lines[i]);
   }
   return clone;
 }
 
 bool 
 nsHtml5HtmlAttributes::equalsAnother(nsHtml5HtmlAttributes* other)
 {
   MOZ_ASSERT(!mode || mode == 3, "Trying to compare attributes in foreign content.");
--- a/parser/html/nsHtml5HtmlAttributes.h
+++ b/parser/html/nsHtml5HtmlAttributes.h
@@ -60,28 +60,30 @@ class nsHtml5HtmlAttributes
 {
   public:
     static nsHtml5HtmlAttributes* EMPTY_ATTRIBUTES;
   private:
     int32_t mode;
     int32_t length;
     autoJArray<nsHtml5AttributeName*,int32_t> names;
     autoJArray<nsString*,int32_t> values;
+    autoJArray<int32_t,int32_t> lines;
   public:
     explicit nsHtml5HtmlAttributes(int32_t mode);
     ~nsHtml5HtmlAttributes();
     int32_t getIndex(nsHtml5AttributeName* name);
     nsString* getValue(nsHtml5AttributeName* name);
     int32_t getLength();
     nsIAtom* getLocalNameNoBoundsCheck(int32_t index);
     int32_t getURINoBoundsCheck(int32_t index);
     nsIAtom* getPrefixNoBoundsCheck(int32_t index);
     nsString* getValueNoBoundsCheck(int32_t index);
     nsHtml5AttributeName* getAttributeNameNoBoundsCheck(int32_t index);
-    void addAttribute(nsHtml5AttributeName* name, nsString* value);
+    int32_t getLineNoBoundsCheck(int32_t index);
+    void addAttribute(nsHtml5AttributeName* name, nsString* value, int32_t line);
     void clear(int32_t m);
     void releaseValue(int32_t i);
     void clearWithoutReleasingContents();
     bool contains(nsHtml5AttributeName* name);
     void adjustForMath();
     void adjustForSvg();
     nsHtml5HtmlAttributes* cloneAttributes(nsHtml5AtomTable* interner);
     bool equalsAnother(nsHtml5HtmlAttributes* other);
--- a/parser/html/nsHtml5PlainTextUtils.cpp
+++ b/parser/html/nsHtml5PlainTextUtils.cpp
@@ -10,31 +10,31 @@
 #include "mozilla/Preferences.h"
 
 // static
 nsHtml5HtmlAttributes*
 nsHtml5PlainTextUtils::NewLinkAttributes()
 {
   nsHtml5HtmlAttributes* linkAttrs = new nsHtml5HtmlAttributes(0);
   nsString* rel = new nsString(NS_LITERAL_STRING("alternate stylesheet"));
-  linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_REL, rel);
+  linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_REL, rel, -1);
   nsString* type = new nsString(NS_LITERAL_STRING("text/css"));
-  linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_TYPE, type);
+  linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_TYPE, type, -1);
   nsString* href = new nsString(
       NS_LITERAL_STRING("resource://gre-resources/plaintext.css"));
-  linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_HREF, href);
+  linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_HREF, href, -1);
 
   nsresult rv;
   nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
   NS_ASSERTION(NS_SUCCEEDED(rv) && bundleService, "The bundle service could not be loaded");
   nsCOMPtr<nsIStringBundle> bundle;
   rv = bundleService->CreateBundle("chrome://global/locale/browser.properties",
                                    getter_AddRefs(bundle));
   NS_ASSERTION(NS_SUCCEEDED(rv) && bundle, "chrome://global/locale/browser.properties could not be loaded");
   nsXPIDLString title;
   if (bundle) {
     bundle->GetStringFromName(u"plainText.wordWrap", getter_Copies(title));
   }
 
   nsString* titleCopy = new nsString(title);
-  linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_TITLE, titleCopy);
+  linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_TITLE, titleCopy, -1);
   return linkAttrs;
 }
--- a/parser/html/nsHtml5Tokenizer.cpp
+++ b/parser/html/nsHtml5Tokenizer.cpp
@@ -194,16 +194,17 @@ nsHtml5Tokenizer::endTagExpectationToArr
       return;
     }
   }
 }
 
 void 
 nsHtml5Tokenizer::setLineNumber(int32_t line)
 {
+  this->attributeLine = line;
   this->line = line;
 }
 
 nsHtml5HtmlAttributes* 
 nsHtml5Tokenizer::emptyAttributes()
 {
   return nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES;
 }
@@ -330,30 +331,30 @@ nsHtml5Tokenizer::attributeNameComplete(
   }
 }
 
 void 
 nsHtml5Tokenizer::addAttributeWithoutValue()
 {
 
   if (attributeName) {
-    attributes->addAttribute(attributeName, nsHtml5Portability::newEmptyString());
+    attributes->addAttribute(attributeName, nsHtml5Portability::newEmptyString(), attributeLine);
     attributeName = nullptr;
   }
 }
 
 void 
 nsHtml5Tokenizer::addAttributeWithValue()
 {
   if (attributeName) {
     nsString* val = strBufToString();
     if (mViewSource) {
       mViewSource->MaybeLinkifyAttributeValue(attributeName, val);
     }
-    attributes->addAttribute(attributeName, val);
+    attributes->addAttribute(attributeName, val, attributeLine);
     attributeName = nullptr;
   }
 }
 
 void 
 nsHtml5Tokenizer::start()
 {
   initializeWithoutStarting();
@@ -615,16 +616,17 @@ nsHtml5Tokenizer::stateLoop(int32_t stat
               if (P::reportErrors) {
                 errBadCharBeforeAttributeNameOrNull(c);
               }
             }
             default: {
               if (c >= 'A' && c <= 'Z') {
                 c += 0x20;
               }
+              attributeLine = line;
               clearStrBufAndAppend(c);
               state = P::transition(mViewSource, NS_HTML5TOKENIZER_ATTRIBUTE_NAME, reconsume, pos);
               NS_HTML5_BREAK(beforeattributenameloop);
             }
           }
         }
         beforeattributenameloop_end: ;
       }
@@ -707,28 +709,31 @@ nsHtml5Tokenizer::stateLoop(int32_t stat
               silentLineFeed();
             }
             case ' ':
             case '\t':
             case '\f': {
               continue;
             }
             case '\"': {
+              attributeLine = line;
               clearStrBuf();
               state = P::transition(mViewSource, NS_HTML5TOKENIZER_ATTRIBUTE_VALUE_DOUBLE_QUOTED, reconsume, pos);
               NS_HTML5_BREAK(beforeattributevalueloop);
             }
             case '&': {
+              attributeLine = line;
               clearStrBuf();
               reconsume = true;
               state = P::transition(mViewSource, NS_HTML5TOKENIZER_ATTRIBUTE_VALUE_UNQUOTED, reconsume, pos);
 
               NS_HTML5_CONTINUE(stateloop);
             }
             case '\'': {
+              attributeLine = line;
               clearStrBuf();
               state = P::transition(mViewSource, NS_HTML5TOKENIZER_ATTRIBUTE_VALUE_SINGLE_QUOTED, reconsume, pos);
               NS_HTML5_CONTINUE(stateloop);
             }
             case '>': {
               if (P::reportErrors) {
                 errAttributeValueMissing();
               }
@@ -745,16 +750,17 @@ nsHtml5Tokenizer::stateLoop(int32_t stat
             case '<':
             case '=':
             case '`': {
               if (P::reportErrors) {
                 errLtOrEqualsOrGraveInUnquotedAttributeOrNull(c);
               }
             }
             default: {
+              attributeLine = line;
               clearStrBufAndAppend(c);
               state = P::transition(mViewSource, NS_HTML5TOKENIZER_ATTRIBUTE_VALUE_UNQUOTED, reconsume, pos);
 
               NS_HTML5_CONTINUE(stateloop);
             }
           }
         }
         beforeattributevalueloop_end: ;
@@ -4038,16 +4044,17 @@ nsHtml5Tokenizer::loadState(nsHtml5Token
 }
 
 void 
 nsHtml5Tokenizer::initializeWithoutStarting()
 {
   confident = false;
   strBuf = nullptr;
   line = 1;
+  attributeLine = 1;
   resetToDataState();
 }
 
 void 
 nsHtml5Tokenizer::setEncodingDeclarationHandler(nsHtml5StreamParser* encodingDeclarationHandler)
 {
   this->encodingDeclarationHandler = encodingDeclarationHandler;
 }
--- a/parser/html/nsHtml5Tokenizer.h
+++ b/parser/html/nsHtml5Tokenizer.h
@@ -130,16 +130,17 @@ class nsHtml5Tokenizer
     nsString* systemIdentifier;
     nsHtml5HtmlAttributes* attributes;
     bool newAttributesEachTime;
     bool shouldSuspend;
   protected:
     bool confident;
   private:
     int32_t line;
+    int32_t attributeLine;
     nsHtml5AtomTable* interner;
     bool viewingXmlSource;
   public:
     nsHtml5Tokenizer(nsHtml5TreeBuilder* tokenHandler, bool viewingXmlSource);
     void setInterner(nsHtml5AtomTable* interner);
     void initLocation(nsString* newPublicId, nsString* newSystemId);
     bool isViewingXmlSource();
     void setStateAndEndTagExpectation(int32_t specialTokenizerState, nsIAtom* endTagExpectation);
--- a/parser/html/nsHtml5TreeBuilder.cpp
+++ b/parser/html/nsHtml5TreeBuilder.cpp
@@ -1238,36 +1238,36 @@ nsHtml5TreeBuilder::startTag(nsHtml5Elem
               errIsindex();
               if (!!formPointer && !isTemplateContents()) {
                 NS_HTML5_BREAK(starttagloop);
               }
               implicitlyCloseP();
               nsHtml5HtmlAttributes* formAttrs = new nsHtml5HtmlAttributes(0);
               int32_t actionIndex = attributes->getIndex(nsHtml5AttributeName::ATTR_ACTION);
               if (actionIndex > -1) {
-                formAttrs->addAttribute(nsHtml5AttributeName::ATTR_ACTION, attributes->getValueNoBoundsCheck(actionIndex));
+                formAttrs->addAttribute(nsHtml5AttributeName::ATTR_ACTION, attributes->getValueNoBoundsCheck(actionIndex), attributes->getLineNoBoundsCheck(actionIndex));
               }
               appendToCurrentNodeAndPushFormElementMayFoster(formAttrs);
               appendVoidElementToCurrentMayFoster(nsHtml5ElementName::ELT_HR, nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
               appendToCurrentNodeAndPushElementMayFoster(nsHtml5ElementName::ELT_LABEL, nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
               int32_t promptIndex = attributes->getIndex(nsHtml5AttributeName::ATTR_PROMPT);
               if (promptIndex > -1) {
                 autoJArray<char16_t,int32_t> prompt = nsHtml5Portability::newCharArrayFromString(attributes->getValueNoBoundsCheck(promptIndex));
                 appendCharacters(stack[currentPtr]->node, prompt, 0, prompt.length);
               } else {
                 appendIsindexPrompt(stack[currentPtr]->node);
               }
               nsHtml5HtmlAttributes* inputAttributes = new nsHtml5HtmlAttributes(0);
-              inputAttributes->addAttribute(nsHtml5AttributeName::ATTR_NAME, nsHtml5Portability::newStringFromLiteral("isindex"));
+              inputAttributes->addAttribute(nsHtml5AttributeName::ATTR_NAME, nsHtml5Portability::newStringFromLiteral("isindex"), tokenizer->getLineNumber());
               for (int32_t i = 0; i < attributes->getLength(); i++) {
                 nsHtml5AttributeName* attributeQName = attributes->getAttributeNameNoBoundsCheck(i);
                 if (nsHtml5AttributeName::ATTR_NAME == attributeQName || nsHtml5AttributeName::ATTR_PROMPT == attributeQName) {
                   attributes->releaseValue(i);
                 } else if (nsHtml5AttributeName::ATTR_ACTION != attributeQName) {
-                  inputAttributes->addAttribute(attributeQName, attributes->getValueNoBoundsCheck(i));
+                  inputAttributes->addAttribute(attributeQName, attributes->getValueNoBoundsCheck(i), attributes->getLineNoBoundsCheck(i));
                 }
               }
               attributes->clearWithoutReleasingContents();
               appendVoidElementToCurrentMayFoster(nsHtml5Atoms::input, inputAttributes, formPointer);
               pop();
               appendVoidElementToCurrentMayFoster(nsHtml5ElementName::ELT_HR, nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
               pop();
               if (!isTemplateContents()) {
--- a/parser/html/nsHtml5ViewSourceUtils.cpp
+++ b/parser/html/nsHtml5ViewSourceUtils.cpp
@@ -9,45 +9,45 @@
 #include "mozilla/UniquePtr.h"
 
 // static
 nsHtml5HtmlAttributes*
 nsHtml5ViewSourceUtils::NewBodyAttributes()
 {
   nsHtml5HtmlAttributes* bodyAttrs = new nsHtml5HtmlAttributes(0);
   auto id = MakeUnique<nsString>(NS_LITERAL_STRING("viewsource"));
-  bodyAttrs->addAttribute(nsHtml5AttributeName::ATTR_ID, id.release());
+  bodyAttrs->addAttribute(nsHtml5AttributeName::ATTR_ID, id.release(), -1);
 
   auto klass = MakeUnique<nsString>();
   if (mozilla::Preferences::GetBool("view_source.wrap_long_lines", true)) {
     klass->Append(NS_LITERAL_STRING("wrap "));
   }
   if (mozilla::Preferences::GetBool("view_source.syntax_highlight", true)) {
     klass->Append(NS_LITERAL_STRING("highlight"));
   }
   if (!klass->IsEmpty()) {
-    bodyAttrs->addAttribute(nsHtml5AttributeName::ATTR_CLASS, klass.release());
+    bodyAttrs->addAttribute(nsHtml5AttributeName::ATTR_CLASS, klass.release(), -1);
   }
 
   int32_t tabSize = mozilla::Preferences::GetInt("view_source.tab_size", 4);
   if (tabSize > 0) {
     auto style = MakeUnique<nsString>(NS_LITERAL_STRING("-moz-tab-size: "));
     style->AppendInt(tabSize);
-    bodyAttrs->addAttribute(nsHtml5AttributeName::ATTR_STYLE, style.release());
+    bodyAttrs->addAttribute(nsHtml5AttributeName::ATTR_STYLE, style.release(), -1);
   }
 
   return bodyAttrs;
 }
 
 // static
 nsHtml5HtmlAttributes*
 nsHtml5ViewSourceUtils::NewLinkAttributes()
 {
   nsHtml5HtmlAttributes* linkAttrs = new nsHtml5HtmlAttributes(0);
   nsString* rel = new nsString(NS_LITERAL_STRING("stylesheet"));
-  linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_REL, rel);
+  linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_REL, rel, -1);
   nsString* type = new nsString(NS_LITERAL_STRING("text/css"));
-  linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_TYPE, type);
+  linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_TYPE, type, -1);
   nsString* href = new nsString(
       NS_LITERAL_STRING("resource://gre-resources/viewsource.css"));
-  linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_HREF, href);
+  linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_HREF, href, -1);
   return linkAttrs;
 }