Don't keep unassociated elements in the past names map
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 28 Aug 2013 18:28:55 +0000 (18:28 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 28 Aug 2013 18:28:55 +0000 (18:28 +0000)
https://bugs.webkit.org/show_bug.cgi?id=120328

Reviewed by Darin Adler.

Source/WebCore:

Remove elements from the past names map of a form element when they are disassociated with the form to match
the behaviors of Firefox 24 and Internet Explorer 10. The specification feedback has been submitted to WHATWG
in http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-August/040586.html

Also fix a memory leak via the past names map when the elements in the map becomes an ancestor of the form
element by storing a raw pointer in the map. This is safe because the form associated elements are kept alive
by another mechanism.

Because ~FormAssociatedElement removes entries from the past names map, we could no longer store HTMLElement*
in HTMLFormElement::m_pastNamesMap as that requires casting FormAssociatedElement* to HTMLElement*, which is
not possible in ~FormAssociatedElement. We instead store pointers to FormNamedItem, new base class of
FormAssociatedElement and HTMLImageElement.

Test: fast/forms/past-names-map-should-not-contained-disassociated-elements.html

* Target.pri:
* WebCore.exp.in:
* WebCore.vcxproj/WebCore.vcxproj:
* WebCore.vcxproj/WebCore.vcxproj.filters:
* WebCore.xcodeproj/project.pbxproj:
* html/FormAssociatedElement.cpp:
* html/FormAssociatedElement.h:
(WebCore::toHTMLElement):

* html/FormNamedItem.h: Added.
(WebCore::FormNamedItem::~FormNamedItem):

* html/HTMLElement.h:
(WebCore::HTMLElement::asFormNamedItem): Added. This allows the conversion from a HTMLFormControlElement,
HTMLObjectElement, HTMLImageElement to FormNamedItem in getNamedElements to update the past names map.

* html/HTMLFormControlElement.h:
* html/HTMLFormElement.cpp:
(WebCore::HTMLFormElement::removeFormElement):
(WebCore::HTMLFormElement::removeImgElement):
(WebCore::HTMLFormElement::assertItemCanBeInPastNamesMap): Asserts that FormNamedItem added to or obtained
from the past names map is either a form associated element or an image element; the condition guarantees
that the item will be removed from the map before its element gets destructed.

(WebCore::HTMLFormElement::elementFromPastNamesMap):
(WebCore::HTMLFormElement::addToPastNamesMap):
(WebCore::HTMLFormElement::removeFromPastNamesMap): Finds and removes the obsolete item from the map in O(n).
Note that removeFromVector, which is called on m_associatedElements or m_imageElements before this function is called,
is already O(n).

(WebCore::HTMLFormElement::getNamedElements):

* html/HTMLFormElement.h:
* html/HTMLImageElement.h:
* html/HTMLObjectElement.h:

LayoutTests:

Add a regression test. Also Updated the tests to expect the new behavior in which elements are not accessible via
their past names in a form element's name getter once they're disassociated with the form element.

* fast/forms/form-image-access-by-name-expected.txt:
* fast/forms/form-image-access-by-name.html:
* fast/forms/old-names-expected.txt:
* fast/forms/old-names.html:
* fast/forms/past-names-map-should-not-contained-disassociated-elements-expected.txt: Added.
* fast/forms/past-names-map-should-not-contained-disassociated-elements.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@154761 268f45cc-cd09-0410-ab3c-d52691b4dbfc

22 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/forms/form-image-access-by-name-expected.txt
LayoutTests/fast/forms/form-image-access-by-name.html
LayoutTests/fast/forms/old-names-expected.txt
LayoutTests/fast/forms/old-names.html
LayoutTests/fast/forms/past-names-map-should-not-contained-disassociated-elements-expected.txt [new file with mode: 0644]
LayoutTests/fast/forms/past-names-map-should-not-contained-disassociated-elements.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Target.pri
Source/WebCore/WebCore.exp.in
Source/WebCore/WebCore.vcxproj/WebCore.vcxproj
Source/WebCore/WebCore.vcxproj/WebCore.vcxproj.filters
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/html/FormAssociatedElement.cpp
Source/WebCore/html/FormAssociatedElement.h
Source/WebCore/html/FormNamedItem.h [new file with mode: 0644]
Source/WebCore/html/HTMLElement.h
Source/WebCore/html/HTMLFormControlElement.h
Source/WebCore/html/HTMLFormElement.cpp
Source/WebCore/html/HTMLFormElement.h
Source/WebCore/html/HTMLImageElement.h
Source/WebCore/html/HTMLObjectElement.h

index a2f735c78a19c5519e2567a929c326a2ec17cce7..f02ad54ac094c4ba24e5422ce0f255d9fcc371a5 100644 (file)
@@ -1,3 +1,20 @@
+2013-08-27  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Don't keep unassociated elements in the past names map
+        https://bugs.webkit.org/show_bug.cgi?id=120328
+
+        Reviewed by Darin Adler.
+
+        Add a regression test. Also Updated the tests to expect the new behavior in which elements are not accessible via
+        their past names in a form element's name getter once they're disassociated with the form element.
+
+        * fast/forms/form-image-access-by-name-expected.txt:
+        * fast/forms/form-image-access-by-name.html:
+        * fast/forms/old-names-expected.txt:
+        * fast/forms/old-names.html:
+        * fast/forms/past-names-map-should-not-contained-disassociated-elements-expected.txt: Added.
+        * fast/forms/past-names-map-should-not-contained-disassociated-elements.html: Added.
+
 2013-08-28  Brendan Long  <b.long@cablelabs.com>
 
         Duplicate in-band tracks when switching <source> elements
index ddb6beec55a636fee80da56a9a276e1d44a3d422..ab1b07e2beef93217091677e3b42b194af12121c 100644 (file)
@@ -1,7 +1,9 @@
 
 PASS form.imageElement.id is '1'
 PASS form.imageElement.id is '2'
-PASS !!form.imageElement is true
+PASS form.imageElement.name = 'foo'; form.foo.id is '2'
+PASS form.imageElement is form.foo
+PASS !!form.imageElement is false
 PASS document.imageElement is undefined.
 PASS !!document.newImage is true
 PASS !!form.newImage is true
index 042369e162952d02ae01076e192e4184152a6292..bde2999a3571eaa8671c549af1bda83d448b0821 100644 (file)
@@ -26,12 +26,11 @@ shouldBe("form.imageElement.id", "'1'");
 
 span.innerHTML = "<img id='2' name='imageElement'>";
 shouldBe("form.imageElement.id", "'2'");
+shouldBe("form.imageElement.name = 'foo'; form.foo.id", "'2'");
+shouldBe("form.imageElement", "form.foo");
 
 span.innerHTML = "<img id='2' name='newImage'>";
-
-// imageElement has been removed from the DOM, but,
-// IE 6 has a quirk where once accessed, form images are always accessible by name
-shouldBeTrue("!!form.imageElement");
+shouldBeFalse("!!form.imageElement");
 
 // This quirk has no bearing on document. access
 shouldBeUndefined("document.imageElement");
index 446860432a392cd1767c94a32e77c6622158a97b..e49edbdce36a5a13a2e2eefe5b8023d0a7fa330b 100644 (file)
@@ -90,10 +90,10 @@ PASS form.elements.third.length is undefined
 now remove element a
 
 PASS form.length is 1
-PASS form.original is a
+PASS form.original is undefined.
 PASS form.originalB is b
 PASS form.second is b
-PASS form.third is a
+PASS form.third is undefined.
 PASS form.fourth is b
 PASS form.elements.original is undefined
 PASS form.elements.originalB is undefined
@@ -101,12 +101,12 @@ PASS form.elements.second is undefined
 PASS form.elements.third is undefined
 PASS form.elements.fourth is b
 
-check we still remember names we should
+check that we no longer remember the past names of a
 
 PASS form.thisWillBeForgotten is undefined
 PASS form.thisWillBeForgottenToo is undefined
-PASS form.thisWillBeRemembered is a
-PASS form.thisWillBeRememberedToo is a
+PASS form.thisWillBeRemembered is undefined.
+PASS form.thisWillBeRememberedToo is undefined.
 
 PASS successfullyParsed is true
 
index 018cd625dbca64da1cebcbd7f5b62d0e6137dac0..94eddb8d6c48adfd1135c45a8a49789cf68d2b32 100644 (file)
@@ -130,10 +130,10 @@ function runTest()
     form.removeChild(a);
 
     shouldBe('form.length', '1');
-    shouldBe('form.original', 'a');
+    shouldBeUndefined('form.original');
     shouldBe('form.originalB', 'b');
     shouldBe('form.second', 'b');
-    shouldBe('form.third', 'a');
+    shouldBeUndefined('form.third');
     shouldBe('form.fourth', 'b');
     shouldBe('form.elements.original', 'undefined');
     shouldBe('form.elements.originalB', 'undefined');
@@ -142,13 +142,13 @@ function runTest()
     shouldBe('form.elements.fourth', 'b');
  
     debug('');
-    debug("check we still remember names we should");
+    debug("check that we no longer remember the past names of a");
     debug('');
 
     shouldBe('form.thisWillBeForgotten', 'undefined');
     shouldBe('form.thisWillBeForgottenToo', 'undefined');
-    shouldBe('form.thisWillBeRemembered', 'a');
-    shouldBe('form.thisWillBeRememberedToo', 'a');
+    shouldBeUndefined('form.thisWillBeRemembered');
+    shouldBeUndefined('form.thisWillBeRememberedToo');
     debug('');
 }
 </script>
