Bug 640136 - onchange & input events are not fired for all form elements on restore [r=dietrich]
☠☠ backed out by dfbe9a0fbf97 ☠ ☠
authorPaul O’Shannessy <paul@oshannessy.com>
Tue, 25 Oct 2011 10:19:29 -0700
changeset 79186 78c921e2b56bf559a9efa5cf3ce4c6579da2dedf
parent 79185 402ffedeff9923a1248e2ac2659ad5609f7db26f
child 79187 5d7c2550a61e9e4c031dde19624fff8235e73db0
push id251
push userposhannessy@mozilla.com
push dateTue, 25 Oct 2011 21:54:29 +0000
treeherderfx-team@5d7c2550a61e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdietrich
bugs640136
milestone10.0a1
Bug 640136 - onchange & input events are not fired for all form elements on restore [r=dietrich]
browser/components/sessionstore/src/nsSessionStore.js
browser/components/sessionstore/test/browser/Makefile.in
browser/components/sessionstore/test/browser/browser_476161.js
browser/components/sessionstore/test/browser/browser_476161_sample.html
browser/components/sessionstore/test/browser/browser_form_restore_events.js
browser/components/sessionstore/test/browser/browser_form_restore_events_sample.html
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -3270,41 +3270,58 @@ 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") {
           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/browser/components/sessionstore/test/browser/Makefile.in
+++ b/browser/components/sessionstore/test/browser/Makefile.in
@@ -45,16 +45,18 @@ relativesrcdir  = browser/components/ses
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 # browser_506482.js is disabled because of frequent failures (bug 538672)
 # browser_526613.js is disabled because of frequent failures (bug 534489)
 
 _BROWSER_TEST_FILES = \
 	head.js \
+	browser_form_restore_events.js \
+	browser_form_restore_events_sample.html \
 	browser_248970_a.js \
 	browser_248970_b.js \
 	browser_248970_b_sample.html \
 	browser_339445.js \
 	browser_339445_sample.html \
 	browser_345898.js \
 	browser_346337.js \
 	browser_346337_sample.html \
@@ -93,18 +95,16 @@ include $(topsrcdir)/config/rules.mk
 	browser_464620_a.html \
 	browser_464620_b.js \
 	browser_464620_b.html \
 	browser_464620_xd.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_480148.js \
 	browser_480893.js \
 	browser_483330.js \
 	browser_485482.js \
 	browser_485482_sample.html \
 	browser_485563.js \
 	browser_490040.js \
rename from browser/components/sessionstore/test/browser/browser_476161.js
rename to browser/components/sessionstore/test/browser/browser_form_restore_events.js
--- a/browser/components/sessionstore/test/browser/browser_476161.js
+++ b/browser/components/sessionstore/test/browser/browser_form_restore_events.js
@@ -30,39 +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/" +
-    "browser/components/sessionstore/test/browser/browser_476161_sample.html";
+    "browser/components/sessionstore/test/browser/browser_form_restore_events_sample.html";
   let tab = gBrowser.addTab(testURL);
   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 = gBrowser.duplicateTab(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 unchanged06 modify07 modify08 modify09 modify11",
+         "change events were only dispatched for modified select, checkbox, radio fields");
+
       // clean up
       gBrowser.removeTab(tab2);
       gBrowser.removeTab(tab);
-      
+
       finish();
     }, true);
   }, true);
 }
rename from browser/components/sessionstore/test/browser/browser_476161_sample.html
rename to browser/components/sessionstore/test/browser/browser_form_restore_events_sample.html
--- a/browser/components/sessionstore/test/browser/browser_476161_sample.html
+++ b/browser/components/sessionstore/test/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>