Bug 841442 - Move HTMLFormElement to WebIDL, r=bz
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 19 Jun 2013 10:24:37 -0400
changeset 147989 259e68f8843dd7c9af1766cb6092434f276c2db6
parent 147988 192cecc0111e5a7c30fe2a0c83aed53d444dce91
child 147990 5a3dc3d3189099e7d1b3c070ef6532c850213764
push id368
push userbbajaj@mozilla.com
push dateMon, 09 Sep 2013 22:57:58 +0000
treeherdermozilla-release@5a4f47ae1217 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs841442
milestone24.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 841442 - Move HTMLFormElement to WebIDL, r=bz
content/base/src/nsFormData.cpp
content/base/src/nsFormData.h
content/html/content/src/HTMLFormElement.cpp
content/html/content/src/HTMLFormElement.h
content/html/content/test/reflect.js
content/html/content/test/test_bug879319.html
dom/bindings/BindingDeclarations.h
dom/bindings/BindingUtils.h
dom/bindings/Bindings.conf
dom/webidl/FormData.webidl
dom/webidl/HTMLFormElement.webidl
dom/webidl/WebIDL.mk
js/xpconnect/src/dom_quickstubs.qsconf
--- a/content/base/src/nsFormData.cpp
+++ b/content/base/src/nsFormData.cpp
@@ -104,23 +104,23 @@ nsFormData::Append(const nsAString& aNam
 /* virtual */ JSObject*
 nsFormData::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return FormDataBinding::Wrap(aCx, aScope, this);
 }
 
 /* static */ already_AddRefed<nsFormData>
 nsFormData::Constructor(const GlobalObject& aGlobal,
-                        const Optional<HTMLFormElement*>& aFormElement,
+                        const Optional<NonNull<HTMLFormElement> >& aFormElement,
                         ErrorResult& aRv)
 {
   nsRefPtr<nsFormData> formData = new nsFormData(aGlobal.Get());
   if (aFormElement.WasPassed()) {
-    MOZ_ASSERT(aFormElement.Value());
-    aRv = aFormElement.Value()->WalkFormElements(formData);
+    // TODO: this should ...Value().WalkFromElements(formData); - Bug 883827
+    aRv = aFormElement.Value().get()->WalkFormElements(formData);
   }
   return formData.forget();
 }
 
 // -------------------------------------------------------------------------
 // nsIXHRSendable
 
 NS_IMETHODIMP
--- a/content/base/src/nsFormData.h
+++ b/content/base/src/nsFormData.h
@@ -47,17 +47,17 @@ public:
   // WebIDL
   nsISupports*
   GetParentObject() const
   {
     return mOwner;
   }
   static already_AddRefed<nsFormData>
   Constructor(const mozilla::dom::GlobalObject& aGlobal,
-              const mozilla::dom::Optional<mozilla::dom::HTMLFormElement*>& aFormElement,
+              const mozilla::dom::Optional<mozilla::dom::NonNull<mozilla::dom::HTMLFormElement> >& aFormElement,
               mozilla::ErrorResult& aRv);
   void Append(const nsAString& aName, const nsAString& aValue);
   void Append(const nsAString& aName, nsIDOMBlob* aBlob,
               const mozilla::dom::Optional<nsAString>& aFilename);
 
   // nsFormSubmission
   virtual nsresult GetEncodedSubmission(nsIURI* aURI,
                                         nsIInputStream** aPostDataStream) MOZ_OVERRIDE;
--- a/content/html/content/src/HTMLFormElement.cpp
+++ b/content/html/content/src/HTMLFormElement.cpp
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/HTMLFormElement.h"
+#include "mozilla/dom/HTMLFormElementBinding.h"
 #include "nsIHTMLDocument.h"
 #include "nsEventStateManager.h"
 #include "nsEventStates.h"
 #include "nsGkAtoms.h"
 #include "nsStyleConsts.h"
 #include "nsPresContext.h"
 #include "nsIDocument.h"
 #include "nsIFormControlFrame.h"
@@ -247,16 +248,18 @@ HTMLFormElement::HTMLFormElement(already
     mDefaultSubmitElement(nullptr),
     mFirstSubmitInElements(nullptr),
     mFirstSubmitNotInElements(nullptr),
     mInvalidElementsCount(0),
     mEverTriedInvalidSubmit(false)
 {
   mImageNameLookupTable.Init(NS_FORM_CONTROL_LIST_HASHTABLE_SIZE);
   mPastNameLookupTable.Init(NS_FORM_CONTROL_LIST_HASHTABLE_SIZE);
+
+  SetIsDOMBinding();
 }
 
 HTMLFormElement::~HTMLFormElement()
 {
   if (mControls) {
     mControls->DropFormReference();
   }
 
@@ -306,18 +309,27 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageNameLookupTable)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPastNameLookupTable)
   tmp->mSelectedRadioButtons.EnumerateRead(ElementTraverser, &cb);
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLFormElement,
                                                 nsGenericHTMLElement)
   tmp->Clear();
