LayoutTests:
authorantti <antti@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 17 Mar 2007 11:53:55 +0000 (11:53 +0000)
committerantti <antti@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 17 Mar 2007 11:53:55 +0000 (11:53 +0000)
        Reviewed by Adele.

        Test for http://bugs.webkit.org/show_bug.cgi?id=12595
        REGRESSION: Can't add item to cart at lnt.com (JS type error)
        <rdar://problem/4722863>

        Expanded version of Darin's test case.

        * fast/forms/old-names-expected.txt: Added.
        * fast/forms/old-names.html: Added.

WebCore:

        Reviewed by Adele.

        Fix http://bugs.webkit.org/show_bug.cgi?id=12595
        REGRESSION: Can't add item to cart at lnt.com (JS type error)
        <rdar://problem/4722863>

        Emulate Firefox behavior where form elements accessed by a name
        can be accessed with that name later even if the name changes or
        even if element is removed from the document.

        This is loosely based on Darin's earlier patch for the same problem but
        is much less expansive. It takes somewhat different approach to more closely
        mimic Firefox behavior. Includes expanded test case.

        * bindings/js/JSHTMLFormElementCustom.cpp:
        (WebCore::JSHTMLFormElement::canGetItemsForName):
            Use new the HTMLFormElement::getNamedElements() method
        (WebCore::JSHTMLFormElement::nameGetter):
            Use new the HTMLFormElement::getNamedElements() method
        * html/HTMLFormElement.cpp:
        (WebCore::HTMLFormElement::HTMLFormElement):
        (WebCore::HTMLFormElement::~HTMLFormElement):
        (WebCore::HTMLFormElement::elementForAlias):
        (WebCore::HTMLFormElement::addElementAlias):
            Maintain a map of known element aliases
        (WebCore::HTMLFormElement::getNamedElements):
            Get a list of elements matching the name, based both their
            current names and known aliases (earlier names).
            Keep the alias list in sync.
        * html/HTMLFormElement.h:

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

LayoutTests/ChangeLog
LayoutTests/fast/forms/old-names-expected.txt [new file with mode: 0644]
LayoutTests/fast/forms/old-names.html [new file with mode: 0644]
WebCore/ChangeLog
WebCore/bindings/js/JSHTMLFormElementCustom.cpp
WebCore/html/HTMLFormElement.cpp
WebCore/html/HTMLFormElement.h

index 326f18e..a49d119 100644 (file)
@@ -1,3 +1,16 @@
+2007-03-17  Antti Koivisto  <antti@apple.com>
+
+        Reviewed by Adele.
+        
+        Test for http://bugs.webkit.org/show_bug.cgi?id=12595
+        REGRESSION: Can't add item to cart at lnt.com (JS type error)
+        <rdar://problem/4722863>
+        
+        Expanded version of Darin's test case. 
+
+        * fast/forms/old-names-expected.txt: Added.
+        * fast/forms/old-names.html: Added.
+
 2007-03-16  Adele Peterson  <adele@apple.com>
 
         Reviewed by Hyatt.
