TypeSchema
holds full details of a type, whether it's a function type, a class, or a simpler thing. Including the function parameters (themselves again of type TypeSchema
), properties (idem), deprecation, references etc.
NamespaceSchema
holds all data of a namespace: name, description, the types it has (array of TypeSchema
objects), functions it has (idem), events (idem), permissions required, etc.
this.schemaData
array of two-member arrays of the JSON files, each of form [filepath, object]
with the object being the full thing read from the file. Automatically made at start from all files in the folders given to the constructor.
JSON files have the structure: array of objects, where there's one object per namespace affected by that JSON file.
The object's .namespace
property is the string describing the namespace, such as browserAction
.
this.namespaces
object of the form
{
namespacestring: NamespaceSchema,
...
}
where the property name is the namespace name, and the value is a NamespaceSchema
object that will be derived from the JSON files, like the types, functions, events etc. related to that namespace.
Automatically filled by the constructor with everything in all of the JSON files. Strange: it fills the .description
property only at the first occasion it encounters the namespace in the files. But what if that's not the namespace's introduction?
See if it changes anything of you do it differently.
Also, the NamespaceSchema object is filled with all types, functions and so on that are found for that namespace in any JSON files.
For all the types, the constructor sets the .optional
flag equal to .unsupported
(as taken from the
JSON-files) when we call
converter.setUnsupportedAsOptional();
in index.ts.
After the constructor, the override function runs on the converter. It calls the methods of Converter:
edit(namespace, section, id/name, editfunction)
edit_path
(a wrapper aroundedit
)remove(namespace, section, id/name)
add
removeNamespace
for thetest
namespace, which is OK of course
Inserted a check in edit
, remove
and add
to avoid crashes from trying to change non-existing properties (commit 383dbf6967).
The full conversion process, including translation to Markdown or HTML, runs after the override scripts have modified some elements of the namespaces.
The converter changes callbacks in the schemas to promises, because the schemas are derived from Chrome's WebExtensions API which uses callbacks, but Firefox and Thunderbird implement them with promises. Some callbacks in the schemas have 'optional' arguments. To be clear: not only the callback is optional (as it usually is, meaning the user is not obliged to 'consume' the promise), but so is the callback's parameter. This means Thunderbird may choose not to return a value; the promise may resolve to undefined
.
Note: if a function in JavaScript is called without any argument, it runs with its arguments set to undefined
. So if the WebExtension engine is to supply a Tab
object to the callback but may also decide to call it without any argument, the extension programmer should write that callback with signature function(Tab|undefined)
, perhaps with a check whether the value received is undefined
. Thus, a callback with an optional parameter of type T
would in principle be one with the signature function(T|undefined)
.
But in fact, this is often untrue according to MDN and Thunderbird API documentation (see also this discussion). Some callbacks may receive the argument null
, and others simply aren't called without an argument under any circumstances, so a check on undefined
values would be redundant. If we trust the 'optional' flag in the schema, we get it wrong more often than right. And it also occurs the other way around: a function whose promise can contain null
being in the schema with a required callback parameter of another type.
So I've gone to the trouble of looking it up in the online documentation for all forty-or-so individual cases, with the help of a script. For those cases where a nullish value is really allowed, the override scripts overrides.ts and tb-overrides.ts add a flag 'by hand' that tells the converter to insert the alternative possibility, making the return value something like Promise<Tab|null>
or Promise<Tab|undefined>
.
Here's the list of functions whose callbacks have an optional parameter according to the schemas:
Thunderbird API
- actually may return
null
:accounts.get
accounts.getDefault
identities.get
- actually may return
undefined
(i.e. get called without an argument):mailTabs.getCurrent
tabs.getCurrent
- listed as optional but the documentation says nothing about that – presumably never gets called without an argument:
action.getLabel
(from the filebrowserAction.json
)composeAction.getLabel
contacts.getPhoto
mailTabs.create
,.update
messageDisplayAction.getLabel
sessions.getTabValue
spaces.open
spacesToolbar.addButton
,.clickButton
tabs.create
,.duplicate
,.update
,.executeScript
windows.create
Firefox API (the parts used in Thunderbird; as mentioned on the MDN page, since the help texts in the JSON files don't tell us)
- actually may return
null
:cookies.get
and.remove
runtime.getBackgroundPage
(to be added by the Firefox types maintainer)
- actually may return
undefined
:alarms.get
- listed as optional but the documentation says nothing about that:
cookies.set
downloads.getFileIcon
identity.launchWebAuthFlow
webNavigation.getFrame
and.getAllFrames
Firefox API (the parts not used in Thunderbird)
- listed as optional but the documentation says nothing about that:
devtools.inspectedWindow.eval
tabs.create
,.duplicate
,.update
,.executeScript
,.getCurrent
windows.create
And here's a list of functions with callbacks that have no optional parameter but can actually return null or undefined:
identities.getDefault
can returnnull
according to documentationaccounts.getDefaultIdentity
can returnnull
according to documentation
A better way of writing a schema is demonstrated by the messageDisplay.json
file. It lists the callback for the function messageDisplay.getDisplayedMessage
with parameter type
"choices": [
{
"$ref": "messages.MessageHeader"
},
{
"type": "null"
}
]
which translates seemlessly to the correct function signature,
function getDisplayedMessage(tabId: number): Promise<messages.MessageHeader | null>
After override, the .convert()
method runs on the object.
It iterates through the namespaces, and for each one:
- sets
this.namespace
to its string name - calls
convertNamespace()
which then picks up the string from thethis
object - That function then puts the relevant namespace object in
data
- It then assigns some (intermediate?) results to
this
-properties by calling all those functionsconvertTypes
, etc. convertTypes
callsconvertType
, where all the errors seem to happen.
Errors so far:
type.additionalProperties.id = type.id; TypeError: Cannot create property 'id' on boolean 'true'
And after preventing .id
to be changed on a Boolean
throw new Error(
Cannot handle type ${JSON.stringify(type)}
); Error: Cannot handle type true
And after the convert method to be called on a boolean property additionalProperties
:
throw new Error(
Cannot handle type ${JSON.stringify(type)}
); Error: Cannot handle type {"optional":true,"description":"A format options object as used by |DateTimeFormat|. Defaults to: includes/cloudFile/defaultDateFormat.jsJavaScript","type":"object","additionalProperties":true,"id":"CloudFileTemplateInfo_download_expiry_date_format"}
This is happening when it tries to 'handle' the types[1].properties.download_expiry_date.properties.format 'type' from the namespace cloudFile.
Fixed this in commit 383dbf6.
You can test how it goes by mousing over format:
in
messenger.cloudFile.onFileUpload.addListener(function(a, f, t, r) {
return {
aborted: true;
templateInfo: {download_expiry_date: {format: }}
}
})
The descriptions are converted by the descToMarkdown
and toMarkdown
functions, but for Thunderbird descriptions some go wrong. The Thunderbird docs are apparently written originally in reStructuredText, a vaguely Markdown-like documentation system connected with Python.
This means that the following constructs occur in the Thunderbird JSON schemas:
- Seems that some literal strings are between
<value>
and</value>
. But in WebStorm that doesn't get rendered correctly (whereas `` does work, and so does<code>
and</code>
). Perhaps use single ` and `? - References to other documentation sometimes come as
which doesn't render as a link in WebStorm, whereas
:ref:`accounts.get`
{@link accounts.get}
renders correctly; hyperlinks can't do this. - Double newlines
\n\n
are deleted, but they actually help format the output, they should be left in - A
<literalinclude>
element (comes from the documentation website, where a code example is included) should basically go, or become a link to something. Can we make it a weblink to https://raw.githubusercontent.com/thundernest/webext-docs/latest-mv2/includes/addressBooks/onSearchRequest.js (so we take the content of the tag and prepend https://raw.githubusercontent.com/thundernest/webext-docs/latest-mv2/)? Weblinks work in the Markdown, HTML and {@link } forms. - Some links in the JSON files are not functional: the file can have
which means nothing (the original link is on the website and its underlying Github repo). This should be replaced by just text.
`legacy properties <|link-legacy-properties|>`__
- The program changes
to
The `theme_experiment add-on in our example repository <https://github.com/thundernest/sample-extensions/tree/master/theme_experiment>`__ is using the
and it shouldn't do that; make it a normal link.The `theme_experiment add-on in our example repository <https: github.com="" thundernest="" sample-extensions="" tree="" master="" theme_experiment="">`__ is using
- Some things are between | and | and they seem to be links to other API objects; replace with
{@link }
- Markdown lists: One description is as follows:
so we should recognize the Markdown list, append extra * signs before those lines (as part of the JSDoc comment), leave a blank line after the list (in addition to all the other replacements); and then it really looks like a bullet list in WebStorm. (Elsewhere the JSON uses the HTML
"A <em>dictionary object</em> defining one or more commands as <em>name-value</em> pairs, the <em>name</em> being the name of the command and the <em>value</em> being a :ref:`commands.CommandsShortcut`. The <em>name</em> may also be one of the following built-in special shortcuts: \n * <value>_execute_browser_action</value> \n * <value>_execute_compose_action</value> \n * <value>_execute_message_display_action</value>\nExample: <literalinclude>includes/commands/manifest. json<lang>JSON</lang></literalinclude>"
<li>
element, which works out of the box.) - Documentation links: :doc: plus a term, where you must link to the TB documentation followed by the term +
.html
After .convert()
, the .write(outfile)
method runs.