firefox附加组件开发者指南(五)——创建一个firefox扩展(下)

开发实用的扩展:一个会话管理扩展

这一节,我们将会创建一个实用新特性的扩展:会话存储API。这可以让用户在任何时刻保存和恢复会话快照(浏览器窗口状态)。

第一阶段:测试安装

图12展示了会话管理扩展的接口。在工具菜单下,会话存储子菜单包含两个项——保存会话和清理会话,在它们之间是一个刚才保存可以恢复的会话清单,最近的在最前面。
图12:菜单中的会话管理扩展接口

如”会话存储API”部分所讨论的,会话表示为JSON字符串,会话存储在一个profile目录下的子目录中(叫做sessionstore),其中每一个会话存储为其中的一个文本文件(图13)。
图13:会话保存与恢复的结构

源文件结构

图14:展示这个项目的目录结构

创建安装清单

创建你的工作文件夹,并使用清单1中的内容创建一个安装清单,但是在这里,将em:id改为sessionstore@example.org并将em:name 改为Session Store。

创建chrome清单

用清单2中的内容创建chrome清单。

寻找叠加融合点

使用与hello world扩展相同的叠加融合点和要添加到的元素。

浏览器窗口叠加

用清单14中的内容创建一个叠加到浏览器窗口的文件 overlay.xul。
清单14:overlay.xul的内容

<?xml version="1.0"?>
<overlay id="sessionstoreOverlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://sessionstore/content/overlay.js" />
<menupopup id="menu_ToolsPopup">
<menu label="Session Store" insertbefore="sanitizeSeparator">
<menupopup>
<menuitem label="Save Session" />
<menuseparator />
<!-- Dynamically generated menu items go here -->
<menuseparator />
<menuitem label="Clear Sessions" />
</menupopup>
</menu>
</menupopup>
</overlay>

测试安装

像前面做的那样使用指针文件进行一次测试安装。之后,你会在工具菜单下看到一个会话存储菜单项。

第二阶段:实现功能

在第二阶段,我们将会使用javascript利用会话存储API来实现会话保存和恢复功能。

给出JavaScript的纲要

决定每一个你实现这些特性的处理方法名和变量名,并将它们放到一个overlay.js文件中。按照清单15的代码进行。
清单15:overlay.js文件的内容

var gSessionStore = {
// Directory to save sessions (nsILocalFile)
_dir: null,
// Initialization
init: function() { },
// uninitialization
uninit: function() { },
// Save session (event handler)
save: function(event) { },
// Restore session (event handler)
restore: function(event) { },
// Delete session (event handler)
clear: function(event) { },
// Dynamically generate menu items (event handler)
createMenu: function(event) { },
// Read file
_readFile: function(aFile) { },
// Write file
_writeFile: function(aFile, aData) { },
};
window.addEventListener("load", function(){
gSessionStore.init(); }, false);
window.addEventListener("unload", function(){
gSessionStore.uninit(); }, false);

将方法和变量作为对象属性进行包装
你会注意到在清单15中,我为一个单独的对象gSessionStore定义了很多不同的方法和变量。注意,当使用一个扩展来创建叠加到浏览器窗口的时候,用javascript定义的任何叠加到browser.xul顶部的全局变量和函数都会成为browser.XUL窗口对象的属性。这意味着所有这些与Firefox本身和其他运行着的扩展定义的变量和函数混合在一起。将所有的变量和函数包装到一个对象中是一个将扩展与其他扩展区分开来防止互相干扰的非常好的方法。定义一个类也是一种好的方法。
浏览器窗口打开时进行初始化
对于扩展,很多情况下你想要在浏览器窗口打开的时候执行一些初始化操作。要那样做,可以添加一个load事件侦听器指向window对象。在清单15中,init方法在窗口打开的时候会运行,而且在窗口关闭的时候会运行一个uninit方法。

添加事件处理函数

添加四个定义在overlay.js中的事件处理函数到overlay.xul中(清单16)。这利用事件浮升,将恢复事件处理函数添加到menupopup元素,menuitem元素的上面一层。保存和恢复事件处理函数的代码我们实际上需要屏蔽以阻止该事件的浮升。
清单16:overlay.xul的修改

<menupopup onpopupshowing="gSessionStore.createMenu(event);"
oncommand="gSessionStore.restore(event);">
<menuitem label="Save Session" oncommand="gSessionStore.save(event);" />
<menuseparator />
<!-- Dynamically generated menu items go here -->
<menuseparator />
<menuitem label="Clear Sessions" oncommand="gSessionStore.clear(event);" />
</menupopup>

实现方法