diff --git a/LayoutTests/fast/forms/old-names-expected.txt b/LayoutTests/fast/forms/old-names-expected.txt
new file mode 100644 (file)
index 0000000..4468604
--- /dev/null
@@ -0,0 +1,114 @@
+This tests accessing form elements by name. IE only lets you look up names under the first name the element had and does not respond to name changes. Firefox remembers every name item has been accessed with, but forgets items that have not been accessed. This test has been written to expect the Firefox behavior.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS form.length is 2
+PASS form.original is a
+PASS form.originalB is b
+PASS form.second is undefined
+PASS form.third is undefined
+PASS form.elements.original is a
+PASS form.elements.originalB is b
+PASS form.elements.second is undefined
+PASS form.elements.third is undefined
+
+change the form item a's name to thisWillBeRemembered
+
+get the variable value through form element
+PASS form.thisWillBeRemembered is a
+
+now change the form item a's name to thisWillBeRememberedToo
+access it in boolean context
+
+accessed form.thisWillBeRememberedToo
+
+now change the form item a's name to thisWillBeForgotten
+
+get the variable value through collection
+PASS form.elements.thisWillBeForgotten is a
+
+now change the form item a's name to thisWillBeForgottenToo, but don't access it afterwards
+
+now change the form item a's name to second
+
+PASS form.length is 2
+PASS form.original is a
+PASS form.originalB is b
+PASS form.second is a
+PASS form.third is undefined
+PASS form.elements.original is undefined
+PASS form.elements.originalB is b
+PASS form.elements.second is a
+PASS form.elements.third is undefined
+
+now change the form item a's name to third
+
+PASS form.length is 2
+PASS form.original is a
+PASS form.originalB is b
+PASS form.second is a
+PASS form.third is a
+PASS form.elements.original is undefined
+PASS form.elements.originalB is b
+PASS form.elements.second is undefined
+PASS form.elements.third is a
+
+now change form item b's name to second
+
+PASS form.length is 2
+PASS form.original is a
+PASS form.originalB is b
+PASS form.second is b
+PASS form.elements.original is undefined
+PASS form.elements.originalB is undefined
+PASS form.elements.second is b
+
+now change a form item b's name to third
+
+PASS form.length is 2
+PASS form.original is a
+PASS form.originalB is b
+PASS form.second is b
+PASS form.third.length is 2
+PASS form.third[0] is a
+PASS form.third[1] is b
+PASS form.elements.original is undefined
+PASS form.elements.originalB is undefined
+PASS form.elements.second is undefined
+PASS form.elements.third.length is 2
+PASS form.elements.third[0] is a
+PASS form.elements.third[1] is b
+
+now change a form item b's name to fourth
+
+PASS form.third is a
+PASS form.third.length is undefined
+PASS form.elements.third is a
+PASS form.elements.third.length is undefined
+
+now remove element a
+
+PASS form.length is 1
+PASS form.original is a
+PASS form.originalB is b
+PASS form.second is b
+PASS form.third is a
+PASS form.fourth is b
+PASS form.elements.original is undefined
+PASS form.elements.originalB is undefined
+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
+
+PASS form.thisWillBeForgotten is undefined
+PASS form.thisWillBeForgottenToo is undefined
+PASS form.thisWillBeRemembered is a
+PASS form.thisWillBeRememberedToo is a
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/forms/old-names.html b/LayoutTests/fast/forms/old-names.html
new file mode 100644 (file)
index 0000000..b17fb7d
--- /dev/null
@@ -0,0 +1,168 @@
+<html>
+<head>
+<link rel="stylesheet" type="text/css" href="../js/resources/js-test-style.css">
+<script src="../js/resources/js-test-pre.js"></script>
+<script>
+function runTest()
+{
+    description("This tests accessing form elements by name. "
+        + "IE only lets you look up names under the first name the element had and "
+        + "does not respond to name changes. Firefox remembers every name item has been "
+        + "accessed with, but forgets items that have not been accessed. "
+        + "This test has been written to expect the Firefox behavior.");
+
+    form = document.getElementById('form');
+    a = document.getElementById('a');
+    b = document.getElementById('b');
+
+    shouldBe('form.length', '2');
+    shouldBe('form.original', 'a');
+    shouldBe('form.originalB', 'b');
+    shouldBe('form.second', 'undefined');
+    shouldBe('form.third', 'undefined');
+    shouldBe('form.elements.original', 'a');
+    shouldBe('form.elements.originalB', 'b');
+    shouldBe('form.elements.second', 'undefined');
+    shouldBe('form.elements.third', 'undefined');
+
+    debug('');   
+    debug("change the form item a's name to thisWillBeRemembered");
+    debug('');   
+    a.name="thisWillBeRemembered";
+    debug("get the variable value through form element");
+    shouldBe('form.thisWillBeRemembered', 'a');
+    debug('');   
+    debug("now change the form item a's name to thisWillBeRememberedToo");
+    debug("access it in boolean context");
+    a.name="thisWillBeRememberedToo";
+    debug('');   
+    if (form.thisWillBeRememberedToo)
+        debug('accessed form.thisWillBeRememberedToo');
+    debug('');   
+    debug("now change the form item a's name to thisWillBeForgotten");
+    debug('');   
+    a.name="thisWillBeForgotten";
+    debug("get the variable value through collection");
+    shouldBe('form.elements.thisWillBeForgotten', 'a');
+    debug('');   
+    debug("now change the form item a's name to thisWillBeForgottenToo, but don't access it afterwards");
+    a.name="thisWillBeForgottenToo";
+        
+    debug('');    
+    debug("now change the form item a's name to second");
+    debug('');
+    a.name="second";
+    
+    shouldBe('form.length', '2');
+    shouldBe('form.original', 'a');
+    shouldBe('form.originalB', 'b');
+    shouldBe('form.second', 'a');
+    shouldBe('form.third', 'undefined');
+    shouldBe('form.elements.original', 'undefined');
+    shouldBe('form.elements.originalB', 'b');
+    shouldBe('form.elements.second', 'a');
+    shouldBe('form.elements.third', 'undefined');
+
+    debug('');
+    debug("now change the form item a's name to third");
+    debug('');
+
+    a.name="third";
+
+    shouldBe('form.length', '2');
+    shouldBe('form.original', 'a');
+    shouldBe('form.originalB', 'b');
+    shouldBe('form.second', 'a');
+    shouldBe('form.third', 'a');
+    shouldBe('form.elements.original', 'undefined');
+    shouldBe('form.elements.originalB', 'b');
+    shouldBe('form.elements.second', 'undefined');
+    shouldBe('form.elements.third', 'a');
+
+    debug('');
+    debug("now change form item b's name to second");
+    debug('');
+
+    b.name="second";
+
+    shouldBe('form.length', '2');
+    shouldBe('form.original', 'a');
+    shouldBe('form.originalB', 'b');
+    shouldBe('form.second', 'b');
+    shouldBe('form.elements.original', 'undefined');
+    shouldBe('form.elements.originalB', 'undefined');
+    shouldBe('form.elements.second', 'b');
+
+    debug('');
+    debug("now change a form item b's name to third");
+    debug('');
+
+    form.originalB.name="third";
+
+    shouldBe('form.length', '2');
+    shouldBe('form.original', 'a');
+    shouldBe('form.originalB', 'b');
+    shouldBe('form.second', 'b');
+    shouldBe('form.third.length', '2');
+    shouldBe('form.third[0]', 'a');
+    shouldBe('form.third[1]', 'b');
+    shouldBe('form.elements.original', 'undefined');
+    shouldBe('form.elements.originalB', 'undefined');
+    shouldBe('form.elements.second', 'undefined');
+    shouldBe('form.elements.third.length', '2');
+    shouldBe('form.elements.third[0]', 'a');
+    shouldBe('form.elements.third[1]', 'b');
+    
+    debug('');
+    debug("now change a form item b's name to fourth");
+    debug('');
+
+    form.originalB.name="fourth";
+    
+    shouldBe('form.third', 'a');
+    shouldBe('form.third.length', 'undefined');
+    shouldBe('form.elements.third', 'a');
+    shouldBe('form.elements.third.length', 'undefined');
+
+    debug('');
+    debug("now remove element a");
+    debug('');
+
+    form.removeChild(a);
+
+    shouldBe('form.length', '1');
+    shouldBe('form.original', 'a');
+    shouldBe('form.originalB', 'b');
+    shouldBe('form.second', 'b');
+    shouldBe('form.third', 'a');
+    shouldBe('form.fourth', 'b');
+    shouldBe('form.elements.original', 'undefined');
+    shouldBe('form.elements.originalB', 'undefined');
+    shouldBe('form.elements.second', 'undefined');
+    shouldBe('form.elements.third', 'undefined');
+    shouldBe('form.elements.fourth', 'b');
+    debug('');
+    debug("check we still remember names we should");
+    debug('');
+
+    shouldBe('form.thisWillBeForgotten', 'undefined');
+    shouldBe('form.thisWillBeForgottenToo', 'undefined');
+    shouldBe('form.thisWillBeRemembered', 'a');
+    shouldBe('form.thisWillBeRememberedToo', 'a');
+    debug('');
+    successfullyParsed = true;
+}
+</script>
+</head>
+<body>
+<form id='form'>
+<input type='hidden' id='a' name='original'>
+<input type='hidden' id='b' name='originalB'>
+</form>
+<p id="description"></p>
+<div id="console"></div>
+<script>runTest();</script>
+<script src="../js/resources/js-test-post.js"></script>
+</body>
+</html>
index 236c343..e210ec9 100644 (file)
@@ -1,3 +1,36 @@
+2007-03-17  Antti Koivisto  <antti@apple.com>
+
+        Reviewed by Adele.
+
+        Fix http://bugs.webkit.org/show_bug.cgi?id=12595
+        REGRESSION: Can't add item to cart at lnt.com (JS type error)
+        <rdar://problem/4722863>
+        
+        Emulate Firefox behavior where form elements accessed by a name
+        can be accessed with that name later even if the name changes or
+        even if element is removed from the document.
+        
+        This is loosely based on Darin's earlier patch for the same problem but
+        is much less expansive. It takes somewhat different approach to more closely
+        mimic Firefox behavior. Includes expanded test case.
+
+        * bindings/js/JSHTMLFormElementCustom.cpp:
+        (WebCore::JSHTMLFormElement::canGetItemsForName):
+            Use new the HTMLFormElement::getNamedElements() method
+        (WebCore::JSHTMLFormElement::nameGetter):
+            Use new the HTMLFormElement::getNamedElements() method
+        * html/HTMLFormElement.cpp:
+        (WebCore::HTMLFormElement::HTMLFormElement):
+        (WebCore::HTMLFormElement::~HTMLFormElement):
+        (WebCore::HTMLFormElement::elementForAlias):
+        (WebCore::HTMLFormElement::addElementAlias):
+            Maintain a map of known element aliases
+        (WebCore::HTMLFormElement::getNamedElements):
+            Get a list of elements matching the name, based both their
+            current names and known aliases (earlier names).
+            Keep the alias list in sync.
+        * html/HTMLFormElement.h:
+
 2007-03-17  Adele Peterson  <adele@apple.com>
 
         Reviewed by Hyatt.