diff --git a/LayoutTests/fast/forms/past-names-map-should-not-contained-disassociated-elements-expected.txt b/LayoutTests/fast/forms/past-names-map-should-not-contained-disassociated-elements-expected.txt
new file mode 100644 (file)
index 0000000..4f0dc20
--- /dev/null
@@ -0,0 +1,16 @@
+This test ensures elements are removed from the past names map of a form element once they are no longer associated with the form element.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS form1['foo'] is input
+PASS form2.appendChild(form1.firstChild); form1['foo'] is undefined.
+PASS form2['foo'] is input
+PASS form2.removeChild(input);form2['foo'] is undefined.
+PASS form1.appendChild(input); form1['foo'] is input
+PASS input.setAttribute('form', 'form1'); form1.removeChild(input); input.appendChild(form1); form1['foo'] is undefined.
+PASS form1['foo'] is not input
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/forms/past-names-map-should-not-contained-disassociated-elements.html b/LayoutTests/fast/forms/past-names-map-should-not-contained-disassociated-elements.html
new file mode 100644 (file)
index 0000000..b5a7999
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<body>
+<form id="form1"><input type="text" name="foo"></form>
+<form id="form2"></form>
+<script src="../js/resources/js-test-pre.js"></script>
+<script>
+
+description('This test ensures elements are removed from the past names map of a form element once they are no longer associated with the form element.');
+
+var form1 = document.querySelector('#form1');
+var form2 = document.querySelector('#form2');
+var input = document.querySelector('input');
+
+shouldBe("form1['foo']", "input");
+shouldBeUndefined("form2.appendChild(form1.firstChild); form1['foo']");
+
+shouldBe("form2['foo']", "input");
+shouldBeUndefined("form2.removeChild(input);form2['foo']");
+
+shouldBe("form1.appendChild(input); form1['foo']", "input");
+shouldBeUndefined("input.setAttribute('form', 'form1'); form1.removeChild(input); input.appendChild(form1); form1['foo']");
+shouldNotBe("form1['foo']", "input");
+
+var successfullyParsed = true;
+
+</script>
+<script src="../js/resources/js-test-post.js"></script>
+</body>
+</html>
index 40edd26d19d6c22b5bdeda01866eb1f5d9886910..c9c84735e2bcba37149856b6f2efa57d5ea6691a 100644 (file)
@@ -1,3 +1,61 @@
+2013-08-27  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Don't keep unassociated elements in the past names map
+        https://bugs.webkit.org/show_bug.cgi?id=120328
+
+        Reviewed by Darin Adler.
+
+        Remove elements from the past names map of a form element when they are disassociated with the form to match
+        the behaviors of Firefox 24 and Internet Explorer 10. The specification feedback has been submitted to WHATWG
+        in http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-August/040586.html
+
+        Also fix a memory leak via the past names map when the elements in the map becomes an ancestor of the form
+        element by storing a raw pointer in the map. This is safe because the form associated elements are kept alive
+        by another mechanism.
+
+        Because ~FormAssociatedElement removes entries from the past names map, we could no longer store HTMLElement*
+        in HTMLFormElement::m_pastNamesMap as that requires casting FormAssociatedElement* to HTMLElement*, which is
+        not possible in ~FormAssociatedElement. We instead store pointers to FormNamedItem, new base class of
+        FormAssociatedElement and HTMLImageElement.
+
+        Test: fast/forms/past-names-map-should-not-contained-disassociated-elements.html
+
+        * Target.pri:
+        * WebCore.exp.in:
+        * WebCore.vcxproj/WebCore.vcxproj:
+        * WebCore.vcxproj/WebCore.vcxproj.filters:
+        * WebCore.xcodeproj/project.pbxproj:
+        * html/FormAssociatedElement.cpp:
+        * html/FormAssociatedElement.h:
+        (WebCore::toHTMLElement):
+
+        * html/FormNamedItem.h: Added.
+        (WebCore::FormNamedItem::~FormNamedItem):
+
+        * html/HTMLElement.h:
+        (WebCore::HTMLElement::asFormNamedItem): Added. This allows the conversion from a HTMLFormControlElement,
+        HTMLObjectElement, HTMLImageElement to FormNamedItem in getNamedElements to update the past names map.
+
+        * html/HTMLFormControlElement.h:
+        * html/HTMLFormElement.cpp:
+        (WebCore::HTMLFormElement::removeFormElement):
+        (WebCore::HTMLFormElement::removeImgElement):
+        (WebCore::HTMLFormElement::assertItemCanBeInPastNamesMap): Asserts that FormNamedItem added to or obtained
+        from the past names map is either a form associated element or an image element; the condition guarantees
+        that the item will be removed from the map before its element gets destructed.
+
+        (WebCore::HTMLFormElement::elementFromPastNamesMap):
+        (WebCore::HTMLFormElement::addToPastNamesMap):
+        (WebCore::HTMLFormElement::removeFromPastNamesMap): Finds and removes the obsolete item from the map in O(n).
+        Note that removeFromVector, which is called on m_associatedElements or m_imageElements before this function is called,
+        is already O(n).
+
+        (WebCore::HTMLFormElement::getNamedElements):
+
+        * html/HTMLFormElement.h:
+        * html/HTMLImageElement.h:
+        * html/HTMLObjectElement.h:
+
 2013-08-28  Brendan Long  <b.long@cablelabs.com>
 
         Duplicate in-band tracks when switching <source> elements
