From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/mailnews/jsaccount/modules/JSAccountUtils.jsm | 264 +++++++++++++++++++++ comm/mailnews/jsaccount/modules/JaBaseUrl.jsm | 83 +++++++ comm/mailnews/jsaccount/moz.build | 20 ++ comm/mailnews/jsaccount/public/moz.build | 15 ++ .../mailnews/jsaccount/public/msgIDelegateList.idl | 19 ++ comm/mailnews/jsaccount/public/msgIJaUrl.idl | 27 +++ comm/mailnews/jsaccount/public/msgIOverride.idl | 42 ++++ comm/mailnews/jsaccount/readme.html | 67 ++++++ comm/mailnews/jsaccount/src/DelegateList.cpp | 24 ++ comm/mailnews/jsaccount/src/DelegateList.h | 48 ++++ comm/mailnews/jsaccount/src/JaAbDirectory.cpp | 81 +++++++ comm/mailnews/jsaccount/src/JaAbDirectory.h | 84 +++++++ comm/mailnews/jsaccount/src/JaCompose.cpp | 89 +++++++ comm/mailnews/jsaccount/src/JaCompose.h | 89 +++++++ comm/mailnews/jsaccount/src/JaIncomingServer.cpp | 96 ++++++++ comm/mailnews/jsaccount/src/JaIncomingServer.h | 95 ++++++++ comm/mailnews/jsaccount/src/JaMsgFolder.cpp | 176 ++++++++++++++ comm/mailnews/jsaccount/src/JaMsgFolder.h | 137 +++++++++++ comm/mailnews/jsaccount/src/JaUrl.cpp | 205 ++++++++++++++++ comm/mailnews/jsaccount/src/JaUrl.h | 138 +++++++++++ comm/mailnews/jsaccount/src/components.conf | 37 +++ comm/mailnews/jsaccount/src/moz.build | 30 +++ comm/mailnews/jsaccount/test/components.conf | 14 ++ comm/mailnews/jsaccount/test/idl/moz.build | 15 ++ comm/mailnews/jsaccount/test/idl/msgIFooUrl.idl | 17 ++ comm/mailnews/jsaccount/test/moz.build | 15 ++ .../mailnews/jsaccount/test/unit/head_jsaccount.js | 60 +++++ .../resources/TestJaMsgProtocolInfoComponent.jsm | 75 ++++++ .../jsaccount/test/unit/resources/readme.html | 62 +++++ .../test/unit/resources/testComponents.manifest | 16 ++ .../unit/resources/testJaBaseIncomingServer.jsm | 74 ++++++ .../resources/testJaBaseIncomingServerComponent.js | 20 ++ .../test/unit/resources/testJaBaseMsgFolder.jsm | 70 ++++++ .../unit/resources/testJaBaseMsgFolderComponent.js | 19 ++ .../test/unit/resources/testJaFooUrlComponent.js | 88 +++++++ .../jsaccount/test/unit/test_componentsExist.js | 90 +++++++ comm/mailnews/jsaccount/test/unit/test_fooUrl.js | 93 ++++++++ .../jsaccount/test/unit/test_jaMsgFolder.js | 56 +++++ comm/mailnews/jsaccount/test/unit/xpcshell.ini | 10 + 39 files changed, 2660 insertions(+) create mode 100644 comm/mailnews/jsaccount/modules/JSAccountUtils.jsm create mode 100644 comm/mailnews/jsaccount/modules/JaBaseUrl.jsm create mode 100644 comm/mailnews/jsaccount/moz.build create mode 100644 comm/mailnews/jsaccount/public/moz.build create mode 100644 comm/mailnews/jsaccount/public/msgIDelegateList.idl create mode 100644 comm/mailnews/jsaccount/public/msgIJaUrl.idl create mode 100644 comm/mailnews/jsaccount/public/msgIOverride.idl create mode 100644 comm/mailnews/jsaccount/readme.html create mode 100644 comm/mailnews/jsaccount/src/DelegateList.cpp create mode 100644 comm/mailnews/jsaccount/src/DelegateList.h create mode 100644 comm/mailnews/jsaccount/src/JaAbDirectory.cpp create mode 100644 comm/mailnews/jsaccount/src/JaAbDirectory.h create mode 100644 comm/mailnews/jsaccount/src/JaCompose.cpp create mode 100644 comm/mailnews/jsaccount/src/JaCompose.h create mode 100644 comm/mailnews/jsaccount/src/JaIncomingServer.cpp create mode 100644 comm/mailnews/jsaccount/src/JaIncomingServer.h create mode 100644 comm/mailnews/jsaccount/src/JaMsgFolder.cpp create mode 100644 comm/mailnews/jsaccount/src/JaMsgFolder.h create mode 100644 comm/mailnews/jsaccount/src/JaUrl.cpp create mode 100644 comm/mailnews/jsaccount/src/JaUrl.h create mode 100644 comm/mailnews/jsaccount/src/components.conf create mode 100644 comm/mailnews/jsaccount/src/moz.build create mode 100644 comm/mailnews/jsaccount/test/components.conf create mode 100644 comm/mailnews/jsaccount/test/idl/moz.build create mode 100644 comm/mailnews/jsaccount/test/idl/msgIFooUrl.idl create mode 100644 comm/mailnews/jsaccount/test/moz.build create mode 100644 comm/mailnews/jsaccount/test/unit/head_jsaccount.js create mode 100644 comm/mailnews/jsaccount/test/unit/resources/TestJaMsgProtocolInfoComponent.jsm create mode 100644 comm/mailnews/jsaccount/test/unit/resources/readme.html create mode 100644 comm/mailnews/jsaccount/test/unit/resources/testComponents.manifest create mode 100644 comm/mailnews/jsaccount/test/unit/resources/testJaBaseIncomingServer.jsm create mode 100644 comm/mailnews/jsaccount/test/unit/resources/testJaBaseIncomingServerComponent.js create mode 100644 comm/mailnews/jsaccount/test/unit/resources/testJaBaseMsgFolder.jsm create mode 100644 comm/mailnews/jsaccount/test/unit/resources/testJaBaseMsgFolderComponent.js create mode 100644 comm/mailnews/jsaccount/test/unit/resources/testJaFooUrlComponent.js create mode 100644 comm/mailnews/jsaccount/test/unit/test_componentsExist.js create mode 100644 comm/mailnews/jsaccount/test/unit/test_fooUrl.js create mode 100644 comm/mailnews/jsaccount/test/unit/test_jaMsgFolder.js create mode 100644 comm/mailnews/jsaccount/test/unit/xpcshell.ini (limited to 'comm/mailnews/jsaccount') diff --git a/comm/mailnews/jsaccount/modules/JSAccountUtils.jsm b/comm/mailnews/jsaccount/modules/JSAccountUtils.jsm new file mode 100644 index 0000000000..adef4710e0 --- /dev/null +++ b/comm/mailnews/jsaccount/modules/JSAccountUtils.jsm @@ -0,0 +1,264 @@ +/* -*- 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 = {}; + +// Logging usage: set mailnews.jsaccount.loglevel to the word "Debug" to +// increase logging level. +var log = console.createInstance({ + prefix: "mail.jsaccount", + maxLogLevel: "Warn", + maxLogLevelPref: "mail.jsaccount.loglevel", +}); + +/** + * + * 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 constructor 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 = ChromeUtils.generateQI([Ci.nsIFactory]); + + factory.createInstance = function (iid) { + // 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(jsDelegate); + 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 Components.Exception("", 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.debug("Skipping " + method); + continue; + } + log.debug("Processing " + method); + 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; +} diff --git a/comm/mailnews/jsaccount/modules/JaBaseUrl.jsm b/comm/mailnews/jsaccount/modules/JaBaseUrl.jsm new file mode 100644 index 0000000000..03f2a851d3 --- /dev/null +++ b/comm/mailnews/jsaccount/modules/JaBaseUrl.jsm @@ -0,0 +1,83 @@ +/* -*- 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"]; + +const { JSAccountUtils } = ChromeUtils.import( + "resource:///modules/jsaccount/JSAccountUtils.jsm" +); + +// A partial JavaScript implementation of the base server methods. + +const JaBaseUrlProperties = { + // The CPP object that delegates 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, + ], + // Don't pass Ci.nsISupports to generateQI(). + baseInterfacesQI: [ + Ci.nsIURI, + Ci.nsIURL, + Ci.nsIMsgMailNewsUrl, + Ci.nsIMsgMessageUrl, + Ci.msgIOverride, + 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}"), +}; + +// Typical boilerplate to include in all implementations. +function JaBaseUrl(aDelegator, aBaseInterfaces) { + // 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); +} + +// Typical boilerplate to include in all implementations. +JaBaseUrl.prototype = { + __proto__: JSAccountUtils.makeCppDelegator(JaBaseUrlProperties), + + // Flag this item as CPP needs to delegate to JS. + _JsPrototypeToDelegate: true, + + // QI to the interfaces. + QueryInterface: ChromeUtils.generateQI(JaBaseUrlProperties.baseInterfacesQI), + + // 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. +}; diff --git a/comm/mailnews/jsaccount/moz.build b/comm/mailnews/jsaccount/moz.build new file mode 100644 index 0000000000..57cdd9d119 --- /dev/null +++ b/comm/mailnews/jsaccount/moz.build @@ -0,0 +1,20 @@ +# -*- 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/unit/resources/TestJaMsgProtocolInfoComponent.jsm", +] + +XPCOM_MANIFESTS += [ + "test/components.conf", +] + + +TEST_DIRS += ["test"] diff --git a/comm/mailnews/jsaccount/public/moz.build b/comm/mailnews/jsaccount/public/moz.build new file mode 100644 index 0000000000..41715534ff --- /dev/null +++ b/comm/mailnews/jsaccount/public/moz.build @@ -0,0 +1,15 @@ +# -*- 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", + "msgIJaUrl.idl", + "msgIOverride.idl", +] + +EXPORTS += [] + +XPIDL_MODULE = "msgjsaccount" diff --git a/comm/mailnews/jsaccount/public/msgIDelegateList.idl b/comm/mailnews/jsaccount/public/msgIDelegateList.idl new file mode 100644 index 0000000000..0a65596206 --- /dev/null +++ b/comm/mailnews/jsaccount/public/msgIDelegateList.idl @@ -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 ACString aMethod); +}; diff --git a/comm/mailnews/jsaccount/public/msgIJaUrl.idl b/comm/mailnews/jsaccount/public/msgIJaUrl.idl new file mode 100644 index 0000000000..41379ab239 --- /dev/null +++ b/comm/mailnews/jsaccount/public/msgIJaUrl.idl @@ -0,0 +1,27 @@ +/* -*- 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" + +/** + * Additional interface supported by JsBaseUrl to allow setting variables that + * are typically done in C++ by overriding classes, which is not allowed in JS. + */ + +[scriptable, uuid(f9148c17-9b67-43bb-b741-4fdfe1aef935)] +interface msgIJaUrl : nsISupports +{ + /** + * Set the URL type, which will be checked by nsIMsgMailNewsUrl::IsUrlType. See + * IsUrlType for possible values. + */ + void setUrlType(in unsigned long type); + + /** + * Set the spec on a URL. + */ + void setSpec(in AUTF8String spec); +}; diff --git a/comm/mailnews/jsaccount/public/msgIOverride.idl b/comm/mailnews/jsaccount/public/msgIOverride.idl new file mode 100644 index 0000000000..cad1bf4157 --- /dev/null +++ b/comm/mailnews/jsaccount/public/msgIOverride.idl @@ -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; +}; diff --git a/comm/mailnews/jsaccount/readme.html b/comm/mailnews/jsaccount/readme.html new file mode 100644 index 0000000000..cb1b875dbe --- /dev/null +++ b/comm/mailnews/jsaccount/readme.html @@ -0,0 +1,67 @@ + + + + + JsAccount Usage and Architecture + + +