index 51df945..2c44e03 100644 (file)
@@ -35,17 +35,23 @@ namespace WebCore {
 
 bool JSHTMLFormElement::canGetItemsForName(ExecState* exec, HTMLFormElement* form, const AtomicString& propertyName)
 {
-    // FIXME: ideally there should be a lighter-weight way of doing this
-    JSValue* namedItems = JSHTMLCollection(exec, form->elements().get()).getNamedItems(exec, propertyName);
-    return !namedItems->isUndefined();
+    Vector<RefPtr<Node> > namedItems;
+    form->getNamedElements(propertyName, namedItems);
+    return namedItems.size();
 }
 
 JSValue* JSHTMLFormElement::nameGetter(ExecState* exec, JSObject*, const Identifier& propertyName, const PropertySlot& slot)
 {
-    JSHTMLElement* thisObj = static_cast<JSHTMLElement*>(slot.slotBase());
-    HTMLFormElement* form = static_cast<HTMLFormElement*>(thisObj->impl());
+    HTMLFormElement* form = static_cast<HTMLFormElement*>(static_cast<JSHTMLElement*>(slot.slotBase())->impl());
     
-    return JSHTMLCollection(exec, form->elements().get()).getNamedItems(exec, propertyName);
+    Vector<RefPtr<Node> > namedItems;
+    form->getNamedElements(propertyName, namedItems);
+    
+    if (namedItems.size() == 1)
+        return toJS(exec, namedItems[0].get());
+    if (namedItems.size() > 1) 
+        return new DOMNamedNodesCollection(exec, namedItems);
+    return jsUndefined();
 }
 
 }
