如何检测浏览器的协议处理程序?

93

我已经创建了一个自定义URL协议处理程序。

http://

mailto://

custom://

我已经注册了一个WinForms应用程序来做出相应的反应,一切都很好。

但是我希望在用户尚未安装自定义URL协议处理程序时能够优雅地处理这种情况。

为了能够做到这一点,我需要能够检测浏览器的已注册协议处理程序,我认为可以通过JavaScript来实现。但是我一直无法找到一种轮询信息的方法。我希望能够找到解决这个问题的方法。

感谢您可能能够分享的任何想法。


5
我认为这只有在Chrome浏览器的代码中(例如XPCOM,ActiveX等)才可能实现。否则,这将是一个隐私问题(“我们检测到您使用Eudora。立即切换到FooMail!”)。但请澄清您感兴趣的浏览器/操作系统是什么。 - Matthew Flaschen
1
好的观点,但我很高兴知道有东西被注册来处理我的专有协议acsfs://Windows IE,FireFox,最好是Safari - Chris Craft
你已经解决了这个问题了吗? - jstuardo
13个回答

40

这将是一种非常,非常“hacky”的方法来做到这一点...但是这样行得通吗?

  • 像平常一样放置链接...
  • 但是附加一个onclick处理程序,设置一个定时器并为窗口添加一个onblur处理程序
  • (理论上)如果浏览器处理链接(应用程序X)会从窗口中窃取焦点...
  • 如果onblur事件触发,则清除计时器...
  • 否则在3-5秒钟后让超时器触发...并通知用户“嗯,看起来您没有安装Mega Uber Cool应用程序...您想现在安装它吗?(确定) (取消)”

远非万无一失...但可能有所帮助?


1
这是一个聪明的想法。看起来应该有一种方法,因为它似乎是一个常见的需求。 - Chris Craft
2
在 Mac 上的 Firefox 浏览器(也可能适用于其他浏览器),即使自定义协议的应用程序无法启动,窗口仍会失去焦点并触发 onblur 事件。 - quano
这种方法在Chrome中有效,除非用户选择记住他们的答案,使用Win8且没有该应用程序。在这种情况下,即使应用程序无法启动,模糊事件仍会发生。 - Paul Haggo
2
对于那些正在寻找不再可用的rajeshsegu页面的人,我使用archive.org获取了源代码,并将其导入到此处的jsfiddle中:https://jsfiddle.net/stefansundin/0uca4h2o/ - stefansundin
1
我不知道这是否是唯一的方法,尽管现在已经是2022年了。然而,在Chrome中似乎运行良好。 - lowfront
显示剩余2条评论

20
HTML5定义了自定义方案和内容处理程序(据我所知,Firefox是迄今为止唯一的实现者),但不幸的是目前还没有办法检查处理程序是否已经存在——虽然已经提出,但并没有后续跟进。这似乎是一个关键功能,以有效地使用自定义处理程序,我们作为开发人员应该引起对此问题的关注,以便实现它。

19

没有跨浏览器的好方法来做到这一点。在Win8+上的IE10+中,一个新的msLaunchUri API使您能够启动一个协议,如下所示:

navigator.msLaunchUri('skype:123456', 
  function() 
  { 
    alert('success');
  }, 
  function()
  {
    alert('failed');
  } 
); 
如果该协议未安装,则会触发失败回调函数。否则,协议将启动并触发成功回调函数。我在这里讨论了这个话题: https://web.archive.org/web/20180308105244/https://blogs.msdn.microsoft.com/ieinternals/2011/07/13/understanding-protocols/
这个话题近年来(2021年)备受关注,请参见https://github.com/fingerprintjs/external-protocol-flooding进行讨论。

它不工作 - 在Windows 7,IE浏览器中测试过。 - user285594
7
msLaunchUri 只能在 Windows 8 及以上版本中使用。 - EricLaw
链接已失效。 - Qiulang

13

目前似乎没有一种直接的JavaScript方法来检测已注册协议处理程序的安装情况。

