基于Mozilla Thunderbird的扩展开发(七)---工欲善其事,必先利其器
Mozilla最为人诟病的地方就是没有称手的开发工具,这对于我们这些被微软惯坏的开发人员来说,如果没有Visual Studio这样舒服的工具的话,谁会投入你的怀抱呢?本文就希望从三个方面介绍下我所了解到的Mozilla 平台下的开发工具及一些小技巧。
最原始的方法当然是全部手工打造,从插件的编写,到打包,这样做的好处是对Mozilla平台下插件的开发有比较深入的了解,但缺点就是每次做一些小的修改后都需要重复进行压缩,打包等一系列繁杂的工序,如果你喜欢这种方法,可以参考我这篇文章:《浅谈基于Mozilla ThunderBird的扩展开发》,也可以参考Mozilla开发中心这篇文章《Building an Extension》。
      每次修改代码后就得自己手动重新打包,发布,安装,你对这些已经感到厌烦了吗?好的,那就想办法脱离苦海吧。我们仔细分析后发现,就是一个打包的问题,因此我们就可以使用Ant来完成这些琐碎的打包工作,下面就是《浅谈基于Mozilla ThunderBird的扩展开发》中使用的build.xml文件:
 <?xml version="1.0"?>
<?xml version="1.0"?> <project name="helloworld" default="createxpi">
<project name="helloworld" default="createxpi"> <delete file="helloworld.xpi"/>
  <delete file="helloworld.xpi"/> <delete file="helloworld.jar"/>
  <delete file="helloworld.jar"/> <target name="createjar">
  <target name="createjar"> <zip destfile="helloworld.jar" compress="false">
    <zip destfile="helloworld.jar" compress="false"> <fileset dir="chrome">
        <fileset dir="chrome"> <include name="content/**"/>
            <include name="content/**"/> </fileset>
        </fileset> </zip>
    </zip> </target>
  </target>
 <target name="createxpi" depends="createjar">
  <target name="createxpi" depends="createjar"> <zip destfile="helloworld.xpi">
    <zip destfile="helloworld.xpi"> <zipfileset dir="." includes="helloworld.jar" prefix="chrome" />
      <zipfileset dir="." includes="helloworld.jar" prefix="chrome" /> <zipfileset dir="." includes="chrome.manifest"/>
      <zipfileset dir="." includes="chrome.manifest"/> <zipfileset dir="." includes="install.rdf" />
      <zipfileset dir="." includes="install.rdf" /> </zip>
    </zip> </target>
  </target> </project>
</project>
如果你的项目结构比这个要复杂的话,请根据自己的需求更改此文件再使用Ant编译就可以了。这种方法也是我目前使用最多的方法了,因为感觉控制起来比较灵活。
      如果你喜欢集成化的开发工具,想让它为你做更多的事情的话,这里推荐一个工具:NetBean+foxbeans+foxfiles,后面两个插件是用于开发Mozilla平台插件的,下载地址为:
Foxbeans: http://plugins.netbeans.org/PluginPortal/faces/PluginDetailPage.jsp?pluginid=4209
Foxfiles: http://plugins.netbeans.org/PluginPortal/faces/PluginDetailPage.jsp?pluginid=4649
官方的入门文档:
http://wiki.netbeans.org/MozillaAddonDevelopment
不过不推荐用这个插件,试用了一下,并不是很好用。
      
      再来谈下另一个问题,在做界面时,如何快速地获知目标程序中想overlay掉的控件的id以及它的属性呢?这在做插件开发时是一个比较重要的方面,因为你的插件的界面就是附着在目标程序(firefox,thunderbird或sunbird)上的。那么官方推荐的工具就够用了,这个插件就是DOM Inspector,它可以用来定位你所需要的控件。
      Venkman是官方推荐的调试工具,但个人感觉还是不大好用。
