Bug 772869. Make getOwnPropertyNames work correctly for WebIDL proxy bindings. r=peterv,ms2ger
authorBoris Zbarsky <bzbarsky@mit.edu>
Mon, 05 Nov 2012 11:58:03 -0500
changeset 112325 9bb44a0caae4c4d1db19cd7fabe541915e71d8a0
parent 112324 af47a345a5be1d72bdb56c524ed98d446bd30864
child 112326 b356ecf4086ca17ad4ed6bbc515eac91b43aaacb
push id23812
push useremorley@mozilla.com
push dateTue, 06 Nov 2012 14:01:34 +0000
treeherdermozilla-central@f4aeed115e54 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv, ms2ger
bugs772869
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 772869. Make getOwnPropertyNames work correctly for WebIDL proxy bindings. r=peterv,ms2ger
content/base/src/nsContentList.cpp
content/base/src/nsContentList.h
content/base/src/nsDOMLists.h
content/html/content/public/nsIHTMLCollection.h
content/html/content/src/HTMLPropertiesCollection.cpp
content/html/content/src/HTMLPropertiesCollection.h
content/html/content/src/nsHTMLFormElement.cpp
content/html/content/src/nsHTMLSelectElement.cpp
content/html/content/src/nsHTMLSelectElement.h
content/html/content/src/nsHTMLTableElement.cpp
content/html/content/test/Makefile.in
content/html/content/test/test_formelements.html
content/html/content/test/test_htmlcollection.html
content/html/content/test/test_named_options.html
content/html/content/test/test_rowscollection.html
dom/bindings/Codegen.py
dom/bindings/DOMJSProxyHandler.cpp
dom/bindings/DOMJSProxyHandler.h
dom/bindings/test/TestBindingHeader.h
dom/imptests/failures/webapps/DOMCore/tests/submissions/Ms2ger/Makefile.in
dom/imptests/failures/webapps/DOMCore/tests/submissions/Ms2ger/test_Element-children.html.json
dom/imptests/html/tests/submission/Opera/microdata/test_001.html
--- a/content/base/src/nsContentList.cpp
+++ b/content/base/src/nsContentList.cpp
@@ -17,16 +17,17 @@
 #include "nsGenericElement.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsContentUtils.h"
 #include "nsCCUncollectableMarker.h"
 #include "nsGkAtoms.h"
 #include "mozilla/dom/HTMLCollectionBinding.h"
 #include "mozilla/dom/NodeListBinding.h"
 #include "mozilla/Likely.h"
+#include "nsGenericHTMLElement.h"
 
 // Form related includes
 #include "nsIDOMHTMLFormElement.h"
 
 #include "pldhash.h"
 
 #ifdef DEBUG_CONTENT_LIST
 #include "nsIContentIterator.h"
@@ -595,16 +596,52 @@ nsContentList::NamedItem(const nsAString
                               name, eCaseMatters))) {
       return content;
     }
   }
 
   return nullptr;
 }
 
+void
+nsContentList::GetSupportedNames(nsTArray<nsString>& aNames)
+{
+  BringSelfUpToDate(true);
+
+  nsAutoTArray<nsIAtom*, 8> atoms;
+  for (uint32_t i = 0; i < mElements.Length(); ++i) {
+    nsIContent *content = mElements.ElementAt(i);
+    nsGenericHTMLElement* el = nsGenericHTMLElement::FromContent(content);
+    if (el) {
+      // XXXbz should we be checking for particular tags here?  How
+      // stable is this part of the spec?
+      // Note: nsINode::HasName means the name is exposed on the document,
+      // which is false for options, so we don't check it here.
+      const nsAttrValue* val = el->GetParsedAttr(nsGkAtoms::name);
+      if (val && val->Type() == nsAttrValue::eAtom) {
+        nsIAtom* name = val->GetAtomValue();
+        if (!atoms.Contains(name)) {
+          atoms.AppendElement(name);
+        }
+      }
+    }
+    if (content->HasID()) {
+      nsIAtom* id = content->GetID();
+      if (!atoms.Contains(id)) {
+        atoms.AppendElement(id);
+      }
+    }
+  }
+
+  aNames.SetCapacity(atoms.Length());
+  for (uint32_t i = 0; i < atoms.Length(); ++i) {
+    aNames.AppendElement(nsDependentAtomString(atoms[i]));
+  }
+}
+
 int32_t
 nsContentList::IndexOf(nsIContent *aContent, bool aDoFlush)
 {
   BringSelfUpToDate(aDoFlush);
     
   return mElements.IndexOf(aContent);
 }
 
