firefox附加組件開發者指南(四)——使用XPCOM:實現高級處理

本章描述如何利用javascript腳本語言使用XPCOM來實現高級的處理。

概述

Javascript沒有類似於用來打開文件以及進行字符編碼轉換的函數。要實現這些功能需要採用其他機制。IE使用activex來處理,在firefox中我們使用XPCOM(跨平臺組件對象模型)。

關於XPCOM

XPCOM是用來開發獨立於平臺的組件的一個框架(framework)。在這個框架下開發的組件通常稱之爲XPCOM組件,有時候也簡單的稱爲XPCOM。
Firefox本身包含大量的XPCOM組件,這些組件也可以用於擴展。有時候,一個擴展也會包裝成一個特殊的XPCOM組件。
注意:如果用C++或者其他編譯語言開發組件,請確保包含用於各種平臺的二進制文件。

參考材料

要獲取關於XPCOM中嵌入的可以處理的功能的信息可以看看其API reference以及其源代碼中的XPIDL文件的接口定義。
注意:接口定義語言(IDL)是一種爲對象、方法等進行標準定義的語言。Mozilla使用的XPIDL是CORBA IDL的一種擴展。
你也可以在MXR(Mozilla Cross-Reference)中firefox的源代碼中進行全文搜索,可以使用字符串、文件名等作爲關鍵詞。如果你對這些接口的細節有任何困難,在firefox的源代碼中搜索使用例子非常有幫助。
注意:要查看firefox 3的源代碼,可以在開始的清單中選擇firefox 3.要查看firefox 3.5的可以選擇“Mozilla 1.9.1.”要查看當前開發的firefox。選擇“Mozilla central”。要查看thunderbird則選擇“Comm.Central”

從XPConnect中調用XPCOM

用javascript腳本利用XPConnect技術來使用XPCOM。清單1顯示瞭如何使用XPConnect來獲取一個XPCOM服務的引用並創建新的XPCOM對象。
每個組件都由一個contractID來唯一標誌,其形式是:@domain_name/module_name/component_name;version_number,實現一個或者多個接口來確定在這些組件中需要調用哪些函數。接口名通常具有nsIXXX的形式。爲了能夠訪問相應函數,有必要使用具有你想使用的接口的組件。有些XPCOM組件是服務,即意味着在內存中只有一個實例。例如:書籤組件其實是一個服務。它使得你可以訪問並操作用戶的書籤。這些服務需要使用getService()來訪問。對於其他組件,你可以根據需要創建多個實例。例如:文件的例子(nsILocalFile),這些實例是用createInstance()來創建的。搞清楚某一個組件是用getService()還是createInstance()來創建的非常重要,因爲使用錯了會導致錯誤的。
清單1:使用XPConnect來調用XPCOM函數

<?xml version="1.0" encoding="UTF-8"?>
<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"><![CDATA[
var ioService = Components.classes['@mozilla.org/network/io-service;1']
.getService(Components.interfaces.nsIIOService);
alert(ioService);
]]></script>
</page>

(棄用)調用XPConnect來使用本地文件

試試將清單1中的內容保存爲文件test.xul,將其拖動到firefox中使其打開。你會發現即使文件包含了一個alert方法也不會發生什麼事情。這是因爲當前test.xul不具有足夠的權限。
爲了使用XPConnect,文件需要特殊的UniversalXPConnect權限。因爲普通的網頁以及本地文件沒有權限,所以就沒有可能去執行本章中的示例代碼。
要設置UniversalXPConnect權限,需要運行清單2中的代碼。
清單2:設置權限

netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); 

注意:如果代碼是擴展的一部分,則不需要這樣做,如果將這樣的代碼提交給addons.mozilla.org將會遭到拒絕。

對話框許可(Permit by dialog)

試着將清單2中的內容添加到test.xul中var ioService=…這一行之前,然後重新在firefox中打開它。你現在應該可以看到圖1所示的確認對話框了。按“Permit”按鈕同意該執行程序的UniversalXPConnect方式許可,使其可以暫時運行XPConnect。這時就會出現一個XPConnect封裝的nsIIOService消息。
如果你選擇“apply these privileges in the future”,今後所有本地文件都可以在不進行確認的情況下運行XPConnect。這相當危險,最好永遠不要選擇這個選項。

編輯prefs.js文件