+  ++tmp->mExpandoAndGeneration.generation;
+  tmp->mExpandoAndGeneration.expando = JS::UndefinedValue();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(HTMLFormElement,
+                                               nsGenericHTMLElement)
+  if (tmp->PreservingWrapper()) {
+    NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mExpandoAndGeneration.expando);
+  }
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
 NS_IMPL_ADDREF_INHERITED(HTMLFormElement, Element)
 NS_IMPL_RELEASE_INHERITED(HTMLFormElement, Element)
 
 
 // QueryInterface implementation for HTMLFormElement
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLFormElement)
   NS_HTML_CONTENT_INTERFACES(nsGenericHTMLElement)
   NS_INTERFACE_TABLE_INHERITED4(HTMLFormElement,
@@ -329,20 +341,26 @@ NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(HTMLFormElement)
 NS_ELEMENT_INTERFACE_MAP_END
 
 
 // nsIDOMHTMLFormElement
 
 NS_IMPL_ELEMENT_CLONE_WITH_INIT(HTMLFormElement)
 
+nsIHTMLCollection*
+HTMLFormElement::Elements()
+{
+  return mControls;
+}
+
 NS_IMETHODIMP
 HTMLFormElement::GetElements(nsIDOMHTMLCollection** aElements)
 {
-  *aElements = mControls;
+  *aElements = Elements();
   NS_ADDREF(*aElements);
   return NS_OK;
 }
 
 nsresult
 HTMLFormElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                          nsIAtom* aPrefix, const nsAString& aValue,
                          bool aNotify)
@@ -394,47 +412,53 @@ NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLForm
 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLFormElement, Enctype, enctype,
                                 kFormDefaultEnctype->tag)
 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLFormElement, Method, method,
                                 kFormDefaultMethod->tag)
 NS_IMPL_BOOL_ATTR(HTMLFormElement, NoValidate, novalidate)
 NS_IMPL_STRING_ATTR(HTMLFormElement, Name, name)
 NS_IMPL_STRING_ATTR(HTMLFormElement, Target, target)
 
