Hello World
——開(kāi)發(fā)你的第一個(gè)Firefox擴展
劉文懋
如果有人問(wèn)我為什么用Firefox[1],首先毫無(wú)疑問(wèn)的是它代表了開(kāi)放和自由的精神,其次嘛,我會(huì )說(shuō)是它的可擴展性。
Firefox最激動(dòng)人心的特點(diǎn)就是它提供了開(kāi)放的接口,你可以使用這些接口來(lái)做各種應用,完成各種各樣的功能。你應該不曾給IE添點(diǎn)什么吧,微軟不見(jiàn)得這么大方能告訴你什么有用的東西。他最慷慨的事就是給你一個(gè)COM組件,記得我對IE的最高級的應用,就是在一次網(wǎng)絡(luò )大作業(yè)中,把整個(gè)IE嵌在我的程序里,僅此而已。而Firefox則不同了,當你看到了Fireftp就知道我在說(shuō)什么了。

請合上你的下巴,這沒(méi)有什么值得驚訝的。這就是Firefox,中間那個(gè)cool的ftp工具只是它的一個(gè)擴展(Extesion)而已。
想做一個(gè)這樣的東東嗎?我不會(huì )告訴你到底有多難,因為這不是我開(kāi)發(fā)的,有興趣的話(huà)你可以自己試一試,網(wǎng)址是http://fireftp.mozdev.org/ 。
類(lèi)似的擴展還有很多,你可以到mozilla的擴展庫去看看,據官方統計,到
以前我在99%的時(shí)間內使用Firefox,另外1%的時(shí)間使用IE瀏覽一些不遵從W
好了,說(shuō)了這么多Firefox擴展的好處,是不是有點(diǎn)心動(dòng)了呢?那么就用一個(gè)Hello World來(lái)開(kāi)始我們的第一個(gè)Firefox擴展吧。別說(shuō)我太不in了,畢竟hello world總是最容易讓我們使所有編程語(yǔ)言的開(kāi)場(chǎng)白,不是嗎?
Here we go!
從功能來(lái)說(shuō),Firefox擴展應該是用戶(hù)和瀏覽器內核交互的一種體系結構。擴展可以滿(mǎn)足用戶(hù)一些特定的需要,實(shí)現特定的功能。開(kāi)發(fā)者可以使用內核提供的一些用戶(hù)接口,編寫(xiě)自己的實(shí)現代碼,完成自己設想的功能。
從開(kāi)發(fā)的角度來(lái)講,Firefox的擴展是一個(gè)文件目錄的集合,它們按照一定格式和規范編寫(xiě)和排布的。最終,發(fā)布給用戶(hù)的是一個(gè)xpi包。別感到奇怪,這個(gè)xpi文件和那些jar[3]的文件一樣,都是zip格式的壓縮文件。所以這下你懂了吧,把你做完的文件按照zip格式壓縮一下就成了我們的擴展包了。
那么究竟具體的xpi文件中是什么情景的?解開(kāi)我提供的helloworld.xpi文件包,你會(huì )發(fā)現文件目錄的排布如下所示。其中樹(shù)型結構的葉子部分都是文件,其他的中間結點(diǎn)都是目錄。
HelloWorld.xpi
│
│
├─chrome
│ ├─content
│ │ ├─ contents.rdf
│ │ ├─ helloworld-Overlay.xul
│ │ └─ hello.js
│ │
│ ├─locale
│ │ ├─ en-US
│ │ │ ├ contents.rdf
│ │ │ └ helloworld.dtd
│ │ │
│ │ └─ zh-CN
│ │ ├ contents.rdf
│ │ └ helloworld.dtd
│ │
│ │
│ └─skin
│ ├─ qq_small.png
│ ├─ qq_big.png
│ ├─ helloworld.css
│ └─ contents.rdf
│
├─ build.xml
├─ install.rdf
└─ chrome.manifest
通常,Firefox的擴展在根目錄中,會(huì )有install.rdf文件,這個(gè)文件說(shuō)明了擴展的基本信息,例如擴展的ID、作者、版本等信息。在Firefox1.5之前的版本,該文件還會(huì )包含擴展的文件分布信息,在Firefox1.5至后,這些信息都移到了chrome.manifest文件中。在調試的時(shí)候,build.xml可以幫助我們自動(dòng)打包,這個(gè)文件對那些在xpi文件中還含有jar文件的擴展特別有用。
根目錄下,總是會(huì )存在一個(gè)chrome目錄。chrome下面的格式就不一定了,但是基本都會(huì )含有content、locale和skin三個(gè)目錄。其中,
content目錄是用來(lái)存放擴展的程序文件和控件格式的資源文件;
locale目錄存放不同語(yǔ)言版本,用于擴展的本地化和國際化;
skin目錄存放圖片等資源文件。
擴展的格式并不一定要拘泥一格,但具體情況需要與install.rdf或chrome.manifest文件中的信息聯(lián)系。記住,無(wú)論如何,良好的習慣總是對你有好處的:請把相應的文件放到它們應該放的地方。
install.rdf是一個(gè)擴展的身份證。這么說(shuō)并不為過(guò),請看下面就是我們helloworld的install.rdf文件:
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:name>Hello World</em:name>
<em:id>{
<em:version>0.4</em:version>
<em:type>2</em:type>
<em:description>A test extension</em:description>
<em:creator>Marvel</em:creator>
<em:contributor>LiuWenmao</em:contributor>
<em:homepageURL>http://marvel.hit.edu.cn/</em:homepageURL>
<em:targetApplication>
<Description>
<em:id>{ec
<em:minVersion>0.9</em:minVersion>
<em:maxVersion>1.5</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>
可以看得出,這是一個(gè)xml格式的文件。根節點(diǎn)為RDF。命名空間為http://www.w3.org/1999/02/22-rdf-syntax-ns# ,前綴為em[4]。下面介紹每一個(gè)元素和屬性的意義:
<em:name>Hello World</em:name>
<em:id>{
<em:version>0.4</em:version>
<em:type>2</em:type>
<em:description>A test extension</em:description>
<em:creator>Marvel</em:creator>
<em:contributor>LiuWenmao</em:contributor>
<em:homepageURL>http://marvel.hit.edu.cn/</em:homepageURL>
em:name:擴展的名字,例如我的名字叫“劉文懋”,而我的擴展叫“Hello World”。J
em:id:擴展的ID號,每一個(gè)擴展都會(huì )有一個(gè)不同的GUID,就如同你的身份證號,用于區分你的擴展與其他人的擴展,所以很明顯,全球所有的Firefox插件的ID都不一樣。所以,當你寫(xiě)自己的擴展的時(shí)候,需要獲得一個(gè)全球唯一對GUID,Andy Hoskinson幫我們完成了這項工作,你可以登錄 http://www.hoskinson.net/webservices/guidgeneratorclient.aspx 來(lái)獲得一個(gè)GUID。點(diǎn)擊一下Generate GUID按鈕,很簡(jiǎn)單吧,但是這很重要,如果你不想惹麻煩的話(huà)!
Firefox1.5支持“User@Location”這種ID的格式。
請注意,當你在“擴展項”中查看擴展信息的時(shí)候,你能看到的只有擴展的name,ID你是看不到的,但是Firefox的確是依靠ID來(lái)工作的。正如我們在平時(shí)總是稱(chēng)呼各自的名字,但是等到登記信息的時(shí)候,完全是按照身份證號來(lái)區分的。Firefox也是一樣的。
em:version:顧名思義,這是擴展的版本號,沒(méi)什么多說(shuō)的。
em:type:這是類(lèi)型。Firefox的插件很強,它支持的不只擴展一種。例如:type=2時(shí)表示擴展(Extensions), type=4時(shí)表示主題(Themes),type= 8時(shí)表示地區(Locale),這和本地化有關(guān),type=16時(shí)表示插件(Plugin)。
em:description:擴展的描述,對擴展的簡(jiǎn)單說(shuō)明。
em:creator:擴展的創(chuàng )建者。
em:contributor:擴展的貢獻者??梢杂卸鄠€(gè)em:contributor,畢竟可以有很多貢獻者。
em:homepageURL:擴展的主頁(yè)。
以上幾項,除了ID之外,都會(huì )出現在Firefox的“工具”-〉“擴展”項的擴展列表的各項摘要中。
好,讓我們到下一部分:
<em:targetApplication>
<Description>
<em:id>{ec
<em:minVersion>0.9</em:minVersion>
<em:maxVersion>1.5</em:maxVersion>
</Description>
</em:targetApplication>
em:targetApplication:本擴展可以被使用的應用程序,它的字節點(diǎn)是對這個(gè)應用程序的說(shuō)明,我們的Hello world僅在Firefox下運行,所以只有一個(gè)em:targetApplication節點(diǎn),如果你認為你開(kāi)發(fā)的擴展可以在mozilla的其它應用程序中運行的話(huà),可以在這里多寫(xiě)幾個(gè)em:targetApplication。
em:id:em:targetApplication的ID號,正如Firefox的擴展有自己的ID號一樣,Mozilla的每一個(gè)應用程序也都有自己的ID號。例如Firefox、Thunderbird等等,他們都需要有自己的ID號來(lái)進(jìn)行標識。例如,本擴展使用的Firefox的ID號是{ec
em:minVersion:擴展支持的應用程序的最低版本號,舉個(gè)例子,Firefox1.5有很多的功能是FF
em:maxVersion:說(shuō)明同上,假設這個(gè)值為1.0+,那么你會(huì )發(fā)現Firefox1.5會(huì )自動(dòng)將其禁用,解決方法是你可以將這個(gè)值手動(dòng)改為1.5,前提是這個(gè)擴展可以在Firefox1.5下正常運行。
有一些支持Firefox舊版本的擴展還會(huì )有下列的東東:
<em:file>
<Description about="urn:mozilla:extension:file:xyzproxy">
<em:package>content/</em:package>
<em:skin>skin/</em:skin>
<em:locale>locale/zh-CN/</em:locale>
<em:locale>locale/zh-TW/</em:locale>
<em:locale>locale/en-US/</em:locale>
</Description>
</em:file>
這段東西是像Firefox說(shuō)明了擴展的目錄分布。Firefox1.5已經(jīng)不使用這部分了,對應的,將這部分的內容轉移到了chrome.manifest文件中,只有當chrome.manifest文件中沒(méi)有相應的目錄分布的時(shí)候,才會(huì )回來(lái)找。
如Firefox擴展的格式所說(shuō),擴展包是由content、skin、locale三部分組成的。這段就說(shuō)明了這三部分的分布情況,具體的對應內容參見(jiàn)下部分chrome.manifest。
需要注意的是,上面每一個(gè)節點(diǎn)中的值都是以“/”結尾的,如果漏掉了這個(gè)東西,Firefox就會(huì )找不到對應得目錄!
在分析chrome.manifest文件之前,我們必須理解Chrome這個(gè)概念。作為Firefox,它的底層是使用了高效并且不能被修改的運行時(shí)引擎(runtime engine),在此之上是比較“厚”的可讀可修改的解釋層。
Chrome代表了Firefox提供的所有用戶(hù)接口——XUL、CSS、JavaScript、圖像、 RDF、文本和HTML文檔。RDF和XUL是最重要的文檔。
從物理層面上說(shuō),Chrome是Firefox數據庫中的數據。Firefox需要獲得擴展的信息,所以它會(huì )在啟動(dòng)的時(shí)候,讀入RDF文件,完成對擴展的注冊,將擴展的信息存放到Firefox的內存數據庫中。所以一個(gè)擴展只有在Firefox的擴展搜索范圍中,并且被Firefox注冊了,它才能稱(chēng)為Chrome。
在邏輯層面上說(shuō),Chome是一組URL的集合。正如你可以使用http://www.google.com 來(lái)訪(fǎng)問(wèn)Google的網(wǎng)頁(yè)一樣,你可以chrome://URL 的方式來(lái)訪(fǎng)問(wèn)Chrome的資源。事實(shí)上,這是一種映射關(guān)系。例如你的電腦中根目錄下存放有一個(gè)擴展,其中有一個(gè)xul文件:/tests/content/package.xul,你可以在瀏覽器中的地址欄中輸入:file:///tests/content/ package.xul ,這樣你就可以查看該文件了,但是在Chrome中,如果你已經(jīng)將conten目錄注冊了,那么你同樣可以在地址欄中輸入chrome://test/content/package.xul,這樣就可以瀏覽這個(gè)文件了。所以從這個(gè)角度來(lái)說(shuō)Chrome可以看作一種Firefox自己定義的協(xié)議,不是嗎?
說(shuō)道Chrome,不得不說(shuō)一下Jar文件。Firefox支持將Chrome的內容全部放在一個(gè)zip格式的文件中,這個(gè)文件的后綴名為.jar。這個(gè)文件可以包括窗口內容、皮膚主題、本地化代碼,以及前三個(gè)的任意組合。
至于jar文件中內容的引用有所不同。例如有一個(gè)/test/hello.jar中還有一個(gè)helloworld.xul文件,那么該文件引用地址應該為:
jar:file:///tmp/example.jar!/example.txt。
請注意jar后面有一個(gè)感嘆號。特別注意的是jar文件的目錄分布。
例如正常沒(méi)有壓縮的目錄排布為:
test/content/…
test/locale/…
test/skin/…
但是如果要將其壓縮為jar文件,那么需要將其重新排列為:
/content/test
/locale /test
/skin /test
這種設計的一個(gè)合理解釋是Firefox可以在運行的時(shí)候更快地搜索該壓縮文件。
這個(gè)文件是Firefox1.5引入的,用于對一些擴展內容、結構的說(shuō)明和映射。我的Hello World的chrome.manifest文件內如如下:
content helloworld chrome/content/
locale helloworld en-US chrome/locale/en-US/
locale helloworld zh-CN chrome/locale/zh-CN/
skin helloworld classic/1.0 chrome/skin/
overlay chrome://browser/content/browser.xul chrome://helloworld/content/helloworld-Overlay.xul
style chrome://global/content/customizeToolbar.xul chrome://helloworld/skin/helloworld.css
先看前四行:
content helloworld chrome/content/
locale helloworld en-US chrome/locale/en-US/
locale helloworld zh-CN chrome/locale/zh-CN/
skin helloworld classic/1.0 chrome/skin/
根據上一節的敘述,這四行是為了讓Firefox在啟動(dòng)的時(shí)候,將本地的目錄注冊到Chrome的數據庫中。
我們的Hello world擴展包中包含的內容有content、locale、skin三個(gè)部分。其中locale包含了美式英語(yǔ)和簡(jiǎn)體中文的文件,所以一共有四項。
Content的格式為:
content Name Location
其中,
content:chrome包中的類(lèi)型,這里為content
Name:chrome包的名字
Location:chrome包文件的位置。注意最后的“/”,別忘了,否則擴展是無(wú)法加載的!
所以,第一行的意思就是:一個(gè)叫sample的chrome包, 我們可以從位置chrome/content/找到它的content文件。這里的路徑是相對于chrome.manifest文件的路徑而言的相對路徑。
類(lèi)似的,下面三句定義了locale和skin的位置。
現在,我們已經(jīng)將本地物理文件與Firefox邏輯上的Chrome的URL建立一個(gè)映射,例如:content-〉chrome/content/。我們的擴展有一個(gè)content.rdf文件,位置在/chrome/content下,那么我們就可以在瀏覽器的地址欄中輸入 “chrome://helloworld/content/content.rdf”來(lái)查看該文件,事實(shí)上,Firefox也是按照這個(gè)URL來(lái)尋找該文件的。
這四行可以與上一節的em:file對照一下,它們在Firefox中實(shí)現的功能是一致的。只不過(guò)這種寫(xiě)法是Firefox 1.5引入的。假如你的擴展包中沒(méi)有chrome.manifest而只有install.rdf文件,那么Firefox會(huì )解析install.rdf文件,之后,生成一個(gè)chrome.manifest文件。
接下來(lái)的兩行:
overlay chrome://browser/content/browser.xul chrome://helloworld/content/helloworld-Overlay.xul
style chrome://global/content/customizeToolbar.xul chrome://helloworld/skin/helloworld.css
這兩行也是向Firefox注冊,但這次注冊的是你需要重寫(xiě)控件的代碼文件(customizeToolbar.xul和helloworld-Overlay.xul)和控件的樣式文件(helloworld.css)。Firefox啟動(dòng)的時(shí)候會(huì )將helloworld-Overlay.xul 合并到browser.xul,從而實(shí)現自定義控件的加載。
這幾個(gè)文件的詳細內容我們在接下來(lái)的部分進(jìn)行討論。
添加控件接下來(lái),我們需要實(shí)現一些基本功能。首先添加一個(gè)菜單,用戶(hù)點(diǎn)擊之后,可以彈出一個(gè)“Hello World”的窗口。接下來(lái),我們熟悉一下其他的控件,例如狀態(tài)欄等。
Firefox的控件是由前臺的xml格式的文件xul和后臺的javascript腳本的js兩部分內容組成的。前臺的xul文件定義控件的外觀(guān)和觸發(fā)事件,后臺的js文件實(shí)現具體的事件,從而實(shí)現了表現和實(shí)現的分離。
首先,我們來(lái)實(shí)現菜單的菜單項。本例的效果是添加一個(gè)菜單項,如下圖的“Click Me!”項:

記得上一節我們在chrome.manifest文件中將browser.xul 和helloworld-Overlay.xul注冊了嗎?我們使用了overlay chrome://browser/content/browser.xul chrome://helloworld/content/helloworld-Overlay.xul,從而Firefox在加載默認瀏覽器文件browser.xul的同時(shí),會(huì )將helloworld-Overlay.xul也加載上去。
所以,我們需要再helloworld-Overlay.xul中加入自己的控件。好了,我寫(xiě)了一個(gè)簡(jiǎn)單的控件menu_Hello:
helloworld-Overlay.xul
<?xml version="1.0"?>
<overlay id="helloworldOverlay"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/x-javascript" src="chrome://helloworld/content/hello.js"/>
<menupopup id="menu_ToolsPopup">
<menuitem id="menu_Hello" label=" Click Me! "
accesskey="C" position="1" oncommand="onShowMenu(); "/>
</menupopup>
</overlay>
下面我們來(lái)分析一下各個(gè)部分。
首先,定義了本文件唯一的ID號為“helloworldOverlay”。
接著(zhù),定義了控件觸發(fā)事件的腳本文件為 chrome://helloworld/content/hello.js。
然后,我們開(kāi)始定義控件。首先要找到控件的父控件——至少Firefox要知道該把它放在哪里。這里我們找到了“工具”菜單menu_ToolsPopup。至于我們怎么知道“工具”菜單的ID是menu_ToolsPopup的,我推薦使用Firefox自帶的開(kāi)發(fā)工具Dom Inspector,至少我是這么知道它的ID的。

找到父控件之后,我們就可以自定義控件了:
<menuitem id="menu_Hello" label=" Click Me! " accesskey="C" position="1" oncommand="onShowMenu(); "/>
這句說(shuō)明,我們的控件ID為menu_Hello,顯示的文本為Click Me!,快捷鍵為C,位置在“工具”菜單的最上面,點(diǎn)擊后觸發(fā)的事件為onShowMenu()(onShowMenu事件在hello.js中定義,正如我們上面所說(shuō)的)。
好了,定義完控件的外部屬性,我們就需要處理它的觸發(fā)事件了。打開(kāi)chrome\content\ hello.js,輸入:
function onShowMenu()
{
window.alert("hello");
}
這樣我們就完成了菜單項的工作。
如果你現在就像看看效果,那么請轉到“部署Firefox擴展”部分;如果你還想看看其他的控件以及樣式表的使用,可以繼續。
接下來(lái),我們會(huì )添加一個(gè)工具欄按鈕。從這部分,我們可以知道如何使用工具欄控件,以及如何使用樣式。這部分的實(shí)際效果如下圖:

我們修改一下helloworld-Overlay.xul文件,添加一個(gè)叫tbarHello 的toolbarbutton,以及下拉菜單和菜單項。為了簡(jiǎn)單起見(jiàn),我去掉了上一節的菜單項的部分:
helloworld-Overlay.xul
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://helloworld/skin/helloworld.css" type="text/css"?>
<!DOCTYPE overlay SYSTEM "chrome://helloworld/locale/helloworld.dtd" >
<overlay id="helloworldOverlay"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/x-javascript" src="chrome://helloworld/content/hello.js"/>
<toolbox id="navigator-toolbox">
<toolbarpalette id="BrowserToolbarPalette" >
<toolbarbutton id="tbarHello" tooltiptext="Toolbar Test"
type="menu-button" label="Test"
oncommand=" onTbarHello();">
<menupopup id="hello-popup-list" onpopupshowing="onShowMenu();">
<menu id="hello-tools-menu" label=" Manage ">
<menupopup id="toolbutton-popup-tools">
<menuitem id="menuitem-add" label="Add"
tooltiptext=" Add your name"
oncommand="addName();"/>
<menuitem id="menuitem-remove" label=" Remove "
tooltiptext="Remove your name"
oncommand="removeName();"/>
</menupopup>
</menu>
<menuseparator />
<menuitem id="menuitem-state" label=" State "
tooltiptext=" Show your state "
oncommand="showState();"/>
</menupopup>
</toolbarbutton>
</toolbarpalette>
</toolbox>
</overlay>
下面我們來(lái)分析這段代碼:
1導入控件樣式:
<?xml-stylesheet href="chrome://helloworld/skin/helloworld.css" type="text/css"?>
我們需要對下面的控件進(jìn)行修飾,所以需要引入css樣式,這與HTTP的css類(lèi)似。上面這句說(shuō)明,我們可能會(huì )用到chrome://helloworld/skin/helloworld.css定義的控件樣式,事實(shí)上的確如此。
2 定義控件
<navigator-toolbox>……</navigator-toolbox>就是定義控件的代碼。首先我們找到navigator-toolbox節點(diǎn),就是Firefox的導航欄。然后在上面添加一個(gè)按鈕tbarHello,類(lèi)型為menu-button,,觸發(fā)事件為onTbarHello()。此外還包括了一個(gè)菜單、三個(gè)菜單項。具體的說(shuō)明這里就省略了,你可以使用Dom Inspector來(lái)查看各個(gè)控件的類(lèi)型和屬性值。
3 定義控件樣式
也許你會(huì )說(shuō),我只看到了你導入了樣式文件,但是具體它是怎么使用的呢?或者說(shuō)控件和它的樣式是怎么管聯(lián)起來(lái)的呢?
我們可以來(lái)看一看我們導入的CSS文件:
Helloworld.css
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
#tbarHello {
list-style-image: url("chrome://helloworld/skin/qq_big.png");
}
toolbar[iconsize="small"] #tbarHello {
list-style-image: url("chrome://helloworld/skin/qq_small.png");
}
我們可以看到,這個(gè)文件定義了一個(gè)tbarHello的list-style-image屬性[6]。list-style-image的一般語(yǔ)法是:list-style-image: url(…)|none, 這個(gè)屬性是用來(lái)顯示特定list-item的圖像的,即用后面url中的圖像來(lái)顯示該控件。這里我們使用的是chrome://helloworld/skin/qq_big.png。
但是僅僅有這句還是不夠的。Firefox提供了兩種圖標的顯示方法,一種是大圖標,另一種是小圖標。大圖標默認的大小為24x24,小圖標默認的大小為16x16。剛才我們定義的圖標是大圖標,所以我們需要定義小圖標,方法為toolbar[iconsize="small"] #tbarHello{}這三行。
這樣我們定義了toolbar的顯示圖像。當然還有其他的屬性可以定義,但是這里我們只定義了它的圖像。
4 實(shí)現控件功能
這部分盡管是最重要的,但是在本例這里不是重點(diǎn)。所以可以用最簡(jiǎn)單的方式實(shí)現xul文件中需要使用的onTbarHello、addName、removeName和showState函數。
我們的hello.js代碼如下:
Hello.js
function addName()
{
window.alert("Add name");
}
function removeName()
{
window.alert("Remove name");
}
function showState()
{
window.alert("Show state");
}
function onShowMenu()
{
window.dump("hello");
window.alert("hello");
}
function onTbarHello()
{
window.alert("hello");
}
至此,我們完成了控件的樣式的定義和使用。你可以加入更多的樣式,完成更復雜的功能,只要你有足夠的想象力。
好了,到目前為止,Firefox可以說(shuō)“Hello World”了。但問(wèn)題是世界上還有十三億中國人,那么顯然“Hello World”使他們認為Firefox是一個(gè)洋玩意兒,它們可能會(huì )給Firefox起一個(gè)名字叫“洋狐貍”[8]。更糟的是,這十三億人中很多人只認識中文,不懂得abc,那么只有英文的Firefox不會(huì )是他們的選擇。
在本節中,我們講述的是如何在Firefox中實(shí)現擴展的本地化。我們需要重寫(xiě)helloworld-Overlay.xul :
helloworld-Overlay.xul
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://helloworld/skin/helloworld.css" type="text/css"?>
<!DOCTYPE overlay SYSTEM "chrome://helloworld/locale/helloworld.dtd" >
<overlay id="helloworldOverlay"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="apKlication/x-javascript" src="chrome://helloworld/content/hello.js"/>
<menuKopup id="menu_ToolsPopup">
<menuitem id="menu_Hello" label="&btn_Hello.label;"
accesskey="&btn_Hello.accesskey;" position="1" oncommand="onShowMenu(); "/>
</menuKopup>
</overlay>
注意到和第一個(gè)版本有什么不同嗎?相信你已經(jīng)發(fā)現了menu_Hello的label已經(jīng)變成了&btn_Hello.label;,同樣的accesskey也變成了這種形式。
如果你學(xué)過(guò)HTML語(yǔ)法,你可能會(huì )想到這可能是轉義。例如,在HTML中“<”被轉義成為了“<”。
對,這也是一種轉義,但是略微不同的是,&btn_Hello.label;和&btn_Hello.accesskey;其實(shí)Firefox或者其他的標準都沒(méi)有定義它們的含義,這是有你自己來(lái)決定的。
你需要在你導入的dtd文件中進(jìn)行說(shuō)明,本例中,你需要在chrome://helloworld/locale/helloworld.dtd添加相應的定義。
也許到這一步你會(huì )問(wèn),這個(gè)helloworld.dtd文件是哪一個(gè)呢?到底是chrome\locale\en-US\ helloworld.dtd 還是 chrome\locale\zh-CN helloworld.dtd呢?
其實(shí),這是由Firefox決定的。如果你的Firefox的locale是en-US,那就是chrome\locale\en-US\ helloworld.dtd,如果你的Firefox的locale是zh-CN,那就是chrome\locale\ zh-CN \ helloworld.dtd。
你需要做的工作就是就是告訴Firefox你的en-US或zh-CN的locale文件夾在什么地方?,F在你要在chrome\locale\en-US和chrome\locale\zh-CN下分別建立一個(gè)contents.rdf文件。chrome\locale\en-US下的contents.rdf如下:
contents.rdf
<?xml version="1.0" ?>
- <RDF:RDF xmlns:RDF="http://www.w3.org/
<RDF:Seq about="urn:mozilla:locale:root">
<RDF:li resource="urn:mozilla:locale:zh-CN" />
</RDF:Seq>
- <!-- locale information -->
- <RDF:Description about="urn:mozilla:locale:en-US">
- <chrome:packages>
- <RDF:Seq about="urn:mozilla:locale: en-US:packages">
<RDF:li resource="urn:mozilla:locale: en-US:helloworld" />
</RDF:Seq>
</chrome:packages>
</RDF:Description>
</RDF:RDF>
chrome\locale\en-US下的contents.rdf也類(lèi)似,只不過(guò)把所有的zh-CN換成en-US即可。
最后,我們要在相應的locale目錄下建立不同locale的helloworld.dtd。還是以英語(yǔ)為例,我們新建chrome\locale\en-US\helloworld.dtd,內容如下:
helloworld.dtd(en-US)
<!ENTITY btn_Hello.label "Click Me!">
<!ENTITY btn_Hello.accesskey "C">
<!ENTITY tbarHello.label "Test">
<!ENTITY tbarHello.tip "Name test">
<!ENTITY manage.label "Manage">
<!ENTITY manage.tip "Management">
<!ENTITY add.label "Add">
<!ENTITY add.tip "Add your name">
<!ENTITY remove.label "Remove">
<!ENTITY remove.tip "Remove your name">
<!ENTITY state.label "State">
<!ENTITY state.tip "Show your state">
在本例中,當Firefox解析到helloworld-Overlay.xul文件中的“label="&btn_Hello.label;”時(shí),并且locale為en-US,那么它就會(huì )到chrome\locale\en-US\helloworld.dtd文件中尋找相應的btn_Hello.label項,即“"Click Me!”。
這樣,我們就實(shí)現了動(dòng)態(tài)加載控件的label,同樣的,我們可以加載tip。
類(lèi)似的,我們可以填寫(xiě)chrome\locale\zh-CN下的helloworld.dtd文件。假如系統的locale為zh-CN,那么我們的helloworld就會(huì )說(shuō)中文了。
這樣,當Firefox每次啟動(dòng)的時(shí)候,會(huì )根據系統的locale來(lái)動(dòng)態(tài)的加載不同的helloworld.dtd。
現在Firefox的部署方式一共有兩種:
這種方式是最普遍的,可以得到Firefox的所有版本的支持。我們可以將完成的擴展目錄壓縮成zip格式的文件,后綴名為.xpi。
當涉及到擴展中有jar文件的時(shí)候,打包過(guò)程會(huì )比較復雜,因為存在多次壓縮的過(guò)程。為了簡(jiǎn)單起見(jiàn),你可以自己編寫(xiě)一個(gè)build.xml, 然后使用ant[9]來(lái)組裝。下面就是一個(gè)文件的樣例:
<?xml version="1.0"?>
<project name="helloworld" default="createxpi">
<target name="createjar">
<zip destfile="helloworld.jar" basedir="."
includes="content/**" />
</target>
<target name="createxpi" depends="createjar">
<zip destfile="helloworld.xpi">
<zipfileset dir="." includes="helloworld.jar"
prefix="chrome" />
<zipfileset dir="." includes="install.rdf" />
</zip>
</target>
</project>
安裝有兩種方式,你可以將這個(gè)xpi文件上傳到Web服務(wù)器上,但前提是該服務(wù)器能實(shí)現application/x-xpinstall功能。另一種方法是在Firefox中選擇“文件”-〉“打開(kāi)文件”,選擇該xpi文件即可。
Firefox1.5支持這種部署方式。這種方式特別適合調試擴展。
你可以直接到Firefox的擴展的系統目錄,一般為“%SYSTEM_DRIVER%:\Documents and Settings\%User%\Application Data\Mozilla\Firefox\Profiles\ default\extensions”。
建立一個(gè)文本文件,文件內容為你的擴展的位置。例如我的是“E:\My Documents\Visual Studio Projects\firefox\helloworld\chrome\content”,文件名為擴展的ID號,本例為{
保存文件后,重啟Firefox即可。
無(wú)論用哪種方式,Firefox啟動(dòng)之后,都會(huì )加載擴展。你可以到“工具”-〉“擴展項”中查看具體的內容,信息應該與你的install.rdf內容一致。


你可以在控制臺中對firefox進(jìn)行調試。Firefox的控制臺類(lèi)似于C語(yǔ)言的控制臺,你可以將一些變量的值打印出來(lái),或是可以打印出一些控制的信息,這些信息對你調試firefox的擴展都是十分有用的。
首先,你需要將這個(gè)功能打開(kāi),在地址欄中輸入:about:config , 此時(shí)你會(huì )發(fā)現有很多firefox的配置項,怎么有點(diǎn)像windows的注冊表呢?That’s right,這就是Firefox的注冊表,你可以在這里放入一些定制的值,實(shí)現特定的功能。
我們需要新建一個(gè)Boolean類(lèi)型的配置項 “browser.dom.window.dump.enabled”,它的值為true。
然后,在你的Firefox的快捷方式的屬性中,把“目標”項添加一個(gè)“-console”,例如我的就改為了"C:\Program Files\Mozilla Firefox\firefox.exe" –console ,這樣每次Firefox啟動(dòng)的時(shí)候都會(huì )出現一個(gè)控制臺。當然你也可以在命令行中輸入上面的命令,但是你應該知道最簡(jiǎn)單的方式工作的最好,不是嗎?

最后,你可以在你的程序中添加window.dump()函數。例如:
window.dump(“Hello world!\n”);
這樣,當程序執行到這里的時(shí)候,控制臺上就會(huì )出現“Hello world”的字樣了。
你可以從以下地址獲得本的實(shí)驗的Hello World代碼: http://marvel.hit.edu.cn:8080/text/firefox/helloworld.xpi
如果你想寫(xiě)自己的Firefox擴展,或者想為Firefox做些東西,可以登錄mozilla的開(kāi)發(fā)網(wǎng)站:http://www.mozdev.org/ 。希望你也能成為Firefox的一名開(kāi)發(fā)者。
本人能力有限,一定有地方講的不正確或是不確切,歡迎來(lái)信交流。我的Email是liuwenmao@hit.edu.cn
[1]
[2] Brian Duff, Writing an Extension for Firefox , October 02, 2004, http://www.orablogs.com/duffblog/archives/000536.html
[1] Firefox的簡(jiǎn)稱(chēng)為FF,本文以后涉及到的所有“FF”,如無(wú)特別說(shuō)明,均指Firefox。
[3] JAR 文件就是 Java Archive File,JAR 文件與 ZIP 文件唯一的區別就是在 JAR 文件的內容中,包含了一個(gè) META-INF/MANIFEST.MF 文件,這個(gè)文件是在生成 JAR 文件的時(shí)候自動(dòng)創(chuàng )建的。
[4] Xml格式不在本文的討論范圍之內。如果讀者想知道xml命名空間的更多內容,請參見(jiàn) 《了解 XML 命名空間》中的“使用命名空間”一節(http://www.chinaitpower.com/A/2004-11-14/140015.html )
[5]參見(jiàn)《Firefox Hacks: Tips & Tools for Next-Generation Web Browsing》 :http://books.google.com/books?hl=en&id=PNEYS39cvRQC&dq=what+is+chrome+firefox&prev=http://www.google.com/search%3Fq%3Dwhat%2Bis%2Bchrome%2Bfirefox%26btnG%3D%25E6%2590%259C%25E7%25B4%25A2%26hl%3Dzh-CN%26newwindow%3D1&pg=PP1&printsec=0&lpg=PP1&sig=oUyWZLI9PC_KsnNAoF6msKBLSbI
[6]參見(jiàn) http://www.htmlhelp.com/reference/css/classification/list-style-image.html 和http://www.w3schools.com/css/pr_list-style-image.asp
[7] 關(guān)于國際化和本地化的更多內容參見(jiàn)“開(kāi)源軟件國際化” http://i18n.linux.net.cn/ 和Linux 國際化本地化和中文化 http://www.linuxforum.net/doc/i18n-new.html
[8] 我們家鄉話(huà)就有很多帶“洋”的詞,例如“洋火柴”、“洋娃娃”等等。因為當時(shí)這些都是從西洋過(guò)來(lái)的。
[9] Ant可以從 http://ant.apache.org/ 免費下載。
聯(lián)系客服