现在我们一个个的来实际的实现我们在gSessionStore对象中定义的方法,为了空间考虑,我不将所有的代码都包含在这里,但是你可以从下面的URL下载:
init方法
init方法如清单17所示。init方法首先使用nsILocalFile获取profile目录下的sessionstore目录,我们将会在随后的几行(3–6行) 引用变量_dir;如果文件目录不存在,则创建这个文件(7–8行)。
清单17:init方法的内容

init: function() {
var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties);
this._dir = dirSvc.get("ProfD", Components.interfaces.nsILocalFile);
this._dir.append("sessionstore");
if (!this._dir.exists())
this._dir.create(this._dir.DIRECTORY_TYPE, 0700);
},

uninit方法
清单18给出了用于uninit方法的代码。这仅仅将init方法创建的变量_dir丢弃了。这个过程其实不重要。
清单18:uninit方法的内容

uninit: function() {
this._dir = null;
},

save方法
清单19给出了save方法的代码。第三行,可以看到阻止事件浮升的代码会在menuitem元素的父元素menupopup的oncommand事件处理函数中调用。第4-6行,我们使用nsISessionStore接口的getBrowserState方法来获取一个JSON的文本字符串表示的当前打开的浏览器窗口的状态。第7-9行,通过复制_dir属性创建目标文件的nsIFile对象。第10行,使用_writefile方法(即将说明)来将JSON字符串表示的会话写入刚才创建的文件中。
清单19:save方法的内容

function(event) {
event.stopPropagation();
var ss = Components.classes["@mozilla.org/browser/sessionstore;1"]
.getService(Components.interfaces.nsISessionStore);
var state = ss.getBrowserState();
var fileName = "session_" + Date.now() + ".js";
var file = this._dir.clone();
file.append(fileName);
this._writeFile(file, state);
},

restore方法
清单20展示了用creatMenu方法(即将说明)动态生成菜单项的事件处理函数,第3-6行通过特殊的由creatMenu方法添加的属性fileName获取文件名,并从那个文件中使用_readFile方法(即将说明)读取JSON文本字符串。第7-9行使用nsISessionStore接口的setWindowState方法使用当然窗口作为原点来恢复会话,会话的恢复会覆盖任何当前打开的标签页。
清单20:restore方法的内容

restore: function(event) {
var fileName = event.target.getAttribute("fileName");
var file = this._dir.clone();
file.append(fileName);
var state = this._readFile(file);
var ss = Components.classes["@mozilla.org/browser/sessionstore;1"]
.getService(Components.interfaces.nsISessionStore);
ss.setWindowState(window, state, false);
},

clear方法
这使用nsIFile接口的directoryEntries属性来删除该目录下你之前得到的所有文件。参考第四章中”穿越文件夹”一节。
清单21: clear方法的内容

clear: function(event)
{
event.preventBubble();
var fileEnum = this._dir.directoryEntries;
while (fileEnum.hasMoreElements()) {
var file = fileEnum.getNext().QueryInterface(Components.interfaces.nsIFile);
file.remove(false);
// debug
dump("SessionStore> clear: " + file.leafName + "\n");
}
},

_readFile方法
读取传递来的一个参数nsiFile对象,以文本字符串方式返回其内容。当文件读取进来的时候,其内容会从UTF-8转换到Unicode。
清单22: _readFile方法的内容

