AJAX request fails when sending FormData() includi

2019-01-22 15:07发布

问题:

I am running a Symfony 2.8 based web app which sends some form data back to a controller using Ajax.

So far everything worked fine, but since the latest macOS update to version 10.13.4 users start to report, that submitting the form does not work anymore in Safari. Other macOS Versions and other browsers on 10.13.4 still work fine, so it seems to be a problem in Safari. Of course I filed a bug report to Apple, but I do not think, that I will ever get feedback from there...

I was able to isolate the source of the problem: Submitting data which includes an empty file input fails:

// safri_bug.html
<html>
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    </head>
<body>
    <form name="app_booking" method="post" action="/test/submit.php">
        <div><input type="text" id="someValue" name="value"></div>
        <div><input id="thefile" type="file" name="file"></div>
    </form>

    <button id="bSubmit" type="button">Submit</button>

    <script>    
        $(document).ready(function() {              
            $('#bSubmit').click(function() {
                var form = $('form');
                var data = new FormData(form[0]);

                $.ajax({
                    url : '/submit.php',
                    type : 'POST',
                    data : data,
                    contentType: false,
                    processData: false,
                    context : this,
                    success : function(response) {
                            alert('success: ' + response);
                    },
                    error: function (xhr, ajaxOptions, thrownError) {
                            alert('error: ' + xhr.responseText + ' - ' + thrownError);
                    }
                });
            });
        });
    </script>
</body>
</html>


// submit.php
<?php 
    echo "OK";

Result

  • Submitting the form works fine on all tested browsers and platforms but in Safari in macOS 10.13.4
  • In Safari on macOS 10.13.4:
    • If not file is selected: The Ajax request runs for about 20 seconds (build in timeout?) and than returns with an empty sucess response. The submit.php does NOT get called.
    • If a file was selected: Everything works fine...

So, this seems to be a bug in the latest Safari update? Or is there anything wrong with my code?

Any idea how to prevent this bug?

回答1:

Andrei Herford's solution will crash other browsers that do not support the entries() method of FormData - using try/catch will only find execution errors, not syntax errors.

Our solution was to use plain JavaScript to remove the empty file input element before creating the FormData object, thus:

for (i = 0; i < form.elements.length; i++) {
  if (form.elements[i].type == 'file') {
    if (form.elements[i].value == '') {
      form.elements[i].parentNode.removeChild(form.elements[i]);
    }
  }
}


回答2:

I use FormData throughout my site and can verify that this is a problem with the latest version of Safari. Removing the empty file fixes the problem. Here's the code that worked for me:

  var form = $('#formID');
  var data = new FormData(form[0])

  //hack to fix safari bug where upload fails if file input is empty
  if (document.getElementById("fileID").files.length == 0 ) { //if the file is empty
      data.delete('fileID'); //remove it from the upload data
  }


回答3:

I used this solution and works for me.

var $form = $('#website_settings_form');
var $inputs = $('input[type="file"]:not([disabled])', $form); //select input files
    $inputs.each(function(_, input) {
        if (input.files.length > 0) return 
        $(input).prop('disabled', true) //if the input doesn't have uploaded files will be disable
    })
    var formData = new FormData($form[0]);// create the form data
    $inputs.prop('disabled', false);//enable fields again.


回答4:

Meanwhile I found this quick and dirty solution. But actually I am looking for a real workaround. Any ideas?

// Filter out empty file just before the Ajax request
// Use try/catch since Safari < 10.13.4 does not support FormData.entries()
try {
   for (var pair of data.entries()) {
      if (pair[1] instanceof File && pair[1].name == '' && pair[1].size == 0)
         data.delete(pair[0]);  
   }
} catch(e) {}


回答5:

I used Andrei's suggestion, which worked for safari, but broke IE.

The only solution I could find that would work in both browsers was to use eval().

Since this appears to be a bug only affecting safari 11 I also added a check on browser version.

if(dataObj instanceof FormData && navigator.userAgent.match(/version\/11((\.[0-9]*)*)? .*safari/i)) {
    try {
        eval('for (var pair of dataObj.entries()) {\
            if (pair[1] instanceof File && pair[1].name === \'\' && pair[1].size === 0) {\
                dataObj.delete(pair[0]);\
            }\
        }');
    } catch(e) {}
}