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 326f18ed4b34955470015150ff5383aa8d212017..a49d1191ed64eaad0fcc4dea4b862b063c5485a1 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 236c34346cda18d2bd0c638acb54c6772d0da62c..e210ec9cafe8ac8336cc8c41c4f95a50e68c8917 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 51df9456070ea24da6dc583beb5f29df0bb19745..2c44e03304b7a5355fef65333fa199fbd41155a4 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 7396cf4db3015e33c4d2d80d7aa22ecdab7a308e..a7ad6b8d8a4ca8958d56906872dcd4754e08fec2 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 d6409d4540ec18481648fccf8d9995a3ef819e83..6bbf57357feeb15c17843d6205183ff28e944353 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;