index 181dfe57882acb93176d0954b967f29a9fb91d99..644532803bfff71edd9e88a7615478bc5bc89361 100644 (file)
@@ -1799,6 +1799,7 @@ HEADERS += \
     html/FormAssociatedElement.h \
     html/FormController.h \
     html/FormDataList.h \
+    html/FormNamedItem.h \
     html/FTPDirectoryDocument.h \
     html/HTMLAllCollection.h \
     html/HTMLAnchorElement.h \
index e328d1492d53420c26fb258e38bcf1c77f5bdb51..a3cc797616cf7cf910b389108d326d4a8a386bcc 100644 (file)
@@ -286,7 +286,6 @@ __ZN7WebCore13createWrapperEPN3JSC9ExecStateEPNS_17JSDOMGlobalObjectEPNS_4NodeE
 __ZN7WebCore13directoryNameERKN3WTF6StringE
 __ZN7WebCore13listDirectoryERKN3WTF6StringES3_
 __ZN7WebCore13pointerCursorEv
-__ZN7WebCore13toHTMLElementEPNS_21FormAssociatedElementE
 __ZN7WebCore13toJSDOMWindowEN3JSC7JSValueE
 __ZN7WebCore14CachedResource12removeClientEPNS_20CachedResourceClientE
 __ZN7WebCore14CachedResource16unregisterHandleEPNS_24CachedResourceHandleBaseE