index 7396cf4..a7ad6b8 100644 (file)
@@ -49,21 +49,23 @@ using namespace HTMLNames;
 
 HTMLFormElement::HTMLFormElement(Document* doc)
     : HTMLElement(formTag, doc)
+    , m_elementAliases(0)
+    , collectionInfo(0)
+    , m_enctype("application/x-www-form-urlencoded")
+    , m_post(false)
+    , m_multipart(false)
+    , m_autocomplete(true)
+    , m_insubmit(false)
+    , m_doingsubmit(false)
+    , m_inreset(false)
+    , m_malformed(false)
+    , m_preserveAcrossRemove(false)
 {
-    collectionInfo = 0;
-    m_post = false;
-    m_multipart = false;
-    m_autocomplete = true;
-    m_insubmit = false;
-    m_doingsubmit = false;
-    m_inreset = false;
-    m_enctype = "application/x-www-form-urlencoded";
-    m_malformed = false;
-    m_preserveAcrossRemove = false;
 }
 
 HTMLFormElement::~HTMLFormElement()
 {
+    delete m_elementAliases;
     delete collectionInfo;
     
     for (unsigned i = 0; i < formElements.size(); ++i)
@@ -638,5 +640,44 @@ void HTMLFormElement::setTarget(const String &value)
 {
     setAttribute(targetAttr, value);
 }
-    
+
+PassRefPtr<HTMLGenericFormElement> HTMLFormElement::elementForAlias(const AtomicString& alias)
+{
+    if (alias.isEmpty() || !m_elementAliases)
+        return 0;
+    return m_elementAliases->get(alias.impl());
+}
+
+void HTMLFormElement::addElementAlias(HTMLGenericFormElement* element, const AtomicString& alias)
+{
+    if (alias.isEmpty())
+        return;
+    if (!m_elementAliases)
+        m_elementAliases = new AliasMap;
+    m_elementAliases->set(alias.impl(), element);
+}
+
+void HTMLFormElement::getNamedElements(const AtomicString& name, Vector<RefPtr<Node> >& namedItems)
+{
+    elements()->namedItems(name, namedItems);
+
+    // see if we have seen something with this name before
+    RefPtr<HTMLGenericFormElement> aliasElem;
+    if (aliasElem = elementForAlias(name)) {
+        bool found = false;
+        for (unsigned n = 0; n < namedItems.size(); n++) {
+            if (namedItems[n] == aliasElem.get()) {
+                found = true;
+                break;
+            }
+        }
+        if (!found)
+            // we have seen it before but it is gone now. still, we need to return it.
+            namedItems.append(aliasElem.get());
+    }
+    // name has been accessed, remember it
+    if (namedItems.size() && aliasElem != namedItems.first())
+        addElementAlias(static_cast<HTMLGenericFormElement*>(namedItems.first().get()), name);        
+}
+
 } // namespace
