Title: [121128] trunk
Revision
121128
Author
[email protected]
Date
2012-06-24 18:47:35 -0700 (Sun, 24 Jun 2012)

Log Message

Selected option is not restored correctly
https://bugs.webkit.org/show_bug.cgi?id=89623

Reviewed by Hajime Morita.

Source/WebCore:

Details of the bug:
We saved a state of a <select> element as a string of which length was
the size of <select>'s children. e.g. If a <select> had five children
and the second and the fifth items were selected, the state string was:
        ".X..X"

This didn't work well if the structure of the children was updated after
parsing. For example,
1. A page has the following <select> initially:
  <select multiple>
    <option>Banana
    <option>Lemon
    <option>Orange
    <option>Strawberry
  <select>

2. For some reasons, <option>Apple</option> is prepended to the children.
3. Some items are selected.
4. The page is unloaded. Selection state is saved.
5. A user go back to the page again. A browser parses the page again.
6. Try to restore the <select> state with the saved data at 4.
  But "Apple" is missing. The <select> has wrong selections.

Solution:
We save the state as a set of selected values. If "Banana" and
"Strawberry" are selected in the above <select>, we save two strings;
"Banana" and "Strawberry", not ".X..X".

Test: fast/forms/select/select-state-restore.html

* html/HTMLSelectElement.cpp:
(WebCore::HTMLSelectElement::saveFormControlState):
Store selected value strings to a FormControlState object.
(WebCore::HTMLSelectElement::searchOptionsForValue):
A helper function to find an <option> with the specified value.
(WebCore::HTMLSelectElement::restoreFormControlState):
Clear all of selections, then select options with saved values.
In order to avoid O(M x N) loop, we start searching at position we found
the previous value.
* html/HTMLSelectElement.h: Declare searchOptionsForValue.
* html/FormController.cpp:
(formStateSignature): Bump up the version because this is a incompatible
change.

LayoutTests:

* fast/forms/select/select-state-restore-expected.txt: Added.
* fast/forms/select/select-state-restore.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (121127 => 121128)


--- trunk/LayoutTests/ChangeLog	2012-06-25 01:46:14 UTC (rev 121127)
+++ trunk/LayoutTests/ChangeLog	2012-06-25 01:47:35 UTC (rev 121128)
@@ -1,3 +1,13 @@
+2012-06-21  Kent Tamura  <[email protected]>
+
+        Selected option is not restored correctly
+        https://bugs.webkit.org/show_bug.cgi?id=89623
+
+        Reviewed by Hajime Morita.
+
+        * fast/forms/select/select-state-restore-expected.txt: Added.
+        * fast/forms/select/select-state-restore.html: Added.
+
 2012-06-24  David Barr  <[email protected]>
 
         Add snap to css3-images image-resolution

Added: trunk/LayoutTests/fast/forms/select/select-state-restore-expected.txt (0 => 121128)


--- trunk/LayoutTests/fast/forms/select/select-state-restore-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/forms/select/select-state-restore-expected.txt	2012-06-25 01:47:35 UTC (rev 121128)
@@ -0,0 +1,14 @@
+Test if selected options are correctly restored even if their positions were changed.
+
+PASS $("opt-la").selected is false
+PASS $("opt-tk").selected is false
+PASS $("opt-os").selected is true
+PASS $("opt-ak").selected is false
+PASS $("opt-wy").selected is false
+We don't care which one is selected because their values are identical:
+PASS !!($("opt-ca-1").selected ^ $("opt-ca-2").selected) is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+
Property changes on: trunk/LayoutTests/fast/forms/select/select-state-restore-expected.txt
___________________________________________________________________

Added: svn:eol-style

Added: trunk/LayoutTests/fast/forms/select/select-state-restore.html (0 => 121128)


