Bug: nsImapService::StreamMessage does not return URI
Status: RESOLVED
Priority: --
Resolution: INVALID
Created: 2021-11-29 15:09:28+00:00
Resolved: 2021-12-24 16:27:42+00:00

Steps to reproduce:

I am sorry if it was already discussed in
- Bug #1623685 `getFull` raises an error for IMAP folders
- or Bug #1644027 `messages.getFull` and `getRaw` for news message
Some time ago I started development of an extension. I realized that `getFull` and `getRaw` methods were severely broken, so had to implement a custom variant of a similar method that calls `nsIMsgMessageService::streamMessage` using experiments API. Most annoying bugs have been fixed, but I still use my own method because of it does some additional things useful for me and due to Ubuntu-20.04 LTS focal still provides thunderbird-78. I can reproduce the following using a daily build downloaded a few days ago.

Essentially, I call `streamMessage` for a message from an IMAP account from an extension using experiments API


Actual results:

I get `null`  in response
```
tst.callStreamMessage =>  null
```


Expected results:

Some URI like for a news account:
```
tst.callStreamMessage =>  news://news.gmane.io:119/-tqdnd4ht7mtKIjDnZ2dnUU7-eHNnZ2d%40mozilla.org?group=gmane.comp.mozilla.thunderbird.user&key=15 background.js:9:12
```

Accordingly to https://hg.mozilla.org/comm-central/file/tip/mailnews/base/public/nsIMsgMessageService.idl#l152
```
   * @return the URL that gets run
```

Test extension:
`background.js`
```javascript
"use strict";
async function testActionHandler(info, tab) {
	let selectedMessages = info && info.selectedMessages
		&& info.selectedMessages.messages;
	for (const message of selectedMessages) {
		if (info.menuItemId === "TEST_CALL_STREAM_MESSAGE") {
			console.log("tst.callStreamMessage(%o)...", message);
			const result = await browser.tst.callStreamMessage(message.id);
			console.log("tst.callStreamMessage => %o", result);
		} else {
			console.error("testActionHandler: unsupported action %o", info.menuItemId);
		}
	}
}

browser.menus.onClicked.addListener(function testMenuListener(info, tab) {
	console.debug("testMenuListener", info, tab);
	testActionHandler(info, tab).catch(ex => {
		console.error("testMenuListener:", ex);
		throw ex;
	});
});

async function testMain() {
	await browser.menus.removeAll();
	browser.menus.create({
		contexts: [ "message_list" ],
		id: "TEST_CALL_STREAM_MESSAGE",
		title: "call StreamMessage",
	}, function tstOnMenusCreated() {
		const error = browser.runtime.lastError;
		if (error) {
			console.error("tstOnMenusCreated: %o", error);
		}
	});
}
testMain();
```

`manifest.json`
```json
{
	"manifest_version": 2,
	"name": "nsIMessageService::streamMessage return value",
	"version": "0.1",
	"permissions": [ "messagesRead", "menus" ],
	"background": { "scripts": [ "background.js" ] },
	"experiment_apis": { "tst": {
		"schema": "schema.json",
		"parent": {
			"scopes": [ "addon_parent" ],
			"paths": [ [ "tst" ] ],
			"script": "experiment.js"
		}
	} }
}
```

`schema.json`
```json
[ {
	"namespace": "tst",
	"functions": [ {
		"name": "callStreamMessage",
		"type": "function",
		"async": true,
		"parameters": [ { "name": "messageId", "type": "integer" } ]
	} ]
} ]
```

`experiment.js`
```json
"use strict";
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");

var gNoopUrlListener = {
	OnStartRunningUrl(aUrl) {
		console.log("tstUrlListener.OnStartRunningUrl", aUrl.displaySpec, aUrl);
	},
	OnStopRunningUrl(aUrl, aExitCode) {
		console.log("tstUrlListener.OnStopRunningUrl", aUrl.displaySpec, aExitCode. aUrl);
	},
	QueryInterface: ChromeUtils.generateQI([Ci.nsIUrlListener]),
};

class PromiseStreamListenerRequestObserver {
	constructor() {
		this.promise = new Promise((resolve, reject) => {
			this._resolve = resolve;
			this._reject = reject;
		});
		this.count = 0;
	}
	onStartRequest(aRequest) {
		console.log("tstPromiseStreamListener.onStartRequest", aRequest);
	}
	onDataAvailable(aRequest, aInputStream, aOffset, aCount) {
		if (this._stream === undefined) {
			this._stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
				Ci.nsIScriptableInputStream
			);
			this._stream.init(aInputStream);
		}
		this.count += aCount;
		this._stream.read(aCount);
	}
	onStopRequest(aRequest, aStatusCode) {
		console.log("tstPromiseStreamListener.onStopRequest", aRequest, aStatusCode);
		delete this._stream;
		try {
			if (aStatusCode !== 0) {
				Cu.reportError(aStatusCode);
				this._reject(new Error("StreamListener error " + aStatusCode));
			} else {
				this._resolve(this.count);
			}
		} catch (ex) {
			this._reject(ex);
		}
	}
}

PromiseStreamListenerRequestObserver.prototype.QueryInterface
	= ChromeUtils.generateQI([Ci.nsIStreamListener]);

var tst = class extends ExtensionCommon.ExtensionAPI {
	getAPI(context) {
		return { tst: {
			async callStreamMessage(messageId) {
				let messenger = Cc["@mozilla.org/messenger;1"].createInstance(
					Ci.nsIMessenger
				);
				const msgHdr = context.extension.messageManager.get(messageId);
				let msgUri = msgHdr.folder.generateMessageURI(msgHdr.messageKey);
				let service = messenger.messageServiceFromURI(msgUri);
				const streamListener = new PromiseStreamListenerRequestObserver();
				const uri = service.streamMessage(
					msgUri,
					streamListener,
					null, // msgWindow,
					gNoopUrlListener,
					false, // convertData,
					"", // additionalHeader
					false // localOnly
				);
				console.log("tst: streamMessage returned", uri && uri.displaySpec, uri);
				console.log("tst: length", await streamListener.promise);
				return uri && uri.displaySpec;
			},
		}, };
	}
	onShutdown(isAppShutdown) {
		if (!isAppShutdown) {
			Services.obs.notifyObservers(null, "startupcache-invalidate", null);
		}
	}
};
```