Mozilla最为人诟病的地方就是没有称手的开发工具,这对于我们这些被微软惯坏的开发人员来说,如果没有Visual Studio这样舒服的工具的话,谁会投入你的怀抱呢?本文就希望从三个方面介绍下我所了解到的Mozilla 平台下的开发工具及一些小技巧。
Mozilla最为人诟病的地方就是没有称手的开发工具,这对于我们这些被微软惯坏的开发人员来说,如果没有Visual Studio这样舒服的工具的话,谁会投入你的怀抱呢?本文就希望从三个方面介绍下我所了解到的Mozilla 平台下的开发工具及一些小技巧。
最原始的方法当然是全部手工打造,从插件的编写,到打包,这样做的好处是对Mozilla平台下插件的开发有比较深入的了解,但缺点就是每次做一些小的修改后都需要重复进行压缩,打包等一系列繁杂的工序,如果你喜欢这种方法,可以参考我这篇文章:《浅谈基于Mozilla ThunderBird的扩展开发》,也可以参考Mozilla开发中心这篇文章《Building an Extension》。
每次修改代码后就得自己手动重新打包,发布,安装,你对这些已经感到厌烦了吗?好的,那就想办法脱离苦海吧。我们仔细分析后发现,就是一个打包的问题,因此我们就可以使用Ant来完成这些琐碎的打包工作,下面就是《浅谈基于Mozilla ThunderBird的扩展开发》中使用的build.xml文件:
<?xml version="1.0"?>
<project name="helloworld" default="createxpi">
<delete file="helloworld.xpi"/>
<delete file="helloworld.jar"/>
<target name="createjar">
<zip destfile="helloworld.jar" compress="false">
<fileset dir="chrome">
<include name="content/**"/>
</fileset>
</zip>
</target>

<target name="createxpi" depends="createjar">
<zip destfile="helloworld.xpi">
<zipfileset dir="." includes="helloworld.jar" prefix="chrome" />
<zipfileset dir="." includes="chrome.manifest"/>
<zipfileset dir="." includes="install.rdf" />
</zip>
</target>
</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;
tBirdBiffUtility.classID = Components.ID("{e07f8540-831f-11db-9fe1-0800200c9a66}");
tBirdBiffUtility.contractID = "@phinecos.cnblogs.com/HelloWorld/utility;1";
tBirdBiffUtility.classDescription = "Utility Service";

//日志组件
function tBirdBiffUtility()


{
this.consoleService = CC["@mozilla.org/consoleservice;1"].getService(CI.nsIConsoleService);//控制台服务
this.logFile = null;//日志文件对象
this.logToFile = true;//是否开启日志功能,默认为开启,插件发布后,关闭它就是
}

tBirdBiffUtility.prototype =


