Bug 870787 - Improve named getter for form, r=bz
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 18 Jun 2013 08:53:23 -0400
changeset 146958 e5b2e2ee91e00287a3c70d28859647f2cfb8694d
parent 146957 5b36a59bf920a1b22afc318fb5c05e10c98efcc8
child 146959 d02f89580ad9589f6452ce76216f9697bcaa62a4
push id2697
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 18:49:53 +0000
treeherdermozilla-beta@dfec938c7b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs870787
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 870787 - Improve named getter for form, r=bz
content/base/public/nsContentUtils.h
content/base/src/nsContentList.cpp
content/base/src/nsContentList.h
content/base/src/nsContentUtils.cpp
content/base/src/nsNodeUtils.cpp
content/html/content/src/HTMLImageElement.cpp
content/html/content/src/HTMLImageElement.h
content/html/content/src/nsGenericHTMLElement.h
content/html/content/src/nsHTMLFormElement.cpp
content/html/content/src/nsHTMLFormElement.h
content/html/content/test/Makefile.in
content/html/content/test/test_bug870787.html
content/html/document/src/nsHTMLDocument.cpp
content/html/document/src/nsHTMLDocument.h
content/html/document/src/nsIHTMLDocument.h
parser/html/nsHtml5TreeOperation.cpp
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -505,27 +505,16 @@ public:
    * @return boolean indicating whether a BOM was detected.
    */
   static bool CheckForBOM(const unsigned char* aBuffer, uint32_t aLength,
                           nsACString& aCharset);
 
   static nsresult GuessCharset(const char *aData, uint32_t aDataLen,
                                nsACString &aCharset);
 
-  /**
-   * Determine whether aContent is in some way associated with aForm.  If the
-   * form is a container the only elements that are considered to be associated
-   * with a form are the elements that are contained within the form. If the
-   * form is a leaf element then all elements will be accepted into this list,
-   * since this can happen due to content fixup when a form spans table rows or
-   * table cells.
-   */
-  static bool BelongsInForm(nsIContent *aForm,
-                              nsIContent *aContent);
-
   static nsresult CheckQName(const nsAString& aQualifiedName,
                              bool aNamespaceAware = true,
                              const PRUnichar** aColon = nullptr);
 
   static nsresult SplitQName(const nsIContent* aNamespaceResolver,
                              const nsAFlatString& aQName,
                              int32_t *aNamespace, nsIAtom **aLocalName);
 
--- a/content/base/src/nsContentList.cpp
+++ b/content/base/src/nsContentList.cpp
@@ -135,36 +135,16 @@ NS_IMPL_ADDREF_INHERITED(nsSimpleContent
 NS_IMPL_RELEASE_INHERITED(nsSimpleContentList, nsBaseContentList)
 
 JSObject*
 nsSimpleContentList::WrapObject(JSContext *cx, JS::Handle<JSObject*> scope)
 {
   return NodeListBinding::Wrap(cx, scope, this);
 }
 
-// nsFormContentList
-
-nsFormContentList::nsFormContentList(nsIContent *aForm,
-                                     nsBaseContentList& aContentList)
-  : nsSimpleContentList(aForm)
-{
-
-  // move elements that belong to mForm into this content list
-
-  uint32_t i, length = 0;
-  aContentList.GetLength(&length);
-
-  for (i = 0; i < length; i++) {
-    nsIContent *c = aContentList.Item(i);
-    if (c && nsContentUtils::BelongsInForm(aForm, c)) {
-      AppendElement(c);
-    }
-  }
-}
-
 // Hashtable for storing nsContentLists
 static PLDHashTable gContentListHashTable;
 
 struct ContentListHashEntry : public PLDHashEntryHdr
 {
   nsContentList* mContentList;
 };
 
--- a/content/base/src/nsContentList.h
+++ b/content/base/src/nsContentList.h
@@ -119,26 +119,16 @@ public:
   virtual JSObject* WrapObject(JSContext *cx,
                                JS::Handle<JSObject*> scope) MOZ_OVERRIDE;
 
 private:
   // This has to be a strong reference, the root might go away before the list.
   nsCOMPtr<nsINode> mRoot;
 };
 
