如何检查自定义协议是否受支持

66
我们正在使用一种能够注册自己协议的软件。我们可以通过类似以下链接在浏览器中运行应用程序:

We are using software that registers its own protocol. We can run application from browser then by link like:

customprotocol://do_this.

但是有没有一种方法可以检查用户系统是否支持这样的自定义协议?如果不支持,我们希望要求用户首先安装软件。

例如:

if (canHandle ('customprotocol')) {
     // run software
}
else {
    // ask to install
}

编辑 我知道protocolLong属性,但它只在IE中有效。


4
你或许想阅读一下这个网页:https://dev59.com/S3RA5IYBdhLWcg3www3D。 - Naeem Sarfraz
1
谢谢,我已经尝试了那里描述的大部分方法。似乎在所有流行的浏览器中,没有一种好的方式可以实现这个功能而不出现警报或其他问题。 - Piotr Pankowski
8个回答

52
很遗憾,没有简单的方法来实现这个。当然没有预先确定协议处理程序是否已安装的方法。
正如您所提到的,Internet Explorer具有protocolLong属性,但我无法使其返回除所有自定义协议处理程序之外的任何其他内容 - 如果有人知道如何让IE返回正确的值,请告诉我,以便我可以更新此部分。 我在IE中找到的最佳解决方案是附加到用户代理字符串或安装浏览器扩展程序和你的应用程序一起公开JavaScript可访问属性。 Firefox是主要浏览器中最容易的,因为它允许您尝试并捕获失败的导航尝试。返回的错误对象包含一个名为NS_ERROR_UNKNOWN_PROTOCOLname属性:
try {
    iframe.contentWindow.location.href = "randomprotocolstring://test/";
} catch(e) {
    if (e.name == "NS_ERROR_UNKNOWN_PROTOCOL")
        window.location = "/download/";
}

Firefox会弹出自己的警告框:

Firefox不知道如何打开此地址,因为协议(randomprotocolstring)未与任何程序关联。

一旦关闭此框,catch块将执行,您就有了一个可用的备选方案。

其次是Opera,它允许您利用可预测性法则来检测自定义协议链接的成功。如果自定义协议单击有效,则页面将保持相同位置。如果没有安装处理程序,则Opera将导航到错误页面。这使得使用iframe非常容易检测:

   iframe.contentWindow.location = "randomprotocolstring://test/";
   window.setTimeout(function () {
       try {
           alert(ifr.contentWindow.location); 
       } catch (e) { window.location = "/download/"; }
   }, 0);

setTimeout 在这里是为了确保我们在导航后检查位置。需要注意的是,如果尝试访问页面,则 Opera 会抛出 ReferenceException(跨域安全错误)。不过这无关紧要,因为我们只需要知道位置已从 about:blank 更改,所以使用 try ... catch 就可以。

Chrome 在此方面官方上说很差。如果自定义协议处理程序失败,则完全没有反应。如果处理程序有效...你猜对了...仍然没有反应。恐怕没有办法区分两者。

我没有测试过 Safari,但我担心它与 Chrome 相同。

在调查此问题时,欢迎尝试我编写的测试代码(我自己也有兴趣)。它在 Opera 和 Firefox 上都兼容,但目前在 IE 和 Chrome 上没有任何作用。


3
自从这篇回答于2010年写成以来,我应该提醒谷歌用户,在Firefox的更新版本中,try/catch方法似乎已经不再起作用。(iframe仍然可以避免错误页面) - Katana314
1
如果打开了处理程序,Chrome和Safari会失去焦点(可以使用window.onblur)。 - Brian Cannard
@d512 现在链接失效了,太糟糕了。 - mix3d
@mix3d 谢谢你告诉我,我已经删除了链接。 - d512
@BrianCannard 这个在Chrome中不再起作用,至少是目前这样。 - Jack
@Jack 那太糟糕了。 - Brian Cannard

14
这里有一个不同寻常的答案:在注册自定义协议时安装一个特殊字体,然后使用JavaScript检查该字体是否存在,可以使用类似这样的方法。虽然它是一种hack方式,但与其他答案不同,它可以跨浏览器和操作系统工作。

