mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-26 02:17:34 +09:00
284 lines
11 KiB
JavaScript
284 lines
11 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* 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/. */
|
|
|
|
/**
|
|
* This file implements helper methods to make the transition of base mailnews
|
|
* objects from JS to C++ easier, and also to allow creating specialized
|
|
* versions of those accounts using only JS XPCOM implementations.
|
|
*
|
|
* In C++ land, the XPCOM component is a generic C++ class that does nothing
|
|
* but delegate any calls to interfaces known in C++ to either the generic
|
|
* C++ implementation (such as nsMsgIncomingServer.cpp) or a JavaScript
|
|
* implementation of those methods. Those delegations could be used for either
|
|
* method-by-method replacement of the generic C++ methods with JavaScript
|
|
* versions, or for specialization of the generic class using JavaScript to
|
|
* implement a particular class type. We use a C++ class as the main XPCOM
|
|
* version for two related reasons: First, we do not want to go through a
|
|
* C++->js->C++ XPCOM transition just to execute a C++ method. Second, C++
|
|
* inheritance is different from JS inheritance, and sometimes the C++ code
|
|
* will ignore the XPCOM parts of the JS, and just execute using C++
|
|
* inheritance.
|
|
*
|
|
* In JavaScript land, the implementation currently uses the XPCOM object for
|
|
* JavaScript calls, with the last object in the prototype chain defaulting
|
|
* to calling using the CPP object, specified in an instance-specific
|
|
* this.cppBase object.
|
|
*
|
|
* Examples of use can be found in the test files for jsaccount stuff.
|
|
*/
|
|
|
|
const EXPORTED_SYMBOLS = ["JSAccountUtils"];
|
|
var JSAccountUtils = {};
|
|
|
|
var Cc = Components.classes;
|
|
var Ci = Components.interfaces;
|
|
var Cu = Components.utils;
|
|
var Cr = Components.results;
|
|
|
|
Cu.import("resource://gre/modules/Log.jsm");
|
|
Cu.import("resource://gre/modules/Preferences.jsm");
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
// Logger definitions.
|
|
const LOGGER_NAME = "JsAccount";
|
|
const PREF_BRANCH_LOG = "mailnews.jsaccount.log.";
|
|
const PREF_LOG_LEVEL = PREF_BRANCH_LOG + "level";
|
|
const PREF_LOG_DUMP = PREF_BRANCH_LOG + "dump";
|
|
|
|
// Set default logging levels.
|
|
const LOG_LEVEL_DEFAULT = "Info"
|
|
const LOG_DUMP_DEFAULT = true;
|
|
|
|
// Logging usage: set mailnews.jsaccount.log.level to the word "Debug" to
|
|
// increase logging level.
|
|
|
|
var log = configureLogging();
|
|
|
|
/**
|
|
*
|
|
* Generic factory to create XPCOM components under JsAccount.
|
|
*
|
|
* @param aProperties This a a const JS object that describes the specific
|
|
* details of a particular JsAccount XPCOM object:
|
|
* {
|
|
* baseContractID: string contractID used to create the base generic C++
|
|
* object. This object must implement the interfaces in
|
|
* baseInterfaces, plus msgIOverride.
|
|
*
|
|
* baseInterfaces: JS array of interfaces implemented by the base, generic
|
|
* C++ object.
|
|
*
|
|
* extraInterfaces: JS array of additional interfaces implemented by the
|
|
* component (accessed using getInterface())
|
|
*
|
|
* contractID: string contract ID for the JS object that will be
|
|
* created by the factory.
|
|
*
|
|
* classID: Components.ID(CID) for the JS object that will be
|
|
* created by the factory, where CID is a string uuid.
|
|
* }
|
|
*
|
|
* @param aJsDelegateConstructor: a JS contructor class, called using new,
|
|
* that will create the JS object to which
|
|
* XPCOM methods calls will be delegated.
|
|
*/
|
|
|
|
JSAccountUtils.jaFactory = function (aProperties, aJsDelegateConstructor)
|
|
{
|
|
let factory = {};
|
|
factory.QueryInterface = XPCOMUtils.generateQI([Ci.nsIFactory]);
|
|
factory.lockFactory = function() {};
|
|
|
|
factory.createInstance = function(outer, iid)
|
|
{
|
|
if (outer != null)
|
|
throw Cr.NS_ERROR_NO_AGGREGATION;
|
|
|
|
// C++ delegator class.
|
|
let delegator = Cc[aProperties.baseContractID]
|
|
.createInstance(Ci.msgIOverride);
|
|
|
|
// Make sure the delegator JS wrapper knows its interfaces.
|
|
aProperties.baseInterfaces.forEach(iface => delegator instanceof iface);
|
|
|
|
// JavaScript overrides of base class functions.
|
|
let jsDelegate = new aJsDelegateConstructor(delegator, aProperties.baseInterfaces);
|
|
delegator.jsDelegate = jsDelegate;
|
|
|
|
// Get the delegate list for this current class. Use OwnProperty in case it
|
|
// inherits from another JsAccount class.
|
|
|
|
let delegateList = null;
|
|
if (Object.getPrototypeOf(jsDelegate).hasOwnProperty("delegateList")) {
|
|
delegateList = Object.getPrototypeOf(jsDelegate).delegateList;
|
|
}
|
|
if (delegateList instanceof Ci.msgIDelegateList) {
|
|
delegator.methodsToDelegate = delegateList;
|
|
} else {
|
|
// Lazily create and populate the list of methods to delegate.
|
|
log.info("creating delegate list for contractID " + aProperties.contractID);
|
|
let delegateList = delegator.methodsToDelegate;
|
|
Object.keys(delegator).forEach(name => {log.debug("delegator has key " + name);});
|
|
|
|
// jsMethods contains the methods that may be targets of the C++ delegation to JS.
|
|
let jsMethods = Object.getPrototypeOf(delegator.jsDelegate.wrappedJSObject);
|
|
for (let name in jsMethods)
|
|
{
|
|
log.debug("processing jsDelegate method: " + name);
|
|
if (name[0] == '_') { // don't bother with methods explicitly marked as internal.
|
|
log.debug("skipping " + name);
|
|
continue;
|
|
}
|
|
// Other methods to skip.
|
|
if (["QueryInterface", // nsISupports
|
|
"methodsToDelegate", "jsDelegate", "cppBase", // msgIOverride
|
|
"delegateList", "wrappedJSObject", // non-XPCOM methods to skip
|
|
].includes(name)) {
|
|
log.debug("skipping " + name);
|
|
continue;
|
|
}
|
|
|
|
let jsDescriptor = getPropertyDescriptor(jsMethods, name);
|
|
if (!jsDescriptor) {
|
|
log.debug("no jsDescriptor for " + name);
|
|
continue;
|
|
}
|
|
let cppDescriptor = Object.getOwnPropertyDescriptor(delegator, name);
|
|
if (!cppDescriptor) {
|
|
log.debug("no cppDescriptor found for " + name);
|
|
// It is OK for jsMethods to have methods that are not used in override of C++.
|
|
continue;
|
|
}
|
|
|
|
let upperCaseName = name[0].toUpperCase() + name.substr(1);
|
|
if ('value' in jsDescriptor) {
|
|
log.info("delegating " + upperCaseName);
|
|
delegateList.add(upperCaseName);
|
|
}
|
|
else {
|
|
if (jsDescriptor.set) {
|
|
log.info("delegating Set" + upperCaseName);
|
|
delegateList.add("Set" + upperCaseName);
|
|
}
|
|
if (jsDescriptor.get) {
|
|
log.info("delegating Get" + upperCaseName);
|
|
delegateList.add("Get" + upperCaseName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save the delegate list for reuse, statically for all instances.
|
|
Object.getPrototypeOf(jsDelegate).delegateList = delegateList;
|
|
}
|
|
|
|
for (let iface of aProperties.baseInterfaces)
|
|
if (iid.equals(iface)) {
|
|
log.debug("Successfully returning delegator " + delegator);
|
|
return delegator;
|
|
}
|
|
throw Cr.NS_ERROR_NO_INTERFACE;
|
|
};
|
|
|
|
return factory;
|
|
}
|
|
|
|
/**
|
|
* Create a JS object that contains calls to each of the methods in a CPP
|
|
* base class, that will reference the cpp object defined on a particular
|
|
* instance of the object. This is intended to be the last item in the
|
|
* prototype chain for a JsAccount implementation.
|
|
*
|
|
* @param aProperties see definition in jsFactory above
|
|
*
|
|
* @returns a JS object suitable as the prototype of a JsAccount implementation.
|
|
*/
|
|
JSAccountUtils.makeCppDelegator = function(aProperties)
|
|
{
|
|
log.info("Making cppDelegator for contractID " + aProperties.contractID);
|
|
let cppDelegator = {};
|
|
let cppDummy = Cc[aProperties.baseContractID].createInstance(Ci.nsISupports);
|
|
// Add methods from all interfaces.
|
|
for (let iface of aProperties.baseInterfaces)
|
|
cppDummy instanceof Ci[iface];
|
|
|
|
for (let method in cppDummy) {
|
|
// skip nsISupports and msgIOverride methods
|
|
if (["QueryInterface", "methodsToDelegate", "jsDelegate", "cppBase", "getInterface"].includes(method)) {
|
|
log.config("Skipping " + method + "\n");
|
|
continue;
|
|
}
|
|
log.config("processing " + method + "\n");
|
|
let descriptor = Object.getOwnPropertyDescriptor(cppDummy, method);
|
|
let property = { enumerable: true };
|
|
// We must use Immediately Invoked Function Expressions to pass method, otherwise it is
|
|
// a closure containing just the last value it was set to.
|
|
if ('value' in descriptor) {
|
|
log.debug("Adding value for " + method);
|
|
property.value = function(aMethod) {
|
|
return function(...args) {
|
|
return Reflect.apply(this.cppBase[aMethod], undefined, args);
|
|
};
|
|
}(method);
|
|
}
|
|
if (descriptor.set) {
|
|
log.debug("Adding setter for " + method);
|
|
property.set = function(aMethod) {
|
|
return function(aVal) {
|
|
this.cppBase[aMethod] = aVal;
|
|
};
|
|
}(method);
|
|
}
|
|
if (descriptor.get) {
|
|
log.debug("Adding getter for " + method);
|
|
property.get = function(aMethod) {
|
|
return function() {
|
|
return this.cppBase[aMethod];
|
|
};
|
|
}(method);
|
|
}
|
|
Object.defineProperty(cppDelegator, method, property);
|
|
}
|
|
return cppDelegator;
|
|
}
|
|
|
|
// Utility functions.
|
|
|
|
// Iterate over an object and its prototypes to get a property descriptor.
|
|
function getPropertyDescriptor(obj, name)
|
|
{
|
|
let descriptor = null;
|
|
|
|
// Eventually we will hit an object that will delegate JS calls to a CPP
|
|
// object, which are not JS overrides of CPP methods. Locate this item, and
|
|
// skip, because it will not have _JsPrototypeToDelegate defined.
|
|
while (obj && ("_JsPrototypeToDelegate" in obj)) {
|
|
descriptor = Object.getOwnPropertyDescriptor(obj, name);
|
|
if (descriptor)
|
|
break;
|
|
obj = Object.getPrototypeOf(obj);
|
|
}
|
|
return descriptor;
|
|
}
|
|
|
|
// Configure the logger based on the preferences.
|
|
function configureLogging()
|
|
{
|
|
let log = Log.repository.getLogger(LOGGER_NAME);
|
|
|
|
// Log messages need to go to the browser console.
|
|
let consoleAppender = new Log.ConsoleAppender(new Log.BasicFormatter());
|
|
log.addAppender(consoleAppender);
|
|
|
|
// Make sure the logger keeps up with the logging level preference.
|
|
log.level = Log.Level[Preferences.get(PREF_LOG_LEVEL, LOG_LEVEL_DEFAULT)];
|
|
|
|
// If enabled in the preferences, add a dump appender.
|
|
let logDumping = Preferences.get(PREF_LOG_DUMP, LOG_DUMP_DEFAULT);
|
|
if (logDumping) {
|
|
let dumpAppender = new Log.DumpAppender(new Log.BasicFormatter());
|
|
log.addAppender(dumpAppender);
|
|
}
|
|
return log;
|
|
}
|