index c17eeead4195f6942988429065702c89619400c5..e420ac3583f9643400bbbf0541ee94f634a1ac05 100644 (file)
     <ClInclude Include="..\html\FormAssociatedElement.h" />
     <ClInclude Include="..\html\FormController.h" />
     <ClInclude Include="..\html\FormDataList.h" />
+    <ClInclude Include="..\html\FormNamedItem.h" />
     <ClInclude Include="..\html\FTPDirectoryDocument.h" />
     <ClInclude Include="..\html\HiddenInputType.h" />
     <ClInclude Include="..\html\HTMLAllCollection.h" />
index 984dacf7bc94c22b19ceba23e2661569c18b9128..7d0d3f54d4bb64c585cb604abbf424ce201a2438 100644 (file)
     <ClInclude Include="..\html\FormDataList.h">
       <Filter>html</Filter>
     </ClInclude>
+    <ClInclude Include="..\html\FormNamedItem.h">
+      <Filter>html</Filter>
+    </ClInclude>
     <ClInclude Include="..\html\FTPDirectoryDocument.h">
       <Filter>html</Filter>
     </ClInclude>
index fb364c41d78d2df4d7db35e3f9425cb6557c3a15..f34fadda73b3a90c9e1421b801d6cc2a3b3a8804 100644 (file)
                9B3A8872145632F9003AE8F5 /* DOMDOMSettableTokenList.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B3A8871145632F9003AE8F5 /* DOMDOMSettableTokenList.h */; };
                9B417064125662B3006B28FC /* ApplyBlockElementCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B417062125662B3006B28FC /* ApplyBlockElementCommand.h */; };
                9B417065125662B3006B28FC /* ApplyBlockElementCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9B417063125662B3006B28FC /* ApplyBlockElementCommand.cpp */; };
