为firefox添加新的protocol

Adding a New Protocol to Mozilla
本文讲述如何为mozilla添加一个新的协议。实现该协议将要使用javascript和XPCOM,也包括了将其添加到现有的mozilla程序中。

概述

Mozilla支持一般的网络协议如HTTP和FTP。有时对现存的mozilla程序进行修改以使其支持一种新的协议非常有用,如此mozilla就可以更好的与其他应用程序集成。也许有的人想要mozilla在用户点击一个链接的时候运行一个IM客户端,或者一个应用程序需要打开mozilla并在第一个页面加载之前执行一个动作。
要添加一个新的协议,需要实现一个XPCOM组件。由于XPCOM使得编程语言之间可以相互交互,因此,现在mozilla的XPCOM组件可以用C++或者javascript来实现。本文使用的是javascript,因为它不需要编译。
本文的例子是构建一个”search:”协议,它会使用用户的默认搜索引擎来实现一次搜索

XPCOM的shell

图1(如下)展示了实现一个新的协议的XPCOM的javascript shell的一个基本结构。
图1:XPCOM shell的基本结构

    // XPCOM constant definitions
     // Protocol definition   
    // ProtocolFactory definition
    // TestModule definition        
    function NSGetModule(){
      return TestModule;
    }

当XPCOM发现我们的组件的时候会调用NSGetModule(),其返回值是TestModule对象。TestModule实现了XPCOM调用的几个方法,例如注册新组件和获取ProtocolFactory对象,ProtocolFactory运行mozilla在需要的时候创建一个Protocol对象。Protocol对象最终实现这个协议以及几个XPCOM调用的方法。它包含了在mozilla需要解析该协议的时候运行的代码,该方法叫做newChannel。
XPCOM shell的代码快速的过一下,因为在创建新的协议的时候它们都不需要修改。
XPCOM常量包括我们需要使用的一般的XPCOM组件,以及一些用于新组件的常量。kPROTOCOL_CID是该协议的一个唯一id号,每个新创建的协议都应该有一个使用uuid尝试的唯一id。kSCHEME定义了该协议的调用方式——本例中是”x-search”,因此要从一个网站调用该协议需要使用"x-search:search term"。
图2:使用的常量
    const kSCHEME = "x-search";
    const kPROTOCOL_NAME = "Search Protocol";
    const kPROTOCOL_CONTRACTID = "@mozilla.org/network/protocol;1?name=" + kSCHEME;
    const kPROTOCOL_CID = Components.ID("789409b9-2e3b-4682-a5d1-71ca80a76456");

    // Mozilla defined
    const kSIMPLEURI_CONTRACTID = "@mozilla.org/network/simple-uri;1";
    const kIOSERVICE_CONTRACTID = "@mozilla.org/network/io-service;1";
    const nsISupports = Components.interfaces.nsISupports;
    const nsIIOService = Components.interfaces.nsIIOService;
    const nsIProtocolHandler = Components.interfaces.nsIProtocolHandler;
    const nsIURI = Components.interfaces.nsIURI; 

下面是TestModule的代码。registerSelf使用该组件定义的几个常量注册该组件,
图3:TestModule的代码
    /**
     * JS XPCOM component registration goop:
     *
     * We set ourselves up to observe the xpcom-startup category.  This provides
     * us with a starting point.
     */

     var TestModule = new Object();
 
     TestModule.registerSelf = function (compMgr, fileSpec, location, type)
     {
       compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
       compMgr.registerFactoryLocation(kPROTOCOL_CID,
                                       kPROTOCOL_NAME,
                                       kPROTOCOL_CONTRACTID,
                                       fileSpec, 
                                       location, 
                                       type);
     }

     TestModule.getClassObject = function (compMgr, cid, iid)
     {
       if (!cid.equals(kPROTOCOL_CID))
         throw Components.results.NS_ERROR_NO_INTERFACE;

       if (!iid.equals(Components.interfaces.nsIFactory))
         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
    
       return ProtocolFactory;
     }

     TestModule.canUnload = function (compMgr)
     {
       return true;
     }