-// This class is used only by form element code and this is a static
-// list of elements. NOTE! This list holds strong references to
-// the elements in the list.
-class nsFormContentList : public nsSimpleContentList
-{
-public:
-  nsFormContentList(nsIContent *aForm,
-                    nsBaseContentList& aContentList);
-};
-
 /**
  * Class that's used as the key to hash nsContentList implementations
  * for fast retrieval
  */
 struct nsContentListKey
 {
   nsContentListKey(nsINode* aRootNode,
                    int32_t aMatchNameSpaceId,
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -2361,72 +2361,16 @@ nsContentUtils::NewURIWithDocumentCharse
                                           nsIURI* aBaseURI)
 {
   return NS_NewURI(aResult, aSpec,
                    aDocument ? aDocument->GetDocumentCharacterSet().get() : nullptr,
                    aBaseURI, sIOService);
 }
 
 // static
-bool
-nsContentUtils::BelongsInForm(nsIContent *aForm,
-                              nsIContent *aContent)
-{
-  NS_PRECONDITION(aForm, "Must have a form");
-  NS_PRECONDITION(aContent, "Must have a content node");
-
-  if (aForm == aContent) {
-    // A form does not belong inside itself, so we return false here
-
-    return false;
-  }
-
-  nsIContent* content = aContent->GetParent();
-
-  while (content) {
-    if (content == aForm) {
-      // aContent is contained within the form so we return true.
-
-      return true;
-    }
-
-    if (content->Tag() == nsGkAtoms::form &&
-        content->IsHTML()) {
-      // The child is contained within a form, but not the right form
-      // so we ignore it.
-
-      return false;
-    }
-
-    content = content->GetParent();
-  }
-
-  if (aForm->GetChildCount() > 0) {
-    // The form is a container but aContent wasn't inside the form,
-    // return false
-
-    return false;
-  }
-
-  // The form is a leaf and aContent wasn't inside any other form so
-  // we check whether the content comes after the form.  If it does,
-  // return true.  If it does not, then it couldn't have been inside
-  // the form in the HTML.
-  if (PositionIsBefore(aForm, aContent)) {
-    // We could be in this form!
-    // In the future, we may want to get document.forms, look at the
-    // form after aForm, and if aContent is after that form after
-    // aForm return false here....
-    return true;
-  }
-
-  return false;
-}
-
-// static
 nsresult
 nsContentUtils::CheckQName(const nsAString& aQualifiedName,
                            bool aNamespaceAware,
                            const PRUnichar** aColon)
 {
   const char* colon = nullptr;
   const PRUnichar* begin = aQualifiedName.BeginReading();
   const PRUnichar* end = aQualifiedName.EndReading();
--- a/content/base/src/nsNodeUtils.cpp
+++ b/content/base/src/nsNodeUtils.cpp
@@ -20,16 +20,17 @@
 #include "nsCOMArray.h"
 #include "nsPIDOMWindow.h"
 #include "nsDocument.h"
 #ifdef MOZ_XUL
 #include "nsXULElement.h"
 #endif
 #include "nsBindingManager.h"
 #include "nsGenericHTMLElement.h"
+#include "mozilla/dom/HTMLImageElement.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsObjectLoadingContent.h"
 #include "nsDOMMutationObserver.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/HTMLTemplateElement.h"
 
 using namespace mozilla::dom;
@@ -211,16 +212,21 @@ nsNodeUtils::LastRelease(nsINode* aNode)
 
     // I wonder whether it's faster to do the HasFlag check first....
     if (aNode->IsNodeOfType(nsINode::eHTML_FORM_CONTROL) &&
         aNode->HasFlag(ADDED_TO_FORM)) {
       // Tell the form (if any) this node is going away.  Don't
       // notify, since we're being destroyed in any case.
       static_cast<nsGenericHTMLFormElement*>(aNode)->ClearForm(true);
     }
+
+    if (aNode->IsElement() && aNode->AsElement()->IsHTML(nsGkAtoms::img)) {
+      HTMLImageElement* imageElem = static_cast<HTMLImageElement*>(aNode);
+      imageElem->ClearForm(true);
+    }
   }
   aNode->UnsetFlags(NODE_HAS_PROPERTIES);
 
   if (aNode->NodeType() != nsIDOMNode::DOCUMENT_NODE &&
       aNode->HasFlag(NODE_HAS_LISTENERMANAGER)) {
 #ifdef DEBUG
     if (nsContentUtils::IsInitialized()) {
       nsEventListenerManager* manager =
--- a/content/html/content/src/HTMLImageElement.cpp
+++ b/content/html/content/src/HTMLImageElement.cpp
@@ -20,16 +20,17 @@
 #include "nsNetUtil.h"
 #include "nsContentUtils.h"
 #include "nsIFrame.h"
 #include "nsNodeInfoManager.h"
 #include "nsGUIEvent.h"
 #include "nsContentPolicyUtils.h"
 #include "nsIDOMWindow.h"
 #include "nsFocusManager.h"
+#include "nsHTMLFormElement.h"
 
 #include "imgIContainer.h"
 #include "imgILoader.h"
 #include "imgINotificationObserver.h"
 #include "imgRequestProxy.h"
 
 #include "nsILoadGroup.h"
 
@@ -62,16 +63,17 @@ NS_NewHTMLImageElement(already_AddRefed<
   return new mozilla::dom::HTMLImageElement(nodeInfo.forget());
 }
 
 namespace mozilla {
 namespace dom {
 
 HTMLImageElement::HTMLImageElement(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo)
+  , mForm(nullptr)
 {
   // We start out broken
   AddStatesSilently(NS_EVENT_STATE_BROKEN);
   SetIsDOMBinding();
 }
 
 HTMLImageElement::~HTMLImageElement()
 {
@@ -79,17 +81,17 @@ HTMLImageElement::~HTMLImageElement()
 }
 
 
 NS_IMPL_ADDREF_INHERITED(HTMLImageElement, Element)
 NS_IMPL_RELEASE_INHERITED(HTMLImageElement, Element)
 
 
 // QueryInterface implementation for HTMLImageElement
-NS_INTERFACE_TABLE_HEAD(HTMLImageElement)
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLImageElement)
   NS_HTML_CONTENT_INTERFACES(nsGenericHTMLElement)
   NS_INTERFACE_TABLE_INHERITED4(HTMLImageElement,
                                 nsIDOMHTMLImageElement,
                                 nsIImageLoadingContent,
                                 imgIOnloadBlocker,
                                 imgINotificationObserver)
   NS_INTERFACE_TABLE_TO_MAP_SEGUE
 NS_ELEMENT_INTERFACE_MAP_END
@@ -292,16 +294,56 @@ HTMLImageElement::IsAttributeMapped(cons
 nsMapRuleToAttributesFunc
 HTMLImageElement::GetAttributeMappingFunction() const
 {
   return &MapAttributesIntoRule;
 }
 
 
 nsresult
+HTMLImageElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+                                const nsAttrValueOrString* aValue,
+                                bool aNotify)
+{
+
+  if (aNameSpaceID == kNameSpaceID_None && mForm &&
+      (aName == nsGkAtoms::name || aName == nsGkAtoms::id)) {
+    // remove the image from the hashtable as needed
+    nsAutoString tmp;
+    GetAttr(kNameSpaceID_None, aName, tmp);
+
+    if (!tmp.IsEmpty()) {
+      mForm->RemoveImageElementFromTable(this, tmp);
+    }
+  }
+
+  return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName,
+                                             aValue, aNotify);
+}
+
+nsresult
+HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+                               const nsAttrValue* aValue, bool aNotify)
+{
+  if (aNameSpaceID == kNameSpaceID_None && mForm &&
+      (aName == nsGkAtoms::name || aName == nsGkAtoms::id) &&
+      aValue && !aValue->IsEmptyString()) {
+    // add the image to the hashtable as needed
+    NS_ABORT_IF_FALSE(aValue->Type() == nsAttrValue::eAtom,
+      "Expected atom value for name/id");
+    mForm->AddImageElementToTable(this,
+      nsDependentAtomString(aValue->GetAtomValue()));
+  }
+
+  return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
+                                            aValue, aNotify);
+}
+
+
+nsresult
 HTMLImageElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
 {
   // If we are a map and get a mouse click, don't let it be handled by
   // the Generic Element as this could cause a click event to fire
   // twice, once by the image frame for the map and once by the Anchor
   // element. (bug 39723)
   if (aVisitor.mEvent->eventStructType == NS_MOUSE_EVENT &&
       aVisitor.mEvent->message == NS_MOUSE_CLICK &&
@@ -409,16 +451,20 @@ HTMLImageElement::BindToTree(nsIDocument
   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
                                                  aBindingParent,
                                                  aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsImageLoadingContent::BindToTree(aDocument, aParent, aBindingParent,
                                     aCompileEventHandlers);
 
+  if (aParent) {
+    UpdateFormOwner();
+  }
+
   if (HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
     // FIXME: Bug 660963 it would be nice if we could just have
     // ClearBrokenState update our state and do it fast...
     ClearBrokenState();
     RemoveStatesSilently(NS_EVENT_STATE_BROKEN);
     // If loading is temporarily disabled, don't even launch MaybeLoadImage.
     // Otherwise MaybeLoadImage may run later when someone has reenabled
     // loading.
@@ -429,21 +475,56 @@ HTMLImageElement::BindToTree(nsIDocument
   }
 
   return rv;
 }
 
 void
 HTMLImageElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
+  if (mForm) {
+    if (aNullParent || !FindAncestorForm(mForm)) {
+      ClearForm(true);
+    } else {
+      UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
+    }
+  }
+
   nsImageLoadingContent::UnbindFromTree(aDeep, aNullParent);
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 }
 
 void
+HTMLImageElement::UpdateFormOwner()
+{
+  if (!mForm) {
+    mForm = FindAncestorForm();
+  }
+
+  if (mForm && !HasFlag(ADDED_TO_FORM)) {
+    // Now we need to add ourselves to the form
+    nsAutoString nameVal, idVal;
+    GetAttr(kNameSpaceID_None, nsGkAtoms::name, nameVal);
+    GetAttr(kNameSpaceID_None, nsGkAtoms::id, idVal);
+
+    SetFlags(ADDED_TO_FORM);
+
+    mForm->AddImageElement(this);
+
+    if (!nameVal.IsEmpty()) {
+      mForm->AddImageElementToTable(this, nameVal);
+    }
+
+    if (!idVal.IsEmpty()) {
+      mForm->AddImageElementToTable(this, idVal);
+    }
+  }
+}
+
+void
 HTMLImageElement::MaybeLoadImage()
 {
   // Our base URI may have changed; claim that our URI changed, and the
   // nsImageLoadingContent will decide whether a new image load is warranted.
   // Note, check LoadingEnabled() after LoadImage call.
   nsAutoString uri;
   if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, uri) &&
       (NS_FAILED(LoadImage(uri, false, true)) ||
@@ -572,10 +653,58 @@ HTMLImageElement::GetCORSMode()
 }
 
 JSObject*
 HTMLImageElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return HTMLImageElementBinding::Wrap(aCx, aScope, this);
 }
 
+#ifdef DEBUG
+nsIDOMHTMLFormElement*
+HTMLImageElement::GetForm() const
+{
+  return mForm;
+}
+#endif
+
+void
+HTMLImageElement::SetForm(nsIDOMHTMLFormElement* aForm)
+{
+  NS_PRECONDITION(aForm, "Don't pass null here");
+  NS_ASSERTION(!mForm,
+               "We don't support switching from one non-null form to another.");
+
+  mForm = static_cast<nsHTMLFormElement*>(aForm);
+}
+
+void
+HTMLImageElement::ClearForm(bool aRemoveFromForm)
+{
+  NS_ASSERTION((mForm != nullptr) == HasFlag(ADDED_TO_FORM),
+               "Form control should have had flag set correctly");
+
+  if (!mForm) {
+    return;
+  }
+
+  if (aRemoveFromForm) {
+    nsAutoString nameVal, idVal;
+    GetAttr(kNameSpaceID_None, nsGkAtoms::name, nameVal);
+    GetAttr(kNameSpaceID_None, nsGkAtoms::id, idVal);
+
+    mForm->RemoveImageElement(this);
+
+    if (!nameVal.IsEmpty()) {
+      mForm->RemoveImageElementFromTable(this, nameVal);
+    }
+
+    if (!idVal.IsEmpty()) {
+      mForm->RemoveImageElementFromTable(this, idVal);
+    }
+  }
+
+  UnsetFlags(ADDED_TO_FORM);
+  mForm = nullptr;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/content/html/content/src/HTMLImageElement.h
+++ b/content/html/content/src/HTMLImageElement.h
@@ -169,20 +169,38 @@ public:
   int32_t X();
   int32_t Y();
   // Uses XPCOM GetLowsrc.
   void SetLowsrc(const nsAString& aLowsrc, ErrorResult& aError)
   {
     SetHTMLAttr(nsGkAtoms::lowsrc, aLowsrc, aError);
   }
 
+#ifdef DEBUG
+  nsIDOMHTMLFormElement* GetForm() const;
+#endif
+  void SetForm(nsIDOMHTMLFormElement* aForm);
+  void ClearForm(bool aRemoveFromForm);
+
 protected:
   CSSIntPoint GetXY();
   virtual void GetItemValueText(nsAString& text) MOZ_OVERRIDE;
   virtual void SetItemValueText(const nsAString& text) MOZ_OVERRIDE;
   virtual JSObject* WrapNode(JSContext *aCx,
                              JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+  void UpdateFormOwner();
+
+  virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+                                 const nsAttrValueOrString* aValue,
+                                 bool aNotify) MOZ_OVERRIDE;
+
+  virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+                                const nsAttrValue* aValue, bool aNotify) MOZ_OVERRIDE;
+
+  // This is a weak reference that this element and the HTMLFormElement
+  // cooperate in maintaining.
+  nsHTMLFormElement* mForm;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_HTMLImageElement_h */
--- a/content/html/content/src/nsGenericHTMLElement.h
+++ b/content/html/content/src/nsGenericHTMLElement.h
@@ -1042,25 +1042,26 @@ namespace dom {
 class HTMLFieldSetElement;
 }
 }
 
 #define FORM_ELEMENT_FLAG_BIT(n_) NODE_FLAG_BIT(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + (n_))
 
 // Form element specific bits
 enum {
-  // If this flag is set on an nsGenericHTMLFormElement, that means that we have
-  // added ourselves to our mForm.  It's possible to have a non-null mForm, but
-  // not have this flag set.  That happens when the form is set via the content
-  // sink.
+  // If this flag is set on an nsGenericHTMLFormElement or an HTMLImageElement,
+  // that means that we have added ourselves to our mForm.  It's possible to
+  // have a non-null mForm, but not have this flag set.  That happens when the
+  // form is set via the content sink.
   ADDED_TO_FORM =                         FORM_ELEMENT_FLAG_BIT(0),
 
-  // If this flag is set on an nsGenericHTMLFormElement, that means that its form
-  // is in the process of being unbound from the tree, and this form element
-  // hasn't re-found its form in nsGenericHTMLFormElement::UnbindFromTree yet.
+  // If this flag is set on an nsGenericHTMLFormElement or an HTMLImageElement,
+  // that means that its form is in the process of being unbound from the tree,
+  // and this form element hasn't re-found its form in
+  // nsGenericHTMLFormElement::UnbindFromTree yet.
   MAYBE_ORPHAN_FORM_ELEMENT =             FORM_ELEMENT_FLAG_BIT(1)
 };
 
 // NOTE: I don't think it's possible to have the above two flags set at the
 // same time, so if it becomes an issue we can probably merge them into the
 // same bit.  --bz
 
 ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 2);
