J'utilise une application Web basée sur Symfony 2.8 qui envoie des données de formulaire à un contrôleur utilisant Ajax.
Jusqu'à présent, tout fonctionnait bien, mais depuis la dernière mise à jour de macOS vers la version 10.13.4, les utilisateurs commencent à signaler que l'envoi du formulaire ne fonctionne plus dans Safari. Les autres versions de macOS et les autres navigateurs sous 10.13.4 fonctionnent toujours correctement. Il semble donc que ce soit un problème dans Safari. Bien sûr, j'ai déposé un rapport de bogue à Apple, mais je ne pense pas que je pourrai recevoir un retour de sa part ...
J'ai pu isoler la source du problème: La soumission de données contenant un fichier vide a échoué:
// 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";
Résultat
submit.php
faitPASest appelé. Alors, cela semble être un bogue dans la dernière mise à jour de Safari? Ou y a-t-il un problème avec mon code?
Une idée de comment empêcher ce bug?
En attendant, j'ai trouvé cette solution rapide et sale. Mais en réalité, je cherche une solution réelle. Des idées?
// 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) {}
La solution d'Andrei Herford plantera d'autres navigateurs qui ne prennent pas en charge la méthode entry () de FormData. L'utilisation de try/catch ne détectera que les erreurs d'exécution, pas les erreurs de syntaxe.
Notre solution consistait à utiliser du code JavaScript simple pour supprimer l'élément d'entrée de fichier vide avant créer l'objet FormData, ainsi:
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]);
}
}
}
J'utilise FormData sur l'ensemble de mon site et peux vérifier qu'il s'agit d'un problème avec la dernière version de Safari. Supprimer le fichier vide corrige le problème. Voici le code qui a fonctionné pour moi:
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
}
J'ai utilisé cette solution et travaille pour moi.
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.
J'ai utilisé la suggestion d'Andrei, qui a fonctionné pour le safari, mais a cassé IE.
La seule solution que je pouvais trouver qui fonctionnerait dans les deux navigateurs était d'utiliser eval ().
Comme cela semble être un bogue affectant uniquement Safari 11, j'ai également ajouté une vérification de la version du navigateur.
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) {}
}