Form state restore: Classify form control sates by owners in internal and serialized...
authortkent@chromium.org <tkent@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 19 Jul 2012 02:48:51 +0000 (02:48 +0000)
committertkent@chromium.org <tkent@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 19 Jul 2012 02:48:51 +0000 (02:48 +0000)
https://bugs.webkit.org/show_bug.cgi?id=91594

Reviewed by Hajime Morita.

Source/WebCore:

Before this change, the internal representation of form control states
was a map like:

    {name, type, formKey} -> [FormControlState, FormControlState, ...]

Also, the serialized representation was:

    [name, type, formKey, FormControlState,
     name, type, formKey, formControlState,
     ...]

This changes these represenations. The internal representation is

    A map owned by FormController:
        formKey -> SavedFormState
    SavedFormState owns a map like:
        {name, type} -> [FormControlState, FormControlState, ...]

The serialized representation is:

    [formKey, count,
     name, type, FormControlState,
     name, type, FormControlState,
     name, type, FormControlState,
     ....
     formKey, count,
     name, type, FormControlState,
     name, type, FormControlState,
     name, type, FormControlState,
     ....]

The serialized representation would be smaller in many cases because
each of items doesn't have formKey.

Tests: Covered by fast/forms/state-restore-broken-state.html,
state-restore-per-form.html, and state-restore-skip-stateless.html.

* html/FormController.cpp:
(FormElementKey): Remove formKey argument and m_formKey.
(WebCore::FormElementKey::FormElementKey):
(WebCore::FormElementKey::operator=):
(WebCore::FormElementKey::ref):
(WebCore::FormElementKey::deref):
(WebCore::operator==):

(SavedFormState):
 - The role of SavedFromState was changed. It represented all of control
  states. Now it represent control states owned by a signle form.
 - Make this non-copyable and fast-allocated.
 - Add m_controlStateCount, a counter of FormControlState
(WebCore::SavedFormState::SavedFormState):
 Initialize m_controlStateCount.
(WebCore::isNotFormControlTypeCharacter):
Moved from another place in this file.
(WebCore::SavedFormState::deserialize):
Added. Build a SavedFromState with the specified stateVector.
(WebCore::SavedFormState::serializeTo):
Added. m_controlStateCount is needed here.
(WebCore::SavedFormState::appendControlState):
 - Remove formKey arguement.
 - Update m_controlStateCount.
(WebCore::SavedFormState::takeControlState): ditto.

(WebCore::formStateSignature): Bump the version.
(WebCore::FormController::createSavedFormStateMap):
Added. Build a SavedFormStateMap from the form control list. This is a
helper for formElementsState, and classifies controls in the document.
(WebCore::FormController::formElementsState):
Build a SavedFormStateMap with createSavedFormStateMap(),
then serializes it.
(WebCore::FormController::setStateForNewFormElements):
Deserialize SavedFormState, and build SavedFormStateMap.
(WebCore::FormController::takeStateForFormElement):
Finds the corresponding SavedFormState with a formKey, and uses it.
* html/FormController.h:
Declare createSavedFormStateMap(), and moved typedefs for it.

LayoutTests:

Because the state serialization order depends on iteration order of
HashMap, serialized state array is not stable. We should remove raw dump
of serialized state array.

* fast/forms/state-restore-per-form.html: Remove the code to dump the
form state. It's useless because the test have enough assertions.
* fast/forms/state-restore-per-form-expected.txt:
* fast/forms/resources/state-restore-per-form-back.html: Removed.

* fast/forms/resources/state-restore-skip-stateless-dump.html:
Instead of dumping form state, check existence of types with JavaScript code.
* fast/forms/state-restore-skip-stateless-expected.txt:
* fast/forms/state-restore-skip-stateless.html: Fix a typo.

* fast/forms/state-restore-broken-state-expected.txt:
Update the expectation because of the serialization format change.

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