-NS_IMETHODIMP
-HTMLFormElement::Submit()
+void
+HTMLFormElement::Submit(ErrorResult& aRv)
 {
   // Send the submit event
-  nsresult rv = NS_OK;
   nsRefPtr<nsPresContext> presContext = GetPresContext();
   if (mPendingSubmission) {
     // aha, we have a pending submission that was not flushed
     // (this happens when form.submit() is called twice)
     // we have to delete it and build a new one since values
     // might have changed inbetween (we emulate IE here, that's all)
     mPendingSubmission = nullptr;
   }
 
-  rv = DoSubmitOrReset(nullptr, NS_FORM_SUBMIT);
-  return rv;
+  aRv = DoSubmitOrReset(nullptr, NS_FORM_SUBMIT);
+}
+
+NS_IMETHODIMP
+HTMLFormElement::Submit()
+{
+  ErrorResult rv;
+  Submit(rv);
+  return rv.ErrorCode();
 }
 
 NS_IMETHODIMP
 HTMLFormElement::Reset()
 {
   nsFormEvent event(true, NS_FORM_RESET);
   nsEventDispatcher::Dispatch(static_cast<nsIContent*>(this), nullptr,
                               &event);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLFormElement::CheckValidity(bool* retVal)
 {
-  *retVal = CheckFormValidity(nullptr);
+  *retVal = CheckValidity();
   return NS_OK;
 }
 
 bool
 HTMLFormElement::ParseAttribute(int32_t aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsAttrValue& aResult)
@@ -1077,16 +1101,24 @@ HTMLFormElement::WalkFormElements(nsForm
 NS_IMETHODIMP_(uint32_t)
 HTMLFormElement::GetElementCount() const 
 {
   uint32_t count = 0;
   mControls->GetLength(&count); 
   return count;
 }
 
+Element*
+HTMLFormElement::IndexedGetter(uint32_t aIndex, bool &aFound)
+{
+  Element* element = mControls->mElements.SafeElementAt(aIndex, nullptr);
+  aFound = element != nullptr;
+  return element;
+}
+
 NS_IMETHODIMP_(nsIFormControl*)
 HTMLFormElement::GetElementAt(int32_t aIndex) const
 {
   return mControls->mElements.SafeElementAt(aIndex, nullptr);
 }
 
 /**
  * Compares the position of aControl1 and aControl2 in the document
@@ -1429,30 +1461,31 @@ HTMLFormElement::HandleDefaultSubmitRemo
                    "What happened here?");
 
   // Notify about change if needed.
   if (mDefaultSubmitElement) {
     mDefaultSubmitElement->UpdateState(true);
   }
 }
 
-static nsresult
-RemoveElementFromTableInternal(
+nsresult
+HTMLFormElement::RemoveElementFromTableInternal(
   nsInterfaceHashtable<nsStringHashKey,nsISupports>& aTable,
   nsIContent* aChild, const nsAString& aName)
 {
   nsCOMPtr<nsISupports> supports;
 
   if (!aTable.Get(aName, getter_AddRefs(supports)))
     return NS_OK;
 
   // Single element in the hash, just remove it if it's the one
   // we're trying to remove...
   if (supports == aChild) {
     aTable.Remove(aName);
+    ++mExpandoAndGeneration.generation;
     return NS_OK;
   }
 
   nsCOMPtr<nsIContent> content(do_QueryInterface(supports));
   if (content) {
     return NS_OK;
   }
 
@@ -1466,16 +1499,17 @@ RemoveElementFromTableInternal(
 
   uint32_t length = 0;
   list->GetLength(&length);
 
   if (!length) {
     // If the list is empty we remove if from our hash, this shouldn't
     // happen tho
     aTable.Remove(aName);
+    ++mExpandoAndGeneration.generation;
   } else if (length == 1) {
     // Only one element left, replace the list in the hash with the
     // single element.
     nsIContent* node = list->Item(0);
     if (node) {
       aTable.Put(aName, node);
     }
   }
@@ -1494,42 +1528,66 @@ RemovePastNames(const nsAString& aName,
 nsresult
 HTMLFormElement::RemoveElementFromTable(nsGenericHTMLFormElement* aElement,
                                         const nsAString& aName,
                                         RemoveElementReason aRemoveReason)
 {
   // If the element is being removed from the form, we have to remove it from
   // the past names map.
   if (aRemoveReason == ElementRemoved) {
+    uint32_t oldCount = mPastNameLookupTable.Count();
     mPastNameLookupTable.Enumerate(RemovePastNames, aElement);
+    if (oldCount != mPastNameLookupTable.Count()) {
+      ++mExpandoAndGeneration.generation;
+    }
   }
 
   return mControls->RemoveElementFromTable(aElement, aName);
 }
 
 already_AddRefed<nsISupports>
+HTMLFormElement::NamedGetter(const nsAString& aName, bool &aFound)
+{
+  aFound = true;
+
+  nsCOMPtr<nsISupports> result = DoResolveName(aName, true);
+  if (result) {
+    AddToPastNamesMap(aName, result);
+    return result.forget();
+  }
+
+  result = mImageNameLookupTable.GetWeak(aName);
+  if (result) {
+    AddToPastNamesMap(aName, result);
+    return result.forget();
+  }
+
+  result = mPastNameLookupTable.GetWeak(aName);
+  if (result) {
+    return result.forget();
+  }
+
+  aFound = false;
+  return nullptr;
+}
+
+void
+HTMLFormElement::GetSupportedNames(nsTArray<nsString >& aRetval)
+{
+  // TODO https://www.w3.org/Bugs/Public/show_bug.cgi?id=22320
+}
+
+already_AddRefed<nsISupports>
 HTMLFormElement::FindNamedItem(const nsAString& aName,
                                nsWrapperCache** aCache)
 {
-  nsCOMPtr<nsISupports> result = DoResolveName(aName, true);
-  if (result) {
-    // FIXME Get the wrapper cache from DoResolveName.
-    *aCache = nullptr;
-    AddToPastNamesMap(aName, result);
-    return result.forget();
-  }
-
-  result = mImageNameLookupTable.GetWeak(aName);
-  if (result) {
-    *aCache = nullptr;
-    AddToPastNamesMap(aName, result);
-    return result.forget();
-  }
-
-  result = mPastNameLookupTable.GetWeak(aName);
+  // FIXME Get the wrapper cache from DoResolveName.
+
+  bool found;
+  nsCOMPtr<nsISupports> result = NamedGetter(aName, found);
   if (result) {
     *aCache = nullptr;
     return result.forget();
   }
 
   return nullptr;
 }
 
@@ -1760,23 +1818,27 @@ HTMLFormElement::GetEncoding(nsAString& 
 }
  
 NS_IMETHODIMP
 HTMLFormElement::SetEncoding(const nsAString& aEncoding)
 {
   return SetEnctype(aEncoding);
 }
 
+int32_t
+HTMLFormElement::Length()
+{
+  return mControls->Length();
+}
+
 NS_IMETHODIMP    
 HTMLFormElement::GetLength(int32_t* aLength)
 {
-  uint32_t length;
-  nsresult rv = mControls->GetLength(&length);
-  *aLength = length;
-  return rv;
+  *aLength = Length();
+  return NS_OK;
 }
 
 void
 HTMLFormElement::ForgetCurrentSubmission()
 {
   mNotifiedObservers = false;
   mIsSubmitting = false;
   mSubmittingRequest = nullptr;
@@ -2451,43 +2513,44 @@ nsFormControlList::NamedItemInternal(con
 {
   if (aFlushContent) {
     FlushPendingNotifications();
   }
 
   return mNameLookupTable.GetWeak(aName);
 }
 
-static nsresult
-AddElementToTableInternal(
+nsresult
+HTMLFormElement::AddElementToTableInternal(
   nsInterfaceHashtable<nsStringHashKey,nsISupports>& aTable,
-  nsIContent* aChild, const nsAString& aName, HTMLFormElement* aForm)
+  nsIContent* aChild, const nsAString& aName)
 {
   nsCOMPtr<nsISupports> supports;
   aTable.Get(aName, getter_AddRefs(supports));
 
   if (!supports) {
     // No entry found, add the element
     aTable.Put(aName, aChild);
+    ++mExpandoAndGeneration.generation;
   } else {
     // Found something in the hash, check its type
     nsCOMPtr<nsIContent> content = do_QueryInterface(supports);
 
     if (content) {
       // Check if the new content is the same as the one we found in the
       // hash, if it is then we leave it in the hash as it is, this will
       // happen if a form control has both a name and an id with the same
       // value
       if (content == aChild) {
         return NS_OK;
       }
 
       // Found an element, create a list, add the element to the list and put
       // the list in the hash
-      nsSimpleContentList *list = new nsSimpleContentList(aForm);
+      nsSimpleContentList *list = new nsSimpleContentList(this);
 
       // If an element has a @form, we can assume it *might* be able to not have
       // a parent and still be in the form.
       NS_ASSERTION(content->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
                    content->GetParent(), "Item in list without parent");
 
       // Determine the ordering between the new and old element.
       bool newFirst = nsContentUtils::PositionIsBefore(aChild, content);
@@ -2553,17 +2616,17 @@ AddElementToTableInternal(
 nsresult
 nsFormControlList::AddElementToTable(nsGenericHTMLFormElement* aChild,
                                      const nsAString& aName)
 {
   if (!ShouldBeInElements(aChild)) {
     return NS_OK;
   }
 
-  return AddElementToTableInternal(mNameLookupTable, aChild, aName, mForm);
+  return mForm->AddElementToTableInternal(mNameLookupTable, aChild, aName);
 }
 
 nsresult
 nsFormControlList::IndexOfControl(nsIFormControl* aControl,
                                   int32_t* aIndex)
 {
   // Note -- not a DOM method; callers should handle flushing themselves
   
@@ -2577,17 +2640,17 @@ nsFormControlList::IndexOfControl(nsIFor
 nsresult
 nsFormControlList::RemoveElementFromTable(nsGenericHTMLFormElement* aChild,
                                           const nsAString& aName)
 {
   if (!ShouldBeInElements(aChild)) {
     return NS_OK;
   }
 
-  return RemoveElementFromTableInternal(mNameLookupTable, aChild, aName);
+  return mForm->RemoveElementFromTableInternal(mNameLookupTable, aChild, aName);
 }
 
 nsresult
 nsFormControlList::GetSortedControls(nsTArray<nsGenericHTMLFormElement*>& aControls) const
 {
 #ifdef DEBUG
   AssertDocumentOrder(mElements, mForm);
   AssertDocumentOrder(mNotInElements, mForm);
@@ -2713,17 +2776,17 @@ HTMLFormElement::AddImageElement(HTMLIma
   AddElementToList(mImageElements, aChild, this);
   return NS_OK;
 }
 
 nsresult
 HTMLFormElement::AddImageElementToTable(HTMLImageElement* aChild,
                                         const nsAString& aName)
 {
-  return AddElementToTableInternal(mImageNameLookupTable, aChild, aName, this);
+  return AddElementToTableInternal(mImageNameLookupTable, aChild, aName);
 }
 
 nsresult
 HTMLFormElement::RemoveImageElement(HTMLImageElement* aChild)
 {
   uint32_t index = mImageElements.IndexOf(aChild);
   NS_ENSURE_STATE(index != mImageElements.NoIndex);
 
@@ -2753,10 +2816,16 @@ HTMLFormElement::AddToPastNamesMap(const
   // node in candidates in the form element's past names map, replacing the
   // previous entry with the same name, if any.
   nsCOMPtr<nsIContent> node = do_QueryInterface(aChild);
   if (node) {
     mPastNameLookupTable.Put(aName, aChild);
   }
 }
  
+JSObject*
+HTMLFormElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return HTMLFormElementBinding::Wrap(aCx, aScope, this);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/content/html/content/src/HTMLFormElement.h
+++ b/content/html/content/src/HTMLFormElement.h
@@ -43,16 +43,18 @@ namespace dom {
 class nsFormControlList;
 
 class HTMLFormElement : public nsGenericHTMLElement,
                         public nsIDOMHTMLFormElement,
                         public nsIWebProgressListener,
                         public nsIForm,
                         public nsIRadioGroupContainer
 {
+  friend class nsFormControlList;
+
 public:
   HTMLFormElement(already_AddRefed<nsINodeInfo> aNodeInfo);
   virtual ~HTMLFormElement();
 
   nsresult Init();
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
@@ -126,18 +128,18 @@ public:
   /**
    * Forget all information about the current submission (and the fact that we
    * are currently submitting at all).
    */
   void ForgetCurrentSubmission();
 
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const MOZ_OVERRIDE;
 
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLFormElement,
-                                           nsGenericHTMLElement)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(HTMLFormElement,
+                                                         nsGenericHTMLElement)
 
   /**
    * Remove an element from this form's list of elements
    *
    * @param aElement the element to remove
    * @param aUpdateValidity If true, updates the form validity.
    * @return NS_OK if the element was successfully removed.
    */
@@ -310,17 +312,117 @@ public:
 
   /**
    * Implements form[name]. Returns form controls in this form with the correct
    * value of the name attribute.
    */
   already_AddRefed<nsISupports>
   FindNamedItem(const nsAString& aName, nsWrapperCache** aCache);
 
+  // WebIDL
+
+  void GetAcceptCharset(DOMString& aValue)
+  {
+    GetHTMLAttr(nsGkAtoms::acceptcharset, aValue);
+  }
+
+  void SetAcceptCharset(const nsAString& aValue, ErrorResult& aRv)
+  {
+    SetHTMLAttr(nsGkAtoms::acceptcharset, aValue, aRv);
+  }
+
+  // XPCOM GetAction() is OK
+  void SetAction(const nsAString& aValue, ErrorResult& aRv)
+  {
+    SetHTMLAttr(nsGkAtoms::action, aValue, aRv);
+  }
+
+  // XPCOM GetAutocomplete() is OK
+  void SetAutocomplete(const nsAString& aValue, ErrorResult& aRv)
+  {
+    SetHTMLAttr(nsGkAtoms::autocomplete, aValue, aRv);
+  }
+
+  // XPCOM GetEnctype() is OK
+  void SetEnctype(const nsAString& aValue, ErrorResult& aRv)
+  {
+    SetHTMLAttr(nsGkAtoms::enctype, aValue, aRv);
+  }
+
+  // XPCOM GetEncoding() is OK
+  void SetEncoding(const nsAString& aValue, ErrorResult& aRv)
+  {
+    SetEnctype(aValue, aRv);
+  }
+
+  // XPCOM GetMethod() is OK
+  void SetMethod(const nsAString& aValue, ErrorResult& aRv)
+  {
+    SetHTMLAttr(nsGkAtoms::method, aValue, aRv);
+  }
+
+  void GetName(DOMString& aValue)
+  {
+    GetHTMLAttr(nsGkAtoms::name, aValue);
+  }
+
+  void SetName(const nsAString& aValue, ErrorResult& aRv)
+  {
+    SetHTMLAttr(nsGkAtoms::name, aValue, aRv);
+  }
+
+  bool NoValidate() const
+  {
+    return GetBoolAttr(nsGkAtoms::novalidate);
+  }
+
+  void SetNoValidate(bool aValue, ErrorResult& aRv)
+  {
+    SetHTMLBoolAttr(nsGkAtoms::novalidate, aValue, aRv);
+  }
+
+  void GetTarget(DOMString& aValue)
+  {
+    GetHTMLAttr(nsGkAtoms::target, aValue);
+  }
+
+  void SetTarget(const nsAString& aValue, ErrorResult& aRv)
+  {
+    SetHTMLAttr(nsGkAtoms::target, aValue, aRv);
+  }
+
+  // it's only out-of-line because the class definition is not available in the
+  // header
+  nsIHTMLCollection* Elements();
+
+  int32_t Length();
+
+  void Submit(ErrorResult& aRv);
+
+  // XPCOM Reset() is OK
+
+  bool CheckValidity()
+  {
+    return CheckFormValidity(nullptr);
+  }
+
+  Element*
+  IndexedGetter(uint32_t aIndex, bool &aFound);
+
+  already_AddRefed<nsISupports>
+  NamedGetter(const nsAString& aName, bool &aFound);
+
+  void GetSupportedNames(nsTArray<nsString >& aRetval);
+
+  js::ExpandoAndGeneration mExpandoAndGeneration;
+
 protected:
+  virtual JSObject* WrapNode(JSContext* aCx,
+                             JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+
   void PostPasswordEvent();
   void EventHandled() { mFormPasswordEvent = nullptr; }
 
   class FormPasswordEvent : public nsAsyncDOMEvent
   {
   public:
     FormPasswordEvent(HTMLFormElement* aEventNode,
                       const nsAString& aEventType)
@@ -361,31 +463,31 @@ protected:
   // Async callback to handle removal of our default submit
   void HandleDefaultSubmitRemoval();
 
   //
   // Submit Helpers
   //
   //
   /**
-   * Attempt to submit (submission might be deferred) 
+   * Attempt to submit (submission might be deferred)
    * (called by DoSubmitOrReset)
    *
    * @param aPresContext the presentation context
    * @param aEvent the DOM event that was passed to us for the submit
    */
   nsresult DoSubmit(nsEvent* aEvent);
 
   /**
    * Prepare the submission object (called by DoSubmit)
    *
    * @param aFormSubmission the submission object
    * @param aEvent the DOM event that was passed to us for the submit
    */
-  nsresult BuildSubmission(nsFormSubmission** aFormSubmission, 
+  nsresult BuildSubmission(nsFormSubmission** aFormSubmission,
                            nsEvent* aEvent);
   /**
    * Perform the submission (called by DoSubmit and FlushPendingSubmission)
    *
    * @param aFormSubmission the submission object
    */
   nsresult SubmitSubmission(nsFormSubmission* aFormSubmission);
 
@@ -425,16 +527,26 @@ protected:
   bool CheckFormValidity(nsIMutableArray* aInvalidElements) const;
 
   // Clear the mImageNameLookupTable and mImageElements.
   void Clear();
 
   // Insert a element into the past names map.
   void AddToPastNamesMap(const nsAString& aName, nsISupports* aChild);
 
+  nsresult
+  AddElementToTableInternal(
+    nsInterfaceHashtable<nsStringHashKey,nsISupports>& aTable,
+    nsIContent* aChild, const nsAString& aName);
+
+  nsresult
+  RemoveElementFromTableInternal(
+    nsInterfaceHashtable<nsStringHashKey,nsISupports>& aTable,
+    nsIContent* aChild, const nsAString& aName);
+
 public:
   /**
    * Flush a possible pending submission. If there was a scripted submission
    * triggered by a button or image, the submission was defered. This method
    * forces the pending submission to be submitted. (happens when the handler
    * returns false or there is an action/target change in the script)
    */
   void FlushPendingSubmission();
--- a/content/html/content/test/reflect.js
+++ b/content/html/content/test/reflect.js
@@ -53,40 +53,28 @@ function reflectString(aParameters)
   element.setAttribute(contentAttr, null);
   is(element.getAttribute(contentAttr), "null",
      "null should have been stringified to 'null' for '" + contentAttr + "'");
   is(element[idlAttr], "null",
       "null should have been stringified to 'null' for '" + idlAttr + "'");
   element.removeAttribute(contentAttr);
 
   element[idlAttr] = null;
-  // TODO: remove this ugly hack when null stringification will work as expected.
-  var todoAttrs = {
-    form: [ "acceptCharset", "name", "target" ],
-  };
-  if (!(element.localName in todoAttrs) || todoAttrs[element.localName].indexOf(idlAttr) == -1) {
-    if (treatNullAs == "EmptyString") {
-      is(element.getAttribute(contentAttr), "",
-         "null should have been stringified to '' for '" + contentAttr + "'");
-      is(element[idlAttr], "",
-         "null should have been stringified to '' for '" + idlAttr + "'");
-    } else {
-      is(element.getAttribute(contentAttr), "null",
-         "null should have been stringified to 'null' for '" + contentAttr + "'");
-      is(element[idlAttr], "null",
-         "null should have been stringified to 'null' for '" + contentAttr + "'");
-    }
-    element.removeAttribute(contentAttr);
+  if (treatNullAs == "EmptyString") {
+    is(element.getAttribute(contentAttr), "",
+       "null should have been stringified to '' for '" + contentAttr + "'");
+    is(element[idlAttr], "",
+       "null should have been stringified to '' for '" + idlAttr + "'");
   } else {
-    todo_is(element.getAttribute(contentAttr), "null",
+    is(element.getAttribute(contentAttr), "null",
        "null should have been stringified to 'null' for '" + contentAttr + "'");
-    todo_is(element[idlAttr], "null",
+    is(element[idlAttr], "null",
        "null should have been stringified to 'null' for '" + contentAttr + "'");
-    element.removeAttribute(contentAttr);
   }
+  element.removeAttribute(contentAttr);
 
   // Tests various strings.
   var stringsToTest = [
     // [ test value, expected result ]
     [ "", "" ],
     [ "null", "null" ],
     [ "undefined", "undefined" ],
     [ "foo", "foo" ],
--- a/content/html/content/test/test_bug879319.html
+++ b/content/html/content/test/test_bug879319.html
@@ -47,19 +47,19 @@ ok("tmp1" in form.elements, "tmp1 is in 
 ok(!("tmp0" in form.elements), "tmp0 is not in form.elements");
 ok(!("foo0" in form.elements), "foo0 is not in form.elements");
 is(form.tmp0, input0, "Form.tmp0 == input0");
 is(form.tmp1, input0, "Form.tmp1 == input0");
 is(form.foo0, input0, "Form.foo0 is still here");
 
 input0.setAttribute("form", "");
 ok(!("foo0" in form.elements), "foo0 is not in form.elements");
-todo_is(form.foo0, undefined, "Form.foo0 should not still be here");
-todo_is(form.tmp0, undefined, "Form.tmp0 should not still be here");
-todo_is(form.tmp1, undefined, "Form.tmp1 should not still be here");
+is(form.foo0, undefined, "Form.foo0 should not still be here");
+is(form.tmp0, undefined, "Form.tmp0 should not still be here");
+is(form.tmp1, undefined, "Form.tmp1 should not still be here");
 
 var input1 = document.getElementById("input1");
 ok(input1, "input1 exists");
 is(form.foo1, input1, "Form.foo1 should exist");
 
 ok("foo1" in form.elements, "foo1 in form.elements");
 is(input1.form, form, "input1.form is form");
 
@@ -68,24 +68,25 @@ ok("foo0" in form.elements, "foo0 is in 
 is(form.foo0, input1, "Form.foo0 should be input1");
 is(form.foo1, input1, "Form.foo1 should be input1");
 
 var input2 = document.getElementById("input2");
 ok(input2, "input2 exists");
 is(form.foo2, input2, "Form.foo2 should exist");
 input2.parentNode.removeChild(input2);
 ok(!("foo2" in form.elements), "foo2 is not in form.elements");
-todo_is(form.foo2, undefined, "Form.foo2 should not longer be there");
+is(form.foo2, undefined, "Form.foo2 should not longer be there");
 
 var img0 = document.getElementById("img0");
 ok(img0, "img0 exists");
 is(form.bar0, img0, "Form.bar0 should exist");
 
 img0.setAttribute("name", "old_bar0");
+is(form.old_bar0, img0, "Form.bar0 is still here");
 is(form.bar0, img0, "Form.bar0 is still here");
 
 img0.parentNode.removeChild(img0);
-todo_is(form.bar0, undefined, "Form.bar0 should not be here");
+is(form.bar0, undefined, "Form.bar0 should not be here");
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/bindings/BindingDeclarations.h
+++ b/dom/bindings/BindingDeclarations.h
@@ -423,16 +423,82 @@ private:
   // Forbid copy-construction and assignment
   Optional(const Optional& other) MOZ_DELETE;
   const Optional &operator=(const Optional &other) MOZ_DELETE;
 
   bool mPassed;
   const nsAString* mStr;
 };
 
+template<class T>
+class NonNull
+{
+public:
+  NonNull()
+#ifdef DEBUG
+    : inited(false)
+#endif
+  {}
+
+  operator T&() {
+    MOZ_ASSERT(inited);
+    MOZ_ASSERT(ptr, "NonNull<T> was set to null");
+    return *ptr;
+  }
+
+  operator const T&() const {
+    MOZ_ASSERT(inited);
+    MOZ_ASSERT(ptr, "NonNull<T> was set to null");
+    return *ptr;
+  }
+
+  void operator=(T* t) {
+    ptr = t;
+    MOZ_ASSERT(ptr);
+#ifdef DEBUG
+    inited = true;
+#endif
+  }
+
+  template<typename U>
+  void operator=(U* t) {
+    ptr = t->ToAStringPtr();
+    MOZ_ASSERT(ptr);
+#ifdef DEBUG
+    inited = true;
+#endif
+  }
+
+  T** Slot() {
+#ifdef DEBUG
+    inited = true;
+#endif
+    return &ptr;
+  }
+
+  T* Ptr() {
+    MOZ_ASSERT(inited);
+    MOZ_ASSERT(ptr, "NonNull<T> was set to null");
+    return ptr;
+  }
+
+  // Make us work with smart-ptr helpers that expect a get()
+  T* get() const {
+    MOZ_ASSERT(inited);
+    MOZ_ASSERT(ptr);
+    return ptr;
+  }
+
+protected:
+  T* ptr;
+#ifdef DEBUG
+  bool inited;
+#endif
+};
+
 // Class for representing sequences in arguments.  We use a non-auto array
 // because that allows us to use sequences of sequences and the like.  This
 // needs to be fallible because web content controls the length of the array,
 // and can easily try to create very large lengths.
 template<typename T>
 class Sequence : public FallibleTArray<T>
 {
 public:
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -1346,75 +1346,16 @@ GetPropertyOnPrototype(JSContext* cx, JS
                        JS::Value* vp);
 
 bool
 HasPropertyOnPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
                        DOMProxyHandler* handler,
                        JS::Handle<jsid> id);
 
 template<class T>
-class NonNull
-{
-public:
-  NonNull()
-#ifdef DEBUG
-    : inited(false)
-#endif
-  {}
-
-  operator T&() {
-    MOZ_ASSERT(inited);
-    MOZ_ASSERT(ptr, "NonNull<T> was set to null");
-    return *ptr;
-  }
-
-  operator const T&() const {
-    MOZ_ASSERT(inited);
-    MOZ_ASSERT(ptr, "NonNull<T> was set to null");
-    return *ptr;
-  }
-
-  void operator=(T* t) {
-    ptr = t;
-    MOZ_ASSERT(ptr);
-#ifdef DEBUG
-    inited = true;
-#endif
-  }
-
-  template<typename U>
-  void operator=(U* t) {
-    ptr = t->ToAStringPtr();
-    MOZ_ASSERT(ptr);
-#ifdef DEBUG
-    inited = true;
-#endif
-  }
-
-  T* Ptr() {
-    MOZ_ASSERT(inited);
-    MOZ_ASSERT(ptr, "NonNull<T> was set to null");
-    return ptr;
-  }
-
-  // Make us work with smart-ptr helpers that expect a get()
-  T* get() const {
-    MOZ_ASSERT(inited);
-    MOZ_ASSERT(ptr);
-    return ptr;
-  }
-
-protected:
-  T* ptr;
-#ifdef DEBUG
-  bool inited;
-#endif
-};
-
-template<class T>
 class OwningNonNull
 {
 public:
   OwningNonNull()
 #ifdef DEBUG
     : inited(false)
 #endif
   {}
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1615,17 +1615,16 @@ addExternalIface('DeviceRotationRate', h
 addExternalIface('mozIDOMApplication', nativeType='mozIDOMApplication', headerFile='nsIDOMApplicationRegistry.h')
 addExternalIface('CSSRuleList')
 addExternalIface('DOMStringList')
 addExternalIface('RTCDataChannel', nativeType='nsIDOMDataChannel')
 addExternalIface('File')
 addExternalIface('FileCallback', nativeType='nsIFileCallback',
                  headerFile='nsIDOMHTMLCanvasElement.h')
 addExternalIface('HitRegionOptions', nativeType='nsISupports')
-addExternalIface('HTMLFormElement', nativeType='mozilla::dom::HTMLFormElement')
 addExternalIface('IDBOpenDBRequest', nativeType='nsIIDBOpenDBRequest')
 addExternalIface('imgINotificationObserver', nativeType='imgINotificationObserver')
 addExternalIface('imgIRequest', nativeType='imgIRequest', notflattened=True)
 addExternalIface('LockedFile')
 addExternalIface('MediaList')
 addExternalIface('MenuBuilder', nativeType='nsIMenuBuilder', notflattened=True)
 addExternalIface('MozBoxObject', nativeType='nsIBoxObject')
 addExternalIface('MozControllers', nativeType='nsIControllers')
--- a/dom/webidl/FormData.webidl
+++ b/dom/webidl/FormData.webidl
@@ -2,15 +2,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  * The origin of this IDL file is
  * http://xhr.spec.whatwg.org
  */
 
-interface HTMLFormElement;
-
 [Constructor(optional HTMLFormElement form)]
 interface FormData {
   void append(DOMString name, Blob value, optional DOMString filename);
   void append(DOMString name, DOMString value);
-};
\ No newline at end of file
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/HTMLFormElement.webidl
@@ -0,0 +1,48 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://www.whatwg.org/specs/web-apps/current-work/#htmlformelement
+ *
+ * © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and
+ * Opera Software ASA. You are granted a license to use, reproduce
+ * and create derivative works of this document.
+ */
+
+[OverrideBuiltins]
+interface HTMLFormElement : HTMLElement {
+           [Pure, SetterThrows]
+           attribute DOMString acceptCharset;
+           [Pure, SetterThrows]
+           attribute DOMString action;
+           [Pure, SetterThrows]
+           attribute DOMString autocomplete;
+           [Pure, SetterThrows]
+           attribute DOMString enctype;
+           [Pure, SetterThrows]
+           attribute DOMString encoding;
+           [Pure, SetterThrows]
+           attribute DOMString method;
+           [Pure, SetterThrows]
+           attribute DOMString name;
+           [Pure, SetterThrows]
+           attribute boolean noValidate;
+           [Pure, SetterThrows]
+           attribute DOMString target;
+
+  [Constant]
+  readonly attribute HTMLCollection elements;
+  [Pure]
+  readonly attribute long length;
+
+  getter Element (unsigned long index);
+  // TODO this should be: getter (RadioNodeList or HTMLInputElement or HTMLImageElement) (DOMString name);
+  getter nsISupports (DOMString name);
+
+  [Throws]
+  void submit();
+  void reset();
+  boolean checkValidity();
+};
--- a/dom/webidl/WebIDL.mk
+++ b/dom/webidl/WebIDL.mk
@@ -104,16 +104,17 @@ webidl_files = \
   HTMLDirectoryElement.webidl \
   HTMLDivElement.webidl \
   HTMLDListElement.webidl \
   HTMLDocument.webidl \
   HTMLElement.webidl \
   HTMLEmbedElement.webidl \
   HTMLFieldSetElement.webidl \
   HTMLFontElement.webidl \
+  HTMLFormElement.webidl \
   HTMLFrameElement.webidl \
   HTMLFrameSetElement.webidl \
   HTMLHeadElement.webidl \
   HTMLHeadingElement.webidl \
   HTMLHRElement.webidl \
   HTMLHtmlElement.webidl \
   HTMLIFrameElement.webidl \
   HTMLImageElement.webidl \
--- a/js/xpconnect/src/dom_quickstubs.qsconf
+++ b/js/xpconnect/src/dom_quickstubs.qsconf
@@ -55,24 +55,16 @@ members = [
 
     'nsIDOMProgressEvent.lengthComputable',
     'nsIDOMProgressEvent.loaded',
     'nsIDOMProgressEvent.total',
     #XXX Can't quickstub initProgressEvent because it has long long parameters.
 
     'nsIDOMTouchList.*',
 
-    # dom/interfaces/html
-    'nsIDOMHTMLFormElement.elements',
-    'nsIDOMHTMLFormElement.name',
-    'nsIDOMHTMLFormElement.submit',
-    'nsIDOMHTMLFormElement.length',
-    'nsIDOMHTMLFormElement.target',
-    'nsIDOMHTMLFormElement.action',
-
     # dom/interfaces/storage
     'nsIDOMToString.toString',
     'nsIDOMStorage.setItem',
     'nsIDOMStorage.length',
     'nsIDOMStorage.getItem',
     'nsIDOMStorage.key',
     'nsIDOMStorage.removeItem',
     'nsIDOMStorage.clear',