--- trunk/LayoutTests/fast/forms/select/select-state-restore.html	                        (rev 0)
+++ trunk/LayoutTests/fast/forms/select/select-state-restore.html	2012-06-25 01:47:35 UTC (rev 121128)
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<p>Test if selected options are correctly restored even if their positions were changed.</p>
+<div id="console"></div>
+
+<input id="emptyOnFirstVisit">
+<div id="parent">
+<form action="" id=form1>
+<select id="select1" multiple>
+  <option id="opt-ca-1" value="CA">California 1</option>
+  <option id="opt-ca-2" value="CA" selected>California 2</option>
+  <option id="opt-la">LA</option>
+  <optgroup>
+    <option id="opt-tk" selected>Tokyo</option>
+    <option id="opt-os">Osaka</option>
+  </optgroup>
+  <option id="opt-ak" value="AK">Alaska</option>
+  <option id="opt-wy" selected>WY</option>
+</select>
+</form>
+</div>
+
+<script>
+
+jsTestIsAsync = true;
+
+function runTest()
+{
+    var state = document.getElementById('emptyOnFirstVisit');
+    if (!state.value) {
+        // First visit.
+        setTimeout(function() {
+            state.value = 'visited';
+            $('opt-tk').selected = false;
+            $('opt-os').selected = true;
+            $('select1').insertBefore($('opt-ak'), $('opt-ca-1'));
+            $('select1').removeChild($('opt-wy'));
+            var newOption = document.createElement('option');
+            newOption.textContent = 'NY';
+            $('select1').appendChild(newOption);
+            newOption.selected = true;
+            $('form1').submit();
+        }, 0);
+    } else {
+        // Went back to this page again, and form state should be restored.
+        shouldBeFalse('$("opt-la").selected');
+        shouldBeFalse('$("opt-tk").selected');
+        shouldBeTrue('$("opt-os").selected');
+        shouldBeFalse('$("opt-ak").selected');
+        shouldBeFalse('$("opt-wy").selected');
+        debug('We don\'t care which one is selected because their values are identical:');
+        shouldBeTrue('!!($("opt-ca-1").selected ^ $("opt-ca-2").selected)');
+    
+        $('parent').innerHTML = '';
+        setTimeout(function() {
+            finishJSTest();
+        }, 0);
+    }
+}
+
+runTest();
+</script>
+<script src=""
+</body>
Property changes on: trunk/LayoutTests/fast/forms/select/select-state-restore.html
___________________________________________________________________

Added: svn:eol-style

Modified: trunk/Source/WebCore/ChangeLog (121127 => 121128)


--- trunk/Source/WebCore/ChangeLog	2012-06-25 01:46:14 UTC (rev 121127)
+++ trunk/Source/WebCore/ChangeLog	2012-06-25 01:47:35 UTC (rev 121128)
@@ -1,3 +1,54 @@
+2012-06-22  Kent Tamura  <[email protected]>
+
+        Selected option is not restored correctly
+        https://bugs.webkit.org/show_bug.cgi?id=89623
+
+        Reviewed by Hajime Morita.
+
+        Details of the bug:
+        We saved a state of a <select> element as a string of which length was
+        the size of <select>'s children. e.g. If a <select> had five children
+        and the second and the fifth items were selected, the state string was:
+                ".X..X"
+
+        This didn't work well if the structure of the children was updated after
+        parsing. For example,
+        1. A page has the following <select> initially:
+          <select multiple>
+            <option>Banana
+            <option>Lemon
+            <option>Orange
+            <option>Strawberry
+          <select>
+
+        2. For some reasons, <option>Apple</option> is prepended to the children.
+        3. Some items are selected.
+        4. The page is unloaded. Selection state is saved.
+        5. A user go back to the page again. A browser parses the page again.
+        6. Try to restore the <select> state with the saved data at 4.
+          But "Apple" is missing. The <select> has wrong selections.
+
+        Solution:
+        We save the state as a set of selected values. If "Banana" and
+        "Strawberry" are selected in the above <select>, we save two strings;
+        "Banana" and "Strawberry", not ".X..X".
+
+        Test: fast/forms/select/select-state-restore.html
+
+        * html/HTMLSelectElement.cpp:
+        (WebCore::HTMLSelectElement::saveFormControlState):
+        Store selected value strings to a FormControlState object.
+        (WebCore::HTMLSelectElement::searchOptionsForValue):
+        A helper function to find an <option> with the specified value.
+        (WebCore::HTMLSelectElement::restoreFormControlState):
+        Clear all of selections, then select options with saved values.
+        In order to avoid O(M x N) loop, we start searching at position we found
+        the previous value.
+        * html/HTMLSelectElement.h: Declare searchOptionsForValue.
+        * html/FormController.cpp:
+        (formStateSignature): Bump up the version because this is a incompatible
+        change.
+
 2012-06-24  David Barr  <[email protected]>
 
         Add snap to css3-images image-resolution

Modified: trunk/Source/WebCore/html/FormController.cpp (121127 => 121128)