--- a/content/base/src/nsContentList.h
+++ b/content/base/src/nsContentList.h
@@ -273,16 +273,17 @@ public:
   {
     return mRootNode;
   }
 
   virtual nsIContent* Item(uint32_t aIndex);
   virtual nsGenericElement* GetElementAt(uint32_t index);
   virtual JSObject* NamedItem(JSContext* cx, const nsAString& name,
                               mozilla::ErrorResult& error);
+  virtual void GetSupportedNames(nsTArray<nsString>& aNames);
 
   // nsContentList public methods
   NS_HIDDEN_(uint32_t) Length(bool aDoFlush);
   NS_HIDDEN_(nsIContent*) Item(uint32_t aIndex, bool aDoFlush);
   NS_HIDDEN_(nsIContent*) NamedItem(const nsAString& aName, bool aDoFlush);
 
   // nsIMutationObserver
   NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
--- a/content/base/src/nsDOMLists.h
+++ b/content/base/src/nsDOMLists.h
@@ -28,13 +28,18 @@ public:
     return mNames.AppendElement(aName) != nullptr;
   }
 
   void Clear()
   {
     mNames.Clear();
   }
 
+  void CopyList(nsTArray<nsString>& aNames)
+  {
+    aNames = mNames;
+  }
+
 private:
   nsTArray<nsString> mNames;
 };
 
 #endif /* nsDOMLists_h___ */
--- a/content/html/content/public/nsIHTMLCollection.h
+++ b/content/html/content/public/nsIHTMLCollection.h
@@ -59,13 +59,15 @@ public:
                               mozilla::ErrorResult& error) = 0;
   JSObject* NamedGetter(JSContext* cx, const nsAString& name,
                         bool& found, mozilla::ErrorResult& error)
   {
     JSObject* namedItem = NamedItem(cx, name, error);
     found = !!namedItem;
     return namedItem;
   }
+
+  virtual void GetSupportedNames(nsTArray<nsString>& aNames) = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIHTMLCollection, NS_IHTMLCOLLECTION_IID)
 
 #endif /* nsIHTMLCollection_h___ */
--- a/content/html/content/src/HTMLPropertiesCollection.cpp
+++ b/content/html/content/src/HTMLPropertiesCollection.cpp
@@ -343,16 +343,23 @@ HTMLPropertiesCollection::CrawlSubtree(E
         aContent = element->GetNextNonChildNode(aElement);
       } else {          
         aContent = element->GetNextNode(aElement);
       }                 
     }                     
   }                    
 }
 
+void
+HTMLPropertiesCollection::GetSupportedNames(nsTArray<nsString>& aNames)
+{
+  EnsureFresh();
+  mNames->CopyList(aNames);
+}
+
 PropertyNodeList::PropertyNodeList(HTMLPropertiesCollection* aCollection,
                                    nsIContent* aParent, const nsAString& aName)
   : mName(aName),
     mDoc(aParent->GetCurrentDoc()),
     mCollection(aCollection),
     mParent(aParent),
     mIsDirty(true)
 {
--- a/content/html/content/src/HTMLPropertiesCollection.h
+++ b/content/html/content/src/HTMLPropertiesCollection.h
@@ -72,16 +72,17 @@ public:
     aFound = IsSupportedNamedProperty(aName);
     return aFound ? NamedItem(aName) : nullptr;
   }
   nsDOMStringList* Names()
   {
     EnsureFresh();
     return mNames;
   }
+  virtual void GetSupportedNames(nsTArray<nsString>& aNames);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIDOMHTMLPROPERTIESCOLLECTION
 
   NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