Overview

+

+ 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. +

+

Existing C++-based architecture of mailnews accounts

+

+ 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, + "@mozilla.org/messenger/server;1?type=", then append "imap" to get: +

+

@mozilla.org/messenger/server;1?type=imap

+

+ 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. +

+

+ 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. +

+

+ 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. +

+

Role of JsAccount

+

+ 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. +

+


+ + diff --git a/comm/mailnews/jsaccount/src/DelegateList.cpp b/comm/mailnews/jsaccount/src/DelegateList.cpp new file mode 100644 index 0000000000..cd146fe3dc --- /dev/null +++ b/comm/mailnews/jsaccount/src/DelegateList.cpp @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; 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 nsACString& aMethodName) { + mMethods.InsertOrUpdate(aMethodName, true); + return NS_OK; +} + +} // namespace mailnews +} // namespace mozilla diff --git a/comm/mailnews/jsaccount/src/DelegateList.h b/comm/mailnews/jsaccount/src/DelegateList.h new file mode 100644 index 0000000000..f59e178f0c --- /dev/null +++ b/comm/mailnews/jsaccount/src/DelegateList.h @@ -0,0 +1,48 @@ +/* -*- 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 "nsTHashMap.h" +#include "nsString.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() {} + nsTHashMap mMethods; + + protected: + virtual ~DelegateList() {} +}; + +} // namespace mailnews +} // namespace mozilla + +/* + * This macro is used in forwarding functions. + * _jsdelegate: the name of the JS pointer that implements a particular + * interface. + * _jsmethods: the DelegateList object + * _cppbase: the C++ base instance (used when call not delegated to js) + * + **/ + +#define DELEGATE_JS(_jsdelegate, _jsmethods, _cppbase) \ + (_jsdelegate && _jsmethods && \ + _jsmethods->Contains(nsLiteralCString(__func__)) \ + ? _jsdelegate \ + : (_cppbase)) + +#endif diff --git a/comm/mailnews/jsaccount/src/JaAbDirectory.cpp b/comm/mailnews/jsaccount/src/JaAbDirectory.cpp new file mode 100644 index 0000000000..11b29e99ab --- /dev/null +++ b/comm/mailnews/jsaccount/src/JaAbDirectory.cpp @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 2; 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 "JaAbDirectory.h" +#include "nsComponentManagerUtils.h" + +namespace mozilla { +namespace mailnews { + +NS_IMPL_ISUPPORTS_INHERITED(JaBaseCppAbDirectory, nsAbDirProperty, + nsIInterfaceRequestor) + +// nsIInterfaceRequestor implementation +NS_IMETHODIMP JaBaseCppAbDirectory::GetInterface(const nsIID& aIID, + void** aSink) { + return QueryInterface(aIID, aSink); +} + +// Delegator +NS_IMPL_ISUPPORTS_INHERITED(JaCppAbDirectoryDelegator, JaBaseCppAbDirectory, + msgIOverride) + +// Delegator object to bypass JS method override. +NS_IMPL_ISUPPORTS(JaCppAbDirectoryDelegator::Super, nsIAbDirectory, + nsIInterfaceRequestor) + +JaCppAbDirectoryDelegator::JaCppAbDirectoryDelegator() + : mCppBase(new Super(this)), mMethods(nullptr) {} + +NS_IMETHODIMP JaCppAbDirectoryDelegator::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(aDelegateList); + mMethods = &(mDelegateList->mMethods); + return NS_OK; +} +NS_IMETHODIMP JaCppAbDirectoryDelegator::GetMethodsToDelegate( + msgIDelegateList** aDelegateList) { + if (!mDelegateList) mDelegateList = new DelegateList(); + mMethods = &(mDelegateList->mMethods); + NS_ADDREF(*aDelegateList = mDelegateList); + return NS_OK; +} + +NS_IMETHODIMP JaCppAbDirectoryDelegator::SetJsDelegate( + nsISupports* aJsDelegate) { + // If these QIs fail, then overrides are not provided for methods in that + // interface, which is OK. + mJsISupports = aJsDelegate; + mJsIAbDirectory = do_QueryInterface(aJsDelegate); + mJsIInterfaceRequestor = do_QueryInterface(aJsDelegate); + return NS_OK; +} +NS_IMETHODIMP JaCppAbDirectoryDelegator::GetJsDelegate( + nsISupports** aJsDelegate) { + NS_ENSURE_ARG_POINTER(aJsDelegate); + if (mJsISupports) { + NS_ADDREF(*aJsDelegate = mJsISupports); + return NS_OK; + } + return NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP JaCppAbDirectoryDelegator::GetCppBase(nsISupports** aCppBase) { + nsCOMPtr cppBaseSupports; + cppBaseSupports = NS_ISUPPORTS_CAST(nsIAbDirectory*, mCppBase); + NS_ENSURE_STATE(cppBaseSupports); + cppBaseSupports.forget(aCppBase); + + return NS_OK; +} + +} // namespace mailnews +} // namespace mozilla diff --git a/comm/mailnews/jsaccount/src/JaAbDirectory.h b/comm/mailnews/jsaccount/src/JaAbDirectory.h new file mode 100644 index 0000000000..7a2903d3d4 --- /dev/null +++ b/comm/mailnews/jsaccount/src/JaAbDirectory.h @@ -0,0 +1,84 @@ +/* -*- 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 _JaAbDirectory_H_ +#define _JaAbDirectory_H_ + +#include "nsISupports.h" +#include "DelegateList.h" +#include "msgIOverride.h" +#include "nsIAbDirectory.h" +#include "nsAbDirProperty.h" +#include "nsTHashMap.h" +#include "nsIInterfaceRequestor.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 JaBaseCppAbDirectory : public nsAbDirProperty, + public nsIInterfaceRequestor { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIINTERFACEREQUESTOR + JaBaseCppAbDirectory() {} + + protected: + virtual ~JaBaseCppAbDirectory() {} +}; + +class JaCppAbDirectoryDelegator : public JaBaseCppAbDirectory, + public msgIOverride { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_MSGIOVERRIDE + + // use mCppBase as a raw pointer where possible + NS_FORWARD_NSIABDIRECTORY(DELEGATE_JS(mJsIAbDirectory, mMethods, mCppBase)->) + NS_FORWARD_NSIINTERFACEREQUESTOR( + DELEGATE_JS( + mJsIInterfaceRequestor, mMethods, + (nsCOMPtr(do_QueryInterface(mCppBase)))) + ->) + + JaCppAbDirectoryDelegator(); + + private: + virtual ~JaCppAbDirectoryDelegator() {} + + class Super : public nsIAbDirectory, public nsIInterfaceRequestor { + public: + explicit Super(JaCppAbDirectoryDelegator* aFakeThis) { + mFakeThis = aFakeThis; + } + NS_DECL_ISUPPORTS + NS_FORWARD_NSIABDIRECTORY(mFakeThis->JaBaseCppAbDirectory::) + NS_FORWARD_NSIINTERFACEREQUESTOR(mFakeThis->JaBaseCppAbDirectory::) + private: + virtual ~Super() {} + JaCppAbDirectoryDelegator* mFakeThis; + }; + + // Interfaces that may be overridden by JS. + nsCOMPtr mJsIAbDirectory; + nsCOMPtr mJsIInterfaceRequestor; + + nsCOMPtr mJsISupports; + + // Class to bypass JS delegates. nsCOMPtr for when we do cycle collection. + nsCOMPtr mCppBase; + + RefPtr mDelegateList; + nsTHashMap* mMethods; +}; + +} // namespace mailnews +} // namespace mozilla + +#endif diff --git a/comm/mailnews/jsaccount/src/JaCompose.cpp b/comm/mailnews/jsaccount/src/JaCompose.cpp new file mode 100644 index 0000000000..a44dac2c01 --- /dev/null +++ b/comm/mailnews/jsaccount/src/JaCompose.cpp @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 2; 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 "JaCompose.h" +#include "nsComponentManagerUtils.h" + +// This file specifies the implementation of nsIMsgCompose.idl objects +// in the JsAccount system. + +namespace mozilla { +namespace mailnews { + +NS_IMPL_ISUPPORTS_INHERITED(JaBaseCppCompose, nsMsgCompose, + nsIInterfaceRequestor) + +// nsIInterfaceRequestor implementation +NS_IMETHODIMP +JaBaseCppCompose::GetInterface(const nsIID& aIID, void** aSink) { + return QueryInterface(aIID, aSink); +} + +// Delegator object to bypass JS method override. + +JaCppComposeDelegator::JaCppComposeDelegator() { + mCppBase = + do_QueryInterface(NS_ISUPPORTS_CAST(nsIMsgCompose*, new Super(this))); + mMethods = nullptr; +} + +NS_IMPL_ISUPPORTS_INHERITED(JaCppComposeDelegator, JaBaseCppCompose, + msgIOverride) + +NS_IMPL_ISUPPORTS(JaCppComposeDelegator::Super, nsIMsgCompose, + nsIMsgSendListener, nsIInterfaceRequestor) + +NS_IMETHODIMP +JaCppComposeDelegator::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(aDelegateList); + mMethods = &(mDelegateList->mMethods); + return NS_OK; +} +NS_IMETHODIMP +JaCppComposeDelegator::GetMethodsToDelegate(msgIDelegateList** aDelegateList) { + if (!mDelegateList) mDelegateList = new DelegateList(); + mMethods = &(mDelegateList->mMethods); + NS_ADDREF(*aDelegateList = mDelegateList); + return NS_OK; +} + +NS_IMETHODIMP +JaCppComposeDelegator::SetJsDelegate(nsISupports* aJsDelegate) { + // If these QIs fail, then overrides are not provided for methods in that + // interface, which is OK. + mJsISupports = aJsDelegate; + mJsIMsgCompose = do_QueryInterface(aJsDelegate); + mJsIMsgSendListener = do_QueryInterface(aJsDelegate); + mJsIInterfaceRequestor = do_QueryInterface(aJsDelegate); + return NS_OK; +} +NS_IMETHODIMP +JaCppComposeDelegator::GetJsDelegate(nsISupports** aJsDelegate) { + NS_ENSURE_ARG_POINTER(aJsDelegate); + if (mJsISupports) { + NS_ADDREF(*aJsDelegate = mJsISupports); + return NS_OK; + } + return NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP +JaCppComposeDelegator::GetCppBase(nsISupports** aCppBase) { + nsCOMPtr cppBaseSupports; + cppBaseSupports = NS_ISUPPORTS_CAST(nsIMsgCompose*, mCppBase); + NS_ENSURE_STATE(cppBaseSupports); + cppBaseSupports.forget(aCppBase); + + return NS_OK; +} + +} // namespace mailnews +} // namespace mozilla diff --git a/comm/mailnews/jsaccount/src/JaCompose.h b/comm/mailnews/jsaccount/src/JaCompose.h new file mode 100644 index 0000000000..587e91738a --- /dev/null +++ b/comm/mailnews/jsaccount/src/JaCompose.h @@ -0,0 +1,89 @@ +/* -*- 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 _JaCompose_H_ +#define _JaCompose_H_ + +#include "nsISupports.h" +#include "DelegateList.h" +#include "msgIOverride.h" +#include "nsMsgCompose.h" +#include "nsIMsgCompose.h" +#include "nsTHashMap.h" +#include "nsIInterfaceRequestor.h" + +// This file specifies the definition of nsIMsgCompose.idl objects +// in the JsAccount system. + +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 JaBaseCppCompose : public nsMsgCompose, public nsIInterfaceRequestor { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIINTERFACEREQUESTOR + JaBaseCppCompose() {} + + protected: + virtual ~JaBaseCppCompose() {} +}; + +class JaCppComposeDelegator : public JaBaseCppCompose, public msgIOverride { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_MSGIOVERRIDE + + NS_FORWARD_NSIMSGCOMPOSE(DELEGATE_JS(mJsIMsgCompose, mMethods, mCppBase)->) + NS_FORWARD_NSIMSGSENDLISTENER( + DELEGATE_JS(mJsIMsgSendListener, mMethods, mCppBase.get())->) + NS_FORWARD_NSIINTERFACEREQUESTOR( + DELEGATE_JS( + mJsIInterfaceRequestor, mMethods, + (nsCOMPtr(do_QueryInterface(mCppBase)))) + ->) + + JaCppComposeDelegator(); + + private: + virtual ~JaCppComposeDelegator() {} + + // This class will call a method on the delegator, but force the use of the + // C++ cppBase class, bypassing any JS Delegate. + class Super : public nsIMsgCompose, public nsIInterfaceRequestor { + public: + explicit Super(JaCppComposeDelegator* aFakeThis) { mFakeThis = aFakeThis; } + NS_DECL_ISUPPORTS + // Forward all overridable methods, bypassing JS override. + NS_FORWARD_NSIMSGCOMPOSE(mFakeThis->JaBaseCppCompose::) + NS_FORWARD_NSIMSGSENDLISTENER(mFakeThis->JaBaseCppCompose::) + NS_FORWARD_NSIINTERFACEREQUESTOR(mFakeThis->JaBaseCppCompose::) + private: + virtual ~Super(){}; + JaCppComposeDelegator* mFakeThis; + }; + + // Interfaces that may be overridden by JS. + nsCOMPtr mJsIMsgCompose; + nsCOMPtr mJsIMsgSendListener; + nsCOMPtr mJsIInterfaceRequestor; + + nsCOMPtr mJsISupports; + + // Class to bypass JS delegates. + nsCOMPtr mCppBase; + + RefPtr mDelegateList; + nsTHashMap* mMethods; +}; + +} // namespace mailnews +} // namespace mozilla + +#endif diff --git a/comm/mailnews/jsaccount/src/JaIncomingServer.cpp b/comm/mailnews/jsaccount/src/JaIncomingServer.cpp new file mode 100644 index 0000000000..8acdf9c655 --- /dev/null +++ b/comm/mailnews/jsaccount/src/JaIncomingServer.cpp @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 2; 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 "JaIncomingServer.h" +#include "nsComponentManagerUtils.h" + +// This file specifies the implementation of nsIMsgIncomingServer.idl objects +// in the JsAccount system. + +namespace mozilla { +namespace mailnews { + +NS_IMPL_ISUPPORTS_INHERITED(JaBaseCppIncomingServer, nsMsgIncomingServer, + nsIInterfaceRequestor) + +// nsMsgIncomingServer overrides +nsresult JaBaseCppIncomingServer::CreateRootFolderFromUri( + const nsACString& serverUri, nsIMsgFolder** rootFolder) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +// nsIInterfaceRequestor implementation +NS_IMETHODIMP +JaBaseCppIncomingServer::GetInterface(const nsIID& aIID, void** aSink) { + return QueryInterface(aIID, aSink); +} + +// Delegator object to bypass JS method override. + +JaCppIncomingServerDelegator::JaCppIncomingServerDelegator() { + mCppBase = do_QueryInterface( + NS_ISUPPORTS_CAST(nsIMsgIncomingServer*, new Super(this))); + mMethods = nullptr; +} + +NS_IMPL_ISUPPORTS_INHERITED(JaCppIncomingServerDelegator, + JaBaseCppIncomingServer, msgIOverride) + +NS_IMPL_ISUPPORTS(JaCppIncomingServerDelegator::Super, nsIMsgIncomingServer, + nsIInterfaceRequestor) + +NS_IMETHODIMP +JaCppIncomingServerDelegator::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(aDelegateList); + mMethods = &(mDelegateList->mMethods); + return NS_OK; +} +NS_IMETHODIMP +JaCppIncomingServerDelegator::GetMethodsToDelegate( + msgIDelegateList** aDelegateList) { + if (!mDelegateList) mDelegateList = new DelegateList(); + mMethods = &(mDelegateList->mMethods); + NS_ADDREF(*aDelegateList = mDelegateList); + return NS_OK; +} + +NS_IMETHODIMP +JaCppIncomingServerDelegator::SetJsDelegate(nsISupports* aJsDelegate) { + // If these QIs fail, then overrides are not provided for methods in that + // interface, which is OK. + mJsISupports = aJsDelegate; + mJsIMsgIncomingServer = do_QueryInterface(aJsDelegate); + mJsIInterfaceRequestor = do_QueryInterface(aJsDelegate); + return NS_OK; +} +NS_IMETHODIMP +JaCppIncomingServerDelegator::GetJsDelegate(nsISupports** aJsDelegate) { + NS_ENSURE_ARG_POINTER(aJsDelegate); + if (mJsISupports) { + NS_ADDREF(*aJsDelegate = mJsISupports); + return NS_OK; + } + return NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP +JaCppIncomingServerDelegator::GetCppBase(nsISupports** aCppBase) { + nsCOMPtr cppBaseSupports; + cppBaseSupports = NS_ISUPPORTS_CAST(nsIMsgIncomingServer*, mCppBase); + NS_ENSURE_STATE(cppBaseSupports); + cppBaseSupports.forget(aCppBase); + + return NS_OK; +} + +} // namespace mailnews +} // namespace mozilla diff --git a/comm/mailnews/jsaccount/src/JaIncomingServer.h b/comm/mailnews/jsaccount/src/JaIncomingServer.h new file mode 100644 index 0000000000..67c7fd48f6 --- /dev/null +++ b/comm/mailnews/jsaccount/src/JaIncomingServer.h @@ -0,0 +1,95 @@ +/* -*- 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 _JaIncomingServer_H_ +#define _JaIncomingServer_H_ + +#include "nsISupports.h" +#include "DelegateList.h" +#include "msgIOverride.h" +#include "nsIMsgIncomingServer.h" +#include "nsMsgIncomingServer.h" +#include "nsTHashMap.h" +#include "nsIInterfaceRequestor.h" + +// This file specifies the definition of nsIMsgIncomingServer.idl objects +// in the JsAccount system. + +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 JaBaseCppIncomingServer : public nsMsgIncomingServer, + public nsIInterfaceRequestor { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIINTERFACEREQUESTOR + JaBaseCppIncomingServer() {} + + // nsMsgIncomingServer overrides + nsresult CreateRootFolderFromUri(const nsACString& serverUri, + nsIMsgFolder** rootFolder) override; + + protected: + virtual ~JaBaseCppIncomingServer() {} +}; + +class JaCppIncomingServerDelegator : public JaBaseCppIncomingServer, + public msgIOverride { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_MSGIOVERRIDE + + // use mCppBase as a raw pointer where possible + NS_FORWARD_NSIMSGINCOMINGSERVER( + DELEGATE_JS(mJsIMsgIncomingServer, mMethods, mCppBase)->) + NS_FORWARD_NSIINTERFACEREQUESTOR( + DELEGATE_JS( + mJsIInterfaceRequestor, mMethods, + (nsCOMPtr(do_QueryInterface(mCppBase)))) + ->) + + JaCppIncomingServerDelegator(); + + private: + virtual ~JaCppIncomingServerDelegator() {} + + // This class will call a method on the delegator, but force the use of the + // C++ parent class, bypassing any JS Delegate. + class Super : public nsIMsgIncomingServer, public nsIInterfaceRequestor { + public: + explicit Super(JaCppIncomingServerDelegator* aFakeThis) { + mFakeThis = aFakeThis; + } + NS_DECL_ISUPPORTS + // Forward all overridable methods, bypassing JS override. + NS_FORWARD_NSIMSGINCOMINGSERVER(mFakeThis->JaBaseCppIncomingServer::) + NS_FORWARD_NSIINTERFACEREQUESTOR(mFakeThis->JaBaseCppIncomingServer::) + private: + virtual ~Super(){}; + JaCppIncomingServerDelegator* mFakeThis; + }; + + // Interfaces that may be overridden by JS. + nsCOMPtr mJsIMsgIncomingServer; + nsCOMPtr mJsIInterfaceRequestor; + + nsCOMPtr mJsISupports; + + // Class to bypass JS delegates. + nsCOMPtr mCppBase; + + RefPtr mDelegateList; + nsTHashMap* mMethods; +}; + +} // namespace mailnews +} // namespace mozilla + +#endif diff --git a/comm/mailnews/jsaccount/src/JaMsgFolder.cpp b/comm/mailnews/jsaccount/src/JaMsgFolder.cpp new file mode 100644 index 0000000000..e10f9f1704 --- /dev/null +++ b/comm/mailnews/jsaccount/src/JaMsgFolder.cpp @@ -0,0 +1,176 @@ +/* -*- Mode: C++; tab-width: 2; 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 "JaMsgFolder.h" +#include "nsComponentManagerUtils.h" + +#define MAILDATABASE_CONTRACTID_BASE "@mozilla.org/nsMsgDatabase/msgDB-" + +namespace mozilla { +namespace mailnews { + +NS_IMPL_ISUPPORTS_INHERITED(JaBaseCppMsgFolder, nsMsgDBFolder, + nsIInterfaceRequestor) + +// nsIInterfaceRequestor implementation +NS_IMETHODIMP +JaBaseCppMsgFolder::GetInterface(const nsIID& aIID, void** aSink) { + return QueryInterface(aIID, aSink); +} + +// Definition of abstract nsMsgDBFolder methods. +nsresult JaBaseCppMsgFolder::GetDatabase() { + nsresult rv = NS_OK; + if (!mDatabase) { + nsCOMPtr msgDBService = + do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Create the database, keeping it if it is "out of date" + rv = msgDBService->OpenFolderDB(this, true, getter_AddRefs(mDatabase)); + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) { + rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase)); + NS_ENSURE_STATE(mDatabase); + // not sure about this ... the issue is that if the summary is not valid, + // then the db does not get added to the cache in the future, and + // reindexes do not show all of the messages. + // mDatabase->SetSummaryValid(true); + mDatabase->SetSummaryValid(false); + CreateDummyFile(this); + } + + if (rv != NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) + NS_ENSURE_SUCCESS(rv, rv); + else if (mDatabase) { + // Not going to warn here, because on initialization we set all + // databases as invalid. + // NS_WARNING("Mail Summary database is out of date"); + // Grrr, the only way to get this into the cache is to set the db as + // valid, + // close, reopen, then set as invalid. + mDatabase->SetSummaryValid(true); + msgDBService->ForceFolderDBClosed(this); + rv = msgDBService->OpenFolderDB(this, true, getter_AddRefs(mDatabase)); + if (mDatabase) mDatabase->SetSummaryValid(false); + } + + if (mDatabase) { + // + // When I inadvertently deleted the out-of-date database, I hit this code + // with the db's m_dbFolderInfo as null from the delete, yet the local + // mDatabase reference kept the database alive. So I hit an assert when I + // tried to open the database. Be careful if you try to fix the + // out-of-date issues! + // + // UpdateNewMessages(); + if (mAddListener) mDatabase->AddListener(this); + // UpdateSummaryTotals can null mDatabase during initialization, so we + // save a local copy + nsCOMPtr database(mDatabase); + UpdateSummaryTotals(true); + mDatabase = database; + } + } + + return rv; +} + +/* + * The utility function GetSummaryFileLocation takes a folder file, + * then appends .msf to come up with the name of the database file. So + * we need a placeholder file with simply the folder name. This method + * creates an appropriate file as a placeholder, or you may use the file if + * appropriate. + */ +nsresult JaBaseCppMsgFolder::CreateDummyFile(nsIMsgFolder* aMailFolder) { + nsresult rv; + if (!aMailFolder) return NS_OK; + nsCOMPtr path; + // need to make sure folder exists... + aMailFolder->GetFilePath(getter_AddRefs(path)); + if (path) { + bool exists; + rv = path->Exists(&exists); + if (!exists) { + rv = path->Create(nsIFile::NORMAL_FILE_TYPE, 0644); + NS_ENSURE_SUCCESS(rv, rv); + } + } + return NS_OK; +} + +// AFAICT this is unused in mailnews code. +nsresult JaBaseCppMsgFolder::CreateChildFromURI(const nsACString& uri, + nsIMsgFolder** folder) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +// Delegator object to bypass JS method override. + +JaCppMsgFolderDelegator::JaCppMsgFolderDelegator() + : mCppBase(new Super(this)), mMethods(nullptr) {} + +NS_IMPL_ISUPPORTS_INHERITED(JaCppMsgFolderDelegator, JaBaseCppMsgFolder, + msgIOverride) + +NS_IMPL_ISUPPORTS(JaCppMsgFolderDelegator::Super, nsIMsgFolder, + nsIDBChangeListener, nsIUrlListener, + nsIJunkMailClassificationListener, + nsIMsgTraitClassificationListener, nsIInterfaceRequestor) + +NS_IMETHODIMP +JaCppMsgFolderDelegator::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(aDelegateList); + mMethods = &(mDelegateList->mMethods); + return NS_OK; +} +NS_IMETHODIMP +JaCppMsgFolderDelegator::GetMethodsToDelegate( + msgIDelegateList** aDelegateList) { + if (!mDelegateList) mDelegateList = new DelegateList(); + mMethods = &(mDelegateList->mMethods); + NS_ADDREF(*aDelegateList = mDelegateList); + return NS_OK; +} + +NS_IMETHODIMP JaCppMsgFolderDelegator::SetJsDelegate(nsISupports* aJsDelegate) { + // If these QIs fail, then overrides are not provided for methods in that + // interface, which is OK. + mJsISupports = aJsDelegate; + mJsIMsgFolder = do_QueryInterface(aJsDelegate); + mJsIDBChangeListener = do_QueryInterface(aJsDelegate); + mJsIUrlListener = do_QueryInterface(aJsDelegate); + mJsIJunkMailClassificationListener = do_QueryInterface(aJsDelegate); + mJsIMsgTraitClassificationListener = do_QueryInterface(aJsDelegate); + mJsIInterfaceRequestor = do_QueryInterface(aJsDelegate); + return NS_OK; +} +NS_IMETHODIMP JaCppMsgFolderDelegator::GetJsDelegate( + nsISupports** aJsDelegate) { + NS_ENSURE_ARG_POINTER(aJsDelegate); + if (mJsISupports) { + NS_ADDREF(*aJsDelegate = mJsISupports); + return NS_OK; + } + return NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP JaCppMsgFolderDelegator::GetCppBase(nsISupports** aCppBase) { + nsCOMPtr cppBaseSupports; + cppBaseSupports = NS_ISUPPORTS_CAST(nsIMsgFolder*, mCppBase); + NS_ENSURE_STATE(cppBaseSupports); + cppBaseSupports.forget(aCppBase); + + return NS_OK; +} + +} // namespace mailnews +} // namespace mozilla diff --git a/comm/mailnews/jsaccount/src/JaMsgFolder.h b/comm/mailnews/jsaccount/src/JaMsgFolder.h new file mode 100644 index 0000000000..97cdfce5e6 --- /dev/null +++ b/comm/mailnews/jsaccount/src/JaMsgFolder.h @@ -0,0 +1,137 @@ +/* -*- 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 _JaMsgFolder_H_ +#define _JaMsgFolder_H_ + +#include "nsISupports.h" +#include "DelegateList.h" +#include "msgIOverride.h" +#include "nsIMsgFolder.h" +#include "nsMsgDBFolder.h" +#include "nsCycleCollectionParticipant.h" +#include "nsTHashMap.h" +#include "nsIInterfaceRequestor.h" +#include "nsNetUtil.h" +#include "nsIDBChangeListener.h" +#include "nsIUrlListener.h" +#include "nsIMsgFilterPlugin.h" +#include "nsIInterfaceRequestor.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 JaBaseCppMsgFolder : public nsMsgDBFolder, + public nsIInterfaceRequestor + +{ + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIINTERFACEREQUESTOR + JaBaseCppMsgFolder() {} + + // nsMsgDBFolder overrides + + nsresult CreateChildFromURI(const nsACString& uri, + nsIMsgFolder** folder) override; + nsresult GetDatabase() override; + + // Local Utility Functions + + // Create a placeholder file to represent a folder. + nsresult CreateDummyFile(nsIMsgFolder* aMailFolder); + + protected: + virtual ~JaBaseCppMsgFolder() {} +}; + +class JaCppMsgFolderDelegator : public JaBaseCppMsgFolder, public msgIOverride { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_MSGIOVERRIDE + + NS_FORWARD_NSIMSGFOLDER(DELEGATE_JS(mJsIMsgFolder, mMethods, mCppBase)->) + NS_FORWARD_NSIDBCHANGELISTENER( + DELEGATE_JS(mJsIDBChangeListener, mMethods, + (nsCOMPtr(do_QueryInterface(mCppBase)))) + ->) + NS_FORWARD_NSIURLLISTENER( + DELEGATE_JS(mJsIUrlListener, mMethods, + (nsCOMPtr(do_QueryInterface(mCppBase)))) + ->) + NS_FORWARD_NSIJUNKMAILCLASSIFICATIONLISTENER( + DELEGATE_JS(mJsIJunkMailClassificationListener, mMethods, + (nsCOMPtr( + do_QueryInterface(mCppBase)))) + ->) + NS_FORWARD_NSIMSGTRAITCLASSIFICATIONLISTENER( + DELEGATE_JS(mJsIMsgTraitClassificationListener, mMethods, + (nsCOMPtr( + do_QueryInterface(mCppBase)))) + ->) + NS_FORWARD_NSIINTERFACEREQUESTOR( + DELEGATE_JS( + mJsIInterfaceRequestor, mMethods, + (nsCOMPtr(do_QueryInterface(mCppBase)))) + ->) + + JaCppMsgFolderDelegator(); + + private: + virtual ~JaCppMsgFolderDelegator() {} + + class Super : public nsIMsgFolder, + public nsIDBChangeListener, + public nsIUrlListener, + public nsIJunkMailClassificationListener, + public nsIMsgTraitClassificationListener, + public nsIInterfaceRequestor { + public: + // Why fake this? Because this method is fully owned by + // JaCppMsgFolderDelegator, and this reference is to the "this" of the + // main method. But it is not really the local "this". + explicit Super(JaCppMsgFolderDelegator* aFakeThis) { + mFakeThis = aFakeThis; + } + NS_DECL_ISUPPORTS + NS_FORWARD_NSIMSGFOLDER(mFakeThis->JaBaseCppMsgFolder::) + NS_FORWARD_NSIDBCHANGELISTENER(mFakeThis->JaBaseCppMsgFolder::) + NS_FORWARD_NSIURLLISTENER(mFakeThis->JaBaseCppMsgFolder::) + NS_FORWARD_NSIJUNKMAILCLASSIFICATIONLISTENER( + mFakeThis->JaBaseCppMsgFolder::) + NS_FORWARD_NSIMSGTRAITCLASSIFICATIONLISTENER( + mFakeThis->JaBaseCppMsgFolder::) + NS_FORWARD_NSIINTERFACEREQUESTOR(mFakeThis->JaBaseCppMsgFolder::) + private: + virtual ~Super() {} + JaCppMsgFolderDelegator* mFakeThis; + }; + + // Interfaces that may be overridden by JS. + nsCOMPtr mJsIMsgFolder; + nsCOMPtr mJsIDBChangeListener; + nsCOMPtr mJsIUrlListener; + nsCOMPtr + mJsIJunkMailClassificationListener; + nsCOMPtr + mJsIMsgTraitClassificationListener; + nsCOMPtr mJsIInterfaceRequestor; + + nsCOMPtr mJsISupports; + + nsCOMPtr mCppBase; + RefPtr mDelegateList; + nsTHashMap* mMethods; +}; + +} // namespace mailnews +} // namespace mozilla + +#endif diff --git a/comm/mailnews/jsaccount/src/JaUrl.cpp b/comm/mailnews/jsaccount/src/JaUrl.cpp new file mode 100644 index 0000000000..249c45c9df --- /dev/null +++ b/comm/mailnews/jsaccount/src/JaUrl.cpp @@ -0,0 +1,205 @@ +/* -*- Mode: C++; tab-width: 2; 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 "nsMsgUtils.h" + +// This file contains an implementation of mailnews URLs in JsAccount. + +namespace mozilla { +namespace mailnews { + +NS_IMPL_ISUPPORTS_INHERITED(JaBaseCppUrl, nsMsgMailNewsUrl, nsIMsgMessageUrl, + msgIJaUrl, nsIInterfaceRequestor, + nsISupportsWeakReference) + +// 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; +} + +NS_IMETHODIMP JaBaseCppUrl::GetServer(nsIMsgIncomingServer** aIncomingServer) { + if (mFolder) { + return mFolder->GetServer(aIncomingServer); + } + return NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP JaBaseCppUrl::IsUrlType(uint32_t type, bool* isType) { + NS_ENSURE_ARG(isType); + *isType = (m_urlType == type); + return NS_OK; +} + +// nsIMsgMessageUrl implementation +NS_IMETHODIMP JaBaseCppUrl::GetUri(nsACString& aUri) { + if (!mUri.IsEmpty()) + aUri = mUri; + else + return NS_ERROR_NOT_INITIALIZED; + return NS_OK; +} +NS_IMETHODIMP JaBaseCppUrl::SetUri(const nsACString& 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(nsACString& aOriginalSpec) { + if (mOriginalSpec.IsEmpty()) return NS_ERROR_NULL_POINTER; + aOriginalSpec = mOriginalSpec; + return NS_OK; +} +NS_IMETHODIMP JaBaseCppUrl::SetOriginalSpec(const nsACString& aOriginalSpec) { + mOriginalSpec = aOriginalSpec; + return NS_OK; +} + +NS_IMETHODIMP JaBaseCppUrl::GetNormalizedSpec(nsACString& aPrincipalSpec) { + // URLs contain a lot of query parts. We want need a normalized form: + // scheme://server/folder?number=123 + nsCOMPtr mailnewsURL; + QueryInterface(NS_GET_IID(nsIMsgMailNewsUrl), getter_AddRefs(mailnewsURL)); + + nsAutoCString spec; + mailnewsURL->GetSpecIgnoringRef(spec); + + nsCString queryPart = MsgExtractQueryPart(spec, "number="); + + // Strip any query part beginning with ? or /; + MsgRemoveQueryPart(spec); + + if (!queryPart.IsEmpty()) spec += "?"_ns + queryPart; + + aPrincipalSpec.Assign(spec); + return NS_OK; +} + +NS_IMETHODIMP JaBaseCppUrl::GetMessageHeader(nsIMsgDBHdr** aMessageHeader) { + // This routine does a lookup using messenger, assuming that the message URI + // has been set in mUri. + NS_ENSURE_TRUE(!mUri.IsEmpty(), NS_ERROR_NOT_INITIALIZED); + nsresult rv; + nsCOMPtr messenger( + do_CreateInstance("@mozilla.org/messenger;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr msgHdr; + rv = messenger->MsgHdrFromURI(mUri, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + msgHdr.forget(aMessageHeader); + return NS_OK; +} + +// msgIJaUrl implementation +NS_IMETHODIMP JaBaseCppUrl::SetUrlType(unsigned int type) { + m_urlType = type; + return NS_OK; +} + +NS_IMETHODIMP JaBaseCppUrl::SetSpec(const nsACString& aSpec) { + return SetSpecInternal(aSpec); +} + +// 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, nsIMsgMessageUrl, nsIURI, nsIURL, + nsIURIWithSpecialOrigin, nsIMsgMailNewsUrl, msgIJaUrl, + nsIInterfaceRequestor, nsISupportsWeakReference) + +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(aDelegateList); + mMethods = &(mDelegateList->mMethods); + return NS_OK; +} +NS_IMETHODIMP JaCppUrlDelegator::GetMethodsToDelegate( + msgIDelegateList** aDelegateList) { + if (!mDelegateList) mDelegateList = new DelegateList(); + 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; + 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 cppBaseSupports; + cppBaseSupports = NS_ISUPPORTS_CAST(nsIMsgMailNewsUrl*, mCppBase); + NS_ENSURE_STATE(cppBaseSupports); + cppBaseSupports.forget(aCppBase); + + return NS_OK; +} + +} // namespace mailnews +} // namespace mozilla diff --git a/comm/mailnews/jsaccount/src/JaUrl.h b/comm/mailnews/jsaccount/src/JaUrl.h new file mode 100644 index 0000000000..6de4feba60 --- /dev/null +++ b/comm/mailnews/jsaccount/src/JaUrl.h @@ -0,0 +1,138 @@ +/* -*- 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 "nsCycleCollectionParticipant.h" +#include "nsTHashMap.h" +#include "nsIFile.h" +#include "nsIInterfaceRequestor.h" +#include "nsIMsgFolder.h" +#include "nsISupports.h" +#include "nsIURI.h" +#include "nsIURL.h" +#include "nsMsgMailNewsUrl.h" +#include "nsWeakReference.h" +#include "msgIJaUrl.h" +#include "nsProxyRelease.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 msgIJaUrl, + public nsIInterfaceRequestor, + public nsSupportsWeakReference { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIMSGMESSAGEURL + NS_DECL_MSGIJAURL + NS_DECL_NSIINTERFACEREQUESTOR + JaBaseCppUrl() {} + + // nsIMsgMailNewsUrl overrides + NS_IMETHOD GetFolder(nsIMsgFolder** aFolder) override; + NS_IMETHOD SetFolder(nsIMsgFolder* aFolder) override; + NS_IMETHOD IsUrlType(uint32_t type, bool* isType) override; + NS_IMETHOD GetServer(nsIMsgIncomingServer** aIncomingServer) override; + + protected: + virtual ~JaBaseCppUrl() {} + + // nsIMsgMailUrl variables. + + nsCOMPtr mFolder; + + // nsIMsgMessageUrl variables. + + // the uri for the original message, like ews-message://server/folder#123 + nsCString mUri; + nsCOMPtr mMessageFile; + bool mCanonicalLineEnding = false; + nsCString mOriginalSpec; + + // msgIJaUrl variables + unsigned int m_urlType{0}; +}; + +class JaCppUrlDelegator : public JaBaseCppUrl, public msgIOverride { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_MSGIOVERRIDE + + NS_FORWARD_NSIMSGMESSAGEURL( + DELEGATE_JS(mJsIMsgMessageUrl, mMethods, + (nsCOMPtr(do_QueryInterface(mCppBase)))) + ->) + NS_FORWARD_NSIINTERFACEREQUESTOR( + DELEGATE_JS( + mJsIInterfaceRequestor, mMethods, + (nsCOMPtr(do_QueryInterface(mCppBase)))) + ->) + + JaCppUrlDelegator(); + + class Super : public nsIMsgMailNewsUrl, + public nsIURIWithSpecialOrigin, + public nsIMsgMessageUrl, + public msgIJaUrl, + public nsIInterfaceRequestor, + public nsISupportsWeakReference { + public: + explicit 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_NSIURIWITHSPECIALORIGIN(mFakeThis->JaBaseCppUrl::) + NS_FORWARD_NSIMSGMESSAGEURL(mFakeThis->JaBaseCppUrl::) + NS_FORWARD_MSGIJAURL(mFakeThis->JaBaseCppUrl::) + NS_FORWARD_NSIINTERFACEREQUESTOR(mFakeThis->JaBaseCppUrl::) + NS_FORWARD_NSISUPPORTSWEAKREFERENCE(mFakeThis->JaBaseCppUrl::) + private: + virtual ~Super() {} + JaCppUrlDelegator* mFakeThis; + }; + + private: + virtual ~JaCppUrlDelegator() { + NS_ReleaseOnMainThread("JaCppUrlDelegator::mJsIMsgMessageUrl", + mJsIMsgMessageUrl.forget()); + NS_ReleaseOnMainThread("JaCppUrlDelegator::mJsIInterfaceRequestor", + mJsIInterfaceRequestor.forget()); + NS_ReleaseOnMainThread("JaCppUrlDelegator::mJsISupports", + mJsISupports.forget()); + NS_ReleaseOnMainThread("JaCppUrlDelegator::mDelegateList", + mDelegateList.forget()); + } + + // Interfaces that may be overridden by JS. + nsCOMPtr mJsIMsgMessageUrl; + nsCOMPtr mJsIInterfaceRequestor; + + // Owning reference to the JS override. + nsCOMPtr mJsISupports; + + // Class to bypass JS delegates. nsCOMPtr for when we do cycle collection. + nsCOMPtr mCppBase; + + RefPtr mDelegateList; + nsTHashMap* mMethods; +}; + +} // namespace mailnews +} // namespace mozilla + +#endif diff --git a/comm/mailnews/jsaccount/src/components.conf b/comm/mailnews/jsaccount/src/components.conf new file mode 100644 index 0000000000..89b4543316 --- /dev/null +++ b/comm/mailnews/jsaccount/src/components.conf @@ -0,0 +1,37 @@ +# 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/. + +Classes = [ + { + "cid": "{77b5592c-5018-436d-a466-c4e5443a1669}", + "contract_ids": ["@mozilla.org/jacppabdirectorydelegator;1"], + "type": "mailnews::JaCppAbDirectoryDelegator", + "headers": ["/comm/mailnews/jsaccount/src/JaAbDirectory.h"], + }, + { + "cid": "{cfcd1caa-00d9-40d0-831e-673820e04fc6}", + "contract_ids": ["@mozilla.org/jacppcomposedelegator;1"], + "type": "mailnews::JaCppComposeDelegator", + "headers": ["/comm/mailnews/jsaccount/src/JaCompose.h"], + }, + { + "cid": "{7aa11dd3-5590-4e01-bd87-91f60272d01a}", + "contract_ids": ["@mozilla.org/jacppincomingserverdelegator;1"], + "type": "mailnews::JaCppIncomingServerDelegator", + "init_method": "Init", + "headers": ["/comm/mailnews/jsaccount/src/JaIncomingServer.h"], + }, + { + "cid": "{d6bd81fa-b1d4-424a-88ea-bb3ea8381d50}", + "contract_ids": ["@mozilla.org/jacppmsgfolderdelegator;1"], + "type": "mailnews::JaCppMsgFolderDelegator", + "headers": ["/comm/mailnews/jsaccount/src/JaMsgFolder.h"], + }, + { + "cid": "{1a0b778c-2fe6-4012-b4f3-e81c0c116409}", + "contract_ids": ["@mozilla.org/jacppurldelegator;1"], + "type": "mailnews::JaCppUrlDelegator", + "headers": ["/comm/mailnews/jsaccount/src/JaUrl.h"], + }, +] diff --git a/comm/mailnews/jsaccount/src/moz.build b/comm/mailnews/jsaccount/src/moz.build new file mode 100644 index 0000000000..a78cb5c1f7 --- /dev/null +++ b/comm/mailnews/jsaccount/src/moz.build @@ -0,0 +1,30 @@ +# -*- 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", + "JaAbDirectory.cpp", + "JaCompose.cpp", + "JaIncomingServer.cpp", + "JaMsgFolder.cpp", + "JaUrl.cpp", +] + +EXPORTS += [ + "DelegateList.h", + "JaAbDirectory.h", + "JaCompose.h", + "JaIncomingServer.h", + "JaMsgFolder.h", + "JaUrl.h", +] + +Library("JsAccount") +FINAL_LIBRARY = "mail" + +XPCOM_MANIFESTS += [ + "components.conf", +] diff --git a/comm/mailnews/jsaccount/test/components.conf b/comm/mailnews/jsaccount/test/components.conf new file mode 100644 index 0000000000..63e26224b3 --- /dev/null +++ b/comm/mailnews/jsaccount/test/components.conf @@ -0,0 +1,14 @@ +# -*- Mode: python; 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/. + +Classes = [ + { + "cid": "{74b9b9c3-9594-41c4-b9f0-326e5daac2e0}", + "contract_ids": ["@mozilla.org/messenger/protocol/info;1?type=testja"], + "jsm": "resource:///modules/jsaccount/TestJaMsgProtocolInfoComponent.jsm", + "constructor": "TestJaMsgProtocolInfo", + }, +] diff --git a/comm/mailnews/jsaccount/test/idl/moz.build b/comm/mailnews/jsaccount/test/idl/moz.build new file mode 100644 index 0000000000..9975e6125b --- /dev/null +++ b/comm/mailnews/jsaccount/test/idl/moz.build @@ -0,0 +1,15 @@ +# -*- 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" + +if "comm" in CONFIG["MOZ_BUILD_APP"]: + test_harness_base = TEST_HARNESS_FILES.xpcshell.comm +else: + test_harness_base = TEST_HARNESS_FILES.xpcshell diff --git a/comm/mailnews/jsaccount/test/idl/msgIFooUrl.idl b/comm/mailnews/jsaccount/test/idl/msgIFooUrl.idl new file mode 100644 index 0000000000..9652a27cc5 --- /dev/null +++ b/comm/mailnews/jsaccount/test/idl/msgIFooUrl.idl @@ -0,0 +1,17 @@ +/* -*- 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; +}; diff --git a/comm/mailnews/jsaccount/test/moz.build b/comm/mailnews/jsaccount/test/moz.build new file mode 100644 index 0000000000..a3af38d313 --- /dev/null +++ b/comm/mailnews/jsaccount/test/moz.build @@ -0,0 +1,15 @@ +# -*- 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"] + +TESTING_JS_MODULES.mailnews += [ + "unit/resources/testJaBaseIncomingServer.jsm", + "unit/resources/testJaBaseMsgFolder.jsm", +] diff --git a/comm/mailnews/jsaccount/test/unit/head_jsaccount.js b/comm/mailnews/jsaccount/test/unit/head_jsaccount.js new file mode 100644 index 0000000000..a3c37dea1f --- /dev/null +++ b/comm/mailnews/jsaccount/test/unit/head_jsaccount.js @@ -0,0 +1,60 @@ +/* -*- 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.Constructor; + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); +var { mailTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/MailTestUtils.jsm" +); +var { localAccountUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/LocalAccountUtils.jsm" +); + +// Load the test components. +let contracts = [ + { + contractID: "@mozilla.org/jsaccount/testjafoourl;1", + classID: "{73F98539-A59F-4F6F-9A72-D83A08646C23}", + source: "resources/testJaFooUrlComponent.js", + }, + { + contractID: "@mozilla.org/mail/folder-factory;1?name=testja", + classID: "{8508ddeb-3eab-4877-a420-297518f62371}", + source: "resources/testJaBaseMsgFolderComponent.js", + }, + { + contractID: "@mozilla.org/messenger/server;1?type=testja", + classID: "{0eec03cd-da67-4949-ab2d-5fa4bdc68135}", + source: "resources/testJaBaseIncomingServerComponent.js", + }, +]; + +let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); +for (let { contractID, classID, source } of contracts) { + let scope = {}; + Services.scriptloader.loadSubScript( + Services.io.newFileURI(do_get_file(source)).spec, + scope + ); + registrar.registerFactory( + Components.ID(classID), + "", + contractID, + scope.xpcomFactory + ); +} + +// Ensure the profile directory is set up. +do_get_profile(); + +registerCleanupFunction(function () { + load("../../../../mailnews/resources/mailShutdown.js"); +}); diff --git a/comm/mailnews/jsaccount/test/unit/resources/TestJaMsgProtocolInfoComponent.jsm b/comm/mailnews/jsaccount/test/unit/resources/TestJaMsgProtocolInfoComponent.jsm new file mode 100644 index 0000000000..555f395220 --- /dev/null +++ b/comm/mailnews/jsaccount/test/unit/resources/TestJaMsgProtocolInfoComponent.jsm @@ -0,0 +1,75 @@ +/* -*- 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/ */ + +// This file is the component definition for a demo base implementation of a +// javascript nsIMsgProtocolInfo implementation. + +var EXPORTED_SYMBOLS = ["TestJaMsgProtocolInfo"]; + +function TestJaMsgProtocolInfo() { + dump("testJaMsgProtocolInfo"); + // nsIFile object to be used for the default local path. + this._defaultLocalPath = null; +} + +TestJaMsgProtocolInfo.prototype = { + // Flag this item as CPP needs to delegate to JS. + _JsPrototypeToDelegate: true, + + get defaultLocalPath() { + if (this._defaultLocalPath) { + return this._defaultLocalPath; + } + // Setup a default location, "TestFoo" directory in profile. + const NS_APP_USER_PROFILE_50_DIR = "ProfD"; + let typedir = Services.dirsvc.get(NS_APP_USER_PROFILE_50_DIR, Ci.nsIFile); + typedir.append("TestFoo"); + if (!typedir.exists()) { + typedir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0700", 8)); + } + this._defaultLocalPath = typedir; + return typedir; + }, + set defaultLocalPath(defaultLocalPath) { + this._defaultLocalPath = defaultLocalPath; + }, + // serverIID is used in AccountWizard.js, if missing will just report an error. + get serverIID() { + return null; + }, + get requiresUsername() { + return false; + }, + get preflightPrettyNameWithEmailAddress() { + return false; + }, + get canDelete() { + return true; + }, + get canLoginAtStartUp() { + return false; + }, + get canDuplicate() { + return false; + }, + getDefaultServerPort: isSecure => 0, + get canGetMessages() { + return false; + }, + get canGetIncomingMessages() { + return false; + }, + get defaultDoBiff() { + return false; + }, + get showComposeMsgLink() { + return false; + }, + get foldersCreatedAsync() { + return false; + }, + + QueryInterface: ChromeUtils.generateQI(["nsIMsgProtocolInfo"]), +}; diff --git a/comm/mailnews/jsaccount/test/unit/resources/readme.html b/comm/mailnews/jsaccount/test/unit/resources/readme.html new file mode 100644 index 0000000000..e5490e7d41 --- /dev/null +++ b/comm/mailnews/jsaccount/test/unit/resources/readme.html @@ -0,0 +1,62 @@ + + + + + JsAccount Usage and Architecture + + +