非常好的回答!+1 - cascading-style
我不理解。 - user7892745
我同意这确实是一种黑客行为,但也是一个聪明的解决方法。+1 - smn.tino
虽然我可能永远不会用到这个,但这种超越常规的思维方式让我不得不给你点赞。 - Papooch

12

我来分享一下我们的经验,我们使用FireBreath创建了一个简单的跨平台插件。安装后,该插件将注册一种可以在页面刷新后通过浏览器JavaScript检测到的MIME类型。检测MIME类型表示协议处理程序已安装。

if(IE) { //This bastard always needs special treatment
    try {
        var flash = new ActiveXObject("Plugin.Name");
    } catch (e) {
        //not installed
    }
else { //firefox,chrome,opera
    navigator.plugins.refresh(true);
    var mimeTypes = navigator.mimeTypes;
    var mime = navigator.mimeTypes['application/x-plugin-name'];
    if(mime) {
        //installed
    } else {
        //not installed
    }
}

1
+1-虽然不是“隐蔽”的检测方法(即会要求用户允许运行ActiveXObjects),但这仍然是一个聪明的方法。特别是对于Chrome,它没有其他解决方法,浏览器插件可能是一些人唯一的选择。 - Andy E
FireBreath是解决这个问题的绝佳方法,如果您只需要这么简单的功能,那么使用它非常容易。在尝试找到同样的解决方案之后,我已经用过它了。 - Iain Collins
1
提醒大家,Mozilla和Google认为插件是一种传统技术,Chrome开始逐步淘汰NPAPI,https://developer.chrome.com/extensions/npapi - Burjua
嗨,Gearoid,你能描述一下你如何使用FireBreath创建插件吗? - yo2011

9

Windows 8上的Internet Explorer 10引入了非常有用的navigator.msLaunchUri方法,用于启动自定义协议URL并检测成功或失败。例如:

        if (typeof (navigator.msLaunchUri) == typeof (Function)) {
            navigator.msLaunchUri(witchUrl,
                function () { /* Success */ },
                function () { /* Failure */ showError(); });

            return;
        }

Windows 7 / IE 9及以下版本支持条件注释,正如@mark-kahn所建议的那样。


1
请注意,msLaunchUri 方法仅在 Windows 8 上的 IE >= 10 中存在。 - aaronk6
1
@aaronk6。我不确定这是否正确,因为我是在Windows 7上使用这种方法的。 - antmeehan
2
我在Windows 7上检查了IE 10和IE 11。在两种情况下,typeof navigator.msLaunchUri返回"undefined"。此外,微软已确认该API未添加到Windows 7中:文档化的API函数“navigator.msLaunchUri”不存在于Windows 7中(请参见2014年5月5日下午12:27的评论)。 - aaronk6
感谢@aaronk6提供的详细信息。我可能从来没有注意到它实际上没有使用这个调用(而是回退到替代方法)。我会更新我的答案。 - antmeehan
msLaunchUri仅存在于+win8上。 - MJB

4

1
对于Chrome浏览器,也许可以在安装期间注册一个MIME类型,并使用window.navigator.mimeTypes[i]进行检查。我找不到一个简单的方法来实现它。 - Mark Kahn
2
+1,这是一种解决IE 9及以下版本的好方法,Mark。不幸的是,IE 10不再支持条件注释,但也许他们修复了 protocolLong 问题。 - Andy E

3
在移动端,您可以使用嵌入式iframe来自动在自定义协议和已知协议(Web或应用商店)之间进行切换,详见:https://gist.github.com/2662899

1
我只是想更详细地解释一下之前马克的回答(例如用户7892745没有理解)。
1)当您启动网页或网络应用程序时,它会检查是否存在异常字体(例如中文孔夫子字体http://www.fontspace.com/apostrophic-lab/konfuciuz)。
以下是带有检查字体功能(称为isFontAvailable)的示例网页代码:
<!DOCTYPE html>
<html>
<head>

</head>
<body>

<script>
/**
* Checks if a font is available to be used on a web page.
*
* @param {String} fontName The name of the font to check
* @return {Boolean}
* @license MIT
* @copyright Sam Clarke 2013
* @author Sam Clarke <sam@samclarke.com>
*/
(function (document) {
   var width;
   var body = document.body;

                    var container = document.createElement('span');
                    container.innerHTML = Array(100).join('wi');
                    container.style.cssText = [
       'position:absolute',
       'width:auto',
       'font-size:128px',
       'left:-99999px'
   ].join(' !important;');

   var getWidth = function (fontFamily) {
       container.style.fontFamily = fontFamily;

       body.appendChild(container);
       width = container.clientWidth;
       body.removeChild(container);

       return width;
   };

   // Pre compute the widths of monospace, serif & sans-serif
   // to improve performance.
   var monoWidth  = getWidth('monospace');
   var serifWidth = getWidth('serif');
   var sansWidth  = getWidth('sans-serif');

   window.isFontAvailable = function (font) {
       return monoWidth !== getWidth(font + ',monospace') ||
           sansWidth !== getWidth(font + ',sans-serif') ||
           serifWidth !== getWidth(font + ',serif');
   };
})(document);



function isProtocolAvailable()
{
    if (isFontAvailable('Konfuciuz')) 
    {
        return true;
    } 
    else 
    {
        return false;
    }
}

function checkProtocolAvail()
{
    if (isProtocolAvailable()) 
    {
        alert('Custom protocol is available!');
    } 
    else 
    {
        alert('Please run executable to install protocol!');
    }
}
</script>

<h3>Check if custom protocol was installed or not</h3>

<pre>


<input type="button" value="Check if custom protocol was installed!" onclick="checkProtocolAvail()">

</body>
</html>

2) 当用户第一次打开此页面时,字体将未被安装,因此他将收到一条消息,提示“请运行可执行文件以安装自定义协议...”。
3) 他将运行可执行文件,该文件将安装字体。您的exe可以将字体文件(在我的情况下是KONFUC__.ttf)复制到C:\ Windows目录中,或者使用类似于以下示例的代码(Delphi上的示例):
// Adding the font ..

