Closed Bug 1211292 Opened 9 years ago Closed 4 years ago

Rewrite nsMsgSend in JS

Categories

(MailNews Core :: Composition, task, P2)

Tracking

(thunderbird_esr78 wontfix, thunderbird83 wontfix)

RESOLVED FIXED
81 Branch
Tracking Status
thunderbird_esr78 --- wontfix
thunderbird83 --- wontfix

People

(Reporter: jcranmer, Assigned: rnons)

References

(Blocks 1 open bug, Regressed 1 open bug)

Details

Attachments

(30 files, 40 obsolete files)

(deleted), patch
Details | Diff | Splinter Review
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), text/plain
Details
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), patch
rnons
: review+
Details | Diff | Splinter Review
(deleted), patch
rnons
: review+
Details | Diff | Splinter Review
(deleted), patch
rnons
: review+
Details | Diff | Splinter Review
(deleted), patch
rnons
: review+
Details | Diff | Splinter Review
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), patch
rnons
: review+
Details | Diff | Splinter Review
(deleted), patch
rnons
: review+
Details | Diff | Splinter Review
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), patch
rnons
: review+
Details | Diff | Splinter Review
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), patch
rnons
: review+
Details | Diff | Splinter Review
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), patch
rnons
: review+
Details | Diff | Splinter Review
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), patch
rnons
: review+
Details | Diff | Splinter Review
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), patch
mkmelin
: review+
Details | Diff | Splinter Review
(deleted), patch
rnons
: review+
Details | Diff | Splinter Review
Don't panic, I'm nowhere near done with this yet. I'm just filing early so that relevant parties can influence some of the development direction. nsMsgSend is the largest part of the compose source code; I use the term loosely here, since the logic implied by nsIMsgSend ends up getting spread out to 10 different files which are really inextricable from the current C++ implementation--the most important being nsMsgSend.cpp, nsMsgCompUtils.cpp (which handles headers), nsURLFetcher.cpp, nsMsgAttachmentHandler.cpp (which mostly handles figuring out how to convert an attachment to a MIME part), and nsMsgSendPart.cpp (which does most of the actual assembly). This is collectively about 10,000 lines of C++ code, which will end up being replaced by perhaps 3000-5000 lines of JS. So the good news is that we get to migrate 3% of our C++ codebase to JS in one go! And the bad news is that this ends up being effectively one gigantic patch: the backend here is not designed (nor is it easy to retrofit into) to allow for piecemeal conversion of logic. nsMsgSend has some quite frankly broken logic and simultaneously overly and insufficiently expressive interfaces; the rewrite will end up "accidentally" fixing several issues of known broken logic (even the possible duplicates list that pops up when I wrote this bug managed to uncover one such possible issue)--and, naturally, will probably come with severe regressions of its own that can't be uncovered except by real-world tests. As I see it, nsMsgSend comprises basically three loosely-dependent but atomic steps of a pipeline: 1. From input values, produce the top-level headers, and collect all of the bodies, attachments, and inline images into a uniform representation. 2. Convert said representation into a MIME blob. 3. Send that MIME blob over SMTP/NNTP/to a folder/etc. as desired. This suggests a potential transition plan that allows for these steps to be converted independently, but reality isn't quite so simple: nsMsgSendPart (in no small part thanks to nsIMsgComposeSecure) isn't really set up to be easily rewritten in JS independently of nsMsgSend, and the burden of tasks like "who chooses the Content-Transfer-Encoding?" is in the wrong place for my tastes. I have an abandoned patch queue that does make it feasible, but it's ≥9 patches that involve almost completely rewriting nsMsgSendPart just to make it possible to use a JS implementation instead. I will point out, though, that the boundary between parts 2 and 3 is much easier to cross: my current work relies on the C++ implementation entirely for part 3 while doing parts 1 and 2 all in JS. It's possible to land a "MimePartBuilder" (the main class in my implementation to do step #2) independently--but the potential uses in the code base outside of the compose backend are too few and too minor to provide much useful feedback on its implementation. So this basically leaves us with "land the JS implementation all at once" or "land the JS implementation minus step #3" as the most plausible landing plans, much as I'm sure the reviewers will hate me for that. I rather suspect that it would behoove us to leave the C++ implementation in-tree (and not break it!) for at least 1 or 2 beta cycles and ideally an ESR release so that we can quickly switch back to it if need be. This implies that interface simplification (sorely needed!) would have to wait that long as well. A rundown of the current architecture of my reimplementation, compared to the original: 1. MimePartBuilder becomes a separate class with a dedicated, exposed interface (although I'm considering not providing XPIDL for it, since the only potential C++ consumer at this point, nsMsgMdnGenerator, could be easily rewritten in JS). 2. The main entry points to nsIMsgSend (createAndSendMessage/createRFC822Message) shove all their parameters into easier-to-handle BodyOptionsBag and SendOptionsBag object and pass those to a promise/Task-based internal operation (with errors being processed in a catch-all handler, although the error-handling code is probably broken at present. Too many listeners!). 3. The C++ methods ::InitCompositionFields and msg_generate_headers have one-to-one correspondence with JS methods such that you could open the C++ code and the JS code and directly confirm their equivalence. 4. Everything else about step 1 was designed from scratch in terms of "how would I go about setting this code up?", since the C++ code is... wrong, squirrely, broken, inane. It does make it much harder to verify that the capabilities are the same, particularly around corner cases like attaching .eml messages. 5. Step 2 is mostly leveraged around jsmime. I don't have any support for anything less than "write the entire body all at once" (internally, the emitters work on a streaming interface, but the API doesn't expose any sort of piecemeal body emission, largely because of concerns about both how to do so (WHATWG streams aren't implemented yet) and complexity of transfer encodings at that point). 6. I've not started any work on the actual send portion of the interface. I have a vague notion of what I want (essentially, expressing the code to send to file/folder/SMTP/NNTP as a "send target" and hooking up send to merely go through each of the send targets in turn). In the medium term, there is scope for radical interface simplification. nsIMsgSend is mostly "internal" methods being exposed, for example, and nsIMsgSendReport and nsIMsgSendListener are far too overengineered when you look at how they get used in practice. On the other hand, there are situations where the current interfaces are just insufficient: nsIMsgComposeSecure desperately wants to be rewritten, I think, and there's no easy way for new account types to do custom mail delivery (that's partially what I want to rectify with send targets), as I think rkent has too much experience with.
The ExQuilla/SkinkGlue override of nsMsgSend is one of two places where I use js overrides instead of C++ classes. The js part of this is 585 lines, not too bad actually. Although the code is not free licensed, because it is in js it is freely readable, and I have no objection to using it to guide development. I fully agree that "nsMsgSend comprises basically three loosely-dependent but atomic steps of a pipeline" means we really need to break those out into clearly defined separate steps. I also fully agree that nsMsgSend is probably too convoluted now to be amendable to piece-wise conversion from C++ to js (unlike say nsMsgDBFolder), which hopefully will be possible in other methods if we can extend JsAccount to include that capability. In the SkinkGlue/JsAccount system, an additional interface is added that enables additional internal features that must be overridden for a js implementation to work. Although ExQuilla does not have a partticularly robust send capability, here is what was needed (see source at https://bitbucket.org/rkentjames/skinkglue/src/43f63f3e35b7ab171125b6d4db287fe4bf6958e9/public/msqISgSend.idl?at=tip) interface msqISgSend : nsISupports { readonly attribute nsIMsgCompFields sendCompFields; AString getSendBody(); ACString getSendBodyType(); readonly attribute nsIMsgIdentity identity; // The lifetime of the attachment is dependent on the existence // of the underlying send object, so do not hold onto these // attachments. msqISgAttachmentHandler getAttachment(in unsigned long index); // testing only void setDeliveryMode(in long aDeliveryMode); // error reporting uses an internal string name which we must set attribute AString savedToFolderName; // allows us to set m_dont_deliver_p outside of init attribute boolean dontDeliver; }; Not a very long list actually.
Attached patch Initial prototype (deleted) — — Splinter Review
I forgot to do this last night, but here's roughly the work in progress I have. It's a bit squirrelly since it's being developed sort-of as an add-on, and the test fixes needed are annoying.
Whiteboard: [patchlove]
Status: ASSIGNED → NEW
Type: defect → task
Priority: -- → P3
Assignee: Pidgeot18 → nobody

(In reply to Joshua Cranmer [:jcranmer] from comment #0)

As I see it, nsMsgSend comprises basically three loosely-dependent but atomic steps of a pipeline:

  1. From input values, produce the top-level headers, and collect all of the bodies, attachments, and inline images into a uniform representation.
  2. Convert said representation into a MIME blob.
  3. Send that MIME blob over SMTP/NNTP/to a folder/etc. as desired.

As Joshua mentions further down in his comment #0, there is also nsIMsgComposeSecure, so at some stage the data needs to be run through encryption. Maybe that happens after step 2 and encryption replaces one "MIME blob" with another.

Assignee: nobody → remotenonsense
Priority: P3 → P2
Attached patch 1211292-take1.patch (obsolete) (deleted) — — Splinter Review

This patch adds a MessageSend.jsm and pipes to nsMsgSend, the goals of this patch are:

  • Everything should work as before
  • When using MOZ_NEW_MSG_SEND=1 ./mach run, the js implementation of createAndSendMessage will be used, and should be able to send plain and/or html text mail

Next steps will be:

  • Handle attachments
  • Handle encryption
  • Details and things I'm not aware of yet

A Try run: https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&revision=8b475b827fc1cb88ca01498ecdae6289727fd03a&selectedTaskRun=BV_QGx_ITICXD4lGRR5-wg.0

Everything seems good, except test_temporaryFilesRemoved.js is failing. The reason is after wrapping nsMsgSend inside MessageSend.jsm, the destructor of nsMsgSend is not run as fast as when using nsMsgSend directly. Haven't found a workaround yet.

Attachment #9164284 - Flags: review?(Pidgeot18)
Status: NEW → ASSIGNED

This patch has quite a few issues, some major and some minor, so let me first focus on those higher-level issues before delving into minutiae.

The biggest issue I have here is that I don't see any sense of a design for the rewrite. Looking at this patch, I have a hard time seeing what you envision the code will look like once we kill off the old code; how will the various stages be organized in their constituent classes/functions; what kind of API that's not the monstrosity of nsIMsgSend will look like. That's the design part that's lacking here, and I believe it would behoove us to nail down the design before going off and doing much implementation.

A related note is the plan for this implementation. When I proposed essentially one big code drop, I was too focused on making changesets be atomic "feature-complete" changesets rather than actually finding ways to make small changes that make sense. I want to push you to do a large-set-of-small-change approach here, and a sketch of what that would start with is:

  1. Add a JS implementation that handles the fallback mechanism, and does nothing else.
  2. Gather the features implied by all of the prefs that are hidden somewhere in the process--and fallback if the implementation doesn't support them yet (which it obviously won't at this stage!).
  3. Change the compose tests to be able to test both the old and new implementation.
  4. Now add support for plain text emission. No format=flowed support, no del=sp, no non-UTF-8 charset support, just the most basic plain text output. For now, we'll send via the old implementation.
  5. Add format=flowed support.
  6. Add HTML support (but don't support embedded images!)
  7. Add support for regular attachments
  8. And now cloud attachments
  9. And embedded images
  10. How about some multipart/alternative support?
  11. Encryption might happen now.
  12. Wouldn't it be nice to attach an email?
  13. Ooh, let's start handling send stuff in JS now. And this probably requires multiple patches of course!

I'm not going to insist on that particular set of changes, but that should give you an idea of the kind of plan I'd like to see for these changes.

I'm not a fan of how the fallback works in this patch. An approach that may work better is to try to use the JS implementation, and fallback to the old C++ implementation if it requires an unsupported feature (this is why I had patch 2 in my above listed sequence). Combine this with a mechanism to force the old or new implementation as well (which works especially well for testing--the tests can run twice, once with forcing old and once with forcing new).

Comment on attachment 9164284 [details] [diff] [review] 1211292-take1.patch Review of attachment 9164284 [details] [diff] [review]: ----------------------------------------------------------------- Let's move onto some comments on the code itself: ::: mailnews/compose/public/nsMsgCompCID.h @@ +148,5 @@ > // > // nsMsgSend > // > +#define NS_MSGSEND_CONTRACTID1 "@mozilla.org/messengercompose/send;1" > +#define NS_MSGSEND_CONTRACTID "@mozilla.org/messengercompose/send;2" This renumbering of the contract-ID here is a massive code smell, and it's rather unnecessary. The biggest issue is that anyone who has been using the original contract-id will keep using the old code; that you have to manually change all the callers to use the new code. I explained above one way to handle the fallback, which is not the way you've chosen to do it here. Were you to do the fallback method you're doing here, a better way to do it would be to change the XPCOM registration of which CID maps to the contract-id. Somewhere in the startup process, you'd query the environment variable to figure out which CID you want to register as the contract-id. ::: mailnews/compose/src/MessageSend.jsm @@ +19,5 @@ > + this._userIdentity = null; > + > + // Hold an instance of nsMsgSend so we can delegate not yet implemented > + // functionalities to it. > + this._send = Cc["@mozilla.org/messengercompose/send;1"].createInstance( Similarly, here you'd want to construct by the CID instead of the contract-id to specifically request the C++ implementation: this._send = Components.classesByID["{935284E0-C5D8-11d2-8297-000000000000}"] .createInstance(Ci.nsIMsgSend); @@ +31,5 @@ > + /** > + * If MOZ_NEW_MSG_SEND is not set, pass all arguments to nsMsgSend, and > + * everything should work as before. > + */ > + async createAndSendMessage( The XPCOM interface does not return a Promise. So do not make the JS implementation of that interface method do so! Instead, wrap the inner method that returns the promise with something that will handle the notifying the listener with the appropriate responses. @@ +61,5 @@ > + > + let env = Cc["@mozilla.org/process/environment;1"].getService( > + Ci.nsIEnvironment > + ); > + if (env.get("MOZ_NEW_MSG_SEND")) { I am not a fan of using environment variables for this sort of thing; it's actually amazingly user-hostile to pass things around via this out-of-band channel. The general trend at Mozilla, I believe, has been to use hidden prefs to trigger this sort of thing. @@ +209,5 @@ > + `multipart/alternative; boundary="${separator}"`, > + ]); > + body = [ > + `--${separator}`, > + "Content-Type: text/plain", Note how you're missing all the nice format=flowed stuff on this content-type? And also the charset support? @@ +217,5 @@ > + "Content-Type: text/html", > + "Content-Transfer-Encoding: 7bit\r\n", > + html, > + `--${separator}--`, > + ].join("\r\n"); This code is incorrect for handling the feature set it's apparently claiming to support. But it's also not going to generalize well to supporting the features that need to be supported. This specifically makes me concerned that you haven't attempted to design the final pipeline, and are instead trying to handle a very complicated topic on the seat of your pants. @@ +289,5 @@ > + if (subject) { > + headers.set("Subject", [subject]); > + } > + > + let replyTo = this._compFields.getHeader("replyTo"); The header is actually "Reply-To", not replyTo. @@ +304,5 @@ > + if (bcc) { > + headers.set("bcc", bcc); > + } > + > + return headers; So if the user specifically requests additional headers, she doesn't get them because you only grab a precious few headers from the compose fields?
Attachment #9164284 - Flags: review?(Pidgeot18) → review-

Hi, thanks for the review.

The biggest issue I have here is that I don't see any sense of a design for the rewrite

The overall roadmap is in comment 4. Let me try to rephrase it a bit

  1. the current patch: introducing MessageSend.jsm, and fallback to nsMsgSend.cpp, support html and/or plain text
  2. support attachments
  3. support encryption
  4. other things I'm not aware yet

I'm learning by doing right now, I'm not exactly clear what will be involved in the next steps.

  1. Change the compose tests to be able to test both the old and new implementation.

Do you mean test the old and new separately? From a user's perspective, there is just one version. In the current patch, the tests are testing nsMsgSend.cpp wrapped inside MessageSend.jsm. When it's ready in a few months, tests will be testing MessageSend.jsm only.

I explained above one way to handle the fallback, which is not the way you've chosen to do it here. Were you to do the fallback method you're doing here
this._send = Components.classesByID["{935284E0-C5D8-11d2-8297-000000000000}"]
.createInstance(Ci.nsIMsgSend);

Yes, I saw Components.classesByID in your prototype patch, but it doesn't seem to exist anymore. https://searchfox.org/mozilla-central/search?path=&q=classesByID returns anything. That's why I think two contractids are needed. I can change the old one to `define NS_MSGSEND_CONTRACTID0 "@mozilla.org/messengercompose/send;0", is that better?

I am not a fan of using environment variables for this sort of thing; it's actually amazingly user-hostile to pass things around via this out-of-band channel.
The general trend at Mozilla, I believe, has been to use hidden prefs to trigger this sort of thing.

Compile time env vars seems common in our code base. Like this https://searchfox.org/comm-central/source/mail/base/modules/MailConstants.jsm#13-18. But it takes a long time to rebuild after toggling a compile time env. That's why I'm using a runtime env here.

This code is incorrect for handling the feature set it's apparently claiming to support. But it's also not going to generalize well to supporting the features that need to be supported.

Is it only the content-type incorrect, or are there other things incorrect? Yes, I'm aware this needs to be changed when handling attachments. I'm going to introduce something similar to the MimePart in your prototype patch and nsMsgSendPart in the next patch.

So if the user specifically requests additional headers, she doesn't get them because you only grab a precious few headers from the compose fields?

Do you mean I should extract all the headers from comp fields? Will update.

Aside from the testing and fallback mechanism, I guess my roadmap is not too different from yours. Do you think changing the old contract id to define NS_MSGSEND_CONTRACTID0 "@mozilla.org/messengercompose/send;0" is good? Or maybe there are other approaches?

For testing "both version", see what we do for ical.js: https://searchfox.org/comm-central/search?q=calendar.icaljs&path=&case=false&regexp=false
(Also does dynamic registration of one or the other calendar library)

Attached patch 1211292-part0.patch (obsolete) (deleted) — — Splinter Review

(In reply to Magnus Melin [:mkmelin] from comment #8)

For testing "both version", see what we do for ical.js: https://searchfox.org/comm-central/search?q=calendar.icaljs&path=&case=false&regexp=false
(Also does dynamic registration of one or the other calendar library)

Thanks, didn't know xpcom can be dynamically registered.

So this patch adds a MsgSendModuleLoader.jsm, that doesn't do anything by default, but will load MessageSend.jsm when a pref value is true.

Attachment #9164284 - Attachment is obsolete: true
Attachment #9164997 - Flags: review?(Pidgeot18)

(In reply to Ping Chen (:rnons) from comment #7)

The overall roadmap is in comment 4. Let me try to rephrase it a bit

  1. the current patch: introducing MessageSend.jsm, and fallback to nsMsgSend.cpp, support html and/or plain text
  2. support attachments
  3. support encryption
  4. other things I'm not aware yet

A roadmap is not a design. The design I am looking for is the skeleton API of what the final product should look like: which classes will need to exist, who will be responsible for handling various steps of the process, what parameters need to be passed around, etc.

I'm learning by doing right now, I'm not exactly clear what will be involved in the next steps.

Please stop writing code for the moment and spend a few days just reading through the existing code and taking notes on the features it supports and some of the ways it goes about doing things. Then think about how the design should look to implement those features (although note that we don't want to continue supporting all features--multipart/apple-double for example we're likely to drop). Only once you have that first draft of a design should you start to go about writing the code. I'm saying this to try to save you hours of time.

I do realize that our compose code is dilapidated and cruddy, and that it can be hard to follow. If you need help, ping me in #maildev on matrix and I can try to respond as quickly as I'm available. (Or others might be able to answer your question as well).

Do you mean test the old and new separately? From a user's perspective, there is just one version. In the current patch, the tests are testing nsMsgSend.cpp wrapped inside MessageSend.jsm. When it's ready in a few months, tests will be testing MessageSend.jsm only.

The old and new versions will have to coexist for some time. While they coexist, it is necessary to test both. (And some time can be a long time; ical.js has been meant to replace libical for over 7 years now!).

Yes, I saw Components.classesByID in your prototype patch, but it doesn't seem to exist anymore. https://searchfox.org/mozilla-central/search?path=&q=classesByID returns anything.

I'm going to have to do some more research here... It may be advisable simply to do the choose-which-one in C++ code rather than JS, since C++ definitely can construct by CID instead of contractID.

Is it only the content-type incorrect, or are there other things incorrect? Yes, I'm aware this needs to be changed when handling attachments. I'm going to introduce something similar to the MimePart in your prototype patch and nsMsgSendPart in the next patch.

I don't know; I didn't spend the time to enumerate all the problems. If you're going to rip the code out shortly anyways, why should I bother trying to understand all the issues? This is why I am pushing you hard to do a design first: it will help you understand what you need to do, and you won't have to write (and I won't have to review) incorrect code.

Well many times just taking it one step at a time to see what needs to happen is the only feasible route.
Trying to understand it all beforehand is asking a lot. Of course there will be iterations. Especially since it's off by default until everything is working, I don't see a problem with that.

(In reply to Joshua Cranmer [:jcranmer] from comment #10)

A roadmap is not a design. The design I am looking for is the skeleton API of what the final product should look like: which classes will need to exist, who will be responsible for handling various steps of the process, what parameters need to be passed around, etc.

Maybe we have different understanding of this task. In my opinion the task is rewriting, the skeleton api will still be nsIMsgSend. I think you mentioned on matrix that you don't like 18 arguments got passed to createAndSendMessage, but I don't think it's good to change it in this task.

Please stop writing code for the moment and spend a few days just reading through the existing code and taking notes on the features it supports and some of the ways it goes about doing things. Then think about how the design should look to implement those features (although note that we don't want to continue supporting all features--multipart/apple-double for example we're likely to drop). Only once you have that first draft of a design should you start to go about writing the code. I'm saying this to try to save you hours of time.

I've already spent a few days reading the existing code and your prototype patch. I tried to understand every line of the code, but it doesn't seem to work for me. I don't think I can form a design different from nsIMsgSend by reading the existing code, but after I implement all the interfaces of nsIMsgSend in JS, I should have better idea. So I changed my mind to start with porting createAndSendMessage, try to support sending html/plain message as the first step, and fill in missing pieces step by step.

The old and new versions will have to coexist for some time. While they coexist, it is necessary to test both. (And some time can be a long time; ical.js has been meant to replace libical for over 7 years now!).

I was only aware of ical.js and libical yesterday, my new patch should support that, do you mind taking a look?

Is it only the content-type incorrect, or are there other things incorrect? Yes, I'm aware this needs to be changed when handling attachments. I'm going to introduce something similar to the MimePart in your prototype patch and nsMsgSendPart in the next patch.

I don't know; I didn't spend the time to enumerate all the problems. If you're going to rip the code out shortly anyways, why should I bother trying to understand all the issues? This is why I am pushing you hard to do a design first: it will help you understand what you need to do, and you won't have to write (and I won't have to review) incorrect code.

I'm going to adapt the code to support attachments, not throw it away. By introducing a MimePart class with a Write function (https://searchfox.org/comm-central/source/mailnews/compose/src/nsMsgSendPart.cpp#378), I still need to build the final message not too different from string concatenation. Getting the most basic html/plain text message right will help me with the next step.

BTW, my new patch in comment 9 is roughly the first item in your roadmap:

  1. Add a JS implementation that handles the fallback mechanism, and does nothing else.

I will send a patch that supports html/plain message after that.

(In reply to Joshua Cranmer [:jcranmer] from comment #10)

A roadmap is not a design. The design I am looking for is the skeleton API of what the final product should look like: which classes will need to exist, who will be responsible for handling various steps of the process, what parameters need to be passed around, etc.

It just came to my mind that the the internal structure of MessageSend.jsm will become much more clear when supporting attachments. I haven't figured out how attachments are handled, and I don't want to put too much stuff in one patch. That's why I did the html/plain message as a separate patch. It helped me understand how a RFC822/RFC2045 message looks like. If we can get things right for html/plain text only message first, then we have less things to worry about in the next steps.

I don't trust my design if I can't see sending/receiving message actually works in TB. It will help me a lot if you can bare with me for the first two patches. We can discuss more with the actual code in the planned third patch, in which I will handle attachments.

Attached patch 1211292-part1.patch (obsolete) (deleted) — — Splinter Review

This patch partially supports sending text message without any attachments. Content-Transfer-Encoding is missing right now. I will add a pickEncoding function and encode the body text when necessary in the next patch.

Attachment #9166755 - Flags: review?(mkmelin+mozilla)
Attachment #9164997 - Flags: review?(mkmelin+mozilla)

If you learn best by implementing code, that is totally fine and I have no issues with you doing that. If you need to implement the code in the context of Thunderbird to get that practical experience, again I have no objections. If you need more experience before you can develop a design, yet again, I will not object.

But to review a project of this magnitude, I do need that design. And I believe it would be a better use of both your time and my time were you to go ahead and complete your learning experiences without trying to work those patches for review. When you have those modifications done, then you should be able to complete a better design, and we can discuss that. And it may be that you can reuse a lot of your prior code; it may be that you cannot use it. Don't feel bad about throwing away code; I know I've got at least two or three attempts at rewriting this code in the past, even if most of them have never even graced a review comment.

Comment on attachment 9164997 [details] [diff] [review] 1211292-part0.patch Review of attachment 9164997 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MsgSendModuleLoader.manifest @@ +1,1 @@ > +category profile-after-change msg-send-module-loader @mozilla.org/messengercompose/send-module-loader;1 \ No newline at end of file Categories can now be registered in components.conf files, so please do so.
Comment on attachment 9164997 [details] [diff] [review] 1211292-part0.patch Review of attachment 9164997 [details] [diff] [review]: ----------------------------------------------------------------- I'm going to r- this for right now: I'm in the middle of moving comm-central to static component registration in bug 1652354, which will likely have knock-on effects for the registration here. (I think there's more work that needs to be done to be able to override contract IDs in static component registration). I don't believe it's worth appraising this patch before that code is ready.
Attachment #9164997 - Flags: review?(Pidgeot18) → review-
Comment on attachment 9166755 [details] [diff] [review] 1211292-part1.patch Review of attachment 9166755 [details] [diff] [review]: ----------------------------------------------------------------- One important design element that I should appraise you of quite early: ::: mailnews/compose/src/MessageSend.jsm @@ +186,5 @@ > + let bodyText = decoder.decode(byteStringToArrayBuffer(aBody)); > + let htmlPart = null; > + let plainPart = null; > + > + // TODO: Add a pickEncoding function for the CTE header. Encode bodyText if Think about what you need to do for 8-bit, non-UTF-8 parts... @@ +468,5 @@ > + > + /** > + * Convert a MimePart and all its parts to string. > + */ > + async toString() { ... because JS strings are likely to cause you to do the wrong thing. And it seems clear to me that you really love using JS's string formatting, but in the long run, you are probably better off using Uint8Array instead of strings. [This is an example of why I want to see a design up-front: you could potentially convince me otherwise that strings instead of Uint8Array are going to be OK for this code, but convincing is going to be very difficult without enough of a design to indicate to me that you've thought through the implications.]
Attachment #9166755 - Flags: feedback-
Attached patch 1211292-part0.patch (obsolete) (deleted) — — Splinter Review

This patch uses the categories field in components.conf.

(In reply to Joshua Cranmer [:jcranmer] from comment #18)

I'm in the middle of moving comm-central to static component registration in
bug 1652354, which will likely have knock-on effects for the registration
here.

Are you worrying there will be lots of conflicts? I didn't change the registration of any existing components. Seems to me the only conflict will be in the components.conf file.

(I think there's more work that needs to be done to be able to
override contract IDs in static component registration). I don't believe
it's worth appraising this patch before that code is ready.

The approach in this patch is the same as calBackendLoader.js, which loads ical.js dynamically. And it seems to work. Please tell me what more I need to do.

Attachment #9164997 - Attachment is obsolete: true
Attachment #9164997 - Flags: review?(mkmelin+mozilla)
Attachment #9167197 - Flags: review?(mkmelin+mozilla)
Attachment #9167197 - Flags: review?(Pidgeot18)

(In reply to Joshua Cranmer [:jcranmer] from comment #19)

Comment on attachment 9166755 [details] [diff] [review]
Think about what you need to do for 8-bit, non-UTF-8 parts...
... because JS strings are likely to cause you to do the wrong thing.

Can you enlighten me what do you mean 8-bit, non-UTF-8 part? And what is the problem of using JS string?

And it seems clear to me that you really love using JS's string formatting,
but in the long run, you are probably better off using Uint8Array instead of
strings.

[This is an example of why I want to see a design up-front: you could
potentially convince me otherwise that strings instead of Uint8Array are
going to be OK for this code, but convincing is going to be very difficult
without enough of a design to indicate to me that you've thought through the
implications.]

In my opinion, this is about details/things I'm unaware of. If I don't know using JS string is a problem, how would my design avoid it? I use JS string because it works so far. If you point out the problems, I would sure change to Uint8Array if necessary. And I already mentioned "I will add a pickEncoding function and encode the body text when necessary in the next patch." in comment 15.

Attached patch 1211292-part1.patch (obsolete) (deleted) — — Splinter Review

(In reply to Joshua Cranmer [:jcranmer] from comment #19)

Comment on attachment 9166755 [details] [diff] [review] [diff] [review]
Think about what you need to do for 8-bit, non-UTF-8 parts...
... because JS strings are likely to cause you to do the wrong thing.

I think I understand the problem better now, it's not that JS can't represent 8-bit non-UTF-8 chars, but it becomes incorrect after converting back and forth, especially TextEncoder only supports UTF-8. So this patch stores 8-bit string in MimePart directly to avoid converting. I think string is more suitable than Uint8Array, because there is no .concat method of Uint8Array, I will need to use something like new Uint8Array([...a, ...b]) to combine two arrays. I also need to convert headers and part separator to Uint8Array. The code will be difficult to read and easy to make mistakes.

The more I work on this bug, the more details I know. I don't worry about the design, I worry about all kinds of cases. When I submitted the first patch, I didn't even know foramt=flowed only applies to text/plain. After adding class MimePart in this patch, and class MimeEncoder on my local, I'm satisfied with the design, I believe it can handle attachments without big change. Thank you for pointing out things I don't know.

Attachment #9166755 - Attachment is obsolete: true
Attachment #9166755 - Flags: review?(mkmelin+mozilla)
Attachment #9167256 - Flags: review?(mkmelin+mozilla)
Attachment #9167256 - Flags: review?(Pidgeot18)
Comment on attachment 9167197 [details] [diff] [review] 1211292-part0.patch Review of attachment 9167197 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MessageSend.jsm @@ +33,5 @@ > + aPassword, > + aOriginalMsgURI, > + aType > + ) { > + dump("To be implemented\n"); for the TODOs, include what function it is in the message. Or perhaps it would be better to throw? throw Components.Exception("createAndSendMessage not implemented", Cr.NS_ERROR_NOT_IMPLEMENTED); I think it would be good to also at this point remove all the a-for-argument namings for parameters. It's not really useful in js. ::: mailnews/compose/src/MsgSendModuleLoader.jsm @@ +9,5 @@ > + > +const EXPORTED_SYMBOLS = ["MsgSendModuleLoader"]; > + > +/** > + * Use nsMsgSend.cpp by default. When `mailnews.send.jsmodule` is `true`, use Perhaps reword this so it's not odd when we switch defaults. Like "Determine whether to use nsMsgSend.cc or MessageSend.jsm. ......"
Attachment #9167197 - Flags: review?(mkmelin+mozilla) → feedback+
Comment on attachment 9167256 [details] [diff] [review] 1211292-part1.patch Review of attachment 9167256 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MessageSend.jsm @@ +1,5 @@ > /* 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/. */ > > +let { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm"); It's really such a shame jsmime is lowercased... (not this bug) Makes calls quite ugly. @@ +92,5 @@ > + notifyListenerOnStopCopy(status) { > + let copyListener = this._sendListener.QueryInterface( > + Ci.nsIMsgCopyServiceListener > + ); > + if (copyListener) { wouldn't this always be true unless something's really wrong? @@ +127,5 @@ > + * 3. Write the MimePart to a tmp file, e.g. /tmp/nsemail.eml > + * 4. Pass the file to this._deliverMessage > + */ > + async _createAndSendMessage( > + aEditor, Same comments as for the other patch. @@ +245,5 @@ > + let file = Services.dirsvc.get("TmpD", Ci.nsIFile); > + let tmpFile = file.clone(); > + file.append("nsemail.eml"); > + tmpFile.append("nscopy.tmp"); > + file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); Can use OS.Constants.Path.tmpDir and use the OS.File (or OS.Path) operators to get the file path. I don't think it needs to be created, you should just call writeAtomic directly. @@ +350,5 @@ > + * Send a message file to smtp service. Far from complete. > + * TODO: handle cc/bcc. Other details. > + */ > + _deliverFileAsMail(file) { > + let to = ""; to = this._compFields.to || ""; @@ +379,5 @@ > + this._msgSend = msgSend; > + this._isNewsDelivery = isNewsDelivery; > +} > + > +MsgDeliveryListener.prototype = { It's nice to add a jsdoc comment for stuff like this /** @implements {nsIUrlListener} */
Attachment #9167256 - Flags: review?(mkmelin+mozilla)
Attached patch [landed] 1211292-part0.patch (deleted) — — Splinter Review

Updates according to comment 23.

  • use Components.Exception
  • Remove the a prefix from argument names
Attachment #9167197 - Attachment is obsolete: true
Attachment #9167197 - Flags: review?(Pidgeot18)
Attachment #9167781 - Flags: review?(mkmelin+mozilla)
Attachment #9167781 - Flags: review?(Pidgeot18)
Attached patch 1211292-part1.patch (obsolete) (deleted) — — Splinter Review

Updates according to comment 24.

file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);

Can use OS.Constants.Path.tmpDir and use the OS.File (or OS.Path) operators to get the file path. I don't think it needs to be created, you should just call writeAtomic directly.

The purpose is to create a unique file, similar to https://searchfox.org/comm-central/source/mailnews/compose/src/nsMsgCompUtils.cpp#84. I guess it's safer this way to avoid two TB instances operating the same file? I will change if you think it's unnecessary. I also changed to use OS.File.openUnique, which seems more efficient than nsIFile.createUnique.

Attachment #9167256 - Attachment is obsolete: true
Attachment #9167256 - Flags: review?(Pidgeot18)
Attachment #9167792 - Flags: review?(mkmelin+mozilla)
Attachment #9167792 - Flags: review?(Pidgeot18)
Attachment #9167781 - Flags: review?(mkmelin+mozilla) → review+
Comment on attachment 9167792 [details] [diff] [review] 1211292-part1.patch Review of attachment 9167792 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MessageSend.jsm @@ +69,5 @@ > + aMode, > + aMsgToReplace, > + aListener, > + aStatusFeedback, > + aPassword please fix the a-naming @@ +126,5 @@ > + }, > + > + get folderUri() { > + throw Components.Exception( > + "folderUri not implemented", folderUri getter not implemented @@ +133,5 @@ > + }, > + > + get messageKey() { > + throw Components.Exception( > + "messageKey not implemented", getter @@ +297,5 @@ > + .msgGenerateMessageId(this._userIdentity); > + } > + let headers = new Map([ > + ["Message-Id", messageId], > + ["Date", new Date()], this is not quite the mail date formatting @@ +328,5 @@ > + delsp, > + formatOutput, > + disallowBreaks > + ) { > + let wrapWidth = Services.prefs.getIntPref("mailnews.wraplength") || 72; I think this is supposed to be let wrapWidth = Services.prefs.getIntPref("mailnews.wraplength", 72); For no pref you'd otherwise throw. But we do always have a pref...
Attachment #9167792 - Flags: review?(mkmelin+mozilla) → feedback+
Attached patch 1211292-part1.patch (obsolete) (deleted) — — Splinter Review

Updates according to comment 27.

+        .msgGenerateMessageId(this._userIdentity);
+    }
+    let headers = new Map([
+      ["Message-Id", messageId],
+      ["Date", new Date()],

this is not quite the mail date formatting

My understanding is jsmime will handle the date formatting. https://searchfox.org/comm-central/source/mailnews/mime/jsmime/jsmime.js#3464

Attachment #9167792 - Attachment is obsolete: true
Attachment #9167792 - Flags: review?(Pidgeot18)
Attachment #9168345 - Flags: review?(mkmelin+mozilla)
Attachment #9168345 - Flags: review?(Pidgeot18)
Attached patch 1211292-part1.1.patch (obsolete) (deleted) — — Splinter Review

This patch adds a class MimeEncoder, that mainly provides two functions

  • pickEncoding: determine the Content-Transfer-Encoding value
  • encode: apply base64 or quoted-printable encoding to the body of MimePart

MimeEncoder will also be used to handle attachments in the next patch.

Attachment #9168346 - Flags: review?(mkmelin+mozilla)
Attachment #9168346 - Flags: review?(Pidgeot18)

(In reply to Ping Chen (:rnons) from comment #22)

I think I understand the problem better now, it's not that JS can't represent 8-bit non-UTF-8 chars, but it becomes incorrect after converting back and forth, especially TextEncoder only supports UTF-8. So this patch stores 8-bit string in MimePart directly to avoid converting. I think string is more suitable than Uint8Array, because there is no .concat method of Uint8Array, I will need to use something like new Uint8Array([...a, ...b]) to combine two arrays. I also need to convert headers and part separator to Uint8Array. The code will be difficult to read and easy to make mistakes.

No. Thinking about the problem as concatenation--string or Uint8Array--is the wrong way to think about the problem. What you're doing is streaming a series of bytes to an output. This output may itself be a transformer, for example someone who is converting it into base64 or quoted-printable text, or maybe someone streaming it into an encryption algorithm to support encrypted messages. Or the target may simply be an output file. Or maybe you're piping it directly into a network connection. In any case, at no point would you be directly concatenating bytes together. As a bonus, at no point do you need to hold an entire copy of the message in memory.

The more I work on this bug, the more details I know. I don't worry about the design, I worry about all kinds of cases.

I worry about all kinds of cases, including weird edge conditions. And that's why I worry about the design: I need to see that the design handles all of the cases--and in lack of a design, I at least need confidence that you are at least worrying about all of them too. But I don't have that confidence here, and your replies have so far not been giving me confidence.

Let me start by apologizing for the length of this comment. It is a wall of text, but I don't think I can express what needs to be expressed without making it a wall of text. Please read all of it before moving to respond to any part.

I have concerns about the design of the rewrite that you are proposing. Getting the design right from the beginning is crucial; bad design has a way of leaking through APIs and spreading through the codebase. Furthermore, people and projects are usually extremely reluctant to tackle fixing bad design: it's easier to build yet another workaround rather than taking the time to rip out bad design.

An example of bad design in mailnews code that has infected beyond its initial APIs is that we store unparsed, rather than parsed, headers in our database, and so all the clients must parse them, which opens up inconsistencies if they don't use the correct API (and it's not easy to do so!).

In rewriting the code, we have a great opportunity to fix the design, especially since we can look back onto the existing code and evaluate design decisions that worked and those that didn't work. We do not need to rely on guesses as to how well the design will scale to future uses, when the uses are pretty well-known.

From my perspective, when I see your code, I have no prior experience to draw on to estimate how good you are at planning for future design. All I can do is extrapolate from the code you have written and from your replies to my review comments. If I were satisfied that you understood the design issues and could come up with a good design, I would not be pushing for a more formal design.

To be blunt: I did not like your design from the first patch, and your comments since then have only strengthened that assessment. Without confidence that you will be able to carry through this process through the end, it is hard for me to justify the immense time sink of thoroughly checking the existing code to understand what is and isn't being regressed.

Let's talk issues in more detail now. I'm not sure I've covered all of the issues in this list, but here they are as they cross my mind:

  • API design. As I mentioned in the beginning of this bug, the current interface of nsIMsgSend is a bad interface design and needs to be replaced with something better. While the new interface does need to be the last step of a very long sequence of patches, it is a good idea to design to a new interface from the beginning. To that end, I expect to see the code implemented in a way that is not so strictly tied to needing to support the current API. I'd actually expect much of the work to exist outside of a MessageSend class.

  • Blind reimplementation of existing code. This is a problem I've seen more with your later patches: there appears to be some copying of decisions from existing C++ code that is wrong and needs to be fixed, rewrite or not. An example here is the use of charsets influencing the type of encoding to use... just no. Now this doesn't mean that there isn't wisdom in existing code that can and should be ported, but it does require understanding the code, understanding why it's doing what it's doing, and then deciding if those reasons are still valid.

  • Using strings for binary data. I realize that JS has really poor facilities for handling binary data, especially compared to its string libraries. But my historical experience is that using (nominally) Unicode strings for binary data is a route that only leads to problems down the line. I've mentioned this in my previous comment, but the way you get around array concatenation is by changing the API so that you never need to actually concatenate arrays. Indeed, even having to do a concatenation of any kind in this context is a code smell.

  • Strings in other contexts. You're using a string to represent content-type, including parameters instead of a data structure that will serialize to the string value, but gives the necessary parameters in an easier format. Considering that you're already going to have to worry about RFC 2231 encoding parameters and writing that out anyways, it makes sense to convey other content-type parameters in a similar manner. In general, strings are bad data types, and you should only convert to strings at the last possible moment.

  • File size and method size. I have a personal distaste for large files--I think code is optimally sized at no more than 1000-ish lines of code per file. So when code starts greatly exceeding that size, I would want to see it broken up into smaller pieces. I envision there being roughly three major stages here at the end: a low-level MIME assembly file, a low-level actual sending phase, and an orchestration component that mediates between the input parameters and the low-level phases. I suggest you separate these phases into separate JS files.

  • Error handling. JS code is pretty notorious for causing errors to be thrown in unexpected places, much more so than our C++ code. The direct implementation of nsIMsgSend needs to have a relatively thin intermediary layer that will catch and propagate errors as needed to the listeners, else things like tests tend to hang without any clear indication why.

  • Tests. Our current test coverage is actually kind of crappy, especially when it comes to ensuring corner cases are handled correctly. I'd expect any rewrite to be adding a lot more tests. I'd also actually expect some of the existing tests to have to be rewritten, because they have a nasty habit of being too specific to particular details that need not be kept (e.g., line break locations in encoded messages).

Now let's talk about next steps.

I don't want to see any more code for the moment; I'm not happy with the current design, and I want to make sure that we can agree on how the code should work before you spend more time pursuing a design that will only be rejected.

You've mentioned that you worry about all kind of cases. I think the best next step for us to take is to put together a list of all the existing features, features to add in the future, and features to remove from the current implementation. To that end, I'm attaching an incomplete list that should give you a starting point. Note that the list is incomplete: it's entirely based on what I can recall from memory after 15 minutes or so, and it has been quite some time since I went through compose code with a fine-toothed comb.

After that, the next step would be to put together what nsIMsgSend should look like if we weren't constrained to implement the current interface. This is the main essence of why we are rewriting the code: if we are not going to improve on what exists, then rewriting is just a waste of time that will introduce yet more bugs and regressions into our codebase.

As part of that, we also need to understand what new interfaces we should add to reduce code duplication. There are a few other places where we build MIME messages, and adding a new interface to support those use cases is a good idea to me.

Only once those steps are completed do I believe it is worth taking the time to actually look at the implementation of code.

Attached file Incomplete list of compose features (deleted) —
Attached patch 1211292-part1.1.patch (obsolete) (deleted) — — Splinter Review

In addition to comment 29, this patch:

  • split out MimePart and MimeEncoder to separate files
  • add a write method to MimePart so no need to hold the whole message in memory

Blind reimplementation of existing code. This is a problem I've seen more with your later patches: there appears to be some copying of decisions from existing C++ code that is wrong and needs to be fixed, rewrite or not. An example here is the use of charsets influencing the type of encoding to use... just no. Now this doesn't mean that there isn't wisdom in existing code that can and should be ported, but it does require understanding the code, understanding why it's doing what it's doing, and then deciding if those reasons are still valid.

I didn't know converting the existing C++ code to JS would be a problem, I still don't what's the problem of using charset to pick encoding, I will think about it. In the meantime, it seems safer for me to just copy the behavior of existing C++ code.

Using strings for binary data.

I changed to write out immediately, instead of holding the whole message in memory, is that better?

Strings in other contexts. You're using a string to represent content-type, including parameters instead of a data structure that will serialize to the string value, but gives the necessary parameters in an easier format. Considering that you're already going to have to worry about RFC 2231 encoding parameters and writing that out anyways, it makes sense to convey other content-type parameters in a similar manner. In general, strings are bad data types, and you should only convert to strings at the last possible moment.

Shouldn't this be handled by jsmime?

File size and method size.

MimePart and MimeEncoder are split into separated files in this patch.

I don't want to see any more code for the moment; I'm not happy with the current design, and I want to make sure that we can agree on how the code should work before you spend more time pursuing a design that will only be rejected.

Please tell me the problems of the current design so I can fix. I don't have enough knowledge to discuss with you on the big picture. The current approaching of rewriting bit by bit seems to work best for me.

After that, the next step would be to put together what nsIMsgSend should look like if we weren't constrained to implement the current interface. This is the main essence of why we are rewriting the code: if we are not going to improve on what exists, then rewriting is just a waste of time that will introduce yet more bugs and regressions into our codebase.

I think it's an improvement if by rewriting, the data flow becomes cleaner, the code becomes easier to make changes. I believe many of the problems you pointed out (e.g. charset used in pickEncoding) can be fixed in a very local place. And I'm thinking about enabling some tests in the next two patches. I'd like to know what are the critical design flaws of MessageSend, MimePart and MimeEncoder.

Attachment #9168346 - Attachment is obsolete: true
Attachment #9168346 - Flags: review?(mkmelin+mozilla)
Attachment #9168346 - Flags: review?(Pidgeot18)
Attachment #9168400 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9168345 [details] [diff] [review] 1211292-part1.patch Review of attachment 9168345 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MessageSend.jsm @@ +84,5 @@ > + Cr.NS_ERROR_NOT_IMPLEMENTED > + ); > + }, > + > + getPartForDomIndex(aDomIndex) { nit: a-naming @@ +411,5 @@ > + > + OnStopRunningUrl(url, exitCode) { > + if (url) { > + let mailUrl = url.QueryInterface(Ci.nsIMsgMailNewsUrl); > + if (mailUrl) { I don't think you need to worry about null url, nor that it wouldn't QI properly. @@ +454,5 @@ > + } > + > + /** > + * _headers will be passed to jsmime, and jsmime requires header content to be > + * an array. seems this comment is not for the function but for the set call below @@ +463,5 @@ > + > + /** > + * Set headers by an iterable. The entries argument can be a Map. > + */ > + setHeaders(entries) { Would be nice to add type documentations as you go. * @param{Iterable.<string, string>} entries - The header entries. @@ +523,5 @@ > + * suitable to pass to OS.File. > + */ > + async toArrayView() { > + let string = await this.toString(); > + // Use TextEncoder.encode would be incorrect here since the argument is not not a
Attachment #9168345 - Flags: review?(mkmelin+mozilla) → feedback+
Comment on attachment 9168400 [details] [diff] [review] 1211292-part1.1.patch Review of attachment 9168400 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MimeEncoder.jsm @@ +45,5 @@ > + return this._encoding; > + } > + > + // Use the combination of charset, content type and scanning this._chunk to > + // decide what encoding it should have. please add jsdoc documentation /** * */ @@ +247,5 @@ > + const typesWhichAreReallyText = [ > + "application/mac-binhex40" /* APPLICATION_BINHEX */, > + "application/pgp" /* APPLICATION_PGP */, > + "application/pgp-keys", > + "application/x-pgp-message" /* APPLICATION_PGP2 */, please use // comments instead. That way it's easier to comment out blocks with /* */ during debugging
Attachment #9168400 - Flags: review?(mkmelin+mozilla) → feedback+

(In reply to Joshua Cranmer [:jcranmer] from comment #31)
Clearly the original API isn't ideal, but if we not kind of copy things fairly directly, making the both implementations live side by side until we're further down the road doesn't seem very possible.

(In reply to Magnus Melin [:mkmelin] from comment #36)

(In reply to Joshua Cranmer [:jcranmer] from comment #31)
Clearly the original API isn't ideal, but if we not kind of copy things fairly directly, making the both implementations live side by side until we're further down the road doesn't seem very possible.

Implementing the current API in terms of a new API is likely quite possible; I've done in that in prior rewrite attempts with no issues whatsoever.

Attached patch [landed] 1211292-part1.patch (deleted) — — Splinter Review

Updates according to comment 34. Added jsdoc docs to MimePart, will add more in the future.

Attachment #9168345 - Attachment is obsolete: true
Attachment #9168345 - Flags: review?(Pidgeot18)
Attachment #9168655 - Flags: review?(mkmelin+mozilla)
Attached patch [landed] 1211292-part1.1.patch (deleted) — — Splinter Review

Updates according to comment 35.

Attachment #9168400 - Attachment is obsolete: true
Attachment #9168662 - Flags: review?(mkmelin+mozilla)

(In reply to Joshua Cranmer [:jcranmer] from comment #31)

Now let's talk about next steps.

I don't want to see any more code for the moment; I'm not happy with the current design, and I want to make sure that we can agree on how the code should work before you spend more time pursuing a design that will only be rejected.

You've mentioned that you worry about all kind of cases. I think the best next step for us to take is to put together a list of all the existing features, features to add in the future, and features to remove from the current implementation. To that end, I'm attaching an incomplete list that should give you a starting point. Note that the list is incomplete: it's entirely based on what I can recall from memory after 15 minutes or so, and it has been quite some time since I went through compose code with a fine-toothed comb.

After that, the next step would be to put together what nsIMsgSend should look like if we weren't constrained to implement the current interface. This is the main essence of why we are rewriting the code: if we are not going to improve on what exists, then rewriting is just a waste of time that will introduce yet more bugs and regressions into our codebase.

As part of that, we also need to understand what new interfaces we should add to reduce code duplication. There are a few other places where we build MIME messages, and adding a new interface to support those use cases is a good idea to me.

Only once those steps are completed do I believe it is worth taking the time to actually look at the implementation of code.

Sorry I'm not convinced this is the only and best way to approach this task. It seems to me most of my code are not directly related to nsIMsgSend, for those that do, they are for the purpose of connecting with the existing code, so I can send/receive emails for testing. If it helps, I can split the current MessageSend to say class MsgSend and class MessageSend, MsgSend implements nsIMsgSend and MessageSend will be a new class with new interfaces. They might still be tightly coupled and limited by nsIMsgSend at first.

Why can't we fix problems one by one? For example, I added a write method to replace toString yesterday, is it better now? Or can we delay it and fix other problems first?

Attached patch 1211292-part1.2.patch (obsolete) (deleted) — — Splinter Review

As mentioned in comment 40, this patch splits out a class MimeMessage from MessageSend.jsm. MessageSend is the class that implements nsIMsgSend, MimeMessage has more flexibility. And in long terms, we may want to replace nsIMsgSend with MimeMessage.

Attachment #9168679 - Flags: review?(mkmelin+mozilla)
Attachment #9167781 - Flags: review?(Pidgeot18)
Attachment #9168655 - Flags: review?(mkmelin+mozilla) → review+
Comment on attachment 9168662 [details] [diff] [review] [landed] 1211292-part1.1.patch Review of attachment 9168662 [details] [diff] [review]: ----------------------------------------------------------------- Looks reasonable
Attachment #9168662 - Flags: review?(mkmelin+mozilla) → review+

Will land these for easier iteration in tree

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/587fde4c6cd0
Init MsgSendModuleLoader and MessageSend to prepare for rewriting nsMsgSend. r=mkmelin
https://hg.mozilla.org/comm-central/rev/6c4eb4fc78a3
Partial support of sending text message in MessageSend.jsm. r=mkmelin
https://hg.mozilla.org/comm-central/rev/2253cbe6d5e5
Add MimeEncoder class to pick encoding and encode message in MessageSend.jsm. r=mkmelin DONTBUILD

Status: ASSIGNED → RESOLVED
Closed: 4 years ago
Resolution: --- → FIXED
Comment on attachment 9168679 [details] [diff] [review] 1211292-part1.2.patch Review of attachment 9168679 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MimeMessage.jsm @@ +5,5 @@ > +let { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); > +let { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); > +let { MimePart } = ChromeUtils.import("resource:///modules/MimePart.jsm"); > + > +const EXPORTED_SYMBOLS = ["MimeMessage"]; I think the export should be first in the file @@ +20,5 @@ > + */ > +class MimeMessage { > + /** > + * Construct a MimeMessage. > + * @param{nsIMsgIdentity} userIdentity please add space after @param for things like this
Attachment #9168679 - Flags: review?(mkmelin+mozilla) → review+
Status: RESOLVED → REOPENED
Keywords: leave-open
Resolution: FIXED → ---
Target Milestone: --- → 81 Branch
Status: REOPENED → ASSIGNED
Whiteboard: [patchlove]
Attachment #9167781 - Attachment description: 1211292-part0.patch → [landed] 1211292-part0.patch
Attachment #9168655 - Attachment description: 1211292-part1.patch → [landed] 1211292-part1.patch
Attachment #9168662 - Attachment description: 1211292-part1.1.patch → [landed] 1211292-part1.1.patch
Attached patch [landed] 1211292-part1.2.patch (deleted) — — Splinter Review

Updates according to comment 45:

  • Move EXPORTED_SYMBOLS to the top
  • Fix @param comment
Attachment #9168679 - Attachment is obsolete: true
Attachment #9169783 - Flags: review+
Attached patch 1211292-part2.patch (obsolete) (deleted) — — Splinter Review

This patch supports sending local attachments.

I've made some progress on the configs to run a test for both nsMsgSend.cpp and MessageSend.jsm, but seems most tests rely on the Draft folder, which MessageSend.jsm doesn't support yet. So I will work on that in the next patch, and remove charset and delsp according to bug 862292.

Attachment #9169784 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9169784 [details] [diff] [review] 1211292-part2.patch Review of attachment 9169784 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MimeMessage.jsm @@ +62,3 @@ > > + if (attachmentParts.length) { > + // Use multipart/mixed as long as there is attachment. > 1 here too (for consistency) @@ +65,5 @@ > + topPart.initMultipart("mixed"); > + if (mainParts.length > 1) { > + // Wrap mainParts inside a multipart/alternative MimePart. > + let alternativePart = new MimePart(); > + alternativePart.initMultipart("alternative"); Maybe MimeMultiPart should be a subclass of MimePart, and the "init" would happen automatically/passed in to the constructor? @@ +66,5 @@ > + if (mainParts.length > 1) { > + // Wrap mainParts inside a multipart/alternative MimePart. > + let alternativePart = new MimePart(); > + alternativePart.initMultipart("alternative"); > + mainParts.forEach(p => alternativePart.addPart(p)); probably worth adding an addParts method that takes the arrray or single part. This will be a common case. @@ +116,5 @@ > return headers; > } > > /** > + * Determine if the message should include a HTML part, a PLAIN part or both. an HTML, lower case plain @@ +217,5 @@ > + } > + > + /** > + * Collect local attachments. > + * @returns {Array.<MimePart>} {MimePart[]} is more standard in js I think. ::: mailnews/compose/src/MimePart.jsm @@ +11,5 @@ > * Because ACString is 8-bit string, non-ASCII character takes multiple bytes. > * For example, 世界 is represented as \xE4\xB8\x96\xE7\x95\x8C. This function > * converts ACString to ArrayBuffer, which can then be passed to a TextDecoder > * or OS.File.write. > + * @param {string} str str - the string to convert to an ArrayBuffer @@ +101,5 @@ > this._bodyText = text; > } > > /** > + * @type {nsIMsgAttachment} @param {nsIMsgAttachment} attachmet - The attachment to use as body. ? @@ +129,5 @@ > } > > /** > + * Fetch the attachment file to get its content type and content. > + * @returns {string} might want to return ArrayBuffer and convert if needed later. @@ +235,5 @@ > + > + /** > + * Use nsIMimeConverter to encode header parameter according to RFC 2047. > + * @param {string} name > + * @param {string} value please document what they are, even if it's rather obvious
Attachment #9169784 - Flags: review?(mkmelin+mozilla) → review+
Attached patch [landed] 1211292-part2.patch (deleted) — — Splinter Review

Updates according to comment 48.

Maybe MimeMultiPart should be a subclass of MimePart, and the "init" would happen automatically/passed in to the constructor?

Good suggestion, I think this approach can be applied to attachment part and main part as well. So there will be classes like MimePartBase, MimeMultiPart, MimeMainPart and MimeAttachmentPart. I will play with this idea when handling cloud attachment. Thanks.

Attachment #9169784 - Attachment is obsolete: true
Attachment #9170302 - Flags: review+

I'll add a Cu.importGlobalProperties(["fetch"]); to MimePart.jsm make eslint happy

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/d204b1fe63f9
Split out MimeMessage class from MessageSend.jsm. r=mkmelin
https://hg.mozilla.org/comm-central/rev/def8329e7a26
Support local attachments in MessageSend.jsm. r=mkmelin

Attached patch 1211292-part3.patch (obsolete) (deleted) — — Splinter Review
  • Remove charset handling following bug 862292
  • Support saving to the Draft folder
  • Run test_messageHeaders against both nsMsgSend.cpp and MessageSend.jsm

Try link: https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&revision=652af3ff64bb8b9cf69ecc74ced94f6bd65cf1c3&selectedTaskRun=NOqXr335T46gD6szqBQIIQ.0

I'm aware the handling of bcc/fcc is incomplete in this patch, I will improve it later by enabling more tests or adding new tests if necessary.

Attachment #9172331 - Flags: review?(mkmelin+mozilla)
Attachment #9169783 - Attachment description: 1211292-part1.2.patch → [landed] 1211292-part1.2.patch
Attachment #9170302 - Attachment description: 1211292-part2.patch → [landed] 1211292-part2.patch
Comment on attachment 9172331 [details] [diff] [review] 1211292-part3.patch Review of attachment 9172331 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/public/nsIMsgCopy.idl @@ +18,5 @@ > + * > + * @param aUserIdentity The identity of the sender > + * @param aFile The message file > + * @param aMode The deliver mode > + * @param aMsgSendObj The nsIMsgSend instance that listens to copy events Or is this really the nsIUrlListener? @@ +26,5 @@ > + void startCopyOperation(in nsIMsgIdentity aUserIdentity, > + in nsIFile aFile, > + in nsMsgDeliverMode aMode, > + in nsIMsgSend aMsgSendObj, > + in string aSavePref, string is ascii, so would be better to upgrade this to UTF8String ::: mailnews/compose/src/MessageSend.jsm @@ +239,5 @@ > }, > > /** > * Send a message file to smtp service. Far from complete. > * TODO: handle cc/bcc. Other details. bcc now handled? ::: mailnews/compose/src/MimeEncoder.jsm @@ +112,5 @@ > + let isCharsetMultiByte = false; > + try { > + isCharsetMultiByte = > + manager.getCharsetData(this._charset, ".isMultibyte") == "true"; > + } catch {} But charset is always UTF-8 now isn't it, so is this needed? And what about the below? ::: mailnews/compose/src/MimeMessage.jsm @@ +169,5 @@ > + .set("FCC", this._fcc); > + } > + > + if (messageId) { > + /** is for function documentation. Inside functions, please use // @@ +175,5 @@ > + * in the message in order to > + * coorelate the MDN reports to the original message. Here will be > + * the right place > + */ > + let { rrt, dnt } = MsgUtils.getMDNHeaders( I would split up getMDNHeaders into two functions instead: getMDNHeader and getDSNHeader @@ +184,5 @@ > + .set("Disposition-Notification-To", dnt) > + .set("Return-Receipt-To", rrt); > + } > + > + MsgUtils.getDefaultCustomHeaders(this._userIdentity).forEach( would use for ({ headerName, headerValue } of MsgUtils.getDefaultCustomHeaders(this._userIdentity)) @@ +332,3 @@ > let attachments = [...this._compFields.attachments]; > + let cloudParts = []; > + let localParts = []; Why not just parts? Is there difference in the final ordering that you want? ::: mailnews/compose/src/MimeMessageUtils.jsm @@ +2,5 @@ > + * 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/. */ > + > +var EXPORTED_SYMBOLS = ["MsgUtils"]; > + please use const for the export @@ +54,5 @@ > + getDefaultCustomHeaders(userIdentity) { > + // mail.identity.<id#>.headers pref is a comma separated value of pref names > + // containing headers to add headers are stored in > + let headerAttributes = userIdentity.getCharAttribute("headers").split(","); > + let ret = []; name it headers perhaps? @@ +55,5 @@ > + // mail.identity.<id#>.headers pref is a comma separated value of pref names > + // containing headers to add headers are stored in > + let headerAttributes = userIdentity.getCharAttribute("headers").split(","); > + let ret = []; > + headerAttributes.forEach(attr => { for (let attr of userIdentity.getCharAttribute("headers").split(",")) { ... and you could just continue with a filtering and mapping too if you like @@ +60,5 @@ > + // mail.identity.<id#>.header.<header name> grab all the headers > + let attrValue = userIdentity.getCharAttribute(`header.${attr}`); > + if (attrValue) { > + let [headerName, headerValue] = userIdentity > + .getCharAttribute(`header.${attr}`) attrValue @@ +61,5 @@ > + let attrValue = userIdentity.getCharAttribute(`header.${attr}`); > + if (attrValue) { > + let [headerName, headerValue] = userIdentity > + .getCharAttribute(`header.${attr}`) > + .split(":"); some trimming needed? @@ +83,5 @@ > + getFcc(userIdentity, compFields, originalMsgURI, compType) { > + // If the identity pref "fcc" is set to false, then we will not do > + // any FCC operation! > + let fcc = ""; > + if (userIdentity.doFcc) { do an early return @@ +117,5 @@ > + ) { > + let messenger = Cc["@mozilla.org/messenger;1"].createInstance( > + Ci.nsIMessenger > + ); > + let msgHdr = messenger MailServices.messenger. @@ +188,5 @@ > + * @returns {string} > + */ > + getMailFollowupToHeader(compFields, userIdentity) { > + let mailLists = userIdentity.getCharAttribute("subscribed_mailing_lists"); > + if (mailLists && (compFields.to || compFields.cc)) { just do an early return here too if there's nothing to be done when this is not set @@ +222,5 @@ > + getMailReplyToHeader(compFields, userIdentity) { > + let mailLists = userIdentity.getCharAttribute( > + "replyto_mangling_mailing_lists" > + ); > + if (mailLists && mailLists[0] != "*" && (compFields.to || compFields.cc)) { early return @@ +373,5 @@ > + > + /** > + * Get the value of References and In-Reply-To header. > + * @param {string} references - Raw References header content > + * @returns {{references: string, inReplyTo: string}} Probably best to still keep these too as separate functions. @@ +404,5 @@ > + /** > + * Get the value of Newsgroups and X-Mozilla-News-Host header. > + * @param {nsMsgDeliverMode} deliverMode - Message deliver mode > + * @param {string} newsgroups - Raw newsgroups header content > + * @returns {{newsgroupsHeaderVal: string, newshostHeaderVal: string}} Not sure you want to return objects like this in general. When you need to get both values, you may want to return an array of values and use it like let [ newsgroups, xMozillaNewsHost] = getNewsgroups() @@ +448,5 @@ > + if ( > + !baseUrl.includes(":") || > + baseUrl.startsWith("news:") || > + baseUrl.startsWith("snews:") || > + baseUrl.startsWith("IMAP:") || why this and shouldn't this all be case insensitive? @@ +466,5 @@ > + " ": "%20", > + "\t": "%09", > + "\n": "%0A", > + "\r": "%0D", > + }; Where to these come from? Please add a code comment. Why not just encodeURI? @@ +469,5 @@ > + "\r": "%0D", > + }; > + for (let i = 0; i < length; i++) { > + let char = baseUrl[i]; > + if (col >= 38) { What is this about. Also, just use baseUrl.length directly @@ +476,5 @@ > + } > + > + value += transformMap[char] || char; > + } > + return `"${value}"`; seems odd to return a quoted string. Better have the caller handle quoting @@ +540,5 @@ > + let dupParamValue = jsmime.mimeutils.typedArrayToString( > + encoder.encode(paramValue) > + ); > + > + if (/[\x80-\xff]/.exec(dupParamValue)) { prefer test() over exec() if you're not using the matches @@ +542,5 @@ > + ); > + > + if (/[\x80-\xff]/.exec(dupParamValue)) { > + needsEscape = true; > + dupParamValue = Services.io.escapeString( encodeURIComponent? @@ +626,5 @@ > + }, > + > + /** > + * Get the target message folder to copy to. > + * @param {nsIMsgIdentity} userIdentity - The user identity add period.
Attachment #9172331 - Flags: review?(mkmelin+mozilla)
Attached patch 1211292-part3.patch (obsolete) (deleted) — — Splinter Review
+   * @param aMsgSendObj   The nsIMsgSend instance that listens to copy events`

Or is this really the nsIUrlListener?

I believe it's nsIMsgSend, they are tightly coupled.
https://searchfox.org/comm-central/source/mailnews/compose/src/nsMsgCopy.cpp#120
https://searchfox.org/comm-central/source/mailnews/compose/src/nsMsgCopy.cpp#58,64,70

+      try {
+        isCharsetMultiByte =
+          manager.getCharsetData(this._charset, ".isMultibyte") == "true";
+      } catch {}

But charset is always UTF-8 now isn't it, so is this needed?

I think for text/* attachments, we still need to use pickCharset to detect the charset, and pass it to the MimeEncoder.

+    let cloudParts = [];
+    let localParts = [];

Why not just parts? Is there difference in the final ordering that you want?

Added inline comments. The C++ version handles cloud attachments first, so the tests expect cloudParts first. https://searchfox.org/comm-central/source/mailnews/compose/test/unit/test_messageHeaders.js#523,549

+      "\n": "%0A",
+      "\r": "%0D",
+    };

Where to these come from? Please add a code comment. Why not just encodeURI?

From https://searchfox.org/comm-central/source/mailnews/compose/src/nsMsgCompUtils.cpp#729-736. Related test is here https://searchfox.org/comm-central/source/mailnews/compose/test/unit/test_messageHeaders.js#434-435. encodeURI will do more than needed, < will become %3C

+      if (col >= 38) {

What is this about.

According to https://tools.ietf.org/html/rfc2557#section-4.4.2, the folding *can be* done, using the algorithm defined in [URLBODY](rfc2017) section 3.1.. And RFC2017 says URL-word Must not exceed 40 characters in length. But RFC2557 says can be, not must be, so I removed the 40 chars limit, and leave it to be folded by jsmime.

+      value += transformMap[char] || char;
+    }
+    return `"${value}"`;

seems odd to return a quoted string.

Agreed, I also found bug 151031. I don't find quote is required in rfc2557, so I removed it and updated tests.

+    if (/[\x80-\xff]/.exec(dupParamValue)) {
+      needsEscape = true;
+      dupParamValue = Services.io.escapeString(

encodeURIComponent?

In this case, encodeURIComponent doesn't do as much as Services.io.escapeString, the related test is https://searchfox.org/comm-central/source/mailnews/compose/test/unit/test_messageHeaders.js#415-417, notice .txt is percent encoded as well. I guess when actually sending mail, encodeURIComponent should work as well, but then I would need to change the C++ and tests code.

It seems if two headers are closely related, get them from the same util function can save some code. There is this function https://searchfox.org/comm-central/source/mailnews/compose/src/nsMsgCompUtils.cpp#313-315 that gets two header values at the same time. To split them, I would need to write the same code twice.

I generally prefer {x, y} = func() than [x, y] = func(), I feel object keys are more stable than array index. I need to be more careful about the orders both when returning array and destructuring the array.

Attachment #9172331 - Attachment is obsolete: true
Attachment #9172570 - Flags: review?(mkmelin+mozilla)

Perhaps we can remove Content-Base and only keep Content-Location? According to https://tools.ietf.org/html/rfc2557#section-2.2

Content-Base This header was specified in RFC 2110, but has been removed in this new version of the MHTML standard.

Attached patch 1211292-part3.patch (obsolete) (deleted) — — Splinter Review
  • Remove Content-Base and only keep Content-Location
  • Break down two functions to return one header only
  • Rename helper functions for X-header** to getXHeader**
  • Fix a few jsdocs
Attachment #9172570 - Attachment is obsolete: true
Attachment #9172570 - Flags: review?(mkmelin+mozilla)
Attachment #9173812 - Flags: review?(mkmelin+mozilla)
Attached patch 1211292-part3.1.patch (obsolete) (deleted) — — Splinter Review
  • Enable test_saveDraft.js by implementing getXMozillaStatus and getXMozillaStatus2
  • Enable test_detectAttachmentCharset.js by implementing pickCharset
  • Enable a few other tests without changing code
Attachment #9173814 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9173812 [details] [diff] [review] 1211292-part3.patch Review of attachment 9173812 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MimeMessage.jsm @@ +238,5 @@ > + // Don't reset "newsgroups" header if newsgroups is undefined. > + if (newsgroups) { > + headers.set("newsgroups", newsgroups); > + } > + headers.set("X-Mozilla-News-Host", newshost); should this be lower-case? @@ +253,1 @@ > let formatFlowed = Services.prefs.getBoolPref( I'd just check this in the if clause. And rename formatParams to format (it's only ever one now) ::: mailnews/compose/src/MimeMessageUtils.jsm @@ +13,5 @@ > + > +/** > + * Collection of helper functions for message sending process. All functions > + * here are expected to be pure functions, they take arguments and return > + * values, but never change arguments or anything directly. I think that is the general expectation everywhere, almost ;) In js relying on side effects (out params changning) is really considered bad style. So not sure we need to document it here. @@ +145,5 @@ > + * with some servers. > + * > + * @param {nsIMsgCompFields} compFields - The compose fields. > + * @param {boolean} isDraft - The deliver mode is taken as draft. > + * @returns {string} Could document the return value with an example @@ +156,5 @@ > + if ( > + !hasDisclosedRecipient && > + (!isDraft || this._deliverMode == Ci.nsIMsgSend.nsMsgQueueForLater) > + ) { > + if ( Could do one or even two early returns here @@ +502,5 @@ > + return ""; > + }, > + > + // Given a string, convert it to 'qtext' (quoted text) for RFC822 header > + // purposes. Please use jsdoc style comments (/** */) here ::: mailnews/compose/test/unit/head_compose_jsm.js @@ +1,5 @@ > +/* Any copyright is dedicated to the Public Domain. > + * http://creativecommons.org/publicdomain/zero/1.0/ */ > + > +/** > + * Set mailnews.send.jsmodule to true, so that test suite will be ran against run
Attachment #9173812 - Flags: review?(mkmelin+mozilla) → review+
Comment on attachment 9173812 [details] [diff] [review] 1211292-part3.patch Review of attachment 9173812 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/base/public/nsIMsgIdentity.idl @@ +252,1 @@ > void setCharAttribute(in string name, in ACString value); Hmm, I think you'd then also need to update the setter to take AUTF8String...
Comment on attachment 9173814 [details] [diff] [review] 1211292-part3.1.patch Review of attachment 9173814 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MessageSend.jsm @@ +241,5 @@ > let msgCopy = Cc["@mozilla.org/messengercompose/msgcopy;1"].createInstance( > Ci.nsIMsgCopy > ); > + let copyFile = file; > + if (folderUri.startsWith("mailbox")) { mailbox: ::: mailnews/compose/src/MimePart.jsm @@ +161,5 @@ > > /** > * Recursively write a MimePart and its parts to a file. > * @param {OS.File} file - The output file to contain a RFC2045 message. > + * @param {number} depth - Nested level of a part, depth 0 means a top part. The default value is documented like this @param {number] [depth=0] - Nested level of a part.
Attachment #9173814 - Flags: review?(mkmelin+mozilla) → review+
Attached patch [landed] 1211292-part3.patch (deleted) — — Splinter Review
+      headers.set("X-Mozilla-News-Host", newshost);

should this be lower-case?

To avoid confusion and duplicated header, I changed all header names to lowercase.

     let formatFlowed = Services.prefs.getBoolPref(

I'd just check this in the if clause.
And rename formatParams to format (it's only ever one now)

formatFlowed is used again a few lines below. I renamed formatParams to formatParam.

   void setCharAttribute(in string name, in ACString value);

Hmm, I think you'd then also need to update the setter to take AUTF8String...

I reverted this change, found that I can use getUnicharAttribute instead, https://searchfox.org/comm-central/source/mailnews/base/public/nsIMsgIdentity.idl#248-252. I guess one function is enough for getUnicharAttribute and getCharAttribute, but I'm not going to touch it in this patch.

Attachment #9173812 - Attachment is obsolete: true
Attachment #9174149 - Flags: review+
Attached patch [landed] 1211292-part3.1.patch (deleted) — — Splinter Review

Updates to comment 60.

Attachment #9173814 - Attachment is obsolete: true
Attachment #9174150 - Flags: review+
Attached patch [landed] 1211292-part3.2.patch (deleted) — — Splinter Review
  • Enable test_attachment by rfc2047 encoding the name, while rfc2311 encoding the filename param
  • Enable some other tests without changing code

There seems to be three modes of mail.strictly_mime.parm_folding https://searchfox.org/comm-central/source/mailnews/compose/test/unit/test_attachment.js#47-51. Is RFC 2047 without folding still relevant? Seems jsmime will always fold, to make the test passes I only kept RFC2047WithCRLF and RFC2231 in this patch.

Attachment #9174159 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9174159 [details] [diff] [review] [landed] 1211292-part3.2.patch Review of attachment 9174159 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/test/unit/test_attachment.js @@ +19,1 @@ > "`abcdefghijklmnopqrstuvwxyz{|}~.txt"; what are these changes about?

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/41824ba64913
Enable test_messageHeaders for MessageSend.jsm. r=mkmelin
https://hg.mozilla.org/comm-central/rev/5fafcb896ef7
Enable test_saveDraft and test_detectAttachmentCharset for MessageSend.jsm. r=mkmelin DONTBUILD

Attachment #9174149 - Attachment description: 1211292-part3.patch → [landed] 1211292-part3.patch
Attachment #9174150 - Attachment description: 1211292-part3.1.patch → [landed] 1211292-part3.1.patch

(In reply to Magnus Melin [:mkmelin] from comment #64)

::: mailnews/compose/test/unit/test_attachment.js
@@ +19,1 @@

"`abcdefghijklmnopqrstuvwxyz{|}~.txt";

what are these changes about?

The problem is the leading whitespace in the filename (whitespace inside might trigger similar problem as well). With
var input1 = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_",
jsmime will format it to

Content-Disposition: attachment; filename*0="
 !\\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ";

While the C++ will format it to

Content-Disposition: attachment;
 filename*0=" !\\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ";

jsmime split header value with space first, then try to fit a line word by word. I don't see an easy fix to this, one solution might be doing rfc2311 encoding inside jsmime, but that would be a big change.

Comment on attachment 9174159 [details] [diff] [review] [landed] 1211292-part3.2.patch Review of attachment 9174159 [details] [diff] [review]: ----------------------------------------------------------------- LGTM.
Attachment #9174159 - Flags: review?(mkmelin+mozilla) → review+

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/0922c9a141ad
Enable test_attachment for MessageSend.jsm. r=mkmelin

Attachment #9174159 - Attachment description: 1211292-part3.2.patch → [landed] 1211292-part3.2.patch
Attached patch 1211292-part3.3.patch (obsolete) (deleted) — — Splinter Review
  • Enable test_sendBackground by implementing sendMessageFile
  • Enable test_sendMessageLater3 by transforming some NS_ERRORs
  • Enable some other tests
Attachment #9174580 - Flags: review?(mkmelin+mozilla)

In test_sendBackground.js, I had to use two nsIMsgSend instances, because sendMessageFile is an async function in the JS implementation. If sendMessageFile is immediately called twice on the same nsIMsgSend instance, the second call will overwrite the internal state of the first call.

Comment on attachment 9174580 [details] [diff] [review] 1211292-part3.3.patch Review of attachment 9174580 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MessageSend.jsm @@ +190,5 @@ > + let newExitCode = exitCode; > + switch (exitCode) { > + case Cr.NS_ERROR_UNKNOWN_HOST: > + case Cr.NS_ERROR_UNKNOWN_PROXY_HOST: > + newExitCode = MsgUtils.generateNSError(12589); What are the magic numbers? If there's no defined constant accessible, add a code comment about what it refers to. @@ +295,5 @@ > ); > + // Add a `From - Date` line, so that nsLocalMailFolder.cpp won't add a > + // dummy envelope. > + await fileWriter.write( > + new TextEncoder().encode(`From - ${new Date().toUTCString()}\r\n`) This date format is not exactly what we use I believe. But I don't know if we really use it for anything either. @@ +327,5 @@ > + this, > + folderUri, > + this._msgToReplace > + ); > + } catch {} What are we try catching? At least a Components.reportError() is probably in order.
Attachment #9174580 - Flags: review?(mkmelin+mozilla)
Attached patch [landed] 1211292-part3.3.patch (deleted) — — Splinter Review
+        newExitCode = MsgUtils.generateNSError(12589);

What are the magic numbers? If there's no defined constant accessible, add a code comment about what it refers to.

Changed to newExitCode = MsgUtils.NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER; for better readability.

+        new TextEncoder().encode(`From - ${new Date().toUTCString()}\r\n`)

This date format is not exactly what we use I believe. But I don't know if we really use it for anything either.

The date string is designed to be parsed by PR_ParseTimeString, which supports many formats, and I tested it can parse toUTCString(). On the other hand, I think there is a bug in nsParseMailbox.cpp. This envelope line is not parsed correctly, and the date is never used.

Attachment #9174580 - Attachment is obsolete: true
Attachment #9174836 - Flags: review?(mkmelin+mozilla)
Attached patch 1211292-part3.4.patch (obsolete) (deleted) — — Splinter Review
  • Enable test_sendMessageFile by calling notifyListenerOnStopCopy at the right moment
  • Enable test_autoReply by using writeBinaryString for MimePart
  • Enable other tests, only five tests left in -cpp.ini now

I have to use two empty catch blocks in this patch. In C++,

  if (mListener != nullptr)
    mListener->OnStopSending(aMsgID, aStatus, aMsg, returnFile);
  return NS_OK;

Non zero return of OnStopSending is not a problem, since it's ignored. However, in JS,

    if (this._sendListener) {
        this._sendListener.onStopSending(msgId, status, msg, returnFile);

Non zero return of onStopSending will become an exception after going through XPConnect, to ignore the exception, I use try with an empty catch block.

Attachment #9174838 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9174836 [details] [diff] [review] [landed] 1211292-part3.3.patch Review of attachment 9174836 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MessageSend.jsm @@ +119,5 @@ > + this.sendReport.deliveryMode = deliverMode; > + this._setStatusMessage( > + this._composeBundle.GetStringFromName("assemblingMailInformation") > + ); > + this.sendReport.currentProcess = Ci.nsIMsgSendReport.process_BuildMessage; Nasty naming of those process constants :( Maybe we should do another patch to uppercase them all properly.
Attachment #9174836 - Flags: review?(mkmelin+mozilla) → review+
Comment on attachment 9174838 [details] [diff] [review] 1211292-part3.4.patch Review of attachment 9174838 [details] [diff] [review]: ----------------------------------------------------------------- r=mkmelin with the below ::: mailnews/compose/src/MessageSend.jsm @@ +188,5 @@ > notifyListenerOnStopSending(msgId, status, msg, returnFile) { > if (this._sendListener) { > + try { > + this._sendListener.onStopSending(msgId, status, msg, returnFile); > + } catch { We usually still use catch (e) { @@ +335,5 @@ > this, > folderUri, > this._msgToReplace > ); > + } catch { here too
Attachment #9174838 - Flags: review?(mkmelin+mozilla) → review+

(In reply to Ping Chen (:rnons) from comment #73)

    if (this._sendListener) {
        this._sendListener.onStopSending(msgId, status, msg, returnFile);

Non zero return of onStopSending will become an exception after going through XPConnect, to ignore the exception, I use try with an empty catch block.

Generally with listeners, it is a good idea to report exceptions to the console if they occur. Then they can be more easily tracked rather than the exception getting hidden. If there are onStopSending implementations that are permanently returning non-zero, then maybe they need to be investigated?

Yes, maybe better to not swallow errors too easily.

Attached patch [landed] 1211292-part3.4.patch (deleted) — — Splinter Review

Print non-zero return of listeners to console, this only happens in tests so far, but we will see.

Attachment #9174838 - Attachment is obsolete: true
Attachment #9175076 - Flags: review+
Attached patch 1211292-part3.5.patch (obsolete) (deleted) — — Splinter Review
  • Enable test_sendMailAddressIDN.js by encoding mail address before passing to SMTP service
  • Enable test_temporaryFilesRemoved.js by reverting back to nsIFile.createUnique. OS.File.openUnique will get a random hash name, which is difficult to test.

All tests except one in mailnews/compose/test/unit have been enabled for MessageSend.jsm. Will see if I can do the same to the mail/test/browser/composition folder.

Attachment #9175140 - Flags: review?(mkmelin+mozilla)

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/8fb018ed386e
Enable test_sendBackground and test_sendMessageLater for MessageSend.jsm. r=mkmelin
https://hg.mozilla.org/comm-central/rev/3c083ed603d8
Enable test_sendMessageFile and test_autoReply for MessageSend.jsm. r=mkmelin DONTBUILD

Attachment #9174836 - Attachment description: 1211292-part3.3.patch → [landed] 1211292-part3.3.patch
Attachment #9175076 - Attachment description: 1211292-part3.4.patch → [landed] 1211292-part3.4.patch
Attached patch 1211292-part4.patch (obsolete) (deleted) — — Splinter Review
  • Setup configs to support running mail/test/browser/composition against MessageSend.jsm
  • Enable browser_drafts by implementing get folderUri
  • Enable some other tests

Many tests in mail/test/browser/composition are not directly related to message sending, but I'm going to enable them anyway.

Because the sending process is async, in browser_drafts, I had to add a sleep(1) to test_open_draft_again, so that saving to draft can finish before press_delete. Without the sleep, the press_delete happens first, and then saving to draft will create a new message in the draft folder, which breaks later tests. I don't know why I don't need to add sleep before every press_delete.

Attachment #9175468 - Flags: review?(mkmelin+mozilla)
Attachment #9175468 - Flags: review?(mkmelin+mozilla) → review+
Comment on attachment 9175140 [details] [diff] [review] 1211292-part3.5.patch Review of attachment 9175140 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MessageSend.jsm @@ +129,5 @@ > > // nsMsgKey_None from MailNewsTypes.h. > this._messageKey = 0xffffffff; > > + // In order throw any nserror, _deliverMessage can't be declared async. Grammar error here. Not sure what it's suppose to read @@ +306,5 @@ > ); > let copyFile = this._messageFile; > if (folderUri.startsWith("mailbox:")) { > + copyFile = Services.dirsvc.get("TmpD", Ci.nsIFile); > + copyFile.append("nscopy.tmp"); Seems backwards to revert this just for testing purposes. I think it would be better to adjust the test (maybe just count files, if names are not stable.)
Attachment #9175140 - Flags: review?(mkmelin+mozilla)
Attached patch 1211292-part3.5.patch (obsolete) (deleted) — — Splinter Review
+    // In order throw any nserror, _deliverMessage can't be declared async.

Grammar error here. Not sure what it's suppose to read

https://searchfox.org/comm-central/source/mailnews/compose/test/unit/test_sendMailAddressIDN.js#128,150 expects to catch exception thrown by sendMessageFile, with async _deliverMessage(){}, the exception won't be caught in test. Remove the async only for one test seems not ideal and I didn't find a workaround, so I changed to disable two cases in test_sendMailAddressIDN.js for MessageSend for now.

+      copyFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
+      copyFile.append("nscopy.tmp");

Seems backwards to revert this just for testing purposes. I think it would be better to adjust the test (maybe just count files, if names are not stable.)

Updated to count files.

Attachment #9175140 - Attachment is obsolete: true
Attachment #9175710 - Flags: review?(mkmelin+mozilla)
Attached patch [landed] 1211292-part4.patch (deleted) — — Splinter Review

Turns out sleep(1) is unreliable as the tests failed on Try, after looking into browser_drafts again, found that the following code appears in almost every test and is reliable.

  utils.waitFor(
    () => !cwc.window.gSaveOperationInProgress && !cwc.window.gWindowLock,
    "Saving of draft did not finish"
  );

So I moved it into head.js and used it after SaveAsDraft.

Attachment #9175468 - Attachment is obsolete: true
Attachment #9175711 - Flags: review+
Comment on attachment 9175710 [details] [diff] [review] 1211292-part3.5.patch Review of attachment 9175710 [details] [diff] [review]: ----------------------------------------------------------------- Looks pretty good. ::: mailnews/compose/test/unit/test_sendMailAddressIDN.js @@ +188,5 @@ > test = kToValid; > DoSendTest(kToValid, kToValidACE, 0); > > + // Because sendMessageFile is an async function in MessageSend.jsm, exception > + // can't be caught by DoSendTest. Could you change this test file to split up the 4 cases to use add_task instead. Then everything is checked async and the test could work?
Attachment #9175710 - Flags: review?(mkmelin+mozilla)
Attached patch 1211292-part3.6.patch (obsolete) (deleted) — — Splinter Review
+  // Because sendMessageFile is an async function in MessageSend.jsm, exception
+  // can't be caught by DoSendTest.

Could you change this test file to split up the 4 cases to use add_task instead. Then everything is checked async and the test could work?

My comment was a bit misleading, the problem was sendMessageFile is sync according to nsIMsgSend.idl, but when an exception is thrown by an async function called inside sendMessageFile, the exception can't be caught by tests.

This patch updates nsIMsgSend.idl to return Promise from sendMessageFile, then uses add_task in test_sendMailAddressIDN.js. I put this in a separate patch because this is my first time using Promise in xpidl, I worry it may break something unexpected. A Try run seems fine: https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&revision=4cd447b8aa83465a957c81d49c01f6

Attachment #9175978 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9175978 [details] [diff] [review] 1211292-part3.6.patch Review of attachment 9175978 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/public/nsIMsgSend.idl @@ +286,5 @@ > + in string aAccountKey, > + in nsIMsgCompFields aFields, > + in nsIFile aSendIFile, > + in boolean aDeleteSendFileOnCompletion, > + in boolean aDigest_p, while we're touching this, please remove the _p. ::: mailnews/compose/src/nsMsgSendLater.cpp @@ +504,5 @@ > true, // bool deleteSendFileOnCompletion, > false, // bool digest_p, > nsIMsgSend::nsMsgSendUnsent, // nsMsgDeliverMode mode, > nullptr, // nsIMsgDBHdr *msgToReplace, > + sendListener, mFeedback, nullptr, getter_AddRefs(promise)); I do think this is basically misuse of c++ promise. But if it works maybe we don't care, especially as this code will be going away once we're done. I mean, you're not rejecting the promise on errors. This is now c++ side not an async function really, but instead a sync function and since an error will cause an exception it kind of works out? A more correct use would proably involve adding some implicit_jscontext. ::: mailnews/compose/test/unit/test_sendMailAddressIDN.js @@ +211,2 @@ > ); > + run_next_test(); The test needs some rebasing. Can we avoid the old-style run_next_test and such and just do the add_task top level?
Attachment #9175978 - Flags: review?(mkmelin+mozilla)
Attached patch [landed] 1211292-part3.5.patch (deleted) — — Splinter Review

Due to bug 1665652, I moved test_sendMailAddressIDN.js back to xpcshell-cpp.ini, so it's not ran against MessageSend and I can move on.

Attachment #9175710 - Attachment is obsolete: true
Attachment #9176450 - Flags: review?(mkmelin+mozilla)

Comment on attachment 9175978 [details] [diff] [review]
1211292-part3.6.patch

The original purpose of this patch is to make test_sendMailAddressIDN.js work with MessageSend.jsm. This patch has its value by returning Promise in xpidl, but then I also need to update test_sendMailAddressIDN.js to use add_task. I will re-submit this patch after bug 1665652.

Attachment #9175978 - Attachment is obsolete: true
Comment on attachment 9176450 [details] [diff] [review] [landed] 1211292-part3.5.patch Review of attachment 9176450 [details] [diff] [review]: ----------------------------------------------------------------- LGTM, r=mkmelin
Attachment #9176450 - Flags: review?(mkmelin+mozilla) → review+

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/bf07498ab5ca
Init mochitest configs and enable browser_drafts for MessageSend.jsm. r=mkmelin
https://hg.mozilla.org/comm-central/rev/160781d72107
Enable test_temporaryFilesRemoved for MessageSend.jsm. r=mkmelin

Attachment #9176450 - Attachment description: 1211292-part3.5.patch → [landed] 1211292-part3.5.patch
Attachment #9175711 - Attachment description: 1211292-part4.patch → [landed] 1211292-part4.patch
Attached patch 1211292-part4.1.patch (obsolete) (deleted) — — Splinter Review
  • Enabled browser_attachmentReminder.js by implementing the error reporting when sending failed
  • Enabled browser_emlActions.js by putting messageServiceFromURI in a a try..catch block

Try run: https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&revision=b9a73d412e09fbf2963f3e5edd13559e43604a78

Attachment #9176832 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9176832 [details] [diff] [review] 1211292-part4.1.patch Review of attachment 9176832 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MessageSend.jsm @@ +176,5 @@ > + }, > + > + fail(exitCode, errorMsg) { > + let prompt = this.getDefaultPrompt(); > + if (exitCode & 0x80000000 && prompt) { Please document what the 0x80000000 is about. Maybe best to declare it as a constant somewhere ::: mailnews/compose/src/MimeMessageUtils.jsm @@ +190,5 @@ > + let msgHdr = Cc["@mozilla.org/messenger;1"] > + .createInstance(Ci.nsIMessenger) > + .messageServiceFromURI(originalMsgURI) > + .messageURIToMsgHdr(originalMsgURI); > + let folder = msgHdr.folder; Instead of try/catch. To handle .eml messages, you should just check if folder is set. If it's not, it's a standalone .eml @@ +961,5 @@ > + let errorsService = Cc["@mozilla.org/nss_errors_service;1"].getService( > + Ci.nsINSSErrorsService > + ); > + try { > + // This is a server security issue as determined by the Mozilla I think you should first check if this is the case, that is check isNSSErrorCode
Attachment #9176832 - Flags: review?(mkmelin+mozilla)
Attached patch 1211292-part4.1.patch (obsolete) (deleted) — — Splinter Review

Please document what the 0x80000000 is about.
Maybe best to declare it as a constant somewhere

Added NS_FAILED helper to MsgUtils.

+          let msgHdr = Cc["@mozilla.org/messenger;1"]
+            .createInstance(Ci.nsIMessenger)
+            .messageServiceFromURI(originalMsgURI)
+            .messageURIToMsgHdr(originalMsgURI);
+          let folder = msgHdr.folder;

Instead of try/catch. To handle .eml messages, you should just check if folder is set. If it's not, it's a standalone .eml

The exception is thrown by messageServiceFromURI before I can check .folder, I moved code out of the try block to make it clearer.

Attachment #9176832 - Attachment is obsolete: true
Attachment #9177034 - Flags: review?(mkmelin+mozilla)
Attached patch 1211292-part4.1.patch (obsolete) (deleted) — — Splinter Review

Add newline to the end of .ini files.

Attachment #9177034 - Attachment is obsolete: true
Attachment #9177034 - Flags: review?(mkmelin+mozilla)
Attachment #9177079 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9177079 [details] [diff] [review] 1211292-part4.1.patch Review of attachment 9177079 [details] [diff] [review]: ----------------------------------------------------------------- Looks pretty good. A few comments below ::: mailnews/compose/src/MimeMessageUtils.jsm @@ +101,5 @@ > + * @returns {boolean} > + */ > + NS_SUCCEEDED(exitCode) { > + return !this.NS_FAILED(exitCode); > + }, Instead of these two, can we just use ```Components.isSuccessCode()```? @@ +211,5 @@ > + .messageServiceFromURI(originalMsgURI) > + .messageURIToMsgHdr(originalMsgURI); > + } catch (e) { > + console.warn( > + `messageServiceFromURI failed with 0x${e.result.toString(16)}\n${ would include the originalMsgURI.spec in the message so we can understand why. Maybe just use Cu.reportError(e) though, like Cu.reportError(`Couldn't get hdr for uri=${originalMsgURI.spec}: ${e}`); @@ +218,5 @@ > + ); > + } > + if (msgHdr) { > + let folder = msgHdr.folder; > + let incomingServerType = would just inline this
Attachment #9177079 - Flags: review?(mkmelin+mozilla) → review+
Attached patch [landed] 1211292-part4.1.patch (deleted) — — Splinter Review

Instead of these two, can we just use Components.isSuccessCode()?

Thanks, didn't know about isSuccessCode.

Maybe just use Cu.reportError(e) though, like

I feel it can't be considered an error here, so I used console.warn

Attachment #9177079 - Attachment is obsolete: true
Attachment #9177261 - Flags: review+

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/13b7004235b7
Enable browser_attachmentReminder and browser_emlActions for MessageSend.jsm. r=mkmelin

Attachment #9177261 - Attachment description: 1211292-part4.1.patch → [landed] 1211292-part4.1.patch
Attached patch 1211292-part4.2.patch (obsolete) (deleted) — — Splinter Review
Attachment #9177337 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9177337 [details] [diff] [review] 1211292-part4.2.patch Review of attachment 9177337 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MimeMessage.jsm @@ +42,5 @@ > bodyText, > deliverMode, > originalMsgURI, > + compType, > + editor Can we avoid passing in an editor. Seems more than a bit wrong... It shouldn't really have anything to do with MimeMessage @@ +286,5 @@ > let htmlPart = null; > let plainPart = null; > let parts = []; > > + // this._bodyText is 8-bit string. is a @@ +308,5 @@ > + } > + bodyText = cs.scanHTML(bodyText, csFlags); > + } > + > + // Convert UTF-16 string back to byte string. You're all in js here. Is this needed? Looks to me it would happen automatically? @@ +403,5 @@ > } > + > + /** > + * Collect embedded objects as attachments. > + * @returns {Array.<MimePart>} I think {MimePart[]} is preferable @@ +412,5 @@ > + } > + let nodes = []; > + nodes.push(...this._editor.document.querySelectorAll("img")); > + nodes.push(...this._editor.document.querySelectorAll("a")); > + for (let body of this._editor.document.querySelectorAll("body")) { I don't think there's more than one body..? So you can probably just querySelector("body[background]"); ::: mailnews/compose/src/MimeMessageUtils.jsm @@ +1037,5 @@ > + } > + } > + // Take the part after the last / or \. > + let lastSlash = url.lastIndexOf("\\"); > + if (lastSlash == "-1") { -1 as integer ::: mailnews/compose/src/MimePart.jsm @@ +87,5 @@ > /** > + * Set an attachment as body, with optional contentDisposition and contentId. > + * @param {nsIMsgAttachment} attachment - The attachment to use as body. > + * @param {string} contentDisposition - Usually "attachment" or "inline". > + * @param {string} contentId - An id for embedded object. So the cid:something url? Could be worth pointing that out
Attachment #9177337 - Flags: review?(mkmelin+mozilla)
Attached patch 1211292-part4.2.patch (obsolete) (deleted) — — Splinter Review

Can we avoid passing in an editor. Seems more than a bit wrong... It shouldn't really have anything to do with MimeMessage

Good suggestion, I moved all interaction with editor to MessageSend.jsm and passed embeddedAttachments to MimeMessage.

+      // Convert UTF-16 string back to byte string.

You're all in js here. Is this needed? Looks to me it would happen automatically?

I added more comments. The reason is the body argument of createAndSendMessage is ACString. Also 8-bit string is used in MimeEncoder to pick encoding and do encoding. And when we fetch an attachment, we get the content as 8-bit string. So we handle message body text the same way as attachment content.

Attachment #9177337 - Attachment is obsolete: true
Attachment #9177807 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9177807 [details] [diff] [review] 1211292-part4.2.patch Review of attachment 9177807 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MessageSend.jsm @@ +432,5 @@ > > _cleanup() { > if (this._copyFile && this._copyFile != this._messageFile) { > + // On Windows, nsIFile.remove often fails with NS_ERROR_FILE_IS_LOCKED. > + OS.File.remove(this._copyFile.path); There are some test failures on treeherder related to this. Is this going to clear those up? @@ +494,5 @@ > + if (body) { > + nodes.push(body); > + } > + > + let urlCidMap = {}; Maybe use use a real Map() ::: mailnews/compose/src/MimeMessage.jsm @@ +34,4 @@ > * @param {nsMsgDeliverMode} deliverMode > * @param {string} originalMsgURI > * @param {MSG_ComposeType} compType > */ Please document embeddedAttachments ::: mailnews/compose/src/MimePart.jsm @@ +87,5 @@ > /** > + * Set an attachment as body, with optional contentDisposition and contentId. > + * @param {nsIMsgAttachment} attachment - The attachment to use as body. > + * @param {string} contentDisposition - Usually "attachment" or "inline". > + * @param {string} contentId - The url of an embedded object is cid:contentId. Please document that these are optional, like @param {string} [contentDisposition=attachment] @param {string} [contentId]
Attachment #9177807 - Flags: review?(mkmelin+mozilla)
Attached patch [landed] 1211292-part4.2.patch (deleted) — — Splinter Review

There are some test failures on treeherder related to this. Is this going to clear those up?

Yes, except for test_autoReply. Did a Try run earlier today, https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&revision=849e0e6adcf8f363d1717538a3375c&selectedTaskRun=MDfxv4S0Q0G-UcWeueIIsQ.0 only test_autoReply failed in win10 debug build.

Maybe use use a real Map()

Plain object seems more light weight to me, I renamed it to urlCidCache

Attachment #9177807 - Attachment is obsolete: true
Attachment #9177829 - Flags: review?(mkmelin+mozilla)
Attached patch [landed] 1211292-part4.3.patch (deleted) — — Splinter Review
  • Support forwarding as attachment
  • Enable browser_forwardHeaders.js and other forwarding tests.

Try run: https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&selectedTaskRun=D9t94zvyQaSaDQEhUqNkPw.0&revision=b83339f8aa9f4ed9f9d2e985113b705f79815194
The lint error already fixed.

Attachment #9177830 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9177829 [details] [diff] [review] [landed] 1211292-part4.2.patch Review of attachment 9177829 [details] [diff] [review]: ----------------------------------------------------------------- (I'll fix the nits before landing.) ::: mailnews/compose/src/MessageSend.jsm @@ +431,5 @@ > }, > > _cleanup() { > if (this._copyFile && this._copyFile != this._messageFile) { > + // On Windows, nsIFile.remove often fails with NS_ERROR_FILE_IS_LOCKED. let's drop this comment ::: mailnews/compose/src/MimePart.jsm @@ +86,5 @@ > > /** > + * Set an attachment as body, with optional contentDisposition and contentId. > + * @param {nsIMsgAttachment} attachment - The attachment to use as body. > + * @param {string} [contentDisposition] - Usually "attachment" or "inline". [contentDisposition=attachment] .. to document the default value
Attachment #9177829 - Flags: review?(mkmelin+mozilla) → review+
Comment on attachment 9177830 [details] [diff] [review] [landed] 1211292-part4.3.patch Review of attachment 9177830 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MimePart.jsm @@ +129,5 @@ > + Cc["@mozilla.org/messenger;1"] > + .createInstance(Ci.nsIMessenger) > + .messageServiceFromURI(this._bodyAttachment.url) > + .GetUrlForUri(this._bodyAttachment.url, outUri, null); > + url = outUri.value.spec; oh man, filed bug 1667338...
Attachment #9177830 - Flags: review?(mkmelin+mozilla) → review+
Pushed by mkmelin@iki.fi: https://hg.mozilla.org/comm-central/rev/50afdb66a6dc Support embedded images and enable browser_blockedContent for MessageSend.jsm. r=mkmelin https://hg.mozilla.org/comm-central/rev/71dd975bf7aa Support forwarding as attachment in MessageSend.jsm. r=mkmelin
Attachment #9177829 - Attachment description: 1211292-part4.2.patch → [landed] 1211292-part4.2.patch
Attachment #9177830 - Attachment description: 1211292-part4.3.patch → [landed] 1211292-part4.3.patch
Attached patch [landed] 1211292-part4.4.patch (deleted) — — Splinter Review
  • Add attribute AUTF8String contentId to nsIMsgAttachment and fix forwarding as attachment
  • Enable remaining composition mochitests
  • Enable test_sendMailAddressIDN.js

Will work on cases not covered by existing tests: like collecting addresses to address book, or sending to newsgroup.

Attachment #9178141 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9178141 [details] [diff] [review] [landed] 1211292-part4.4.patch Review of attachment 9178141 [details] [diff] [review]: ----------------------------------------------------------------- LGTM, r=mkmelin
Attachment #9178141 - Flags: review?(mkmelin+mozilla) → review+

Pushed by geoff@darktrojan.net:
https://hg.mozilla.org/comm-central/rev/f4e236a8735f
Enable remaining composition mochitests for MessageSend.jsm. r=mkmelin

Attachment #9178141 - Attachment description: 1211292-part4.4.patch → [landed] 1211292-part4.4.patch
Attached patch 1211292-part5.patch (obsolete) (deleted) — — Splinter Review

A small patch

  • collects outgoing addresses to address book
  • shows status message in each step
  • passes mail.sanitize_date_header pref value to emitStructuredHeaders

Found that when copy operation failed, need to show a dialog with retry button. And need to FilterSentMessage when copy succeeded, will work on them in the next patch.

Attachment #9178340 - Flags: review?(mkmelin+mozilla)

Not sure why, but the most recent patch that landed has increased the amount of memory leaked dramatically. 40% in the case I looked at. It's not because we're running more tests, all of the added tests are run separately. Log of a task that ran the C++ version. Log of a task that ran the JS version.

Now that the JS version of this code is running all of the tests, that test run is also reporting more memory leaked than our threshold.

Attached patch [landed] 1211292-part5.patch (deleted) — — Splinter Review

Folder name can be empty, fix unit tests.

Attachment #9178340 - Attachment is obsolete: true
Attachment #9178340 - Flags: review?(mkmelin+mozilla)
Attachment #9178385 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9178385 [details] [diff] [review] [landed] 1211292-part5.patch Review of attachment 9178385 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MessageSend.jsm @@ +475,4 @@ > let converter = Cc["@mozilla.org/messenger/mimeconverter;1"].getService( > Ci.nsIMimeConverter > ); > + let encodedRecipients = encodeURIComponent( Total nonsense - we need to clean this up this API later. It should be easer then.
Attachment #9178385 - Flags: review?(mkmelin+mozilla) → review+

(In reply to Geoff Lankow (:darktrojan) from comment #112)

Not sure why, but the most recent patch that landed has increased the amount of memory leaked dramatically. 40% in the case I looked at.

Ping: please check that patch. It's very non-obvious what it could have caused a leak so probably need to take the patch apart and send a few try runs to compare, until you find the right spot.

Attached patch [landed] 1211292-part4.5.patch (deleted) — — Splinter Review

Disable three mochitests to fix leak failure. These three tests were already unstable when run with nsMsgSend.cpp, each of them has an intermittent bug. I guess running them twice just make things worse. Two Try runs:
https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&revision=b13873efcc1f21d8717b76d2305826aefc07c0bf
https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&revision=4be3aa999b5a62d20e1d16fec62e990af7e7eb7f (not finished yet)

Will enable debug mode on local and investigate one by one.

Attachment #9178719 - Flags: review?(mkmelin+mozilla)
Attachment #9178719 - Flags: review?(mkmelin+mozilla) → review+

Pushed by geoff@darktrojan.net:
https://hg.mozilla.org/comm-central/rev/405b4dc4dfcd
Disable four tests for MessageSend.jsm. r=mkmelin
https://hg.mozilla.org/comm-central/rev/03fd007540eb
Collect outgoing addresses to address book in MessageSend.jsm. r=mkmelin

Attachment #9178385 - Attachment description: 1211292-part5.patch → [landed] 1211292-part5.patch
Attachment #9178719 - Attachment description: 1211292-part4.5.patch → [landed] 1211292-part4.5.patch
Attached patch 1211292-part5.1.patch (obsolete) (deleted) — — Splinter Review
  • Support retrying on copy failure, manually tested
  • Support fcc2 field, add test_fcc2.js
Attachment #9179189 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9179189 [details] [diff] [review] 1211292-part5.1.patch Review of attachment 9179189 [details] [diff] [review]: ----------------------------------------------------------------- LGTM, r=mkmelin ::: mailnews/compose/src/MessageSend.jsm @@ +566,5 @@ > this._msgToReplace > ); > } catch (e) { > + if (throwOnError) { > + Components.Exception("startCopyOperation failed", e.result); throw Components.Exception (and no return needed then)
Attachment #9179189 - Flags: review?(mkmelin+mozilla) → review+
Attached patch [landed] 1211292-part5.1.patch (deleted) — — Splinter Review

Rebase and use throw Components.Exception.

Attachment #9179189 - Attachment is obsolete: true
Attachment #9179491 - Flags: review+
Attached patch [landed] 1211292-part5.2.patch (deleted) — — Splinter Review
  • Run filters after sending
  • Support sending to newsgroup

Will start working on encryption.

Attachment #9179507 - Flags: review?(mkmelin+mozilla)

Pushed by geoff@darktrojan.net:
https://hg.mozilla.org/comm-central/rev/30ca8975fad5
Support retrying on copy failure and support fcc2 in MessageSend.jsm. r=mkmelin

Attachment #9179491 - Attachment description: 1211292-part5.1.patch → [landed] 1211292-part5.1.patch
Comment on attachment 9179507 [details] [diff] [review] [landed] 1211292-part5.2.patch Review of attachment 9179507 [details] [diff] [review]: ----------------------------------------------------------------- LGTM, r=mkmelin
Attachment #9179507 - Flags: review?(mkmelin+mozilla) → review+

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/69f70e9e5dd4
Run filters after sending, support sending to newsgroup in MessageSend.jsm. r=mkmelin DONTBUILD

Attachment #9179507 - Attachment description: 1211292-part5.2.patch → [landed] 1211292-part5.2.patch
Attached patch 1211292-part6.patch (obsolete) (deleted) — — Splinter Review

Support message encryption.

Attachment #9180328 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9180328 [details] [diff] [review] 1211292-part6.patch Review of attachment 9180328 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MessageSend.jsm @@ +594,5 @@ > if (this._folderUri.startsWith("mailbox:")) { > + this._copyFile = Services.dirsvc.get("TmpD", Ci.nsIFile); > + this._copyFile.append("nscopy.tmp"); > + this._copyFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); > + let fileWriter = await OS.File.open(this._copyFile.path, { write: true }); Why this change? ::: mailnews/compose/src/MimeMessage.jsm @@ +81,2 @@ > > + this._secureCompose = this._getSecureCompose(); Maybe we should make sure to rename this to _composeSecure to clarify what it is. @@ +88,5 @@ > + let beginCryptoEncapsulation = this._secureCompose > + ? () => this._beginCryptoEncapsulation(buffer) > + : null; > + await topPart.write(mimeWrite, 0, beginCryptoEncapsulation); > + this._secureCompose?.finishCryptoEncapsulation(false, this._sendReport); It would probably be clearer to instead have an if-else block for the secure compose here. @@ +90,5 @@ > + : null; > + await topPart.write(mimeWrite, 0, beginCryptoEncapsulation); > + this._secureCompose?.finishCryptoEncapsulation(false, this._sendReport); > + > + buffer.QueryInterface(Ci.nsISafeOutputStream).finish(); Why not do the QI directly after creation? ::: mailnews/compose/src/MimePart.jsm @@ +202,3 @@ > * @param {number} [depth=0] - Nested level of a part. > + * @param {function()} [beginCryptoEncapsulation] - A callback to start crypto > + * encaspsulation. nit: indent by two spaces when changing lines Also add @see nsIMsgComposeSecure.beginCryptoEncapsulation But I don't know. It doesn't seem like a good API to be passing this in. Maybe one of the classes should implement nsIMsgComposeSecure (possibly refactoring, splitting out just the relevant parts of that) so that write for that class would handle encryption automatically.
Attachment #9180328 - Flags: review?(mkmelin+mozilla)
Attached patch 1211292-part6.patch (obsolete) (deleted) — — Splinter Review

::: mailnews/compose/src/MessageSend.jsm
@@ +594,5 @@

 if (this._folderUri.startsWith("mailbox:")) {
  this._copyFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
  this._copyFile.append("nscopy.tmp");
  this._copyFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
  let fileWriter = await OS.File.open(this._copyFile.path, { write: true });

Why this change?

This change is to keep nsemail.eml and nscopy.tmp in the same folder, because OS.Constants.Path.tmpDir is not always the same as Services.dirsvc.get("TmpD". Without this change, test_temporaryFilesRemoved.js will still pass, but doesn't really test nscopy.tmp.

Services.dirsvc.get("TmpD" is used for nsemail.eml because to work with nsIMsgComposeSecure, nsIOutputStream has to be used. Previously, file hanlder from OS.File.openUnique was used to append to nsemail.eml.

But I don't know. It doesn't seem like a good API to be passing this in.
Maybe one of the classes should implement nsIMsgComposeSecure (possibly
refactoring, splitting out just the relevant parts of that) so that write
for that class would handle encryption automatically.

I agree the api is not great. But to do beginCryptoEncapsulation directly in MimePart, all the arguments of beginCryptoEncapsulation need to be passed to MimePart, which I think is worse than passing a callback. I renamed this callback to startCryptoEncapsulation to avoid confusion with nsIMsgComposeSecure.beginCryptoEncapsulation.

Attachment #9180328 - Attachment is obsolete: true
Attachment #9180622 - Flags: review?(mkmelin+mozilla)

How about creating a SecureMimeMessage class inherting from MimeMessage. SecureMimeMessage would override createMessageFile and possibly something else. The write() method only does something different for the top part anyway so maybe the logic to do encryption/signing could just be in MimeMessage.createMessageFile as well?

Attachment #9180622 - Flags: review?(mkmelin+mozilla)

The current flow is

  1. MimeMessage.createMessageFile
  2. (top)MimePart.write(Headers)
  3. MimeMessage.startCryptoEncapsulation
  4. MimePart.write (recursively write out child parts and top part body)

Because it's interleaved, I followed the C++ code to pass startCryptoEncapsulation as a callback to MimePart.

A new SecureMimeMessage class doesn't seem to help, instead I suggest moving the write function from MimePart to MimeMessage. The new flow will be

  1. MimeMessage.createMessageFile
  2. MimeMessage.writePart(topPart)

Then inside MimeMessage.writePart

  1. write top headers
  2. startCryptoEncapsulation
  3. recursively write out child parts and top part body

Will update.

Sounds good. But with this changed, then you could have a SecureMimeMessage class that would handle encrypting, right?

Attached patch [landed] 1211292-part6.patch (deleted) — — Splinter Review

Move write from MimePart to MimeMessage.

But with this changed, then you could have a SecureMimeMessage class that would handle encrypting, right?

Seems to me one MimeMessage class is enough. True there are a few if..else in createMessageFile and _writePart. But with an extra SecureMimeMessage class, I need to decide which class to use in MessageSend. I can't further split createMessageFile and _writePart to smaller reusable functions, so they will be mostly the same in MimeMessage and SecureMimeMessage.

Attachment #9180622 - Attachment is obsolete: true
Attachment #9180675 - Flags: review?(mkmelin+mozilla)
Attached patch 1211292-part6.1.patch (obsolete) (deleted) — — Splinter Review

mailnews.send.jsmodule is enabled globally in this Try run https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&revision=7f30537d6298fd19c837f9e608537a24a5d0634d, two tests failed, will fix them in the next patch.

Attachment #9180967 - Flags: review?(mkmelin+mozilla)
Attachment #9180675 - Flags: review?(mkmelin+mozilla) → review+
Comment on attachment 9180967 [details] [diff] [review] 1211292-part6.1.patch Review of attachment 9180967 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MessageSend.jsm @@ +109,5 @@ > this._messageKey = nsMsgKey_None; > + > + // nsMsgCompose will do some cleanups depending if the return value of > + // createAndSendMessage is not 0, this required createAndSendMessage to run > + // synchonously. Please add a todo code comment to clean this up later when it's easier, like // FIXME: make this work properly with async operation. @@ +533,5 @@ > + ); > + let prompt = this.getDefaultPrompt(); > + if (!prompt.confirm(null, msg)) { > + this.fail(MsgUtils.NS_ERROR_BUT_DONT_SHOW_ALERT, msg); > + throw Components.Exception("", Cr.NS_ERROR_FAILURE); Please always add a message instead of "" - it's useful for understanding later when it happens. ::: mailnews/compose/src/MimeMessageUtils.jsm @@ +1050,5 @@ > .split("#")[0]; > }, > + > + /** > + * Synchronize a promise: wait synchonously until a promise has completed and Please also note down in the documentation this is a hack, not to be used unless there's a good temporary reason.
Attachment #9180967 - Flags: review?(mkmelin+mozilla)
Attached patch 1211292-part6.1.patch (obsolete) (deleted) — — Splinter Review

Added some comments.

Hopefully I can remove syncPromise in the next patch. Learned about DomPromiseListener https://searchfox.org/mozilla-central/source/dom/promise/PromiseNativeHandler.h#49-50 today, will use it to resolve Promise in C++.

Attachment #9180967 - Attachment is obsolete: true
Attachment #9181177 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9181177 [details] [diff] [review] 1211292-part6.1.patch Review of attachment 9181177 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MessageSend.jsm @@ +973,5 @@ > + _runPromise(promise) { > + let e = MsgUtils.syncPromise(promise); > + if (e && e.result != 0) { > + this._sendReport.setError(Ci.nsIMsgSendReport.process_Current, e, false); > + throw Components.Exception("", e.result); For this too add a message instead of ""
Attachment #9181177 - Flags: review?(mkmelin+mozilla) → review+
Attached patch [landed] 1211292-part6.1.patch (deleted) — — Splinter Review

Added a message to Components.Exception

Attachment #9181177 - Attachment is obsolete: true
Attachment #9181204 - Flags: review+

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/7ff7b0021034
Support message encryption in MessageSend.jsm. r=mkmelin
https://hg.mozilla.org/comm-central/rev/eae99a0ccada
Show a warning dialog for large message file in MessageSend.jsm. r=mkmelin

Attachment #9180675 - Attachment description: 1211292-part6.patch → [landed] 1211292-part6.patch
Attachment #9181204 - Attachment description: 1211292-part6.1.patch → [landed] 1211292-part6.1.patch
Attached patch 1211292-part6.2.patch (obsolete) (deleted) — — Splinter Review

Returns Promise in nsIMsgSend and nsIMsgCompose.

(In reply to Magnus Melin [:mkmelin] from comment #87)

::: mailnews/compose/src/nsMsgSendLater.cpp
@@ +504,5 @@

   true,                         // bool deleteSendFileOnCompletion,
   false,                        // bool digest_p,
   nsIMsgSend::nsMsgSendUnsent,  // nsMsgDeliverMode mode,
   nullptr,                      // nsIMsgDBHdr *msgToReplace,
   sendListener, mFeedback, nullptr, getter_AddRefs(promise));

I do think this is basically misuse of c++ promise. But if it works maybe we
don't care, especially as this code will be going away once we're done.

I mean, you're not rejecting the promise on errors. This is now c++ side not
an async function really, but instead a sync function and since an error
will cause an exception it kind of works out? A more correct use would
proably involve adding some implicit_jscontext.

In this case, we are not passing any useful info through the promise, everything still go through listeners, so I ignored the Promise.

In this patch, only CreateAndSendMessage passes a nsresult through Promise, the other changes are to make it compile and consistent.

Attachment #9181486 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9181486 [details] [diff] [review] 1211292-part6.2.patch Review of attachment 9181486 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/public/nsIMsgCompose.idl @@ +113,5 @@ > /* ... */ > void UnregisterStateListener(in nsIMsgComposeStateListener stateListener); > > /* ... */ > + Promise SendMsg(in MSG_DeliverMode deliverMode, in nsIMsgIdentity identity, in string accountKey, in nsIMsgWindow aMsgWindow, in nsIMsgProgress progress); Let's make it sendMsg while we're at it ::: mailnews/compose/src/nsMsgCompose.cpp @@ +1189,5 @@ > m_compFields, false, false, (nsMsgDeliverMode)deliverMode, nullptr, > + m_composeHTML ? TEXT_HTML : TEXT_PLAIN, bodyString, m_window, > + mProgress, sendListener, mSmtpPassword, mOriginalMsgURI, mType, > + getter_AddRefs(promise)); > + promise.forget(aPromise); I think promise can easily be null @@ +1194,2 @@ > } else > rv = NS_ERROR_FAILURE; With a promise out, what about these rvs? @@ +1196,5 @@ > } else > rv = NS_ERROR_NOT_INITIALIZED; > > if (NS_FAILED(rv)) > NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, rv); Would this get invoked? @@ +1383,5 @@ > + } > + > + if (mProgress) mProgress->CloseProgressDialog(true); > + }); > + promise.forget(aPromise); You're not rv checking. promise could well be null @@ +2074,5 @@ > switch (replyHeaderType) { > + case 0: // No reply header at all (actually the "---- original > + // message > + // ----" string, which is kinda misleading. TODO: Should > + // there be a "really no header" option? odd folding here. I think we can remove the TODO @@ +4162,5 @@ > case nsIMsgCompType::MailToUrl: /* same as New */ > case nsIMsgCompType::Reply: /* should not happen! but just in case */ > case nsIMsgCompType::ReplyAll: /* should not happen! but just in case */ > + case nsIMsgCompType::ReplyToList: /* should not happen! but just in case > + */ there should really be // comments. Anyway, not folded like this
Attachment #9181486 - Flags: review?(mkmelin+mozilla)
Attached patch 1211292-part6.2.patch (obsolete) (deleted) — — Splinter Review
  promise.forget(aPromise);

I think promise can easily be null

I think it's fine as long as aPromise is not null, https://searchfox.org/mozilla-central/source/mfbt/RefPtr.h#270. I tested it works when promise is null.

if (NS_FAILED(rv))
  NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, rv);

Would this get invoked?

Seems SendMsgToServer is never used directly, so I moved them into SendMsg.

              // ----" string, which is kinda misleading. TODO: Should
              // there be a "really no header" option?

odd folding here. I think we can remove the TODO

Reverted unrelated changes by clang-format, don't know how it happened. I ran clang-format again and no such changes.

Attachment #9181486 - Attachment is obsolete: true
Attachment #9181716 - Flags: review?(mkmelin+mozilla)
Attachment #9181716 - Flags: review?(mkmelin+mozilla) → review+

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/5c87be1d969d
Return Promise in nsIMsgSend and nsIMsgCompose. r=mkmelin DONTBUILD

The last push breaks Windows:
0:10.71 z:/Mozilla/comm-central/comm/mailnews/mapi/mapihook/src/msgMapiHook.cpp(340,62): error: too few arguments to function call, expected 6, have 5
0:10.71 pMsgId, nullptr, nullptr, nullptr);
0:10.72 ^
0:10.72 z:/Mozilla/TB/dist/include\nsIMsgCompose.h(341,36): note: 'SendMsg' declared here
0:10.72 JS_HAZ_CAN_RUN_SCRIPT NS_IMETHOD SendMsg(MSG_DeliverMode deliverMode, nsIMsgIdentity *identity, const char * accountKey, nsIMsgWindow *aMsgWindow, nsIMsgProgress *progress, ::mozilla::dom::Promise * * _retval) = 0;

Attached patch bug1211292_sendMsgPromise.patch (obsolete) (deleted) — — Splinter Review

Does this work?

Attachment #9181748 - Flags: review?(richard.marti)

Comment on attachment 9181748 [details] [diff] [review]
bug1211292_sendMsgPromise.patch

No, I get a:
1:30.92 In file included from z:/Mozilla/comm-central/comm/mailnews/mapi/mapihook/src/msgMapiHook.cpp:10:
1:30.93 In file included from z:/Mozilla/TB/dist/include/nsCOMPtr.h:29:
1:30.93 z:/Mozilla/TB/dist/include/mozilla/RefPtr.h(50,38): error: member access into incomplete type 'mozilla::dom::Promise'
1:30.93 static void Release(U* aPtr) { aPtr->Release(); }
1:30.94 ^
1:30.94 z:/Mozilla/TB/dist/include/mozilla/RefPtr.h(381,62): note: in instantiation of member function 'mozilla::RefPtrTraits<mozilla::dom::Promise>::Release' requested here
1:30.94 static void Release(U* aPtr) { mozilla::RefPtrTraits<U>::Release(aPtr); }
1:30.94 ^
1:30.94 z:/Mozilla/TB/dist/include/mozilla/RefPtr.h(81,37): note: in instantiation of member function 'RefPtr<mozilla::dom::Promise>::ConstRemovingRefPtrTraits<mozilla::dom::Promise>::Release' requested here
1:30.94 ConstRemovingRefPtrTraits<T>::Release(mRawPtr);
1:30.94 ^
1:30.94 z:/Mozilla/comm-central/comm/mailnews/mapi/mapihook/src/msgMapiHook.cpp(339,33): note: in instantiation of member function 'RefPtr<mozilla::dom::Promise>::~RefPtr' requested here
1:30.94 RefPtr<mozilla::dom::Promise> promise;
1:30.94 ^
1:30.94 z:/Mozilla/TB/dist/include/nsrootidl.h(25,7): note: forward declaration of 'mozilla::dom::Promise'
1:30.95 class Promise;
1:30.95 ^
1:30.95 1 error generated.

Attachment #9181748 - Flags: review?(richard.marti) → review-
Attached patch [landed] 1211292-part6.2.patch (deleted) — — Splinter Review

Updated msgMapiHook.cpp to build on Windows.

+ #include "mozilla/dom/Promise.h"

+  RefPtr<mozilla::dom::Promise> promise;
   rv = pMsgCompose->SendMsg(WeAreOffline() ? nsIMsgSend::nsMsgQueueForLater
                                           : nsIMsgSend::nsMsgDeliverNow,
                            pMsgId, nullptr, nullptr, nullptr,
+                           getter_AddRefs(promise));

Try run: https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&revision=fcdb6e4f40516240aa0831f4994e65f33635e5fd

Attachment #9181716 - Attachment is obsolete: true
Attachment #9181748 - Attachment is obsolete: true
Attachment #9181768 - Flags: review?(mkmelin+mozilla)
Attachment #9181768 - Flags: review?(mkmelin+mozilla) → review+

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/592fb5c396eb
Return Promise in nsIMsgSend and nsIMsgCompose. r=mkmelin

Attachment #9181768 - Attachment description: 1211292-part6.2.patch → [landed] 1211292-part6.2.patch
Attached patch [landed] 1211292-part6.3.patch (obsolete) (deleted) — — Splinter Review
  • Fix browser_ext_compose_onBeforeSend.js when run with MessageSend.jsm
  • Fix browser_filelinkTelemetry.js when run with MessageSend.jsm
  • Remove some unused code

When MessageSend.jsm is enabled globally, all tests pass now.
https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&revision=7e31c1e4d33086117bceb578916440

Attachment #9181971 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9181971 [details] [diff] [review] [landed] 1211292-part6.3.patch Review of attachment 9181971 [details] [diff] [review]: ----------------------------------------------------------------- LGTM, r=mkmelin
Attachment #9181971 - Flags: review?(mkmelin+mozilla) → review+

Pushed by thunderbird@calypsoblue.org:
https://hg.mozilla.org/comm-central/rev/feeeaae233ae
Fix browser_ext_compose_onBeforeSend.js when run with MessageSend.jsm. r=mkmelin

Attachment #9181971 - Attachment description: 1211292-part6.3.patch → [landed] 1211292-part6.3.patch

Backed out for test failures in comm/mail/test/browser/cloudfile/browser_filelinkTelemetry.js:
https://hg.mozilla.org/comm-central/rev/92f9ece20d5775d6f42d938b330c2307b6f20b03

Attached patch 1211292-part6.3.patch (obsolete) (deleted) — — Splinter Review

Fix browser_filelinkTelemetry.js with

+  let sendPromise = BrowserTestUtils.waitForEvent(cwc.window, "aftersend");
   cwc.click(cwc.eid("button-send"));
-  await BrowserTestUtils.waitForEvent(cwc.window, "aftersend");
+  await sendPromise;
Attachment #9181971 - Attachment is obsolete: true
Attachment #9182255 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9182255 [details] [diff] [review] 1211292-part6.3.patch Review of attachment 9182255 [details] [diff] [review]: ----------------------------------------------------------------- Do you have a successful try? Use -p all to get one for all platforms ::: mail/test/browser/cloudfile/browser_filelinkTelemetry.js @@ +110,5 @@ > // Multiple big attachments should be counted as one ignoring. > add_attachments(cwc, "http://www.example.com/1", maxSize); > add_attachments(cwc, "http://www.example.com/2", maxSize + 10); > add_attachments(cwc, "http://www.example.com/3", maxSize - 1); > + let sendPromise = BrowserTestUtils.waitForEvent(cwc.window, "aftersend"); Personally I've preferred naming the promise the same as the event since it makes a very logical naming when you then "await <eventname>". Like `let aftersend = BrowserTestUtils.waitForEvent(cwc.window, "aftersend");`
Attachment #9182255 - Flags: review?(mkmelin+mozilla) → review+
Attached patch [landed] 1211292-part6.3.patch (deleted) — — Splinter Review

Renamed the promise. It passed on my local, I was confident it will pass on Try. Last time, I only tested with MessageSend.jsm enabled globally.

Pushed to Try just now, https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&revision=c31075927c9f7d7e42aaba6512c489491c151612

Attachment #9182255 - Attachment is obsolete: true
Attachment #9182271 - Flags: review+

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/a23c5e6f23cb
Fix browser_ext_compose_onBeforeSend.js when run with MessageSend.jsm. r=mkmelin DONTBUILD

Attachment #9182271 - Attachment description: 1211292-part6.3.patch → [landed] 1211292-part6.3.patch
Attached patch [landed] 1211292-part6.4.patch (deleted) — — Splinter Review

Enable test_sendMailAddressIDN.js again for MessageSend.jsm, previously it failed in debug mode.

Attachment #9182797 - Flags: review?(mkmelin+mozilla)

Please reference a successful try run for all platforms (-p all)

Attachment #9182797 - Flags: review?(mkmelin+mozilla) → review+
Pushed by mkmelin@iki.fi: https://hg.mozilla.org/comm-central/rev/1324593cec2b Enable test_sendMailAddressIDN.js for MessageSend.jsm. r=mkmelin
Attached patch 1211292-part6.5.patch (obsolete) (deleted) — — Splinter Review
  • Fix QP encoding for trailing whitespace
  • Add test_messageBody.js

Try run: https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&revision=7e3e9f10a0c4112e56606f0bbcd2d63ec36fdf32

Attachment #9183086 - Flags: review?(mkmelin+mozilla)
Attachment #9182797 - Attachment description: 1211292-part6.4.patch → [landed] 1211292-part6.4.patch
Comment on attachment 9183086 [details] [diff] [review] 1211292-part6.5.patch Review of attachment 9183086 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MimeEncoder.jsm @@ +371,1 @@ > } while here, please above and around, fix the variable name not to be "char" which is a reserved word in js. I thing "ch" or "c" would be good. ::: mailnews/compose/test/unit/test_messageBody.js @@ +13,5 @@ > +}); > + > +/** > + * Test trailing whitespace is QP encoded. > + */ Can you also add a test that contains some non-ascii, and also e.g. a few trailing whitespaces (spaces and tab for instance)
Attachment #9183086 - Flags: review?(mkmelin+mozilla)
Attached patch [landed] 1211292-part6.5.patch (deleted) — — Splinter Review
  • Rename char to ch
  • Add a test case for non-ascii text
Attachment #9183086 - Attachment is obsolete: true
Attachment #9183121 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9183121 [details] [diff] [review] [landed] 1211292-part6.5.patch Review of attachment 9183121 [details] [diff] [review]: ----------------------------------------------------------------- Thanks
Attachment #9183121 - Flags: review?(mkmelin+mozilla) → review+

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/e17e6a8f62f0
Fix QP encoding for MessageSend.jsm. r=mkmelin DONTBUILD

Attachment #9183121 - Attachment description: 1211292-part6.5.patch → [landed] 1211292-part6.5.patch
Attached patch [landed] 1211292-part7.patch (deleted) — — Splinter Review

It's about time to test MessageSend.jsm with real use. This patch enables MessageSend.jsm for nightly build, please help me land it when you see fit.

A note about the memory leak problem found in comment 112, and three mochitests disabled for MessageSend.jsm in comment comment 116.

Bytes leaked when run in local debug build.

test file cpp jsm
browser_drafts.js 24309888 24342968
browser_newmsgComposeIdentity.js 13427512 13401336
three mochi tests together 34861084 34926332

The three tests work fine when running for cpp or jsm separately. But when moving them into browser-shared.ini, Try server will report test failure caused by memory leak. I have no idea how to decrease the memory leak, I'm not going to investigate it further in this bug.

Attachment #9183387 - Flags: review?(mkmelin+mozilla)

Can parties working on this patch take a look at Bug 1673288, especially
the bug 1673288 comment 1 there?
https://bugzilla.mozilla.org/show_bug.cgi?id=1673288#c1

Attached patch 1211292-part6.6.patch (obsolete) (deleted) — — Splinter Review

Update sendDeliveryCallback and the fcc process to follow the C++ code closely.

Attachment #9184209 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9184209 [details] [diff] [review] 1211292-part6.6.patch Review of attachment 9184209 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/src/MessageSend.jsm @@ +213,5 @@ > return prompt; > }, > > fail(exitCode, errorMsg) { > + let newExitCode = exitCode; I'd just reuse the existing exitCode @@ +527,5 @@ > + this._doFcc(); > + }, > + > + sendDeliveryCallback(url, isNewsDelivery, exitCode) { > + let newExitCode = exitCode; here too, would just reuse the exitCode ::: mailnews/compose/test/unit/test_longLines.js @@ +106,5 @@ > // Note: Services.appinfo.OS returns "XPCShell" in the test, so we > // use this hacky condition to separate Windows from the others. > + if ( > + "@mozilla.org/windows-registry-key;1" in Cc || > + Services.prefs.getBoolPref("mailnews.send.jsmodule") why this change? Js send always creating CRLF? Should it? If it really should, please add a code comment
Attachment #9184209 - Flags: review?(mkmelin+mozilla)
Attached patch 1211292-part6.6.patch (obsolete) (deleted) — — Splinter Review

Renamed newExitCode to exitCode.

why this change? Js send always creating CRLF? Should it? If it really should, please add a code comment

Yes, valid mail should always use CRLF. The \n\n seems to be added by ConvertBufToPlainText, I think the current behavior is due to it's an xpcshell test. When a window instance is available, the final mail won't contain \n\n.

Try link: https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&revision=eec4888d138e7cfc163dc1301f5e67

Attachment #9184209 - Attachment is obsolete: true
Attachment #9184453 - Flags: review?(mkmelin+mozilla)
Comment on attachment 9184453 [details] [diff] [review] 1211292-part6.6.patch Review of attachment 9184453 [details] [diff] [review]: ----------------------------------------------------------------- ::: mailnews/compose/test/unit/test_longLines.js @@ +107,5 @@ > // use this hacky condition to separate Windows from the others. > + if ( > + "@mozilla.org/windows-registry-key;1" in Cc || > + // Mail body should be independant of the system. > + Services.prefs.getBoolPref("mailnews.send.jsmodule") Let's add a note: Lines in the message body are split by CRLF according to RFC 5322. For nsMsgSend.cpp on non-Windows this is not respected...
Attachment #9184453 - Flags: review?(mkmelin+mozilla) → review+
Attached patch [landed] 1211292-part6.6.patch (deleted) — — Splinter Review

Updated comment in test_longLines.js.

Attachment #9184453 - Attachment is obsolete: true
Attachment #9184459 - Flags: review+

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/9bd688d14fbd
Fix sendDeliveryCallback and doFcc in MessageSend.jsm. r=mkmelin

Attachment #9184459 - Attachment description: 1211292-part6.6.patch → [landed] 1211292-part6.6.patch
Comment on attachment 9183387 [details] [diff] [review] [landed] 1211292-part7.patch Review of attachment 9183387 [details] [diff] [review]: ----------------------------------------------------------------- I've been running with this for a while and with the current state, I don't see any problems. Time to enable in nightly!
Attachment #9183387 - Flags: review?(mkmelin+mozilla) → review+

Let's do follow-up work in other bugs, to ease tracking.

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/a3f9fbf593ee
Enable MessageSend.jsm in nightly builds (mailnews.send.jsmodule true). r=mkmelin

Status: ASSIGNED → RESOLVED
Closed: 4 years ago4 years ago
Resolution: --- → FIXED
Attachment #9183387 - Attachment description: 1211292-part7.patch → [landed] 1211292-part7.patch
Blocks: 1679818
Blocks: 1680189
Regressed by: 1683548
Regressions: 1683804
Regressions: 1683803
Regressions: 1683640
Regressions: 1646604
No longer regressed by: 1683548
Regressions: 1683548
Regressions: 1685033
No longer regressions: 1646604
Regressions: 1686636
Regressions: 1701450
Regressions: 1701741
Regressions: 1699057
Regressions: 1710220
Regressions: 1710323
Regressions: 1710043
No longer regressions: 1710323
Regressions: 1710572
Depends on: 1724177
Regressions: 1729437

(In reply to Joshua Cranmer [:jcranmer] from comment #31)

Let me start by apologizing for the length of this comment. It is a wall of text, but I don't think I can express what needs to be expressed without making it a wall of text. Please read all of it before moving to respond to any part.

I have concerns about the design of the rewrite that you are proposing. Getting the design right from the beginning is crucial; bad design has a way of leaking through APIs and spreading through the codebase. Furthermore, people and projects are usually extremely reluctant to tackle fixing bad design: it's easier to build yet another workaround rather than taking the time to rip out bad design.

An example of bad design in mailnews code that has infected beyond its initial APIs is that we store unparsed, rather than parsed, headers in our database, and so all the clients must parse them, which opens up inconsistencies if they don't use the correct API (and it's not easy to do so!).

In rewriting the code, we have a great opportunity to fix the design, especially since we can look back onto the existing code and evaluate design decisions that worked and those that didn't work. We do not need to rely on guesses as to how well the design will scale to future uses, when the uses are pretty well-known.

From my perspective, when I see your code, I have no prior experience to draw on to estimate how good you are at planning for future design. All I can do is extrapolate from the code you have written and from your replies to my review comments. If I were satisfied that you understood the design issues and could come up with a good design, I would not be pushing for a more formal design.

To be blunt: I did not like your design from the first patch, and your comments since then have only strengthened that assessment. Without confidence that you will be able to carry through this process through the end, it is hard for me to justify the immense time sink of thoroughly checking the existing code to understand what is and isn't being regressed.

Let's talk issues in more detail now. I'm not sure I've covered all of the issues in this list, but here they are as they cross my mind:

  • API design. As I mentioned in the beginning of this bug, the current interface of nsIMsgSend is a bad interface design and needs to be replaced with something better. While the new interface does need to be the last step of a very long sequence of patches, it is a good idea to design to a new interface from the beginning. To that end, I expect to see the code implemented in a way that is not so strictly tied to needing to support the current API. I'd actually expect much of the work to exist outside of a MessageSend class.

  • Blind reimplementation of existing code. This is a problem I've seen more with your later patches: there appears to be some copying of decisions from existing C++ code that is wrong and needs to be fixed, rewrite or not. An example here is the use of charsets influencing the type of encoding to use... just no. Now this doesn't mean that there isn't wisdom in existing code that can and should be ported, but it does require understanding the code, understanding why it's doing what it's doing, and then deciding if those reasons are still valid.

  • Using strings for binary data. I realize that JS has really poor facilities for handling binary data, especially compared to its string libraries. But my historical experience is that using (nominally) Unicode strings for binary data is a route that only leads to problems down the line. I've mentioned this in my previous comment, but the way you get around array concatenation is by changing the API so that you never need to actually concatenate arrays. Indeed, even having to do a concatenation of any kind in this context is a code smell.

  • Strings in other contexts. You're using a string to represent content-type, including parameters instead of a data structure that will serialize to the string value, but gives the necessary parameters in an easier format. Considering that you're already going to have to worry about RFC 2231 encoding parameters and writing that out anyways, it makes sense to convey other content-type parameters in a similar manner. In general, strings are bad data types, and you should only convert to strings at the last possible moment.

  • File size and method size. I have a personal distaste for large files--I think code is optimally sized at no more than 1000-ish lines of code per file. So when code starts greatly exceeding that size, I would want to see it broken up into smaller pieces. I envision there being roughly three major stages here at the end: a low-level MIME assembly file, a low-level actual sending phase, and an orchestration component that mediates between the input parameters and the low-level phases. I suggest you separate these phases into separate JS files.

  • Error handling. JS code is pretty notorious for causing errors to be thrown in unexpected places, much more so than our C++ code. The direct implementation of nsIMsgSend needs to have a relatively thin intermediary layer that will catch and propagate errors as needed to the listeners, else things like tests tend to hang without any clear indication why.

  • Tests. Our current test coverage is actually kind of crappy, especially when it comes to ensuring corner cases are handled correctly. I'd expect any rewrite to be adding a lot more tests. I'd also actually expect some of the existing tests to have to be rewritten, because they have a nasty habit of being too specific to particular details that need not be kept (e.g., line break locations in encoded messages).

Now let's talk about next steps.

I don't want to see any more code for the moment; I'm not happy with the current design, and I want to make sure that we can agree on how the code should work before you spend more time pursuing a design that will only be rejected.

You've mentioned that you worry about all kind of cases. I think the best next step for us to take is to put together a list of all the existing features, features to add in the future, and features to remove from the current implementation. To that end, I'm attaching an incomplete list that should give you a starting point. Note that the list is incomplete: it's entirely based on what I can recall from memory after 15 minutes or so, and it has been quite some time since I went through compose code with a fine-toothed comb.

After that, the next step would be to put together what nsIMsgSend should look like if we weren't constrained to implement the current interface. This is the main essence of why we are rewriting the code: if we are not going to improve on what exists, then rewriting is just a waste of time that will introduce yet more bugs and regressions into our codebase.

As part of that, we also need to understand what new interfaces we should add to reduce code duplication. There are a few other places where we build MIME messages, and adding a new interface to support those use cases is a good idea to me.

Only once those steps are completed do I believe it is worth taking the time to actually look at the implementation of code.

Is Joshua Cranmer still around? He had some good information here.

Flags: needinfo?(Pidgeot18)
Regressions: 1732554
Regressions: 1732555
Regressions: 1734210
Regressions: 1736130
Regressions: 1738890
Regressions: 1731529
Regressions: 1745676
Regressions: 1751461

(In reply to Worcester12345 from comment #176)

Is Joshua Cranmer still around? He had some good information here.

I'm mostly active only in the Matrix chat right now.

Flags: needinfo?(Pidgeot18)
Regressions: 1809474
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: