FireTray/src/modules/commons.js

363 lines
11 KiB
JavaScript

/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* for now, logging facilities (imported from logging.jsm) and Services are
automatically provided by this module */
var EXPORTED_SYMBOLS =
[ "firetray", "FIRETRAY_VERSION", "FIRETRAY_OS_SUPPORT", "FIRETRAY_ID",
"FIRETRAY_PREF_BRANCH", "FIRETRAY_SPLASH_PAGE",
"FIRETRAY_APPLICATION_ICON_TYPE_THEMED",
"FIRETRAY_APPLICATION_ICON_TYPE_CUSTOM",
"FIRETRAY_MIDDLE_CLICK_ACTIVATE_LAST", "FIRETRAY_MIDDLE_CLICK_SHOW_HIDE",
"FIRETRAY_NOTIFICATION_MESSAGE_COUNT",
"FIRETRAY_NOTIFICATION_NEWMAIL_ICON", "FIRETRAY_NOTIFICATION_CUSTOM_ICON",
"FIRETRAY_IM_STATUS_AVAILABLE", "FIRETRAY_IM_STATUS_AWAY",
"FIRETRAY_IM_STATUS_BUSY", "FIRETRAY_IM_STATUS_OFFLINE",
"FIRETRAY_ACCOUNT_SERVER_TYPE_IM", "FIRETRAY_DELAY_STARTUP_MILLISECONDS",
"FIRETRAY_DELAY_NOWAIT_MILLISECONDS", "FIRETRAY_MESSAGE_COUNT_TYPE_UNREAD",
"FIRETRAY_MESSAGE_COUNT_TYPE_NEW", "FIRETRAY_CHAT_ICON_BLINK_STYLE_NORMAL",
"FIRETRAY_CHAT_ICON_BLINK_STYLE_FADE", "FIRETRAY_APPINDICATOR_ID",
"FIRETRAY_APP_DB", "FIRETRAY_CB_SENTINEL" ];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://firetray/logging.jsm");
const FIRETRAY_VERSION = "0.5.3"; // needed for sync call of onVersionChange() :(
const FIRETRAY_OS_SUPPORT = ['linux', 'winnt']; // install.rdf sync :(
const FIRETRAY_ID = "{9533f794-00b4-4354-aa15-c2bbda6989f8}";
const FIRETRAY_PREF_BRANCH = "extensions.firetray.";
const FIRETRAY_SPLASH_PAGE = "http://foudfou.github.com/FireTray/";
const FIRETRAY_APPLICATION_ICON_TYPE_THEMED = 0;
const FIRETRAY_APPLICATION_ICON_TYPE_CUSTOM = 1;
const FIRETRAY_MIDDLE_CLICK_ACTIVATE_LAST = 0;
const FIRETRAY_MIDDLE_CLICK_SHOW_HIDE = 1;
const FIRETRAY_MESSAGE_COUNT_TYPE_UNREAD = 0;
const FIRETRAY_MESSAGE_COUNT_TYPE_NEW = 1;
const FIRETRAY_NOTIFICATION_MESSAGE_COUNT = 0;
const FIRETRAY_NOTIFICATION_NEWMAIL_ICON = 1;
const FIRETRAY_NOTIFICATION_CUSTOM_ICON = 2;
const FIRETRAY_IM_STATUS_AVAILABLE = "user-available";
const FIRETRAY_IM_STATUS_AWAY = "user-away";
const FIRETRAY_IM_STATUS_BUSY = "user-busy";
const FIRETRAY_IM_STATUS_OFFLINE = "user-offline";
const FIRETRAY_ACCOUNT_SERVER_TYPE_IM = "im";
const FIRETRAY_DELAY_STARTUP_MILLISECONDS = 500;
const FIRETRAY_DELAY_NOWAIT_MILLISECONDS = 0;
const FIRETRAY_CHAT_ICON_BLINK_STYLE_NORMAL = 0;
const FIRETRAY_CHAT_ICON_BLINK_STYLE_FADE = 1;
const FIRETRAY_APPINDICATOR_ID = "firetray";
const FIRETRAY_APP_DB = {
firefox: {
id: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
},
thunderbird: {
id: "{3550f703-e582-4d05-9a08-453d09bdfdc6}",
},
seamonkey: {
id: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
},
songbird: {
id: "songbird@songbirdnest.com",
},
sunbird: {
id: "{718e30fb-e89b-41dd-9da7-e25a45638b28}",
},
chatzilla: {
id: "{59c81df5-4b7a-477b-912d-4e0fdf64e5f2}",
},
zotero: {
id: "zotero@chnm.gmu.edu",
}
};
/*
* Debugging purpose: if a callback fails (like "expected type int, got (void
* 0)"), there is no easy way to find out which (for ex. when called through
* g_signal_connect()). A possible way is to remove the sentinel definition on
* each callback definition one-by-one. For ex:
*
* let callback = gtk.GCallbackWidgetFocusEvent_t(
* firetray.Window.onFocusIn, null, FIRETRAY_CB_SENTINEL);
*
* becomes
*
* let callback = gtk.GCallbackWidgetFocusEvent_t(
* firetray.Window.onFocusIn);
*
* and see if the the message "JavaScript callback failed, and an error
* sentinel was not specified" appears in the console.
*
* Note: it's not possible to define a sentinel when the return type is void.
* Note: almost all return types end up as int's (even gboolean).
*/
const FIRETRAY_CB_SENTINEL = -1;
/**
* firetray namespace.
*/
if ("undefined" == typeof(firetray)) {
var firetray = {};
};
let log = firetray.Logging.getLogger("firetray.commons");
firetray.Utils = {
prefService: Services.prefs.getBranch(FIRETRAY_PREF_BRANCH),
strings: Services.strings.createBundle("chrome://firetray/locale/overlay.properties"),
addObservers: function(handler, topics){
topics.forEach(function(topic){
if (this.observedTopics[topic]) {
log.warn(topic+" already registred for "+handler);
return;
}
Services.obs.addObserver(this, topic, false);
this.observedTopics[topic] = true;
log.debug("registred "+topic+" for "+handler);
}, handler);
},
removeObservers: function(handler, topics) {
topics.forEach(function(topic){
Services.obs.removeObserver(this, topic);
delete this.observedTopics[topic];
}, handler);
},
removeAllObservers: function(handler) {
for (let topic in handler.observedTopics)
Services.obs.removeObserver(handler, topic);
handler.observedTopics = {};
},
getObjPref: function(prefStr) {
try {
var objPref = JSON.parse(
firetray.Utils.prefService.getCharPref(prefStr));
} catch (x) {
log.error(x);
}
return objPref;
},
setObjPref: function(prefStr, obj) {
log.debug(obj);
try {
firetray.Utils.prefService.setCharPref(prefStr, JSON.stringify(obj));
} catch (x) {
log.error(x);
}
},
getArrayPref: function(prefStr) {
let arrayPref = this.getObjPref(prefStr);
if (!firetray.js.isArray(arrayPref))
throw new TypeError("'"+prefStr+"' preference is not array.");
return arrayPref;
},
setArrayPref: function(prefStr, aArray) {
if (!firetray.js.isArray(aArray))
throw new TypeError("'"+aArray+"' is not array.");
this.setObjPref(prefStr, aArray);
},
QueryInterfaces: function(obj) {
for each (i in Components.interfaces)
try {
if (obj instanceof i) log.debug (i);
} catch(x) {}
},
// adapted from http://forums.mozillazine.org/viewtopic.php?p=921150#921150
chromeToPath: function(aPath) {
if (!aPath || !(/^chrome:/.test(aPath)))
return null; // not a chrome url
let uri = Services.io.newURI(aPath, "UTF-8", null);
let registeryValue = Cc['@mozilla.org/chrome/chrome-registry;1']
.getService(Ci.nsIChromeRegistry)
.convertChromeURL(uri).spec;
log.debug(registeryValue);
if (/^file:/.test(registeryValue))
registeryValue = this._urlToPath(registeryValue);
else
registeryValue = this._urlToPath("file://"+registeryValue);
return registeryValue;
},
_urlToPath: function (aPath) {
if (!aPath || !/^file:/.test(aPath))
return null;
let protocolHandler = Cc["@mozilla.org/network/protocol;1?name=file"]
.createInstance(Ci.nsIFileProtocolHandler);
return protocolHandler.getFileFromURLSpec(aPath).path;
},
dumpObj: function(obj) {}, // Use JSON.stringify(obj) instead.
_nsResolver: function(prefix) {
var ns = {
xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
};
return ns[prefix] || null;
},
// adapted from http://code.google.com/p/jslibs/wiki/InternalTipsAndTricks
XPath: function(ref, xpath) {
var doc = ref.ownerDocument || ref;
const XPathResult = Ci.nsIDOMXPathResult;
try {
let that = this;
var result = doc.evaluate(xpath, ref, that._nsResolver,
XPathResult.ANY_TYPE, null);
} catch (x) {
log.error(x);
}
log.debug("XPathResult="+result.resultType);
switch (result.resultType) {
case XPathResult.NUMBER_TYPE:
return result.numberValue;
case XPathResult.BOOLEAN_TYPE:
return result.booleanValue;
case XPathResult.STRING_TYPE:
return result.stringValue;
} // else XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
var list = [];
try {
for (let node = result.iterateNext(); node; node = result.iterateNext()) {
log.debug("node="+node.nodeName);
switch (node.nodeType) {
case node.ATTRIBUTE_NODE:
list.push(node.value);
break;
case node.TEXT_NODE:
list.push(node.data);
break;
default:
list.push(node);
}
}
} catch (x) {
log.error(x);
}
log.debug("len="+list.length);
return list;
},
/*
* keep a long-living reference to the returned timer, if you don't want to
* see it GC'ed ! see
* http://www.joshmatthews.net/blog/2011/03/nsitimer-anti-pattern/
*/
timer: function(delay, timerType, callback) {
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback({ notify: callback },
delay, timerType);
return timer;
},
/*
* Extracts statements from functions. Intended for feeding a 'oncommand'
* attribute.
* BUG: |let| assignations break oncommand inline callback under TB27
* The statements should probably be limited to a single function call.
*/
bodyToString: function(func) {
let matches = func.toSource().match(/\{([\s\S]*)\}/m);
return matches ? matches[1] : matches;
}
};
////////////////////////// more fundamental helpers //////////////////////////
firetray.js = {
// http://stackoverflow.com/questions/767486/how-do-you-check-if-a-variable-is-an-array-in-javascript
isArray: function(o) {
return this.getType(o) === '[object Array]';
},
getType: function(thing) {
if(thing === null) return "[object Null]"; // special case
return Object.prototype.toString.call(thing);
},
// http://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object-from-json
isEmpty: function(obj) {
for(var prop in obj) {
if(obj.hasOwnProperty(prop))
return false;
}
return true;
},
// values of different ctypes objects can never be compared. See:
// https://developer.mozilla.org/en/js-ctypes/Using_js-ctypes/Working_with_data#Quirks_in_equality
strEquals: function(obj1, obj2) {
return obj1.toString() === obj2.toString();
},
assert: function(condition, message) {
if (!condition) {
throw new Error(message || "Assertion failed");
}
},
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Enumerating_all_properties_of_an_object
listAllProperties: function(obj){
var objectToInspect;
var result = [];
for(objectToInspect = obj; objectToInspect !== null; objectToInspect = Object.getPrototypeOf(objectToInspect)){
result = result.concat(Object.getOwnPropertyNames(objectToInspect));
}
return result;
},
floatToInt: function(nb) { return nb >> 0; } // bitwise ops on signed int
};
// http://stackoverflow.com/questions/18912/how-to-find-keys-of-a-hash
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys
if(!Object.keys) Object.keys = function(o){
if (o !== Object(o))
throw new TypeError('Object.keys called on non-object');
var ret=[],p;
for(p in o) if(Object.prototype.hasOwnProperty.call(o,p)) ret.push(p);
return ret;
};
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/Trim
if(!String.prototype.trim) {
String.prototype.trim = function () {
return this.replace(/^\s+|\s+$/g,'');
};
}