--- a/content/html/content/src/nsHTMLFormElement.cpp
+++ b/content/html/content/src/nsHTMLFormElement.cpp
@@ -49,16 +49,19 @@
 
 #include "nsIConstraintValidation.h"
 
 #include "nsIDOMHTMLButtonElement.h"
 #include "mozilla/dom/HTMLCollectionBinding.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "nsSandboxFlags.h"
 
+// images
+#include "mozilla/dom/HTMLImageElement.h"
+
 using namespace mozilla::dom;
 
 static const int NS_FORM_CONTROL_LIST_HASHTABLE_SIZE = 16;
 
 static const uint8_t NS_FORM_AUTOCOMPLETE_ON  = 1;
 static const uint8_t NS_FORM_AUTOCOMPLETE_OFF = 0;
 
 static const nsAttrValue::EnumTable kFormAutocompleteTable[] = {
@@ -99,16 +102,18 @@ public:
   }
 
   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 AddImageElementToTable(HTMLImageElement* aChild,
+                                  const nsAString& aName);
   nsresult RemoveElementFromTable(nsGenericHTMLFormElement* aChild,
                                   const nsAString& aName);
   nsresult IndexOfControl(nsIFormControl* aControl,
                           int32_t* aIndex);
 
   nsISupports* NamedItemInternal(const nsAString& aName, bool aFlushContent);
   
   /**
@@ -236,23 +241,26 @@ nsHTMLFormElement::nsHTMLFormElement(alr
     mPendingSubmission(nullptr),
     mSubmittingRequest(nullptr),
     mDefaultSubmitElement(nullptr),
     mFirstSubmitInElements(nullptr),
     mFirstSubmitNotInElements(nullptr),
     mInvalidElementsCount(0),
     mEverTriedInvalidSubmit(false)
 {
+  mImageNameLookupTable.Init(NS_FORM_CONTROL_LIST_HASHTABLE_SIZE);
 }
 
 nsHTMLFormElement::~nsHTMLFormElement()
 {
   if (mControls) {
     mControls->DropFormReference();
   }
+
+  Clear();
 }
 
 nsresult
 nsHTMLFormElement::Init()
 {
   mControls = new nsFormControlList(this);
   if (!mControls) {
     return NS_ERROR_OUT_OF_MEMORY;
@@ -285,19 +293,25 @@ ElementTraverser(const nsAString& key, n
  
   cb->NoteXPCOMChild(element);
   return PL_DHASH_NEXT;
 }
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsHTMLFormElement,
                                                   nsGenericHTMLElement)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControls)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageNameLookupTable)
   tmp->mSelectedRadioButtons.EnumerateRead(ElementTraverser, &cb);
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHTMLFormElement,
+                                                nsGenericHTMLElement)
+  tmp->Clear();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
 NS_IMPL_ADDREF_INHERITED(nsHTMLFormElement, Element)
 NS_IMPL_RELEASE_INHERITED(nsHTMLFormElement, Element)
 
 
 DOMCI_NODE_DATA(HTMLFormElement, nsHTMLFormElement)
 
 // QueryInterface implementation for nsHTMLFormElement
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHTMLFormElement)
@@ -450,18 +464,19 @@ nsHTMLFormElement::BindToTree(nsIDocumen
   nsCOMPtr<nsIHTMLDocument> htmlDoc(do_QueryInterface(aDocument));
   if (htmlDoc) {
     htmlDoc->AddedForm();
   }
 
   return rv;
 }
 
+template<typename T>
 static void
-MarkOrphans(const nsTArray<nsGenericHTMLFormElement*>& aArray)
+MarkOrphans(const nsTArray<T*>& aArray)
 {
   uint32_t length = aArray.Length();
   for (uint32_t i = 0; i < length; ++i) {
     aArray[i]->SetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
   }
 }
 
 static void
@@ -478,18 +493,17 @@ CollectOrphans(nsINode* aRemovalRoot,
   // Walk backwards so that if we remove elements we can just keep iterating
   uint32_t length = aArray.Length();
   for (uint32_t i = length; i > 0; --i) {
     nsGenericHTMLFormElement* node = aArray[i-1];
 
     // Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the
     // node is in fact a descendant of the form and hence should stay in the
     // form.  If it _is_ set, then we need to check whether the node is a
-    // descendant of aRemovalRoot.  If it is, we leave it in the form.  See
-    // also the code in nsGenericHTMLFormElement::FindForm.
+    // descendant of aRemovalRoot.  If it is, we leave it in the form.
 #ifdef DEBUG
     bool removed = false;
 #endif
     if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) {
       node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
       if (!nsContentUtils::ContentIsDescendantOf(node, aRemovalRoot)) {
         node->ClearForm(true);
 
@@ -506,51 +520,97 @@ CollectOrphans(nsINode* aRemovalRoot,
       nsCOMPtr<nsIDOMHTMLFormElement> form;
       node->GetForm(getter_AddRefs(form));
       NS_ASSERTION(form == aThisForm, "How did that happen?");
     }
 #endif /* DEBUG */
   }
 }
 