index d6409d4..6bbf573 100644 (file)
@@ -50,6 +50,8 @@ public:
     virtual void handleLocalEvents(Event*, bool useCapture);
      
     PassRefPtr<HTMLCollection> elements();
+    void getNamedElements(const AtomicString&, Vector<RefPtr<Node> >&);
+    
     unsigned length() const;
     Node* item(unsigned index);
 
@@ -98,6 +100,9 @@ public:
 
     virtual String target() const;
     void setTarget(const String&);
+    
+    PassRefPtr<HTMLGenericFormElement> elementForAlias(const AtomicString&);
+    void addElementAlias(HTMLGenericFormElement*, const AtomicString& alias);
 
     // FIXME: Change this to be private after getting rid of all the clients.
     Vector<HTMLGenericFormElement*> formElements;
@@ -109,13 +114,15 @@ private:
 
     friend class HTMLFormCollection;
 
+    typedef HashMap<RefPtr<AtomicStringImpl>, RefPtr<HTMLGenericFormElement> > AliasMap;
+    
+    AliasMap* m_elementAliases;
     HTMLCollection::CollectionInfo* collectionInfo;
 
     Vector<HTMLImageElement*> imgElements;
     String m_url;
     String m_target;
     String m_enctype;
-    String m_boundary;
     String m_acceptcharset;
     bool m_post : 1;
     bool m_multipart : 1;