{
initialize: function()

{//初始化
this.getLoggingPref();
},

closeLogFile: function()

{//关闭日志文件
if(this.logFile)

{
this.logFile.close(null);
}
},

openLogFile: function()

{//打开日志文件
this.closeLogFile();//关闭先前的日志文件

if(!this.logToFile)

{
return;
}

var tmpFile = CC["@mozilla.org/file/directory_service;1"]
.getService(CI.nsIProperties).get("TmpD", CI.nsILocalFile);
//构造日志文件名称
var filename = "HelloWorld";
if(this.isFirefox())

{
filename += "-firefox.log";
}

if(this.isSunbird())

{
filename += "-sunbird.log";
}

if(this.isThunderbird())

{
filename += "-thunderbird.log";
}

tmpFile.append(filename);
//创建不重名的日志文件
tmpFile.createUnique(CI.nsIFile.NORMAL_FILE_TYPE, 0664);

this.logFile = CC["@mozilla.org/network/file-output-stream;1"]
.createInstance(CI.nsIFileOutputStream);

// write, create, truncate
this.logFile.init(tmpFile, 0x02 | 0x08 | 0x20, 0664, this.logFile.DEFAULT_REPLACEMENT_CHARACTER);
},

isFirefox: function()

{
if(this.getApplicationId() == "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}")

{
return true;
}
else

{
return false;
}
},

isSunbird: function()

{
if(this.getApplicationId() == "{718e30fb-e89b-41dd-9da7-e25a45638b28}")

{
return true;
}
else

{
return false;
}
},

isThunderbird: function()

{
if(this.getApplicationId() == "{3550f703-e582-4d05-9a08-453d09bdfdc6}")

{
return true;
}
else

{
return false;
}
},

getLoggingPref: function()

{
// look for and use a preference if given
if (this.logToFile == true)

{//开启了日志功能
this.openLogFile();
this.logToConsole("tBirdBiffUtility.getLoggingPref", "thunderbirdbiff.logToFile preference setting is " + this.logToFile);
}
else

{//没有开启日志功能
this.logToConsole("tBirdBiffUtility.logToFile", "No thunderbirdbiff.logToFile preference setting found, using FALSE default");
this.logToFile = false;
}
},

logError: function(source, message)

{//记录错误信息
var callingFunction = source;
var error = CC["@mozilla.org/scripterror;1"].createInstance(CI.nsIScriptError);
error.init(callingFunction + "(): " + message, null, null, null, null, CI.nsIScriptError.errorFlag, null);
this.consoleService.logMessage(error);
this.log(source, "ERROR-" + message);
this.callingFunction = null;
this.error = null;
},

writeToLogFile: function(msg)

{//写入日志文件
if(this.logToFile)

{
this.logFile.write(msg + "\n", msg.length + 1);
}
},

log: function(source, message)

{//日志功能
var msg = source + "():\n\ " + message;

try

{
this.writeToLogFile(msg);
}
catch(e)

{
var errorMsg = "Error trying to write logfile: " + e + " -- Opening a new file\n";
this.logToConsole(errorMsg);
this.openLogFile();
if(this.logFile)

{
this.writeToLogFile(errorMsg);
this.writeToLogFile(msg);
}

errorMsg = null;
}

msg = null;
},

logToConsole: function(source, message)

{//写日志到控制台
var msg = this.getAddonName() + "." + source + "():\n\ " + message;
this.consoleService.logStringMessage(msg);
this.log(source, message);
msg = null;
},

getApplicationInfo: function()

{//获取目标程序信息
return CC["@mozilla.org/xre/app-info;1"].getService(CI.nsIXULAppInfo);
},

getApplicationId: function()

{//获取目标程序id
if(!this.id)

{
this.id = this.getApplicationInfo().ID;
}

return this.id;
},

onObserve: function(subject, topic, data)

{
},

// nsISupports
QueryInterface: function(aIID)

{
if( aIID.equals(CI.nsISupports) ||
aIID.equals(CI.nsIClassInfo) ||
aIID.equals(CI.nsIObserver))

{
return this;
}

throw CR.NS_ERROR_NO_INTERFACE;
},

// nsIClassInfo
getInterfaces: function(aCount)

{
var ifaces = new Array();
ifaces.push(CI.nsISupports);
ifaces.push(CI.nsIClassInfo);
ifaces.push(CI.nsIObserver);
aCount.value = ifaces.length;
return ifaces;
},

// nsIClassInfo

getHelperForLanguage: function(aLanguage)
{ return null; },

get contractID()
{ return tBirdBiffUtility.contractID; },

get classID()
{ return tBirdBiffUtility.classID; },

get classDescription()
{ return tBirdBiffUtility.classDescription; },

get implementationLanguage()
{ return CI.nsIProgrammingLanguage.JAVASCRIPT; },

get flags()
{ return CI.nsIClassInfo.SINGLETON; },

// nsIObserver
observe: function(aSubject, aTopic, aData)

{
switch(aTopic)

{
case "xpcom-startup":

{
// this is run very early, right after XPCOM is initialized, but before
// user profile information is applied. Register ourselves as an observer
// for 'profile-after-change' and 'quit-application'
//
var obsSvc = CC["@mozilla.org/observer-service;1"].getService(CI.nsIObserverService);
obsSvc.addObserver(this, "profile-after-change", false);
obsSvc.addObserver(this, "quit-application", false);
break;
}

case "profile-after-change":

{
// This happens after profile has been loaded and user preferences have been read.
// startup code here
this.initialize();
break;
}

case "quit-application":

{
var obsSvc = CC["@mozilla.org/observer-service;1"].getService(CI.nsIObserverService);
obsSvc.removeObserver(this, "profile-after-change");
obsSvc.removeObserver(this, "quit-application");
break;
}

default:

{
this.onObserve(aSubject, aTopic, aData);
}
}
},


get wrappedJSObject()
{ return this; }
}

