Bug 700144 - Port Bug 640136 [onchange & input events are not fired for all form elements on restore]. r=Neil, a=Ian Neal
authorMisak Khachatryan <misak.bugzilla@gmail.com>
Tue, 21 Feb 2012 19:34:05 +0400
changeset 10190 fcdbfac1e792d552580dd64f006e4077d857aebd
parent 10186 e6773952ed93ad1d75d2d7b1ab9b91f50bc85f1a
child 10191 b3fa8988e83b24d7b59e871918bf8898ff6e50c9
push idunknown
push userunknown
push dateunknown
reviewersNeil, Ian
bugs700144, 640136
Bug 700144 - Port Bug 640136 [onchange & input events are not fired for all form elements on restore]. r=Neil, a=Ian Neal
suite/common/src/nsSessionStore.js
suite/common/tests/browser/Makefile.in
suite/common/tests/browser/browser_476161.js
suite/common/tests/browser/browser_476161_sample.html
suite/common/tests/browser/browser_form_restore_events.js
suite/common/tests/browser/browser_form_restore_events_sample.html
--- a/suite/common/src/nsSessionStore.js
+++ b/suite/common/src/nsSessionStore.js
@@ -2988,41 +2988,63 @@ SessionStoreService.prototype = {
         if (!hasExpectedURL(aDocument, aURL))
           return;
 
         let node = key.charAt(0) == "#" ? aDocument.getElementById(key.slice(1)) :
                                           XPathHelper.resolve(aDocument, key);
         if (!node)
           continue;
 
+        let eventType;
         let value = aData[key];
         if (typeof value == "string" && node.type != "file") {
           if (node.value == value)
             continue; // don't dispatch an input event for no change
 
           node.value = value;
-
-          let event = aDocument.createEvent("UIEvents");
-          event.initUIEvent("input", true, true, aDocument.defaultView, 0);
-          node.dispatchEvent(event);
+          eventType = "input";
         }
-        else if (typeof value == "boolean")
+        else if (typeof value == "boolean") {
+          if (node.checked == value)
+            continue; // don't dispatch a change event for no change
+
           node.checked = value;
-        else if (typeof value == "number")
+          eventType = "change";
+        }
+        else if (typeof value == "number") {
+          // We saved the value blindly since selects take more work to determine
+          // default values. So now we should check to avoid unnecessary events.
+          if (node.selectedIndex == value)
+            continue;
+
           try {
             node.selectedIndex = value;
+            eventType = "change";
           } catch (ex) { /* throws for invalid indices */ }
-        else if (value && value.fileList && value.type == "file" && node.type == "file")
+        }
+        else if (value && value.fileList && value.type == "file" && node.type == "file") {
           node.mozSetFileNameArray(value.fileList, value.fileList.length);
+          eventType = "input";
+        }
         else if (value && typeof value.indexOf == "function" && node.options) {
           Array.forEach(node.options, function(aOpt, aIx) {
             aOpt.selected = value.indexOf(aIx) > -1;
+
+            // Only fire the event here if this wasn't selected by default
+            if (!aOpt.defaultSelected)
+              eventType = "change";
           });
         }
-        // NB: dispatching "change" events might have unintended side-effects
+
+        // Fire events for this node if applicable
+        if (eventType) {
+          let event = aDocument.createEvent("UIEvents");
+          event.initUIEvent(eventType, true, true, aDocument.defaultView, 0);
+          node.dispatchEvent(event);
+        }
       }
     }
 
     let selectedPageStyle = aBrowser.__SS_restore_pageStyle;
     function restoreTextDataAndScrolling(aContent, aData, aPrefix) {
       if (aData.formdata)
         restoreFormData(aContent.document, aData.formdata, aData.url);
       if (aData.innerHTML) {
--- a/suite/common/tests/browser/Makefile.in
+++ b/suite/common/tests/browser/Makefile.in
@@ -41,16 +41,18 @@ srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = suite/common/tests/browser
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_FILES = \
 	head.js \
+	browser_form_restore_events.js \
+	browser_form_restore_events_sample.html \
 	browser_339445.js \
 	browser_339445_sample.html \
 	browser_345898.js \
 	browser_346337.js \
 	browser_346337_sample.html \
 	browser_350525.js \
 	browser_367052.js \
 	browser_393716.js \
@@ -68,18 +70,16 @@ include $(topsrcdir)/config/rules.mk
 	browser_456342_sample.xhtml \
 	browser_461634.js \
 	browser_463206.js \
 	browser_463206_sample.html \
 	browser_465215.js \
 	browser_465223.js \
 	browser_466937.js \
 	browser_466937_sample.html \
-	browser_476161.js \
-	browser_476161_sample.html \
 	browser_477657.js \
 	browser_480893.js \
 	browser_483330.js \
 	browser_485482.js \
 	browser_485482_sample.html \
 	browser_490040.js \
 	browser_491168.js \
 	browser_493467.js \
rename from suite/common/tests/browser/browser_476161.js
rename to suite/common/tests/browser/browser_form_restore_events.js
--- a/suite/common/tests/browser/browser_476161.js
+++ b/suite/common/tests/browser/browser_form_restore_events.js
@@ -30,40 +30,70 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function test() {
-  /** Test for Bug 476161 **/
-  
+  /** Originally a test for Bug 476161, but then expanded to include all input types in bug 640136 **/
+
   waitForExplicitFinish();
-  
+
+  let file = Components.classes["@mozilla.org/file/directory_service;1"]
+                       .getService(Components.interfaces.nsIProperties)
+                       .get("TmpD", Components.interfaces.nsIFile);
+
   let testURL = "http://mochi.test:8888/browser/" +
-    "suite/common/tests/browser/browser_476161_sample.html";
+    "suite/common/tests/browser/browser_form_restore_events_sample.html";
   let tab = getBrowser().addTab(testURL);
   let window = tab.ownerDocument.defaultView;
   tab.linkedBrowser.addEventListener("load", function(aEvent) {
     tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
     let doc = tab.linkedBrowser.contentDocument;
-    
-    doc.getElementById("modify1").value += Math.random();
-    doc.getElementById("modify2").value += " " + Date.now();
-    
+
+    // text fields
+    doc.getElementById("modify01").value += Math.random();
+    doc.getElementById("modify02").value += " " + Date.now();
+
+    // textareas
+    doc.getElementById("modify03").value += Math.random();
+    doc.getElementById("modify04").value += " " + Date.now();
+
+    // file
+    doc.getElementById("modify05").value = file.path;
+
+    // select
+    doc.getElementById("modify06").selectedIndex = 1;
+    var multipleChange = doc.getElementById("modify07");
+    Array.forEach(multipleChange.options, function(option) option.selected = true);
+
+    // checkbox
+    doc.getElementById("modify08").checked = true;
+    doc.getElementById("modify09").checked = false;
+
+    // radio
+    // select one then another in the same group - only last one should get event on restore
+    doc.getElementById("modify10").checked = true;
+    doc.getElementById("modify11").checked = true;
+
     let tab2 = ss.duplicateTab(window,tab);
     tab2.linkedBrowser.addEventListener("load", function(aEvent) {
       tab2.linkedBrowser.removeEventListener("load", arguments.callee, true);
       let doc = tab2.linkedBrowser.contentDocument;
-      let changed = doc.getElementById("changed").textContent.trim().split();
-      
-      is(changed.sort().join(" "), "modify1 modify2",
-         "input events were only dispatched for modified text fields");
-      
+      let inputFired = doc.getElementById("inputFired").textContent.trim().split();
+      let changeFired = doc.getElementById("changeFired").textContent.trim().split();
+
+      is(inputFired.sort().join(" "), "modify01 modify02 modify03 modify04 modify05",
+         "input events were only dispatched for modified input, textarea fields");
+
+      is(changeFired.sort().join(" "), "modify06 modify07 modify08 modify09 modify11",
+         "change events were only dispatched for modified select, checkbox, radio fields");
+
       // clean up
       getBrowser().removeTab(tab2);
       getBrowser().removeTab(tab);
 
       finish();
     }, true);
   }, true);
 }
rename from suite/common/tests/browser/browser_476161_sample.html
rename to suite/common/tests/browser/browser_form_restore_events_sample.html
--- a/suite/common/tests/browser/browser_476161_sample.html
+++ b/suite/common/tests/browser/browser_form_restore_events_sample.html
@@ -1,24 +1,98 @@
 <!DOCTYPE html>
-<title>Test for bug 476161</title>
+<title>Test for form restore events (originally bug 476161)</title>
 
 <script>
 
 document.addEventListener("input", function(aEvent) {
   var inputEl = aEvent.originalTarget;
-  var changedEl = document.getElementById("changed");
-  
+  var changedEl = document.getElementById("inputFired");
+  changedEl.textContent += " " + inputEl.id;
+}, false);
+
+document.addEventListener("change", function(aEvent) {
+  var inputEl = aEvent.originalTarget;
+  var changedEl = document.getElementById("changeFired");
   changedEl.textContent += " " + inputEl.id;
 }, false);
 
 </script>
 
+<!-- input events -->
 <h3>Text fields with changed text</h3>
 <input type="text" id="modify1">
 <input type="text" id="modify2" value="preset value">
+<input type="text" id="modify01">
+<input type="text" id="modify02" value="preset value">
 
 <h3>Text fields with unchanged text</h3>
 <input type="text" id="unchanged1">
 <input type="text" id="unchanged2" value="preset value">
+<input type="text" id="unchanged01">
+<input type="text" id="unchanged02" value="preset value">
+
+<h3>Textarea with changed text</h3>
+<textarea id="modify03"></textarea>
+<textarea id="modify04">preset value</textarea>
+
+<h3>Textarea with unchanged text</h3>
+<textarea id="unchanged03"></textarea>
+<textarea id="unchanged04">preset value</textarea>
+
+<h3>file field with changed value</h3>
+<input type="file" id="modify05">
+
+<h3>file field with unchanged value</h3>
+<input type="file" id="unchanged05">
+
+<!-- change events -->
+
+<h3>Select menu with changed selection</h3>
+<select id="modify06">
+  <option value="one">one</option>
+  <option value="two">two</option>
+  <option value="three">three</option>
+</select>
+
+<h3>Select menu with unchanged selection (change event still fires)</h3>
+<select id="unchanged06">
+  <option value="one">one</option>
+  <option value="two" selected>two</option>
+  <option value="three">three</option>
+</select>
+
+<h3>Multiple Select menu with changed selection</h3>
+<select id="modify07" multiple>
+  <option value="one">one</option>
+  <option value="two" selected>two</option>
+  <option value="three">three</option>
+</select>
+
+<h3>Select menu with unchanged selection</h3>
+<select id="unchanged07" multiple>
+  <option value="one">one</option>
+  <option value="two" selected>two</option>
+  <option value="three" selected>three</option>
+</select>
+
+<h3>checkbox with changed value</h3>
+<input type="checkbox" id="modify08">
+<input type="checkbox" id="modify09" checked>
+
+<h3>checkbox with unchanged value</h3>
+<input type="checkbox" id="unchanged08">
+<input type="checkbox" id="unchanged09" checked>
+
+<h3>radio with changed value</h3>
+<input type="radio" id="modify10"  name="group">Radio 1</input>
+<input type="radio" id="modify11"  name="group">Radio 2</input>
+<input type="radio" id="modify12" name="group" checked>Radio 3</input>
+
+<h3>radio with unchanged value</h3>
+<input type="radio" id="unchanged10"  name="group2">Radio 4</input>
+<input type="radio" id="unchanged11"  name="group2">Radio 5</input>
+<input type="radio" id="unchanged12" name="group2" checked>Radio 6</input>
 
 <h3>Changed field IDs</h3>
 <div id="changed"></div>
+<div id="inputFired"></div>
+<div id="changeFired"></div>