打開test.xul文件會產生一個如圖1所示的對話框。這可能很煩。要運行文本的腳本而不進行手動的確認可以添加清單3中的內容到用戶profile文件夾中的prefs.js文件中。打開test.xul就會顯示笨蛋文件的URL在地址欄。複製並將清單3中的內容粘貼到合適位置。
注意:用戶profile文件夾的位置會根據你的系統的不同而不同。在windows vista系統中位於C:\Users\username\AppData\Roaming\Mozilla\Firefox\Profiles\random number.default\ ;在windows XP或2000上位於C:\Documents and Settings\username\Application Data\Mozilla\Firefox\Profiles\random number.default\ ;在Linux上位於 ~/.mozilla/firefox/random number.default/ ;在Mac OS X上位於~/Library/Application Support/Firefox/Profiles/random number.default/。
安全起見,在完成了這些測試之後刪除prefs.js文件中的這些代碼行。
清單3:對特定文件不進行手動確認而允許權限

user_pref("capability.principal.codebase.test.granted", "UniversalXPConnect");
user_pref("capability.principal.codebase.test.id", "File URL");

常用的XPCOM函數

我們來看看那些使用特別頻繁的XPCOM函數,你應該可以通過查看示例代碼瞭解XPCOM是如何使用的。本章中,幾乎所有的代碼你都可以將其複製到你的test.xul文件中並試驗出來。隨時可以用這些代碼來填充你認爲需要的地方。確保將文件路徑匹配你自己的環境。你可以下載本章中使用的源代碼。

獲取窗口

你可以使用JavaScript來獲取父窗口打開的子窗口,但是卻不可以獲取那些與父窗口無關的對話框或窗口。要克服這一限制可以使用nsIWindowMediator來訪問所有firefox窗口。

獲取活動窗口

nsIWindowMediator經常使用的是獲取活動窗口,清單4展示瞭如何獲取活動的瀏覽器窗口以及打開的標籤頁總數。
將窗口類型的名稱作爲一個參數傳遞給nsIWindowMediator.getMostRecentWindow()方法會返回窗口根元素中最近打開的具有該種類型的windowtype屬性值的窗口。將這個參數設置爲null會返回所有包括對話框在內的窗口中的活動窗口,等等。
清單4:獲取活動瀏覽器窗口

netscape.security.PrivilegeManager
.enablePrivilege('UniversalXPConnect');

var WindowMediator = Components
.classes['@mozilla.org/appshell/window-mediator;1']
.getService(Components.interfaces.nsIWindowMediator);
var browser = WindowMediator.getMostRecentWindow('navigator:browser');
alert(browser.gBrowser.mTabs.length);

獲取所有窗口中具有特定類型的窗口概況

使用nsIWindowMediator.getEnumerator()方法可以獲取所有窗口中具有特定類型的窗口的概況。清單5展示瞭如何獲取firefox中所有瀏覽器窗口的概況然後關閉它們。
清單5:關閉所有瀏覽器窗口

var browsers = WindowMediator.getEnumerator('navigator:browser');
var browser;
while (browsers.hasMoreElements()) {
browser = browsers.getNext().QueryInterface(Components.interfaces.nsIDOMWindowInternal);
browser.BrowserTryToCloseWindow();
}

該方法返回一個特定窗口類型的概況,其形式是一個叫做nsISimpleEnumerator的迭代模式對象。在使用nsISimpleEnumerator.getNext()方法獲取一個元素之後,使用QueryInterface方法獲取這個接口,這樣就可以將每個元素作爲一個窗口對象來處理。
與getMostRecentWindow()方法相似,如果傳遞null作爲getEnumerator()的參數就可以獲取firefox的所有窗口,包括對話框等等。

使用XPCOM操作文件

XPCOM提供了一系列的接口可以來進行文件的操作而不需要考慮是運行在windows、mac OS X或者是Linux等平臺。
要能夠處理本地文件,首先需要創建一個nsILocalFile對象來表示一個本地文件,如清單6所示。當你通過傳遞文件的完整路徑給nsILocalFile.initWithPath()方法來初始化這個對象的時候,這個對象就變成所有函數都可以訪問的對象了。這個文件是否存在於指定的路徑都沒有影響。
注意:使用路徑的格式需要適合於所使用的平臺,在windows中的路徑分割符“\”是轉義字符,因此總是需要寫成“\\”,而Linux中,像“./”這樣的字符就不需要特殊的處理。
清單6:創建表示文件的XPCOM對象