在iTunes模型中,苹果提供了指向其服务器的URL,然后提供运行某些JavaScript代码的页面:

http://ax.itunes.apple.com/detection/itmsCheck.js

因此,iTunes安装程序显然会为主要浏览器部署插件,然后可以检测到这些插件的存在。

如果您的插件已安装,则可以比较确定地认为重定向到您的应用程序特定的URL将成功。


2
这应该是最可靠的解决方案。但是,这意味着您需要安装并创建大多数浏览器的插件,这有点棘手。相比之下,将用户重定向到下载页面可能更加用户友好。 - Natim
为什么不像这里提到的那样使用FireBreath?https://dev59.com/w3E85IYBdhLWcg3wPBB8#14758085 - swdev

11

最简单的解决方法似乎是在用户第一次使用时询问用户。

例如,可以使用JavaScript确认对话框:

You need this software to be able to read this link. Did you install it ?

if yes: create a cookie to not ask next time; return false and the link applies
if false: window.location.href = '/downloadpage/'

2
Cookie可以每天清除干净。你不能用更好的方法制作基于Flash的cookie吗? - user285594
如果Cookie被删除,用户将再次收到提示。 - Natim
我认为VSCode使用这种方法,请查看他们的网站https://marketplace.visualstudio.com/ - Qiulang

6
如果您掌握要运行的程序(代码), 一种检查用户是否成功运行应用程序的方法是:
  1. 在尝试打开自定义协议之前,向服务器脚本发出AJAX请求,将用户意图保存在数据库中(例如,保存用户ID和他想要执行的操作)。

  2. 尝试打开程序,并传递意图数据。

  3. 让程序请求服务器删除数据库条目(使用意图数据查找正确的行)。

  4. 使javascript轮询服务器一段时间,以查看数据库条目是否已删除。 如果该条目已被删除,则您将知道用户成功打开了应用程序,否则该条目将保留(您可以稍后使用cronjob将其删除)。

我还没有尝试过这种方法,只是想到了它。

4
我最终成功实现了一个跨浏览器(Chrome 32、Firefox 27、IE 11和Safari 6)的解决方案,结合了这个和一个超级简单的Safari扩展程序。很多这个解决方案的内容都在这个问题以及这个其他问题中提到过。
以下是脚本:
function launchCustomProtocol(elem, url, callback) {
    var iframe, myWindow, success = false;

    if (Browser.name === "Internet Explorer") {
        myWindow = window.open('', '', 'width=0,height=0');
        myWindow.document.write("<iframe src='" + url + "'></iframe>");

        setTimeout(function () {
            try {
                myWindow.location.href;
                success = true;
            } catch (ex) {
                console.log(ex);
            }

            if (success) {
                myWindow.setTimeout('window.close()', 100);
            } else {
                myWindow.close();
            }

            callback(success);
        }, 100);
    } else if (Browser.name === "Firefox") {
        try {
            iframe = $("<iframe />");
            iframe.css({"display": "none"});
            iframe.appendTo("body");
            iframe[0].contentWindow.location.href = url;

            success = true;
        } catch (ex) {
            success = false;
        }

        iframe.remove();

        callback(success);
    } else if (Browser.name === "Chrome") {
        elem.css({"outline": 0});
        elem.attr("tabindex", "1");
        elem.focus();

        elem.blur(function () {
            success = true;
            callback(true);  // true
        });

        location.href = url;

        setTimeout(function () {
            elem.off('blur');
            elem.removeAttr("tabindex");

            if (!success) {
                callback(false);  // false
            }
        }, 1000);
    } else if (Browser.name === "Safari") {
        if (myappinstalledflag) {
            location.href = url;
            success = true;
        } else {
            success = false;
        }

        callback(success);
    }
}

实现Safari扩展很容易。它只包含一行注入脚本:

myinject.js:

window.postMessage("myappinstalled", window.location.origin);

接下来在网页JavaScript中,你需要首先注册消息事件,并在接收到消息时设置一个标志:

window.addEventListener('message', function (msg) {
    if (msg.data === "myappinstalled") {
        myappinstalledflag = true;
    }
}, false);