ProtocolFactory所做的就是在实例化的时候都使用createInstance来做一些与XPCOM相关的错误检验,如果成功则返回一个Protocol对象。
图4:ProtocolFactory代码
    var ProtocolFactory = new Object();

    ProtocolFactory.createInstance = function (outer, iid)
    {
      if (outer != null)
        throw Components.results.NS_ERROR_NO_AGGREGATION;

      if (!iid.equals(nsIProtocolHandler) && !iid.equals(nsISupports))
        throw Components.results.NS_ERROR_NO_INTERFACE;
    
      return new Protocol();
    }

最后,Protocol对象实现该协议的功能。newChannel()是在使用该协议的时候代码运行在地点的方法。协议就是通道,为了让协议正常工作,其代码需要实现一个通道。
图5:Protocol的代码
    function Protocol()
    {
    }

    Protocol.prototype =
    {
      QueryInterface: function(iid)
      {
        if (!iid.equals(nsIProtocolHandler) &&
            !iid.equals(nsISupports))
          throw Components.results.NS_ERROR_NO_INTERFACE;
        return this;
      },

      scheme: kSCHEME,
      defaultPort: -1,
      protocolFlags: nsIProtocolHandler.URI_NORELATIVE |
                 nsIProtocolHandler.URI_NOAUTH,
  
      allowPort: function(port, scheme)
      {
        return false;
      },

      newURI: function(spec, charset, baseURI)
      {
        var uri = Components.classes[kSIMPLEURI_CONTRACTID].createInstance(nsIURI);
        uri.spec = spec;
        return uri;
      },

      newChannel: function(input_uri)
      {
        // here goes the code that should be run when the protocol gets used.
      }    
    },
  }

协议的实现

最后一步是实现使用该协议的时候所调用的代码。newChannel调用的时候需要带有一个参数,是XPCOM的nsIUri类型。要获取一个字符串版本可以使用其spec成员。该字符串包含了调用该协议所使用的完整URI,这里是"x-search:[search terms]"。首先要做的是解析掉"x-search:"部分。dump() 是用来向控制台打印信息的,在调试的时候很有用。
图6:参数处理

    newChannel: function(aURI)
    {
      // aURI is a nsIUri, so get a string from it using .spec
      var mySearchTerm = aURI.spec;

      // strip away the kSCHEME: part
      mySearchTerm = mySearchTerm.substring(mySearchTerm.indexOf(":") + 1, mySearchTerm.length);    
      mySearchTerm = encodeURI(mySearchTerm);

      dump("[mySearchTerm=" + mySearchTerm + "]\n");
      
      ...
    },

下一步就是获取用户的搜索引擎。下面的代码来自mozilla的navigator.js文件。首先,默认的可靠搜索引擎的首选项是需要的。然后使用一个指向网络搜索服务的数据RDF数据源来查询用户选择的搜索引擎。
图7:检索搜索引擎
    newChannel: function(input_uri)
    {
      ...
      
      var finalURL = "";
    
      try{ 
        // Get the preferences service
        var prefService = Components.classes["@mozilla.org/preferences-service;1"]
                                    .getService(Components.interfaces.nsIPrefService);

        var prefBranch = prefService.getBranch(null);
  
        defaultSearchURL = prefBranch.getComplexValue("browser.search.defaulturl",
                              Components.interfaces.nsIPrefLocalizedString).data;

        var searchDS = Components.classes["@mozilla.org/rdf/datasource;1?name=internetsearch"]
                                 .getService(Components.interfaces.nsIInternetSearchService);

        var searchEngineURI = prefBranch.getCharPref("browser.search.defaultengine");
        if (searchEngineURI) {          
          searchURL = searchDS.GetInternetSearchURL(searchEngineURI, mySearchTerm, 0, 0, {value:0});
          if (searchURL)
            defaultSearchURL = searchURL;      
        }
        dump("[Search Protocol Success: " + defaultSearchURL + "]")
      } catch (e){
        dump("[Search Protocol failed to get the search pref: " + e + "]\n");
      }

      finalURL = defaultSearchURL + mySearchTerm;

      ...
    },