+static void
+CollectOrphans(nsINode* aRemovalRoot,
+               const nsTArray<HTMLImageElement*>& aArray
+#ifdef DEBUG
+               , nsIDOMHTMLFormElement* aThisForm
+#endif
+               )
+{
+  // Walk backwards so that if we remove elements we can just keep iterating
+  uint32_t length = aArray.Length();
+  for (uint32_t i = length; i > 0; --i) {
+    HTMLImageElement* node = aArray[i-1];
+
+    // Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the
+    // node is in fact a descendant of the form and hence should stay in the
+    // form.  If it _is_ set, then we need to check whether the node is a
+    // descendant of aRemovalRoot.  If it is, we leave it in the form.
+#ifdef DEBUG
+    bool removed = false;
+#endif
+    if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) {
+      node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
+      if (!nsContentUtils::ContentIsDescendantOf(node, aRemovalRoot)) {
+        node->ClearForm(true);
+
+#ifdef DEBUG
+        removed = true;
+#endif
+      }
+    }
+
+#ifdef DEBUG
+    if (!removed) {
+      nsCOMPtr<nsIDOMHTMLFormElement> form = node->GetForm();
+      NS_ASSERTION(form == aThisForm, "How did that happen?");
+    }
+#endif /* DEBUG */
+  }
+}
+
 void
 nsHTMLFormElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
   nsCOMPtr<nsIHTMLDocument> oldDocument = do_QueryInterface(GetCurrentDoc());
 
   // Mark all of our controls as maybe being orphans
   MarkOrphans(mControls->mElements);
   MarkOrphans(mControls->mNotInElements);
