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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
|
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
"resource://devtools/shared/event-emitter.js");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
// Import the android PageActions module.
XPCOMUtils.defineLazyModuleGetter(this, "PageActions",
"resource://gre/modules/PageActions.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
IconDetails,
SingletonEventManager,
} = ExtensionUtils;
// WeakMap[Extension -> PageAction]
var pageActionMap = new WeakMap();
function PageAction(options, extension) {
this.id = null;
this.extension = extension;
this.icons = IconDetails.normalize({path: options.default_icon}, extension);
this.popupUrl = options.default_popup;
this.options = {
title: options.default_title || extension.name,
id: `{${extension.uuid}}`,
clickCallback: () => {
if (this.popupUrl) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
win.BrowserApp.addTab(this.popupUrl, {
selected: true,
parentId: win.BrowserApp.selectedTab.id,
});
} else {
this.emit("click");
}
},
};
this.shouldShow = false;
EventEmitter.decorate(this);
}
PageAction.prototype = {
show(tabId, context) {
if (this.id) {
return Promise.resolve();
}
if (this.options.icon) {
this.id = PageActions.add(this.options);
return Promise.resolve();
}
this.shouldShow = true;
// TODO(robwu): Remove dependency on contentWindow from this file. It should
// be put in a separate file called ext-c-pageAction.js.
// Note: Fennec is not going to be multi-process for the foreseaable future,
// so this layering violation has no immediate impact. However, it is should
// be done at some point.
let {contentWindow} = context.xulBrowser;
// TODO(robwu): Why is this contentWindow.devicePixelRatio, while
// convertImageURLToDataURL uses browserWindow.devicePixelRatio?
let {icon} = IconDetails.getPreferredIcon(this.icons, this.extension,
18 * contentWindow.devicePixelRatio);
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
return IconDetails.convertImageURLToDataURL(icon, contentWindow, browserWindow).then(dataURI => {
if (this.shouldShow) {
this.options.icon = dataURI;
this.id = PageActions.add(this.options);
}
}).catch(() => {
return Promise.reject({
message: "Failed to load PageAction icon",
});
});
},
hide(tabId) {
this.shouldShow = false;
if (this.id) {
PageActions.remove(this.id);
this.id = null;
}
},
setPopup(tab, url) {
// TODO: Only set the popup for the specified tab once we have Tabs API support.
this.popupUrl = url;
},
getPopup(tab) {
// TODO: Only return the popup for the specified tab once we have Tabs API support.
return this.popupUrl;
},
shutdown() {
this.hide();
},
};
/* eslint-disable mozilla/balanced-listeners */
extensions.on("manifest_page_action", (type, directive, extension, manifest) => {
let pageAction = new PageAction(manifest.page_action, extension);
pageActionMap.set(extension, pageAction);
});
extensions.on("shutdown", (type, extension) => {
if (pageActionMap.has(extension)) {
pageActionMap.get(extension).shutdown();
pageActionMap.delete(extension);
}
});
/* eslint-enable mozilla/balanced-listeners */
extensions.registerSchemaAPI("pageAction", "addon_parent", context => {
let {extension} = context;
return {
pageAction: {
onClicked: new SingletonEventManager(context, "pageAction.onClicked", fire => {
let listener = (event) => {
fire();
};
pageActionMap.get(extension).on("click", listener);
return () => {
pageActionMap.get(extension).off("click", listener);
};
}).api(),
show(tabId) {
return pageActionMap.get(extension)
.show(tabId, context)
.then(() => {});
},
hide(tabId) {
pageActionMap.get(extension).hide(tabId);
return Promise.resolve();
},
setPopup(details) {
// TODO: Use the Tabs API to get the tab from details.tabId.
let tab = null;
let url = details.popup && context.uri.resolve(details.popup);
pageActionMap.get(extension).setPopup(tab, url);
},
getPopup(details) {
// TODO: Use the Tabs API to get the tab from details.tabId.
let tab = null;
let popup = pageActionMap.get(extension).getPopup(tab);
return Promise.resolve(popup);
},
},
};
});
|