Firefox的插件也曾经研究过,对于XUL感觉也有点痛苦,当初想研究它是为了给自己的博客增加插件,可以方便的让自己在找到好的资料的时候,可以直接通过插件发送到博客上。但最终我还是放弃了。没有什么理由。。。。
看到有人在贴一步一步学习的教程,又有点拾起了回忆,如果有可能,这个系列,我会在这个作者写的同时,也一点点的转载。
希望作者不要见怪,转载完了,我也贴点我在开发的时候遇到的问题之类。当然一切都到元旦后。。。
原文网址:http://www.cnblogs.com/uubox/archive/2008/12/25/1361278.html
作者:DOMS
内容:Firefox提供开放、强大且灵活的扩展机制,因此衍生出了大量功能丰富的扩展组件,这些扩展组件甚至可以说是某些用户爱上firefox的主要因素。
比较可惜的是firefox扩展开发的中文资料相对比较少,因此在这里我会从简单到深入介绍开发firefox扩展的一些技术及常遇到的问题。
本文主要内容:
- 制作一个包含工具栏按钮及右键菜单的简单组件
- 解决扩展安装后工具栏按钮却不显示的问题
因为这个组件不需要设计交互窗口,因此开发过程相对简单,下面是这个组件运行的效果图:
在开始之前先介绍一下几个网址:
- https://developer.mozilla.org/en/Extensions 官方开发网,类似MSDN,是开发时的主要参考资料;
- https://developer.mozilla.org/en/Building_an_Extension,官方的一个基础教程,笔者通过此篇文章而入门;
- http://ted.mielczarek.org/code/mozilla/extensionwiz/,一个在线的创建扩展的向导,如果你想立即创建一个属于自己的扩展,可以试试用这个向导制作一个雏形,然后再自己慢慢添砖添瓦。本文所讲的例子也是用这个向导制作出来的。
另外还有本文例子的源代码:http://www.uushare.com/user/csprogram/file/1139797。
好了,下面就开始讲解本文的示例组件:uusharedemo1的制作过程。
1、了解扩展组件的构成
Firefox 扩展组件是以一个xpi文件的形式发布的,而xpi文件实际上是zip压缩包文件,我们只要把Firefox扩展组件下载回来(用右键另存为即可下载), 把文件扩展名改为zip即可解压并看到所有的源代码,有时可能还会在压缩包里面看到jar格式文件,这个也是zip压缩包,改名解压即可看到源代码。因此 多下载优秀的扩展回来慢慢研究其源代码也不失为一种快速的学习方法。
一般xpi文件内部有(但不一定都有)如下目录及文件:
其中 uusharedemo1 为本文示例组件的名称,可以看到压缩包首层有:install.rdf及chrome.manifest两个文件以及一个chrome目录。
- install.rdf:是该组件的安装信息,例如组件的名称、版本、作者等信息,XML格式;
- chrome.manifest:是用于描述该组件由哪些文件组成的,即一个资源列表;
在chrome目录里就是该组件的主要内容了,一般有:content、defaults、locale、skin等目录,它们的作用如下:
- content:这个是最主要的目录,包含组件的界面定义以及程序代码,其中xul文件是界面定义文件,类似SilverLight的XAML文件,同样是XML格式;js文件是组件功能实现的代码,全部用javascript实现的。
- skin:存放css样式表文件及资源位图,css是用来控制组件界面外观的。
- locale:如果想让组件能够在不同语言的firefox里显示正确的文本,则将不用的语言列表放到此目录,firefox会自动加载合适的语言文件。这点有些类似Win32PE可执行程序中的资源文件。
- defaults:存放一些默认值,一般不要此目录也可以;
我 们可以把Firefox程序认为是一个平台,而我们看到firefox浏览器外观本身也是用跟扩展组件类似的结构搭建起来的,感兴趣的朋友可以解压 “C:\Program Files\Mozilla Firefox\chrome”目录下的jar文件观摩一下firefox浏览器外观的源代码,所以我们制作的扩展组件也会成为firefox浏览器自身 的一部分,可以实现非常强大而灵活的功能(例如Firebug这个神奇的组件)。相对来说,开发ie扩展程序显得困难多了,除非只是想做工具栏按钮和右键 菜单(用注册表即搞定),否则需要使用VC,VB,Delphi这些工具开发COM组件才能完成。
如果想了解firefox浏览器本身各个元素的定义,但又不想阅读chrome目录下的脚本代码,可以安装 DOM Inspector 这个组件,这个组件有如开发web时的FireBug,是开发具有界面的扩展组件时的神兵利器!
提醒:自己编写的组件代码文件最好用“UTF-8”格式保存,一般可以减少很多麻烦的问题。
2、认识install.rdf
install.rdf包含组件安装信息,本文例子中它的内容如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:em="http://www.mozilla.org/2004/em-rdf#">
- <Description about="urn:mozilla:install-manifest">
- <em:id>uusharedemo1@uushare.com</em:id>
- <em:name>Uushare书签(示例)</em:name>
- <em:version>1.0</em:version>
- <em:creator>KwanhongYoung</em:creator>
- <em:description>这是一个模仿Uushare书签插件的示例</em:description>
- <em:homepageURL>http://www.uushare.com</em:homepageURL>
- <em:iconURL>chrome://uusharedemo1/content/uusharedemo1.png</em:iconURL>
- <em:targetApplication>
- <Description>
- <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!-- firefox -->
- <em:minVersion>1.5</em:minVersion>
- <em:maxVersion>3.0.*</em:maxVersion>
- </Description>
- </em:targetApplication>
- </Description>
- </RDF>
文件里有很多是固定的写法,这里就不一一讲解了。
第 5行的id是组件的一个标识符号,类似Windows里COM组件的CLSID,用于区别其他组件。可以使用 xxx@yyy.zzz 这样类似email地址的格式组成,而这个“email地址”不要求是真实或有效的。也可以用的GUID来标识,例如“{c45c406e- ab73-11d8-be73-000a95be3b12}”是WebDevelop组件的标识符。
name、version、creator、description、homepageURL分别是组件的名称、版本、创建者的姓名、组件描述、组件官方网站地址。
iconURL用于显示组件的图标,格式一般用32x32pixel的png图片。
这里出现了chrome://这样的地址,跟http://和file://协议类似,chrome://也是用来定位资源的。格式是:
chrome://组件名称/目录路径/文件名
需要注意的是,这里的组件名称可以跟em:name属性中所指定的名称(用于显示的)不一样的,并且这个组件名称需要在chrome.manifest里定义。
这些信息在安装完组件之后能够在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所有修订版。版本号码不能 自己随便造,这里有一个完整的列表:
https://addons.mozilla.org/en-US/firefox/pages/appversions
3、认识chrome.manifest
这是资源列表文件,内容如下:
- content uusharedemo1 chrome/content/
- locale uusharedemo1 en-US chrome/locale/en-US/
- skin uusharedemo1 classic/1.0 chrome/skin/
- overlay chrome://browser/content/browser.xul chrome://uusharedemo1/content/firefoxOverlay.xul
- style chrome://global/content/customizeToolbar.xul chrome://uusharedemo1/skin/overlay.css
前3行每一行表示一种类型的资源的地址,格式是:
资源名称<Tab>组件名称<Tab>参数<Tab>目录路径
其中组件名称是自定义的,只要跟其他组件名不一样并且使用全小写字母即可。定义好之后,就可以使用chrome://地址来定位资源了。例如,假设当前开发文件路径是“c:/dev/uusharedemo1/”,那么:
chrome://uusharedemo1/content/uusharedemo1.png 实际指的是 c:/dev/uusharedemo1/chrome/content/uusharedemo1.png 这个文件。
第4行及第5行表示将当前组件的界面结合到系统本身的界面,因为当前组件会修改firefox本身的工具栏及右键菜单,所以需要加上这两行。
4、default/、skin/、locale/
这几个目录下的文件比较简单,下载本文的源代码查看一般能明白其中的意思,为了节约篇幅这里就不讲解了,等以后我写后续的章节时再详细介绍。
5、认识 content/firefoxOverlay.xul
Firefox的所有窗口都是使用xul文件定义的,例如在地址栏输入:
chrome://browser/content/aboutDialog.xul
(这个地址对应的源文件是:“C:\Program Files\Mozilla Firefox\chrome\browser.jar/content/aboutDialog.xul”
就可以看到firefox的“关于”对话框,在这个例子里我们用firefoxOverlay.xul定义一个工具栏按钮、右键菜单以及一个工具菜单项,源代码如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <!-- 下面一行指明当前窗口控件所引用的样式表文件 -->
- <?xml-stylesheet href="chrome://uusharedemo1/skin/overlay.css" type="text/css"?>
- <!-- 这行指明当前窗口控件所使用的语言文件,注意这里不需指定语言的名称(即zh-CN或en-US等),只需指明文件名即可
- 有了语言文件之后,所有控件的文本(即text/label等属性)都统一写到语言文件里。 -->
- <!DOCTYPE overlay SYSTEM "chrome://uusharedemo1/locale/uusharedemo1.dtd">
- <overlay id="uusharedemo1-overlay"
- xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
- <!-- 这行用于指定当前窗口控件的事件(如单击事件)触发后所执行的代码所在的文件 -->
- <script src="overlay.js"/>
- <stringbundleset id="stringbundleset">
- <stringbundle id="uusharedemo1-strings" src="chrome://uusharedemo1/locale/uusharedemo1.properties"/>
- </stringbundleset>
- <menupopup id="menu_ToolsPopup">
- <menuitem id="uusharedemo1-hello" label="&uusharedemo1.label;"
- oncommand="uusharedemo1.onMenuItemCommand(event);"/>
- </menupopup>
- <popup id="contentAreaContextMenu">
- <menuitem id="context-uusharedemo1" label="&uusharedemo1Context.label;"
- accesskey="&uusharedemo1Context.accesskey;"
- insertafter="context-stop"
- oncommand="uusharedemo1.onMenuItemCommand(event)"/>
- </popup>
- <toolbarpalette id="BrowserToolbarPalette">
- <toolbarbutton id="uusharedemo1-toolbar-button"
- label="&uusharedemo1Toolbar.label;"
- tooltiptext="&uusharedemo1Toolbar.tooltip;"
- oncommand="uusharedemo1.onToolbarButtonCommand()"
- class="toolbarbutton-1 chromeclass-toolbar-additional"/>
- </toolbarpalette>
- </overlay>
menupopup 段定义了一个位于“工具”菜单栏下的项目,其中id是菜单项的名称,我们自己添加的菜单项、按钮等结合到firefox本身的控件的id必须惟 一;label属性是菜单项显示的文本,这里因为引用了语言文件,所以真正显示的文本内容位于语言文件(uusharedemo1.dtd)里标有 uusharedemo1.label 这行定义的后面;oncommand属性定义菜单被点击后执行 overlay.js 里面的那一个方法过程。
popup 段定义了一个右键菜单项,accesskey为菜单项的快捷键(即带下划线的字母),insertafter为新菜单项所出现的位置位于id为 “context-stop”(即“停止”)这项的后面,如何得知某一项菜单项的id呢?出动Dom Inspector 吧。包括firefox工具栏上面的所有按钮的id,都可以用Dom Inspector查看得知。
toolbarpalette段定义了一个工具栏按钮,其中tooltiptext为鼠标停留在这个新按钮上面时所显示的提示文本。
上面一段代码并没有定义自己的窗口,我会在下一章再讲解。
6、认识content/overlay.js
overlay.js 文件是专门为上面的firefoxOverlay.xul服务的,(怎么知道的呢?在firefoxOverlay.xul文件的第13行有引用啊)。在 xul文件里可以看到菜单被点击之后会调用一个名叫“onMenuItemCommand”的方法过程,而工具栏按钮被点击之后会调用 “onToolbarButtonCommand”方法过程。我们想在他们被点击之后做什么事情呢?为了演示如何在js里编写调用firefox对象的代 码,这里模仿uushare书签的功能,让菜单和按钮点击之后调用Uushare.com网站上的书签服务的添加书签的接口,接口是这样的:
http://www.uushare.com/bookmark/save?v=1&client=ff10&noui=yes&jump=close&url=XXXX&title=YYYY&content=ZZZZ
其中XXXX为待收藏的网页地址,YYYY是网页的标题,ZZZZ是网页中选中的文本。所以我们需要在js里获取当前网页的URL、Title以及被选中的文本。
下面是例子中的代码:
- var uusharedemo1 = {
- onLoad: function() {
- // initialization code
- this.initialized = true;
- this.strings = document.getElementById("uusharedemo1-strings");
- document.getElementById("contentAreaContextMenu")
- .addEventListener("popupshowing", function(e) { this.showContextMenu(e); }, false);
- },
- showContextMenu: function(event) {
- // 根据上下文决定显示/隐藏右键菜单项
- // see http://kb.mozillazine.org/Adding_items_to_menus
- document.getElementById("context-uusharedemo1").hidden = gContextMenu.onImage;
- },
- onMenuItemCommand: function(e) {
- uusharedemo1.addToUushareBookmark();
- },
- addToUushareBookmark: function() {
- //这里调用 uushare.com 书签API的接口
- var location, title, desc;
- var browser = window.getBrowser();
- var webNav = browser.webNavigation;
- if (webNav.currentURI)
- location = webNav.currentURI.spec;
- else
- location = gURLBar.value;
- if (webNav.document.title) {
- title = webNav.document.title;
- }
- else {
- title = location;
- }
- var focusedWindow = document.commandDispatcher.focusedWindow;
- var winWrapper = new XPCNativeWrapper(focusedWindow, 'getSelection()');
- desc = winWrapper.getSelection().toString();
- var serverUrl = "http://www.uushare.com/bookmark/save?v=1&client=ff10";
- var saveUrl = serverUrl + "&noui=yes&jump=close&url=" + encodeURIComponent(location) + "&title=" + encodeURIComponent(title) + "&content=" + encodeURIComponent(desc);
- window.open(saveUrl, 'uusharev1', 'location=yes,links=no,scrollbars=yes,toolbar=no,width=550,height=550');
- },
- onToolbarButtonCommand: function(e) {
- uusharedemo1.addToUushareBookmark();
- }
- };
- window.addEventListener("load", function(e) { uusharedemo1.onLoad(e); }, false);
相信对于熟悉js的朋友上面的代码并不难。大部分代码跟在普通html页面里写的差不多,只是firefox提供了内置的一些对象需要翻查官方的文档才能知道它的作用及其成员。
7、打包、测试
至 此我们的扩展组件就编写完毕了,下面开始做安装测试。首先用压缩工具将install.rdf、chrome.manifest以及chrome目录打包 成一个zip格式的压缩包,要注意的是这2个文件及chrome目录必须在压缩包的顶层目录,否则安装会失败。然后将压缩包文件的扩展名更改了 “xpi”,并把这个文件拖到firefox窗口当中,firefox应该会提示安装并要求重启,重启完之后若无问题即可看到我们制作的“工具”栏菜单 项、右键菜单等。如果组件的源代码有错误,例如xul文件或js脚本文件有错误,一般什么预期的效果都看不到,这时需要重复卸载->修复错误代码 ->重新打包->安装这几个步骤。
开发firefox扩展组件最烦人的事情可能就是调试问题了,幸好firefox提供了一些工具,例如 Extension Developer's Extension 等可以减少一些麻烦。
8、工具栏按钮怎么看不到?如何解决
刚 才我们测试的时候会发现虽然右键菜单、“工具”菜单项都显示出来了,不过怎么工具栏上面的按钮没有显示呢?估计这是很多扩展组件开发者都会遇到的共同问 题,原来firefox将非系统默认的按钮都放到按钮箱里,如果想显示这些按钮,需要对着工具栏右键鼠标,选择“定制”,然后将需要的按钮拖出来才行。这 个特性某种程度上可以给用户最大的选择权,不过另一方面这特性给初级的用户带来困惑。所以我们最好能将工具栏按钮自动显示出来。下面是目前“民间”常用的 方法:
大致思路跟我们平时修改windows注册表类似。firefox工具栏上面的按钮布局被储存在firefox设置表中 navigator-toolbox/nav-bar/currentset 键下面,键的值为当前各个工具栏按钮id的以逗号分隔而连在一起的字符串。我们必须读出这个字符串,并把自己的按钮的id(本示例中按钮id为uusharedemo1-toolbar-button,见xul文件)掺和在其中,再写入新的字符串到设置表。
那 么在什么执行此连串操作呢?很明显不能在每次firefox启动时都执行,因为这样会耗时并影响firefox的启动速度,“民间”的方法是先在设置表中 检查某个自定义的键(例如extensions.uusharedemo1)的值,如果这个值为空的或者不等于当前组件的版本,则执行上述的连串操作,并 写入当前组件的版本号。所以完整的过程是:
- 读取某个自定义键(extensions.uusharedemo1)的值;
- 如果键的值为空或者不等于当前组件版本号,则下一步,否则退出过程;
- 读取navigator-toolbox/nav-bar/currentset键的字符串;
- 寻找自己按钮的id是否在字符串当中,不存在则将id连接到地址栏id(urlbar-container)的前面;
- 将新字符串写入键;
- 写当前组件的版本号到自定义键。
下面是上面过程的js代码:
- var uusharedemo1 = {
- strVersion : '1.0',
- getUBKCharPref: function(strPrefName) {
- this.prefManager = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
- try {
- return(this.prefManager.getCharPref("extensions.uusharedemo1." + strPrefName));
- } catch(e) {
- return(false);
- }
- },
- setUBKCharPref: function(strPrefName, strValue) {
- this.prefManager = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
- this.prefManager.setCharPref("extensions.uusharedemo1." + strPrefName, strValue);
- },
- onLoadEvent: function()
- {
- //first load
- var strPrefVersion = this.getUBKCharPref('version');
- if(strPrefVersion != this.strVersion) {
- var bolWongPostButtonExists = document.getElementById('uusharedemo1-toolbar-button');
- if(!bolWongPostButtonExists) {
- var objToolbox = document.getElementById("navigator-toolbox");
- var strChild = '';
- var strSet = '';
- var objNavBar = false;
- if(objToolbox) {
- for (var i = 0; i < objToolbox.childNodes.length; ++i) {
- if(objToolbox.childNodes[i].id == "nav-bar" && objToolbox.childNodes[i].getAttribute("customizable") == "true") {
- objNavBar = objToolbox.childNodes[i];
- break;
- }
- }
- }
- if(objNavBar) {
- for (var i = 0; i < objNavBar.childNodes.length; i++) {
- if(objNavBar.childNodes[i].id == "urlbar-container") {
- if(!bolWongPostButtonExists) {
- strSet += "uusharedemo1-toolbar-button,"
- }
- }
- strSet += objNavBar.childNodes[i].id + ",";
- }
- strSet = strSet.substring(0, strSet.length-1);
- objNavBar.currentSet = strSet;
- objNavBar.setAttribute("currentset", strSet);
- objNavBar.ownerDocument.persist(objNavBar.id, "currentset");
- BrowserToolboxCustomizeDone(true);
- }
- strSet = strSet.substring(0, strSet.length-1);
- toolbar.currentSet = strSet;
- if(toolbar.setAttribute) {
- toolbar.setAttribute("currentset", strSet);
- }
- toolboxDocument.persist(toolbar.id, "currentset");
- BrowserToolboxCustomizeDone(true);
- }
- this.setUBKCharPref('version', this.strVersion);
- }//end if first load
- }
- }
- window.addEventListener("load", function() { uusharedemo1.onLoadEvent(); }, false);
为了简单起见,这段代码并没有整合到示例的压缩包当中,如果想查看整合后的结果,可以参考uushare书签组件的源代码:http://www.uushare.com/help/bookmark/toolbar_firefox。
本文完。