正如之前所描述的那样,协议就是通道,所以newChannel必须返回一个通道。由于这个协议的目的是运行一次搜索,返回的通道有一些javascript来改变浏览器窗口的位置到搜索页面。这可以通过获取IOService服务,创建一个新的通道并从newChannel返回这个通道来实现。
图8:创建一个通道
    newChannel: function(input_uri)
    {
      ...

      /* create dummy nsIChannel instance */
      var ios = Components.classes[kIOSERVICE_CONTRACTID]
                          .getService(nsIIOService);

      return ios.newChannel("javascript:document.location.href='" + finalURL + "'", null, null);

    },

最终newChannel的代码如下图9所示。完整的源代码可以在这里找到。
图9:最终Protocol代码
    newChannel: function(input_uri)
    {
      // aURI is a nsIUri, so get a string from it using .spec
      var mySearchTerm = aURI.spec;

      // strip away the kSCHEME: part
      mySearchTerm = mySearchTerm.substring(mySearchTerm.indexOf(":") + 1, mySearchTerm.length);    
      mySearchTerm = encodeURI(mySearchTerm);

      dump("[mySearchTerm=" + mySearchTerm + "]\n");
      var finalURL = "";
    
      try{ 
        // Get the preferences service
        var prefService = Components.classes["@mozilla.org/preferences-service;1"]
                                    .getService(Components.interfaces.nsIPrefService);

        var prefBranch = prefService.getBranch(null);
  
        defaultSearchURL = prefBranch.getComplexValue("browser.search.defaulturl",
                              Components.interfaces.nsIPrefLocalizedString).data;

        var searchDS = Components.classes["@mozilla.org/rdf/datasource;1?name=internetsearch"]
                                 .getService(Components.interfaces.nsIInternetSearchService);

        var searchEngineURI = prefBranch.getCharPref("browser.search.defaultengine");
        if (searchEngineURI) {          
          searchURL = searchDS.GetInternetSearchURL(searchEngineURI, mySearchTerm, 0, 0, {value:0});
          if (searchURL)
            defaultSearchURL = searchURL;      
        }
        dump("[Search Protocol Success: " + defaultSearchURL + "]")
      } catch (e){
        dump("[Search Protocol failed to get the search pref: " + e + "]\n");
      }

      finalURL = defaultSearchURL + mySearchTerm;

      /* create dummy nsIURI and nsIChannel instances */
      var ios = Components.classes[kIOSERVICE_CONTRACTID]
                          .getService(nsIIOService);

      return ios.newChannel("javascript:document.location='" + finalURL + "'", null, null);

    },

安装协议

XPCOM组件在mozilla的mozillaDir/components目录下。要安装该搜索协议,复制完整的javascript文件到那个目录。还需要告知Mozilla注册这个组件(注册了的组件列在components/目录下的compreg.dat文件中),可以通过将一个叫做.autoreg的空文件放到mozilla的安装目录(与mozilla可执行程序同一级别)下来完成。
在协议安装完成之后,必须重新启动mozilla来完成新组件的注册。然后就可以通过输入x-search:mozilla到url栏并按enter键来调用该协议。
译注:
上述实例在gecko2.0之前的版本才可用,经测试,将TestPprotocol.js文件放到firefox的components目录下,然后在firefox的安装目录添加.autoreg文件之后重启浏览器即可在components目录下的compreg.dat文件中发现TestPprotocol.js已经成功注册,在地址栏中输入x-search:mozilla没什么反应(在the talk page for nsIProtocolHandler 提到到了上述例子代码的一些不足,但没有给出解决方案),但是在注册该协议之前如果输入这个并按回车会提示x-search协议未与任何应用程序关联。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值