实战 Firefox 扩展开发
級別: 中級
成 富 (chengfbj@cn.ibm.com ), 軟件工程師, IBM 中國軟件開發中心
2008 年 2 月 28 日
Firefox 瀏覽器自身提供良好的擴展結構,使得開發人員可以方便的擴展其行為。很多網站,比如 del.icio.us,都提供 Firefox 擴展來提供更好的用戶體驗。學習這方面的知識不僅對于網站開發人員是有用的,其他人也可以通過開發擴展來解決一些使用 Firefox 中遇到的具體問題。本文以一個能夠批量下載某個 HTML 頁面上所有圖片的 Firefox 擴展作為案例,詳細的介紹了 Firefox 擴展的開發流程。這其中包括構建開發環境,使用 XUL 來描述用戶界面,使用 JavaScript 來為擴展增加行為,擴展的打包、發布和更新等方面的內容。案例介紹
本 文中所要構建的是一個能夠批量下載某個 HTML 頁面上所有圖片的 Firefox 擴展。通常我們在瀏覽包含許多圖片的網頁時,如果想要把自己感興趣的圖片全部下載下來,需要逐一在圖片上點擊右鍵,然后選擇另存為,再選擇文件存放的目 錄,最后才能把圖片保存在本機上。另外一種做法是把整個網頁都保存下來,不過這樣會保存不需要的信息,包括 JavaScript 腳本和 CSS 文件等,會增加所需的磁盤空間,瀏覽起來也不方便。該擴展要做的事情就是把網頁上所有的圖片在一個新窗口中列出來,用戶可以勾選其感興趣的圖片,并指定需 要保存的目錄。然后該擴展能夠一次性把用戶選擇的圖片都下載下來。用戶以后瀏覽起來也更加方便。
?
| |
|
?
構建開發環境
在動手開發之前,首先需要構建擴展開發所需的環境。Firefox 把用戶的個人信息,包括設置、已安裝的擴展等,都保存在一個概要文件中,默認是使用名為 default 的概要文件。通過創建一個專門為開發使用的概要文件,可以不影響正常的使用,也不會破壞個人信息。為了創建另外一個概要文件,運行 firefox –P ,在彈出的“選擇概要文件”的對話框中,新建一個名為 dev 的概要文件,并使用此概要文件來運行 Firefox。接下來需要安裝幾個幫助開發的擴展,分別是 Venkman、Extension Developer's Extension 、Console2 、Chrome List 和 Firebug??梢栽?參考資源 部分找到這些擴展的下載地址。最后修改 Firefox 的設置使得調試更加容易。在地址欄輸入 about:config 可以打開 Firefox 的參數設置頁面。按照如下的設置修改參數:
清單 1. Firefox 擴展開發環境參數設置
| javascript.options.showInConsole = true //把 JavaScript 的出錯信息顯示在錯誤控制臺 nglayout.debug.disable_xul_cache = true //禁用 XUL 緩存,使得對窗口和對話框的修改不需要重新加載 XUL 文件 browser.dom.window.dump.enabled = true //允許使用 dump() 語句向標準控制臺輸出信息 javascript.options.strict = true //在錯誤控制臺中啟用嚴格的 JavaScript 警告信息 |
?
至此,開發環境就構建完成了。當需要進行擴展開發時,運行 firefox -P dev 啟動 Firefox 即可。
?
| |
|
?
構建初始的擴展目錄結構
接下來正式進行擴展開發。首先介紹一下一個 Firefox 擴展的基本目錄結構。
圖 1. Firefox 擴展目錄結構
在 圖 1 中,content 目錄下面存放的是擴展的描述界面的 XUL 文件和增加行為的 JavaScript 文件。locale 目錄存放的是本地化相關的文件。如果需要支持英文和中文,就可以在 locale 目錄下面新建 en-US 和 zh-CN 目錄來存放相應的本地化字符串。skin 目錄存放的是一些 CSS 文件,用來定義擴展的外觀。chrome.manifest 是 Chrome 注冊的清單文件(參見 側欄 )。install.rdf 分別包含了擴展安裝的信息。
|
在 構建了初始的目錄結構之后,需要讓 Firefox 能夠識別并加載該擴展。首先需要找到當前的概要文件所對應的目錄。在 Microsoft Windows 2000 和 XP 的電腦上面,該目錄是 C:/Documents and Settings/<您的登錄用戶名>/Application Data/Mozilla/Firefox/Profiles。找到該目錄之后,可以看到以 dev 結尾的目錄,那就是我們之前構建的開發環境的概要文件所在的目錄。在其下的 extensions 目錄下面,新建一個文件,其文件名為 install.rdf 中指定的擴展的 ID,此處為 batchimagesdownloader@cn.ibm.com。該文件的內容就是擴展內容所在的實際目錄,比如: C:/FirefoxExtDev/batchimagesdownloader。Firefox 就能識別并加載我們添加的擴展了。在每次對擴展做了一定的修改之后,不需要重新啟動 Firefox,只需要安裝之前介紹的 Extension Developer's Extension,并在 Tools 菜單中單擊 Extension Developer > Reload all Chrome 即可。接下來就可以嘗試為擴展添加功能了。
?
| |
|
?
構建基本的用戶界面
我 們首先從用戶界面入手。如之前所述,我們希望在一個新的窗口中顯示當前 HTML 頁面中所有的圖片,并可以讓用戶進行選擇。Firefox 擴展使用 XUL 來描述其用戶界面。XUL 提供了一套基于 XML 的描述方式,可以用來描述用戶界面的各種組件,比如按鈕、菜單和工具條等。最初始的界面包含顯示圖片的一個表格以及 OK 和 Cancel 兩個按鈕。
清單 2. 基本用戶界面的 XUL 描述
| <?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin/" type="text/css"?> <window id="batchimagesdownloader-mainwindow" title="Batch Images Downloader" orient="horizontal" οnlοad="mainWindowOnLoad();" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <vbox flex="1"> <vbox flex="1"> <label value="Images on this web page:" /> <spacer style="height: 5px"/> <hbox height="500" width="750" style="overflow:auto;"> <grid> <columns> <column/> <column/> <column/> </columns> <rows id="imagesContainer"></rows> <!-- 顯示圖片的表格 --> </grid> </hbox> </vbox> <spacer style="height: 10px"/> <hbox> <spacer flex="1"/> <button id="mainWindow-add-button" label="OK" default="true" oncommand="download();"/> <button id="mainWindow-cancel-button" label="Cancel" oncommand="close();"/> </hbox> <spacer style="height: 5px"/> </vbox> </window> |
?
上面就是顯示圖片的新窗口的界面元素的聲明。下面需要添加用戶的交互行為。
?
| |
|
?
添加菜單事件響應
我們希望當用戶點擊 Firefox 上面的一個菜單項時,彈出剛才構建的新窗口。我們這里要做的是向 Firefox 自帶的 Tools 菜單添加一個新的名為 Batch Images Downloader 的菜單項。當用戶點擊此菜單項時,就會彈出 代碼清單2 中 定義的窗口。在擴展中可以使用覆蓋(Overlay)來向已有的界面中添加元素。使用覆蓋可以在運行時向一個 XUL 文檔添加新的組件。我們在 chrome.manifest 中定義了名為 overlay.xul 的文件,會對 Firefox 已有的用戶界面進行一定的修改。只需要在 overlay.xul 中添加下面的內容即可:
清單 3. 增加菜單項的 XUL 描述
| <menupopup id="menu_ToolsPopup"> <menuitem id="batchimagesdownloader-show" label="Batch Images Downloader" oncommand="BatchImagesDownloader.show(event);"/> </menupopup> |
?
上面定義了當點擊菜單項時,會調用 BatchImagesDownloader.show 方法,這是在 overlay.js 中定義的一個 JavaScript 方法,用來處理新窗口的彈出。overlay.js 由 overlay.xul 包含進來。
清單 4. 菜單項的事件響應方法
| var BatchImagesDownloader = { show : function() { var doc = window.getBrowser().selectedBrowser.contentDocument; var imageNodes = doc.getElementsByTagName("img"); //獲取所有的 img 節點 var params = {"imageNodes" : imageNodes}; this.openWindow("BatchImagesDownloader.mainWindow", "chrome://batchimagesdownloader/content/mainWindow.xul", "chrome=yes,centerscreen", params); }, //打開一個新的窗口,或是使得已經創建的窗口獲得焦點 openWindow : function(windowName, url, flags, params) { var windowsMediator = Components.classes["@mozilla.org/appshell/window-mediator;1"] .getService(Components.interfaces.nsIWindowMediator); var aWindow = windowsMediator.getMostRecentWindow(windowName); if (aWindow) { aWindow.focus(); } else { aWindow = window.openDialog(url, windowName, flags, params); } return aWindow; } }; |
?
添加上述的代碼之后,可以通過點擊 Tools 菜單項下來的 Batch Images Downloader 菜單項來彈出新的窗口。
?
| |
|
?
顯示圖片
可以在 BatchImagesDownloader.show 方 法中看到,當彈出新窗口的時候,會把當前頁面上的所有 img 節點都作為參數傳遞給新打開的窗口。這些 img 節點就是需要展現給用戶并供其選擇的。接下來要做的就是在新窗口中顯示這些圖片。在 JavaScript 的方法中,可以像在 HTML 中的 DOM 操作一樣,對 XUL 定義的 DOM 樹進行修改。這其中包括使用 document.createElementNS 來創建新的 XUL 元素,同樣也可以使用 CSS 來修改 XUL 元素的外觀。
清單 5. 顯示圖片的 JavaScript 方法
| const COLUMNS_PER_ROW = 3; //每行顯示3張圖片 function mainWindowOnLoad() { var params = window.arguments[0]; var imageNodes = params.imageNodes; displayImages(imageNodes); } function displayImages(imageNodes) { imageNodes = imageNodes || []; var cols = COLUMNS_PER_ROW, row, image, hbox, checkbox; var rows = document.getElementById("imagesContainer"); for (var i = 0, n = imageNodes.length; i < n; i++) { var imageNode = imageNodes[i]; var imageSrc = imageNode.getAttribute("src"); if (imageSrc == "") { continue; } if (cols >= COLUMNS_PER_ROW) { row = document.createElementNS(XUL_NS, "row"); //開始新的一行 row.setAttribute("align", "center"); rows.appendChild(row); cols = 0; } else { hbox = document.createElementNS(XUL_NS, "hbox"); hbox.setAttribute("style", "padding:5px 5px 5px 5px;"); image = document.createElementNS(XUL_NS, "image");//創建 XUL 圖像元素來顯示圖片 image.setAttribute("src", imageSrc); checkbox = document.createElementNS(XUL_NS, "checkbox");//創建 XUL 復選框元素以供用戶選擇 checkbox.setAttribute("imageUrl", imageSrc); hbox.appendChild(checkbox); hbox.appendChild(image); row.appendChild(hbox); cols++; } } } |
?
我們需要在新窗口加載完成之后,就顯示當前頁面的所有圖片。因此需要注冊新窗口的 onload 事件的響應方法。這里是 mainWindowOnLoad 。在 mainWindowOnLoad 中,通過 window.arguments[0] 可以獲得作為打開新窗口的參數傳進來的 img 節點列表。然后根據這些 img 節點的 src 屬性,創建相應的 XUL 圖像元素并顯示在表格中,并在每個圖片下面創建一個復選框以供用戶選擇。
?
| |
|
?
下載圖片
為了讓用戶能夠下載所選的圖片,需要添加新的界面元素讓用戶可以指定下載圖片存放的目錄,并提供一個進度條來顯示當前的下載進度。
清單 6. 支持下載的用戶界面 XUL 描述
| <hbox> <label value="Save images to" /> <textbox id="mainWindow-save-path" readonly="true" style="min-width: 15em;" flex="1"/> <button label="Browse..." oncommand="selectSaveDirectory();"/> </hbox> <spacer style="height: 10px"/> <progressmeter mode="determined" id="downloadProgress" value="0" style="visibility:hidden;"/> |
?
該擴展提供了一個默認的圖片保存路徑,那就是當前用戶的根目錄。用戶也可以選擇他想要的保存圖片的目錄。
清單 7. 用戶選擇圖片保存目錄的 JavaScript 方法
| var saveDirectory = getDefaultSaveDirectory(); function selectSaveDirectory() { const nsIFilePicker = Components.interfaces.nsIFilePicker; var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); fp.init(window, "", nsIFilePicker.modeGetFolder); var result = fp.show(); //顯示目錄選擇對話框 if (result == nsIFilePicker.returnOK) { var file = fp.file; saveDirectory = file; byId("mainWindow-save-path").value = file.path;//把目錄的路徑顯示在文本框中 } } //獲得默認的圖片保存目錄,也就是當前用戶的根目錄 function getDefaultSaveDirectory() { var file = Components.classes["@mozilla.org/file/directory_service;1"] .getService(Components.interfaces.nsIProperties) .get("Home", Components.interfaces.nsIFile); return file; } |
?
當用戶對圖片進行了選擇,并點擊 OK 之后,需要執行圖片下載的任務。在下載圖片中,會使用 Firefox 的 XPCOM 的實現,請參看 側欄 和 參考資料 ,獲得關于 XPCOM 的更多信息。
清單 8. 下載單張圖片的 JavaScript 方法
| function downloadSingleImage(uri, callback) { var ios = Components.classes["@mozilla.org/network/io-service;1"] .getService(Components.interfaces.nsIIOService); var imageURI = ios.newURI(uri, null, null); //創建圖像的 URI var imageFileName = uri.substring(uri.lastIndexOf("/") + 1); var channel = ios.newChannelFromURI(imageURI); //創建讀取 URI 指定的數據流的通道 var observer = { onStreamComplete : function(loader, context, status, length, result) { var file = Components.classes["@mozilla.org/file/local;1"] .createInstance(Components.interfaces.nsILocalFile); file.initWithFile(saveDirectory); //圖片保存的目錄 file.appendRelativePath(imageFileName); var stream = Components.classes["@mozilla.org/network/safe-file-output-stream;1"] .createInstance(Components.interfaces.nsIFileOutputStream); stream.init(file, -1, -1, 0); var bstream = Components.classes["@mozilla.org/binaryoutputstream;1"] .createInstance(Components.interfaces.nsIBinaryOutputStream); bstream.setOutputStream(stream); bstream.writeByteArray(result, length); //把圖片流的全部字節寫入輸出文件流中 if (stream instanceof Components.interfaces.nsISafeOutputStream) { stream.finish(); } else { stream.close(); } if (typeof callback == "function") { callback(); } } }; var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"] .createInstance(Components.interfaces.nsIStreamLoader); streamLoader.init(channel, observer, null); } |
?
|
這里的實現方式是讀取從遠程獲取的圖片數據流,并把相應的數據寫入到本地磁盤指定的目錄中。為了實現以異步的方式讀取和保存數據,使用了 nsIStreamLoader 接口的實現。它從指定的通道讀取數據,當數據讀取完成之后,會通知相應的監聽器。在這里,我們用圖片的URL地址來初始化一個通道,同時創建了一個監聽器。在 onStreamComplete 的方法中,把得到的圖片字節流寫入到本地文件存儲中。最后,如果注冊了回調函數,就執行此回調函數。
清單 9. 下載用戶選擇的全部圖片的 JavaScript 方法
| function download() { var rows = document.getElementById("imagesContainer"); var checkboxes = rows.getElementsByTagName("checkbox"); var imageUrls = []; for (var i = 0, n = checkboxes.length; i < n; i++) { if (checkboxes[i].checked) { imageUrls.push(checkboxes[i].getAttribute("imageUrl")); //用戶選擇的圖片的 URL } } var progressmeter = byId("downloadProgress"); progressmeter.style.visibility = "visible"; var total = imageUrls.length, step = 100 / total, current = 0; for (var i = 0; i < total; i++) { downloadSingleImage(imageUrls[i], function() { var value = parseInt(progressmeter.value); //更新進度條 progressmeter.value = value + step; }); } close(); } |
?
下載全部圖片時,會逐個檢查復選框的狀態,把用戶選擇的圖片的 URL 記錄下來。對每張圖片,都會調用 downloadSingleImage 以異步的方式來下載。在單張圖片下載完成之后,會有回調函數來通知主窗口,更新進度條的狀態。當所有圖片都下載完成之后,關閉當前窗口。
至此,整個擴展就開發完成了。實際的擴展的截圖如下:
圖 2. 該 Firefox 擴展實際使用的截圖
在 圖 2 中,HTML 頁面的內容是使用“flower”作為關鍵字來訪問百度圖片搜索。
?
| |
|
?
打包、發布和更新
打包
擴展打包的過程非常簡單。只需要把整個目錄的內容打包成一個 ZIP 格式的文件,并把文件的擴展名改為 xpi 即可。需要注意的是 install.rdf 要在 ZIP 文件的根目錄下面,這樣擴展才能安裝到 Firefox 中。
發布
如果您想讓別人也來使用您開發的擴展,一種方式是直接把打包好的 xpi 文件發給對方,他只需要用 Firefox 打開這個文件,就會自動提示安裝。另外一種方式是把該 xpi 文件存放在某個公開的 HTTP 服務器中,對方只需要用 Firefox 訪問該 xpi 文件,就同樣會自動提示安裝。這里需要注意的是要正確的設置 xpi 文件的 MIME 類型。Firefox 能識別的 xpi 文件的 MIME 類型是 application/x-xpinstall。您可能需要配置您的 HTTP 服務器。
更新
如果您的擴展的開發周期較長,需要發布多個版本的話,可以利用擴展的自動更新的能力。這樣當有新的版本發布時,Firefox 會自動提示用戶去獲取最新的版本。要實現自動更新,需要在擴展的 install.rdf 中指定描述更新信息的 rdf 文件的位置,該 rdf 文件通常命名為 update.rdf。在 update.rdf 中聲明了當前最新的版本號和最新版本的下載地址。如果用戶安裝的擴展的版本低于 update.rdf 中聲明的版本,則 Firefox 會提示用戶是否更新。請參考本文附帶的 源代碼 中的 update.rdf 文件。
?
| |
|
?
聲明
本文章僅代表作者本人觀點,與 IBM 公司無關。
?
| |
|
?
下載
| batchimagesdownloader.zip | 4KB | HTTP |
| 關于下載方法的信息 | ||||
?
參考資料
學習
- 在 Mozilla 開發者中心的擴展開發專題 中學習更多關于擴展開發的知識。
- 在 Mozilla 開發者中心的 XPCOM 專題 中學習更多關于 XPCOM 的知識。
- 在 XUL Planet 中學習更多關于 XUL 的知識。
- 閱讀“XUL - 快速開發跨平臺易用用戶接口的新途徑 ”:介紹 XML User-interface Language - 基于 XML 的用戶接口語言。
- 閱讀“XML 用戶界面語言(XUL)開發入門 ”:在這個教程中,您將使用 XUL 進行編程。
- 閱讀“使用 XUL 實現瀏覽器擴展,第 1 部分: 使用用戶界面特性創建一個 Firefox 瀏覽器擴展 ”:介紹如何創建超越 Web 瀏覽器內置功能的擴展。
- 閱讀“使用 XUL 實現瀏覽器擴展,第 2 部分: 組建一個跨平臺的 Firefox 擴展 ”:了解如何構建功能強大的靈活的 Mozilla 瀏覽器擴展。
- 閱讀“創建動態的 Firefox 用戶界面 ”:學習如何使用 Ajax 從 Web 服務器下載 XML 數據,以及如何使用 XSLT 將 XML 數據動態地轉換為用 XUL 表達的 Firefox 用戶界面元素。
- 通過 developerWorks Web 開發專區 中介紹 Web 技術的文章和教程擴展您的網站開發技巧。
獲得產品和技術
- 下載擴展開發者常用的擴展
- Venkman
- Extension Developer's Extension
- Console2
- Chrome List
- Firebug
討論
- Mozilla 擴展開發的新聞組
- Mozilla 擴展開發的 Google 討論組
?
關于作者
| ? | 成富任職于 IBM 中國軟件開發中心,目前在 CETI Web 2.0 小組從事開發工作。他畢業于北京大學信息科學技術學院,獲得計算機軟件與理論專業碩士學位。 | |
總結
以上是生活随笔為你收集整理的实战 Firefox 扩展开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: cad工具箱详细讲解_cad学院派工具箱
- 下一篇: MPQ解密篇