Bug 592802 - Send change event when <input type='file'> is changed even with display:none. r=sicking a2.0=blocking
authorMounir Lamouri <mounir.lamouri@gmail.com>
Tue, 14 Sep 2010 20:35:37 +0200
changeset 53805 5f2250cb58c9eb1aa0d3f5515931f640152f2c1d
parent 53804 7a99955956fde6fcc75c4d333b555d8a85461ded
child 53806 045bfb95fe6a38c133d563ce48274deabfb28cd8
push id15704
push usermlamouri@mozilla.com
push dateTue, 14 Sep 2010 18:33:07 +0000
treeherdermozilla-central@5f2250cb58c9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssicking
bugs592802
milestone2.0b7pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 592802 - Send change event when <input type='file'> is changed even with display:none. r=sicking a2.0=blocking
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/test/Makefile.in
content/html/content/test/test_bug592802.html
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -302,20 +302,18 @@ AsyncClickHandler::Run()
   nsXPIDLString title;
   nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
                                      "FileUpload", title);
 
   nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1");
   if (!filePicker)
     return NS_ERROR_FAILURE;
 
-  nsFileControlFrame* frame = static_cast<nsFileControlFrame*>(mInput->GetPrimaryFrame());
-  nsTextControlFrame* textFrame = nsnull;
-  if (frame)
-    textFrame = static_cast<nsTextControlFrame*>(frame->GetTextFrame());
+  nsFileControlFrame* frame =
+    static_cast<nsFileControlFrame*>(mInput->GetPrimaryFrame());
 
   PRBool multi;
   rv = mInput->GetMultiple(&multi);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = filePicker->Init(win, title, multi ?
                         (PRInt16)nsIFilePicker::modeOpenMultiple :
                         (PRInt16)nsIFilePicker::modeOpen);
@@ -379,37 +377,36 @@ AsyncClickHandler::Run()
       // Default to "desktop" directory for each platform
       nsCOMPtr<nsIFile> homeDir;
       NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(homeDir));
       localFile = do_QueryInterface(homeDir);
     }
     filePicker->SetDisplayDirectory(localFile);
   }
 
-  // Tell our textframe to remember the currently focused value
-  if (textFrame)
-    textFrame->InitFocusedValue();
-
   // Open dialog
   PRInt16 mode;
   rv = filePicker->Show(&mode);
   NS_ENSURE_SUCCESS(rv, rv);
