Skip to content

Commit d845907

Browse files
committed
WindowListener APIs update
1 parent 41b2c72 commit d845907

File tree

3 files changed

+100
-51
lines changed

3 files changed

+100
-51
lines changed

api/WindowListener/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Usage description can be found in the [wiki](https://github.com/thundernest/addon-developer-support/wiki/Using-the-WindowListener-API-to-convert-a-Legacy-Overlay-WebExtension-into-a-MailExtension-for-Thunderbird-78).

api/WindowListener/implementation.js

Lines changed: 82 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,31 @@
22
* This file is provided by the addon-developer-support repository at
33
* https://github.com/thundernest/addon-developer-support
44
*
5+
* Version: 1.30
6+
* - replace setCharPref by setStringPref to cope with URTF-8 encoding
7+
*
8+
* Version: 1.29
9+
* - open options window centered
10+
*
11+
* Version: 1.28
12+
* - do not crash on missing icon
13+
*
14+
* Version: 1.27
15+
* - add openOptionsDialog()
16+
*
17+
* Version: 1.26
18+
* - pass WL object to legacy preference window
19+
*
20+
* Version: 1.25
21+
* - adding waitForMasterPassword
22+
*
23+
* Version: 1.24
24+
* - automatically localize i18n locale strings in injectElements()
25+
*
26+
* Version: 1.22
27+
* - to reduce confusions, only check built-in URLs as add-on URLs cannot
28+
* be resolved if a temp installed add-on has bin zipped
29+
*
530
* Version: 1.21
631
* - print debug messages only if add-ons are installed temporarily from
732
* the add-on debug page
@@ -71,6 +96,19 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
7196
if (this.debug) console.error("WindowListener API: " + msg);
7297
}
7398