+  MarkOrphans(mImageElements);
 
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 
   nsINode* ancestor = this;
   nsINode* cur;
   do {
     cur = ancestor->GetParentNode();
     if (!cur) {
       break;
     }
     ancestor = cur;
   } while (1);
   
   CollectOrphans(ancestor, mControls->mElements
 #ifdef DEBUG
                  , this
-#endif                 
+#endif
                  );
   CollectOrphans(ancestor, mControls->mNotInElements
 #ifdef DEBUG
                  , this
-#endif                 
+#endif
+                 );
+  CollectOrphans(ancestor, mImageElements
+#ifdef DEBUG
+                 , this
+#endif
                  );
 
   if (oldDocument) {
     oldDocument->RemovedForm();
-  }     
+  }
   ForgetCurrentSubmission();
 }
 
 nsresult
 nsHTMLFormElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
 {
   aVisitor.mWantsWillHandleEvent = true;
   if (aVisitor.mEvent->originalTarget == static_cast<nsIContent*>(this)) {
@@ -1029,45 +1089,44 @@ nsHTMLFormElement::GetElementAt(int32_t 
  * @param aControl1 First control to compare.
  * @param aControl2 Second control to compare.
  * @param aForm Parent form of the controls.
  * @return < 0 if aControl1 is before aControl2,
  *         > 0 if aControl1 is after aControl2,
  *         0 otherwise
  */
 static inline int32_t
-CompareFormControlPosition(nsGenericHTMLFormElement *aControl1,
-                           nsGenericHTMLFormElement *aControl2,
+CompareFormControlPosition(Element *aElement1, Element *aElement2,
                            const nsIContent* aForm)
 {
-  NS_ASSERTION(aControl1 != aControl2, "Comparing a form control to itself");
+  NS_ASSERTION(aElement1 != aElement2, "Comparing a form control to itself");
 
   // 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((aControl1->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
-                aControl1->GetParent()) &&
-               (aControl2->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
-                aControl2->GetParent()),
+  NS_ASSERTION((aElement1->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
+                aElement1->GetParent()) &&
+               (aElement2->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
+                aElement2->GetParent()),
                "Form controls should always have parents");
 
   // If we pass aForm, we are assuming both controls are form descendants which
   // is not always the case. This function should work but maybe slower.
   // However, checking if both elements are form descendants may be slow too...
   // TODO: remove the prevent asserts fix, see bug 598468.
 #ifdef DEBUG
   nsLayoutUtils::gPreventAssertInCompareTreePosition = true;
-  int32_t rVal = nsLayoutUtils::CompareTreePosition(aControl1, aControl2, aForm);
+  int32_t rVal = nsLayoutUtils::CompareTreePosition(aElement1, aElement2, aForm);
   nsLayoutUtils::gPreventAssertInCompareTreePosition = false;
 
   return rVal;
 #else // DEBUG
-  return nsLayoutUtils::CompareTreePosition(aControl1, aControl2, aForm);
+  return nsLayoutUtils::CompareTreePosition(aElement1, aElement2, aForm);
 #endif // DEBUG
 }
- 
+
 #ifdef DEBUG
 /**
  * Checks that all form elements are in document order. Asserts if any pair of
  * consecutive elements are not in increasing document order.
  *
  * @param aControls List of form controls to check.
  * @param aForm Parent form of the controls.
  */
@@ -1101,72 +1160,84 @@ nsHTMLFormElement::PostPasswordEvent()
   }
 
   nsRefPtr<FormPasswordEvent> event =
     new FormPasswordEvent(this, NS_LITERAL_STRING("DOMFormHasPassword"));
   mFormPasswordEvent = event;
   event->PostDOMEvent();
 }
 
+// This function return true if the element, once appended, is the last one in
+// the array.
+template<typename ElementType>
+static bool
+AddElementToList(nsTArray<ElementType*>& aList, ElementType* aChild,
+                 nsHTMLFormElement* aForm)
+{
+  NS_ASSERTION(aList.IndexOf(aChild) == aList.NoIndex,
+               "aChild already in aList");
+
+  uint32_t count = aList.Length();
+  ElementType* element;
+  bool lastElement = false;
+
+  // Optimize most common case where we insert at the end.
+  int32_t position = -1;
+  if (count > 0) {
+    element = aList[count - 1];
+    position = CompareFormControlPosition(aChild, element, aForm);
+  }
+
+  // If this item comes after the last element, or the elements array is
+  // empty, we append to the end. Otherwise, we do a binary search to
+  // determine where the element should go.
+  if (position >= 0 || count == 0) {
+    // WEAK - don't addref
+    aList.AppendElement(aChild);
+    lastElement = true;
+  }
+  else {
+    int32_t low = 0, mid, high;
+    high = count - 1;
+
+    while (low <= high) {
+      mid = (low + high) / 2;
+
+      element = aList[mid];
+      position = CompareFormControlPosition(aChild, element, aForm);
+      if (position >= 0)
+        low = mid + 1;
+      else
+        high = mid - 1;
+    }
+
+    // WEAK - don't addref
+    aList.InsertElementAt(low, aChild);
+  }
+
+  return lastElement;
+}
+
 nsresult
 nsHTMLFormElement::AddElement(nsGenericHTMLFormElement* aChild,
                               bool aUpdateValidity, bool aNotify)
 {
   // 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(aChild->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
                aChild->GetParent(),
                "Form control should have a parent");
 
   // Determine whether to add the new element to the elements or
   // the not-in-elements list.
   bool childInElements = ShouldBeInElements(aChild);
   nsTArray<nsGenericHTMLFormElement*>& controlList = childInElements ?
       mControls->mElements : mControls->mNotInElements;
-  
-  NS_ASSERTION(controlList.IndexOf(aChild) == controlList.NoIndex,
-               "Form control already in form");
-
-  uint32_t count = controlList.Length();
-  nsGenericHTMLFormElement* element;
-  
-  // Optimize most common case where we insert at the end.
-  bool lastElement = false;
-  int32_t position = -1;
-  if (count > 0) {
-    element = controlList[count - 1];
-    position = CompareFormControlPosition(aChild, element, this);
-  }
-
-  // If this item comes after the last element, or the elements array is
-  // empty, we append to the end. Otherwise, we do a binary search to
-  // determine where the element should go.
-  if (position >= 0 || count == 0) {
-    // WEAK - don't addref
-    controlList.AppendElement(aChild);
-    lastElement = true;
-  }
-  else {
-    int32_t low = 0, mid, high;
-    high = count - 1;
-      
-    while (low <= high) {
-      mid = (low + high) / 2;
-        
-      element = controlList[mid];
-      position = CompareFormControlPosition(aChild, element, this);
-      if (position >= 0)
-        low = mid + 1;
-      else
-        high = mid - 1;
-    }
-      
-    // WEAK - don't addref
-    controlList.InsertElementAt(low, aChild);
-  }
+
+  bool lastElement = AddElementToList(controlList, aChild, this);
 
 #ifdef DEBUG
   AssertDocumentOrder(controlList, this);
 #endif
 
   int32_t type = aChild->GetType();
 
   //
@@ -1354,16 +1425,65 @@ nsHTMLFormElement::HandleDefaultSubmitRe
                    "What happened here?");
 
   // Notify about change if needed.
   if (mDefaultSubmitElement) {
     mDefaultSubmitElement->UpdateState(true);
   }
 }
 
+static nsresult
+RemoveElementFromTableInternal(
+  nsInterfaceHashtable<nsStringHashKey,nsISupports>& aTable,
+  nsIContent* aChild, const nsAString& aName)
+{
+  nsCOMPtr<nsISupports> supports;
+
+  if (!aTable.Get(aName, getter_AddRefs(supports)))
+    return NS_OK;
+
+  nsCOMPtr<nsIContent> content(do_QueryInterface(supports));
+
+  if (content) {
+    // Single element in the hash, just remove it if it's the one
+    // we're trying to remove...
+    if (content == aChild) {
+      aTable.Remove(aName);
+    }
+
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIDOMNodeList> nodeList(do_QueryInterface(supports));
+  NS_ENSURE_TRUE(nodeList, NS_ERROR_FAILURE);
+
+  // Upcast, uggly, but it works!
+  nsBaseContentList *list = static_cast<nsBaseContentList*>(nodeList.get());
+
+  list->RemoveElement(aChild);
+
+  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);
+  } 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);
+    }
+  }
+
+  return NS_OK;
+}
+
 nsresult
 nsHTMLFormElement::RemoveElementFromTable(nsGenericHTMLFormElement* aElement,
                                           const nsAString& aName)
 {
   return mControls->RemoveElementFromTable(aElement, aName);
 }
 
 already_AddRefed<nsISupports>
