Safari 10.13.4中使用包含空文件输入的FormData()发送AJAX请求失败

14

我正在运行一个基于Symfony 2.8的Web应用程序,该应用程序使用Ajax将一些表单数据发送回控制器。

到目前为止,一切都运行良好,但自从最新的macOS更新至10.13.4版本后,用户开始报告,在Safari中提交表单不再起作用。其他macOS版本和10.13.4上的其他浏览器仍然正常工作,因此似乎是Safari的问题。当然,我已经向苹果公司提交了错误报告,但我不认为我会从那里得到反馈...

我能够确定问题的源头:包含空文件输入的数据提交失败:

// 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";

结果

  • 在所有测试的浏览器和平台上,提交表单都正常工作,但在macOS 10.13.4中的Safari上不行。
  • 在macOS 10.13.4的Safari中:
    • 如果没有选择任何文件: Ajax请求运行约20秒钟(内置超时?),然后返回一个空的成功响应。 submit.php 没有被调用。
    • 如果已选择文件:一切都正常...

因此,这似乎是最新Safari更新中的错误?还是我的代码有问题?

有什么想法可以防止这个错误吗?


1
报告错误并在解决之前通过删除空文件来清理FormData。 - Kulvar
刚提交答案后才注意到评论。过滤掉空文件可以解决问题,但这只是一种hack方法。有没有更干净的解决方案? - Andrei Herford
这是一个bug,任何解决方案都只能是一种hack。 - Kulvar
当然,也可能有一种解决方案根本不使用FormData,从而以更清晰的方式解决问题。但是,过滤掉空文件值确实完成了工作,希望苹果能够修复这个问题... - Andrei Herford
这并不会使代码更简洁,但您可以检索每个文件的二进制代码,将输入类型"type=file"转换为输入类型"type=text",并插入base64编码的二进制代码。 - Kulvar
5个回答

6

Andrei Herford 的解决方案会导致不支持 FormData 的浏览器崩溃 - 使用 try/catch 只能找到执行错误,而不能找到语法错误。

我们的解决方案是在创建 FormData 对象之前使用纯 JavaScript 删除空的文件输入元素,即:

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]);
    }
  }
}

6

我在整个网站中都使用了FormData,可以证实这是最新版Safari的一个问题。移除空文件可以解决这个问题。以下是适用于我的代码:

  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
  }

1
一个警告,删除方法在IE上不起作用,因此在实现时可能需要先检查Safari浏览器。 - Leopold Mc

3
我使用了这个解决方案,对我很有效。
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.

1

同时,我找到了这个快速而粗糙的解决方案。但实际上我正在寻找一个真正的解决办法。有什么想法吗?

// 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) {}

0

我采用了Andrei的建议,它在Safari中运行正常,但却破坏了IE。

我找到的唯一可同时在两个浏览器上工作的解决方案是使用eval()。

由于这似乎只影响Safari 11的bug,我还添加了一个浏览器版本检查。

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) {}
}

正则表达式不会匹配iOS 11.3 Safari,该浏览器也有相同的问题。我个人只是放弃了它,并无条件地运行了eval函数。 - Ilya Semenov
我已经更新了正则表达式,使其能够同时工作。尽可能地,我更喜欢在我的提交中包含文件属性,因此我仅在绝对必要时才使用正则表达式来删除它。 - Andrew Primeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接