Не удалось выполнить запрос AJAX при отправке FormData(), включая ввод пустого файла в Safari 10.13.4

Я запускаю веб-приложение на основе Symfony 2.8, которое отправляет некоторые данные формы обратно контроллеру с помощью Ajax.

До сих пор все работало нормально, но с момента появления последнего обновления macOS до версии 10.13.4 пользователи, отправляющие форму, больше не работают в Safari. Другие версии MacOS и другие браузеры на 10.13.4 все еще работают нормально, так что это похоже на проблему в Safari. Конечно, я подал отчет об ошибке Apple, но я не думаю, что у меня когда-нибудь появятся отзывы...

Я смог изолировать источник проблемы: Отправка данных, которая включает в себя пустые входные файлы:

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

результат

  • Отправка формы отлично работает на всех проверенных браузерах и платформах, но в Safari в macOS 10.13.4
  • В Safari на macOS 10.13.4:
    • Если не выбран файл: запрос Ajax запускается около 20 секунд (выстраивается в тайм-аут?) И возвращается с пустым ответчиком sucess. submit.php НЕ вызывается.
    • Если был выбран файл: все работает отлично...

Итак, это, кажется, ошибка в последнем обновлении Safari? Или что-то не так с моим кодом?

Любая идея, как предотвратить эту ошибку?

Ответ 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) {}

Ответ 2

Решение Andrei Herford приведет к сбою других браузеров, которые не поддерживают метод entries() FormData. Использование try/catch будет только найти ошибки выполнения, а не синтаксические ошибки.

Наше решение состояло в том, чтобы использовать простой JavaScript для удаления пустого элемента ввода файла перед созданием объекта FormData, таким образом:

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

Ответ 3

Я использую 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
  }

Ответ 4

Я использовал предложение Андрея, которое работало на сафари, но сломало IE.

Единственное решение, которое я мог бы найти в обеих браузерах, - использовать eval().

Поскольку это кажется ошибкой, затрагивающей только сафари 11, я также добавил проверку версии браузера.

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

Ответ 5

Я использовал это решение и работает для меня.

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.