Pull to refresh

Асинхронная загрузка изображений в скрытом iframe: подводные камни

Reading time3 min
Views7.7K
Недавно пришлось делать загрузчик фотографий на сервер. Поскольку опыт использования сторонних скриптов уже был, а время на их доработку часто было больше времени на разработку скрипта с нуля, то было принято решение сделать загрузчик самостоятельно.

При обращению к гуглу выдаёт много статей по загрузке файлов через iframe. Алгоритм в общем сводится к:
1) Создаём скрытый фрейм (обычно просто обнуляется ширина и высота через HTML и CSS свойства)
2) Устанавливаем action формы в имя фрейма.
3) Отправляем файл. Радуемся.

Для удобства использования отдельная кнопка «Начать загрузку» не создавалась, а был повешен обработчик onChange для файлового input'a.


Далее тестирование: Chrome 9,10,11; Firefox 3.6 (четвёртый тогда ещё не вышел); Opera 9,10,11; IE 6 и IE 8, в том числе в режиме совместимости с IE 7. Chrome, Opera и FF тестировались и в Linux и в Windows. Полёт нормальный, тестирование командой пройдено. Отправляем на живую версию сайта.

Вот с этого момента и началось самое веселье. На сайт заходит несколько тысяч посетителей в день. И 6 из них написали в техподдержку, что новый метод загрузки фотографий не работает. После добавления в скрипт предупреждения об ошибках и логирования их на сервер выяснилось, что скрипт на самом деле не работает у нескольких сотен человек.

Первый найденный глюк был в IE 7. И касался он как раз обработчика события onChange. Как оказалось, это известный баг — при изменении DOM-дерева в IE7 вызывается onChange. В нём JS-код изменяет структуру HTML-документа. Это опять вызывает onChange. Т.е. получается race condition и IE7 просто игнорирует изменения и посылает пустое имя файла. Решение: обернуть вызов в функции в setTimeout. Этот метод корректно работает во всех тестируемых браузерах.

Осталось зарубить себе на носу: IE 7 и IE 8 в режиме совместимости — это совсем разные браузеры…

Следующий очень интересный глюк был в Opera 11, причём всего у 3 пользователей. Возможно, виновата не сама опера, а какой-нибудь вспомогательный софт. Дело в том, что JSON-ответ от сервера приходится передавать с mime-типом text/html, а не application/json. При попытке открыть в iframe данные с типом application/json некоторые браузеры предлагали пользователю скачать бинарный файл, в то время как он должен втихую обрабатываться в JavaScript.

Опера видит, что передаётся родной HTML, почему б не пихнуть туда баннер? И пихала. Естественно, парсер валился при попытке переварить такую строку. Решение: во-первых, желательно использовать text/plain, во-вторых было добавлено обрезание строки по первому вхождению открывающей и закрывающей фигурной скобки.

Третий глюк был самым страшным. Чаще всего проявлялся в Firefox, IE и Chrome. Данные отправлялись не в скрытый iframe, а в новую вкладку. Файл, конечно, передавался на сервер. Однако в другую вкладку JS обратиться не мог, поэтому данные не отображались и использовать их было нельзя.

Хуже всего, что повторить этот баг так и не удалось — даже IE на машинах команды отрабатывал всё хорошо. Была сделана тестовая страница, на которой были использованы разные способы отображения/скрытия фрейма. Как и ожидалось, видимые фреймы всё корректно отработали, скрытые создали вкладки. Решилось просто — нужно обрамить отображаемый frame в невидимый div (display: none).

Расскажу также немного о глюках, которые удалось выловить ещё на этапе написания скрипта.

Отдельно пришлось заниматься извращениями с кроппером (рамочка для выбора конкретной области изображения). Этот скрипт как раз и был сторонним. При статическом изображении всё работало на ура. А вот как только изображение создавалось динамически вылазили разные глюки — от неправильных размеров до полного затенения. Пересоздание объекта через delete/new не помогало. Методом проб и ошибок выяснилось, что инициализировать скрипт нужно обязательно после того, как изображение полностью загружено. А для перечитывания параметров есть метод reset. Только так, и никак иначе!

Одной из самых неприятных проблем было то, что не всегда корректно срабатывает отправление на сервер пересекающийся форм (например, форма в форме). А файловый input — обязательно элемент формы. Первое приходящее на ум решение — копировать данные из input'a и создавать аналогичный в другом месте DOM-дерева. Но это поддерживают не все браузеры. Переносить input по DOM-дереву также не получилось. Зато если обрамить его в DIV — то его можно перемещать спокойно, вместе с элементом формы.

Кстати, о динамическом создании элементов. В конечной версии скрипта формы создаются динамически и помещаются в не отображаемый слой вначале страницы. Это позволило избежать пересечений форм и дало возможность удобнее создавать большое количество форм-загрузчиков. А вот iframe динамически создать так и не получилось, зато я наконец-то увидел своими глазами глюк с открытием новой вкладки.

В заключение скажу, что о решении разрабатывать скрипт своими силами мы не пожалели. Лично я получил много опыта и весёлых моментов при ловле жуков, иногда весёлых до истерики и кровавых слёз. Зато в результате получилось именно то, что нужно.
Tags:
Hubs:
+27
Comments32

Articles