var file = Components.classes['@mozilla.org/file/local;1']
.createInstance(Components.interfaces.nsILocalFile);
file.initWithPath('C:\\temp\\temp.txt');

創建與刪除文件

清單7展示了刪除已經存在的文件並用相同名稱創建新文件。
爲nsILocalFile.remove()傳遞一個true參數會遞歸的刪除文件夾和文件。看看當傳遞true參數的時候刪除文件夾中所有內容時會發生什麼。
清單7:檢查文件是否存在、刪除、創建

file.initWithPath('C:\\temp\\temp.txt');
if (file.exists())
file.remove(false);
file.create(file.NORMAL_FILE_TYPE, 0666);

nsILocalFile.create()的第一個參數給出需要創建的文件類型。使用常量來定義——要創建一個常規文件,使用NORMAL_FILE_TYPE,要創建文件夾使用DIRECTORY_TYPE。第二個參數給出了這個文件的訪問權限,使用Unix類型的八進制值。
注意:windows會忽略權限參數,而其他平臺會正常工作。
nsILocalFile對象包含的方法可以返回當前文件的虛擬狀態值,如表1所示。
表1:檢查文件狀態的方法

方法名稱

描述

nsILocalFile.exists()

確定文件是否存在

nsILocalFile.isWriteable()

確定文件是否可以寫入.

nsILocalFile.isReadable()

確定文件是否可以讀取.

nsILocalFile.isExecutable()

確定文件是否可以執行.

nsILocalFile.isHidden()

確定文件是否是隱藏文件

nsILocalFile.isDirectory()

確定引用是否指向一個目錄.

nsILocalFile.isFile()

確定引用是否指向一個文件。

nsILocalFile.isSymlink()

確定引用是否指向一個符號鏈接.

目錄轉換

移動到一個目錄

使用nsILocalFile.append()方法來移動到下一級目錄(或文件)。見清單8.
清單8目錄轉換

file.initWithPath('C:\\');
file.append('Documents and Settings');
file.append('All Users');
file.append('Documents');

列出指定目錄中的文件

使用directoryEntries屬性可以實現對指定的文件夾內的所有文件或者文件夾進行操作。該屬性返回一個nsISimpleEnumerator類型的對象,可以使用與窗口概覽相似的方法獲取每一個元素。清單9展示瞭如何列出一個文件夾內所有內容。
清單9:列出指定目錄下的內容

file.initWithPath('C:\\');
var children = file.directoryEntries;
var child;
var list = [];
while (children.hasMoreElements()) {
child = children.getNext().QueryInterface(Components.interfaces.nsILocalFile);
list.push(child.leafName + (child.isDirectory() ? ' [DIR]' : ''));
}
alert(list.join('\n'));

獲取父目錄

雖然nsILocalFile對象沒有提供一個向上級目錄移動的方法,但清單10展示瞭如何使用parent屬性來獲取當前目錄的父目錄。
清單10:爲指定文件在不同目錄下創建一個備份

file.initWithPath('C:\\temp\\temp.txt');
backupFolder = file.parent.parent; // C:\
backupFolder.append('backup'); // C:\backup
backupFolder.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0666);
file.copyTo(backupFolder, file.leafName+'.bak');

文件路徑與文件URL之間的轉換

XPCOM函數既可以使用遠程資源也可以使用本地文件,這些函數基本上都會用URI指定它們的目標。本地文件路徑可以轉換爲文件URL,如:file:///C/temp/temp.txt,如清單11所示。清單12展示了相反的過程。
清單11:將一個本地文件路徑轉換爲URL

var path = 'C:\\temp\\temp.txt';
var file = Components.classes['@mozilla.org/file/local;1']
.createInstance(Components.interfaces.nsILocalFile);
file.initWithPath(path);
var ioService = Components.classes['@mozilla.org/network/io-service;1']
.getService(Components.interfaces.nsIIOService);
var url = ioService.newFileURI(file);
var fileURL = url.spec;
alert(fileURL); // "file:///C:/temp/temp.txt"


清單12:將一個URL轉換爲本地文件路徑
var url = 'file:///C:/temp/test.txt';
var ioService = Components.classes['@mozilla.org/network/io-service;1']
.getService(Components.interfaces.nsIIOService);
var fileHandler = ioService.getProtocolHandler('file')
.QueryInterface(Components.interfaces.nsIFileProtocolHandler);
var file = fileHandler.getFileFromURLSpec(url);
var path = file.path;
alert(path); // "C:\temp\temp.txt"