LayoutTests/ChangeLog
LayoutTests/fast/forms/resources/state-restore-per-form-back.html [deleted file]
LayoutTests/fast/forms/resources/state-restore-skip-stateless-dump.html
LayoutTests/fast/forms/state-restore-broken-state-expected.txt
LayoutTests/fast/forms/state-restore-per-form-expected.txt
LayoutTests/fast/forms/state-restore-per-form.html
LayoutTests/fast/forms/state-restore-skip-stateless-expected.txt
LayoutTests/fast/forms/state-restore-skip-stateless.html
Source/WebCore/ChangeLog
Source/WebCore/html/FormController.cpp
Source/WebCore/html/FormController.h

index 64ae064..facd87c 100644 (file)
@@ -1,3 +1,27 @@
+2012-07-18  Kent Tamura  <tkent@chromium.org>
+
+        Form state restore: Classify form control sates by owners in internal and serialized representations
+        https://bugs.webkit.org/show_bug.cgi?id=91594
+
+        Reviewed by Hajime Morita.
+
+        Because the state serialization order depends on iteration order of
+        HashMap, serialized state array is not stable. We should remove raw dump
+        of serialized state array.
+
+        * fast/forms/state-restore-per-form.html: Remove the code to dump the
+        form state. It's useless because the test have enough assertions.
+        * fast/forms/state-restore-per-form-expected.txt:
+        * fast/forms/resources/state-restore-per-form-back.html: Removed.
+
+        * fast/forms/resources/state-restore-skip-stateless-dump.html:
+        Instead of dumping form state, check existence of types with JavaScript code.
+        * fast/forms/state-restore-skip-stateless-expected.txt:
+        * fast/forms/state-restore-skip-stateless.html: Fix a typo.
+
+        * fast/forms/state-restore-broken-state-expected.txt:
+        Update the expectation because of the serialization format change.
+
 2012-07-18  Hayato Ito  <hayato@chromium.org>
 
         [Shadow] A 'selectstart' event fired on distributed nodes should not be stopped at shadow boundary of the parent shadow host.
