Оказалось, что каких-либо стандартных механизмов для этого не существует. Но поскольку пользователи никак не хотели обращать внимания на кнопку Download и красную надпись о необходимости предварительной установки приложения, пришлось искать варианты. Об этом и пойдет речь ниже.
Сразу хочу предупредить, что не оказалось ни одного способа, сработавшего хотя бы в двух браузерах стабильно одинаково. Поэтому любителей красивых кроссбраузерных решений ждет разочарование — статья полна хаков (просьба не минусовать за это, самому тошно, но задача есть задача).
Для начала создадим наши ссылки.
<a class="runlink" href="myapp://command_line_parameters">Run</a> <a class="downloadlink" style="display:none;" href="http://mysite.com/download/app.exe">Download</a>
Теперь создадим общую функцию, которая будет определять браузер и запускать соответствующий обработчик. Ну и еще одну вспомогательную, которая будет выводить запрос на загрузку приложения и загружать его в случае положительного ответа.
function initApplicationLink(runlink, downloadlink) { // Проверяем браузер var func = null; if (navigator.userAgent.indexOf('Firefox')>=0 ) func = checkFirefox; else if (navigator.userAgent.indexOf('Opera')>=0 ) func = checkOpera; else if (navigator.userAgent.indexOf('Chrome')>=0 ) func = checkChrome; else if (navigator.userAgent.indexOf('Safari')>=0 ) func = checkSafari; if ( func!=null ) { // Скрываем ссылку Download $(downloadlink).hide(); // Вешаем выбранную функцию на клик по ссылке запуска приложения $(runlink).on('click', function(){ func(runlink, downloadlink); // Отменяем нажатие ссылки, чтобы она не открылась в окне браузера return false; }); } else { // Для всех других браузеров просто показываем ссылку Download $(downloadlink).show(); } } function downloadConfirmation(downloadlink) { if ( confirm('You need to install our application first. Do you really want to download it now?') ) document.location = $(downloadlink).attr('href'); }
Начнем с Firefox и Opera, поскольку они в этом вопросе предоставляют 100% надежный и красивый механизм исключений. Но, к сожалению, реализация все равно отличается.
У Firefox самое простое решение.
function checkFirefox(runlink, downloadlink) { // Создаем скрытый фрейм, в котором пытаемся открыть наш URL var f = createFrame(); try { f.contentWindow.location = $(runlink).attr('href'); } catch (e) { // Если URL открыть не удалось, выводим запрос на загрузку приложения downloadConfirmation(downloadlink); } // Удаляем наш временный фрейм deleteFrame(f); }
Opera не намного отличается от Firefox, за исключением того, что она отлавливает исключение не в момент попытки загрузки во фрейм URL с незарегистрированным протоколом, а в момент попытки обращения к не определенному атрибуту фрейма (contentWindow.location).
function checkOpera(runlink, downloadlink) { var f = createFrame(); f.contentWindow.location = $(runlink).attr('href'); setTimeout(function (){ try { // Пытаемся поработать с не определенным атрибутом фрейма // (вместо something можно использовать что-угодно) if ( f.contentWindow.location!='something' ) {} } catch (e) { downloadConfirmation(downloadlink); } deleteFrame(f); }, 0); }
Функции createFrame() и deleteFrame() для Firefox и Opera:
function createFrame() { var f = document.createElement('iframe'); f.style.display = 'none'; return document.body.appendChild(f); } function deleteFrame(f) { document.body.removeChild(f); }
Safari под Windows тоже умеет ловить исключения, но со скрытым фреймом этот номер не прошел. Как вариант, можно использовать обычное окно. Решение не элегантное, но рабочее.
Safari под MacOS с исключениями, видимо, не дружит. Поэтому тут придется применить чисто костыльный метод. Фишка заключается в том, чтобы после запуска приложения проверить фокус нашего окна браузера. Если приложение успешно запустилось, то окно потеряло фокус, и в обработчике этого события мы зафиксировали этот факт. Если же приложение не открылось, то окно по-прежнему имеет фокус.
К сожалению, этот способ не на 100% стабилен. Например, если за время таймаута браузер не успел открыть приложение, или наоборот — пользователь успел закрыть приложение до наступления таймаута, и окно опять получило фокус. В результате, такие варианты выливаются в следующую неприятную картину. Всплывает окно с предложением загрузки, а через секунду стартует приложение. Или наоборот — стартует приложение, пользователь его тут же закрывает, и появляется окно с предложением загрузки. Поэтому увеличение или уменьшение таймаута почти одинаково плохо (увеличение все-таки немного лучше, т.к. в большинстве случаев пользователь не станет мгновенно закрывать только что открытое приложение). На практике наиболее приемлемым получился таймаут в одну секунду.
function checkSafari(runlink, downloadlink) { if ( navigator.userAgent.indexOf('Windows')>=0 ) { // Открываем маленькое окошко с нашим не стандартным URL var w = window.open($(runlink).attr('href'), '', 'width=50, height=50'); setTimeout(function(){ try { // Пытаемся поработать с не определенным атрибутом окна if ( w.location!='about:blank' ) {} w.close(); window.focus(); } catch (e) { w.close(); window.focus(); downloadConfirmation(downloadlink); } }, 1000); } else { // Под MacOS и теоретически под iOS document.location = $(runlink).attr('href'); setTimeout(function(){ // Если окно по-прежнему в фокусе, значит приложение не запустилось if ( window.isFocused ) downloadConfirmation(downloadlink); }, 1000); } } // Обработчики событий получения и потери фокуса окна браузера window.isFocused = true; $(window).on('focus', function(){ window.isFocused = true; }) .on('blur', function(){ window.isFocused = false; });
Chrome оказался полностью совместимым с Safari под MacOS, за исключением таймаута (Safari потребовалась почти секунда, чтобы запустить приложение, в то время, как Chrome запустил его меньше, чем за 250 миллисекунд).
(На практике способ с таймаутом очень сильно зависит от загруженности компьютера в момент запуска приложения. Я попадал на ситуации с Chrome и Safari, когда приложение не успевало запуститься, и выводилось окно с предложением о загрузке, после чего запускалось приложение. Опять-таки проблема с таймаутом, описанная выше).
function checkChrome(runlink, downloadlink) { document.location = $(runlink).attr('href'); setTimeout(function(){ if ( window.isFocused ) downloadConfirmation(downloadlink); }, 1000); }
Ну и наконец Internet Explorer не показал стабильного результата при всех стараниях. Для IE технически подходит вариант Safari под Windows (с открытием маленького окна). Но получить хотя бы 50% стабильность так и не удалось. Поэтому IE ушел в ветку «другие браузеры», которая не делает никаких проверок, а просто отображает обе ссылки — на запуск и на загрузку приложения. (Буду признателен, если кто-то подскажет способ для IE).
Теперь нам осталось только проинициализировать наши ссылки.
initApplicationLink('.runlink', '.downloadlink');
Примечание: Решение тестировалось в последних версиях Chrome, Firefox, Opera, Safari и IE на платформах Windows и MacOS. В мобильных браузерах тесты не проводились, но вариант с таймаутом вполне может оказаться работоспособным на Android и iOS.
ссылка на оригинал статьи http://habrahabr.ru/post/182484/
Добавить комментарий