_readFile: function(aFile)
{
try {
var stream = Components.classes["@mozilla.org/network/file-input-stream;1"].
createInstance(Components.interfaces.nsIFileInputStream);
stream.init(aFile, 0x01, 0, 0);
var cvstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
createInstance(Components.interfaces.nsIConverterInputStream);
cvstream.init(stream, "UTF-8", 1024, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
var content = "";
var data = {};
while (cvstream.readString(4096, data)) {
content += data.value;
}
cvstream.close();
return content.replace(/\r\n?/g, "\n");
}
catch (ex) { }
return null;
},

_writeFile方法
以传递来的nsiFile对象创建一个新文件,并将也作为参数传递来的文本字符串写入。在写入的时候,文本由Unicode转换为UTF-8。
清单23:_writeFile方法的内容

_writeFile: function(aFile, aData)
{
// init stream
var stream = Components.classes["@mozilla.org/network/safe-file-output-stream;1"].
createInstance(Components.interfaces.nsIFileOutputStream);
stream.init(aFile, 0x02 | 0x08 | 0x20, 0600, 0);
// convert to UTF-8
var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
var convertedData = converter.ConvertFromUnicode(aData);
convertedData += converter.Finish();
// write and close stream
stream.write(convertedData, convertedData.length);
if (stream instanceof Components.interfaces.nsISafeOutputStream) {
stream.finish();
} else {
stream.close();
}
},

createMenu方法
这个事件处理函数是在会话存储子菜单打开的时候调用的。首先,它删除上一次打开这个子菜单时动态生成的还存在着的menuitem元素。然后,基于sessionstorage目录下所有的文件名动态的生成menuitem元素并将它们插入到菜单中。
清单24:createMenu方法的内容

createMenu: function(event)
{
var menupopup = event.target;
for (var i = menupopup.childNodes.length - 1; i >= 0; i--) {
var node = menupopup.childNodes[i];
if (node.hasAttribute("fileName"))
menupopup.removeChild(node);
}
var fileEnum = this._dir.directoryEntries;
while (fileEnum.hasMoreElements()) {
var file = fileEnum.getNext().QueryInterface(Components.interfaces.nsIFile);
var re = new RegExp("^session_(\\d+)\.js$");
if (!re.test(file.leafName))
continue;
var dateTime = new Date(parseInt(RegExp.$1, 10)).toLocaleString();
var menuitem = document.createElement("menuitem");
menuitem.setAttribute("label", dateTime);
menuitem.setAttribute("fileName", file.leafName);
menupopup.insertBefore(menuitem, menupopup.firstChild.nextSibling.nextSibling);
}
},

操作检查

这就完成了功能的实现。打开一个新的窗口确定所有这三个功能——保存会话,恢复会话以及删除会话——都正常工作。

第三阶段:创建首选项面板

不需要ini文件、注册表或者其他类似的东西就可以为用户显示一个用来设置选项的首选项面板。Firefox可以让你方便的使用XPCOM服务来读写首选项,并用接口窗体小部件来创建首选项面板。在第三阶段,我们来做这个(图15)。
图15:

目录结构

图16显示了我们在第三阶段要使用的目录结构。表5对这些要使用的新文件进行解释。
表5:第三阶段使用的文件:

名称

目的

sessionstore-prefs.js

首选项设置默认值文件。只能放在defaults\preferences目录下

prefs.xul

首选项面板。

确定首选项的形式

下一步需要确定首选项的名称,类型和值(表6)。每个首选项都应该有一股能够唯一标示你的扩展的前缀——这我们使用extensions.sessionstore。
表6:首选项的形式

首选项名称

类型

extensions.session

store.warnOnClear

Boolean

删除会话时激活。如果为true,会出现一股对话框以进行确认;如果为false,则立即删除。

extensions.session

store.replaceTabs

Integer

存储会话时激活。

0: 离开当前标签页

1: 覆盖当前标签页

2: 总是询问

默认值定义文件

清单25给出了默认首选项的内容,如表6所描述。将其保存到defaults\preferences\sessionstoreprefs.js。
清单25:sessionstoreprefs.js文件的内容

pref("extensions.sessionstore.warnOnClear", true);
pref("extensions.sessionstore.replaceTabs", 2);

创建首选项面板

从清单26创建prefs.xul文件的内容。prefwindow元素是一个特殊的用于创建首选项面板的根元素,它有一个下级元素为prefpane。你可以通过插入多个可以让用户点击按钮在面板之前切换的prefpane元素来创建一个与Firefox选项窗口相似的界面。preference元素创建一个可以从首选项面板进行读取和写入的首选项值,首选项名称用name属性来设置,类型用type属性来设置。另外,通过将preference元素的id与一个复选框或单选组框的preference属性想匹配就可以指定用来设置首选项的界面部件。将清单27的内容添加到你的安装清单文件中以使附加组件管理器可以打开你的首选项面板。
清单26:pref.xul文件的内容

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<prefwindow id="sessionstorePrefs" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Session Store Preferences">
<prefpane>
<preferences>
<preference id="extensions.sessionstore.warnOnClear"
name="extensions.sessionstore.warnOnClear"
type="bool" />
<preference id="extensions.sessionstore.replaceTabs"
name="extensions.sessionstore.replaceTabs"
type="int" />
</preferences>
<checkbox label="Confirm before clearing sessions"
preference="extensions.sessionstore.warnOnClear" />
<groupbox>
<caption label="When restoring session:" />
<radiogroup preference="extensions.sessionstore.replaceTabs">
<radio value="0" label="Keep current tabs" />
<radio value="1" label="Replace current tabs"/>
<radio value="2" label="Ask me every time" />
</radiogroup>
</groupbox>
</prefpane>
</prefwindow>

清单27:添加到install.rdf文件中的内容
<em:optionsURL>chrome://sessionstore/content/prefs.xul</em:optionsURL>

通过首选项值改变行为

在清单28中,我们添加一个首选项以使确认对话框在我们删除会话的时候显示。当我们要用javascript获得首选项的值时,我们利用XPCOM的nsIPrefService接口,使用getBoolPref和getIntPref方法来获取准确的类型。清单28中,我们获取nsIPrefBranch对象来读取和写入所有以extensions.sessionstore.为前缀的首选项,这样我们就能够用getBoolPref方法来获取值了。

清单28(文中无)

操作检查

我们修改了安装清单,因此我们需重新安装扩展(参考“修改源文件后的操作检查”)一旦你重新安装完毕,首先打开about:config以确认前面的首选项值都设置了。接着在附加组件管理器中选择会话存储并点击“首选项”按钮就会打开首选项面板。试着改变首选项,并看看在存储或删除会话的时候那些变化是如何反映的。

第四阶段: XPI打包

这个过程与“开发一个简单的扩展”一节的第五阶段没有区别,但是要注意在defaults文件夹下的东西。

结论

我们已经在本章看到了如何创建最基本扩展,扩展和XUL中有很多有趣的图像可以发现。已经带给你涉足了扩展的开发,但是我希望你会去发现更多。

修改源文件后的操作检查

为了有效的开发扩展,你需要将修改源文件和进行一次操作检查之间的步骤最小化。对每种类型的源文件,都有一个不同的过程。下面所有都假设你已经安装“设置你的开发环境”一节中讨论的那样禁用了XUL缓存。

打开新窗口的XUL

确认由关闭和重新打开窗口导致的变化。如果XUL显示的是一个侧边栏,从新打开侧边栏。同样的其他任何XUL引用了的javascript、DTD、或者样式表要进行确认。

叠加到其他窗口的XUL

你需要强制叠加的目标XUL进行重新加载。如果浏览器窗口是目标,可以简单的打开一个新的浏览器窗口来确认变化。

properties文件

如果你对locale程序包中的properties文件进行了修改,在重启Firefox之前这些变化都不会反应出来。

components目录下的XPCOM 组件

需要删除profile文件夹下的compreg.dat文件和 xpti.dat文件并重启Firefox。

defaults\preferences目录下的默认首选项文件

需要重启Firefox。

chrome.manifest

需要重启Firefox。

install.rdf

需要暂时的卸载扩展并重新安装它。实际上,你需要做的就是移除指针文件,重启Firefox,回到指针文件,并再次重启Firefox。在linux系统中,在指针文件指向的目录下使用touch命令改变其“最后修改”日期可以避免重启。

会话存储API

会话存储API是firefox2中的一个面向开发者的新特性。使用一个XPCOM服务nsIsessionStore接口的各种方法你就可以见到的保存和恢复会话状态。Firefox中的功能可以让你再次打开最后关闭的标签页,或者在崩溃之后恢复上一次的状态,都是通过这个API来实现的。
我们在本项目中使用了两个nsISessionStore接口方法。我们来看看它们是任何工作的。

getBrowserState()

返回一个表示当前打开的每一个浏览器窗口状态的JSON-格式文本字符串。这个字符串包含了关于每个打开的窗口和标签的大量信息,每个标签页的前进/后退历史,网页滚动位置,文本缩放,表单元素的内容等等。

setWindowState(aWindow, aState, aOverwrite)

恢复参数aWindow说明的浏览器窗口到aState说明的状态。如果aOverwrite为true,当前打开的标签就会被覆盖;否则会打开新的窗口来恢复内容。

JavaScript调试方法

alert

最简单的调试javascript的方法是使用window.alert方法在对话框中显示变量(清单29。除了异步操作,所有操作都会在对话框出现的时候停止,因此这个技术在你想要对某个值进行微小调整时非常重要。
清单29: 检查变量foo的值

alert(foo);

dump

在windows环境中,你可以用-console参数启动Firefox,这样会打开一个输出控制台。使用dump方法,你可以将文本发送到输出控制台(清单30)。输出到控制台的文本不会减慢其他操作的进程,并且当在需要输出大量信息时非常有用。为了最高效的使用dump方法你需要按照“设置你的开发环境”中的建议,注意在windows环境中,输出控制台中的非拉丁文字不会正常显示。
清单30:列出obj的属性

for (var i in obj) {
dump(i + " : " + obj[i] + "\n");
}

错误控制台

nsIConsoleService接口
使用一个XPCOM服务nsIConsoleService接口的logStringMessage方法可以将文本输出到错误控制台的“消息面板”。这里非拉丁文字也可以正常显示。如果你经常使用错误控制台来分析问题,像清单31那样定义一个函数会非常方便。
清单31:输出消息到错误控制台的函数

function log(aText) {
var console = Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService);
console.logStringMessage(aText);
}

Components.utils.reportError方法
另一种输出文本到相同的错误控制台的方式是使用Components.utils.reportError方法。这会将其输出到错误控制台的“错误”面板。

在哪学习更多与XPCOM接口相关知识?

Mozilla开发中心(MDN)有大量的关于我们在这一节讨论过的nsISessionStore接口的文档。一般的,XPCOM接口的细节(IDL)可以从包含具有可以进行全文本搜索的源代码的Mozilla Cross-Reference获取。


译文pdf下载


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值