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函数





(弃用)调用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);


你可能感兴趣的:(mozilla)