--- a/content/html/content/src/nsHTMLFormElement.cpp
+++ b/content/html/content/src/nsHTMLFormElement.cpp
@@ -95,16 +95,17 @@ public:
   virtual nsGenericElement* GetElementAt(uint32_t index);
   virtual nsINode* GetParentObject()
   {
     return mForm;
   }
 
   virtual JSObject* NamedItem(JSContext* cx, const nsAString& name,
                               mozilla::ErrorResult& error);
+  virtual void GetSupportedNames(nsTArray<nsString>& aNames);
 
   nsresult AddElementToTable(nsGenericHTMLFormElement* aChild,
                              const nsAString& aName);
   nsresult RemoveElementFromTable(nsGenericHTMLFormElement* aChild,
                                   const nsAString& aName);
   nsresult IndexOfControl(nsIFormControl* aControl,
                           int32_t* aIndex);
 
@@ -2535,8 +2536,27 @@ nsFormControlList::NamedItem(JSContext* 
   JSAutoCompartment ac(cx, wrapper);
   JS::Value v;
   if (!mozilla::dom::WrapObject(cx, wrapper, item, &v)) {
     error.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
   return &v.toObject();
 }
+
+static PLDHashOperator
+CollectNames(const nsAString& aName,
+             nsISupports* /* unused */,
+             void* aClosure)
+{
+  static_cast<nsTArray<nsString>*>(aClosure)->AppendElement(aName);
+  return PL_DHASH_NEXT;
+}
+
+void
+nsFormControlList::GetSupportedNames(nsTArray<nsString>& aNames)
+{
+  FlushPendingNotifications();
+  // Just enumerate mNameLookupTable.  This won't guarantee order, but
+  // that's OK, because the HTML5 spec doesn't define an order for
+  // this enumeration.
+  mNameLookupTable.EnumerateRead(CollectNames, &aNames);
+}
--- a/content/html/content/src/nsHTMLSelectElement.cpp
+++ b/content/html/content/src/nsHTMLSelectElement.cpp
@@ -2191,16 +2191,47 @@ nsHTMLOptionCollection::NamedItem(JSCont
   JS::Value v;
   if (!mozilla::dom::WrapObject(cx, wrapper, item, item, nullptr, &v)) {
     error.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
   return &v.toObject();
 }
 
+void
+nsHTMLOptionCollection::GetSupportedNames(nsTArray<nsString>& aNames)
+{
+  nsAutoTArray<nsIAtom*, 8> atoms;
+  for (uint32_t i = 0; i < mElements.Length(); ++i) {
+    nsHTMLOptionElement *content = mElements.ElementAt(i);
+    if (content) {
+      // Note: HasName means the names is exposed on the document,
+      // which is false for options, so we don't check it here.
+      const nsAttrValue* val = content->GetParsedAttr(nsGkAtoms::name);
+      if (val && val->Type() == nsAttrValue::eAtom) {
+        nsIAtom* name = val->GetAtomValue();
+        if (!atoms.Contains(name)) {
+          atoms.AppendElement(name);
+        }
+      }
+      if (content->HasID()) {
+        nsIAtom* id = content->GetID();
+        if (!atoms.Contains(id)) {
+          atoms.AppendElement(id);
+        }
+      }
+    }
+  }
+
+  aNames.SetCapacity(atoms.Length());
+  for (uint32_t i = 0; i < atoms.Length(); ++i) {
+    aNames.AppendElement(nsDependentAtomString(atoms[i]));
+  }
+}
+
 NS_IMETHODIMP
 nsHTMLOptionCollection::GetSelect(nsIDOMHTMLSelectElement **aReturn)
 {
   NS_IF_ADDREF(*aReturn = mSelect);
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/content/html/content/src/nsHTMLSelectElement.h
+++ b/content/html/content/src/nsHTMLSelectElement.h
@@ -136,16 +136,17 @@ public:
   void Remove(int32_t aIndex, mozilla::ErrorResult& aError);
   int32_t GetSelectedIndex(mozilla::ErrorResult& aError);
   void SetSelectedIndex(int32_t aSelectedIndex, mozilla::ErrorResult& aError);
   void IndexedSetter(uint32_t aIndex, nsIDOMHTMLOptionElement *aOption,
                      mozilla::ErrorResult& aError)
   {
     aError = SetOption(aIndex, aOption);
   }
+  virtual void GetSupportedNames(nsTArray<nsString>& aNames);
 
 private:
   /** The list of options (holds strong references).  This is infallible, so
    * various members such as InsertOptionAt are also infallible. */
   nsTArray<nsRefPtr<nsHTMLOptionElement> > mElements;
   /** The select element that contains this array */
   nsHTMLSelectElement* mSelect;
 };
--- a/content/html/content/src/nsHTMLTableElement.cpp
+++ b/content/html/content/src/nsHTMLTableElement.cpp
@@ -47,16 +47,17 @@ public:
   virtual nsGenericElement* GetElementAt(uint32_t aIndex);
   virtual nsINode* GetParentObject()
   {
     return mParent;
   }
 
   virtual JSObject* NamedItem(JSContext* cx, const nsAString& name,
                               ErrorResult& error);
+  virtual void GetSupportedNames(nsTArray<nsString>& aNames);
 
   NS_IMETHOD    ParentDestroyed();
 
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TableRowsCollection)
 
   // nsWrapperCache
   virtual JSObject* WrapObject(JSContext *cx, JSObject *scope,
                                bool *triedToWrap)
@@ -268,16 +269,34 @@ TableRowsCollection::NamedItem(JSContext
         }
         return &v.toObject();
       }
     }
   );
   return nullptr;
 }
 