@@ -1372,23 +1492,23 @@ nsHTMLFormElement::FindNamedItem(const n
 {
   nsCOMPtr<nsISupports> result = DoResolveName(aName, true);
   if (result) {
     // FIXME Get the wrapper cache from DoResolveName.
     *aCache = nullptr;
     return result.forget();
   }
 
-  nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(GetCurrentDoc());
-  if (!htmlDoc) {
+  result = mImageNameLookupTable.GetWeak(aName);
+  if (result) {
     *aCache = nullptr;
-    return nullptr;
+    return result.forget();
   }
 
-  return htmlDoc->ResolveName(aName, this, aCache);
+  return nullptr;
 }
 
 already_AddRefed<nsISupports>
 nsHTMLFormElement::DoResolveName(const nsAString& aName,
                                  bool aFlushContent)
 {
   nsCOMPtr<nsISupports> result =
     mControls->NamedItemInternal(aName, aFlushContent);
@@ -1905,17 +2025,17 @@ nsHTMLFormElement::OnStatusChange(nsIWeb
 NS_IMETHODIMP
 nsHTMLFormElement::OnSecurityChange(nsIWebProgress* aWebProgress,
                                     nsIRequest* aRequest,
                                     uint32_t state)
 {
   NS_NOTREACHED("notification excluded in AddProgressListener(...)");
   return NS_OK;
 }
- 
+
 NS_IMETHODIMP_(int32_t)
 nsHTMLFormElement::IndexOfControl(nsIFormControl* aControl)
 {
   int32_t index = 0;
   return mControls->IndexOfControl(aControl, &index) == NS_OK ? index : 0;
 }
 
 void
@@ -2136,16 +2256,26 @@ nsHTMLFormElement::IntrinsicState() cons
     state |= NS_EVENT_STATE_INVALID;
   } else {
       state |= NS_EVENT_STATE_VALID;
   }
 
   return state;
 }
 
+void
+nsHTMLFormElement::Clear()
+{
+  for (int32_t i = mImageElements.Length() - 1; i >= 0; i--) {
+    mImageElements[i]->ClearForm(false);
+  }
+  mImageElements.Clear();
+  mImageNameLookupTable.Clear();
+}
+
 //----------------------------------------------------------------------
 // nsFormControlList implementation, this could go away if there were
 // a lightweight collection implementation somewhere
 
 nsFormControlList::nsFormControlList(nsHTMLFormElement* aForm) :
   mForm(aForm),
   // Initialize the elements list to have an initial capacity
   // of 8 to reduce allocations on small forms.
@@ -2224,17 +2354,17 @@ NS_INTERFACE_MAP_END
 
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFormControlList)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFormControlList)
 
 
 // nsIDOMHTMLCollection interface
 
-NS_IMETHODIMP    
+NS_IMETHODIMP
 nsFormControlList::GetLength(uint32_t* aLength)
 {
   FlushPendingNotifications();
   *aLength = mElements.Length();
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -2293,63 +2423,60 @@ nsFormControlList::NamedItemInternal(con
 {
   if (aFlushContent) {
     FlushPendingNotifications();
   }
 
   return mNameLookupTable.GetWeak(aName);
 }
 
-nsresult
-nsFormControlList::AddElementToTable(nsGenericHTMLFormElement* aChild,
-                                     const nsAString& aName)
+static nsresult
+AddElementToTableInternal(
+  nsInterfaceHashtable<nsStringHashKey,nsISupports>& aTable,
+  nsIContent* aChild, const nsAString& aName, nsHTMLFormElement* aForm)
 {
-  if (!ShouldBeInElements(aChild)) {
-    return NS_OK;
-  }
-
   nsCOMPtr<nsISupports> supports;
-  mNameLookupTable.Get(aName, getter_AddRefs(supports));
+  aTable.Get(aName, getter_AddRefs(supports));
 
   if (!supports) {
-    // No entry found, add the form control
-    mNameLookupTable.Put(aName, NS_ISUPPORTS_CAST(nsIContent*, aChild));
+    // No entry found, add the element
+    aTable.Put(aName, aChild);
   } 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(mForm);
+      nsSimpleContentList *list = new nsSimpleContentList(aForm);
 
       // 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);
 
-      list->AppendElement(newFirst ? aChild : content);
-      list->AppendElement(newFirst ? content : aChild);
+      list->AppendElement(newFirst ? aChild : content.get());
+      list->AppendElement(newFirst ? content.get() : aChild);
 
 
       nsCOMPtr<nsISupports> listSupports = do_QueryObject(list);
 
       // Replace the element with the list.
-      mNameLookupTable.Put(aName, listSupports);
+      aTable.Put(aName, listSupports);
     } else {
       // There's already a list in the hash, add the child to the list
       nsCOMPtr<nsIDOMNodeList> nodeList = do_QueryInterface(supports);
       NS_ENSURE_TRUE(nodeList, NS_ERROR_FAILURE);
 
       // Upcast, uggly, but it works!
       nsSimpleContentList *list =
         static_cast<nsSimpleContentList*>(nodeList.get());
@@ -2391,16 +2518,27 @@ nsFormControlList::AddElementToTable(nsG
       list->InsertElementAt(aChild, first);
     }
   }
 
   return NS_OK;
 }
 
 nsresult
+nsFormControlList::AddElementToTable(nsGenericHTMLFormElement* aChild,
+                                     const nsAString& aName)
+{
+  if (!ShouldBeInElements(aChild)) {
+    return NS_OK;
+  }
+
+  return AddElementToTableInternal(mNameLookupTable, aChild, aName, mForm);
+}
+
+nsresult
 nsFormControlList::IndexOfControl(nsIFormControl* aControl,
                                   int32_t* aIndex)
 {
   // Note -- not a DOM method; callers should handle flushing themselves
   
   NS_ENSURE_ARG_POINTER(aIndex);
 
   *aIndex = mElements.IndexOf(aControl);
@@ -2411,58 +2549,17 @@ nsFormControlList::IndexOfControl(nsIFor
 nsresult
 nsFormControlList::RemoveElementFromTable(nsGenericHTMLFormElement* aChild,
                                           const nsAString& aName)
 {
   if (!ShouldBeInElements(aChild)) {
     return NS_OK;
   }
 
-  nsCOMPtr<nsISupports> supports;
-
-  if (!mNameLookupTable.Get(aName, getter_AddRefs(supports)))
-    return NS_OK;
-
-  nsCOMPtr<nsIFormControl> fctrl(do_QueryInterface(supports));
-
-  if (fctrl) {
-    // Single element in the hash, just remove it if it's the one
-    // we're trying to remove...
-    if (fctrl == aChild) {
-      mNameLookupTable.Remove(aName);
-    }
-
-    return NS_OK;
-  }
-
-  nsCOMPtr<nsIDOMNodeList> nodeList(do_QueryInterface(supports));
-  NS_ENSURE_TRUE(nodeList, NS_ERROR_FAILURE);
-
-  // Upcast, uggly, but it works!
-  nsBaseContentList *list = static_cast<nsBaseContentList*>(nodeList.get());
-
-  list->RemoveElement(aChild);
-
-  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
-    mNameLookupTable.Remove(aName);
-  } 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) {
-      mNameLookupTable.Put(aName, node);
-    }
-  }
-
-  return NS_OK;
+  return RemoveElementFromTableInternal(mNameLookupTable, aChild, aName);
 }
 
 nsresult
 nsFormControlList::GetSortedControls(nsTArray<nsGenericHTMLFormElement*>& aControls) const
 {
 #ifdef DEBUG
   AssertDocumentOrder(mElements, mForm);
   AssertDocumentOrder(mNotInElements, mForm);
@@ -2576,8 +2673,40 @@ 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);
 }
+
+nsresult
+nsHTMLFormElement::AddImageElement(HTMLImageElement* aChild)
+{
+  AddElementToList(mImageElements, aChild, this);
+  return NS_OK;
+}
+
+nsresult
+nsHTMLFormElement::AddImageElementToTable(HTMLImageElement* aChild,
+                                          const nsAString& aName)
+{
+  return AddElementToTableInternal(mImageNameLookupTable, aChild, aName, this);
+}
+
+nsresult
+nsHTMLFormElement::RemoveImageElement(HTMLImageElement* aChild)
+{
+  uint32_t index = mImageElements.IndexOf(aChild);
+  NS_ENSURE_STATE(index != mImageElements.NoIndex);
+
+  mImageElements.RemoveElementAt(index);
+  return NS_OK;
+}
+
+nsresult
+nsHTMLFormElement::RemoveImageElementFromTable(HTMLImageElement* aElement,
+                                               const nsAString& aName)
+{
+  return RemoveElementFromTableInternal(mImageNameLookupTable, aElement, aName);
+}
+
--- a/content/html/content/src/nsHTMLFormElement.h
+++ b/content/html/content/src/nsHTMLFormElement.h
@@ -27,16 +27,22 @@
 #undef GetClassInfo
 #endif
 #endif
 
 class nsFormControlList;
 class nsIMutableArray;
 class nsIURI;
 