-  if (mode == nsIFilePicker::returnCancel)
+  if (mode == nsIFilePicker::returnCancel) {
     return NS_OK;
-  
+  }
+
   // Collect new selected filenames
   nsCOMArray<nsIDOMFile> newFiles;
   if (multi) {
     nsCOMPtr<nsISimpleEnumerator> iter;
     rv = filePicker->GetFiles(getter_AddRefs(iter));
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsISupports> tmp;
     PRBool prefSaved = PR_FALSE;
-    while (NS_SUCCEEDED(iter->GetNext(getter_AddRefs(tmp)))) {
+    PRBool loop = PR_TRUE;
+    while (NS_SUCCEEDED(iter->HasMoreElements(&loop)) && loop) {
+      iter->GetNext(getter_AddRefs(tmp));
       nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(tmp);
       if (localFile) {
         nsString unicodePath;
         rv = localFile->GetPath(unicodePath);
         if (!unicodePath.IsEmpty()) {
           nsCOMPtr<nsIDOMFile> domFile =
             do_QueryObject(new nsDOMFile(localFile, doc));
           newFiles.AppendObject(domFile);
@@ -439,31 +436,24 @@ AsyncClickHandler::Run()
       rv = nsHTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(doc->GetDocumentURI(),
                                                                       localFile);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
   // Set new selected files
   if (newFiles.Count()) {
-    // Tell mTextFrame that this update of the value is a user initiated
-    // change. Otherwise it'll think that the value is being set by a script
-    // and not fire onchange when it should.
-    PRBool oldState;
-    if (textFrame) {
-      oldState = textFrame->GetFireChangeEventState();
-      textFrame->SetFireChangeEventState(PR_TRUE);
-    }
-
+    // The text control frame (if there is one) isn't going to send a change
+    // event because it will think this is done by a script.
+    // So, we can safely send one by ourself.
     mInput->SetFiles(newFiles);
-    if (textFrame) {
-      textFrame->SetFireChangeEventState(oldState);
-      // May need to fire an onchange here
-      textFrame->CheckFireOnChange();
-    }
+    nsContentUtils::DispatchTrustedEvent(mInput->GetOwnerDoc(),
+                                         static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
+                                         NS_LITERAL_STRING("change"), PR_FALSE,
+                                         PR_FALSE);
   }
 
   return NS_OK;
 }
 
 #define CPS_PREF_NAME NS_LITERAL_STRING("browser.upload.lastDir")
 
 NS_IMPL_ISUPPORTS2(UploadLastDir, nsIObserver, nsISupportsWeakReference)
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -218,12 +218,13 @@ include $(topsrcdir)/config/rules.mk
 		583288_submit_server.sjs \
 		583288_redirect_server.sjs \
 		test_bug555840.html \
 		test_bug561636.html \
 		test_bug556013.html \
 		test_bug590363.html \
 		test_bug557628-1.html \
 		test_bug557628-2.html \
+		test_bug592802.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_bug592802.html
@@ -0,0 +1,221 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=592802
+-->
+<head>
+  <title>Test for Bug 592802</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=592802">Mozilla Bug 592802</a>
+<p id="display"></p>
+<div id="content">
+  <input id='a' type='file'>
+  <input id='a2' type='file'>
+</div>
+<button id='b' onclick="document.getElementById('a').click();">Show Filepicker</button>
+<button id='b2' onclick="document.getElementById('a2').click();">Show Filepicker</button>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 592802 **/
+
+SimpleTest.waitForExplicitFinish();
+
+netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+  .getService(Ci.mozIJSSubScriptLoader)
+  .loadSubScript("chrome://mochikit/content/browser/toolkit/content/tests/browser/common/mockObjects.js", this);
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function simpleEnumerator(items)
+{
+  this._items = items;
+  this._nextIndex = 0;
+}
+
+simpleEnumerator.prototype = {
+  QueryInterface: function(aIID)
+  {
+    if (Ci.nsISimpleEnumerator.equals(aIID) ||
+        Ci.nsISupports.equals(aIID)) {
+      return this;
+    }
+
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+
+  hasMoreElements: function()
+  {
+    return this._nextIndex < this._items.length;
+  },
+
+  getNext: function()
+  {
+    if (!this.hasMoreElements()) {
+      throw Cr.NS_ERROR_FAILURE;
+    }
+
+    return this._items[this._nextIndex++];
+  }
+};
+
+function MockFilePicker()
+{
+}
+
+MockFilePicker.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFilePicker]),
+
+  // Constants
+  returnOK: 0, // the user hits OK (select a file)
+  returnCancel: 1, // the user cancel the file selection
+  returnReplace: 2, // the user replace the selection
+
+  // Properties
+  defaultExtension: "",
+  defaultString: "",
+  get displayDirectory() { return null; },
+  set displayDirectory(val) { },
+  get fileURL() { return null; },
+  filterIndex: 0,
+
+  get file() {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+    var fileName = "592808_file";
+    var fileData = "file content";
+
+    var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
+                           .getService(Components.interfaces.nsIProperties);
+    var testFile = dirSvc.get("ProfD", Components.interfaces.nsIFile);
+    testFile.append(fileName);
+    var outStream = Components.
+                    classes["@mozilla.org/network/file-output-stream;1"].
+                    createInstance(Components.interfaces.nsIFileOutputStream);
+    outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+                   0666, 0);
+    outStream.write(fileData, fileData.length);
+    outStream.close();
+
+    return testFile;
+  },
+
+  get files() {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+    var fileName = "592808_file";
+    var fileData = "file content";
+
+    var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
+                           .getService(Components.interfaces.nsIProperties);
+    var testFile = dirSvc.get("ProfD", Components.interfaces.nsIFile);
+    testFile.append(fileName);
+    var outStream = Components.
+                    classes["@mozilla.org/network/file-output-stream;1"].
+                    createInstance(Components.interfaces.nsIFileOutputStream);
+    outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+                   0666, 0);
+    outStream.write(fileData, fileData.length);
+    outStream.close();
+
+    return new simpleEnumerator([testFile]);
+  },
+
+  appendFilter: function(val) {},
+
+  appendFilters: function(val) {},
+
+  init: function() {},
+
+  show: function()
+  {
+    if (firstFilePickerShow) {
+      firstFilePickerShow = false;
+      return this.returnCancel;
+    } else {
+      return this.returnOK;
+    }
+  }
+};
+
+var mockFilePickerRegisterer =
+  new MockObjectRegisterer("@mozilla.org/filepicker;1",MockFilePicker);
+
+mockFilePickerRegisterer.register();
+
+var testData = [
+/* visibility | display | multiple */
+  [ "",       "",     false ],
+  [ "hidden", "",     false ],
+  [ "",       "none", false ],
+  [ "",       "",     true ],
+  [ "hidden", "",     true ],
+  [ "",       "none", true ],
+];
+
+var testCounter = 0;
+var testNb = testData.length;
+
+var firstFilePickerShow = true;
+
+function finished()
+{
+  mockFilePickerRegisterer.unregister();
+
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(function() {
+  // mockFilePicker will simulate a cancel for the first time the file picker will be shown.
+  var b2 = document.getElementById('b2');
+  b2.focus(); // Be sure the element is visible.
+  document.getElementById('b2').addEventListener("change", function(aEvent) {
+    aEvent.target.removeEventListener("change", arguments.callee, false);
+    ok(false, "When cancel is received, change should not fire");
+  }, false);
+  synthesizeMouse(b2, 2, 2, {});
+
+  // Now, we can launch tests when file picker isn't canceled.
+  var b = document.getElementById('b');
+  b.focus(); // Be sure the element is visible.
+
+  document.getElementById('a').addEventListener("change", function(aEvent) {
+    ok(true, "change event correctly sent");
+    testCounter++;
+
+    if (testCounter >= testNb) {
+      aEvent.target.removeEventListener("change", arguments.callee, false);
+      SimpleTest.executeSoon(finished);
+    } else {
+      var data = testData[testCounter];
+      var a = document.getElementById('a');
+      a.style.visibility = data[0];
+      a.style.display = data[1];
+      a.multiple = data[2];
+
+      SimpleTest.executeSoon(function() {
+        synthesizeMouse(b, 2, 2, {});
+      });
+    }
+  }, false);
+
+  synthesizeMouse(b, 2, 2, {});
+});
+
+</script>
+</pre>
+</body>
+</html>