Overview of Testing Objects

+ This directory contains sample JS components to test the basic JsAccount + concepts, which can also serve as templates for new implementations.
+

Component Naming

+ 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.
+
+ Names are constructed with the following subparts:
+
+ (Ja)(AccountType)(Language)(ComponentType)(Role) +
+

Common parts of names

+
    +
  • Ja: All objects or classes begin with Ja
  • +
  • AccountType: the type of account being created (here + Foo), or Base 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.
  • +
  • + Language: Use Cpp with objects and + classes implemented using C++, leave blank for JS versions. +
  • +
  • + ComponentType: the standard MailNews term for objects + that implement a particular interface. The legacy .cpp base components + are typically named:
    + (nsMsg)(ComponentType).cpp
    + for example nsMsgIncomingServer.cpp. This may be + shortened where appropriate, for example we use Url + instead of MailNewsUrl as the Ja + prefix implies that this is an implementation of MailNews objects.
  • +
  • Role: the function of the object within the JA + architecture.
  • +
      +
    • Constructor: Creates the object only.
    • +
    • Delegator: Calls the appropriate object, either a + JS or C++ variant, that implements a particular XPCOM method. +
    • +
    • Properties: 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.
    • +
    • (blank): Actual implementation.
    • +
    +
+ Example: The C++ class that delegates the implementation of + nsIMsgMailNewsUrl (abbreviated as Url) to either a C++ or JS method is + called JaCppUrlDelegator. + + diff --git a/comm/mailnews/jsaccount/test/unit/resources/testComponents.manifest b/comm/mailnews/jsaccount/test/unit/resources/testComponents.manifest new file mode 100644 index 0000000000..22bacb7262 --- /dev/null +++ b/comm/mailnews/jsaccount/test/unit/resources/testComponents.manifest @@ -0,0 +1,16 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +# Definitions of components used in testing of JsAccount + +# nsIMsgMailNewsUrl implementation of a demo base URL +component {73F98539-A59F-4F6F-9A72-D83A08646C23} testJaFooUrlComponent.js +contract @mozilla.org/jsaccount/testjafoourl;1 {73F98539-A59F-4F6F-9A72-D83A08646C23} + +# nsIMsgFolder implementation +component {8508ddeb-3eab-4877-a420-297518f62371} testJaBaseMsgFolderComponent.js +contract @mozilla.org/mail/folder-factory;1?name=testja {8508ddeb-3eab-4877-a420-297518f62371} + +# nsIMsgIncomingServer implementation +component {0eec03cd-da67-4949-ab2d-5fa4bdc68135} testJaBaseIncomingServerComponent.js +contract @mozilla.org/messenger/server;1?type=testja {0eec03cd-da67-4949-ab2d-5fa4bdc68135} diff --git a/comm/mailnews/jsaccount/test/unit/resources/testJaBaseIncomingServer.jsm b/comm/mailnews/jsaccount/test/unit/resources/testJaBaseIncomingServer.jsm new file mode 100644 index 0000000000..602df26c30 --- /dev/null +++ b/comm/mailnews/jsaccount/test/unit/resources/testJaBaseIncomingServer.jsm @@ -0,0 +1,74 @@ +/* -*- 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/ */ + +/** + This file creates a JS-based override of the JaIncomingServer implementation. It + demos a minimal JS class, and is also used in testing the additional methods + added to JaIncomingServer.cpp that are not in nsMsgDBFolder.cpp + */ + +const EXPORTED_SYMBOLS = [ + "JaBaseIncomingServerProperties", + "JaBaseIncomingServer", +]; + +// A partial JavaScript implementation of the base server methods. + +const JaBaseIncomingServerProperties = { + baseContractID: "@mozilla.org/jacppincomingserverdelegator;1", + baseInterfaces: [ + Ci.nsISupports, + Ci.nsIMsgIncomingServer, + Ci.nsIInterfaceRequestor, + Ci.msgIOverride, + Ci.nsISupportsWeakReference, + ], + delegateInterfaces: ["nsIMsgIncomingServer"], + contractID: "@mozilla.org/messenger/server;1?type=testja", + classID: Components.ID("{0eec03cd-da67-4949-ab2d-5fa4bdc68135}"), +}; + +function JaBaseIncomingServer(aDelegator, aBaseInterfaces) { + dump("JaBaseIncomingServer\n"); + // Typical boilerplate to include in all implementations. + + // Object delegating method calls to the appropriate XPCOM object. + // Weak because it owns us. + this.delegator = 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); +} + +JaBaseIncomingServer.prototype = { + // Typical boilerplate to include in all implementations. + + // Flag this item as CPP needs to delegate to JS. + _JsPrototypeToDelegate: true, + + // QI to the (partially implemented only) interfaces. + QueryInterface: ChromeUtils.generateQI( + JaBaseIncomingServerProperties.delegateInterfaces + ), + + // Used to access an instance as JS, bypassing XPCOM. + get wrappedJSObject() { + return this; + }, + + // Dynamically-generated list of delegate methods. + delegateList: null, + + // nsIMsgIncomingServer overrides. + get localStoreType() { + return "testja"; + }, + get localDatabaseType() { + return "mailbox"; + }, +}; diff --git a/comm/mailnews/jsaccount/test/unit/resources/testJaBaseIncomingServerComponent.js b/comm/mailnews/jsaccount/test/unit/resources/testJaBaseIncomingServerComponent.js new file mode 100644 index 0000000000..dc2e6682bb --- /dev/null +++ b/comm/mailnews/jsaccount/test/unit/resources/testJaBaseIncomingServerComponent.js @@ -0,0 +1,20 @@ +/* -*- 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/ */ + +// This file is the component definition for a demo base implementation of a +// javascript IncomingServer. + +const { JSAccountUtils } = ChromeUtils.import( + "resource:///modules/jsaccount/JSAccountUtils.jsm" +); +var { JaBaseIncomingServerProperties, JaBaseIncomingServer } = + ChromeUtils.import( + "resource://testing-common/mailnews/testJaBaseIncomingServer.jsm" + ); + +var xpcomFactory = JSAccountUtils.jaFactory( + JaBaseIncomingServerProperties, + JaBaseIncomingServer +); diff --git a/comm/mailnews/jsaccount/test/unit/resources/testJaBaseMsgFolder.jsm b/comm/mailnews/jsaccount/test/unit/resources/testJaBaseMsgFolder.jsm new file mode 100644 index 0000000000..7f56bbdaef --- /dev/null +++ b/comm/mailnews/jsaccount/test/unit/resources/testJaBaseMsgFolder.jsm @@ -0,0 +1,70 @@ +/* -*- 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/ */ + +/** + This file creates a JS-based override of the JaMsgFolder implementation. It + demos a minimal JS class, and is also used in testing the additional methods + added to JaMsgFolder.cpp that are not in nsMsgDBFolder.cpp + */ + +const EXPORTED_SYMBOLS = ["JaBaseMsgFolderProperties", "JaBaseMsgFolder"]; + +// A partial JavaScript implementation of the base server methods. + +const JaBaseMsgFolderProperties = { + baseContractID: "@mozilla.org/jacppmsgfolderdelegator;1", + baseInterfaces: [ + Ci.nsISupports, + Ci.nsIMsgFolder, + Ci.nsIDBChangeListener, + Ci.nsIUrlListener, + Ci.nsIJunkMailClassificationListener, + Ci.nsIMsgTraitClassificationListener, + Ci.nsIInterfaceRequestor, + Ci.msgIOverride, + ], + delegateInterfaces: ["nsIMsgFolder"], + contractID: "@mozilla.org/mail/folder-factory;1?name=testja", + classID: Components.ID("{8508ddeb-3eab-4877-a420-297518f62371}"), +}; + +function JaBaseMsgFolder(aDelegator, aBaseInterfaces) { + // Typical boilerplate to include in all implementations. + + // Object delegating method calls to the appropriate XPCOM object. + // Weak because it owns us. + this.delegator = 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); +} + +JaBaseMsgFolder.prototype = { + // Typical boilerplate to include in all implementations. + + // Flag this item as CPP needs to delegate to JS. + _JsPrototypeToDelegate: true, + + // QI to the (partially implemented only) interfaces. + QueryInterface: ChromeUtils.generateQI( + JaBaseMsgFolderProperties.delegateInterfaces + ), + + // Used to access an instance as JS, bypassing XPCOM. + get wrappedJSObject() { + return this; + }, + + // Dynamically-generated list of delegate methods. + delegateList: null, + + // nsIMsgFolder overrides. + get incomingServerType() { + return "testja"; + }, +}; diff --git a/comm/mailnews/jsaccount/test/unit/resources/testJaBaseMsgFolderComponent.js b/comm/mailnews/jsaccount/test/unit/resources/testJaBaseMsgFolderComponent.js new file mode 100644 index 0000000000..8f71f71b6c --- /dev/null +++ b/comm/mailnews/jsaccount/test/unit/resources/testJaBaseMsgFolderComponent.js @@ -0,0 +1,19 @@ +/* -*- 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/ */ + +// This file is the component definition for a demo base implementation of a +// javascript msgFolder. + +const { JSAccountUtils } = ChromeUtils.import( + "resource:///modules/jsaccount/JSAccountUtils.jsm" +); +var { JaBaseMsgFolderProperties, JaBaseMsgFolder } = ChromeUtils.import( + "resource://testing-common/mailnews/testJaBaseMsgFolder.jsm" +); + +var xpcomFactory = JSAccountUtils.jaFactory( + JaBaseMsgFolderProperties, + JaBaseMsgFolder +); diff --git a/comm/mailnews/jsaccount/test/unit/resources/testJaFooUrlComponent.js b/comm/mailnews/jsaccount/test/unit/resources/testJaFooUrlComponent.js new file mode 100644 index 0000000000..9ed4d4c15b --- /dev/null +++ b/comm/mailnews/jsaccount/test/unit/resources/testJaFooUrlComponent.js @@ -0,0 +1,88 @@ +/* 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". +*/ + +const { JSAccountUtils } = ChromeUtils.import( + "resource:///modules/jsaccount/JSAccountUtils.jsm" +); +const { JaBaseUrl, JaBaseUrlProperties } = ChromeUtils.import( + "resource:///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 +var xpcomFactory = 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(iid) { + for (let iface of FooUrlProperties.extraInterfaces) { + if (iid.equals(iface)) { + return this; + } + } + return this.delegator.QueryInterface(iid); + }, + + // 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.includes(ATTACHMENT_QUERY); + }, +}; diff --git a/comm/mailnews/jsaccount/test/unit/test_componentsExist.js b/comm/mailnews/jsaccount/test/unit/test_componentsExist.js new file mode 100644 index 0000000000..726281bccc --- /dev/null +++ b/comm/mailnews/jsaccount/test/unit/test_componentsExist.js @@ -0,0 +1,90 @@ +/* -*- 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"], + // (probably a url bug) ["@mozilla.org/jacppurldelegator;1", "nsISupportsWeakReference"], + + // 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"], + // JaAbDirectory + ["@mozilla.org/jacppabdirectorydelegator;1", "nsISupports"], + ["@mozilla.org/jacppabdirectorydelegator;1", "nsIAbDirectory"], + ["@mozilla.org/jacppabdirectorydelegator;1", "msgIOverride"], + ["@mozilla.org/jacppabdirectorydelegator;1", "nsIInterfaceRequestor"], + ["@mozilla.org/jacppabdirectorydelegator;1", "nsISupportsWeakReference"], + // JaCompose + ["@mozilla.org/jacppcomposedelegator;1", "nsISupports"], + ["@mozilla.org/jacppcomposedelegator;1", "nsIMsgCompose"], + ["@mozilla.org/jacppcomposedelegator;1", "nsIMsgSendListener"], + ["@mozilla.org/jacppcomposedelegator;1", "msgIOverride"], + ["@mozilla.org/jacppcomposedelegator;1", "nsIInterfaceRequestor"], + ["@mozilla.org/jacppcomposedelegator;1", "nsISupportsWeakReference"], + // JaIncomingServer + ["@mozilla.org/jacppincomingserverdelegator;1", "nsISupports"], + ["@mozilla.org/jacppincomingserverdelegator;1", "nsIMsgIncomingServer"], + ["@mozilla.org/jacppincomingserverdelegator;1", "msgIOverride"], + ["@mozilla.org/jacppincomingserverdelegator;1", "nsIInterfaceRequestor"], + ["@mozilla.org/jacppincomingserverdelegator;1", "nsISupportsWeakReference"], + // JaMsgFolder + ["@mozilla.org/jacppmsgfolderdelegator;1", "nsISupports"], + ["@mozilla.org/jacppmsgfolderdelegator;1", "nsIMsgFolder"], + ["@mozilla.org/jacppmsgfolderdelegator;1", "nsIDBChangeListener"], + ["@mozilla.org/jacppmsgfolderdelegator;1", "nsIUrlListener"], + [ + "@mozilla.org/jacppmsgfolderdelegator;1", + "nsIJunkMailClassificationListener", + ], + [ + "@mozilla.org/jacppmsgfolderdelegator;1", + "nsIMsgTraitClassificationListener", + ], + ["@mozilla.org/jacppmsgfolderdelegator;1", "msgIOverride"], + ["@mozilla.org/jacppmsgfolderdelegator;1", "nsISupportsWeakReference"], + // TestJaIncomingServer + ["@mozilla.org/messenger/server;1?type=testja", "nsISupports"], + ["@mozilla.org/messenger/server;1?type=testja", "nsIMsgIncomingServer"], + ["@mozilla.org/messenger/server;1?type=testja", "msgIOverride"], + ["@mozilla.org/messenger/server;1?type=testja", "nsISupportsWeakReference"], + // TestJaMsgProtocolInfo + ["@mozilla.org/messenger/protocol/info;1?type=testja", "nsISupports"], + ["@mozilla.org/messenger/protocol/info;1?type=testja", "nsIMsgProtocolInfo"], +]; + +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]); + } +} diff --git a/comm/mailnews/jsaccount/test/unit/test_fooUrl.js b/comm/mailnews/jsaccount/test/unit/test_fooUrl.js new file mode 100644 index 0000000000..8526ef2093 --- /dev/null +++ b/comm/mailnews/jsaccount/test/unit/test_fooUrl.js @@ -0,0 +1,93 @@ +/* -*- 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. + +var { JaBaseUrlProperties } = ChromeUtils.import( + "resource:///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); + // Since the URL wasn't properly initialised, that is, it has no spec + // the following will crash. The underlying nsMsgMailNewsUrl + // has no m_baseURL yet and hence GetSpec() triggered by the + // Assert.uk(urlQI) will crash. So use this instead: + Assert.ok(urlQI != null); + } + for (let iface of extraInterfaces) { + let fooUrl = url.getInterface(iface); + Assert.ok(fooUrl instanceof iface); + Assert.ok(fooUrl.QueryInterface(iface) != null); + } + }, + function test_msgIOverride() { + let url = newURL().QueryInterface(Ci.msgIOverride); + + // test of access to wrapped JS object. + + // Access the ._hidden attribute using the XPCOM interface, + // where it is not defined. + Assert.equal(typeof url.jsDelegate._hidden, "undefined"); + + // Get the JS object, where _hidden IS defined. + Assert.equal(url.jsDelegate.wrappedJSObject._hidden, "IAmHidden"); + }, + + // We used to test nsIURI, nsIURL, and nsIMsgMailNewsUrl overrides, but those + // can no longer be overridden. + function test_nsIMsgMessageUrl() { + let url = newURL().QueryInterface(Ci.nsIMsgMessageUrl); + Assert.ok("originalSpec" in url); + let appDir = Services.dirsvc.get("GreD", Ci.nsIFile); + Assert.ok(appDir.path); + // test attributes + url.messageFile = appDir; + Assert.equal(url.messageFile.path, appDir.path); + }, + function test_msgIJaUrl() { + let url = newURL().QueryInterface(Ci.msgIJaUrl); + url.setUrlType(Ci.nsIMsgMailNewsUrl.eMove); + Assert.ok( + url + .QueryInterface(Ci.nsIMsgMailNewsUrl) + .IsUrlType(Ci.nsIMsgMailNewsUrl.eMove) + ); + }, + 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.msgIJaUrl).setSpec("https://foo.invalid/bar/"); + Assert.ok(!fooUrl.isAttachment); + url + .QueryInterface(Ci.msgIJaUrl) + .setSpec("https://foo.invalid/bar?part=1.4&dummy=stuff"); + Assert.ok(fooUrl.isAttachment); + }, +]; + +function run_test() { + for (var test of tests) { + test(); + } +} diff --git a/comm/mailnews/jsaccount/test/unit/test_jaMsgFolder.js b/comm/mailnews/jsaccount/test/unit/test_jaMsgFolder.js new file mode 100644 index 0000000000..30a8557b01 --- /dev/null +++ b/comm/mailnews/jsaccount/test/unit/test_jaMsgFolder.js @@ -0,0 +1,56 @@ +/* -*- 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/ */ + +// This tests the additional methods added to JaMsgFolder.cpp that are not +// in nsMsgDBFolder.cpp Although this code have been done creating the +// delegator class directly, instead we use a JS component as a demo of +// JS override classes. + +var { JaBaseMsgFolderProperties } = ChromeUtils.import( + "resource://testing-common/mailnews/testJaBaseMsgFolder.jsm" +); +var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm"); +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +function run_test() { + let server = MailServices.accounts.createIncomingServer( + "foouser", + "foohost", + "testja" + ); + Assert.ok(server instanceof Ci.msgIOverride); + + // If you create a folder object directly, it will complain about not being registered. + // Use folder-lookup-service instead. + let testJaMsgFolder = MailUtils.getOrCreateFolder( + "testja://foouser@foohost/somefolder" + ); + // let testJaMsgFolder = Cc[JaBaseMsgFolderProperties.contractID] + // .createInstance(Ci.msgIOverride); + Assert.ok(testJaMsgFolder instanceof Ci.nsIMsgFolder); + + JaBaseMsgFolderProperties.baseInterfaces.forEach(iface => { + dump("testing interface " + iface + "(" + Ci[iface] + ")\n"); + testJaMsgFolder.QueryInterface(Ci[iface]); + }); + + let db = testJaMsgFolder.msgDatabase; + Assert.ok(db instanceof Ci.nsIMsgDatabase); + + // Make sure the DB actually works. + let dbFolder = db.folder; + Assert.ok(dbFolder instanceof Ci.nsIMsgFolder); + Assert.equal(dbFolder.URI, "testja://foouser@foohost/somefolder"); + let fi = db.dBFolderInfo; + Assert.ok(fi instanceof Ci.nsIDBFolderInfo); + fi.setCharProperty("testProperty", "foobar"); + Assert.equal(fi.getCharProperty("testProperty"), "foobar"); + db.forceClosed(); + db = null; + + // Confirm that we can access XPCOM properties. +} diff --git a/comm/mailnews/jsaccount/test/unit/xpcshell.ini b/comm/mailnews/jsaccount/test/unit/xpcshell.ini new file mode 100644 index 0000000000..69676b5d6f --- /dev/null +++ b/comm/mailnews/jsaccount/test/unit/xpcshell.ini @@ -0,0 +1,10 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +[DEFAULT] +head = head_jsaccount.js +tail = +support-files = resources/* +[test_componentsExist.js] +[test_fooUrl.js] +[test_jaMsgFolder.js] -- cgit v1.2.3