+namespace mozilla {
+namespace dom {
+class HTMLImageElement;
+}
+}
+
 class nsHTMLFormElement : public nsGenericHTMLElement,
                           public nsIDOMHTMLFormElement,
                           public nsIWebProgressListener,
                           public nsIForm,
                           public nsIRadioGroupContainer
 {
 public:
   nsHTMLFormElement(already_AddRefed<nsINodeInfo> aNodeInfo);
@@ -116,18 +122,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_NO_UNLINK(nsHTMLFormElement,
-                                                     nsGenericHTMLElement)
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsHTMLFormElement,
+                                           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.
    */
@@ -153,26 +159,66 @@ public:
    * @param aElement the element to add
    * @param aUpdateValidity If true, the form validity will be updated.
    * @param aNotify If true, send nsIDocumentObserver notifications as needed.
    * @return NS_OK if the element was successfully added
    */
   nsresult AddElement(nsGenericHTMLFormElement* aElement, bool aUpdateValidity,
                       bool aNotify);
 
-  /**    
+  /**
    * Add an element to the lookup table maintained by the form.
    *
    * We can't fold this method into AddElement() because when
    * AddElement() is called, the form control has no
    * attributes.  The name or id attributes of the form control
    * are used as a key into the table.
    */
   nsresult AddElementToTable(nsGenericHTMLFormElement* aChild,
                              const nsAString& aName);
+
+  /**
+   * Remove an image element from this form's list of image elements
+   *
+   * @param aElement the image element to remove
+   * @return NS_OK if the element was successfully removed.
+   */
+  nsresult RemoveImageElement(mozilla::dom::HTMLImageElement* aElement);
+
+  /**
+   * Remove an image element from the lookup table maintained by the form.
+   * We can't fold this method into RemoveImageElement() because when
+   * RemoveImageElement() is called it doesn't know if the element is
+   * removed because the id attribute has changed, or because the
+   * name attribute has changed.
+   *
+   * @param aElement the image element to remove
+   * @param aName the name or id of the element to remove
+   * @return NS_OK if the element was successfully removed.
+   */
+  nsresult RemoveImageElementFromTable(mozilla::dom::HTMLImageElement* aElement,
+                                      const nsAString& aName);
+  /**
+   * Add an image element to the end of this form's list of image elements
+   *
+   * @param aElement the element to add
+   * @return NS_OK if the element was successfully added
+   */
+  nsresult AddImageElement(mozilla::dom::HTMLImageElement* aElement);
+
+  /**
+   * Add an image element to the lookup table maintained by the form.
+   *
+   * We can't fold this method into AddImageElement() because when
+   * AddImageElement() is called, the image attributes can change.
+   * The name or id attributes of the image are used as a key into the table.
+   */
+  nsresult AddImageElementToTable(mozilla::dom::HTMLImageElement* aChild,
+                                 const nsAString& aName);
+
    /**
     * Return whether there is one and only one input text control.
     *
     * @return Whether there is exactly one input text control.
     */
   bool HasSingleTextControl() const;
 
   /**
@@ -358,16 +404,19 @@ protected:
    *
    * @param aInvalidElements [out] parameter containing the list of unhandled
    * invalid controls.
    *
    * @return Whether the form is currently valid.
    */
   bool CheckFormValidity(nsIMutableArray* aInvalidElements) const;
 
+  // Clear the mImageNameLookupTable and mImageElements.
+  void Clear();
+
 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();
@@ -412,16 +461,30 @@ protected:
   nsGenericHTMLFormElement* mDefaultSubmitElement;
 
   /** The first submit element in mElements -- WEAK */
   nsGenericHTMLFormElement* mFirstSubmitInElements;
 
   /** The first submit element in mNotInElements -- WEAK */
   nsGenericHTMLFormElement* mFirstSubmitNotInElements;
 
+  // This array holds on to all HTMLImageElement(s).
+  // This is needed to properly clean up the bi-directional references
+  // (both weak and strong) between the form and its HTMLImageElements.
+
+  nsTArray<mozilla::dom::HTMLImageElement*> mImageElements;  // Holds WEAK references
+
+  // A map from an ID or NAME attribute to the HTMLImageElement(s), this
+  // hash holds strong references either to the named HTMLImageElement, or
+  // to a list of named HTMLImageElement(s), in the case where this hash
+  // holds on to a list of named HTMLImageElement(s) the list has weak
+  // references to the HTMLImageElement.
+
+  nsInterfaceHashtable<nsStringHashKey,nsISupports> mImageNameLookupTable;
+
   /**
    * Number of invalid and candidate for constraint validation elements in the
    * form the last time UpdateValidity has been called.
    * @note Should only be used by UpdateValidity() and GetValidity()!
    */
   int32_t mInvalidElementsCount;
 
   /**
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -373,16 +373,17 @@ MOCHITEST_FILES = \
 		test_bug839371.html \
 		test_element_prototype.html \
 		test_formData.html \
 		test_audio_wakelock.html \
 		test_video_wakelock.html \
 		wakelock.ogg \
 		wakelock.ogv \
 		test_bug869040.html \
+		test_bug870787.html \
 		allowMedia.sjs \
 		test_bug874758.html \
 		$(NULL)
 
 MOCHITEST_CHROME_FILES = \
 		test_allowMedia.html \
 		$(NULL)
 
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_bug870787.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=870787
+-->
+<head>
+  <title>Test for Bug 870787</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="reflect.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=870787">Mozilla Bug 870787</a>
+
+<p id="msg"></p>
+
+<form id="form0"></form>
+<img name="img0" id="img0id">
+
+<img name="img1" id="img1id" />
+<form id="form1">
+  <img name="img2" id="img2id" />
+</form>
+<img name="img3" id="img3id" />
+
+<table>
+  <form id="form2">
+  <tr><td>
+    <button name="input1" id="input1id" />
+    <input name="input2" id="input2id" />
+  </form>
+</table>
+
+<table>
+  <form id="form3">
+  <tr><td>
+    <img name="img4" id="img4id" />
+    <img name="img5" id="img5id" />
+  </form>
+</table>
+
+<form id="form4"><img id="img6"></form>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 870787 **/
+
+var form0 = document.getElementById("form0");
+ok(form0, "Form0 exists");
+ok(!form0.img0, "Form0.img0 doesn't exist");
+ok(!form0.img0id, "Form0.img0id doesn't exist");
+
+var form1 = document.getElementById("form1");
+ok(form1, "Form1 exists");
+ok(!form1.img1, "Form1.img1 doesn't exist");
+ok(!form1.img1id, "Form1.img1id doesn't exist");
+is(form1.img2, document.getElementById("img2id"), "Form1.img2 exists");
+is(form1.img2id, document.getElementById("img2id"), "Form1.img2id exists");
+ok(!form1.img3, "Form1.img3 doesn't exist");
+ok(!form1.img3id, "Form1.img3id doesn't exist");
+
+var form2 = document.getElementById("form2");
+ok(form2, "Form2 exists");
+is(form2.input1, document.getElementById("input1id"), "Form2.input1 exists");
+is(form2.input1id, document.getElementById("input1id"), "Form2.input1id exists");
+is(form2.input2, document.getElementById("input2id"), "Form2.input2 exists");
+is(form2.input2id, document.getElementById("input2id"), "Form2.input2id exists");
+
+var form3 = document.getElementById("form3");
+ok(form3, "Form3 exists");
+is(form3.img4, document.getElementById("img4id"), "Form3.img4 doesn't exists");
+is(form3.img4id, document.getElementById("img4id"), "Form3.img4id doesn't exists");
+is(form3.img5, document.getElementById("img5id"), "Form3.img5 doesn't exists");
+is(form3.img5id, document.getElementById("img5id"), "Form3.img5id doesn't exists");
+
+var form4 = document.getElementById("form4");
+ok(form4, "Form4 exists");
+is(Object.getOwnPropertyNames(form4.elements).indexOf("img6"), -1, "Form4.elements should not contain img6");
+
+</script>
+</pre>
+</body>
+</html>
--- a/content/html/document/src/nsHTMLDocument.cpp
+++ b/content/html/document/src/nsHTMLDocument.cpp
@@ -2290,49 +2290,16 @@ nsHTMLDocument::ResolveName(const nsAStr
     *aCache = e;
     return e;
   }
 
   *aCache = nullptr;
   return nullptr;
 }
 