diff --git a/LayoutTests/fast/forms/resources/state-restore-per-form-back.html b/LayoutTests/fast/forms/resources/state-restore-per-form-back.html
deleted file mode 100644 (file)
index ee1c825..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<script>
-if (window.internals) {
-    buffer = 'Form state vector:';
-    var states = internals.formControlStateOfPreviousHistoryItem();
-    for (var i = 1; i < states.length; ++i) {
-        if (i % 5 == 1) {
-            console.log(buffer);
-            buffer = '';
-        }
-        buffer += states[i] + ', ';
-    }
-    console.log(buffer);
-    console.log('');
-}
-history.back();
-</script>
index 2f8fbcb..cd48776 100644 (file)
@@ -1,17 +1,24 @@
 <script src="../../js/resources/js-test-pre.js"></script>
 <script>
 if (window.internals) {
-    buffer = 'Form state vector:';
+    var statefulTypes = ['hidden', 'text', 'tel', 'url', 'email', 'number', 'range', 'checkbox',
+                         'radio', 'file', 'select-one', 'select-multiple', 'textarea'];
+    var statelessTypes = ['fieldset', 'password', 'submit', 'reset', 'button', 'keygen', 'output'];
+
     var states = internals.formControlStateOfPreviousHistoryItem();
-    for (var i = 1; i < states.length; ++i) {
-        if (i % 4 == 1) {
-            console.log(buffer);
-            buffer = '';
-        }
-        buffer += states[i] + ', ';
+    for (var i = 0; i < statefulTypes.length; ++i) {
+        if (states.indexOf(statefulTypes[i]) >= 0)
+            testPassed('There is a state for ' + statefulTypes[i]);
+        else
+            testFailed('There no state for ' + statefulTypes[i]);
+    }
+
+    for (var i = 0; i < statelessTypes.length; ++i) {
+        if (states.indexOf(statelessTypes[i]) < 0)
+            testPassed('There is no state for ' + statelessTypes[i]);
+        else
+            testFailed('There is a state for ' + statelessTypes[i]);
     }
-    console.log(buffer);
-    console.log('');
 }
 jsTestIsAsync = true;
 finishJSTest();
index 122041e..7f1740d 100644 (file)
@@ -1,4 +1,4 @@
-CONSOLE MESSAGE: line 5: Generated state: [name1,text,state-restore-broken-state-2.html #0,1,modified]
+CONSOLE MESSAGE: line 5: Generated state: [state-restore-broken-state-2.html #0,1,name1,text,1,modified]
 The value was modified in the first load of state-restore-broken-state-1.html, but it should not be restored because the state-restore-broken-state-2.html breaks the state.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
index e1808c0..2d9b076 100644 (file)
@@ -1,16 +1,3 @@
-CONSOLE MESSAGE: line 7: Form state vector:
-CONSOLE MESSAGE: line 7: , text, No owner, 1, visited, 
-CONSOLE MESSAGE: line 7: before, text, No owner, 1, form3-outer1-modified, 
-CONSOLE MESSAGE: line 7: before, text, No owner, 1, noowner1-modified, 
-CONSOLE MESSAGE: line 7: before, text, No owner, 1, form2-outer1-modified, 
-CONSOLE MESSAGE: line 7: common-name1, text, state-restore-per-form-back.html #0, 0, common-name1, 
-CONSOLE MESSAGE: line 7: text, state-restore-per-form-back.html #0, 1, form1-control2-modified, common-name1, 
-CONSOLE MESSAGE: line 7: text, http://example.com/foo.cgi#bar #0, 1, query-changed-control1-modified, username, 
-CONSOLE MESSAGE: line 7: text, http://example.com/foo.cgi#bar #1, 1, username-modified, common-name1, 
-CONSOLE MESSAGE: line 7: text, http://example.com/foo.cgi#bar #1, 1, form3-control2-modified, after, 
-CONSOLE MESSAGE: line 7: text, No owner, 1, form3-outer2-modified, after, 
-CONSOLE MESSAGE: line 12: text, No owner, 1, form2-outer2-modified, 
-CONSOLE MESSAGE: line 13: 
 Confirm we can restore correctly even if the order of forms are changed:
 PASS $("form1-control1").value is "initial"
 PASS $("form1-control2").value is "form1-control2-modified"
index d6d6e6c..15d60a2 100644 (file)
@@ -18,7 +18,7 @@ function makeForms(stage) {
         (stage == 1 ? '<input value=initial name=before id=noowner1>' : '') +
         '<input value=initial id=form2-outer1 name=before form=form2>';
 
-    var backForm = '<form action="resources/state-restore-per-form-back.html" id=form1>' +
+    var backForm = '<form action="data:text/html,&lt;script>history.back();&lt;/script>" id=form1>' +
         '<input value=initial id=form1-control1 name=common-name1>' +
         '<input value=initial id=form1-control2 name=common-name1>' +
         '</form>';
index 5448b6e..95ec3a2 100644 (file)
@@ -1,22 +1,24 @@
 CONSOLE MESSAGE: line 44: Test if state of stateless form control types are not saved.
-CONSOLE MESSAGE: line 8: Form state vector:
-CONSOLE MESSAGE: line 8: name02, hidden, No owner, 0, 
-CONSOLE MESSAGE: line 8: name03, text, No owner, 0, 
-CONSOLE MESSAGE: line 8: name04, tel, No owner, 0, 
-CONSOLE MESSAGE: line 8: name05, url, No owner, 0, 
-CONSOLE MESSAGE: line 8: name06, email, No owner, 0, 
-CONSOLE MESSAGE: line 8: name07, text, No owner, 0, 
-CONSOLE MESSAGE: line 8: name14, number, No owner, 0, 
-CONSOLE MESSAGE: line 8: name15, range, No owner, 1, 
-CONSOLE MESSAGE: line 8: 50, name17, checkbox, No owner, 
-CONSOLE MESSAGE: line 8: 1, off, name18, radio, 
-CONSOLE MESSAGE: line 8: No owner, 1, off, name19, 
-CONSOLE MESSAGE: line 8: file, No owner, 0, name26, 
-CONSOLE MESSAGE: line 8: select-one, No owner, 0, name27, 
-CONSOLE MESSAGE: line 8: select-multiple, No owner, 0, name28, 
-CONSOLE MESSAGE: line 8: textarea, No owner, 0, , 
-CONSOLE MESSAGE: line 13: select-one, No owner, 1, 2048 (High Grade), 
-CONSOLE MESSAGE: line 14: 
+PASS There is a state for hidden
+PASS There is a state for text
+PASS There is a state for tel
+PASS There is a state for url
+PASS There is a state for email
+PASS There is a state for number
+PASS There is a state for range
+PASS There is a state for checkbox
+PASS There is a state for radio
+PASS There is a state for file
+PASS There is a state for select-one
+PASS There is a state for select-multiple
+PASS There is a state for textarea
+PASS There is no state for fieldset
+PASS There is no state for password
+PASS There is no state for submit
+PASS There is no state for reset
+PASS There is no state for button
+PASS There is no state for keygen
+PASS There is no state for output
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 4a20bdd..8fa130b 100644 (file)
@@ -13,7 +13,7 @@
 <input name=name04 type=tel>
 <input name=name05 type=url>
 <input name=name06 type=email>
-<input name=name07 type=pasword>
+<input name=name07 type=password>
 <!-- Don't test optional types.
 <input name=name08 type=datetime>
 <input name=name09 type=date>
index cf26707..2bde9d7 100644 (file)
@@ -1,3 +1,87 @@
+2012-07-18  Kent Tamura  <tkent@chromium.org>
+
+        Form state restore: Classify form control sates by owners in internal and serialized representations
+        https://bugs.webkit.org/show_bug.cgi?id=91594
+
+        Reviewed by Hajime Morita.
+
+        Before this change, the internal representation of form control states
+        was a map like:
+
+            {name, type, formKey} -> [FormControlState, FormControlState, ...]
+
+        Also, the serialized representation was:
+
+            [name, type, formKey, FormControlState,
+             name, type, formKey, formControlState,
+             ...]
+
+        This changes these represenations. The internal representation is
+
+            A map owned by FormController:
+                formKey -> SavedFormState
+            SavedFormState owns a map like:
+                {name, type} -> [FormControlState, FormControlState, ...]
+
+        The serialized representation is:
+
+            [formKey, count,
+             name, type, FormControlState,
+             name, type, FormControlState,
+             name, type, FormControlState,
+             ....
+             formKey, count,
+             name, type, FormControlState,
+             name, type, FormControlState,
+             name, type, FormControlState,
+             ....]
+
+        The serialized representation would be smaller in many cases because
+        each of items doesn't have formKey.
+
+        Tests: Covered by fast/forms/state-restore-broken-state.html,
+        state-restore-per-form.html, and state-restore-skip-stateless.html.
+
+        * html/FormController.cpp:
+        (FormElementKey): Remove formKey argument and m_formKey.
+        (WebCore::FormElementKey::FormElementKey):
+        (WebCore::FormElementKey::operator=):
+        (WebCore::FormElementKey::ref):
+        (WebCore::FormElementKey::deref):
+        (WebCore::operator==):
+
+        (SavedFormState):
+         - The role of SavedFromState was changed. It represented all of control
+          states. Now it represent control states owned by a signle form.
+         - Make this non-copyable and fast-allocated.
+         - Add m_controlStateCount, a counter of FormControlState
+        (WebCore::SavedFormState::SavedFormState):
+         Initialize m_controlStateCount.
+        (WebCore::isNotFormControlTypeCharacter):
+        Moved from another place in this file.
+        (WebCore::SavedFormState::deserialize):
+        Added. Build a SavedFromState with the specified stateVector.
+        (WebCore::SavedFormState::serializeTo):
+        Added. m_controlStateCount is needed here.
+        (WebCore::SavedFormState::appendControlState):
+         - Remove formKey arguement.
+         - Update m_controlStateCount.
+        (WebCore::SavedFormState::takeControlState): ditto.
+
+        (WebCore::formStateSignature): Bump the version.
+        (WebCore::FormController::createSavedFormStateMap):
+        Added. Build a SavedFormStateMap from the form control list. This is a
+        helper for formElementsState, and classifies controls in the document.
+        (WebCore::FormController::formElementsState):
+        Build a SavedFormStateMap with createSavedFormStateMap(),
+        then serializes it.
+        (WebCore::FormController::setStateForNewFormElements):
+        Deserialize SavedFormState, and build SavedFormStateMap.
+        (WebCore::FormController::takeStateForFormElement):
+        Finds the corresponding SavedFormState with a formKey, and uses it.
+        * html/FormController.h:
+        Declare createSavedFormStateMap(), and moved typedefs for it.
+
 2012-07-18  Antoine Labour  <piman@chromium.org>
 
         [chromium] Introduce CCResourceProvider, replacing TextureAllocator and hiding textures from clients to allow transport
index 46241be..7a992b8 100644 (file)
@@ -79,14 +79,13 @@ FormControlState FormControlState::deserialize(const Vector<String>& stateVector
 
 class FormElementKey {
 public:
-    FormElementKey(AtomicStringImpl* = 0, AtomicStringImpl* = 0, AtomicStringImpl* = 0);
+    FormElementKey(AtomicStringImpl* = 0, AtomicStringImpl* = 0);
     ~FormElementKey();
     FormElementKey(const FormElementKey&);
     FormElementKey& operator=(const FormElementKey&);
 
     AtomicStringImpl* name() const { return m_name; }
     AtomicStringImpl* type() const { return m_type; }
-    AtomicStringImpl* formKey() const { return m_formKey; }
 
     // Hash table deleted values, which are only constructed and never copied or destroyed.
     FormElementKey(WTF::HashTableDeletedValueType) : m_name(hashTableDeletedValue()) { }
@@ -100,13 +99,11 @@ private:
 
     AtomicStringImpl* m_name;
     AtomicStringImpl* m_type;
-    AtomicStringImpl* m_formKey;
 };
 
-FormElementKey::FormElementKey(AtomicStringImpl* name, AtomicStringImpl* type, AtomicStringImpl* formKey)
+FormElementKey::FormElementKey(AtomicStringImpl* name, AtomicStringImpl* type)
     : m_name(name)
     , m_type(type)
-    , m_formKey(formKey)
 {
     ref();
 }
@@ -119,7 +116,6 @@ FormElementKey::~FormElementKey()
 FormElementKey::FormElementKey(const FormElementKey& other)
     : m_name(other.name())
     , m_type(other.type())
-    , m_formKey(other.formKey())
 {
     ref();
 }
@@ -130,7 +126,6 @@ FormElementKey& FormElementKey::operator=(const FormElementKey& other)
     deref();
     m_name = other.name();
     m_type = other.type();
-    m_formKey = other.formKey();
     return *this;
 }
 
@@ -140,8 +135,6 @@ void FormElementKey::ref() const
         name()->ref();
     if (type())
         type()->ref();
-    if (formKey())
-        formKey()->ref();
 }
 
 void FormElementKey::deref() const
@@ -150,13 +143,11 @@ void FormElementKey::deref() const
         name()->deref();
     if (type())
         type()->deref();
-    if (formKey())
-        formKey()->deref();
 }
 
 inline bool operator==(const FormElementKey& a, const FormElementKey& b)
 {
-    return a.name() == b.name() && a.type() == b.type() && a.formKey() == b.formKey();
+    return a.name() == b.name() && a.type() == b.type();
 }
 
 struct FormElementKeyHash {
@@ -178,17 +169,23 @@ struct FormElementKeyHashTraits : WTF::GenericHashTraits<FormElementKey> {
 // ----------------------------------------------------------------------------
 
 class SavedFormState {
+    WTF_MAKE_NONCOPYABLE(SavedFormState);
+    WTF_MAKE_FAST_ALLOCATED;
+
 public:
     static PassOwnPtr<SavedFormState> create();
+    static PassOwnPtr<SavedFormState> deserialize(const Vector<String>&, size_t& index);
+    void serializeTo(Vector<String>&) const;
     bool isEmpty() const { return m_stateForNewFormElements.isEmpty(); }
-    void appendControlState(const AtomicString& name, const AtomicString& type, const AtomicString& formKey, const FormControlState&);
-    FormControlState takeControlState(const AtomicString& name, const AtomicString& type, const AtomicString& formKey);
+    void appendControlState(const AtomicString& name, const AtomicString& type, const FormControlState&);
+    FormControlState takeControlState(const AtomicString& name, const AtomicString& type);
 
 private:
-    SavedFormState() { }
+    SavedFormState() : m_controlStateCount(0) { }
 
     typedef HashMap<FormElementKey, Deque<FormControlState>, FormElementKeyHash, FormElementKeyHashTraits> FormElementStateMap;
     FormElementStateMap m_stateForNewFormElements;
+    size_t m_controlStateCount;
 };
 
 PassOwnPtr<SavedFormState> SavedFormState::create()
@@ -196,9 +193,50 @@ PassOwnPtr<SavedFormState> SavedFormState::create()
     return adoptPtr(new SavedFormState);
 }
 
-void SavedFormState::appendControlState(const AtomicString& name, const AtomicString& type, const AtomicString& formKey, const FormControlState& state)
+static bool isNotFormControlTypeCharacter(UChar ch)
+{
+    return ch != '-' && (ch > 'z' || ch < 'a');
+}
+
+PassOwnPtr<SavedFormState> SavedFormState::deserialize(const Vector<String>& stateVector, size_t& index)
+{
+    if (index >= stateVector.size())
+        return nullptr;
+    // FIXME: We need String::toSizeT().
+    size_t itemCount = stateVector[index++].toUInt();
+    if (!itemCount)
+        return nullptr;
+    OwnPtr<SavedFormState> savedFormState = adoptPtr(new SavedFormState);
+    while (itemCount--) {
+        if (index + 1 >= stateVector.size())
+            return nullptr;
+        String name = stateVector[index++];
+        String type = stateVector[index++];
+        FormControlState state = FormControlState::deserialize(stateVector, index);
+        if (type.isEmpty() || type.find(isNotFormControlTypeCharacter) != notFound || state.isFailure())
+            return nullptr;
+        savedFormState->appendControlState(name, type, state);
+    }
+    return savedFormState.release();
+}
+
+void SavedFormState::serializeTo(Vector<String>& stateVector) const
+{
+    stateVector.append(String::number(m_controlStateCount));
+    for (FormElementStateMap::const_iterator it = m_stateForNewFormElements.begin(); it != m_stateForNewFormElements.end(); ++it) {
+        const FormElementKey& key = it->first;
+        const Deque<FormControlState>& queue = it->second;
+        for (Deque<FormControlState>::const_iterator queIterator = queue.begin(); queIterator != queue.end(); ++queIterator) {
+            stateVector.append(key.name());
+            stateVector.append(key.type());
+            queIterator->serializeTo(stateVector);
+        }
+    }
+}
+
+void SavedFormState::appendControlState(const AtomicString& name, const AtomicString& type, const FormControlState& state)
 {
-    FormElementKey key(name.impl(), type.impl(), formKey.impl());
+    FormElementKey key(name.impl(), type.impl());
     FormElementStateMap::iterator it = m_stateForNewFormElements.find(key);
     if (it != m_stateForNewFormElements.end())
         it->second.append(state);
@@ -207,17 +245,19 @@ void SavedFormState::appendControlState(const AtomicString& name, const AtomicSt
         stateList.append(state);
         m_stateForNewFormElements.set(key, stateList);
     }
+    m_controlStateCount++;
 }
 
-FormControlState SavedFormState::takeControlState(const AtomicString& name, const AtomicString& type, const AtomicString& formKey)
+FormControlState SavedFormState::takeControlState(const AtomicString& name, const AtomicString& type)
 {
     if (m_stateForNewFormElements.isEmpty())
         return FormControlState();
-    FormElementStateMap::iterator it = m_stateForNewFormElements.find(FormElementKey(name.impl(), type.impl(), formKey.impl()));
+    FormElementStateMap::iterator it = m_stateForNewFormElements.find(FormElementKey(name.impl(), type.impl()));
     if (it == m_stateForNewFormElements.end())
         return FormControlState();
     ASSERT(it->second.size());
     FormControlState state = it->second.takeFirst();
+    m_controlStateCount--;
     if (!it->second.size())
         m_stateForNewFormElements.remove(it);
     return state;
@@ -304,26 +344,35 @@ static String formStateSignature()
     // In the legacy version of serialized state, the first item was a name
     // attribute value of a form control. The following string literal should
     // contain some characters which are rarely used for name attribute values.
-    DEFINE_STATIC_LOCAL(String, signature, ("\n\r?% WebKit serialized form state version 5 \n\r=&"));
+    DEFINE_STATIC_LOCAL(String, signature, ("\n\r?% WebKit serialized form state version 6 \n\r=&"));
     return signature;
 }
 
-Vector<String> FormController::formElementsState() const
+PassOwnPtr<FormController::SavedFormStateMap> FormController::createSavedFormStateMap(const FormElementListHashSet& controlList)
 {
     OwnPtr<FormKeyGenerator> keyGenerator = FormKeyGenerator::create();
+    OwnPtr<SavedFormStateMap> stateMap = adoptPtr(new SavedFormStateMap);
+    for (FormElementListHashSet::const_iterator it = controlList.begin(); it != controlList.end(); ++it) {
+        HTMLFormControlElementWithState* control = *it;
+        if (!control->shouldSaveAndRestoreFormControlState())
+            continue;
+        SavedFormStateMap::AddResult result = stateMap->add(keyGenerator->formKey(*control).impl(), nullptr);
+        if (result.isNewEntry)
+            result.iterator->second = SavedFormState::create();
+        result.iterator->second->appendControlState(control->name(), control->type(), control->saveFormControlState());
+    }
+    return stateMap.release();
+}
+
+Vector<String> FormController::formElementsState() const
+{
+    OwnPtr<SavedFormStateMap> stateMap = createSavedFormStateMap(m_formElementsWithState);
     Vector<String> stateVector;
-    stateVector.reserveInitialCapacity(m_formElementsWithState.size() * 5 + 1);
+    stateVector.reserveInitialCapacity(m_formElementsWithState.size() * 4);
     stateVector.append(formStateSignature());
-    typedef FormElementListHashSet::const_iterator Iterator;
-    Iterator end = m_formElementsWithState.end();
-    for (Iterator it = m_formElementsWithState.begin(); it != end; ++it) {
-        HTMLFormControlElementWithState* elementWithState = *it;
-        if (!elementWithState->shouldSaveAndRestoreFormControlState())
-            continue;
-        stateVector.append(elementWithState->name().string());
-        stateVector.append(elementWithState->formControlType().string());
-        stateVector.append(keyGenerator->formKey(*elementWithState).string());
-        elementWithState->saveFormControlState().serializeTo(stateVector);
+    for (SavedFormStateMap::const_iterator it = stateMap->begin(); it != stateMap->end(); ++it) {
+        stateVector.append(it->first.get());
+        it->second->serializeTo(stateVector);
     }
     bool hasOnlySignature = stateVector.size() == 1;
     if (hasOnlySignature)
@@ -331,11 +380,6 @@ Vector<String> FormController::formElementsState() const
     return stateVector;
 }
 
-static bool isNotFormControlTypeCharacter(UChar ch)
-{
-    return ch != '-' && (ch > 'z' || ch < 'a');
-}
-
 void FormController::setStateForNewFormElements(const Vector<String>& stateVector)
 {
     m_formElementsWithState.clear();
@@ -344,30 +388,31 @@ void FormController::setStateForNewFormElements(const Vector<String>& stateVecto
     if (stateVector.size() < 1 || stateVector[i++] != formStateSignature())
         return;
 
-    while (i + 2 < stateVector.size()) {
-        AtomicString name = stateVector[i++];
-        AtomicString type = stateVector[i++];
+    while (i + 1 < stateVector.size()) {
         AtomicString formKey = stateVector[i++];
-        FormControlState state = FormControlState::deserialize(stateVector, i);
-        if (type.isEmpty() || type.impl()->find(isNotFormControlTypeCharacter) != notFound || state.isFailure())
+        OwnPtr<SavedFormState> state = SavedFormState::deserialize(stateVector, i);
+        if (!state) {
+            i = 0;
             break;
-        if (!m_savedFormState)
-            m_savedFormState = SavedFormState::create();
-        m_savedFormState->appendControlState(name, type, formKey, state);
+        }
+        m_savedFormStateMap.add(formKey.impl(), state.release());
     }
     if (i != stateVector.size())
-        m_savedFormState.clear();
+        m_savedFormStateMap.clear();
 }
 
 FormControlState FormController::takeStateForFormElement(const HTMLFormControlElementWithState& control)
 {
-    if (!m_savedFormState)
+    if (m_savedFormStateMap.isEmpty())
         return FormControlState();
     if (!m_formKeyGenerator)
         m_formKeyGenerator = FormKeyGenerator::create();
-    FormControlState state = m_savedFormState->takeControlState(control.name(), control.type(), m_formKeyGenerator->formKey(control));
-    if (m_savedFormState->isEmpty())
-        m_savedFormState.clear();
+    SavedFormStateMap::iterator it = m_savedFormStateMap.find(m_formKeyGenerator->formKey(control).impl());
+    if (it == m_savedFormStateMap.end())
+        return FormControlState();
+    FormControlState state = it->second->takeControlState(control.name(), control.type());
+    if (it->second->isEmpty())
+        m_savedFormStateMap.remove(it);
     return state;
 }
 
index 2dd7b22..5d3748d 100644 (file)
@@ -93,15 +93,16 @@ public:
     void restoreControlStateIn(HTMLFormElement&);
 
 private:
+    typedef ListHashSet<HTMLFormControlElementWithState*, 64> FormElementListHashSet;
+    typedef HashMap<RefPtr<AtomicStringImpl>, OwnPtr<SavedFormState> > SavedFormStateMap;
+
     FormController();
+    static PassOwnPtr<SavedFormStateMap> createSavedFormStateMap(const FormElementListHashSet&);
     FormControlState takeStateForFormElement(const HTMLFormControlElementWithState&);
 
     CheckedRadioButtons m_checkedRadioButtons;
-
-    typedef ListHashSet<HTMLFormControlElementWithState*, 64> FormElementListHashSet;
     FormElementListHashSet m_formElementsWithState;
-
-    OwnPtr<SavedFormState> m_savedFormState;
+    SavedFormStateMap m_savedFormStateMap;
     OwnPtr<FormKeyGenerator> m_formKeyGenerator;
 };