这假设与自定义协议相关联的应用程序将管理Safari扩展的安装。
在所有情况下,如果回调返回false,则需要通知用户该应用程序(即其自定义协议)未安装。

它在IE上不起作用。它应该如何工作?myWindow.location.href;和定义在该窗口内的iframe src之间有什么联系?它是否应该抛出异常?无论自定义协议是否受支持,它都不会抛出异常。 - Burjua

2
这是微软支持团队推荐的IE解决方案。

http://msdn.microsoft.com/en-us/library/ms537503%28VS.85%29.aspx#related_topics

"If you have some control over the binaries being installed on a user's machine, checking the UA in script seems like a relevant approach: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\5.0\User Agent\Post Platform " -- By M$ support
每个网页都可以访问 userAgent 字符串,如果您放置了自定义的 post platform 值,则可以使用 navigator.userAgent 在 JavaScript 中轻松检测。
幸运的是,其他主要浏览器(如 Firefox 和 Chrome,除了 Safari :( )在单击带有自定义协议的链接并且用户计算机上未安装该协议时不会抛出“页面未找到”错误。IE 在这里非常严格,任何在不可见框架中单击或捕获 JavaScript 错误的技巧都无法奏效,并最终显示难看的“无法显示网页”错误。我们在这种情况下使用的技巧是通过特定于浏览器的图像通知用户单击自定义协议链接将打开应用程序。如果他们没有看到应用程序打开,他们可以单击“安装”页面。在 XD 方面,这比 IE 的 ActiveX 方法更好。对于 Firefox 和 Chrome,请直接启动自定义协议而不进行任何检测。让用户告诉您他看到了什么。 对于 Safari,:( 目前还没有答案。

User-Agent扩展是一种常见但有问题的方法。http://blogs.msdn.com/b/ieinternals/archive/2009/10/08/extending-the-user-agent-string-problems-and-alternatives.aspx - EricLaw

1

我正在尝试做类似的事情,我刚刚发现了一个在Firefox中有效的技巧。如果你将它与IE的技巧结合起来,你就可以拥有一个在两个主要浏览器上都能运行的技巧(我不确定它是否适用于Safari,我知道它在Chrome中不起作用)

if (navigator.appName=="Microsoft Internet Explorer" && document.getElementById("testprotocollink").protocolLong=="Unknown Protocol") {
    alert("No handler registered");
} else {
    try {
        window.location = "custom://stuff";
    } catch(err) {
        if (err.toString().search("NS_ERROR_UNKNOWN_PROTOCOL") != -1) {
            alert("No handler registered");
        }
    }
}

为了使其正常工作,您还需要在页面上某个地方放置一个隐藏链接,例如:
<a id="testprotocollink" href="custom://testprotocol" style="display: none;">testprotocollink</a>

这个方法有点取巧,但它确实有效。不幸的是,Firefox 版本仍会弹出默认警报,当您尝试访问一个未知协议的链接时,但在警报被解除后,它将运行您的代码。


1
在 catch 块之前,我发现我仍然收到“Firefox不知道如何打开此地址,因为协议(tel)未与任何程序关联”的消息。 - Deebster
6
protocolLong 只返回“已知”协议(file:,mailto:,gopher:,ftp:,http:,https:,news:)的结果,而不是其他应用程序协议的结果。 - EricLaw

1
你可以尝试这样做:
function OpenCustomLink(link) {

    var w = window.open(link, 'xyz', 'status=0,toolbar=0,menubar=0,height=0,width=0,top=-10,left=-10');
    if(w == null) {            
        //Work Fine
    }
    else {
        w.close();
        if (confirm('You Need a Custom Program. Do you want to install?')) {
            window.location = 'SetupCustomProtocol.exe'; //URL for installer
        }
    }
}

1
不,无法在Firefox 27中工作。(未在其他浏览器中测试) - Blaise
3
不工作 - 在Windows 7,IE,Firefox,Opera,Chrome上进行了测试。 - user285594

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