-already_AddRefed<nsISupports>
-nsHTMLDocument::ResolveName(const nsAString& aName,
-                            nsIContent *aForm,
-                            nsWrapperCache **aCache)
-{
-  nsISupports* result = ResolveName(aName, aCache);
-  if (!result) {
-    return nullptr;
-  }
-
-  nsCOMPtr<nsIContent> node = do_QueryInterface(result);
-  if (!node) {
-    // We create a nsFormContentList which will filter out the elements in the
-    // list that don't belong to aForm.
-    nsRefPtr<nsBaseContentList> list =
-      new nsFormContentList(aForm, *static_cast<nsBaseContentList*>(result));
-    if (list->Length() > 1) {
-      *aCache = list;
-      return list.forget();
-    }
-
-    // After the nsFormContentList is done filtering there's either nothing or
-    // one element in the list. Return that element, or null if there's no
-    // element in the list.
-    node = list->Item(0);
-  } else if (!nsContentUtils::BelongsInForm(aForm, node)) {
-    node = nullptr;
-  }
-
-  *aCache = node;
-  return node.forget();
-}
-
 JSObject*
 nsHTMLDocument::NamedGetter(JSContext* cx, const nsAString& aName, bool& aFound,
                             ErrorResult& rv)
 {
   nsWrapperCache* cache;
   nsISupports* supp = ResolveName(aName, &cache);
   if (!supp) {
     aFound = false;
--- a/content/html/document/src/nsHTMLDocument.h
+++ b/content/html/document/src/nsHTMLDocument.h
@@ -110,19 +110,16 @@ public:
    * id.
    */
   nsISupports *GetDocumentAllResult(const nsAString& aID,
                                     nsWrapperCache **aCache,
                                     nsresult *aResult);
   JSObject* GetAll(JSContext* aCx, mozilla::ErrorResult& aRv);
 
   nsISupports* ResolveName(const nsAString& aName, nsWrapperCache **aCache);
-  virtual already_AddRefed<nsISupports> ResolveName(const nsAString& aName,
-                                                    nsIContent *aForm,
-                                                    nsWrapperCache **aCache) MOZ_OVERRIDE;
 
   virtual void AddedForm() MOZ_OVERRIDE;
   virtual void RemovedForm() MOZ_OVERRIDE;
   virtual int32_t GetNumFormsSynchronous() MOZ_OVERRIDE;
   virtual void TearingDownEditor(nsIEditor *aEditor) MOZ_OVERRIDE;
   virtual void SetIsXHTML(bool aXHTML) MOZ_OVERRIDE { mIsRegularHTML = !aXHTML; }
   virtual void SetDocWriteDisabled(bool aDisabled) MOZ_OVERRIDE
   {
--- a/content/html/document/src/nsIHTMLDocument.h
+++ b/content/html/document/src/nsIHTMLDocument.h
@@ -28,20 +28,16 @@ class nsIHTMLDocument : public nsISuppor
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IHTMLDOCUMENT_IID)
 
   /**
    * Set compatibility mode for this document
    */
   virtual void SetCompatibilityMode(nsCompatibility aMode) = 0;
 
-  virtual already_AddRefed<nsISupports> ResolveName(const nsAString& aName,
-                                                    nsIContent *aForm,
-                                                    nsWrapperCache **aCache) = 0;
-
   /**
    * Called when form->BindToTree() is called so that document knows
    * immediately when a form is added
    */
   virtual void AddedForm() = 0;
   /**
    * Called when form->SetDocument() is called so that document knows
    * immediately when a form is removed
--- a/parser/html/nsHtml5TreeOperation.cpp
+++ b/parser/html/nsHtml5TreeOperation.cpp
@@ -27,16 +27,17 @@
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
 #include "nsIMutationObserver.h"
 #include "nsIFormProcessor.h"
 #include "nsIServiceManager.h"
 #include "nsEscape.h"
 #include "mozilla/dom/Comment.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLImageElement.h"
 #include "mozilla/dom/HTMLTemplateElement.h"
 #include "nsHtml5SVGLoadDispatcher.h"
 #include "nsIURI.h"
 #include "nsIProtocolHandler.h"
 #include "nsNetUtil.h"
 #include "nsIHTMLDocument.h"
 #include "mozilla/Likely.h"
 #include "nsTextNode.h"
@@ -436,25 +437,32 @@ nsHtml5TreeOperation::Perform(nsHtml5Tre
       }
 
       return rv;
     }
     case eTreeOpSetFormElement: {
       nsIContent* node = *(mOne.node);
       nsIContent* parent = *(mTwo.node);
       nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(node));
+      nsCOMPtr<nsIDOMHTMLImageElement> domImageElement = do_QueryInterface(node);
       // NS_ASSERTION(formControl, "Form-associated element did not implement nsIFormControl.");
       // TODO: uncomment the above line when <keygen> (bug 101019) is supported by Gecko
       nsCOMPtr<nsIDOMHTMLFormElement> formElement(do_QueryInterface(parent));
       NS_ASSERTION(formElement, "The form element doesn't implement nsIDOMHTMLFormElement.");
       // avoid crashing on <keygen>
       if (formControl &&
           !node->HasAttr(kNameSpaceID_None, nsGkAtoms::form)) {
         formControl->SetForm(formElement);
+      } else if (domImageElement) {
+        nsRefPtr<dom::HTMLImageElement> imageElement =
+          static_cast<dom::HTMLImageElement*>(domImageElement.get());
+        MOZ_ASSERT(imageElement);
+        imageElement->SetForm(formElement);
       }
+
       return rv;
     }
     case eTreeOpAppendText: {
       nsIContent* parent = *mOne.node;
       PRUnichar* buffer = mTwo.unicharPtr;
       uint32_t length = mFour.integer;
       return AppendText(buffer, length, parent, aBuilder);
     }