99+
// async sleep function using Promise
100+
async sleep(delay) {
101+
let timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
102+
return new Promise(function(resolve, reject) {
103+
let event = {
104+
notify: function(timer) {
105+
resolve();
106+
}
107+
}
108+
timer.initWithCallback(event, delay, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
109+
});
110+
}
111+
74112
getAPI(context) {
75113
// track if this is the background/main context
76114
this.isBackgroundContext = (context.viewType == "background");
@@ -98,8 +136,17 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
98136
return {
99137
WindowListener: {
100138

139+
async waitForMasterPassword() {
140+
// Wait until master password has been entered (if needed)
141+
while (!Services.logins.isLoggedIn) {
142+
self.log("Waiting for master password.");
143+
await self.sleep(1000);
144+
}
145+
self.log("Master password has been entered.");
146+
},
147+
101148
aDocumentExistsAt(uriString) {
102-
self.log("Checking if document at <" + uriString + "> used in registration actually exits.");
149+
self.log("Checking if document at <" + uriString + "> used in registration actually exists.");
103150
try {
104151
let uriObject = Services.io.newURI(uriString);
105152
let content = Cu.readUTF8URI(uriObject);
@@ -114,30 +161,18 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
114161
self.pathToOptionsPage = optionsUrl.startsWith("chrome://")
115162
? optionsUrl
116163
: context.extension.rootURI.resolve(optionsUrl);
117-
118-
if (self.debug && !this.aDocumentExistsAt(self.pathToOptionsPage)) {
119-
self.error("Attempt to register non-existent options page: " + self.pathToOptionsPage
120-
+ ((optionsUrl != self.pathToOptionsPage) ? "\n(user provided options page was: " + optionsUrl + ")" : ""));
121-
self.pathToOptionsPage = null;
122-
}
123164
},
124165

125166
registerDefaultPrefs(defaultUrl) {
126167
let url = context.extension.rootURI.resolve(defaultUrl);
127168

128-
if (self.debug && !this.aDocumentExistsAt(url)) {
129-
self.error("Attempt to register non-existent default prefs script: " + url
130-
+ ((url != defaultUrl) ? "\n(user provided script path was: " + defaultUrl + ")" : ""));
131-
return;
132-
}
133-
134169
let prefsObj = {};
135170
prefsObj.Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
136171
prefsObj.pref = function(aName, aDefault) {
137172
let defaults = Services.prefs.getDefaultBranch("");
138173
switch (typeof aDefault) {
139174
case "string":
140-
return defaults.setCharPref(aName, aDefault);
175+
return defaults.setStringPref(aName, aDefault);
141176

142177
case "number":
143178
return defaults.setIntPref(aName, aDefault);
@@ -205,12 +240,6 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
205240
? jsFile
206241
: context.extension.rootURI.resolve(jsFile)
207242

208-
if (self.debug && !this.aDocumentExistsAt(path)) {
209-
self.error("Attempt to register a non-existent injector script: " + path
210-
+ "\nfor window " + windowHref
211-
+ ((path != jsFile) ? "\n(user provided script path was: " + jsFile + " )" : ""));
212-
return;
213-
}
214243
self.registeredWindows[windowHref] = path;
215244
} else {
216245
self.error("Window <" +windowHref + "> has already been registered");
@@ -224,12 +253,6 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
224253
self.pathToStartupScript = aPath.startsWith("chrome://")
225254
? aPath
226255
: context.extension.rootURI.resolve(aPath);
227-
228-
if (self.debug && !this.aDocumentExistsAt(self.pathToStartupScript)) {
229-
self.error("Attempt to register non-existent startup script: " + self.pathToStartupScript
230-
+ ((aPath != self.pathToStartupScript) ? "\n(user provided script path was: " + aPath + ")" : ""));
231-
self.pathToStartupScript = null;
232-
}
233256
},
234257

235258
registerShutdownScript(aPath) {
@@ -239,28 +262,19 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
239262
self.pathToShutdownScript = aPath.startsWith("chrome://")
240263
? aPath
241264
: context.extension.rootURI.resolve(aPath);
265+
},
242266

243-
if (self.debug && !this.aDocumentExistsAt(self.pathToShutdownScript)) {
244-
self.error("Attempt to register non-existent shutdown script: " + self.pathToShutdownScript
245-
+ ((aPath != self.pathToShutdownScript) ? "(user provided script path was: " + aPath + ")" : ""));
246-
self.pathToShutdownScript = null;
247-
}
267+
openOptionsDialog(windowId) {
268+
let window = context.extension.windowManager.get(windowId, context).window
269+
let WL = {}
270+
WL.extension = self.extension;
271+
WL.messenger = Array.from(self.extension.views).find(
272+
view => view.viewType === "background").xulBrowser.contentWindow
273+
.wrappedJSObject.browser;
274+
window.openDialog(self.pathToOptionsPage, "AddonOptions", "chrome,resizable,centerscreen", WL);
248275
},
249276

250277
async startListening() {
251-
// async sleep function using Promise
252-
async function sleep(delay) {
253-
let timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
254-
return new Promise(function(resolve, reject) {
255-
let event = {
256-
notify: function(timer) {
257-
resolve();
258-
}
259-
}
260-
timer.initWithCallback(event, delay, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
261-
});
262-
};
263-
264278
if (!self.isBackgroundContext)
265279
throw new Error("The WindowListener API may only be called from the background page.");
266280

@@ -332,16 +346,27 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
332346
let id = self.menu_addonPrefs_id + "_" + self.uniqueRandomID;
333347

334348
// Get the best size of the icon (16px or bigger)
335-
let iconSizes = Object.keys(self.extension.manifest.icons);
349+
let iconSizes = self.extension.manifest.icons
350+
? Object.keys(self.extension.manifest.icons)
351+
: [];
336352
iconSizes.sort((a,b)=>a-b);
337353
let bestSize = iconSizes.filter(e => parseInt(e) >= 16).shift();
338354
let icon = bestSize ? self.extension.manifest.icons[bestSize] : "";
339355

340356
let name = self.extension.manifest.name;
341-
let entry = window.MozXULElement.parseXULToFragment(
342-
`<menuitem class="menuitem-iconic" id="${id}" image="${icon}" label="${name}" />`);
357+
let entry = icon
358+
? window.MozXULElement.parseXULToFragment(
359+
`<menuitem class="menuitem-iconic" id="${id}" image="${icon}" label="${name}" />`)
360+
: window.MozXULElement.parseXULToFragment(
361+
`<menuitem id="${id}" label="${name}" />`);
362+
343363
element_addonPrefs.appendChild(entry);
344-
window.document.getElementById(id).addEventListener("command", function() {window.openDialog(self.pathToOptionsPage, "AddonOptions")});
364+
let WL = {}
365+
WL.extension = self.extension;
366+
WL.messenger = Array.from(self.extension.views).find(
367+
view => view.viewType === "background").xulBrowser.contentWindow
368+
.wrappedJSObject.browser;
369+
window.document.getElementById(id).addEventListener("command", function() {window.openDialog(self.pathToOptionsPage, "AddonOptions", "chrome,resizable,centerscreen", WL)});
345370
} catch (e) {
346371
Components.utils.reportError(e)
347372
}
@@ -361,7 +386,7 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
361386
// On my system it takes 70ms.
362387
let loaded = false;
363388
for (let i=0; i < 100 && !loaded; i++) {
364-
await sleep(100);
389+
await self.sleep(100);
365390
let targetWindow = mutation.target.contentWindow.wrappedJSObject;
366391
if (targetWindow && targetWindow.location.href == mutation.target.getAttribute("src") && targetWindow.document.readyState == "complete") {
367392
loaded = true;
@@ -415,7 +440,8 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
415440
if (window.hasOwnProperty(this.uniqueRandomID) && this.registeredWindows.hasOwnProperty(window.location.href)) {
416441
try {
417442
let uniqueRandomID = this.uniqueRandomID;
418-
443+
let extension = this.extension;
444+
419445
// Add reference to window to add-on scope
420446
window[this.uniqueRandomID].window = window;
421447
window[this.uniqueRandomID].document = window.document;
@@ -461,6 +487,10 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
461487
return null;
462488
}
463489

490+
function localize(entity) {
491+
let msg = entity.slice("__MSG_".length,-2);
492+
return extension.localeData.localizeMessage(msg)
493+
}
464494

465495
function injectChildren(elements, container) {
466496
if (debug) console.log(elements);
@@ -535,7 +565,8 @@ var WindowListener = class extends ExtensionCommon.ExtensionAPI {
535565
}
536566

537567
if (debug) console.log ("Injecting into root document:");
538-
injectChildren(Array.from(window.MozXULElement.parseXULToFragment(xulString, dtdFiles).children), window.document.documentElement);
568+
let localicedXulString = xulString.replace(/__MSG_(.*?)__/g, localize);
569+
injectChildren(Array.from(window.MozXULElement.parseXULToFragment(localicedXulString, dtdFiles).children), window.document.documentElement);
539570

540571
for (let bar of toolbarsToResolve) {
541572
let currentset = Services.xulStore.getValue(

api/WindowListener/schema.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,23 @@
4242
}
4343
]
4444
},
45+
{
46+
"name": "waitForMasterPassword",
47+
"type": "function",
48+
"async": true,
49+
"parameters": []
50+
},
51+
{
52+
"name": "openOptionsDialog",
53+
"type": "function",
54+
"parameters": [
55+
{
56+
"name": "windowId",
57+
"type": "integer",
58+
"description": "Id of the window the dialog should be opened from."
59+
}
60+
]
61+
},
4562
{
4663
"name": "startListening",
4764
"type": "function",

0 commit comments

Comments
 (0)