二進制文件I/O

在XPCOM 中像java一樣使用流進行文件I/O。

打開二進制文件

清單13展示瞭如何以字節串(1字節=8個bit數組)獲取一個文件的內容。保存一個只包含ASCII字符“XUL”的文本文件,並將路徑寫入文件。運行該段代碼會產生輸出“58 55 4C”,這樣就可以檢查結果了。
注意:這裏,我們假設文件名爲temp.txt且存儲於C盤的temp文件夾中。
清單13:讀取二進制文件的內容

file.initWithPath('C:\\temp\\temp.txt');
var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1']
.createInstance(Components.interfaces.nsIFileInputStream);
fileStream.init(file, 1, 0, false);
var binaryStream = Components.classes['@mozilla.org/binaryinputstream;1']
.createInstance(Components.interfaces.nsIBinaryInputStream);
binaryStream.setInputStream(fileStream);
var array = binaryStream.readByteArray(fileStream.available());
binaryStream.close();
fileStream.close();
alert(array.map(
function(aItem) {return aItem.toString(16); }
).join(' ').toUpperCase(
));

當我們初始化nsIFileInputStream的時候,我們將第二個和第三個參數初始化爲只讀模式。一旦過程完成,應該關閉所有的流。

輸出二進制文件

清單14展示了相反的操作,獲取一個字符串的字節碼,並以二進制文件的方式輸出。這裏我們輸出的文本文件由ASCII字符“XUL”組成。
初始化nsIFileInputStream的時候,將第二和第三個參數初始化爲只寫模式。過程完成,關閉所有打開的流。
清單14:寫入二進制文件

var array = [88, 85, 76];
file.initWithPath('C:\\temp\\temp.txt');
if (file.exists())
file.remove(true);
file.create(file.NORMAL_FILE_TYPE, 0666);
var fileStream = Components.classes['@mozilla.org/network/file-output-stream;1']
.createInstance(Components.interfaces.nsIFileOutputStream);
fileStream.init(file, 2, 0x200, false);
var binaryStream = Components.classes['@mozilla.org/binaryoutputstream;1']
.createInstance(Components.interfaces.nsIBinaryOutputStream);
binaryStream.setOutputStream(fileStream);
binaryStream.writeByteArray(array , array.length);
binaryStream.close();
fileStream.close();

文本文件I/O

文本文件與流的讀取相似。如果文本包含多字節字符,需要對字符編碼進行轉換。

文本文件輸入

清單15展示了一個打開編碼爲Shift-JIS(日語中的一種雙字節字符編碼)的文本文件的例子。複製一些日語文本到一個文本文件中,將其保存爲Shift-JIS編碼,然後輸入其路徑。文本就會以其本該顯示的方式顯示。讀取的文本在內部其實是用Unicode(UTF-16)編碼的。
清單15:讀取Shift-JIS文本文件

file.initWithPath('C:\\temp\\temp.txt');
var charset = 'Shift_JIS';
var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1']
.createInstance(Components.interfaces.nsIFileInputStream);
fileStream.init(file, 1, 0, false);
var converterStream = Components.classes['@mozilla.org/intl/converter-input-stream;1']
.createInstance(Components.interfaces.nsIConverterInputStream);
converterStream.init(fileStream, charset, fileStream.available(),
converterStream.DEFAULT_REPLACEMENT_CHARACTER);
var out = {};
converterStream.readString(fileStream.available(), out);
var fileContents = out.value;
converterStream.close();
fileStream.close();
alert(fileContents);

輸出文本文件

(不確定本例與英文相關,可能來自代碼片段)
清單16展示瞭如何獲取內部表示爲Unicode的文本,並將其輸出到一個文件中以EUC-JP(一種日文文本編碼)編碼保存。這裏,用於寫入的字符串“変換テスト”是在javascript源代碼中直接使用轉義unicode實體進行硬編碼的。打開輸出文件以檢查結果。
注意,如果將字符編碼設置爲null將會使用默認的UTF-8進行輸入或輸出。
清單16:寫入文本到一個編碼爲EUC-JP的文件中