--- trunk/Source/WebCore/html/FormController.cpp	2012-06-25 01:46:14 UTC (rev 121127)
+++ trunk/Source/WebCore/html/FormController.cpp	2012-06-25 01:47:35 UTC (rev 121128)
@@ -81,7 +81,7 @@
     // 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 2 \n\r=&"));
+    DEFINE_STATIC_LOCAL(String, signature, ("\n\r?% WebKit serialized form state version 3 \n\r=&"));
     return signature;
 }
 

Modified: trunk/Source/WebCore/html/HTMLSelectElement.cpp (121127 => 121128)


--- trunk/Source/WebCore/html/HTMLSelectElement.cpp	2012-06-25 01:46:14 UTC (rev 121127)
+++ trunk/Source/WebCore/html/HTMLSelectElement.cpp	2012-06-25 01:47:35 UTC (rev 121128)
@@ -919,30 +919,66 @@
 {
     const Vector<HTMLElement*>& items = listItems();
     size_t length = items.size();
-    StringBuilder builder;
-    builder.reserveCapacity(length);
+    FormControlState state;
     for (unsigned i = 0; i < length; ++i) {
-        HTMLElement* element = items[i];
-        bool selected = element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected();
-        builder.append(selected ? 'X' : '.');
+        if (!items[i]->hasTagName(optionTag))
+            continue;
+        HTMLOptionElement* option = toHTMLOptionElement(items[i]);
+        if (!option->selected())
+            continue;
+        state.append(option->value());
+        if (!multiple())
+            break;
     }
-    return FormControlState(builder.toString());
+    return state;
 }
 
+size_t HTMLSelectElement::searchOptionsForValue(const String& value, size_t listIndexStart, size_t listIndexEnd) const
+{
+    const Vector<HTMLElement*>& items = listItems();
+    size_t loopEndIndex = std::min(items.size(), listIndexEnd);
+    for (size_t i = listIndexStart; i < loopEndIndex; ++i) {
+        if (!items[i]->hasLocalName(optionTag))
+            continue;
+        if (static_cast<HTMLOptionElement*>(items[i])->value() == value)
+            return i;
+    }
+    return notFound;
+}
+
 void HTMLSelectElement::restoreFormControlState(const FormControlState& state)
 {
     recalcListItems();
 
     const Vector<HTMLElement*>& items = listItems();
-    size_t length = items.size();
+    size_t itemsSize = items.size();
+    if (!itemsSize)
+        return;
 
-    String mask = state[0];
-    for (size_t i = 0; i < length; ++i) {
-        HTMLElement* element = items[i];
-        if (element->hasTagName(optionTag))
-            toHTMLOptionElement(element)->setSelectedState(mask[i] == 'X');
+    for (size_t i = 0; i < itemsSize; ++i) {
+        if (!items[i]->hasLocalName(optionTag))
+            continue;
+        static_cast<HTMLOptionElement*>(items[i])->setSelectedState(false);
     }
 
+    if (!multiple()) {
+        size_t foundIndex = searchOptionsForValue(state[0], 0, itemsSize);
+        if (foundIndex != notFound)
+            toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
+    } else {
+        size_t startIndex = 0;
+        for (size_t i = 0; i < state.valueSize(); ++i) {
+            const String& value = state[i];
+            size_t foundIndex = searchOptionsForValue(value, startIndex, itemsSize);
+            if (foundIndex == notFound)
+                foundIndex = searchOptionsForValue(value, 0, startIndex);
+            if (foundIndex == notFound)
+                continue;
+            toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
+            startIndex = foundIndex + 1;
+        }
+    }
+
     setOptionsChangedOnRenderer();
     setNeedsValidityCheck();
 }

Modified: trunk/Source/WebCore/html/HTMLSelectElement.h (121127 => 121128)


--- trunk/Source/WebCore/html/HTMLSelectElement.h	2012-06-25 01:46:14 UTC (rev 121127)
+++ trunk/Source/WebCore/html/HTMLSelectElement.h	2012-06-25 01:47:35 UTC (rev 121128)
@@ -164,6 +164,7 @@
     bool platformHandleKeydownEvent(KeyboardEvent*);
     void listBoxDefaultEventHandler(Event*);
     void setOptionsChangedOnRenderer();
+    size_t searchOptionsForValue(const String&, size_t listIndexStart, size_t listIndexEnd) const;
 
     enum SkipDirection {
         SkipBackwards = -1,
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to