前言
本节主要是讲了一下 Firefox extension 在开发的时候,遇到的一些问题,大的问题会在之前的系列会提到。小的问题会在这边提。
如何找到现有的 extension 的代码
开发一个东西,一个很快的捷径就是参考别人的代码,尤其是一些界面细节的时候,所以当我们找到了一个值得我们参考的 extension 的时候,怎么样得到这个插件的源代码,这个也是很重要的。
以这个应用 pushbullet
来说,当安装了 pushbullet
的 Firefox extension
, 接下来当前要找到插件所在的文件,从而看到代码。
注意,Firefox extension
的代码存在两个地方,一个是本身浏览器自带的extension
,这个就放在Firefox
的安装目录里面:
而如果是自己去下载的extension
,那就不是放在这边了。而是放在个人的文件夹中,以 windows
来说,就是1
C:\Users\admin\AppData\Roaming\Mozilla\Firefox\Profiles\0p5aekfy.default\extensions
这时候,就可以看到你下载的所有Firefox extension
。但是我们发现有些extension
是以xpi
后缀,那怎么看?其实只要把xpi
重命名为 zip
,然后解压zip
包,然后就可以看到了,最后就可以看到pushbullet
的源码了
popup 页面没办法做文件拖拽的原因
通过 浏览器 extension 插件开发系列(15) -- chrome多文件上传(拖拽上传或者点击上传)我们知道 Chrome 可以在插件的 popup 上做文件拖拽上传。 但是 Firefox
却不行,就是因为在点击上传文件的时候,当文件选择框弹出来的时候,该panel
框会关闭:
后面查了一下资料,发现如果是用 add-on
初始化的panel
(我们就是这一种),是不能保持显示的,他一旦焦点失去(第三方登录也是一样),就会关闭掉。只有使用 xul
形式的extension
,才有一个属性是可以保持显示的。 即这一种才行:
具体可以看这边: firefox-addon-builder-how-to-keep-a-panel-shown
以下是对应的文档:
这边就涉及到一个问题,那就是为啥我们要选用 addon 的方式来做扩展?
为啥选用 addon 的方式来做扩展
一个很重要的原因是如果选用 xul
的方式,这个 ui 的界面的开发会非常不习惯,因为是另外一种语法,跟 Chrome extension
的写法差太多了,在界面上根本没办法重用。所以才要做这种自引导性的扩展,而且它还有以下好处:
- 可以手动添加用户界面, 这样就不用担心做一些适应
Firefox extension
的xul
页面了,比如: 您需要在相关应用程序中调用document.getElementById()
,通过UI
元素的ID
来查找它们,然后操纵它们来注入您的UI
。例如,您可以通过document.getElementById("main-menubar")
来访问Firefox
的菜单栏。 可以像
Chrome extension
开发那样,引入一些js进去。
要做到自引导型扩展,添加下列元素到它的安装清单,即install.rdf
1
<em:bootstrap>true</em:bootstrap>
不需要
Chrome.manifest
文件
不过没有这个文件,但也多了其他的文件。
怎么在浏览器打开这个插件的目录索引
很简单,找到这个插件名字,然后在地址栏输入 resource://extension-id
这样就可以了。
甚至可以通过这种方式来调整页面细节
对 cookie 的操作
Firefox 对 cookie 的操作,不像 Chrome 那样,还有一个专门的对象来读取:1
chrome.cookies
而是要在中转页 main.js 那边去设置的,所以抽象出来是这样子的:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41var COOKIE_URL = "http://*.airdroid.com";
Cookies = {
get: function(name, cb){
var uniqId = _.uniqueId("cookie_");
Airdroid.Event.dispatchEvent(Airdroid.Event.FireFoxEvent.get_cookie,{
uid: uniqId,
name: name,
domain: COOKIE_URL
});
Airdroid.Event.addEventListener(uniqId, function(data){
Airdroid.log("cookie 为" + data.detail);
_.isFunction(cb) && cb((data.detail ? {
name: data.detail,
domain: COOKIE_URL
} : null));
},true);
},
set: function(opt, cb){
var uniqId = _.uniqueId("cookie_");
Airdroid.Event.dispatchEvent(Airdroid.Event.FireFoxEvent.set_cookie,{
uid: uniqId,
opt: opt,
domain: COOKIE_URL
});
Airdroid.Event.addEventListener(uniqId, function(data){
_.isFunction(cb) && cb(data);
},true);
},
remove: function(name, cb){
var uniqId = _.uniqueId("cookie_");
Airdroid.Event.dispatchEvent(Airdroid.Event.FireFoxEvent.remove_cookie,{
uid: uniqId,
name: name,
// 这边要 .airdroid.com 不能加星号
domain: COOKIE_URL.split("//")[1].substr(1)
});
Airdroid.Event.addEventListener(uniqId, function(data){
_.isFunction(cb) && cb(data);
},true);
}
}
而具体的操作其实在 main.js
, 这边也是用到了之前将的事件驱动模型: 浏览器 extension 插件开发系列(10) -- 事件驱动模型1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41var {Cc, Ci} = require('chrome');
var ioSvc = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
var cookieSvc = Cc['@mozilla.org/cookieService;1'].getService(Ci.nsICookieService);
var cookieMgr = Cc['@mozilla.org/cookiemanager;1'].getService(Ci.nsICookieManager);
// cookie operate
var getCookie = function(name, domain){
var cookieString = cookieSvc.getCookieString(ioSvc.newURI(domain, null, null), null);
if (cookieString != null) {
var cookies = cookieString.split(';');
for (var i = 0, cookiesLength = cookies.length; i < cookiesLength; i++) {
var cookie = cookies[i];
if (cookie.length > 0) {
var parts = cookie.split('=');
if (trim(parts[0]) == name) {
return trim(parts[1]);
}
}
}
}
return null;
};
page.port.on("get_cookie", function(data){
data = data.detail;
var str = getCookie(data.name, data.domain);
page.port.emit(data.uid, {detail: str});
});
page.port.on("set_cookie", function(data){
data = data.detail;
var opt = data.opt;
cookieSvc.setCookieString(ioSvc.newURI(data.domain, null, null), null, opt.name + '=' + opt.value, null);
page.port.emit(data.uid, {detail: cookieSvc.getCookieString(ioSvc.newURI(data.domain, null, null), null)});
});
page.port.on("remove_cookie", function(data){
data = data.detail;
cookieMgr.remove(data.domain, data.name, '/', false);
page.port.emit(data.uid, {});
});
这样才能得到 cookie
进行跨域请求的时候
Firefox
在进行跨域请求的时候,也很奇葩,如果服务端没有添加跨域头部的话,会失败,哪怕用 jsonp get
请求也不行。 所以要用它自带的网络请求,也是在 main.js
里面处理, 当然我们先在背景页先封装方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43// 最基本的get方法
baseGetRequest: function (url, data, type) {
if(Airdroid.Util.Browser.firefox){
// Firefox 比较特殊,有限制,如果服务端没有添加跨域头部的话,就会请求失败, 所有要用自带的请求
var defer = $.Deferred();
url = url + "?" + $.param(data);
var uid = _.uniqueId("request_");
type = type || 'json';
Airdroid.Event.addEventListener(uid, function(data){
var status = data.status;
if (status == 200) {
try {
// 请求成功
if(type == 'json'){
defer.resolve(data.json || {});
}else{
defer.resolve(data.text || "");
}
} catch (e) {
console.log(e);
defer.reject();
}
} else if (status === 401) {
defer.reject();
} else {
defer.reject();
}
},true);
Airdroid.Event.dispatchEvent(Airdroid.Event.FireFoxEvent.request_get,{
url: url,
uid: uid
});
return defer;
} else {
return $.ajax({
type: "GET",
timeout: 10000,
url: url,
data: data,
dataType: type || 'json'
});
}
},
具体的实现是在 main.js
里面:1
2
3
4
5
6
7
8
9
10
11
12
13
14page.port.on("request_get", function(data){
data = data.detail;
Request({
'url': data.url,
'onComplete': function(response) {
page.port.emit(data.uid, {
json: response.json,
text: response.text,
status: response.status,
statusText: response.statusText
});
}
}).get();
});
通过最后回调 uid 来回到背景页的回调。
系列文章:
浏览器 extension 插件开发系列(01) -- 前言和确认需求
浏览器 extension 插件开发系列(02) -- Chrome 插件的启动以及调试
浏览器 extension 插件开发系列(03) -- Firefox 插件的启动以及调试
浏览器 extension 插件开发系列(04) -- Safari 插件的添加以及调试
浏览器 extension 插件开发系列(05) -- Safari 插件申请开发者证书
浏览器 extension 插件开发系列(06) -- 各浏览器导航栏按钮的配置的点击出现的panel
浏览器 extension 插件开发系列(07) -- 获取各浏览器端的背景页
浏览器 extension 插件开发系列(08) -- 背景页启动和登录持久化
浏览器 extension 插件开发系列(09) -- popup以及其他前端页面的启动
浏览器 extension 插件开发系列(10) -- 事件驱动模型
浏览器 extension 插件开发系列(11) -- 登录模块(包括第三方登录和弹框)
浏览器 extension 插件开发系列(12) -- 实现右键菜单推送消息
浏览器 extension 插件开发系列(13) -- 实现消息过来出现桌面通知
浏览器 extension 插件开发系列(14) -- 点击reply出现回复小窗口
浏览器 extension 插件开发系列(15) -- chrome多文件上传(拖拽上传或者点击上传)
浏览器 extension 插件开发系列(16) -- Firefox 遇到的问题
浏览器 extension 插件开发系列(17) -- Safari 遇到的问题