// ====================================================================================
// constructors for objects we want to XPCOMify
//
var gXpComObjects = [tBirdBiffUtility];
var gCatObserverName = tBirdBiffUtility.classDescription; // can be anything
var gCatContractID = tBirdBiffUtility.contractID;

// **********
// generic registration code below. Useful URL: http://www.mozilla.org/projects/xpcom/book/cxc/html/weblock.html
// **********
function NSGetModule(compMgr, fileSpec)


{
gModule._catObserverName = gCatObserverName;
gModule._catContractId = gCatContractID;

for (var i in gXpComObjects)
gModule._xpComObjects[i] = new FactoryHolder(gXpComObjects[i]);

return gModule;
}

function FactoryHolder(aObj)


{
this.classID = aObj.classID;
this.contractID = aObj.contractID;
this.className = aObj.classDescription;
this.factory =

{
createInstance: function(aOuter, aIID)

{
if (aOuter)
throw CR.NS_ERROR_NO_AGGREGATION;

return (new this.constructor).QueryInterface(aIID);
}
};

this.factory.constructor = aObj;
}

var gModule =


{

_xpComObjects:
{},
_catObserverName: null,
_catContractId: null,

registerSelf: function(aComponentManager, aFileSpec, aLocation, aType)

{
aComponentManager.QueryInterface(CI.nsIComponentRegistrar);
for (var key in this._xpComObjects)

{
var obj = this._xpComObjects[key];
aComponentManager.registerFactoryLocation(obj.classID, obj.className,
obj.contractID, aFileSpec, aLocation, aType);
}

var catman = CC["@mozilla.org/categorymanager;1"].getService(CI.nsICategoryManager);
catman.addCategoryEntry("xpcom-startup", this._catObserverName, this._catContractId, true, true);
catman.addCategoryEntry("xpcom-shutdown", this._catObserverName, this._catContractId, true, true);
},

unregisterSelf: function(aCompMgr, aFileSpec, aLocation)

{
var catman = CC["@mozilla.org/categorymanager;1"].getService(CI.nsICategoryManager);
catman.deleteCategoryEntry("xpcom-startup", this._catObserverName, true);
catman.deleteCategoryEntry("xpcom-shutdown", this._catObserverName, true);

aComponentManager.QueryInterface(CI.nsIComponentRegistrar);
for (var key in this._xpComObjects)

{
var obj = this._xpComObjects[key];
aComponentManager.unregisterFactoryLocation(obj.classID, aFileSpec);
}
},

getClassObject: function(aComponentManager, aCID, aIID)

{
if (!aIID.equals(CI.nsIFactory))
throw CR.NS_ERROR_NOT_IMPLEMENTED;

for (var key in this._xpComObjects)

{
if (aCID.equals(this._xpComObjects[key].classID))
return this._xpComObjects[key].factory;
}

throw CR.NS_ERROR_NO_INTERFACE;
},

canUnload: function(aComponentManager)

{
return true;
}
}


【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述