+               9B50B1DE17CD4C0F0087F63C /* FormNamedItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B50B1DC17CD4C0F0087F63C /* FormNamedItem.h */; settings = {ATTRIBUTES = (Private, ); }; };
                9B6C41531344949000085B62 /* StringWithDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B6C41521344949000085B62 /* StringWithDirection.h */; settings = {ATTRIBUTES = (Private, ); }; };
                9B7E78BD16F16CC600126914 /* HTMLTreeBuilderSimulator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9B7E78BA16F16CAE00126914 /* HTMLTreeBuilderSimulator.cpp */; };
                9B7E78BE16F16CC800126914 /* HTMLTreeBuilderSimulator.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B7E78BB16F16CAE00126914 /* HTMLTreeBuilderSimulator.h */; };
                9B3A8871145632F9003AE8F5 /* DOMDOMSettableTokenList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DOMDOMSettableTokenList.h; sourceTree = "<group>"; };
                9B417062125662B3006B28FC /* ApplyBlockElementCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ApplyBlockElementCommand.h; sourceTree = "<group>"; };
                9B417063125662B3006B28FC /* ApplyBlockElementCommand.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ApplyBlockElementCommand.cpp; sourceTree = "<group>"; };
+               9B50B1DC17CD4C0F0087F63C /* FormNamedItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FormNamedItem.h; sourceTree = "<group>"; };
                9B6C41521344949000085B62 /* StringWithDirection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringWithDirection.h; sourceTree = "<group>"; };
                9B7E78BA16F16CAE00126914 /* HTMLTreeBuilderSimulator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = HTMLTreeBuilderSimulator.cpp; path = parser/HTMLTreeBuilderSimulator.cpp; sourceTree = "<group>"; };
                9B7E78BB16F16CAE00126914 /* HTMLTreeBuilderSimulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTMLTreeBuilderSimulator.h; path = parser/HTMLTreeBuilderSimulator.h; sourceTree = "<group>"; };
                                F50664F6157F52DC00AC226F /* FormController.h */,
                                A8136D370973A8E700D74463 /* FormDataList.cpp */,
                                A8136D360973A8E700D74463 /* FormDataList.h */,
+                               9B50B1DC17CD4C0F0087F63C /* FormNamedItem.h */,
                                97205AAD123928CA00B17380 /* FTPDirectoryDocument.cpp */,
                                97205AAE123928CA00B17380 /* FTPDirectoryDocument.h */,
                                F55B3D8B1251F12D003EF269 /* HiddenInputType.cpp */,
                                1A0D57370A5C77FE007EDD4C /* OverflowEvent.h in Headers */,
                                3774ABA50FA21EB400AD7DE9 /* OverlapTestRequestClient.h in Headers */,
                                65A21468097A329100B9050A /* Page.h in Headers */,
+                               9B50B1DE17CD4C0F0087F63C /* FormNamedItem.h in Headers */,
                                1477E7770BF4134A00152872 /* PageCache.h in Headers */,
                                DAED203116F244480070EC0F /* PageConsole.h in Headers */,
                                F3820893147D35F90010BC06 /* PageConsoleAgent.h in Headers */,