另外几个调试的技巧是:1)可以试用”dump语句来显示调试信息,不过这需要进行一些配置工作。2)在代码中使用nsIConsoleService 将日志信息输出到JavaScript控制台中。
3)自己编译一个debug版本的firefox或thunderbird,并且在其源代码或你的C++ XPCOM组件中设置断点,进一步的信息可以参考Mozilla开发中心的文档。
      除此以外,我们也可以自己开发一个日志工具类来记录感兴趣的信息,下面是我常用的一个日志组件类:
 const CI = Components.interfaces, CC = Components.classes, CR = Components.results;
const CI = Components.interfaces, CC = Components.classes, CR = Components.results; tBirdBiffUtility.classID = Components.ID("{e07f8540-831f-11db-9fe1-0800200c9a66}");
tBirdBiffUtility.classID = Components.ID("{e07f8540-831f-11db-9fe1-0800200c9a66}"); tBirdBiffUtility.contractID = "@phinecos.cnblogs.com/HelloWorld/utility;1";
tBirdBiffUtility.contractID = "@phinecos.cnblogs.com/HelloWorld/utility;1"; tBirdBiffUtility.classDescription = "Utility Service";
tBirdBiffUtility.classDescription = "Utility Service";
 //日志组件
//日志组件 function tBirdBiffUtility()
function tBirdBiffUtility() {
{ this.consoleService = CC["@mozilla.org/consoleservice;1"].getService(CI.nsIConsoleService);//控制台服务
  this.consoleService = CC["@mozilla.org/consoleservice;1"].getService(CI.nsIConsoleService);//控制台服务 this.logFile = null;//日志文件对象
  this.logFile = null;//日志文件对象 this.logToFile = true;//是否开启日志功能,默认为开启,插件发布后,关闭它就是
  this.logToFile = true;//是否开启日志功能,默认为开启,插件发布后,关闭它就是 }
}
 tBirdBiffUtility.prototype =
tBirdBiffUtility.prototype = {
{ initialize: function()
  initialize: function() {//初始化
  {//初始化 this.getLoggingPref();
    this.getLoggingPref(); },
  },
 closeLogFile: function()
  closeLogFile: function() {//关闭日志文件
  {//关闭日志文件 if(this.logFile)
    if(this.logFile) {
    { this.logFile.close(null);
      this.logFile.close(null); }
    } },
  },
 openLogFile: function()
  openLogFile: function() {//打开日志文件
  {//打开日志文件 this.closeLogFile();//关闭先前的日志文件
    this.closeLogFile();//关闭先前的日志文件
 if(!this.logToFile)
    if(!this.logToFile) {
    { return;
      return; }
    }
 var tmpFile = CC["@mozilla.org/file/directory_service;1"]
    var tmpFile = CC["@mozilla.org/file/directory_service;1"] .getService(CI.nsIProperties).get("TmpD", CI.nsILocalFile);
        .getService(CI.nsIProperties).get("TmpD", CI.nsILocalFile); //构造日志文件名称
    //构造日志文件名称 var filename = "HelloWorld";
    var filename = "HelloWorld"; if(this.isFirefox())
    if(this.isFirefox()) {
    { filename += "-firefox.log";
      filename += "-firefox.log"; }
    }
 if(this.isSunbird())
    if(this.isSunbird()) {
    { filename += "-sunbird.log";
      filename += "-sunbird.log"; }
    }
 if(this.isThunderbird())
    if(this.isThunderbird()) {
    { filename += "-thunderbird.log";
      filename += "-thunderbird.log"; }
    }
 tmpFile.append(filename);
    tmpFile.append(filename); //创建不重名的日志文件
    //创建不重名的日志文件 tmpFile.createUnique(CI.nsIFile.NORMAL_FILE_TYPE, 0664);
    tmpFile.createUnique(CI.nsIFile.NORMAL_FILE_TYPE, 0664);
 this.logFile = CC["@mozilla.org/network/file-output-stream;1"]
    this.logFile = CC["@mozilla.org/network/file-output-stream;1"] .createInstance(CI.nsIFileOutputStream);
        .createInstance(CI.nsIFileOutputStream);
 // write, create, truncate
    // write, create, truncate this.logFile.init(tmpFile, 0x02 | 0x08 | 0x20, 0664, this.logFile.DEFAULT_REPLACEMENT_CHARACTER);
    this.logFile.init(tmpFile, 0x02 | 0x08 | 0x20, 0664, this.logFile.DEFAULT_REPLACEMENT_CHARACTER); },
  },
 isFirefox: function()
  isFirefox: function() {
  { if(this.getApplicationId() == "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}")
    if(this.getApplicationId() == "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") {
    { return true;
      return true; }
    } else
    else {
    { return false;
      return false; }
    } },
  },
 isSunbird: function()
  isSunbird: function() {
  { if(this.getApplicationId() == "{718e30fb-e89b-41dd-9da7-e25a45638b28}")
    if(this.getApplicationId() == "{718e30fb-e89b-41dd-9da7-e25a45638b28}") {
    { return true;
      return true; }
    } else
    else {
    { return false;
      return false; }
    } },
  },
 isThunderbird: function()
  isThunderbird: function() {
  { if(this.getApplicationId() == "{3550f703-e582-4d05-9a08-453d09bdfdc6}")
    if(this.getApplicationId() == "{3550f703-e582-4d05-9a08-453d09bdfdc6}") {
    { return true;
      return true; }
    } else
    else {
    { return false;
      return false; }
    } },
  },
 getLoggingPref: function()
  getLoggingPref: function() {
  { // look for and use a preference if given
    // look for and use a preference if given if (this.logToFile == true)
    if (this.logToFile == true) {//开启了日志功能
    {//开启了日志功能 this.openLogFile();
        this.openLogFile(); this.logToConsole("tBirdBiffUtility.getLoggingPref", "thunderbirdbiff.logToFile preference setting is " + this.logToFile);
          this.logToConsole("tBirdBiffUtility.getLoggingPref", "thunderbirdbiff.logToFile preference setting is " + this.logToFile); }
    } else
    else {//没有开启日志功能
    {//没有开启日志功能 this.logToConsole("tBirdBiffUtility.logToFile", "No thunderbirdbiff.logToFile preference setting found, using FALSE default");
      this.logToConsole("tBirdBiffUtility.logToFile", "No thunderbirdbiff.logToFile preference setting found, using FALSE default"); this.logToFile = false;
      this.logToFile = false; }
    } },
  },
 logError: function(source, message)
  logError: function(source, message) {//记录错误信息
  {//记录错误信息 var callingFunction = source;
    var callingFunction = source; var error = CC["@mozilla.org/scripterror;1"].createInstance(CI.nsIScriptError);
    var error = CC["@mozilla.org/scripterror;1"].createInstance(CI.nsIScriptError); error.init(callingFunction + "(): " + message, null, null, null, null, CI.nsIScriptError.errorFlag, null);
    error.init(callingFunction + "(): " + message, null, null, null, null, CI.nsIScriptError.errorFlag, null); this.consoleService.logMessage(error);
    this.consoleService.logMessage(error); this.log(source, "ERROR-" + message);
    this.log(source, "ERROR-" + message); this.callingFunction = null;
    this.callingFunction = null; this.error = null;
    this.error = null; },
  },
 writeToLogFile: function(msg)
  writeToLogFile: function(msg) {//写入日志文件
  {//写入日志文件 if(this.logToFile)
    if(this.logToFile) {
    { this.logFile.write(msg + "\n", msg.length + 1);
      this.logFile.write(msg + "\n", msg.length + 1); }
    } },
  },
 log: function(source, message)
  log: function(source, message) {//日志功能
  {//日志功能 var msg =  source + "():\n\   " + message;
    var msg =  source + "():\n\   " + message;
 try
    try {
    { this.writeToLogFile(msg);
      this.writeToLogFile(msg); }
    } catch(e)
    catch(e) {
    { var errorMsg = "Error trying to write logfile: " + e + " -- Opening a new file\n";
      var errorMsg = "Error trying to write logfile: " + e + " -- Opening a new file\n"; this.logToConsole(errorMsg);
      this.logToConsole(errorMsg); this.openLogFile();
      this.openLogFile(); if(this.logFile)
      if(this.logFile) {
      { this.writeToLogFile(errorMsg);
        this.writeToLogFile(errorMsg); this.writeToLogFile(msg);
        this.writeToLogFile(msg); }
      }
 errorMsg = null;
      errorMsg = null; }
    }
 msg = null;
    msg = null; },
  },
 logToConsole: function(source, message)
  logToConsole: function(source, message) {//写日志到控制台
  {//写日志到控制台 var msg = this.getAddonName() + "." + source + "():\n\   " + message;
    var msg = this.getAddonName() + "." + source + "():\n\   " + message; this.consoleService.logStringMessage(msg);
    this.consoleService.logStringMessage(msg); this.log(source, message);
    this.log(source, message); msg = null;
    msg = null; },
  },
 getApplicationInfo: function()
  getApplicationInfo: function() {//获取目标程序信息
  {//获取目标程序信息 return CC["@mozilla.org/xre/app-info;1"].getService(CI.nsIXULAppInfo);
    return CC["@mozilla.org/xre/app-info;1"].getService(CI.nsIXULAppInfo); },
  },
 getApplicationId: function()
  getApplicationId: function() {//获取目标程序id
  {//获取目标程序id if(!this.id)
    if(!this.id) {
    { this.id = this.getApplicationInfo().ID;
      this.id = this.getApplicationInfo().ID; }
    }
 return this.id;
    return this.id; },
  },
 onObserve: function(subject, topic, data)
  onObserve: function(subject, topic, data) {
    { },
    },
 // nsISupports
  // nsISupports QueryInterface: function(aIID)
    QueryInterface: function(aIID) {
    { if( aIID.equals(CI.nsISupports) ||
    if( aIID.equals(CI.nsISupports) || aIID.equals(CI.nsIClassInfo) ||
      aIID.equals(CI.nsIClassInfo) || aIID.equals(CI.nsIObserver))
      aIID.equals(CI.nsIObserver)) {
        { return this;
            return this; }
        }
 throw CR.NS_ERROR_NO_INTERFACE;
        throw CR.NS_ERROR_NO_INTERFACE; },
    },
 // nsIClassInfo
  // nsIClassInfo getInterfaces: function(aCount)
    getInterfaces: function(aCount) {
    { var ifaces = new Array();
        var ifaces = new Array(); ifaces.push(CI.nsISupports);
    ifaces.push(CI.nsISupports); ifaces.push(CI.nsIClassInfo);
    ifaces.push(CI.nsIClassInfo); ifaces.push(CI.nsIObserver);
    ifaces.push(CI.nsIObserver); aCount.value = ifaces.length;
        aCount.value = ifaces.length; return ifaces;
        return ifaces; },
    },
 // nsIClassInfo
  // nsIClassInfo getHelperForLanguage: function(aLanguage) { return null; },
    getHelperForLanguage: function(aLanguage) { return null; }, get contractID() { return tBirdBiffUtility.contractID; },
  get contractID() { return tBirdBiffUtility.contractID; }, get classID() { return tBirdBiffUtility.classID; },
  get classID() { return tBirdBiffUtility.classID; }, get classDescription() { return tBirdBiffUtility.classDescription; },
  get classDescription() { return tBirdBiffUtility.classDescription; }, get implementationLanguage() { return CI.nsIProgrammingLanguage.JAVASCRIPT; },
  get implementationLanguage() { return CI.nsIProgrammingLanguage.JAVASCRIPT; }, get flags() { return CI.nsIClassInfo.SINGLETON; },
  get flags() { return CI.nsIClassInfo.SINGLETON; },
 // nsIObserver
  // nsIObserver observe: function(aSubject, aTopic, aData)
    observe: function(aSubject, aTopic, aData) {
    { switch(aTopic)
        switch(aTopic) {
        { case "xpcom-startup":
            case "xpcom-startup": {
      { // this is run very early, right after XPCOM is initialized, but before
        // this is run very early, right after XPCOM is initialized, but before // user profile information is applied. Register ourselves as an observer
                // user profile information is applied. Register ourselves as an observer // for 'profile-after-change' and 'quit-application'
                // for 'profile-after-change' and 'quit-application' //
                // var obsSvc = CC["@mozilla.org/observer-service;1"].getService(CI.nsIObserverService);
        var obsSvc = CC["@mozilla.org/observer-service;1"].getService(CI.nsIObserverService); obsSvc.addObserver(this, "profile-after-change", false);
                obsSvc.addObserver(this, "profile-after-change", false); obsSvc.addObserver(this, "quit-application", false);
                obsSvc.addObserver(this, "quit-application", false); break;
                break; }
      }
 case "profile-after-change":
      case "profile-after-change": {
      { // This happens after profile has been loaded and user preferences have been read.
                // This happens after profile has been loaded and user preferences have been read. // startup code here
                // startup code here this.initialize();
        this.initialize(); break;
        break; }
      }
 case "quit-application":
            case "quit-application": {
      { var obsSvc = CC["@mozilla.org/observer-service;1"].getService(CI.nsIObserverService);
        var obsSvc = CC["@mozilla.org/observer-service;1"].getService(CI.nsIObserverService); obsSvc.removeObserver(this, "profile-after-change");
                obsSvc.removeObserver(this, "profile-after-change"); obsSvc.removeObserver(this, "quit-application");
                obsSvc.removeObserver(this, "quit-application"); break;
        break; }
      }
 default:
            default: {
      { this.onObserve(aSubject, aTopic, aData);
                this.onObserve(aSubject, aTopic, aData); }
      } }
        } },
  },
 get wrappedJSObject() { return this; }
    get wrappedJSObject() { return this; } }
}
 // ====================================================================================
// ==================================================================================== // constructors for objects we want to XPCOMify
// constructors for objects we want to XPCOMify //
// var gXpComObjects = [tBirdBiffUtility];
var gXpComObjects = [tBirdBiffUtility]; var gCatObserverName = tBirdBiffUtility.classDescription; // can be anything
var gCatObserverName = tBirdBiffUtility.classDescription; // can be anything var gCatContractID = tBirdBiffUtility.contractID;
var gCatContractID = tBirdBiffUtility.contractID;
 // **********
// ********** // generic registration code below. Useful URL: http://www.mozilla.org/projects/xpcom/book/cxc/html/weblock.html
// generic registration code below. Useful URL: http://www.mozilla.org/projects/xpcom/book/cxc/html/weblock.html // **********
// ********** function NSGetModule(compMgr, fileSpec)
function NSGetModule(compMgr, fileSpec) {
{ gModule._catObserverName = gCatObserverName;
    gModule._catObserverName = gCatObserverName; gModule._catContractId = gCatContractID;
    gModule._catContractId = gCatContractID;
 for (var i in gXpComObjects)
    for (var i in gXpComObjects) gModule._xpComObjects[i] = new FactoryHolder(gXpComObjects[i]);
        gModule._xpComObjects[i] = new FactoryHolder(gXpComObjects[i]);
 return gModule;
    return gModule; }
}
 function FactoryHolder(aObj)
function FactoryHolder(aObj) {
{ this.classID = aObj.classID;
    this.classID = aObj.classID; this.contractID = aObj.contractID;
    this.contractID = aObj.contractID; this.className  = aObj.classDescription;
    this.className  = aObj.classDescription; this.factory =
    this.factory = {
    { createInstance: function(aOuter, aIID)
        createInstance: function(aOuter, aIID) {
        { if (aOuter)
            if (aOuter) throw CR.NS_ERROR_NO_AGGREGATION;
                throw CR.NS_ERROR_NO_AGGREGATION;
 return (new this.constructor).QueryInterface(aIID);
            return (new this.constructor).QueryInterface(aIID); }
        } };
    };
 this.factory.constructor = aObj;
    this.factory.constructor = aObj; }
}
 var gModule =
var gModule = {
{ _xpComObjects: {},
    _xpComObjects: {}, _catObserverName: null,
    _catObserverName: null, _catContractId: null,
    _catContractId: null,
 registerSelf: function(aComponentManager, aFileSpec, aLocation, aType)
    registerSelf: function(aComponentManager, aFileSpec, aLocation, aType) {
    { aComponentManager.QueryInterface(CI.nsIComponentRegistrar);
    aComponentManager.QueryInterface(CI.nsIComponentRegistrar); for (var key in this._xpComObjects)
        for (var key in this._xpComObjects) {
        { var obj = this._xpComObjects[key];
            var obj = this._xpComObjects[key]; aComponentManager.registerFactoryLocation(obj.classID, obj.className,
            aComponentManager.registerFactoryLocation(obj.classID, obj.className, obj.contractID, aFileSpec, aLocation, aType);
            obj.contractID, aFileSpec, aLocation, aType); }
        }
 var catman = CC["@mozilla.org/categorymanager;1"].getService(CI.nsICategoryManager);
    var catman = CC["@mozilla.org/categorymanager;1"].getService(CI.nsICategoryManager); catman.addCategoryEntry("xpcom-startup", this._catObserverName, this._catContractId, true, true);
        catman.addCategoryEntry("xpcom-startup", this._catObserverName, this._catContractId, true, true); catman.addCategoryEntry("xpcom-shutdown", this._catObserverName, this._catContractId, true, true);
        catman.addCategoryEntry("xpcom-shutdown", this._catObserverName, this._catContractId, true, true); },
    },
 unregisterSelf: function(aCompMgr, aFileSpec, aLocation)
    unregisterSelf: function(aCompMgr, aFileSpec, aLocation) {
    { var catman = CC["@mozilla.org/categorymanager;1"].getService(CI.nsICategoryManager);
    var catman = CC["@mozilla.org/categorymanager;1"].getService(CI.nsICategoryManager); catman.deleteCategoryEntry("xpcom-startup", this._catObserverName, true);
        catman.deleteCategoryEntry("xpcom-startup", this._catObserverName, true); catman.deleteCategoryEntry("xpcom-shutdown", this._catObserverName, true);
        catman.deleteCategoryEntry("xpcom-shutdown", this._catObserverName, true);
 aComponentManager.QueryInterface(CI.nsIComponentRegistrar);
    aComponentManager.QueryInterface(CI.nsIComponentRegistrar); for (var key in this._xpComObjects)
        for (var key in this._xpComObjects) {
        { var obj = this._xpComObjects[key];
            var obj = this._xpComObjects[key]; aComponentManager.unregisterFactoryLocation(obj.classID, aFileSpec);
            aComponentManager.unregisterFactoryLocation(obj.classID, aFileSpec); }
        } },
    },
 getClassObject: function(aComponentManager, aCID, aIID)
    getClassObject: function(aComponentManager, aCID, aIID) {
    { if (!aIID.equals(CI.nsIFactory))
    if (!aIID.equals(CI.nsIFactory)) throw CR.NS_ERROR_NOT_IMPLEMENTED;
            throw CR.NS_ERROR_NOT_IMPLEMENTED;
 for (var key in this._xpComObjects)
        for (var key in this._xpComObjects) {
        { if (aCID.equals(this._xpComObjects[key].classID))
            if (aCID.equals(this._xpComObjects[key].classID)) return this._xpComObjects[key].factory;
                return this._xpComObjects[key].factory; }
        }
 throw CR.NS_ERROR_NO_INTERFACE;
        throw CR.NS_ERROR_NO_INTERFACE; },
    },
 canUnload: function(aComponentManager)
  canUnload: function(aComponentManager) {
  { return true;
    return true; }
  } }
}
 
    
作者:洞庭散人
出处:http://phinecos.cnblogs.com/
posted on 2008-05-21 21:31 Phinecos(洞庭散人) 阅读(3951) 评论(4) 收藏 举报
 
                    
                


 
   
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号