AddFontResource(PChar('XXXFont.TTF'));
SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);

4) 之后,当用户再次运行 Web 应用程序时,由于此次安装了字体,他会收到“自定义协议可用!”的消息。
在 Google Chrome、Internet Explorer 和 Firefox 上测试 - 运行良好!

2
就此而言,这种方法很可能会在未来出现问题,因为字体检测的隐私影响非常棘手(指纹识别向量)。 - EricLaw

0
对于Firefox,我在Google上搜索的大多数文章,包括Andy E在这里的答案,以及这个gist Cross-browser implementation of navigator.msLaunchUri 或者 https://github.com/ismailhabib/custom-protocol-detection 都使用了。
iframe.contentWindow.location.href = uri

但自Firefox 64以来,它已经停止工作,例如这里https://github.com/ismailhabib/custom-protocol-detection/issues/37也得到了确认。

因此,在FF 64+中,我发现我可以使用Chrome的方法blurHandler或使用那里发布的内容https://github.com/ismailhabib/custom-protocol-detection/issues/37#issuecomment-617962659

try {
        iframe.contentWindow.location.href = uri;
        setTimeout(function () {
             try {
                    if (iframe.contentWindow.location.protocol === "about:") {
                         successCb();
                     } else {
                         failCb();
                     }
             } catch (e) {
                if (e.name === "NS_ERROR_UNKNOWN_PROTOCOL" || 
                 e.name === "NS_ERROR_FAILURE" || e.name === "SecurityError") {
                     failCb();
               }
             }
        }, 500);
} catch (e) {
   if (e.name === "NS_ERROR_UNKNOWN_PROTOCOL" || e.name === "NS_ERROR_FAILURE" 
    || e.name === "SecurityError") {
       failCb();
   }
}

对于Chrome 86+,它也无法正常工作,请查看我的答案以获取详细信息在Chrome 86中检测自定义协议处理程序

顺便说一句,在某些情况下,我发现大多数答案/文章已经过时了。


这里的failCb是什么?它在哪里声明的? - Ambuj Khanna
那是您提供的回调函数,请检查原始代码 https://github.com/ismailhabib/custom-protocol-detection/blob/master/protocolcheck.js - Qiulang
请自行检查并尝试该GitHub项目,这比不断询问我要有效得多。我最后回答一次,failBc可以由您提供,例如()=> alert("协议未被识别"); - Qiulang

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