options/ 0000755 0000000 0000000 00000000000 14051011234 011236 5 ustar root root options/options.html 0000644 0000000 0000000 00000005015 14050747612 013637 0 ustar root root
__MSG_preferences__
options/options.js 0000644 0000000 0000000 00000011656 14051011234 013300 0 ustar root root import '/extlib/l10n.js';
//The object only works if retrieved through chrome.extension, but not
//through browser.extension or messenger.extension
var bgPage = chrome.extension.getBackgroundPage();
var prefs = bgPage.getPreferences();
var btnSelectStoragePath = document.getElementById("btnSelectStoragePath");
function isInputType(node, type){
return node.nodeName.toLowerCase() == "input" && node.type.toLowerCase() == type.toLowerCase();
}
async function selectStoragePath() {
// console.debug("selectStoragePath called");
let startDir = prefs.storage_path;
let profileDir = await bgPage.getProfileDirectory();
if (startDir.startsWith("[ProfD]")) {
try {
// console.debug(`profileDir: ${profileDir}; startDir: ${startDir}`);
startDir = await bgPage.appendRelativePath(profileDir, startDir.substring(7));
// console.debug(`startDir for selectStoragePath: ${startDir}`);
}
catch (e) {
// console.debug(`Directory does not exist: ${startDir}.`, e);
startDir = profileDir;
}
}
try {
bgPage.selectDirectory(startDir, bgPage.browser.i18n.getMessage("Select.storage.dir")).then((storagePath) => {
// console.debug(`selected storage path: ${storagePath}`);
if (storagePath == null) return;
//Check whether the new path is inside the profile directory
//and if yes, make the path relative to the profile.
if (storagePath.indexOf(profileDir) == 0) {
if (storagePath.length == profileDir.length) {
storagePath = "[ProfD]";
}
else {
storagePath = "[ProfD]"+storagePath.substr(profileDir.length+1);
}
}
let prefPath = document.getElementById("storage.path");
prefPath.value = storagePath;
prefs.storage_path = storagePath;
});
}
catch (e) {
console.error(e);
}
}
async function savePrefs() {
const storagePathChanged = document.getElementById('storage.path').value != prefs.storage_path;
for (const node of document.querySelectorAll('[data-preference]')) {
const pref = node.dataset.preference;
//console.debug(`Saving preference: ${pref}`);
if (pref.startsWith("tag.")) {
switch (pref) {
case "tag.color":
bgPage.setTbPref("mailnews.tags.xnote.color", node.value);
break;
case "tag.name":
bgPage.setTbPref("mailnews.tags.xnote.tag", node.value);
break;
default:
console.error(`Unknown tag preference ${pref}`);
}
}
else {
switch(node.nodeName) {
case "SELECT":
for(let option of node.querySelectorAll("option")){
if(option.selected){
prefs[pref] = node.value;
break;
}
}
break;
case "INPUT":
if(isInputType(node,"checkbox")){
// debugger;
prefs[pref] = node.checked;
} else if(isInputType(node, "radio") && node.checked){
prefs[pref] = node.value;
} else if (isInputType(node, "number")) {
prefs[pref] = parseInt(node.value);
}
else {
prefs[pref] = node.value;
}
break;
default:
console.error(`Unknown node type ${node.nodeName}`);
console.error(node);
}
}
}
bgPage.setPreferences(prefs);
}
async function initOptions() {
//console.debug(prefs);
for (const node of document.querySelectorAll('[data-preference]')) {
const pref = node.dataset.preference;
//console.debug(`Loading preference: ${pref}`);
const value = prefs[pref];
if (pref.startsWith("tag.")) {
switch (pref) {
case "tag.color":
node.value = await bgPage.getTbPref("mailnews.tags.xnote.color");
break;
case "tag.name":
node.value = await bgPage.getTbPref("mailnews.tags.xnote.tag");
break;
default:
console.error(`Unknown tag preference ${pref}`);
}
}
else {
switch(node.nodeName) {
case "SELECT":
for(let option of node.querySelectorAll("option")){
if(option.value == value){
option.selected = true;
break;
}
}
break;
case "INPUT":
if(isInputType(node, "checkbox")) {
node.checked = value;
} else if(isInputType(node, "radio")) {
node.checked = (value === node.value);
} else {
node.value = value;
}
break;
default:
console.error(`Unknown node type ${node.nodeName}`);
console.error(node);
}
}
}
btnSave.addEventListener('click', savePrefs);
btnSelectStoragePath.addEventListener('click', selectStoragePath);
}
document.addEventListener('DOMContentLoaded', () => {
initOptions();
}, { once: true });
mDisplay.js 0000644 0000000 0000000 00000007053 14050747612 011707 0 ustar root root
var xnote = ""; // k b\nbbbb\nbbbbbbbbbbbböklllll11111111222222222222222333333333llllcbvvv42vvvvvv";
var xnoteOrig = "";
var dateOrig = "";
var btnState = 0;//debugger;
//console.log("loading mdisplay script");
function notify(message) {
// console.log("received in msgDisplay from background");
// console.log("Msg:", message.XNoteText);
//debugger;
try {
let old = document.getElementById("xnote_msgDisplay");
old.remove();
}
catch (e) { };
function truncate(fullStr, strLen, separator) {
if (fullStr.length <= strLen) return fullStr;
// acknowledgement to https://gist.github.com/ugurozpinar/10263513
separator = separator || '...';
var sepLen = separator.length,
charsToShow = strLen - sepLen,
frontChars = Math.ceil(charsToShow / 2),
backChars = Math.floor(charsToShow / 2);
return fullStr.substr(0, frontChars) + separator + fullStr.substr(fullStr.length - backChars);
};
xnoteOrig = xnote = message.text;
dateOrig = message.date;
if (xnote.length > 0) {
let no_linebreak = xnote.replace(/(\r\n|\n|\r)/gm, " ");
let no_double_space = no_linebreak.replace(/\s+/g, " ");
let trunc = truncate(no_double_space, 350, "...");
let imgMax = document.createElement("img");
imgMax.src = messenger.runtime.getURL("icons/iconfinder_maximize-2_2561250MIT16.png");
// https://ourcodeworld.com/articles/read/376/how-to-strip-html-from-a-string-extract-only-text-content-in-javascript
let strippedHtml = "";
strippedHtml = trunc.replace(/<[^>]+>/g, ''); //html entities are not converted, <> are stripped
//document.documentElement.firstChild.appendChild(img);
let text3 = "" +
"
XNote " + message.date + ": " + strippedHtml + " ";
document.documentElement.firstChild.insertAdjacentHTML("beforebegin", text3);
let btn = document.getElementById("chgSize");
btn.addEventListener("click", showAll, false);
};
};
function handleResponse(message) {
//console.log(`note Message from the background script: `, message);
}
function showAll() {
//console.log("all", xnoteOrig);
let img = document.getElementById("aa");
img.src = messenger.runtime.getURL("icons/iconfinder_minimize-2_2561246MIT16.png");
// btn.textContent = "Compact";
if (btnState == 0) {
let note = document.getElementById("xnoteShort");
note.textContent = "";//remove();
let div = document.createElement("div");
div.setAttribute("id", "fullNoteDiv");
note.append(div);
let span = document.getElementById("xnoteFull");
// let fullnote = "" + xnote + "
";
//span.insertAdjacentHTML("afterend",fullnote);
let brNote = xnoteOrig.replace(/(?:\r\n|\r|\n)/g, '
');//xnoteOrig.replace("\r", "
");
div.insertAdjacentHTML("afterend", brNote);
btnState = 1;
}
else {
btnState = 0;
this.value = "Show all";
let all = document.getElementById("xnote_msgDisplay");
all.remove();
notify({ text: xnoteOrig, date: dateOrig });
}
};
async function startup() {
await messenger.runtime.onMessage.addListener(notify);
let message = await messenger.runtime.sendMessage({ command: "getXNote" });
//console.log(message);// sending.then(handleResponse);//
notify(message);
}
startup();
_locales/ 0000755 0000000 0000000 00000000000 13740054446 011344 5 ustar root root _locales/nl_NL/ 0000755 0000000 0000000 00000000000 14050747610 012343 5 ustar root root _locales/nl_NL/messages.json 0000644 0000000 0000000 00000002330 14050747610 015043 0 ustar root root {
"extensionName": {
"message": "XNote++",
"description": "Name of the extension."
},
"extensionDescription": {
"message": "Add Post-It to your mails.",
"description": "Description of the extension."
},
"preferences": { "message": "Voorkeuren"},
"btn.browse": {"message": "Browse..."},
"btn.save" : {"message": "Bespaar"},
"Window.defaults": {"message": "Standaard Venster"},
"Width": {"message": "Breedte"},
"Height": {"message": "Hoogte"},
"Window.defPosition" : {"message": "Standaard positie van het venster"},
"Window.horPos" : {"message": "Horizontale"},
"Window.vertPos" : {"message": "Verticaal"},
"storage.path": {"message": "Opslaan als"},
"Message.tag": {"message": "Bericht Label"},
"Name": {"message": "Naam"},
"Color": {"message": "Kleur"},
"use.tag": {"message": "Gebruik Label?"},
"Behavior": {"message": "Gedrag"},
"show.on.select": {"message": "Notitie tonen bij selectie"},
"show.first.x.chars.in.col": {"message": "Toon eerste x karakters in Xnote Kolom"},
"Select.storage.dir": {"message": "Selecteer standaard directory voor Xnote."},
"show.in.messageDisplay": {"message": "Show XNote summary in messsage display"}
}
_locales/pt_BR/ 0000755 0000000 0000000 00000000000 14050747610 012347 5 ustar root root _locales/pt_BR/messages.json 0000644 0000000 0000000 00000002336 14050747610 015055 0 ustar root root {
"extensionName": {
"message": "XNote++",
"description": "Name of the extension."
},
"extensionDescription": {
"message": "Add Post-It to your mails.",
"description": "Description of the extension."
},
"preferences": { "message": "Config"},
"btn.browse": {"message": "Localizar..."},
"btn.save" : {"message": "Salvar"},
"Window.defaults": {"message": "Configuração Padrão"},
"Width": {"message": "Largura"},
"Height": {"message": "Altura"},
"Window.defPosition" : {"message": "Posição padrão da janela"},
"Window.horPos" : {"message": "Horizontal"},
"Window.vertPos" : {"message": "Vertical"},
"storage.path": {"message": "Salvar em..."},
"Message.tag": {"message": "Etiqueta da mensagem"},
"Name": {"message": "Nome"},
"Color": {"message": "Cor"},
"use.tag": {"message": "Usar etiqueta?"},
"Behavior": {"message": "Processo"},
"show.on.select": {"message": "Exibir XNote ao selecionar e-mail"},
"show.first.x.chars.in.col": {"message": "Exibir X caracteres no campo XNote"},
"Select.storage.dir": {"message": "Selecione a pasta de armazenamento do XNotes."},
"show.in.messageDisplay": {"message": "Show XNote summary in messsage display"}
}
_locales/ja_JP/ 0000755 0000000 0000000 00000000000 14050747610 012324 5 ustar root root _locales/ja_JP/messages.json 0000644 0000000 0000000 00000002513 14050747610 015027 0 ustar root root {
"extensionName": {
"message": "XNote++",
"description": "Name of the extension."
},
"extensionDescription": {
"message": "メッセージに付箋を貼り付ける機能を追加します。",
"description": "Description of the extension."
},
"preferences": { "message": "設定"},
"btn.browse": {"message": "参照"},
"btn.save" : {"message": "保存"},
"Window.defaults": {"message": "デフォルトのサイズ"},
"Width": {"message": "幅"},
"Height": {"message": "高さ"},
"Window.defPosition" : {"message": "Fenster 窓の標準位置"},
"Window.horPos" : {"message": "水平方向"},
"Window.vertPos" : {"message": "垂直方向"},
"storage.path": {"message": "メモの保存先"},
"Message.tag": {"message": "タグとの連動"},
"Name": {"message": "タグ名"},
"Color": {"message": "色"},
"use.tag": {"message": "タグを使う"},
"Behavior": {"message": "動作"},
"show.on.select": {"message": "メッセージを選択したらメモを表示する"},
"show.first.x.chars.in.col": {"message": "スレッドペインのXNoteカラムに先頭数文字を表示する"},
"Select.storage.dir": {"message": "メモの保存先を選択してください"},
"show.in.messageDisplay": {"message": "Show XNote summary in messsage display"}
}
_locales/de/ 0000755 0000000 0000000 00000000000 14050747610 011731 5 ustar root root _locales/de/messages.json 0000644 0000000 0000000 00000002434 14050747610 014436 0 ustar root root {
"extensionName": {
"message": "XNote++",
"description": "Name of the extension."
},
"extensionDescription": {
"message": "Notizen zu E-Mails hinzufügen.",
"description": "Description of the extension."
},
"preferences": { "message": "Einstellungen"},
"btn.browse": {"message": "Durchsuchen..."},
"btn.save" : {"message": "Speichern"},
"Window.defaults": {"message": "Fenster Standardgröße"},
"Width": {"message": "Breite"},
"Height": {"message": "Höhe"},
"Window.defPosition" : {"message": "Fenster Standardposition"},
"Window.horPos" : {"message": "Horizontal"},
"Window.vertPos" : {"message": "Vertikal"},
"storage.path": {"message": "Speicherpfad"},
"Message.tag": {"message": "Nachrichtenschlagwort"},
"use.tag": {"message": "Schlagwort verwenden?"},
"Name": {"message": "Name"},
"Color": {"message": "Farbe"},
"Behavior": {"message": "Verhalten"},
"show.on.select": {"message": "XNote nach E-Mail Auswahl anzeigen"},
"show.first.x.chars.in.col": {"message": "Zeige die ersten X Zeichen in der XNote Spalte"},
"Select.storage.dir": {"message": "Wählen Sie bitte das Verzeichnis zum Speichern der XNotes."},
"show.in.messageDisplay": {"message": "XNote Zusammenfassung in der email anzeigen"}
}
_locales/it_IT/ 0000755 0000000 0000000 00000000000 14050747610 012351 5 ustar root root _locales/it_IT/messages.json 0000644 0000000 0000000 00000002416 14050747610 015056 0 ustar root root {
"extensionName": {
"message": "XNote++",
"description": "Name of the extension."
},
"extensionDescription": {
"message": "Add Post-It to your mails.",
"description": "Description of the extension."
},
"preferences": { "message": "Impostazioni"},
"btn.browse": {"message": "Sfoglia..."},
"btn.save" : {"message": "Salva"},
"Window.defaults": {"message": "Finestra default"},
"Width": {"message": "Larghezza"},
"Height": {"message": "Altezza"},
"Window.defPosition" : {"message": "Fenster Standardposition"},
"Window.horPos" : {"message": "Orizzontale"},
"Window.vertPos" : {"message": "Verticale"},
"storage.path": {"message": "Percorso salvataggio"},
"Message.tag": {"message": "Etichetta messaggio"},
"Name": {"message": "Nome"},
"Color": {"message": "Colore"},
"use.tag": {"message": "Usare etichetta?"},
"Behavior": {"message": "Comportamento"},
"show.on.select": {"message": "Mostra nota quando si seleziona il messaggio"},
"show.first.x.chars.in.col": {"message": "Mostra i primi x caratteri nella colonna XNote"},
"Select.storage.dir": {"message": "Selezionare la cartella dove salvare i file XNote."},
"show.in.messageDisplay": {"message": "Show XNote summary in messsage display"}
}
_locales/fr_FR/ 0000755 0000000 0000000 00000000000 14050747610 012337 5 ustar root root _locales/fr_FR/messages.json 0000644 0000000 0000000 00000002557 14050747610 015052 0 ustar root root {
"extensionName": {
"message": "XNote++",
"description": "Name of the extension."
},
"extensionDescription": {
"message": "Add Post-It to your mails.",
"description": "Description of the extension."
},
"preferences": { "message": "Préférences"},
"btn.browse": {"message": "Parcourir…"},
"btn.save" : {"message": "Sauvegarder"},
"Window.defaults": {"message": "Taille par défaut d'une nouvelle note"},
"Width": {"message": "Largeur"},
"Height": {"message": "Hauteur"},
"Window.defPosition" : {"message": "Position par défaut d'une nouvelle note"},
"Window.horPos" : {"message": "Horizontal"},
"Window.vertPos" : {"message": "Vertikal"},
"storage.path": {"message": "Chemin de stockage des notes"},
"Message.tag": {"message": "Étiquette de message"},
"Name": {"message": "Nom"},
"Color": {"message": "Couleur"},
"use.tag": {"message": "Utiliser l'étiquette de message ?"},
"Behavior": {"message": "Comportement"},
"show.on.select": {"message": "Afficher la note du message sélectionné"},
"show.first.x.chars.in.col": {"message": "Montrer les n premiers caractères de la note dans la colonne XNote"},
"Select.storage.dir": {"message": "Veuillez sélectionner le dossier d'enregistrement des notes."},
"show.in.messageDisplay": {"message": "Show XNote summary in messsage display"}
} _locales/en_US/ 0000755 0000000 0000000 00000000000 14050747610 012352 5 ustar root root _locales/en_US/messages.json 0000644 0000000 0000000 00000002307 14050747610 015056 0 ustar root root {
"extensionName": {
"message": "XNote++",
"description": "Name of the extension."
},
"extensionDescription": {
"message": "Add Post-It to your mails.",
"description": "Description of the extension."
},
"preferences": { "message": "Preferences"},
"btn.browse": {"message": "Browse..."},
"btn.save" : {"message": "Save"},
"Window.defaults": {"message": "Window default size"},
"Width": {"message": "Width"},
"Height": {"message": "Height"},
"Window.defPosition" : {"message": "Window default position"},
"Window.horPos" : {"message": "Horizontal"},
"Window.vertPos" : {"message": "Vertical"},
"storage.path": {"message": "Storage path"},
"Message.tag": {"message": "Message tag"},
"Name": {"message": "Name"},
"Color": {"message": "Color"},
"use.tag": {"message": "Use tag?"},
"Behavior": {"message": "Behavior"},
"show.on.select": {"message": "Show note on message selection"},
"show.first.x.chars.in.col": {"message": "Show first X characters in XNote column"},
"Select.storage.dir": {"message": "Please select the XNote storage directory."},
"show.in.messageDisplay": {"message": "Show XNote summary in messsage display"}
}
_locales/gl/ 0000755 0000000 0000000 00000000000 14050747610 011743 5 ustar root root _locales/gl/messages.json 0000644 0000000 0000000 00000002412 14050747610 014444 0 ustar root root {
"extensionName": {
"message": "XNote++",
"description": "Name of the extension."
},
"extensionDescription": {
"message": "Add Post-It to your mails.",
"description": "Description of the extension."
},
"preferences": { "message": "Preferencias"},
"btn.browse": {"message": "Examinar..."},
"btn.save" : {"message": "Save"},
"Window.defaults": {"message": "Xanela predeterminada"},
"Width": {"message": "Largura"},
"Height": {"message": "Altura"},
"Window.defPosition" : {"message": "Posición predeterminada da xanela"},
"Window.horPos" : {"message": "Horizontal"},
"Window.vertPos" : {"message": "Vertical"},
"storage.path": {"message": "Ruta de almacenamento"},
"Message.tag": {"message": "Etiqueta de mensaxe"},
"Name": {"message": "Nome"},
"Color": {"message": "Cor"},
"use.tag": {"message": "Usar etiqueta?"},
"Behavior": {"message": "Comportamento"},
"show.on.select": {"message": "Amosar nota ao selecionar a mensaxe"},
"show.first.x.chars.in.col": {"message": "Amosar primeiros X caracteres na columna XNote"},
"Select.storage.dir": {"message": "Selecione o cartafol de almacenamento para XNote."},
"show.in.messageDisplay": {"message": "Show XNote summary in messsage display"}
}
_locales/pl_PL/ 0000755 0000000 0000000 00000000000 14050747610 012347 5 ustar root root _locales/pl_PL/messages.json 0000644 0000000 0000000 00000002410 14050747610 015046 0 ustar root root {
"extensionName": {
"message": "XNote++",
"description": "Name of the extension."
},
"extensionDescription": {
"message": "Add Post-It to your mails.",
"description": "Description of the extension."
},
"preferences": { "message": "Preferencje"},
"btn.browse": {"message": "Otwórz..."},
"btn.save" : {"message": "Zapisz"},
"Window.defaults": {"message": "Domyślny rozmiar okna"},
"Width": {"message": "Szerokość"},
"Height": {"message": "Wysokość"},
"Window.defPosition" : {"message": "Standardowa pozycja okna"},
"Window.horPos" : {"message": "Horyzontalny"},
"Window.vertPos" : {"message": "Pionowo"},
"storage.path": {"message": "Ścieżka do bazy danych"},
"Message.tag": {"message": "Etykieta wiadomości"},
"Name": {"message": "Nazwa"},
"Color": {"message": "Kolor"},
"use.tag": {"message": "Użyj etykiety?"},
"Behavior": {"message": "Ustawienia notatek"},
"show.on.select": {"message": "Pokaż po wybraniu wiadomości"},
"show.first.x.chars.in.col": {"message": "Pokaż pierwszych X znaków w kolumnie XNote:"},
"Select.storage.dir": {"message": "Proszę wybrać ścieżkę zapisu dla XNote."},
"show.in.messageDisplay": {"message": "Show XNote summary in messsage display"}
}
manifest.json 0000644 0000000 0000000 00000004262 14114406444 012263 0 ustar root root {
"manifest_version": 2,
"name": "__MSG_extensionName__",
"description": "__MSG_extensionDescription__",
"version": "3.2.4",
"author": "Lorenz Froihofer: support for TB >= 3.0, since Dec. 2009, Klaus Bücher since TB 78",
"homepage_url": "https://github.com/xnotepp/xnote/wiki",
"applications": {
"gecko": {
"id": "xnote@froihofer.net",
"strict_min_version": "91.0",
"strict_max_version": "91.*"
}
},
"default_locale" : "en_US",
"icons": {
"32": "chrome/skin/icon32.png"
},
"browser_action": {
"default_title": "XNote++",
"default_icon": "chrome/skin/icon32.png"
},
"options_ui": {
"page": "options/options.html"
},
"permissions": ["activeTab", "tabs", "storage", "messagesRead", "messagesModify", "clipboardWrite"],
"background": {
"scripts": ["xn-background.js"]
},
"experiment_apis": {
"WindowListener": {
"schema": "experiment-apis/WindowListener/schema.json",
"parent": {
"scopes": ["addon_parent"],
"paths": [["WindowListener"]],
"script": "experiment-apis/WindowListener/implementation.js"
}
},
"NotifyTools": {
"schema": "experiment-apis/NotifyTools/schema.json",
"parent": {
"scopes": ["addon_parent"],
"paths": [["NotifyTools"]],
"script": "experiment-apis/NotifyTools/implementation.js"
}
},
"xnoteUtilities": {
"schema": "experiment-apis/Utilities/utilities.json",
"parent": {
"scopes": ["addon_parent"],
"paths": [["xnoteUtilities"]],
"script": "experiment-apis/Utilities/utilities.js"
}
},
"xnoteapi": {
"schema": "experiment-apis/xnote/xnote-experiments.json",
"parent": {
"scopes": ["addon_parent"],
"paths": [["xnoteapi"]],
"script": "experiment-apis/xnote/xnote-experiments.js"
}
},
"xnotefiles": {
"schema": "experiment-apis/files/files-api.json",
"parent": {
"scopes": ["addon_parent"],
"paths": [["xnotefiles"]],
"script": "experiment-apis/files/files-api.js"
}
}
}
} experiment-apis/ 0000755 0000000 0000000 00000000000 14050747610 012672 5 ustar root root experiment-apis/files/ 0000755 0000000 0000000 00000000000 14051011234 013757 5 ustar root root experiment-apis/files/files-api.js 0000644 0000000 0000000 00000004003 14051011234 016163 0 ustar root root var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm"),
{ Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"),
win = Services.wm.getMostRecentWindow("mail:3pane");
var xnotefiles = class extends ExtensionCommon.ExtensionAPI {
getAPI(context) {
return {
xnotefiles: {
selectDirectory : async function(window, startDirectory, title) {
let domWindow = window;
if (domWindow == null) {
domWindow = Services.wm.getMostRecentWindow(null);
}
let fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(Components.interfaces.nsIFilePicker);
fp.init(domWindow, title, fp.modeGetFolder);
let FileUtils = ChromeUtils.import("resource://gre/modules/FileUtils.jsm").FileUtils;
let startDir = new FileUtils.File(startDirectory);
fp.displayDirectory = startDir;
return new Promise(function(resolve, reject) {
fp.open(rv => {
if(rv === fp.returnOK){
resolve(fp.file.path);
}
});
})
},
getProfileDirectory: async function() {
var directoryService = Components.classes['@mozilla.org/file/directory_service;1']
.getService(Components.interfaces.nsIProperties);
let profileDir = directoryService.get('ProfD', Components.interfaces.nsIFile);
// console.debug(`getProfileDir returns: ${profileDir.path}`);
return profileDir.path;
},
appendRelativePath: async function(path, extension) {
let FileUtils = ChromeUtils.import("resource://gre/modules/FileUtils.jsm").FileUtils;
var result = new FileUtils.File(path);
result.appendRelativePath(extension);
// console.debug(result);
// console.debug(`appendRelativePath result: ${result.path}`);
return result.path;
}
}
}
};
}
experiment-apis/files/files-api.json 0000644 0000000 0000000 00000002614 13761405776 016557 0 ustar root root [
{
"namespace": "xnotefiles",
"functions": [
{
"name": "selectDirectory",
"type": "function",
"async": true,
"parameters": [
{
"name": "window",
"type": "any",
"description": "File picker depends on this window. Pass 'null' to use the last top level window."
},
{
"name": "startDirectory",
"type": "string",
"description": "Directory to show initially to the user."
},
{
"name": "title",
"type": "string",
"description": "Title of the dialog."
}
]
},
{
"name": "getProfileDirectory",
"type": "function",
"async": true,
"parameters": []
},
{
"name": "appendRelativePath",
"type": "function",
"async": true,
"description": "Allows to connect two path segments by taking care of the platform-specific path separator.",
"parameters": [
{
"name": "path",
"type": "string",
"description": "Base path"
},
{
"name": "extension",
"type": "string",
"description": "Additional path to add to the base path."
}
]
}
]
}
]
experiment-apis/WindowListener/ 0000755 0000000 0000000 00000000000 14101077270 015642 5 ustar root root experiment-apis/WindowListener/schema.json 0000644 0000000 0000000 00000005737 14046775662 020033 0 ustar root root [
{
"namespace": "WindowListener",
"functions": [
{
"name": "registerDefaultPrefs",
"type": "function",
"parameters": [
{
"name": "aPath",
"type": "string",
"description": "Relative path to the default file."
}
]
},
{
"name": "registerOptionsPage",
"type": "function",
"parameters": [
{
"name": "aPath",
"type": "string",
"description": "Path to the options page, which should be made accessible in the (legacy) Add-On Options menu."
}
]
},
{
"name": "registerChromeUrl",
"type": "function",
"description": "Register folders which should be available as chrome:// urls (as defined in the legacy chrome.manifest)",
"parameters": [
{
"name": "data",
"type": "array",
"items": {
"type": "array",
"items" : {
"type": "string"
}
},
"description": "Array of manifest url definitions (content, locale, resource)"
}
]
},
{
"name": "waitForMasterPassword",
"type": "function",
"async": true,
"parameters": []
},
{
"name": "openOptionsDialog",
"type": "function",
"parameters": [
{
"name": "windowId",
"type": "integer",
"description": "Id of the window the dialog should be opened from."
}
]
},
{
"name": "startListening",
"type": "function",
"async": true,
"parameters": []
},
{
"name": "registerWindow",
"type": "function",
"parameters": [
{
"name": "windowHref",
"type": "string",
"description": "Url of the window, which should be listen for."
},
{
"name": "jsFile",
"type": "string",
"description": "Path to the JavaScript file, which should be loaded into the window."
}
]
},
{
"name": "registerStartupScript",
"type": "function",
"parameters": [
{
"name": "aPath",
"type": "string",
"description": "Path to a JavaScript file, which should be executed on add-on startup. The script will be executed after the main application window has been sucessfully loaded."
}
]
},
{
"name": "registerShutdownScript",
"type": "function",
"parameters": [
{
"name": "aPath",
"type": "string",
"description": "Path to a JavaScript file, which should be executed on add-on shutdown."
}
]
}
]
}
]
experiment-apis/WindowListener/implementation.js 0000644 0000000 0000000 00000125704 14071265224 021242 0 ustar root root /*
* This file is provided by the addon-developer-support repository at
* https://github.com/thundernest/addon-developer-support
*
* Version: 1.56
*
* Author: John Bieling (john@thunderbird.net)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// Import some things we need.
var { ExtensionCommon } = ChromeUtils.import(
"resource://gre/modules/ExtensionCommon.jsm"
);
var { ExtensionSupport } = ChromeUtils.import(
"resource:///modules/ExtensionSupport.jsm"
);
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var WindowListener = class extends ExtensionCommon.ExtensionAPI {
log(msg) {
if (this.debug) console.log("WindowListener API: " + msg);
}
getThunderbirdVersion() {
let parts = Services.appinfo.version.split(".");
return {
major: parseInt(parts[0]),
minor: parseInt(parts[1]),
revision: parts.length > 2 ? parseInt(parts[2]) : 0,
}
}
getCards(e) {
// This gets triggered by real events but also manually by providing the outer window.
// The event is attached to the outer browser, get the inner one.
let doc;
// 78,86, and 87+ need special handholding. *Yeah*.
if (this.getThunderbirdVersion().major < 86) {
let ownerDoc = e.document || e.target.ownerDocument;
doc = ownerDoc.getElementById("html-view-browser").contentDocument;
} else if (this.getThunderbirdVersion().major < 87) {
let ownerDoc = e.document || e.target;
doc = ownerDoc.getElementById("html-view-browser").contentDocument;
} else {
doc = e.document || e.target;
}
return doc.querySelectorAll("addon-card");
}
// Add pref entry to 68
add68PrefsEntry(event) {
let id = this.menu_addonPrefs_id + "_" + this.uniqueRandomID;
// Get the best size of the icon (16px or bigger)
let iconSizes = this.extension.manifest.icons
? Object.keys(this.extension.manifest.icons)
: [];
iconSizes.sort((a, b) => a - b);
let bestSize = iconSizes.filter((e) => parseInt(e) >= 16).shift();
let icon = bestSize ? this.extension.manifest.icons[bestSize] : "";
let name = this.extension.manifest.name;
let entry = icon
? event.target.ownerGlobal.MozXULElement.parseXULToFragment(
``
)
: event.target.ownerGlobal.MozXULElement.parseXULToFragment(
``
);
event.target.appendChild(entry);
let noPrefsElem = event.target.querySelector('[disabled="true"]');
// using collapse could be undone by core, so we use display none
// noPrefsElem.setAttribute("collapsed", "true");
noPrefsElem.style.display = "none";
event.target.ownerGlobal.document
.getElementById(id)
.addEventListener("command", this);
}
// Event handler for the addon manager, to update the state of the options button.
handleEvent(e) {
switch (e.type) {
// 68 add-on options menu showing
case "popupshowing":
{
this.add68PrefsEntry(e);
}
break;
// 78/88 add-on options menu/button click
case "click":
{
e.preventDefault();
e.stopPropagation();
let WL = {};
WL.extension = this.extension;
WL.messenger = this.getMessenger(this.context);
let w = Services.wm.getMostRecentWindow("mail:3pane");
w.openDialog(
this.pathToOptionsPage,
"AddonOptions",
"chrome,resizable,centerscreen",
WL
);
}
break;
// 68 add-on options menu command
case "command":
{
let WL = {};
WL.extension = this.extension;
WL.messenger = this.getMessenger(this.context);
e.target.ownerGlobal.openDialog(
this.pathToOptionsPage,
"AddonOptions",
"chrome,resizable,centerscreen",
WL
);
}
break;
// update, ViewChanged and manual call for add-on manager options overlay
default: {
let cards = this.getCards(e);
for (let card of cards) {
// Setup either the options entry in the menu or the button
//window.document.getElementById(id).addEventListener("command", function() {window.openDialog(self.pathToOptionsPage, "AddonOptions", "chrome,resizable,centerscreen", WL)});
if (card.addon.id == this.extension.id) {
let optionsMenu =
(this.getThunderbirdVersion().major > 78 && this.getThunderbirdVersion().major < 88) ||
(this.getThunderbirdVersion().major == 78 && this.getThunderbirdVersion().minor < 10) ||
(this.getThunderbirdVersion().major == 78 && this.getThunderbirdVersion().minor == 10 && this.getThunderbirdVersion().revision < 2);
if (optionsMenu) {
// Options menu in 78.0-78.10 and 79-87
let addonOptionsLegacyEntry = card.querySelector(
".extension-options-legacy"
);
if (card.addon.isActive && !addonOptionsLegacyEntry) {
let addonOptionsEntry = card.querySelector(
"addon-options panel-list panel-item[action='preferences']"
);
addonOptionsLegacyEntry = card.ownerDocument.createElement(
"panel-item"
);
addonOptionsLegacyEntry.setAttribute(
"data-l10n-id",
"preferences-addon-button"
);
addonOptionsLegacyEntry.classList.add(
"extension-options-legacy"
);
addonOptionsEntry.parentNode.insertBefore(
addonOptionsLegacyEntry,
addonOptionsEntry
);
card
.querySelector(".extension-options-legacy")
.addEventListener("click", this);
} else if (!card.addon.isActive && addonOptionsLegacyEntry) {
addonOptionsLegacyEntry.remove();
}
} else {
// Add-on button in 88
let addonOptionsButton = card.querySelector(
".windowlistener-options-button"
);
if (card.addon.isActive && !addonOptionsButton) {
addonOptionsButton = card.ownerDocument.createElement("button");
addonOptionsButton.classList.add("windowlistener-options-button");
addonOptionsButton.classList.add("extension-options-button");
card.optionsButton.parentNode.insertBefore(
addonOptionsButton,
card.optionsButton
);
card
.querySelector(".windowlistener-options-button")
.addEventListener("click", this);
} else if (!card.addon.isActive && addonOptionsButton) {
addonOptionsButton.remove();
}
}
}
}
}
}
}
// Some tab/add-on-manager related functions
getTabMail(window) {
return window.document.getElementById("tabmail");
}
// returns the outer browser, not the nested browser of the add-on manager
// events must be attached to the outer browser
getAddonManagerFromTab(tab) {
if (tab.browser) {
let win = tab.browser.contentWindow;
if (win && win.location.href == "about:addons") {
return win;
}
}
}
getAddonManagerFromWindow(window) {
let tabMail = this.getTabMail(window);
for (let tab of tabMail.tabInfo) {
let win = this.getAddonManagerFromTab(tab);
if (win) {
return win;
}
}
}
setupAddonManager(managerWindow, forceLoad = false) {
if (!managerWindow) {
return;
}
if (!(
managerWindow &&
managerWindow[this.uniqueRandomID] &&
managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners
)) {
managerWindow.document.addEventListener("ViewChanged", this);
managerWindow.document.addEventListener("update", this);
managerWindow.document.addEventListener("view-loaded", this);
managerWindow[this.uniqueRandomID] = {};
managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners = true;
}
if (forceLoad) this.handleEvent(managerWindow);
}
getMessenger(context) {
let apis = ["storage", "runtime", "extension", "i18n"];
function getStorage() {
let localstorage = null;
try {
localstorage = context.apiCan.findAPIPath("storage");
localstorage.local.get = (...args) =>
localstorage.local.callMethodInParentProcess("get", args);
localstorage.local.set = (...args) =>
localstorage.local.callMethodInParentProcess("set", args);
localstorage.local.remove = (...args) =>
localstorage.local.callMethodInParentProcess("remove", args);
localstorage.local.clear = (...args) =>
localstorage.local.callMethodInParentProcess("clear", args);
} catch (e) {
console.info("Storage permission is missing");
}
return localstorage;
}
let messenger = {};
for (let api of apis) {
switch (api) {
case "storage":
XPCOMUtils.defineLazyGetter(messenger, "storage", () => getStorage());
break;
default:
XPCOMUtils.defineLazyGetter(messenger, api, () =>
context.apiCan.findAPIPath(api)
);
}
}
return messenger;
}
error(msg) {
if (this.debug) console.error("WindowListener API: " + msg);
}
// async sleep function using Promise
async sleep(delay) {
let timer = Components.classes["@mozilla.org/timer;1"].createInstance(
Components.interfaces.nsITimer
);
return new Promise(function (resolve, reject) {
let event = {
notify: function (timer) {
resolve();
},
};
timer.initWithCallback(
event,
delay,
Components.interfaces.nsITimer.TYPE_ONE_SHOT
);
});
}
getAPI(context) {
// Track if this is the background/main context
if (context.viewType != "background")
throw new Error(
"The WindowListener API may only be called from the background page."
);
this.context = context;
this.uniqueRandomID = "AddOnNS" + context.extension.instanceId;
this.menu_addonPrefs_id = "addonPrefs";
this.registeredWindows = {};
this.pathToStartupScript = null;
this.pathToShutdownScript = null;
this.pathToOptionsPage = null;
this.chromeHandle = null;
this.chromeData = null;
this.resourceData = null;
this.openWindows = [];
this.debug = context.extension.addonData.temporarilyInstalled;
const aomStartup = Cc[
"@mozilla.org/addons/addon-manager-startup;1"
].getService(Ci.amIAddonManagerStartup);
const resProto = Cc[
"@mozilla.org/network/protocol;1?name=resource"
].getService(Ci.nsISubstitutingProtocolHandler);
let self = this;
// TabMonitor to detect opening of tabs, to setup the options button in the add-on manager.
this.tabMonitor = {
onTabTitleChanged(aTab) {},
onTabClosing(aTab) {},
onTabPersist(aTab) {},
onTabRestored(aTab) {},
onTabSwitched(aNewTab, aOldTab) {
//self.setupAddonManager(self.getAddonManagerFromTab(aNewTab));
},
async onTabOpened(aTab) {
if (aTab.browser) {
if (!aTab.pageLoaded) {
// await a location change if browser is not loaded yet
await new Promise((resolve) => {
let reporterListener = {
QueryInterface: ChromeUtils.generateQI([
"nsIWebProgressListener",
"nsISupportsWeakReference",
]),
onStateChange() {},
onProgressChange() {},
onLocationChange(
/* in nsIWebProgress*/ aWebProgress,
/* in nsIRequest*/ aRequest,
/* in nsIURI*/ aLocation
) {
aTab.browser.removeProgressListener(reporterListener);
resolve();
},
onStatusChange() {},
onSecurityChange() {},
onContentBlockingEvent() {},
};
aTab.browser.addProgressListener(reporterListener);
});
}
self.setupAddonManager(self.getAddonManagerFromTab(aTab));
}
},
};
return {
WindowListener: {
async waitForMasterPassword() {
// Wait until master password has been entered (if needed)
while (!Services.logins.isLoggedIn) {
self.log("Waiting for master password.");
await self.sleep(1000);
}
self.log("Master password has been entered.");
},
aDocumentExistsAt(uriString) {
self.log(
"Checking if document at <" +
uriString +
"> used in registration actually exists."
);
try {
let uriObject = Services.io.newURI(uriString);
let content = Cu.readUTF8URI(uriObject);
} catch (e) {
Components.utils.reportError(e);
return false;
}
return true;
},
registerOptionsPage(optionsUrl) {
self.pathToOptionsPage = optionsUrl.startsWith("chrome://")
? optionsUrl
: context.extension.rootURI.resolve(optionsUrl);
},
registerDefaultPrefs(defaultUrl) {
let url = context.extension.rootURI.resolve(defaultUrl);
let prefsObj = {};
prefsObj.Services = ChromeUtils.import(
"resource://gre/modules/Services.jsm"
).Services;
prefsObj.pref = function (aName, aDefault) {
let defaults = Services.prefs.getDefaultBranch("");
switch (typeof aDefault) {
case "string":
return defaults.setStringPref(aName, aDefault);
case "number":
return defaults.setIntPref(aName, aDefault);
case "boolean":
return defaults.setBoolPref(aName, aDefault);
default:
throw new Error(
"Preference <" +
aName +
"> has an unsupported type <" +
typeof aDefault +
">. Allowed are string, number and boolean."
);
}
};
Services.scriptloader.loadSubScript(url, prefsObj, "UTF-8");
},
registerChromeUrl(data) {
let chromeData = [];
let resourceData = [];
for (let entry of data) {
if (entry[0] == "resource") resourceData.push(entry);
else chromeData.push(entry);
}
if (chromeData.length > 0) {
const manifestURI = Services.io.newURI(
"manifest.json",
null,
context.extension.rootURI
);
self.chromeHandle = aomStartup.registerChrome(
manifestURI,
chromeData
);
}
for (let res of resourceData) {
// [ "resource", "shortname" , "path" ]
let uri = Services.io.newURI(
res[2],
null,
context.extension.rootURI
);
resProto.setSubstitutionWithFlags(
res[1],
uri,
resProto.ALLOW_CONTENT_ACCESS
);
}
self.chromeData = chromeData;
self.resourceData = resourceData;
},
registerWindow(windowHref, jsFile) {
if (self.debug && !this.aDocumentExistsAt(windowHref)) {
self.error(
"Attempt to register an injector script for non-existent window: " +
windowHref
);
return;
}
if (!self.registeredWindows.hasOwnProperty(windowHref)) {
// path to JS file can either be chrome:// URL or a relative URL
let path = jsFile.startsWith("chrome://")
? jsFile
: context.extension.rootURI.resolve(jsFile);
self.registeredWindows[windowHref] = path;
} else {
self.error(
"Window <" + windowHref + "> has already been registered"
);
}
},
registerStartupScript(aPath) {
self.pathToStartupScript = aPath.startsWith("chrome://")
? aPath
: context.extension.rootURI.resolve(aPath);
},
registerShutdownScript(aPath) {
self.pathToShutdownScript = aPath.startsWith("chrome://")
? aPath
: context.extension.rootURI.resolve(aPath);
},
openOptionsDialog(windowId) {
let window = context.extension.windowManager.get(windowId, context)
.window;
let WL = {};
WL.extension = self.extension;
WL.messenger = self.getMessenger(self.context);
window.openDialog(
self.pathToOptionsPage,
"AddonOptions",
"chrome,resizable,centerscreen",
WL
);
},
async startListening() {
// load the registered startup script, if one has been registered
// (mail3:pane may not have been fully loaded yet)
if (self.pathToStartupScript) {
let startupJS = {};
startupJS.WL = {};
startupJS.WL.extension = self.extension;
startupJS.WL.messenger = self.getMessenger(self.context);
try {
if (self.pathToStartupScript) {
Services.scriptloader.loadSubScript(
self.pathToStartupScript,
startupJS,
"UTF-8"
);
// delay startup until startup has been finished
self.log(
"Waiting for async startup() in <" +
self.pathToStartupScript +
"> to finish."
);
if (startupJS.startup) {
await startupJS.startup();
self.log(
"startup() in <" + self.pathToStartupScript + "> finished"
);
} else {
self.log(
"No startup() in <" + self.pathToStartupScript + "> found."
);
}
}
} catch (e) {
Components.utils.reportError(e);
}
}
let urls = Object.keys(self.registeredWindows);
if (urls.length > 0) {
// Before registering the window listener, check which windows are already open
self.openWindows = [];
for (let window of Services.wm.getEnumerator(null)) {
self.openWindows.push(window);
}
// Register window listener for all pre-registered windows
ExtensionSupport.registerWindowListener(
"injectListener_" + self.uniqueRandomID,
{
// React on all windows and manually reduce to the registered
// windows, so we can do special actions when the main
// messenger window is opened.
//chromeURLs: Object.keys(self.registeredWindows),
async onLoadWindow(window) {
// Create add-on scope
window[self.uniqueRandomID] = {};
// Special action #1: If this is the main messenger window
if (
window.location.href ==
"chrome://messenger/content/messenger.xul" ||
window.location.href ==
"chrome://messenger/content/messenger.xhtml"
) {
if (self.pathToOptionsPage) {
if (self.getThunderbirdVersion().major < 78) {
let element_addonPrefs = window.document.getElementById(
self.menu_addonPrefs_id
);
element_addonPrefs.addEventListener(
"popupshowing",
self
);
} else {
// Setup the options button/menu in the add-on manager, if it is already open.
self.setupAddonManager(
self.getAddonManagerFromWindow(window),
true
);
// Add a tabmonitor, to be able to setup the options button/menu in the add-on manager.
self
.getTabMail(window)
.registerTabMonitor(self.tabMonitor);
window[self.uniqueRandomID].hasTabMonitor = true;
}
}
}
// Special action #2: If this page contains browser elements
let browserElements = window.document.getElementsByTagName(
"browser"
);
if (browserElements.length > 0) {
//register a MutationObserver
window[
self.uniqueRandomID
]._mObserver = new window.MutationObserver(function (
mutations
) {
mutations.forEach(async function (mutation) {
if (
mutation.attributeName == "src" &&
self.registeredWindows.hasOwnProperty(
mutation.target.getAttribute("src")
)
) {
// When the MutationObserver callsback, the window is still showing "about:black" and it is going
// to unload and then load the new page. Any eventListener attached to the window will be removed
// so we cannot listen for the load event. We have to poll manually to learn when loading has finished.
// On my system it takes 70ms.
let loaded = false;
for (let i = 0; i < 100 && !loaded; i++) {
await self.sleep(100);
let targetWindow =
mutation.target.contentWindow.wrappedJSObject;
if (
targetWindow &&
targetWindow.location.href ==
mutation.target.getAttribute("src") &&
targetWindow.document.readyState == "complete"
) {
loaded = true;
break;
}
}
if (loaded) {
let targetWindow =
mutation.target.contentWindow.wrappedJSObject;
// Create add-on scope
targetWindow[self.uniqueRandomID] = {};
// Inject with isAddonActivation = false
self._loadIntoWindow(targetWindow, false);
}
}
});
});
for (let element of browserElements) {
if (
self.registeredWindows.hasOwnProperty(
element.getAttribute("src")
)
) {
let targetWindow =
element.contentWindow.wrappedJSObject;
// Create add-on scope
targetWindow[self.uniqueRandomID] = {};
// Inject with isAddonActivation = true
self._loadIntoWindow(targetWindow, true);
} else {
// Window/Browser is not yet fully loaded, postpone injection via MutationObserver
window[self.uniqueRandomID]._mObserver.observe(
element,
{
attributes: true,
childList: false,
characterData: false,
}
);
}
}
}
// Load JS into window
self._loadIntoWindow(
window,
self.openWindows.includes(window)
);
},
onUnloadWindow(window) {
// Remove JS from window, window is being closed, addon is not shut down
self._unloadFromWindow(window, false);
},
}
);
} else {
self.error("Failed to start listening, no windows registered");
}
},
},
};
}
_loadIntoWindow(window, isAddonActivation) {
if (
window.hasOwnProperty(this.uniqueRandomID) &&
this.registeredWindows.hasOwnProperty(window.location.href)
) {
try {
let uniqueRandomID = this.uniqueRandomID;
let extension = this.extension;
// Add reference to window to add-on scope
window[this.uniqueRandomID].window = window;
window[this.uniqueRandomID].document = window.document;
// Keep track of toolbarpalettes we are injecting into
window[this.uniqueRandomID]._toolbarpalettes = {};
//Create WLDATA object
window[this.uniqueRandomID].WL = {};
window[this.uniqueRandomID].WL.scopeName = this.uniqueRandomID;
// Add helper function to inject CSS to WLDATA object
window[this.uniqueRandomID].WL.injectCSS = function (cssFile) {
let element;
let v = parseInt(Services.appinfo.version.split(".").shift());
// using createElementNS in TB78 delays the insert process and hides any security violation errors
if (v > 68) {
element = window.document.createElement("link");
} else {
let ns = window.document.documentElement.lookupNamespaceURI("html");
element = window.document.createElementNS(ns, "link");
}
element.setAttribute("wlapi_autoinjected", uniqueRandomID);
element.setAttribute("rel", "stylesheet");
element.setAttribute("href", cssFile);
return window.document.documentElement.appendChild(element);
};
// Add helper function to inject XUL to WLDATA object
window[this.uniqueRandomID].WL.injectElements = function (
xulString,
dtdFiles = [],
debug = false
) {
let toolbarsToResolve = [];
function checkElements(stringOfIDs) {
let arrayOfIDs = stringOfIDs.split(",").map((e) => e.trim());
for (let id of arrayOfIDs) {
let element = window.document.getElementById(id);
if (element) {
return element;
}
}
return null;
}
function localize(entity) {
let msg = entity.slice("__MSG_".length, -2);
return extension.localeData.localizeMessage(msg);
}
function injectChildren(elements, container) {
if (debug) console.log(elements);
for (let i = 0; i < elements.length; i++) {
// take care of persists
const uri = window.document.documentURI;
for (const persistentNode of elements[i].querySelectorAll(
"[persist]"
)) {
for (const persistentAttribute of persistentNode
.getAttribute("persist")
.trim()
.split(" ")) {
if (
Services.xulStore.hasValue(
uri,
persistentNode.id,
persistentAttribute
)
) {
persistentNode.setAttribute(
persistentAttribute,
Services.xulStore.getValue(
uri,
persistentNode.id,
persistentAttribute
)
);
}
}
}
if (
elements[i].hasAttribute("insertafter") &&
checkElements(elements[i].getAttribute("insertafter"))
) {
let insertAfterElement = checkElements(
elements[i].getAttribute("insertafter")
);
if (debug)
console.log(
elements[i].tagName +
"#" +
elements[i].id +
": insertafter " +
insertAfterElement.id
);
if (
debug &&
elements[i].id &&
window.document.getElementById(elements[i].id)
) {
console.error(
"The id <" +
elements[i].id +
"> of the injected element already exists in the document!"
);
}
elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID);
insertAfterElement.parentNode.insertBefore(
elements[i],
insertAfterElement.nextSibling
);
} else if (
elements[i].hasAttribute("insertbefore") &&
checkElements(elements[i].getAttribute("insertbefore"))
) {
let insertBeforeElement = checkElements(
elements[i].getAttribute("insertbefore")
);
if (debug)
console.log(
elements[i].tagName +
"#" +
elements[i].id +
": insertbefore " +
insertBeforeElement.id
);
if (
debug &&
elements[i].id &&
window.document.getElementById(elements[i].id)
) {
console.error(
"The id <" +
elements[i].id +
"> of the injected element already exists in the document!"
);
}
elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID);
insertBeforeElement.parentNode.insertBefore(
elements[i],
insertBeforeElement
);
} else if (
elements[i].id &&
window.document.getElementById(elements[i].id)
) {
// existing container match, dive into recursivly
if (debug)
console.log(
elements[i].tagName +
"#" +
elements[i].id +
" is an existing container, injecting into " +
elements[i].id
);
injectChildren(
Array.from(elements[i].children),
window.document.getElementById(elements[i].id)
);
} else if (elements[i].localName === "toolbarpalette") {
// These vanish from the document but still exist via the palette property
if (debug) console.log(elements[i].id + " is a toolbarpalette");
let boxes = [
...window.document.getElementsByTagName("toolbox"),
];
let box = boxes.find(
(box) => box.palette && box.palette.id === elements[i].id
);
let palette = box ? box.palette : null;
if (!palette) {
if (debug)
console.log(
`The palette for ${elements[i].id} could not be found, deferring to later`
);
continue;
}
if (debug)
console.log(`The toolbox for ${elements[i].id} is ${box.id}`);
toolbarsToResolve.push(...box.querySelectorAll("toolbar"));
toolbarsToResolve.push(
...window.document.querySelectorAll(
`toolbar[toolboxid="${box.id}"]`
)
);
for (let child of elements[i].children) {
child.setAttribute("wlapi_autoinjected", uniqueRandomID);
}
window[uniqueRandomID]._toolbarpalettes[palette.id] = palette;
injectChildren(Array.from(elements[i].children), palette);
} else {
// append element to the current container
if (debug)
console.log(
elements[i].tagName +
"#" +
elements[i].id +
": append to " +
container.id
);
elements[i].setAttribute("wlapi_autoinjected", uniqueRandomID);
container.appendChild(elements[i]);
}
}
}
if (debug) console.log("Injecting into root document:");
let localizedXulString = xulString.replace(
/__MSG_(.*?)__/g,
localize
);
injectChildren(
Array.from(
window.MozXULElement.parseXULToFragment(
localizedXulString,
dtdFiles
).children
),
window.document.documentElement
);
for (let bar of toolbarsToResolve) {
let currentset = Services.xulStore.getValue(
window.location,
bar.id,
"currentset"
);
if (currentset) {
bar.currentSet = currentset;
} else if (bar.getAttribute("defaultset")) {
bar.currentSet = bar.getAttribute("defaultset");
}
}
};
// Add extension object to WLDATA object
window[this.uniqueRandomID].WL.extension = this.extension;
// Add messenger object to WLDATA object
window[this.uniqueRandomID].WL.messenger = this.getMessenger(
this.context
);
// Load script into add-on scope
Services.scriptloader.loadSubScript(
this.registeredWindows[window.location.href],
window[this.uniqueRandomID],
"UTF-8"
);
window[this.uniqueRandomID].onLoad(isAddonActivation);
} catch (e) {
Components.utils.reportError(e);
}
}
}
_unloadFromWindow(window, isAddonDeactivation) {
// unload any contained browser elements
if (
window.hasOwnProperty(this.uniqueRandomID) &&
window[this.uniqueRandomID].hasOwnProperty("_mObserver")
) {
window[this.uniqueRandomID]._mObserver.disconnect();
let browserElements = window.document.getElementsByTagName("browser");
for (let element of browserElements) {
if (element.contentWindow) {
this._unloadFromWindow(
element.contentWindow.wrappedJSObject,
isAddonDeactivation
);
}
}
}
if (
window.hasOwnProperty(this.uniqueRandomID) &&
this.registeredWindows.hasOwnProperty(window.location.href)
) {
// Remove this window from the list of open windows
this.openWindows = this.openWindows.filter((e) => e != window);
if (window[this.uniqueRandomID].onUnload) {
try {
// Call onUnload()
window[this.uniqueRandomID].onUnload(isAddonDeactivation);
} catch (e) {
Components.utils.reportError(e);
}
}
// Remove all auto injected objects
let elements = Array.from(
window.document.querySelectorAll(
'[wlapi_autoinjected="' + this.uniqueRandomID + '"]'
)
);
for (let element of elements) {
element.remove();
}
// Remove all autoinjected toolbarpalette items
for (const palette of Object.values(
window[this.uniqueRandomID]._toolbarpalettes
)) {
let elements = Array.from(
palette.querySelectorAll(
'[wlapi_autoinjected="' + this.uniqueRandomID + '"]'
)
);
for (let element of elements) {
element.remove();
}
}
}
// Remove add-on scope, if it exists
if (window.hasOwnProperty(this.uniqueRandomID)) {
delete window[this.uniqueRandomID];
}
}
onShutdown(isAppShutdown) {
if (isAppShutdown) {
return; // the application gets unloaded anyway
}
// Unload from all still open windows
let urls = Object.keys(this.registeredWindows);
if (urls.length > 0) {
for (let window of Services.wm.getEnumerator(null)) {
//remove our entry in the add-on options menu
if (
this.pathToOptionsPage &&
(window.location.href == "chrome://messenger/content/messenger.xul" ||
window.location.href ==
"chrome://messenger/content/messenger.xhtml")
) {
if (this.getThunderbirdVersion().major < 78) {
let element_addonPrefs = window.document.getElementById(
this.menu_addonPrefs_id
);
element_addonPrefs.removeEventListener("popupshowing", this);
// Remove our entry.
let entry = window.document.getElementById(
this.menu_addonPrefs_id + "_" + this.uniqueRandomID
);
if (entry) entry.remove();
// Do we have to unhide the noPrefsElement?
if (element_addonPrefs.children.length == 1) {
let noPrefsElem = element_addonPrefs.querySelector(
'[disabled="true"]'
);
noPrefsElem.style.display = "inline";
}
} else {
// Remove event listener for addon manager view changes
let managerWindow = this.getAddonManagerFromWindow(window);
if (
managerWindow &&
managerWindow[this.uniqueRandomID] &&
managerWindow[this.uniqueRandomID].hasAddonManagerEventListeners
) {
managerWindow.document.removeEventListener("ViewChanged", this);
managerWindow.document.removeEventListener("view-loaded", this);
managerWindow.document.removeEventListener("update", this);
let cards = this.getCards(managerWindow);
if (this.getThunderbirdVersion().major < 88) {
// Remove options menu in 78-87
for (let card of cards) {
let addonOptionsLegacyEntry = card.querySelector(
".extension-options-legacy"
);
if (addonOptionsLegacyEntry) addonOptionsLegacyEntry.remove();
}
} else {
// Remove options button in 88
for (let card of cards) {
if (card.addon.id == this.extension.id) {
let addonOptionsButton = card.querySelector(
".windowlistener-options-button"
);
if (addonOptionsButton) addonOptionsButton.remove();
break;
}
}
}
}
// Remove tabmonitor
if (window[this.uniqueRandomID].hasTabMonitor) {
this.getTabMail(window).unregisterTabMonitor(this.tabMonitor);
window[this.uniqueRandomID].hasTabMonitor = false;
}
}
}
// if it is app shutdown, it is not just an add-on deactivation
this._unloadFromWindow(window, !isAppShutdown);
}
// Stop listening for new windows.
ExtensionSupport.unregisterWindowListener(
"injectListener_" + this.uniqueRandomID
);
}
// Load registered shutdown script
let shutdownJS = {};
shutdownJS.extension = this.extension;
try {
if (this.pathToShutdownScript)
Services.scriptloader.loadSubScript(
this.pathToShutdownScript,
shutdownJS,
"UTF-8"
);
} catch (e) {
Components.utils.reportError(e);
}
// Extract all registered chrome content urls
let chromeUrls = [];
if (this.chromeData) {
for (let chromeEntry of this.chromeData) {
if (chromeEntry[0].toLowerCase().trim() == "content") {
chromeUrls.push("chrome://" + chromeEntry[1] + "/");
}
}
}
// Unload JSMs of this add-on
const rootURI = this.extension.rootURI.spec;
for (let module of Cu.loadedModules) {
if (
module.startsWith(rootURI) ||
(module.startsWith("chrome://") &&
chromeUrls.find((s) => module.startsWith(s)))
) {
this.log("Unloading: " + module);
Cu.unload(module);
}
}
// Flush all caches
Services.obs.notifyObservers(null, "startupcache-invalidate");
this.registeredWindows = {};
if (this.resourceData) {
const resProto = Cc[
"@mozilla.org/network/protocol;1?name=resource"
].getService(Ci.nsISubstitutingProtocolHandler);
for (let res of this.resourceData) {
// [ "resource", "shortname" , "path" ]
resProto.setSubstitution(res[1], null);
}
}
if (this.chromeHandle) {
this.chromeHandle.destruct();
this.chromeHandle = null;
}
}
};
experiment-apis/WindowListener/CHANGELOG.md 0000644 0000000 0000000 00000006172 14071265224 017465 0 ustar root root Version: 1.56
-------------
- be precise on which revision the wrench symbol should be displayed, instead of
the options button
Version: 1.54
-------------
- fix "ownerDoc.getElementById() is undefined" bug
Version: 1.53
-------------
- fix "tab.browser is undefined" bug
Version: 1.52
-------------
- clear cache only if add-on is uninstalled/updated, not on app shutdown
Version: 1.51
-------------
- use wrench button for options for TB78.10
Version: 1.50
-------------
- use built-in CSS rules to fix options button for dark themes (thanks to Thunder)
- fix some occasions where options button was not added
Version: 1.49
-------------
- fixed missing eventListener for Beta + Daily
Version: 1.48
-------------
- moved notifyTools into its own NotifyTools API.
Version: 1.39
-------------
- fix for 68
Version: 1.36
-------------
- fix for beta 87
Version: 1.35
-------------
- add support for options button/menu in add-on manager and fix 68 double menu entry
Version: 1.34
-------------
- fix error in unload
Version: 1.33
-------------
- fix for e10s
Version: 1.30
-------------
- replace setCharPref by setStringPref to cope with UTF-8 encoding
Version: 1.29
-------------
- open options window centered
Version: 1.28
-------------
- do not crash on missing icon
Version: 1.27
-------------
- add openOptionsDialog()
Version: 1.26
-------------
- pass WL object to legacy preference window
Version: 1.25
-------------
- adding waitForMasterPassword
Version: 1.24
-------------
- automatically localize i18n locale strings in injectElements()
Version: 1.22
-------------
- to reduce confusions, only check built-in URLs as add-on URLs cannot
be resolved if a temp installed add-on has bin zipped
Version: 1.21
-------------
- print debug messages only if add-ons are installed temporarily from
the add-on debug page
- add checks to registered windows and scripts, if they actually exists
Version: 1.20
-------------
- fix long delay before customize window opens
- fix non working removal of palette items
Version: 1.19
-------------
- add support for ToolbarPalette
Version: 1.18
-------------
- execute shutdown script also during global app shutdown (fixed)
Version: 1.17
-------------
- execute shutdown script also during global app shutdown
Version: 1.16
-------------
- support for persist
Version: 1.15
-------------
- make (undocumented) startup() async
Version: 1.14
-------------
- support resource urls
Version: 1.12
-------------
- no longer allow to enforce custom "namespace"
- no longer call it namespace but uniqueRandomID / scopeName
- expose special objects as the global WL object
- autoremove injected elements after onUnload has ben executed
Version: 1.9
-------------
- automatically remove all entries added by injectElements
Version: 1.8
-------------
- add injectElements
Version: 1.7
-------------
- add injectCSS
- add optional enforced namespace
Version: 1.6
-------------
- added mutation observer to be able to inject into browser elements
- use larger icons as fallback
experiment-apis/WindowListener/README.md 0000644 0000000 0000000 00000000332 13721221232 017113 0 ustar root root 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).
experiment-apis/Utilities/ 0000755 0000000 0000000 00000000000 14051011234 014630 5 ustar root root experiment-apis/Utilities/utilities.js 0000644 0000000 0000000 00000003036 14051011234 017203 0 ustar root root /* eslint-disable object-shorthand */
var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm"),
{ Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"),
win = Services.wm.getMostRecentWindow("mail:3pane");
var {xnote} = ChromeUtils.import("resource://xnote/modules/xnote.jsm");
//console.log("xnote - implementation utilities");
var xnoteUtilities = class extends ExtensionCommon.ExtensionAPI {
getAPI(context) {
const PrefTypes = {
[Services.prefs.PREF_STRING] : "string",
[Services.prefs.PREF_INT] : "number",
[Services.prefs.PREF_BOOL] : "boolean",
[Services.prefs.PREF_INVALID] : "invalid"
};
return {
xnoteUtilities: {
logDebug (text) {
// console.debug(text);
},
openLinkExternally(url) {
let uri = url;
if (!(uri instanceof Ci.nsIURI)) {
uri = Services.io.newURI(url);
}
Cc["@mozilla.org/uriloader/external-protocol-service;1"]
.getService(Ci.nsIExternalProtocolService)
.loadURI(uri);
},
showXhtmlPage(uri) {
let mail3PaneWindow = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("mail:3pane");
mail3PaneWindow.openDialog(uri);
}
// get may only return something, if a value is set
}
}
};
}
experiment-apis/Utilities/utilities.json 0000644 0000000 0000000 00000001546 14004402740 017550 0 ustar root root [
{
"namespace": "xnoteUtilities",
"functions": [
{
"name": "logDebug",
"type": "function",
"async": true,
"parameters": [
{
"name": "text",
"type": "string",
"description": "what text to log"
}
]
},
{
"name": "showXhtmlPage",
"type": "function",
"parameters": [
{
"name": "url",
"type": "string",
"description": "url to open in browser"
}
]
},
{
"name": "openLinkExternally",
"type": "function",
"parameters": [
{
"name": "url",
"type": "string",
"description": "url to open in browser"
}
]
}
]
}
]
experiment-apis/NotifyTools/ 0000755 0000000 0000000 00000000000 14050747610 015163 5 ustar root root experiment-apis/NotifyTools/schema.json 0000644 0000000 0000000 00000001606 14050747610 017321 0 ustar root root [
{
"namespace": "NotifyTools",
"events": [
{
"name": "onNotifyBackground",
"type": "function",
"description": "Fired when a new notification from notifyTools.js in an Experiment has been received.",
"parameters": [
{
"name": "data",
"type": "any",
"description": "Restrictions of the structured clone algorythm apply."
}
]
}
],
"functions": [
{
"name": "notifyExperiment",
"type": "function",
"async": true,
"description": "Notifies notifyTools.js in an Experiment and sends data.",
"parameters": [
{
"name": "data",
"type": "any",
"description": "Restrictions of the structured clone algorythm apply."
}
]
}
]
}
]
experiment-apis/NotifyTools/implementation.js 0000644 0000000 0000000 00000007440 14050747610 020553 0 ustar root root /*
* This file is provided by the addon-developer-support repository at
* https://github.com/thundernest/addon-developer-support
*
* Author: John Bieling (john@thunderbird.net)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// Get various parts of the WebExtension framework that we need.
var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
var { ExtensionSupport } = ChromeUtils.import("resource:///modules/ExtensionSupport.jsm");
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var NotifyTools = class extends ExtensionCommon.ExtensionAPI {
getAPI(context) {
var self = this;
this.onNotifyBackgroundObserver = {
observe: async function (aSubject, aTopic, aData) {
if (
Object.keys(self.observerTracker).length > 0 &&
aData == self.extension.id
) {
let payload = aSubject.wrappedJSObject;
// This is called from the BL observer.js and therefore it should have a resolve
// payload, but better check.
if (payload.resolve) {
let observerTrackerPromises = [];
// Push listener into promise array, so they can run in parallel
for (let listener of Object.values(self.observerTracker)) {
observerTrackerPromises.push(listener(payload.data));
}
// We still have to await all of them but wait time is just the time needed
// for the slowest one.
let results = [];
for (let observerTrackerPromise of observerTrackerPromises) {
let rv = await observerTrackerPromise;
if (rv != null) results.push(rv);
}
if (results.length == 0) {
payload.resolve();
} else {
if (results.length > 1) {
console.warn(
"Received multiple results from onNotifyBackground listeners. Using the first one, which can lead to inconsistent behavior.",
results
);
}
payload.resolve(results[0]);
}
} else {
// Just call the listener.
for (let listener of Object.values(self.observerTracker)) {
listener(payload.data);
}
}
}
},
};
this.observerTracker = {};
this.observerTrackerNext = 1;
// Add observer for notifyTools.js
Services.obs.addObserver(
this.onNotifyBackgroundObserver,
"NotifyBackgroundObserver",
false
);
return {
NotifyTools: {
notifyExperiment(data) {
return new Promise(resolve => {
Services.obs.notifyObservers(
{ data, resolve },
"NotifyExperimentObserver",
self.extension.id
);
});
},
onNotifyBackground: new ExtensionCommon.EventManager({
context,
name: "NotifyTools.onNotifyBackground",
register: (fire) => {
let trackerId = self.observerTrackerNext++;
self.observerTracker[trackerId] = fire.sync;
return () => {
delete self.observerTracker[trackerId];
};
},
}).api(),
}
};
}
onShutdown(isAppShutdown) {
// Remove observer for notifyTools.js
Services.obs.removeObserver(
this.onNotifyBackgroundObserver,
"NotifyBackgroundObserver"
);
// Flush all caches
Services.obs.notifyObservers(null, "startupcache-invalidate");
}
};
experiment-apis/NotifyTools/README.md 0000644 0000000 0000000 00000010676 14050747610 016454 0 ustar root root # Objective
The NotifyTools provide a bidirectional messaging system between Experiments scripts and the WebExtension's background page (even with [e10s](https://developer.thunderbird.net/add-ons/updating/tb91/changes#thunderbird-is-now-multi-process-e-10-s) being enabled in Thunderbird Beta 86).

They allow to work on add-on uprades in smaller steps, as single calls (like `window.openDialog()`)
in the middle of legacy code can be replaced by WebExtension calls, by stepping out of the Experiment
and back in when the task has been finished.
More details can be found in [this update tutorial](https://github.com/thundernest/addon-developer-support/wiki/Tutorial:-Convert-add-on-parts-individually-by-using-a-messaging-system).
# Usage
Add the [NotifyTools API](https://github.com/thundernest/addon-developer-support/tree/master/auxiliary-apis/NotifyTools) to your add-on. Your `manifest.json` needs an entry like this:
```
"experiment_apis": {
"NotifyTools": {
"schema": "api/NotifyTools/schema.json",
"parent": {
"scopes": ["addon_parent"],
"paths": [["NotifyTools"]],
"script": "api/NotifyTools/implementation.js"
}
}
},
```
Additionally to the [NotifyTools API](https://github.com/thundernest/addon-developer-support/tree/master/auxiliary-apis/NotifyTools) the [notifyTools.js](https://github.com/thundernest/addon-developer-support/tree/master/scripts/notifyTools) script is needed as the counterpart in Experiment scripts.
**Note:** You need to adjust the `notifyTools.js` script and add your add-on ID at the top.
## Receiving notifications from Experiment scripts
Add a listener for the `onNotifyBackground` event in your WebExtension's background page:
```
messenger.NotifyTools.onNotifyBackground.addListener(async (info) => {
switch (info.command) {
case "doSomething":
//do something
let rv = await doSomething();
return rv;
break;
}
});
```
The `onNotifyBackground` event will receive and respond to notifications send from your Experiment scripts:
```
notifyTools.notifyBackground({command: "doSomething"}).then((data) => {
console.log(data);
});
```
Include the [notifyTools.js](https://github.com/thundernest/addon-developer-support/tree/master/scripts/notifyTools) script in your Experiment script to be able to use `notifyTools.notifyBackground()`.
**Note**: If multiple `onNotifyBackground` listeners are registered in the WebExtension's background page and more than one is returning data, the value
from the first one is returned to the Experiment. This may lead to inconsistent behavior, so make sure that for each
request only one listener is returning data.
## Sending notifications to Experiments scripts
Use the `notifyExperiment()` method to send a notification from the WebExtension's background page to Experiment scripts:
```
messenger.NotifyTools.notifyExperiment({command: "doSomething"}).then((data) => {
console.log(data)
});
```
The receiving Experiment script needs to include the [notifyTools.js](https://github.com/thundernest/addon-developer-support/tree/master/scripts/notifyTools) script and must setup a listener using the following methods:
### registerListener(callback);
Registers a callback function, which is called when a notification from the WebExtension's background page has been received. The `registerListener()` function returns an `id` which can be used to remove the listener again.
Example:
```
function doSomething(data) {
console.log(data);
return true;
}
let id = notifyTools.registerListener(doSomething);
```
### removeListener(id)
Removes the listener with the given `id`.
Example:
```
notifyTools.removeListener(id);
```
### enable()
The [notifyTools.js](https://github.com/thundernest/addon-developer-support/tree/master/scripts/notifyTools) script attaches its `enable()` method to the `load` event of the current window. If the script is loaded into a window-less environment, `enable()` needs to be called manually.
### disable()
The [notifyTools.js](https://github.com/thundernest/addon-developer-support/tree/master/scripts/notifyTools) script attaches its `disable()` method to the `unload` event of the current window. If the script is loaded into a window-less environment, `disable()` needs to be called manually.
experiment-apis/xnote/ 0000755 0000000 0000000 00000000000 14051011234 014012 5 ustar root root experiment-apis/xnote/xnote-experiments.json 0000644 0000000 0000000 00000004007 14050747612 020423 0 ustar root root [
{
"namespace": "xnoteapi",
"functions": [
{
"name": "init",
"type": "function",
"async": true,
"parameters": []
},
{
"name": "initNote",
"type": "function",
"async": true,
"parameters": []
},
{
"name": "closeNoteWindow",
"type": "function",
"async": true,
"parameters": []
},
{
"name": "getXNote",
"type": "function",
"async": true,
"parameters": [
{
"name": "id",
"type": "any",
"description": "MX message id"
}
]
},
{
"name": "getPref",
"type": "function",
"async": true,
"parameters": [
{
"name": "name",
"type": "string",
"description": "Old preference string excluding extensions.xnote. prefix."
}
]
},
{
"name": "setPreferences",
"type": "function",
"async": true,
"parameters": [
{
"name": "prefs",
"type": "any",
"description": "Set the preferences object from the local browser storage."
}
]
},
{
"name": "getTbPref",
"type": "function",
"async": true,
"parameters": [
{
"name": "name",
"type": "string",
"description": "Old TB preference string."
}
]
},
{
"name": "setTbPref",
"type": "function",
"async": true,
"parameters": [
{
"name": "name",
"type": "string",
"description": "Old TB preference string."
},
{
"name": "value",
"type": "any",
"description": "TB preference value."
}
]
}
]
}
]
experiment-apis/xnote/xnote-experiments.js 0000644 0000000 0000000 00000015507 14102030160 020052 0 ustar root root var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm"),
{ Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"),
{ TagUtils } = ChromeUtils.import("resource:///modules/TagUtils.jsm"),
win = Services.wm.getMostRecentWindow("mail:3pane");
const { ExtensionParent } = ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm");
const xnoteExtension = ExtensionParent.GlobalManager.getExtension("xnote@froihofer.net");
var {xnote} = ChromeUtils.import(xnoteExtension.rootURI.resolve("chrome/modules/xnote.jsm"));
if (!xnote.ns) xnote.ns = {};
ChromeUtils.import(xnoteExtension.rootURI.resolve("chrome/modules/commons.jsm"), xnote.ns);
ChromeUtils.import(xnoteExtension.rootURI.resolve("chrome/modules/storage.jsm"), xnote.ns);
ChromeUtils.import(xnoteExtension.rootURI.resolve("chrome/modules/xnote-upgrades.jsm"), xnote.ns);
const XNOTE_BASE_PREF_NAME = "extensions.xnote.";
/**
* This maps preference names to their types. This is needed as the prefs
* system doesn't actually know what format you've stored your pref in.
*/
function prefType(name) {
switch (name) {
case XNOTE_BASE_PREF_NAME+"usetag": {
return "bool";
}
case XNOTE_BASE_PREF_NAME+"dateformat": {
return "string";
}
case XNOTE_BASE_PREF_NAME+"width": {
return "int";
}
case XNOTE_BASE_PREF_NAME+"height": {
return "int";
}
case XNOTE_BASE_PREF_NAME+"HorPos": {
return "int";
}
case XNOTE_BASE_PREF_NAME+"VertPos": {
return "int";
}
case XNOTE_BASE_PREF_NAME+"show_on_select": {
return "bool";
}
case XNOTE_BASE_PREF_NAME+"show_in_messageDisplay": {
return "bool";
}
case XNOTE_BASE_PREF_NAME+"show_first_x_chars_in_col": {
return "int";
}
case XNOTE_BASE_PREF_NAME+"storage_path": {
return "string";
}
case "mailnews.tags.xnote.tag": {
return "string";
}
case "mailnews.tags.xnote.color": {
return "string";
}
}
throw new Error(`Unexpected pref type ${name}`);
}
//console.log("xnote - experiments API");
var xnoteapi = class extends ExtensionCommon.ExtensionAPI {
getAPI(context) {
return {
xnoteapi: {
async init() {
xnote.ns.Commons.init();
xnote.ns.Storage.updateStoragePath();
//TODO: Move the stored version to the browser storage or do the check
//for a previous installation before preferences are migrated
let storedVersion = xnote.ns.Commons.xnoteLegacyPrefs.prefHasUserValue("version") ?
xnote.ns.Commons.xnoteLegacyPrefs.getCharPref("version") : null
// console.log(`storedVersion: ${storedVersion}; comparison: `+ (storedVersion == null));
xnote.ns.Commons.isNewInstallation = storedVersion == null;
xnote.ns.Upgrades.checkUpgrades(storedVersion, xnote.ns.Commons.XNOTE_VERSION)
xnote.ns.Commons.xnoteLegacyPrefs.setCharPref("version", xnote.ns.Commons.XNOTE_VERSION);
xnote.ns.Commons.checkXNoteTag();
},
async closeNoteWindow() {
//console.log("now close window");
let winNote = Services.wm.getMostRecentWindow("xnote:note");
//debugger;
if (winNote) winNote.close();
},
async initNote() {
xnote.ns.Overlay.initialise('clicBouton');
},
async getPref(name) {
return this.getTbPref(`${XNOTE_BASE_PREF_NAME}${name}`)
},
async setPreferences(prefs) {
xnote.ns.Commons.xnotePrefs = prefs;
// console.debug({"XnotePrefs" : xnote.ns.Commons.xnotePrefs});
xnote.ns.Storage.updateStoragePath();
xnote.ns.Commons.checkXNoteTag();
},
async getXNote(id) {
let note = {};
try {
let realMessage = context.extension.messageManager.get(id);
// console.log("realmsg", realMessage.messageId );
note = new xnote.ns.Note(realMessage.messageId);
// console.log("xnote", note);
} catch (ex) {
console.error(`Could not get TB mesg` );
}
return {text: note.text, date: note.modificationDate};
},
async getTbPref(name) {
try {
switch (prefType(name)) {
case "bool": {
return Services.prefs.getBoolPref(name);
}
case "int": {
return Services.prefs.getIntPref(name);
}
case "char": {
return Services.prefs.getStringPref(name);
}
case "string": {
return Services.prefs.getStringPref(name);
}
default: {
console.error(`Unexpected pref type for: ${name}`);
}
}
} catch (ex) {
console.error(`Could not get TB pref ${name}` , ex);
return undefined;
}
},
async setTbPref(name, value) {
try {
if (name == "mailnews.tags.xnote.color") {
TagUtils.addTagToAllDocumentSheets("xnote", value);
Services.prefs.setStringPref(name, value);
}
else switch (prefType(name)) {
case "bool": {
Services.prefs.setBoolPref(name, value);
break;
}
case "int": {
Services.prefs.setIntPref(name, value);
break;
}
case "char": {
Services.prefs.setStringPref(name, value);
break;
}
case "string": {
Services.prefs.setStringPref(name, value);
break;
}
default:
console.error(`Unknown preference type: ${prefType(name)}`)
}
} catch (ex) {
console.error(`Could not set TB pref ${name}` , ex);
}
}
}
}
}
onShutdown(isAppShutdown) {
// console.debug(`onShutdown: isAppShutdown=${isAppShutdown}`);
if (isAppShutdown) return;
Components.utils.unload(xnoteExtension.rootURI.resolve("chrome/modules/xnote-upgrades.jsm"));
Components.utils.unload(xnoteExtension.rootURI.resolve("chrome/modules/storage.jsm"));
Components.utils.unload(xnoteExtension.rootURI.resolve("chrome/modules/commons.jsm"));
Components.utils.unload(xnoteExtension.rootURI.resolve("chrome/modules/xnote.jsm"));
// invalidate the startup cache, such that after updating the addon the old
// version is no longer cached
Services.obs.notifyObservers(null, "startupcache-invalidate");
}
}
experiment-apis/LegacyPrefs/ 0000755 0000000 0000000 00000000000 13761305360 015076 5 ustar root root experiment-apis/LegacyPrefs/legacyprefs.js 0000644 0000000 0000000 00000003430 13761305360 017740 0 ustar root root /* eslint-disable object-shorthand */
var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var LegacyPrefs = class extends ExtensionCommon.ExtensionAPI {
getAPI(context) {
const PrefTypes = {
[Services.prefs.PREF_STRING] : "string",
[Services.prefs.PREF_INT] : "number",
[Services.prefs.PREF_BOOL] : "boolean",
[Services.prefs.PREF_INVALID] : "invalid"
};
return {
LegacyPrefs: {
// get may only return something, if a value is set
get: async function(aName, aDefault) {
let prefType = Services.prefs.getPrefType(aName);
if (prefType == Services.prefs.PREF_INVALID) {
return null;
}
if (typeof aDefault != PrefTypes[prefType]) {
throw new Error("PrefType of <" + aName + "> is <" + PrefTypes[prefType] + "> and does not match the type of its default value <" + aDefault + "> which is <" + typeof aDefault + ">!");
}
switch (typeof aDefault) {
case "string":
return Services.prefs.getCharPref(aName, aDefault);
case "number":
return Services.prefs.getIntPref(aName, aDefault);
case "boolean":
return Services.prefs.getBoolPref(aName, aDefault);
default:
throw new Error("Preference <" + aName + "> has an unsupported type <" + typeof aDefault + ">. Allowed are string, number and boolean.");
}
},
clear: async function(aName) {
Services.prefs.clearUserPref(aName);
}
},
};
}
};
experiment-apis/LegacyPrefs/legacyprefs.json 0000644 0000000 0000000 00000002215 13761305360 020275 0 ustar root root [
{
"namespace": "LegacyPrefs",
"functions": [
{
"name": "get",
"type": "function",
"description": "Gets a value from the legacy pref system.",
"async": true,
"parameters": [
{
"name": "aName",
"type": "string",
"description": "Name of the preference."
},
{
"name": "aDefault",
"description": "Default value of the preference.",
"choices": [
{
"type": "integer"
},
{
"type": "string"
},
{
"type": "boolean"
}
]
}
]
},
{
"name": "clear",
"type": "function",
"description": "Removes a value from the legacy pref system.",
"async": true,
"parameters": [
{
"name": "aName",
"type": "string",
"description": "Name of the preference."
}
]
}
]
}
]
icons/ 0000755 0000000 0000000 00000000000 14056761670 010703 5 ustar root root icons/iconfinder_maximize-2_2561250_MIT.svg 0000644 0000000 0000000 00000000533 14050310766 017212 0 ustar root root