index d966e44de4646bf39cc67e227329707ac1454e46..353a027558bd9616d5b2b0ea1e4d805d9cb306b1 100644 (file)
@@ -273,21 +273,6 @@ bool FormAssociatedElement::isFormControlElementWithState() const
     return false;
 }
 
-const HTMLElement* toHTMLElement(const FormAssociatedElement* associatedElement)
-{
-    if (associatedElement->isFormControlElement())
-        return static_cast<const HTMLFormControlElement*>(associatedElement);
-    // Assumes the element is an HTMLObjectElement
-    const HTMLElement* element = static_cast<const HTMLObjectElement*>(associatedElement);
-    ASSERT(element->hasTagName(objectTag));
-    return element;
-}
-
-HTMLElement* toHTMLElement(FormAssociatedElement* associatedElement)
-{
-    return const_cast<HTMLElement*>(toHTMLElement(static_cast<const FormAssociatedElement*>(associatedElement)));
-}
-
 PassOwnPtr<FormAttributeTargetObserver> FormAttributeTargetObserver::create(const AtomicString& id, FormAssociatedElement* element)
 {
     return adoptPtr(new FormAttributeTargetObserver(id, element));
index 40f2b36e133e7ca1210b87971eb02d149ea15334..bdad7b7a84cde5b16a022189207a8b7790d597d9 100644 (file)
@@ -24,6 +24,7 @@
 #ifndef FormAssociatedElement_h
 #define FormAssociatedElement_h
 
+#include "FormNamedItem.h"
 #include <wtf/text/WTFString.h>
 
 namespace WebCore {
@@ -39,7 +40,7 @@ class ValidationMessage;
 class ValidityState;
 class VisibleSelection;
 
-class FormAssociatedElement {
+class FormAssociatedElement : public FormNamedItem {
 public:
     virtual ~FormAssociatedElement();
 
@@ -112,14 +113,23 @@ private:
 
     void resetFormAttributeTargetObserver();
 
+    virtual bool isFormAssociatedElement() OVERRIDE FINAL { return true; }
+
     OwnPtr<FormAttributeTargetObserver> m_formAttributeTargetObserver;
     HTMLFormElement* m_form;
     OwnPtr<ValidityState> m_validityState;
     String m_customValidationMessage;
 };
 
-HTMLElement* toHTMLElement(FormAssociatedElement*);
-const HTMLElement* toHTMLElement(const FormAssociatedElement*);
+inline const HTMLElement* toHTMLElement(const FormAssociatedElement* associatedElement)
+{
+    return const_cast<FormAssociatedElement*>(associatedElement)->asHTMLElement();
+}
+
+inline HTMLElement* toHTMLElement(FormAssociatedElement* associatedElement)
+{
+    return associatedElement->asHTMLElement();
+}
 
 } // namespace
 
diff --git a/Source/WebCore/html/FormNamedItem.h b/Source/WebCore/html/FormNamedItem.h
new file mode 100644 (file)
index 0000000..e1a8e2e
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 Apple Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef FormNamedItem_h
+#define FormNamedItem_h
+
+namespace WebCore {
+
+class HTMLElement;
+
+class FormNamedItem {
+public:
+    virtual ~FormNamedItem() { }
+    virtual HTMLElement* asHTMLElement() = 0;
+    virtual bool isFormAssociatedElement() = 0;
+};
+
+}
+
+#endif /* FormNamedItem_h */
index 0a6aa9bf683b7d80c1c5dc0500ff86a49bac49e7..af74ee404adc3898f05914d5b598b8fbb18463af 100644 (file)
@@ -28,6 +28,7 @@
 namespace WebCore {
 
 class DocumentFragment;
+class FormNamedItem;
 class HTMLCollection;
 class HTMLFormElement;
 
@@ -93,6 +94,7 @@ public:
     virtual bool isHTMLUnknownElement() const { return false; }
 
     virtual bool isLabelable() const { return false; }
+    virtual FormNamedItem* asFormNamedItem() { return 0; }
 
 protected:
     HTMLElement(const QualifiedName& tagName, Document*, ConstructionType);
index 0047dfb1c50484dc65bb11e0094edd2ba777f448..8cdf2c031fc88eff048484c3723999f01fa3d91b 100644 (file)
@@ -144,6 +144,9 @@ private:
     virtual bool isValidFormControlElement();
     void updateAncestorDisabledState() const;
 
+    virtual HTMLElement* asHTMLElement() OVERRIDE FINAL { return this; }
+    virtual FormNamedItem* asFormNamedItem() OVERRIDE FINAL { return this; }
+
     OwnPtr<ValidationMessage> m_validationMessage;
     bool m_disabled : 1;
     bool m_isReadOnly : 1;
index 6a2714698f849f7701716ce5d9d70a0ae91dcab0..efc398f07d2ee0f3bb6e477d2b41defcfe88af04 100644 (file)
@@ -499,6 +499,7 @@ void HTMLFormElement::removeFormElement(FormAssociatedElement* e)
         --m_associatedElementsBeforeIndex;
     if (index < m_associatedElementsAfterIndex)
         --m_associatedElementsAfterIndex;
+    removeFromPastNamesMap(e);
     removeFromVector(m_associatedElements, e);
 }
 
@@ -516,6 +517,7 @@ void HTMLFormElement::registerImgElement(HTMLImageElement* e)
 void HTMLFormElement::removeImgElement(HTMLImageElement* e)
 {
     ASSERT(m_imageElements.find(e) != notFound);
+    removeFromPastNamesMap(e);
     removeFromVector(m_imageElements, e);
 }
 
@@ -611,20 +613,58 @@ bool HTMLFormElement::checkInvalidControlsAndCollectUnhandled(Vector<RefPtr<Form
     return hasInvalidControls;
 }
 
-HTMLFormControlElement* HTMLFormElement::elementFromPastNamesMap(const AtomicString& pastName) const
+#ifndef NDEBUG
+void HTMLFormElement::assertItemCanBeInPastNamesMap(FormNamedItem* item) const
+{
+    HTMLElement* element = item->asHTMLElement();
+    ASSERT_WITH_SECURITY_IMPLICATION(element);
+    ASSERT_WITH_SECURITY_IMPLICATION(element->form() == this);
+    if (item->isFormAssociatedElement()) {
+        ASSERT_WITH_SECURITY_IMPLICATION(m_associatedElements.find(static_cast<FormAssociatedElement*>(item)) != notFound);
+        return;
+    }
+
+    ASSERT_WITH_SECURITY_IMPLICATION(element->hasTagName(imgTag));
+    ASSERT_WITH_SECURITY_IMPLICATION(m_imageElements.find(toHTMLImageElement(element)) != notFound);
+}
+#else
+inline void HTMLFormElement::assertItemCanBeInPastNamesMap(FormNamedItem*) const
+{
+}
+#endif
+
+HTMLElement* HTMLFormElement::elementFromPastNamesMap(const AtomicString& pastName) const
 {
     if (pastName.isEmpty() || !m_pastNamesMap)
         return 0;
-    return m_pastNamesMap->get(pastName.impl());
+    FormNamedItem* item = m_pastNamesMap->get(pastName.impl());
+    if (!item)
+        return 0;
+    assertItemCanBeInPastNamesMap(item);
+    return item->asHTMLElement();
 }
 
-void HTMLFormElement::addElementToPastNamesMap(HTMLFormControlElement* element, const AtomicString& pastName)
+void HTMLFormElement::addToPastNamesMap(FormNamedItem* item, const AtomicString& pastName)
 {
+    assertItemCanBeInPastNamesMap(item);
     if (pastName.isEmpty())
         return;
     if (!m_pastNamesMap)
         m_pastNamesMap = adoptPtr(new PastNamesMap);
-    m_pastNamesMap->set(pastName.impl(), element);
+    m_pastNamesMap->set(pastName.impl(), item);
+}
+
+void HTMLFormElement::removeFromPastNamesMap(FormNamedItem* item)
+{
+    ASSERT(item);
+    if (!m_pastNamesMap)
+        return;
+
+    PastNamesMap::iterator end = m_pastNamesMap->end();
+    for (PastNamesMap::iterator it = m_pastNamesMap->begin(); it != end; ++it) {
+        if (it->value == item)
+            it->value = 0; // Keep looping. Single element can have multiple names.
+    }
 }
 
 bool HTMLFormElement::hasNamedElement(const AtomicString& name)
@@ -632,15 +672,16 @@ bool HTMLFormElement::hasNamedElement(const AtomicString& name)
     return elements()->hasNamedItem(name) || elementFromPastNamesMap(name);
 }
 
+// FIXME: Use RefPtr<HTMLElement> for namedItems. elements()->namedItems never return non-HTMLElement nodes.
 void HTMLFormElement::getNamedElements(const AtomicString& name, Vector<RefPtr<Node> >& namedItems)
 {
     // http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#dom-form-nameditem
     elements()->namedItems(name, namedItems);
 
     // FIXME: The specification says we should not add the element from the past when names map when namedItems is not empty.
-    HTMLFormControlElement* elementFromPast = elementFromPastNamesMap(name);
+    HTMLElement* elementFromPast = elementFromPastNamesMap(name);
     if (namedItems.size() == 1 && namedItems.first() != elementFromPast)
-        addElementToPastNamesMap(static_cast<HTMLFormControlElement*>(namedItems.first().get()), name);
+        addToPastNamesMap(toHTMLElement(namedItems.first().get())->asFormNamedItem(), name);
     else if (elementFromPast && namedItems.find(elementFromPast) == notFound)
         namedItems.append(elementFromPast);
 }
index 468302886977926954c8487976d2c7fac29ba761..8912ef505bd6cccd153e48326cdaa630167c860d 100644 (file)
@@ -138,11 +138,12 @@ private:
     // are any invalid controls in this form.
     bool checkInvalidControlsAndCollectUnhandled(Vector<RefPtr<FormAssociatedElement> >&);
 
-    HTMLFormControlElement* elementFromPastNamesMap(const AtomicString&) const;
-    void addElementToPastNamesMap(HTMLFormControlElement*, const AtomicString& pastName);
+    HTMLElement* elementFromPastNamesMap(const AtomicString&) const;
+    void addToPastNamesMap(FormNamedItem*, const AtomicString& pastName);
+    void assertItemCanBeInPastNamesMap(FormNamedItem*) const;
+    void removeFromPastNamesMap(FormNamedItem*);
 
-    // FIXME: This can leak HTMLFormControlElements.
-    typedef HashMap<RefPtr<AtomicStringImpl>, RefPtr<HTMLFormControlElement> > PastNamesMap;
+    typedef HashMap<RefPtr<AtomicStringImpl>, FormNamedItem*> PastNamesMap;
 
     FormSubmission::Attributes m_attributes;
     OwnPtr<PastNamesMap> m_pastNamesMap;
index 625ccb249d71985d5dcfa557499b3d580948c2c9..9f713cc2029911bf6c71554079f15aa0c54f48d4 100644 (file)
@@ -24,6 +24,7 @@
 #ifndef HTMLImageElement_h
 #define HTMLImageElement_h
 
+#include "FormNamedItem.h"
 #include "GraphicsTypes.h"
 #include "HTMLElement.h"
 #include "HTMLImageLoader.h"
@@ -32,7 +33,7 @@ namespace WebCore {
 
 class HTMLFormElement;
 
-class HTMLImageElement : public HTMLElement {
+class HTMLImageElement : public HTMLElement, public FormNamedItem {
     friend class HTMLFormElement;
 public:
     static PassRefPtr<HTMLImageElement> create(Document*);
@@ -104,6 +105,10 @@ private:
     virtual InsertionNotificationRequest insertedInto(ContainerNode*) OVERRIDE;
     virtual void removedFrom(ContainerNode*) OVERRIDE;
 
+    virtual bool isFormAssociatedElement() OVERRIDE FINAL { return false; }
+    virtual FormNamedItem* asFormNamedItem() OVERRIDE FINAL { return this; }
+    virtual HTMLElement* asHTMLElement() OVERRIDE FINAL { return this; }
+
     HTMLImageLoader m_imageLoader;
     HTMLFormElement* m_form;
     CompositeOperator m_compositeOperator;
index ae0675eb805afd047c6d9ea2c600660649821b3b..ff034ee08a5c60bc35804eb102be512247461519 100644 (file)
@@ -101,6 +101,9 @@ private:
     virtual void derefFormAssociatedElement() { deref(); }
     virtual HTMLFormElement* virtualForm() const;
 
+    virtual FormNamedItem* asFormNamedItem() OVERRIDE FINAL { return this; }
+    virtual HTMLElement* asHTMLElement() OVERRIDE FINAL { return this; }
+
     String m_classId;
     bool m_docNamedItem : 1;
     bool m_useFallbackContent : 1;