2023年11月25日发(作者:)
⼀步⼀步学习Firefox扩展的开发(1)——制作⼯具栏按钮及右键菜单
Firefox提供开放、强⼤且灵活的扩展机制,因此衍⽣出了⼤量功能丰富的扩展组件,这些扩展
组件甚⾄可以说是某些⽤户爱上firefox的主要因素。
⽐较可惜的是firefox扩展开发的中⽂资料相对⽐较少,因此在这⾥我会从简单到深⼊介绍开发
firefox扩展的⼀些技术及常遇到的问题。
本⽂主要内容:
制作⼀个包含⼯具栏按钮及右键菜单的简单组件 解决扩展安装后⼯具栏按钮却不显⽰的问
题
因为这个组件不需要设计交互窗⼝,因此开发过程相对简单,下⾯是这个组件运⾏的效果图:
在开始之前先介绍⼀下⼏个⽹址:
/en/Extensions 官⽅开发⽹,类似MSDN,是开发时的主要参考资
料;
/en/Building_an_Extension,官⽅的⼀个基础教程,笔者通过此篇
⽂章⽽⼊门;
/code/mozilla/extensionwiz/,⼀个在线的创建扩展的向导,如果你想⽴
即创建⼀个属于⾃⼰的扩展,可以试试⽤这个向导制作⼀个雏形,然后再⾃⼰慢慢添砖添⽡。
本⽂所讲的例⼦也是⽤这个向导制作出来的。
好了,下⾯就开始讲解本⽂的⽰例组件:uusharedemo1的制作过程。
1、了解扩展组件的构成
Firefox扩展组件是以⼀个xpi⽂件的形式发布的,⽽xpi⽂件实际上是zip压缩包 ⽂件,我们只要
把Firefox扩展组件下载回来(⽤右键另存为即可下载),把⽂件扩展名改为zip即可解压并看到
所有的源代码,有时可能还会在压缩包⾥ ⾯看到jar格式⽂件,这个也是zip压缩包,改名解压即
可看到源代码。因此多下载优秀的扩展回来慢慢研究其源代码也不失为⼀种快速的学习⽅法。
⼀般xpi⽂件内部有(但不⼀定都有)如下⽬录及⽂件:
其中 uusharedemo1 为本⽂⽰例组件的名称,可以看到压缩包⾸层有:及
st两个⽂件以及⼀个chrome⽬录。
:是该组件的安装信息,例如组件的名称、版本、作者等信息,XML格式;
st:是⽤于描述该组件由哪些⽂件组成的,即⼀个资源列表;
在chrome⽬录⾥就是该组件的主要内容了,⼀般有:content、defaults、locale、skin等⽬录,
它们的作⽤如下:
content: 这个是最主要的⽬录,包含组件的界⾯定义以及程序代码,其中xul⽂件是界⾯定义⽂
件,类似SilverLight的XAML⽂件,同样是XML格 式;js⽂件是组件功能实现的代码,全部⽤
javascript实现的。 skin:存放css样式表⽂件及资源位图,css是⽤来控制组件界⾯外观的。
locale:如果想让组件能够在不同语⾔的firefox⾥显⽰正确的⽂本,则将不⽤的语⾔列表放到此
⽬录,firefox会⾃动加载合适的语⾔⽂件。这点有些类似Win32PE可执⾏程序中的资源⽂件。
defaults:存放⼀些默认值,⼀般不要此⽬录也可以;
我 们可以把Firefox程序认为是⼀个平台,⽽我们看到firefox浏览器外观本⾝也是⽤跟扩展组件
类似的结构搭建起来的,感兴趣的朋友可以解压 “C:Program FilesMozilla Firefoxchrome”⽬录
下的jar⽂件观摩⼀下firefox浏览器外观的源代码,所以我们制作的扩展组件也会成为firefox浏览
器⾃⾝ 的⼀部分,可以实现⾮常强⼤⽽灵活的功能(例如Firebug这个神奇的组件)。相对来
说,开发ie扩展程序显得困难多了,除⾮只是想做⼯具栏按钮和右键 菜单(⽤注册表即搞
定),否则需要使⽤VC,VB,Delphi这些⼯具开发COM组件才能完成。
如果想了解firefox浏览器本⾝各个元素的定义,但⼜不想阅读chrome⽬录下的脚本代码,可以
安装 DOM Inspector 这个组件,这个组件有如开发web时的FireBug,是开发具有界⾯的扩展组
件时的神兵利器!
提醒:⾃⼰编写的组件代码⽂件最好⽤“UTF-8”格式保存,⼀般可以减少很多⿇烦的问题。
2、认识
包含组件安装信息,本⽂例⼦中它的内容如下:
1. 1
2. 2 3. 3 xmlns:em="/2004/em-rdf#"> 4. 4 5. 5 6. 6 7. 7 8. 8 9. 9 10. 10 11. 11 12. 12 13. 13 14. 14 15. 15 16. 16 17. 17 18. 18 19. 19 20. 20
21. 21
复制代码
⽂件⾥有很多是固定的写法,这⾥就不⼀⼀讲解了。
第5⾏的id是组件的⼀个标识符号,类似Windows⾥COM组件的CLSID,⽤于区别其他组件。可
以使⽤ xxx@ 这样类似email地址的格式组成,⽽这个“email地址”不要求是真实或有效
的。也可以⽤的GUID来标识,例如“{c45c406e-ab73-11d8-be73-000a95be3b12}”是
WebDevelop组件的标识符。
name、version、creator、description、homepageURL分别是组件的名称、版本、创建者的姓
名、组件描述、组件官⽅⽹站地址。
iconURL⽤于显⽰组件的图标,格式⼀般⽤32x32pixel的png图⽚。
这⾥出现了chrome://这样的地址,跟和file://协议类似,chrome://也是⽤来定位资源的。格
式是:
chrome://组件名称/⽬录路径/⽂件名
需要注意的是,这⾥的组件名称可以跟em:name属性中所指定的名称(⽤于显⽰的)不⼀样
的,并且这个组件名称需要在st⾥定义。
这些信息在安装完组件之后能够在firefox的组件管理列表显⽰,如下图:
第14⾏⽐较重要,⽤于标识当前组件是⽤于哪个平台的(firefox/Thunderbird/SeaMonkey
等),如果开发的组件是给firefox⽤的,则id={ec8030f7-c20a-464f-9b0e-13a3a9e97384}。
第15、16⾏⽤于指定当前组件适⽤于firefox的哪些版本,因为firefox从1.5到3.0有些地⽅有所改
变,所以如果你不知道⾃⼰的组件在哪些版本下可运⾏的话,最好先测试⼀下,然后准确地指
定⼀个范围值,上例的值表⽰该组件适⽤于firefox1.5到3.0及3.0所有修订版。版本号码不能⾃⼰
随便造,这⾥有⼀个完整的列表:
/en-US/firefox/pages/appversions
3、认识st
这是资源列表⽂件,内容如下:
1content uusharedemo1 chrome/content/
2locale uusharedemo1 en-US chrome/locale/en-US/
3skin uusharedemo1 classic/1.0 chrome/skin/
4overlay chrome://browser/content/
chrome://uusharedemo1/content/
5style chrome://global/content/
chrome://uusharedemo1/skin/
前3⾏每⼀⾏表⽰⼀种类型的资源的地址,格式是:
资源名称
其中组件名称是⾃定义的,只要跟其他组件名不⼀样并且使⽤全⼩写字母即可。定义好之后,
就可以使⽤chrome://地址来定位资源了。例如,假设当前开发⽂件路径
是“c:/dev/uusharedemo1/”,那么:
chrome://uusharedemo1/content/ 实际指的是
c:/dev/uusharedemo1/chrome/content/ 这个⽂件。
第4⾏及第5⾏表⽰将当前组件的界⾯结合到系统本⾝的界⾯,因为当前组件会修改firefox本⾝的
⼯具栏及右键菜单,所以需要加上这两⾏。
4、default/、skin/、locale/
这⼏个⽬录下的⽂件⽐较简单,下载本⽂的源代码查看⼀般能明⽩其中的意思,为了节约篇幅
这⾥就不讲解了,等以后我写后续的章节时再详细介绍。
5、认识 content/
Firefox的所有窗⼝都是使⽤xul⽂件定义的,例如在地址栏输⼊:
chrome://browser/content/
(这个地址对应的源⽂件是:“C:Program FilesMozilla
/content/”
就可以看到firefox的“关于”对话框,在这个例⼦⾥我们⽤定义⼀个⼯具栏按
钮、右键菜单以及⼀个⼯具菜单项,源代码如下:
1. 1
2. 2
3. 3
4. 4
5. 5
7. 7
8. 8
9. 9 10. 10 xmlns="/keymaster/gatekeeper/"> 11. 11 12. 12 13. 13 14. 14 15. 15 src="chrome://uusharedemo1/locale/ties"/> 16. 16 17. 17 18. 18 19. 19
复制代码
menupopup 段定义了⼀个位于“⼯具”菜单栏下的项⽬,其中id是菜单项的名称,我们⾃⼰添加
的菜单项、按钮等结合到firefox本⾝的控件的id必须惟 ⼀;label属性是菜单项显⽰的⽂本,这
⾥因为引⽤了语⾔⽂件,所以真正显⽰的⽂本内容位于语⾔⽂件()⾥标有
这⾏定义的后⾯;oncommand属性定义菜单被点击后执⾏ ⾥⾯
的那⼀个⽅法过程。
popup 段定义了⼀个右键菜单项,accesskey为菜单项的快捷键(即带下划线的字
母),insertafter为新菜单项所出现的位置位于id为 “context-stop”(即“停⽌”)这项的后⾯,如
何得知某⼀项菜单项的id呢?出动Dom Inspector 吧。包括firefox⼯具栏上⾯的所有按钮的id,
都可以⽤Dom Inspector查看得知。
toolbarpalette段定义了⼀个⼯具栏按钮,其中tooltiptext为⿏标停留在这个新按钮上⾯时所显⽰
的提⽰⽂本。
上⾯⼀段代码并没有定义⾃⼰的窗⼝,我会在下⼀章再讲解。
6、认识content/
⽂件是专门为上⾯的服务的,(怎么知道的呢?在
⽂件的第13⾏有引⽤啊)。在 xul⽂件⾥可以看到菜单被点击之后会调⽤⼀个名
叫“onMenuItemCommand”的⽅法过程,⽽⼯具栏按钮被点击之后会调⽤
“onToolbarButtonCommand”⽅法过程。我们想在他们被点击之后做什么事情呢?为了演⽰如何
在js⾥编写调⽤firefox对象的代 码,这⾥模仿uushare书签的功能,让菜单和按钮点击之后调⽤
⽹站上的书签服务的添加书签的接⼝,接⼝是这样的:
/bookmark/save?
v=1&client=ff10&noui=yes&jump=close&url=XXXX&title=YYYY&content=ZZZZ
其中XXXX为待收藏的⽹页地址,YYYY是⽹页的标题,ZZZZ是⽹页中选中的⽂本。所以我们
需要在js⾥获取当前⽹页的URL、Title以及被选中的⽂本。
下⾯是例⼦中的代码:
1. 1var uusharedemo1 = {
2. 2 onLoad: function() {
3. 3 // initialization code
4. 4 lized = true;
5. 5 s = mentById("uusharedemo1-strings");
6. 6 mentById("contentAreaContextMenu")
7. 7 .addEventListener("popupshowing", function(e) { ntextMenu(e); },
false);
8. 8 },
9. 9
10. 10 showContextMenu: function(event) {
11. 11 // 根据上下⽂决定显⽰/隐藏右键菜单项
12. 12 // see [url]/Adding_items_to_menus[/url]
13. 13 mentById("context-uusharedemo1").hidden =
e;
14. 14 },
15. 15
16. 16 onMenuItemCommand: function(e) {
17. 17 ushareBookmark();
18. 18 },
19. 19
20. 20 addToUushareBookmark: function() {
21. 21 //这⾥调⽤ 书签API的接⼝
22. 22 var location, title, desc;
23. 23 var browser = wser();
24. 24 var webNav = igation;
25. 25
26. 26 if (tURI)
27. 27 location = ;
28. 28 else
29. 29 location = ;
30. 30
31. 31 if () {
32. 32 title = ;
33. 33 }
34. 34 else {
35. 35 title = location;
36. 36 }
37. 37
38. 38 var focusedWindow = dWindow;
39. 39 var winWrapper = new XPCNativeWrapper(focusedWindow, 'getSelection()');
40. 40 desc = ection().toString();
41. 41
42. 42 var serverUrl = "/bookmark/save?v=1&client=ff10";
43. 43 var saveUrl = serverUrl + "&noui=yes&jump=close&url=" +
encodeURIComponent(location) + "&title=" + encodeURIComponent(title) + "&content="
+ encodeURIComponent(desc);
44. 44 (saveUrl, 'uusharev1',
'location=yes,links=no,scrollbars=yes,toolbar=no,width=550,height=550');
45. 45 },
46. 46
47. 47 onToolbarButtonCommand: function(e) {
48. 48 ushareBookmark();
49. 49 }
50. 50
51. 51};
52. ntListener("load", function(e) { (e); }, false);
复制代码
相信对于熟悉js的朋友上⾯的代码并不难。⼤部分代码跟在普通html页⾯⾥写的差不多,只是
firefox提供了内置的⼀些对象需要翻查官⽅的⽂档才能知道它的作⽤及其成员。
7、打包、测试
⾄ 此我们的扩展组件就编写完毕了,下⾯开始做安装测试。⾸先⽤压缩⼯具将、
st以及chrome⽬录打包 成⼀个zip格式的压缩包,要注意的是这2个⽂件及
chrome⽬录必须在压缩包的顶层⽬录,否则安装会失败。然后将压缩包⽂件的扩展名更改了
“xpi”,并把这个⽂件拖到firefox窗⼝当中,firefox应该会提⽰安装并要求重启,重启完之后若⽆
问题即可看到我们制作的“⼯具”栏菜单 项、右键菜单等。如果组件的源代码有错误,例如xul⽂
8、⼯具栏按钮怎么看不到?如何解决
刚 才我们测试的时候会发现虽然右键菜单、“⼯具”菜单项都显⽰出来了,不过怎么⼯具栏上⾯的
按钮没有显⽰呢?估计这是很多扩展组件开发者都会遇到的共同问 题,原来firefox将⾮系统默
认的按钮都放到按钮箱⾥,如果想显⽰这些按钮,需要对着⼯具栏右键⿏标,选择“定制”,然后
将需要的按钮拖出来才⾏。这 个特性某种程度上可以给⽤户最⼤的选择权,不过另⼀⽅⾯这特
性给初级的⽤户带来困惑。所以我们最好能将⼯具栏按钮⾃动显⽰出来。下⾯是⽬前“民间”常⽤
的 ⽅法:
⼤致思路跟我们平时修改windows注册表类似。firefox⼯具栏上⾯的按钮布局被储存在firefox设
置表中 navigator-toolbox/nav-bar/currentset 键下⾯,键的值为当前各个⼯具栏按钮id的以逗号
分隔⽽连在⼀起的字符串。我们必须读出这个字符串,并把⾃⼰的按钮的id(本⽰例中按钮id为
uusharedemo1-toolbar-button,见xul⽂件)掺和在其中,再写⼊新的字符串到设置表。
那么在什么执⾏此连串操作呢? 很明显不能在每次firefox启动时都执⾏,因为这样会耗时并影
响firefox的启动速度,“民间”的⽅法是先在设置表中检查某个⾃定义的键(例如
edemo1)的值,如果这个值为空的或者不等于当前组件的版本,则执⾏上述
的连串操作,并写⼊当前组件的版本号。所 以完整的过程是:
读取某个⾃定义键(edemo1)的值;
如果键的值为空或者不等于当前组件版本号,则下⼀步,否则退出过程;
读取navigator-toolbox/nav-bar/currentset键的字符串;
寻找⾃⼰按钮的id是否在字符串当中,不存在则将id连接到地址栏id(urlbar-container)的前
⾯;
将新字符串写⼊键;
写当前组件的版本号到⾃定义键。
下⾯是上⾯过程的js代码:
1. 1var uusharedemo1 = {
2. 2 strVersion : '1.0',
3. 3
4. 4 getUBKCharPref: function(strPrefName) {
5. 5 nager = s["@/preferences-
service;1"].getService(fBranch);
6. 6 try {
7. 7 return(rPref("edemo1." +
strPrefName));
8. 8 } catch(e) {
9. 9 return(false);
10. 10 }
11. 11 },
12. 12
13. 13 setUBKCharPref: function(strPrefName, strValue) {
14. 14 nager = s["@/preferences-
service;1"].getService(fBranch);
15. 15 rPref("edemo1." + strPrefName,
strValue);
16. 16 },
17. 17
18. 18 onLoadEvent: function()
19. 19 {
20. 20 //first load
21. 21 var strPrefVersion = CharPref('version');
22. 22
23. 23 if(strPrefVersion != sion) {
24. 24
25. 25 var bolWongPostButtonExists = mentById('uusharedemo1-
toolbar-button');
26. 26
复制代码
为了简单起见,这段代码并没有整合到⽰例的压缩包当中,如果想查看整合后的结果,可以参
考uushare书签组件的源
发布评论