Bug 430716 - part 1, Use mailnews url override as initial conceptual demo of JsAccount, r=jorgk
This commit is contained in:
Родитель
823048784c
Коммит
9fd1161ce4
|
@ -162,6 +162,7 @@
|
|||
; interfaces.manifest doesn't get packaged because it is dynamically
|
||||
; re-created at packaging time when linking the xpts that will actually
|
||||
; go into the package, so the test related interfaces aren't included.
|
||||
@RESPATH@/components/msgjsaccount.xpt
|
||||
@RESPATH@/components/mime.xpt
|
||||
@RESPATH@/components/mimeJSComponents.js
|
||||
@RESPATH@/components/msgMime.manifest
|
||||
|
|
|
@ -190,6 +190,12 @@
|
|||
#include "nsSmtpServer.h"
|
||||
#include "nsMsgCompUtils.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// jsAccount includes
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#include "msgJsAccountCID.h"
|
||||
#include "JaUrl.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// imap includes
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -585,6 +591,13 @@ NS_DEFINE_NAMED_CID(NS_MSGQUOTELISTENER_CID);
|
|||
NS_DEFINE_NAMED_CID(NS_URLFETCHER_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_MSGCOMPUTILS_CID);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// jsAccount factories
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(JaCppUrlDelegator)
|
||||
|
||||
NS_DEFINE_NAMED_CID(JACPPURLDELEGATOR_CID);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// imap factories
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -968,6 +981,8 @@ const mozilla::Module::CIDEntry kMailNewsCIDs[] = {
|
|||
{ &kNS_MSGQUOTELISTENER_CID, false, NULL, nsMsgQuoteListenerConstructor},
|
||||
{ &kNS_URLFETCHER_CID, false, NULL, nsURLFetcherConstructor},
|
||||
{ &kNS_MSGCOMPUTILS_CID, false, NULL, nsMsgCompUtilsConstructor},
|
||||
// JsAccount Entries
|
||||
{ &kJACPPURLDELEGATOR_CID, false, nullptr, JaCppUrlDelegatorConstructor },
|
||||
// Imap Entries
|
||||
{ &kNS_IMAPURL_CID, false, NULL, nsImapUrlConstructor },
|
||||
{ &kNS_IMAPPROTOCOL_CID, false, nullptr, nsImapProtocolConstructor },
|
||||
|
@ -1184,6 +1199,8 @@ const mozilla::Module::ContractIDEntry kMailNewsContracts[] = {
|
|||
{ NS_MSGQUOTELISTENER_CONTRACTID, &kNS_MSGQUOTELISTENER_CID },
|
||||
{ NS_URLFETCHER_CONTRACTID, &kNS_URLFETCHER_CID },
|
||||
{ NS_MSGCOMPUTILS_CONTRACTID, &kNS_MSGCOMPUTILS_CID },
|
||||
// JsAccount Entries
|
||||
{ JACPPURLDELEGATOR_CONTRACTID, &kJACPPURLDELEGATOR_CID },
|
||||
// Imap Entries
|
||||
{ NS_IMAPINCOMINGSERVER_CONTRACTID, &kNS_IMAPINCOMINGSERVER_CID },
|
||||
{ NS_RDF_RESOURCE_FACTORY_CONTRACTID_PREFIX "imap", &kNS_IMAPRESOURCE_CID },
|
||||
|
@ -1304,6 +1321,7 @@ static const mozilla::Module::CategoryEntry kMailNewsCategories[] = {
|
|||
// Bayesian Filter Entries
|
||||
// Compose Entries
|
||||
{ "command-line-handler", "m-compose", NS_MSGCOMPOSESTARTUPHANDLER_CONTRACTID },
|
||||
// JsAccount Entries
|
||||
// Imap Entries
|
||||
// Local Entries
|
||||
// msgdb Entries
|
||||
|
|
|
@ -0,0 +1,282 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
|
||||
/* 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;
|
||||
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
|
||||
/* 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/. */
|
||||
|
||||
const EXPORTED_SYMBOLS = ["JaBaseUrlProperties", "JaBaseUrl"];
|
||||
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
var Cr = Components.results;
|
||||
var Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/jsaccount/JSAccountUtils.jsm");
|
||||
|
||||
// A partial JavaScript implementation of the base server methods.
|
||||
|
||||
const JaBaseUrlProperties = {
|
||||
|
||||
// The CPP object that delgates to CPP or JS.
|
||||
baseContractID: "@mozilla.org/jacppurldelegator;1",
|
||||
|
||||
// Interfaces implemented by the base CPP version of this object.
|
||||
baseInterfaces: [ Ci.nsIURI,
|
||||
Ci.nsIURL,
|
||||
Ci.nsIMsgMailNewsUrl,
|
||||
Ci.nsIMsgMessageUrl,
|
||||
Ci.msgIOverride,
|
||||
Ci.nsISupports,
|
||||
Ci.nsIInterfaceRequestor,
|
||||
],
|
||||
|
||||
// We don't typically define this as a creatable component, but if we do use
|
||||
// these. Subclasses for particular account types require these defined for
|
||||
// that type.
|
||||
contractID: "@mozilla.org/jsaccount/jaurl;1",
|
||||
classID: Components.ID("{1E7B42CA-E6D9-408F-A4E4-8D2F82AECBBD}"),
|
||||
};
|
||||
|
||||
function JaBaseUrl(aDelegator, aBaseInterfaces) {
|
||||
|
||||
// Typical boilerplate to include in all implementations.
|
||||
|
||||
// Object delegating method calls to the appropriate XPCOM object.
|
||||
// Weak because it owns us.
|
||||
this._delegatorWeak = Cu.getWeakReference(aDelegator);
|
||||
|
||||
// Base implementation of methods with no overrides.
|
||||
this.cppBase = aDelegator.cppBase;
|
||||
|
||||
// cppBase class sees all interfaces
|
||||
aBaseInterfaces.forEach(iface => this.cppBase instanceof iface);
|
||||
}
|
||||
|
||||
JaBaseUrl.prototype = {
|
||||
// Typical boilerplate to include in all implementations.
|
||||
__proto__: JSAccountUtils.makeCppDelegator(JaBaseUrlProperties),
|
||||
|
||||
// Flag this item as CPP needs to delegate to JS.
|
||||
_JsPrototypeToDelegate: true,
|
||||
|
||||
// QI to the interfaces.
|
||||
QueryInterface: XPCOMUtils.generateQI(JaBaseUrlProperties.baseInterfaces),
|
||||
|
||||
// Used to access an instance as JS, bypassing XPCOM.
|
||||
get wrappedJSObject() {
|
||||
return this;
|
||||
},
|
||||
|
||||
// Accessor to the weak cpp delegator.
|
||||
get delegator() {
|
||||
return this._delegatorWeak.get();
|
||||
},
|
||||
|
||||
// Dynamically-generated list of delegate methods.
|
||||
delegateList: null,
|
||||
|
||||
// Implementation in JS (if any) of methods in XPCOM interfaces.
|
||||
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DIRS += [
|
||||
'public',
|
||||
'src'
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.jsaccount += [
|
||||
'modules/JaBaseUrl.jsm',
|
||||
'modules/JSAccountUtils.jsm',
|
||||
]
|
||||
|
||||
TEST_DIRS += ['test']
|
|
@ -0,0 +1,17 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'msgIDelegateList.idl',
|
||||
'msgIOverride.idl',
|
||||
]
|
||||
|
||||
EXPORTS += [
|
||||
'msgJsAccountCID.h',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'msgjsaccount'
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
/**
|
||||
* This interface provides a list of methods that should be delegated to
|
||||
* a JsObject rather than a C++ XPCOM base object in JsAccount classes.
|
||||
*/
|
||||
|
||||
[scriptable, builtinclass, uuid(627D3A34-F8A3-40eb-91FE-E413D6638D27)]
|
||||
interface msgIDelegateList : nsISupports
|
||||
{
|
||||
/// Method name to delegate to JavaScript.
|
||||
void add(in string aMethod);
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
#include "msgIDelegateList.idl"
|
||||
|
||||
/**
|
||||
* Mailnews code typically has a C++ base class for objects, which is then
|
||||
* specialized for each account type with a C++ subclass of the base class.
|
||||
*
|
||||
* This interface provides the ability of JavaScript-based account
|
||||
* implementations to use the same C++ base classes as core objects, but
|
||||
* use JavaScript to override methods instead of C++.
|
||||
*/
|
||||
|
||||
[scriptable, uuid(68075269-8BBD-4a09-AC04-3241BF44F633)]
|
||||
interface msgIOverride : nsISupports
|
||||
{
|
||||
/**
|
||||
*
|
||||
* A list of methods in the C++ base class that will be delegated to the JS
|
||||
* delegate. This is calculated once, and then a fixed value is set to
|
||||
* all subsequent instances so that it does not need to be recalculated each
|
||||
* time. If the value has not yet been set, this will return a new instance.
|
||||
*/
|
||||
attribute msgIDelegateList methodsToDelegate;
|
||||
|
||||
/**
|
||||
* JavaScript-based xpcom object that overrides C++ methods.
|
||||
*/
|
||||
attribute nsISupports jsDelegate;
|
||||
|
||||
/**
|
||||
* C++ class used to implement default functionality. This is used when
|
||||
* JavaScript methods want to call the base class default action, bypassing a
|
||||
* possible JS override.
|
||||
*/
|
||||
readonly attribute nsISupports cppBase;
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
// Contains the definitions of contract IDs for modules in JsAccounts.
|
||||
|
||||
#ifndef _msgJsAccountCID_H_
|
||||
#define _msgJsAccountCID_H_
|
||||
|
||||
#define JACPPURLDELEGATOR_CID \
|
||||
{ 0x1a0b778c, 0x2fe6, 0x4012, { 0xb4, 0xf3, 0xe8, 0x1c, 0xc, 0x11, 0x64, 0x9 } }
|
||||
#define JACPPURLDELEGATOR_CONTRACTID "@mozilla.org/jacppurldelegator;1"
|
||||
|
||||
#endif
|
|
@ -0,0 +1,56 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>JsAccount Usage and Architecture</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Overview</h1>
|
||||
<p>JsAccount is a technology that allows message account types to be created
|
||||
in Mozilla Mailnews code using JavaScript. Although this is primarily
|
||||
targeted at allowing extensions to create new accounts, it might also be
|
||||
useful as a bridge to convert existing account types from being C++ based
|
||||
to JavaScript based.</p>
|
||||
<h2>Existing C++-based architecture of mailnews accounts</h2>
|
||||
<p>In mailnews code, an account type is a set of classes that allow
|
||||
implementation of a messaging particular protocol. The account type is
|
||||
given a short string identifier ("imap", "news", "pop3") and is then used
|
||||
to create objects of the appropriate type by appending that string to the
|
||||
end of a base XPCOM contractID. So, for example, to create an imap server,
|
||||
you generate a contractID using a base ID,
|
||||
NS_MSGINCOMINGSERVER_CONTRACTID_PREFIX
|
||||
"@mozilla.org/messenger/server;1?type=", then append "imap" to get:</p>
|
||||
<p>@mozilla.org/messenger/server;1?type=imap</p>
|
||||
<p>In the C++ code, there is a base object implementing shared
|
||||
functionality. An account-specific class inherits that base functionality,
|
||||
then extends it to represent the account-specific behavior that is needed.
|
||||
This same basic concept is used to represent a whole series of classes
|
||||
that are necessary to implement a specific mailnews account type.</p>
|
||||
<p>For the server example, there is a base class named
|
||||
nsMsgIncomingServer.cpp that implements that base interface
|
||||
nsIMsgIncomingServer.idl. For imap, there is a specific class
|
||||
nsImapIncomingServer.cpp that inherits from nsMsgIncomingServer.cpp,
|
||||
overrides some of the methods in nsIMsgIncomingServer.idl, and also
|
||||
implements an imap-specific interface nsIImapIncomingServer.idl. All of
|
||||
this works fine using C++ inheritance and polymorphism.</p>
|
||||
<p>Although JsAccount is intended mostly for mailnews accounts, the same
|
||||
basic method of using a base class extended for specific types is also used
|
||||
in other ways in mailnews code, including for addressbook types and views.
|
||||
The technology may also be applied to those other object types as well.</p>
|
||||
<h2>Role of JsAccount</h2>
|
||||
<p>The JavaScript class system works very differently than the C++ system,
|
||||
and you cannot use normal language constructs to override a C++ class with
|
||||
a JavaScript class. What JsAccount allows you to do is to create XPCOM
|
||||
objects in JavaScript, and use those objects to override or extend the
|
||||
methods from the C++ base class in a way that will function correctly
|
||||
whether those objects are executed from within C++ code or JavaScript
|
||||
code. This allows you to create a new account using JavaScript code, while
|
||||
using the same base class functionality that is used by the core C++
|
||||
account types. Thus a new account type may be created in JavaScript-based
|
||||
extension. The technology may also be used to create JavaScript
|
||||
versions of existing account types in an incremental manner, slowly
|
||||
converting methods from C++ to JavaScript.</p>
|
||||
<p><br>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,33 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "DelegateList.h"
|
||||
|
||||
// This class is used within JsAccount to allow static storage of a list
|
||||
// of methods to be overridden by JS implementations, in a way that can
|
||||
// be stored and manipulated in JS, but used efficiently in C++.
|
||||
|
||||
namespace mozilla {
|
||||
namespace mailnews {
|
||||
|
||||
NS_IMPL_ISUPPORTS(DelegateList, msgIDelegateList)
|
||||
|
||||
NS_IMETHODIMP DelegateList::Add(const char *aMethodName)
|
||||
{
|
||||
// __FUNCTION__ is the undecorated function name in gcc, but decorated in
|
||||
// Windows. __func__ will resolve this when supported in VS 2015.
|
||||
nsCString prettyFunction;
|
||||
#if defined (_MSC_VER)
|
||||
prettyFunction.Append(mPrefix);
|
||||
#endif
|
||||
prettyFunction.Append(nsDependentCString(aMethodName));
|
||||
|
||||
mMethods.Put(prettyFunction, true);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mailnews
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,52 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef _DelegateList_H_
|
||||
#define _DelegateList_H_
|
||||
|
||||
#include "nsISupports.h"
|
||||
#include "msgIDelegateList.h"
|
||||
#include "nsDataHashtable.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace mailnews {
|
||||
|
||||
// This class provides a list of method names to delegate to another object.
|
||||
class DelegateList : public msgIDelegateList
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_MSGIDELEGATELIST
|
||||
DelegateList(const char *aWindowsPrefix) :
|
||||
mPrefix(aWindowsPrefix)
|
||||
{ }
|
||||
nsDataHashtable<nsCStringHashKey, bool> mMethods;
|
||||
|
||||
protected:
|
||||
virtual ~DelegateList() { }
|
||||
nsCString mPrefix; // Windows decorated method prefix.
|
||||
};
|
||||
|
||||
} // namespace mailnews
|
||||
} // namespace mozilla
|
||||
|
||||
/*
|
||||
* This macro is used in forwarding functions.
|
||||
* _interface: the interface being forwarded.
|
||||
* _jsdelegate: the name of the JS pointer that implements a particular
|
||||
* interface.
|
||||
*
|
||||
* You must follow the naming convention:
|
||||
* 1) use mCppBase as the name of the C++ base class instance.
|
||||
* 2) use mMethod as the name of the DelegateList object.
|
||||
**/
|
||||
|
||||
#define DELEGATE_JS(_interface, _jsdelegate) (\
|
||||
_jsdelegate && mMethods && \
|
||||
mMethods->Contains(nsLiteralCString(__FUNCTION__)) ? \
|
||||
_jsdelegate : nsCOMPtr<_interface>(do_QueryInterface(mCppBase)))
|
||||
|
||||
#endif
|
|
@ -0,0 +1,198 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "JaUrl.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsIMessenger.h"
|
||||
#include "nsIMsgHdr.h"
|
||||
#include "nsISupportsUtils.h"
|
||||
#include "nsMsgBaseCID.h"
|
||||
|
||||
// This file contains an implementation of mailnews URLs in JsAccount.
|
||||
|
||||
namespace mozilla {
|
||||
namespace mailnews {
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED(JaBaseCppUrl, nsMsgMailNewsUrl,
|
||||
nsIMsgMessageUrl,
|
||||
nsIInterfaceRequestor)
|
||||
|
||||
// nsIMsgMailNewsUrl overrides
|
||||
NS_IMETHODIMP JaBaseCppUrl::GetFolder(nsIMsgFolder **aFolder)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aFolder);
|
||||
NS_IF_ADDREF(*aFolder = mFolder);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP JaBaseCppUrl::SetFolder(nsIMsgFolder *aFolder)
|
||||
{
|
||||
mFolder = aFolder;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// nsIMsgMessageUrl implementation
|
||||
NS_IMETHODIMP JaBaseCppUrl::GetUri(char **aUri)
|
||||
{
|
||||
if (!mUri.IsEmpty())
|
||||
*aUri = ToNewCString(mUri);
|
||||
else
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP JaBaseCppUrl::SetUri(const char *aUri)
|
||||
{
|
||||
mUri = aUri;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP JaBaseCppUrl::GetMessageFile(nsIFile **aMessageFile)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aMessageFile);
|
||||
NS_IF_ADDREF(*aMessageFile = mMessageFile);
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP JaBaseCppUrl::SetMessageFile(nsIFile *aMessageFile)
|
||||
{
|
||||
mMessageFile = aMessageFile;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP JaBaseCppUrl::GetAddDummyEnvelope(bool *aAddDummyEnvelope)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
NS_IMETHODIMP JaBaseCppUrl::SetAddDummyEnvelope(bool aAddDummyEnvelope)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP JaBaseCppUrl::GetCanonicalLineEnding(bool *aCanonicalLineEnding)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aCanonicalLineEnding);
|
||||
*aCanonicalLineEnding = mCanonicalLineEnding;
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP JaBaseCppUrl::SetCanonicalLineEnding(bool aCanonicalLineEnding)
|
||||
{
|
||||
mCanonicalLineEnding = aCanonicalLineEnding;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP JaBaseCppUrl::GetOriginalSpec(char **aOriginalSpec)
|
||||
{
|
||||
if (!aOriginalSpec || mOriginalSpec.IsEmpty())
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
*aOriginalSpec = ToNewCString(mOriginalSpec);
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP JaBaseCppUrl::SetOriginalSpec(const char *aOriginalSpec)
|
||||
{
|
||||
mOriginalSpec = aOriginalSpec;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP JaBaseCppUrl::GetMessageHeader(nsIMsgDBHdr **aMessageHeader)
|
||||
{
|
||||
// This routine does a lookup using messenger, assumming that the message URI
|
||||
// has been set in mUri.
|
||||
NS_ENSURE_TRUE(!mUri.IsEmpty(), NS_ERROR_NOT_INITIALIZED);
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIMessenger> messenger(do_CreateInstance(NS_MESSENGER_CONTRACTID, &rv));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<nsIMsgDBHdr> msgHdr;
|
||||
rv = messenger->MsgHdrFromURI(mUri, getter_AddRefs(msgHdr));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
msgHdr.forget(aMessageHeader);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP JaBaseCppUrl::SetMessageHeader(nsIMsgDBHdr *aMsgHdr)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
// nsIInterfaceRequestor implementation
|
||||
NS_IMETHODIMP JaBaseCppUrl::GetInterface(const nsIID & aIID, void **aSink)
|
||||
{
|
||||
return QueryInterface(aIID, aSink);
|
||||
}
|
||||
|
||||
// Delegator
|
||||
NS_IMPL_ISUPPORTS_INHERITED(JaCppUrlDelegator,
|
||||
JaBaseCppUrl,
|
||||
msgIOverride)
|
||||
|
||||
// Delegator object to bypass JS method override.
|
||||
NS_IMPL_ISUPPORTS(JaCppUrlDelegator::Super,
|
||||
nsIMsgMailNewsUrl,
|
||||
nsIMsgMessageUrl,
|
||||
nsIURI,
|
||||
nsIURL,
|
||||
nsIInterfaceRequestor)
|
||||
|
||||
JaCppUrlDelegator::JaCppUrlDelegator() :
|
||||
mCppBase(new Super(this)),
|
||||
mMethods(nullptr)
|
||||
{ }
|
||||
|
||||
NS_IMETHODIMP JaCppUrlDelegator::SetMethodsToDelegate(msgIDelegateList *aDelegateList)
|
||||
{
|
||||
if (!aDelegateList)
|
||||
{
|
||||
NS_WARNING("Null delegate list");
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
// We static_cast since we want to use the hash object directly.
|
||||
mDelegateList = static_cast<DelegateList*> (aDelegateList);
|
||||
mMethods = &(mDelegateList->mMethods);
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP JaCppUrlDelegator::GetMethodsToDelegate(msgIDelegateList **aDelegateList)
|
||||
{
|
||||
if (!mDelegateList)
|
||||
mDelegateList = new DelegateList("mozilla::mailnews::JaCppUrlDelegator::");
|
||||
mMethods = &(mDelegateList->mMethods);
|
||||
NS_ADDREF(*aDelegateList = mDelegateList);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP JaCppUrlDelegator::SetJsDelegate(nsISupports *aJsDelegate)
|
||||
{
|
||||
// If these QIs fail, then overrides are not provided for methods in that
|
||||
// interface, which is OK.
|
||||
mJsISupports = aJsDelegate;
|
||||
mJsIMsgMailNewsUrl = do_QueryInterface(aJsDelegate);
|
||||
mJsIURI = do_QueryInterface(aJsDelegate);
|
||||
mJsIURL = do_QueryInterface(aJsDelegate);
|
||||
mJsIMsgMessageUrl = do_QueryInterface(aJsDelegate);
|
||||
mJsIInterfaceRequestor = do_QueryInterface(aJsDelegate);
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP JaCppUrlDelegator::GetJsDelegate(nsISupports **aJsDelegate)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aJsDelegate);
|
||||
if (mJsISupports)
|
||||
{
|
||||
NS_ADDREF(*aJsDelegate = mJsISupports);
|
||||
return NS_OK;
|
||||
}
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP JaCppUrlDelegator::GetCppBase(nsISupports **aCppBase)
|
||||
{
|
||||
nsCOMPtr<nsISupports> cppBaseSupports;
|
||||
cppBaseSupports = NS_ISUPPORTS_CAST(nsIMsgMailNewsUrl*, mCppBase);
|
||||
NS_ENSURE_STATE(cppBaseSupports);
|
||||
cppBaseSupports.forget(aCppBase);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mailnews
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,117 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef _JaUrl_H_
|
||||
#define _JaUrl_H_
|
||||
|
||||
#include "DelegateList.h"
|
||||
#include "msgCore.h"
|
||||
#include "msgIOverride.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsIInterfaceRequestor.h"
|
||||
#include "nsIMsgFolder.h"
|
||||
#include "nsISupports.h"
|
||||
#include "nsMsgMailNewsUrl.h"
|
||||
#include "nsWeakReference.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace mailnews {
|
||||
|
||||
/* Header file */
|
||||
|
||||
// This class is an XPCOM component, usable in JS, that calls the methods
|
||||
// in the C++ base class (bypassing any JS override).
|
||||
class JaBaseCppUrl : public nsMsgMailNewsUrl,
|
||||
public nsIMsgMessageUrl,
|
||||
public nsIInterfaceRequestor
|
||||
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_NSIMSGMESSAGEURL
|
||||
NS_DECL_NSIINTERFACEREQUESTOR
|
||||
JaBaseCppUrl() { }
|
||||
|
||||
// nsIMsgMailNewsUrl overrides
|
||||
NS_IMETHOD GetFolder(nsIMsgFolder **aFolder);
|
||||
NS_IMETHOD SetFolder(nsIMsgFolder *aFolder);
|
||||
|
||||
protected:
|
||||
virtual ~JaBaseCppUrl() { }
|
||||
|
||||
// nsIMsgMailUrl variables.
|
||||
|
||||
nsCOMPtr<nsIMsgFolder> mFolder;
|
||||
|
||||
// nsIMsgMessageUrl variables.
|
||||
|
||||
// the uri for the original message, like ews-message://server/folder#123
|
||||
nsCString mUri;
|
||||
nsCOMPtr<nsIFile> mMessageFile;
|
||||
bool mCanonicalLineEnding;
|
||||
nsCString mOriginalSpec;
|
||||
};
|
||||
|
||||
class JaCppUrlDelegator : public JaBaseCppUrl,
|
||||
public msgIOverride
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_MSGIOVERRIDE
|
||||
|
||||
NS_FORWARD_NSIMSGMAILNEWSURL(DELEGATE_JS(nsIMsgMailNewsUrl, mJsIMsgMailNewsUrl)->)
|
||||
NS_FORWARD_NSIURI(DELEGATE_JS(nsIURI, mJsIURI)->)
|
||||
NS_FORWARD_NSIURL(DELEGATE_JS(nsIURL, mJsIURL)->)
|
||||
NS_FORWARD_NSIMSGMESSAGEURL(DELEGATE_JS(nsIMsgMessageUrl, mJsIMsgMessageUrl)->)
|
||||
NS_FORWARD_NSIINTERFACEREQUESTOR(DELEGATE_JS(nsIInterfaceRequestor, mJsIInterfaceRequestor)->)
|
||||
|
||||
JaCppUrlDelegator();
|
||||
|
||||
class Super : public nsIMsgMailNewsUrl,
|
||||
public nsIMsgMessageUrl,
|
||||
public nsIInterfaceRequestor
|
||||
{
|
||||
public:
|
||||
Super(JaCppUrlDelegator *aFakeThis) {mFakeThis = aFakeThis;}
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_FORWARD_NSIMSGMAILNEWSURL(mFakeThis->JaBaseCppUrl::)
|
||||
NS_FORWARD_NSIURI(mFakeThis->JaBaseCppUrl::)
|
||||
NS_FORWARD_NSIURL(mFakeThis->JaBaseCppUrl::)
|
||||
NS_FORWARD_NSIMSGMESSAGEURL(mFakeThis->JaBaseCppUrl::)
|
||||
NS_FORWARD_NSIINTERFACEREQUESTOR(mFakeThis->JaBaseCppUrl::)
|
||||
private:
|
||||
virtual ~Super() {}
|
||||
JaCppUrlDelegator *mFakeThis;
|
||||
};
|
||||
|
||||
private:
|
||||
virtual ~JaCppUrlDelegator() {
|
||||
}
|
||||
|
||||
// Interfaces that may be overridden by JS.
|
||||
nsCOMPtr<nsIMsgMailNewsUrl> mJsIMsgMailNewsUrl;
|
||||
nsCOMPtr<nsIURI> mJsIURI;
|
||||
nsCOMPtr<nsIURL> mJsIURL;
|
||||
nsCOMPtr<nsIMsgMessageUrl> mJsIMsgMessageUrl;
|
||||
nsCOMPtr<nsIInterfaceRequestor> mJsIInterfaceRequestor;
|
||||
|
||||
// Owning reference to the JS override.
|
||||
nsCOMPtr<nsISupports> mJsISupports;
|
||||
|
||||
// Class to bypass JS delegates. nsCOMPtr for when we do cycle collection.
|
||||
nsCOMPtr<nsIMsgMailNewsUrl> mCppBase;
|
||||
|
||||
RefPtr<DelegateList> mDelegateList;
|
||||
nsDataHashtable<nsCStringHashKey, bool> *mMethods;
|
||||
};
|
||||
|
||||
} // namespace mailnews
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
|
@ -0,0 +1,18 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
SOURCES += [
|
||||
'DelegateList.cpp',
|
||||
'JaUrl.cpp',
|
||||
]
|
||||
|
||||
EXPORTS += [
|
||||
'DelegateList.h',
|
||||
'JaUrl.h',
|
||||
]
|
||||
|
||||
Library('JsAccount')
|
||||
FINAL_LIBRARY = 'mail'
|
|
@ -0,0 +1,14 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# Any copyright is dedicated to the Public Domain.
|
||||
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'msgIFooUrl.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'testJsAccount'
|
||||
|
||||
TEST_HARNESS_FILES.xpcshell.mailnews.jsaccount.test.unit.resources += [
|
||||
'!/dist/bin/components/testJsAccount.xpt',
|
||||
]
|
|
@ -0,0 +1,19 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// This is a sample test interface implemented by the URL object.
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
[scriptable, uuid(12CAD9FC-57FC-4AEE-A800-895A289237DD)]
|
||||
interface msgIFooUrl : nsISupports
|
||||
{
|
||||
/// Foo id for item.
|
||||
attribute AString itemId;
|
||||
/// Does this url refer to an attachment?
|
||||
readonly attribute boolean isAttachment;
|
||||
/// urlType (copy, move, display) from nsIMsgMailNewsUrl
|
||||
void setUrlType(in unsigned long aType);
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# Any copyright is dedicated to the Public Domain.
|
||||
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
TEST_DIRS += [
|
||||
'idl',
|
||||
]
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
var Cr = Components.results;
|
||||
var CC = Components.Constructor;
|
||||
var Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource:///modules/mailServices.js");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://testing-common/mailnews/mailTestUtils.js");
|
||||
Cu.import("resource://testing-common/mailnews/localAccountUtils.js");
|
||||
|
||||
// Load the test components.
|
||||
do_load_manifest("resources/testComponents.manifest")
|
||||
// Ensure the profile directory is set up.
|
||||
do_get_profile();
|
|
@ -0,0 +1,62 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type">
|
||||
<title>JsAccount Usage and Architecture</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Overview of Testing Objects </h2>
|
||||
This directory contains sample JS components to test the basic JsAccount
|
||||
concepts, which can also serve as templates for new implementations.<br>
|
||||
<h3>Component Naming</h3>
|
||||
Because there are many different components involved with different roles,
|
||||
it will be helpful to keep things straight by using a specific naming
|
||||
convention for objects.
|
||||
For testing, we consider that we are creating a new account type "foo". For
|
||||
the specific case of the JA implementation of an object that implements the
|
||||
nsIMsgMailNewsUrl interface, we'll use the following naming convention.
|
||||
Typically C++ classes and JS constructors are capitalized, class instances
|
||||
are not.<br>
|
||||
<br>
|
||||
Names are constructed with the following subparts:<br>
|
||||
<br>
|
||||
(<strong>Ja)</strong>(<strong>AccountType</strong>)(<strong>Language</strong>)(<strong>ComponentType</strong>)(<strong>Role</strong>)
|
||||
<br>
|
||||
<h4>Common parts of names</h4>
|
||||
<ul>
|
||||
<li><strong>Ja</strong>: All objects or classes begin with Ja</li>
|
||||
<li><strong>AccountType</strong>: the type of account being created (here
|
||||
<strong>Foo</strong>), or <strong>Base</strong> for the generic
|
||||
implementation that is the base class of all types. May be *blank* if
|
||||
the object is used for both base types and specific types.</li>
|
||||
<li>
|
||||
<strong>Language</strong>: Use <strong>Cpp</strong> with objects and
|
||||
classes implemented using C++, leave blank for JS versions.
|
||||
</li>
|
||||
<li>
|
||||
<strong>ComponentType</strong>: the standard MailNews term for objects
|
||||
that implement a particular interface. The legacy .cpp base components
|
||||
are typically named:<br>
|
||||
<strong>(nsMsg)(ComponentType</strong>).cpp<br>
|
||||
for example <strong>nsMsgIncomingServer</strong>.cpp. This may be
|
||||
shortened where appropriate, for example we use <strong>Url</strong>
|
||||
instead of <strong>MailNewsUrl</strong> as the <strong>Ja</strong>
|
||||
prefix implies that this is an implementation of MailNews objects.</li>
|
||||
<li><strong>Role</strong>: the function of the object within the JA
|
||||
architecture.</li>
|
||||
<ul>
|
||||
<li><strong>Constructor</strong>: Creates the object only. </li>
|
||||
<li><strong>Delegator</strong>: Calls the appropriate object, either a
|
||||
JS or C++ variant, that implements a particular XPCOM method.
|
||||
</li>
|
||||
<li> <strong>Properties</strong>: JavaScript object that establishes
|
||||
properties of a JA implementation class. This is used for automatic
|
||||
generation of a list of methods to delegate to the JavaScript classes.</li>
|
||||
<li>(blank): Actual implementation.</li>
|
||||
</ul>
|
||||
</ul>
|
||||
Example: The C++ class that delegates the implementation of
|
||||
nsIMsgMailNewsUrl (abbreviated as Url) to either a C++ or JS method is
|
||||
called <strong>JaCppUrlDelegator</strong>.
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,3 @@
|
|||
interfaces testJsAccount.xpt
|
||||
component {73F98539-A59F-4F6F-9A72-D83A08646C23} testJaFooUrlComponent.js
|
||||
contract @mozilla.org/jsaccount/testjafoourl;1 {73F98539-A59F-4F6F-9A72-D83A08646C23}
|
|
@ -0,0 +1,119 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
One of the goals of JsAccount is to be able to incrementally extend a base
|
||||
implementation, possibly adding a new interface. This code demonstrates
|
||||
a mailnews URL extended for a hypthetical account type "foo".
|
||||
**/
|
||||
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
var Cr = Components.results;
|
||||
var Cu = Components.utils;
|
||||
var CE = Components.Exception;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/jsaccount/JSAccountUtils.jsm");
|
||||
Cu.import("resource://gre/modules/jsaccount/JaBaseUrl.jsm");
|
||||
|
||||
const ATTACHMENT_QUERY = "part=1.";
|
||||
|
||||
var FooUrlProperties = {
|
||||
// Extend the base properties.
|
||||
__proto__: JaBaseUrlProperties,
|
||||
|
||||
contractID: "@mozilla.org/jsaccount/testjafoourl;1",
|
||||
classID: Components.ID("{73F98539-A59F-4F6F-9A72-D83A08646C23}"),
|
||||
|
||||
// Add an additional interface only needed by this custom class.
|
||||
extraInterfaces: [ Ci.msgIFooUrl ],
|
||||
}
|
||||
|
||||
// Constructor
|
||||
function FooUrlConstructor() {
|
||||
}
|
||||
|
||||
// Constructor prototype (not instance prototype).
|
||||
FooUrlConstructor.prototype = {
|
||||
classID: FooUrlProperties.classID,
|
||||
_xpcom_factory: JSAccountUtils.jaFactory(FooUrlProperties, FooUrl),
|
||||
}
|
||||
|
||||
// Main class.
|
||||
function FooUrl(aDelegator, aBaseInterfaces) {
|
||||
|
||||
// Superclass constructor
|
||||
JaBaseUrl.call(this, aDelegator, aBaseInterfaces);
|
||||
|
||||
// I'm not sure why I have to call this again, as it is called in the
|
||||
// base constructor, but without it this method will not find the
|
||||
// interfaces beyond nsISupports.
|
||||
aBaseInterfaces.forEach(iface => this.cppBase instanceof iface);
|
||||
|
||||
// instance variables
|
||||
this._urlType = -1; // unknown;
|
||||
this._itemId = null;
|
||||
this._hidden = "IAmHidden";
|
||||
}
|
||||
|
||||
// Extend the base class methods.
|
||||
FooUrl.prototype = {
|
||||
|
||||
// Typical boilerplate to include in all implementations.
|
||||
|
||||
// Extended the JS URL object.
|
||||
__proto__: JaBaseUrl.prototype,
|
||||
|
||||
// Delegate these methods to CPP.
|
||||
_JsPrototypeToDelegate: true,
|
||||
|
||||
// InterfaceRequestor override, needed if extraInterfaces.
|
||||
|
||||
getInterface: function(iid) {
|
||||
for (let iface of FooUrlProperties.extraInterfaces) {
|
||||
if (iid.equals(iface))
|
||||
return this;
|
||||
}
|
||||
return this.delegator.QueryInterface(iid);
|
||||
},
|
||||
|
||||
// nsIURI overrides
|
||||
|
||||
// Override clone() to always make the pathname capitalized. This is useful
|
||||
// to test that a method called from C++, but overridden in JS, gets the JS
|
||||
// method, since nsMsgMailNewsUrl::CloneIgnoringRef calls Clone().
|
||||
clone: function()
|
||||
{
|
||||
let uriClone = this.cppBase.clone();
|
||||
uriClone.path = uriClone.path.toUpperCase();
|
||||
return uriClone;
|
||||
},
|
||||
|
||||
// nsIMsgMailNewsUrl overrides
|
||||
|
||||
// Override to allow setting from a JS variable.
|
||||
IsUrlType(aType) { return aType == this._urlType;},
|
||||
|
||||
// msgIFooUrl implementation
|
||||
|
||||
// Foo id for item.
|
||||
// attribute AString itemId;
|
||||
get itemId() { return this._itemId;},
|
||||
set itemId(aVal) {this._itemId = aVal;},
|
||||
|
||||
// Does this url refer to an attachment?
|
||||
//readonly attribute boolean isAttachment;
|
||||
get isAttachment() {
|
||||
// We look to see if the URL has an attachment query
|
||||
let query = this.QueryInterface(Ci.nsIURL).query;
|
||||
return (query && query.indexOf(ATTACHMENT_QUERY) != -1);
|
||||
},
|
||||
|
||||
/// urlType (copy, move, display) from nsIMsgMailNewsUrl
|
||||
//void setUrlType(in unsigned long aType);
|
||||
setUrlType: function(aType) { this._urlType = aType;},
|
||||
|
||||
}
|
||||
|
||||
var NSGetFactory = XPCOMUtils.generateNSGetFactory([FooUrlConstructor]);
|
|
@ -0,0 +1,6 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
load("../../../resources/mailShutdown.js");
|
|
@ -0,0 +1,43 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that the components made available by JaAccount can be created with
|
||||
// each supported interface.
|
||||
|
||||
let tests = [
|
||||
// JaUrl
|
||||
["@mozilla.org/jacppurldelegator;1", "nsISupports"],
|
||||
["@mozilla.org/jacppurldelegator;1", "nsIMsgMailNewsUrl"],
|
||||
["@mozilla.org/jacppurldelegator;1", "nsIMsgMessageUrl"],
|
||||
["@mozilla.org/jacppurldelegator;1", "nsIURL"],
|
||||
["@mozilla.org/jacppurldelegator;1", "nsIURI"],
|
||||
["@mozilla.org/jacppurldelegator;1", "msgIOverride"],
|
||||
["@mozilla.org/jacppurldelegator;1", "nsIInterfaceRequestor"],
|
||||
|
||||
// FooJaUrl
|
||||
["@mozilla.org/jsaccount/testjafoourl;1", "nsISupports"],
|
||||
["@mozilla.org/jsaccount/testjafoourl;1", "nsIMsgMailNewsUrl"],
|
||||
["@mozilla.org/jsaccount/testjafoourl;1", "nsIMsgMessageUrl"],
|
||||
["@mozilla.org/jsaccount/testjafoourl;1", "nsIURL"],
|
||||
["@mozilla.org/jsaccount/testjafoourl;1", "nsIURI"],
|
||||
["@mozilla.org/jsaccount/testjafoourl;1", "msgIOverride"],
|
||||
["@mozilla.org/jsaccount/testjafoourl;1", "nsIInterfaceRequestor"],
|
||||
];
|
||||
|
||||
function run_test()
|
||||
{
|
||||
for (let [contractID, iface] of tests) {
|
||||
dump('trying to create component ' + contractID +
|
||||
' with interface ' + iface + '\n');
|
||||
try {
|
||||
dump(Cc[contractID] + " " + Ci[iface] + '\n');
|
||||
}
|
||||
catch (e) {dump(e + '\n');}
|
||||
|
||||
let comp = Cc[contractID].createInstance(Ci[iface]);
|
||||
Assert.ok(comp instanceof Ci[iface]);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests of override functionality using a demo "foo" type url.
|
||||
|
||||
Cu.import("resource://gre/modules/jsaccount/JaBaseUrl.jsm");
|
||||
|
||||
var extraInterfaces = [
|
||||
Ci.msgIFooUrl,
|
||||
];
|
||||
|
||||
function newURL() {
|
||||
return Cc["@mozilla.org/jsaccount/testjafoourl;1"]
|
||||
.createInstance(Ci.nsISupports);
|
||||
}
|
||||
|
||||
var tests = [
|
||||
function testExists() {
|
||||
// test the existence of components and their interfaces.
|
||||
let url = newURL();
|
||||
for (let iface of JaBaseUrlProperties.baseInterfaces) {
|
||||
Assert.ok(url instanceof iface);
|
||||
let urlQI = url.QueryInterface(iface);
|
||||
Assert.ok(urlQI);
|
||||
}
|
||||
for (let iface of extraInterfaces) {
|
||||
let fooUrl = url.getInterface(iface);
|
||||
Assert.ok(fooUrl instanceof iface);
|
||||
Assert.ok(fooUrl.QueryInterface(iface));
|
||||
}
|
||||
},
|
||||
function test_msgIOverride() {
|
||||
let url = newURL().QueryInterface(Ci.msgIOverride);
|
||||
// test of access to wrapped JS object
|
||||
Assert.equal(typeof url.jsDelegate._hidden, "undefined");
|
||||
Assert.equal(url.jsDelegate.wrappedJSObject._hidden, "IAmHidden");
|
||||
},
|
||||
function test_nsIURI() {
|
||||
let url = newURL().QueryInterface(Ci.nsIURI);
|
||||
|
||||
// test attributes
|
||||
Assert.ok("spec" in url);
|
||||
url.spec = "https://test.invalid/folder?isFoo=true&someBar=theBar";
|
||||
Assert.equal(url.host, "test.invalid");
|
||||
|
||||
// test non-attributes
|
||||
// url.resolve is overridden in nsMsgMailNewsUrl to only work if starts with "#"
|
||||
Assert.equal("https://test.invalid/folder?isFoo=true&someBar=theBar#modules",
|
||||
url.resolve("#modules"));
|
||||
|
||||
// Test JS override of method called virtually in C++.
|
||||
// nsMsgMailNewsUrl::CloneIgnoringRef calls Clone(). We overrode the JS to
|
||||
// capitalize the path.
|
||||
url.spec = "https://test.invalid/folder#modules";
|
||||
Assert.equal(url.clone().spec, "https://test.invalid/FOLDER#MODULES");
|
||||
Assert.equal(url.cloneIgnoringRef().spec, "https://test.invalid/FOLDER");
|
||||
|
||||
// Demo of differences between the various versions of the object. The
|
||||
// standard XPCOM constructor returns the JS implementation, as was tested
|
||||
// above. Try the same tests using the JS delegate, which should give the
|
||||
// same (overridden) results (overridden for clone, C++ class
|
||||
// for cloneIgnoringRef.
|
||||
let jsDelegate = url.QueryInterface(Ci.msgIOverride).jsDelegate.wrappedJSObject;
|
||||
Assert.equal(jsDelegate.clone().spec, "https://test.invalid/FOLDER#MODULES");
|
||||
Assert.equal(jsDelegate.cloneIgnoringRef().spec, "https://test.invalid/FOLDER");
|
||||
|
||||
// Not sure why you would want to do this, but you call also call the C++
|
||||
// object that does delegation, and get the same result. This is actually
|
||||
// what we expect C++ callers to see.
|
||||
let cppDelegator = jsDelegate.delegator.QueryInterface(Ci.nsIURI);
|
||||
Assert.equal(cppDelegator.clone().spec, "https://test.invalid/FOLDER#MODULES");
|
||||
Assert.equal(cppDelegator.cloneIgnoringRef().spec, "https://test.invalid/FOLDER");
|
||||
|
||||
// The cppBase object will not have the overrides.
|
||||
let cppBase = url.QueryInterface(Ci.msgIOverride).cppBase.QueryInterface(Ci.nsIURI);
|
||||
Assert.equal(cppBase.clone().spec, "https://test.invalid/folder#modules");
|
||||
|
||||
// But then it gets tricky. We can call cloneIgnoringRef in the C++ base
|
||||
// but it will use the virtual clone which is overridden.
|
||||
Assert.equal(cppBase.cloneIgnoringRef().spec, "https://test.invalid/FOLDER");
|
||||
|
||||
},
|
||||
function test_nsIURL() {
|
||||
let url = newURL().QueryInterface(Ci.nsIURL);
|
||||
Assert.ok("filePath" in url);
|
||||
url.spec = "https://test.invalid/folder?isFoo=true&someBar=theBar";
|
||||
Assert.equal(url.query, "isFoo=true&someBar=theBar");
|
||||
// Note that I tried here to test getCommonSpec, but that does not work
|
||||
// because nsStandardURL.cpp makes an assumption that the URL is directly
|
||||
// an nsStandardURL.cpp.
|
||||
},
|
||||
function test_nsIMsgMailNewsUrl() {
|
||||
let url = newURL().QueryInterface(Ci.nsIMsgMailNewsUrl);
|
||||
Assert.ok("msgWindow" in url);
|
||||
url.maxProgress = 23;
|
||||
Assert.equal(url.maxProgress, 23);
|
||||
},
|
||||
function test_nsIMsgMessageUrl() {
|
||||
let url = newURL().QueryInterface(Ci.nsIMsgMessageUrl);
|
||||
Assert.ok("originalSpec" in url);
|
||||
let appDir = Services.dirsvc.get("GreD", Components.interfaces.nsIFile);
|
||||
Assert.ok(appDir.path);
|
||||
// test attributes
|
||||
url.messageFile = appDir;
|
||||
Assert.equal(url.messageFile.path, appDir.path);
|
||||
},
|
||||
function test_msgIFooUrl() {
|
||||
let url = newURL().QueryInterface(Ci.nsIInterfaceRequestor);
|
||||
let fooUrl = url.getInterface(Ci.msgIFooUrl);
|
||||
Assert.ok(fooUrl instanceof Ci.msgIFooUrl);
|
||||
|
||||
fooUrl.itemId = "theItemId";
|
||||
Assert.equal(fooUrl.itemId, "theItemId");
|
||||
|
||||
url.QueryInterface(Ci.nsIURI).spec = "https://foo.invalid/bar/";
|
||||
Assert.ok(!fooUrl.isAttachment);
|
||||
url.QueryInterface(Ci.nsIURI).spec = "https://foo.invalid/bar?part=1.4&dummy=stuff";
|
||||
Assert.ok(fooUrl.isAttachment);
|
||||
|
||||
let msgMailNewsUrl = url.QueryInterface(Ci.nsIMsgMailNewsUrl);
|
||||
Assert.ok(!msgMailNewsUrl.IsUrlType(Ci.nsIMsgMailNewsUrl.eMove));
|
||||
fooUrl.setUrlType(Ci.nsIMsgMailNewsUrl.eMove);
|
||||
Assert.ok(msgMailNewsUrl.IsUrlType(Ci.nsIMsgMailNewsUrl.eMove));
|
||||
|
||||
},
|
||||
];
|
||||
|
||||
function run_test()
|
||||
{
|
||||
for (var test of tests)
|
||||
test();
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
# Any copyright is dedicated to the Public Domain.
|
||||
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
[DEFAULT]
|
||||
head = head_jsaccount.js
|
||||
tail = tail_jsaccount.js
|
||||
support-files = resources/*
|
||||
[test_componentsExist.js]
|
||||
[test_fooUrl.js]
|
|
@ -17,6 +17,7 @@ DIRS += [
|
|||
'import/text/src',
|
||||
'import/vcard/src',
|
||||
'intl',
|
||||
'jsaccount',
|
||||
'local/public',
|
||||
'local/src',
|
||||
'mime',
|
||||
|
|
Загрузка…
Ссылка в новой задаче