var string = '\u5909\u63db\u30c6\u30b9\u30c8';
file.initWithPath('C:\\temp\\temp.txt');
file.create(file.NORMAL_FILE_TYPE, 0666);
var charset = 'EUC-JP';
var fileStream = Components.classes['@mozilla.org/network/file-output-stream;1']
.createInstance(Components.interfaces.nsIFileOutputStream);
fileStream.init(file, 2, 0x200, false);
var converterStream = Components.classes['@mozilla.org/intl/converter-output-stream;1']
.createInstance(Components.interfaces.nsIConverterOutputStream);
converterStream.init(fileStream, charset, string.length,
Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
converterStream.writeString(string);
converterStream.close();
fileStream.close();

字符編碼的轉換

Firefox內部的文本編碼都是採用unicode編碼的,但是,在某些情況下,你可能想要用其他編碼方式處理。這些情況下,可以使用nsIScriptableUnicodeConverter在各種編碼之間方便的進行轉換。

從Unicode轉換到其他編碼

清單17展示瞭如何將編碼爲Unicode的文本轉換爲EUC-JP編碼。
清單17:從unicode轉換到EUC-JP

var converter = Components.classes['@mozilla.org/intl/scriptableunicodeconverter']
.getService(Components.interfaces.nsIScriptableUnicodeConverter);
converter.charset = 'EUC-JP';
var unicode_str = '\u5909\u63db\u30c6\u30b9\u30c8';
var eucjp_str = converter.ConvertFromUnicode(unicode_str);

從其他編碼轉換到Unicode編碼

清單18展示瞭如何做相反的過程,從ISO-2022-JP轉換到unicode編碼。
清單18:從ISO-2022-JP轉換到unicode編碼

converter.charset = 'ISO-2022-JP';
var unicode_str = converter.ConvertToUnicode(iso2022jp_str);

讀寫首選項文件

你可以使用nsIPrefBranch函數來獲取firefox的首選項系統。這個函數可以利用三種類型值來獲取並設置首選項,三種類型分別是boolean型、整型以及文本型,每種類型都有指定的方法,如表2所示。

讀取首選項

清單19展示瞭如何獲取存儲在首選項中的字符串。在地址欄輸入about:config可以對所存儲的首選項的值進行確認。
注意:nsIPrefBranch.getCharPref()返回的值是一個UTF-8字節字符串;這裏我們使用escape()和decodeURIComponent()將其轉換爲UTF-16。
清單19:讀取設置的文本字符串

var pref = Components.classes['@mozilla.org/preferences-service;1']
.getService(Components.interfaces.nsIPrefBranch);
var dir = pref.getCharPref('browser.download.lastDir');
alert(decodeURIComponent(escape(dir)));

寫入首選項

清單20展示了相反的操作,將字符串文本寫入到一個唯一首選項。也可以使用about:config來檢查其值是否正確寫入。
注意:nsIPrefBranch.setCharPref()的第二個參數的值是一個UTF-8字節字符串,這裏我們使用unescape()和encodeURIComponent()將UTF-16轉換爲UTF-8。
清單20:寫入文本字符串設置

var string = 'This is test.';
pref.setCharPref('extensions.myextension.testPref', unescape(encodeURIComponent(string)));

數據類型

獲取

設置

Boolean

getBoolPref(prefname)

setBoolPref(prefname)

Integer

getIntPref(prefname)

setIntPref(prefname)

Text string

getCharPref(prefname)

setCharPref(prefname)

使用XUL元素的方法

使用XPCOM你可以對XUL元素的函數進行訪問。例如,使用第三章介紹的browser元素的loadURI()方法可以打開一個用HTTP_REFERER指定的頁面,如清單21所示;清單22展示瞭如何使用loadURIWithFlags()方法通過POST方法傳送數據來打開一個頁面。
清單21:通過設置referrer來加載頁面

var browser = document.getElementById('browser');
var ioService = Components.classes['@mozilla.org/network/io-service;1']
.getService(Components.interfaces.nsIIOService);
var referrer = ioService.newURI('http://www.gihyo.co.jp/', null, null);
browser.loadURI('http://www.gihyo.co.jp/magazines/SD', referrer);

清單22:通過POST方法加載帶有數據傳送的頁面

var content = encodeURIComponent('password=foobar');
var referrer = null;
var postData = Components.classes['@mozilla.org/io/string-input-stream;1']
.createInstance(Components.interfaces.nsIStringInputStream);
content = 'Content-Type: application/x-www-form-urlencoded\n'+
'Content-Length: '+content.length+'\n\n'+
content;
postData.setData(content, content.length);
var flags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE;
browser.loadURIWithFlags('http://piro.sakura.ne.jp/', flags, referrer, null, postData);


發佈了32 篇原創文章 · 獲贊 83 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章