+void
+TableRowsCollection::GetSupportedNames(nsTArray<nsString>& aNames)
+{
+  DO_FOR_EACH_ROWGROUP(
+    nsTArray<nsString> names;
+    nsCOMPtr<nsIHTMLCollection> coll = do_QueryInterface(rows);
+    if (coll) {
+      coll->GetSupportedNames(names);
+      for (uint32_t i = 0; i < names.Length(); ++i) {
+        if (!aNames.Contains(names[i])) {
+          aNames.AppendElement(names[i]);
+        }
+      }
+    }
+  );
+}
+
+
 NS_IMETHODIMP 
 TableRowsCollection::NamedItem(const nsAString& aName,
                                nsIDOMNode** aReturn)
 {
   DO_FOR_EACH_ROWGROUP(
     nsCOMPtr<nsIHTMLCollection> collection = do_QueryInterface(rows);
     if (collection) {
       nsresult rv = collection->NamedItem(aName, aReturn);
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -324,16 +324,20 @@ MOCHITEST_FILES = \
 		file_iframe_sandbox_top_navigation_fail.html \
 		test_iframe_sandbox_plugins.html \
 		file_iframe_sandbox_f_if1.html \
 		file_iframe_sandbox_f_if2.html \
 		file_iframe_sandbox_f_if2.html^headers^ \
 		test_iframe_sandbox_workers.html \
 		file_iframe_sandbox_g_if1.html \
 		file_iframe_sandbox_worker.js \
+		test_named_options.html \
+		test_htmlcollection.html \
+		test_formelements.html \
+		test_rowscollection.html \
 		$(NULL)
 
 MOCHITEST_BROWSER_FILES = \
 		browser_bug649778.js \
 		file_bug649778.html \
 		file_bug649778.html^headers^ \
 		$(NULL)
 
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_formelements.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=772869
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 772869</title>
+  <script type="application/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=772869">Mozilla Bug 772869</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  <form id="f">
+    <input name="x">
+    <input type="image" name="a">
+    <input type="file" name="y">
+    <input type="submit" name="z">
+    <input id="w">
+    <input name="w">
+  </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 772869 **/
+var x = $("f").elements;
+x.something = "another";
+names = [];
+for (var name in x) {
+  names.push(name);
+}
+is(names.length, 14, "Should have 14 items");
+// Now sort entries 5 through 8, for comparison purposes.  We don't sort the
+// whole array, because we want to make sure the ordering between the parts
+// is correct
+temp = names.slice(5, 9);
+temp.sort();
+names.splice.bind(names, 5, 4).apply(null, temp);
+is(names.length, 14, "Should have still have 14 items");
+is(names[0], "0", "Entry 1")
+is(names[1], "1", "Entry 2")
+is(names[2], "2", "Entry 3")
+is(names[3], "3", "Entry 4")
+is(names[4], "4", "Entry 5")
+is(names[5], "w", "Entry 6")
+is(names[6], "x", "Entry 7")
+is(names[7], "y", "Entry 8")
+is(names[8], "z", "Entry 9")
+is(names[9], "something", "Entry 10")
+is(names[10], "item", "Entry 11")
+is(names[11], "namedItem", "Entry 12")
+is(names[12], "iterator", "Entry 13")
+is(names[13], "length", "Entry 14")
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_htmlcollection.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=772869
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 772869</title>
+  <script type="application/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=772869">Mozilla Bug 772869</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  <a class="foo" name="x"></a>
+  <span class="foo" id="y"></span>
+  <span class="foo" name="x"></span>
+  <form class="foo" name="z" id="w"></form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 772869 **/
+var x = document.getElementsByClassName("foo");
+x.something = "another";
+names = [];
+for (var name in x) {
+  names.push(name);
+}
+is(names.length, 13, "Should have 13 items");
+is(names[0], "0", "Entry 1")
+is(names[1], "1", "Entry 2")
+is(names[2], "2", "Entry 3")
+is(names[3], "3", "Entry 4")
+is(names[4], "x", "Entry 5")
+is(names[5], "y", "Entry 6")
+is(names[6], "z", "Entry 7")
+is(names[7], "w", "Entry 8")
+is(names[8], "something", "Entry 9")
+is(names[9], "item", "Entry 10")
+is(names[10], "namedItem", "Entry 11")
+is(names[11], "iterator", "Entry 12")
+is(names[12], "length", "Entry 13")
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_named_options.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=772869
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 772869</title>
+  <script type="application/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=772869">Mozilla Bug 772869</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  <select id="s">
+    <option name="x"></option>
+    <option name="y" id="z"></option>
+    <option name="z" id="x"></option>
+    <option id="w"></option>
+  </select>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 772869 **/
+var opt = $("s").options;
+opt.loopy = "something"
+var names = Object.getOwnPropertyNames(opt);
+is(names.length, 9, "Should have eight entries");
+is(names[0], "0", "Entry 1")
+is(names[1], "1", "Entry 2")
+is(names[2], "2", "Entry 3")
+is(names[3], "3", "Entry 4")
+is(names[4], "x", "Entry 5")
+is(names[5], "y", "Entry 6")
+is(names[6], "z", "Entry 7")
+is(names[7], "w", "Entry 8")
+is(names[8], "loopy", "Entry 9")
+
+var names2 = [];
+for (var name in opt) {
+  names2.push(name);
+}
+for (var i = 0; i < names.length; ++i) {
+  is(names2[i], names[i], "Correct entry at " + i);
+}
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_rowscollection.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=772869
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 772869</title>
+  <script type="application/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=772869">Mozilla Bug 772869</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  <table id="f">
+    <thead>
+      <tr id="x"></tr>
+    </thead>
+    <tfoot>
+      <tr id="z"></tr>
+      <tr id="w"></tr>
+    </tfoot>
+    <tr id="x"></tr>
+    <tr id="y"></tr>
+    <tbody>
+      <tr id="z"></tr>
+    </tbody>
+  </table>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 772869 **/
+var x = $("f").rows;
+x.something = "another";
+names = [];
+for (var name in x) {
+  names.push(name);
+}
+is(names.length, 15, "Should have 15 items");
+is(names[0], "0", "Entry 1")
+is(names[1], "1", "Entry 2")
+is(names[2], "2", "Entry 3")
+is(names[3], "3", "Entry 4")
+is(names[4], "4", "Entry 5")
+is(names[5], "5", "Entry 6")
+is(names[6], "x", "Entry 7")
+is(names[7], "y", "Entry 8")
+is(names[8], "z", "Entry 9")
+is(names[9], "w", "Entry 10")
+is(names[10], "something", "Entry 11")
+is(names[11], "item", "Entry 12")
+is(names[12], "namedItem", "Entry 13")
+is(names[13], "iterator", "Entry 14")
+is(names[14], "length", "Entry 15")
+</script>
+</pre>
+</body>
+</html>
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -5367,37 +5367,52 @@ class CGDOMJSProxyHandler_delete(ClassMe
 
 class CGDOMJSProxyHandler_getOwnPropertyNames(ClassMethod):
     def __init__(self, descriptor):
         args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'proxy'),
                 Argument('JS::AutoIdVector&', 'props')]
         ClassMethod.__init__(self, "getOwnPropertyNames", "bool", args)
         self.descriptor = descriptor
     def getBody(self):
+        # Per spec, we do indices, then named props, then everything else
         indexedGetter = self.descriptor.operations['IndexedGetter']
         if indexedGetter:
             addIndices = """uint32_t length = UnwrapProxy(proxy)->Length();
 MOZ_ASSERT(int32_t(length) >= 0);
 for (int32_t i = 0; i < int32_t(length); ++i) {
   if (!props.append(INT_TO_JSID(i))) {
     return false;
   }
 }
 
 """
         else:
             addIndices = ""
 
-        return addIndices + """JSObject* expando;
+        supportsNames = (self.descriptor.operations['NamedGetter'] or
+                         self.descriptor.operations['NamedSetter'] or
+                         self.descriptor.operations['NamedCreator'] or
+                         self.descriptor.operations['NamedDeleter'])
+        if supportsNames:
+            addNames = """nsTArray<nsString> names;
+UnwrapProxy(proxy)->GetSupportedNames(names);
+if (!AppendNamedPropertyIds(cx, proxy, names, props)) {
+  return false;
+}
+
+"""
+        else:
+            addNames = ""
+
+        return addIndices + addNames + """JSObject* expando;
 if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = DOMProxyHandler::GetExpandoObject(proxy)) &&
     !js::GetPropertyNames(cx, expando, JSITER_OWNONLY | JSITER_HIDDEN, &props)) {
   return false;
 }
 
-// FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=772869 Add named items
 return true;"""
 
 class CGDOMJSProxyHandler_hasOwn(ClassMethod):
     def __init__(self, descriptor):
         args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'proxy'),
                 Argument('jsid', 'id'), Argument('bool*', 'bp')]
         ClassMethod.__init__(self, "hasOwn", "bool", args)
         self.descriptor = descriptor
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -210,16 +210,42 @@ DOMProxyHandler::obj_toString(JSContext*
 
   JSString* str = JS_NewUCString(cx, chars, nchars);
   if (!str) {
     JS_free(cx, chars);
   }
   return str;
 }
 
+bool
+DOMProxyHandler::AppendNamedPropertyIds(JSContext* cx, JSObject* proxy,
+                                        nsTArray<nsString>& names,
+                                        JS::AutoIdVector& props)
+{
+  for (uint32_t i = 0; i < names.Length(); ++i) {
+    JS::Value v;
+    if (!xpc::NonVoidStringToJsval(cx, names[i], &v)) {
+      return false;
+    }
+
+    jsid id;
+    if (!JS_ValueToId(cx, v, &id)) {
+      return false;
+    }
+
+    if (!HasPropertyOnPrototype(cx, proxy, this, id)) {
+      if (!props.append(id)) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
 int32_t
 IdToInt32(JSContext* cx, jsid id)
 {
   JSAutoRequest ar(cx);
 
   jsval idval;
   double array_index;
   int32_t i;
--- a/dom/bindings/DOMJSProxyHandler.h
+++ b/dom/bindings/DOMJSProxyHandler.h
@@ -51,16 +51,22 @@ public:
     return v.isUndefined() ? NULL : v.toObjectOrNull();
   }
   static JSObject* EnsureExpandoObject(JSContext* cx, JSObject* obj);
 
   const DOMClass& mClass;
 
 protected:
   static JSString* obj_toString(JSContext* cx, const char* className);
+
+  // Append the property names in "names" that don't live on our proto
+  // chain to "props"
+  bool AppendNamedPropertyIds(JSContext* cx, JSObject* proxy,
+                              nsTArray<nsString>& names,
+                              JS::AutoIdVector& props);
 };
 
 extern jsid s_length_id;
 
 int32_t IdToInt32(JSContext* cx, jsid id);
 
 inline int32_t
 GetArrayIndexFromId(JSContext* cx, jsid id)
--- a/dom/bindings/test/TestBindingHeader.h
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -607,31 +607,33 @@ class TestNamedGetterInterface : public 
 {
 public:
   NS_DECL_ISUPPORTS
 
   // We need a GetParentObject to make binding codegen happy
   virtual nsISupports* GetParentObject();
 
   void NamedGetter(const nsAString&, bool&, nsAString&);
+  void GetSupportedNames(nsTArray<nsString>&);
 };
 
 class TestIndexedAndNamedGetterInterface : public nsISupports,
                                            public nsWrapperCache
 {
 public:
   NS_DECL_ISUPPORTS
 
   // We need a GetParentObject to make binding codegen happy
   virtual nsISupports* GetParentObject();
 
   uint32_t IndexedGetter(uint32_t, bool&);
   void NamedGetter(const nsAString&, bool&, nsAString&);
   void NamedItem(const nsAString&, nsAString&);
   uint32_t Length();
+  void GetSupportedNames(nsTArray<nsString>&);
 };
 
 class TestIndexedSetterInterface : public nsISupports,
                                    public nsWrapperCache
 {
 public:
   NS_DECL_ISUPPORTS
 
@@ -647,44 +649,47 @@ class TestNamedSetterInterface : public 
 {
 public:
   NS_DECL_ISUPPORTS
 
   // We need a GetParentObject to make binding codegen happy
   virtual nsISupports* GetParentObject();
 
   void NamedSetter(const nsAString&, TestIndexedSetterInterface&);
+  void GetSupportedNames(nsTArray<nsString>&);
 };
 
 class TestIndexedAndNamedSetterInterface : public nsISupports,
                                            public nsWrapperCache
 {
 public:
   NS_DECL_ISUPPORTS
 
   // We need a GetParentObject to make binding codegen happy
   virtual nsISupports* GetParentObject();
 
   void IndexedSetter(uint32_t, TestIndexedSetterInterface&);
   void NamedSetter(const nsAString&, TestIndexedSetterInterface&);
   void SetNamedItem(const nsAString&, TestIndexedSetterInterface&);
+  void GetSupportedNames(nsTArray<nsString>&);
 };
 
 class TestIndexedAndNamedGetterAndSetterInterface : public TestIndexedSetterInterface
 {
 public:
   uint32_t IndexedGetter(uint32_t, bool&);
   uint32_t Item(uint32_t);
   void NamedGetter(const nsAString&, bool&, nsAString&);
   void NamedItem(const nsAString&, nsAString&);
   void IndexedSetter(uint32_t, int32_t&);
   void IndexedSetter(uint32_t, const nsAString&) MOZ_DELETE;
   void NamedSetter(const nsAString&, const nsAString&);
   void Stringify(nsAString&);
   uint32_t Length();
+  void GetSupportedNames(nsTArray<nsString>&);
 };
 
 class TestCppKeywordNamedMethodsInterface : public nsISupports,
                                             public nsWrapperCache
 {
 public:
   NS_DECL_ISUPPORTS
 
@@ -731,31 +736,33 @@ class TestNamedDeleterInterface : public
 {
 public:
   NS_DECL_ISUPPORTS
 
   // We need a GetParentObject to make binding codegen happy
   virtual nsISupports* GetParentObject();
 
   void NamedDeleter(const nsAString&, bool&);
+  void GetSupportedNames(nsTArray<nsString>&);
 };
 
 class TestNamedDeleterWithRetvalInterface : public nsISupports,
                                             public nsWrapperCache
 {
 public:
   NS_DECL_ISUPPORTS
 
   // We need a GetParentObject to make binding codegen happy
   virtual nsISupports* GetParentObject();
 
   bool NamedDeleter(const nsAString&, bool&);
   bool NamedDeleter(const nsAString&) MOZ_DELETE;
   bool DelNamedItem(const nsAString&);
   bool DelNamedItem(const nsAString&, bool&) MOZ_DELETE;
+  void GetSupportedNames(nsTArray<nsString>&);
 };
 
 class TestIndexedAndNamedDeleterInterface : public nsISupports,
                                             public nsWrapperCache
 {
 public:
   NS_DECL_ISUPPORTS
 
@@ -763,14 +770,15 @@ public:
   virtual nsISupports* GetParentObject();
 
   void IndexedDeleter(uint32_t, bool&);
 
   void NamedDeleter(const nsAString&, bool&);
   void NamedDeleter(const nsAString&) MOZ_DELETE;
   void DelNamedItem(const nsAString&);
   void DelNamedItem(const nsAString&, bool&) MOZ_DELETE;
+  void GetSupportedNames(nsTArray<nsString>&);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* TestBindingHeader_h */
--- a/dom/imptests/failures/webapps/DOMCore/tests/submissions/Ms2ger/Makefile.in
+++ b/dom/imptests/failures/webapps/DOMCore/tests/submissions/Ms2ger/Makefile.in
@@ -11,17 +11,16 @@ include $(DEPTH)/config/autoconf.mk
 
 DIRS := \
   $(NULL)
 
 MOCHITEST_FILES := \
   test_DOMImplementation-createDocument.html.json \
   test_Document-createElementNS.html.json \
   test_Document-getElementsByTagName.html.json \
-  test_Element-children.html.json \
   test_Event-constructors.html.json \
   test_Event-defaultPrevented.html.json \
   test_EventTarget-dispatchEvent.html.json \
   test_Node-appendChild.html.json \
   test_Node-constants.html.json \
   test_Node-contains.xml.json \
   test_Node-insertBefore.html.json \
   test_Node-isEqualNode.xhtml.json \
deleted file mode 100644
--- a/dom/imptests/failures/webapps/DOMCore/tests/submissions/Ms2ger/test_Element-children.html.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-  "HTMLCollection edge cases 1": true
-}
--- a/dom/imptests/html/tests/submission/Opera/microdata/test_001.html
+++ b/dom/imptests/html/tests/submission/Opera/microdata/test_001.html
@@ -1525,16 +1525,22 @@ test(function () {
 	var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"><div itemprop="foo"></div></div><div itemprop="baz qux"></div>');
 	assert_equals( testEl.properties.item(0), testEl.childNodes[0], 'item(0)' );
 	assert_equals( testEl.properties.item(1), testEl.childNodes[1], 'item(1)' );
 	assert_equals( testEl.properties.item(2), testEl.childNodes[1].childNodes[0], 'item(2)' );
 	assert_equals( testEl.properties.item(3), testEl.childNodes[2], 'item(3)' );
 }, 'properties.item must give each property in tree order');
 test(function () {
 	var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"><div itemprop="foo"></div></div><div itemprop="baz qux"></div>');
+	testEl.properties.something = "another";
+	var names = Object.getOwnPropertyNames(testEl.properties);
+	assert_array_equals( names, ["0", "1", "2", "3", "foo", "bar", "baz", "qux", "something"] );
+}, 'properties.item must have the right property names on it when enumerated');
+test(function () {
+	var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"><div itemprop="foo"></div></div><div itemprop="baz qux"></div>');
 	assert_equals( testEl.properties.item(4), null, 'positive index' );
 	assert_equals( testEl.properties.item(-1), null, 'negative index' );
 }, 'properties.item must give null for out of range index');
 test(function () {
 	var testEl = makeEl('div',{itemscope:'itemscope'},'<div itemprop="foo"></div><div itemprop="bar"><div itemprop="foo"></div></div><div itemprop="baz qux"></div>');
 	assert_equals( testEl.properties[0], testEl.childNodes[0], '[0]' );
 	assert_equals( testEl.properties[1], testEl.childNodes[1], '[1]' );
 	assert_equals( testEl.properties[2], testEl.childNodes[1].childNodes[0], '[2]' );