Bug 1090439 - PPrinting calls from child to parent via ShowProgress and ShowPrintDialog should not be sync
When the content process requests that the dialog be open, we want a message to go up to the parent to actually open the dialog, but the message should be async instead of sync.

The child should then spin a nested event loop and wait for a message from the parent with the return value of having opened the printing dialog.

Make the call to SendShowPrintDialog be async instead of sync (altering PPrinting.ipdl)
Alter nsPrintingPromptServiceProxy so that it doesn't expect return values from SendShowPrintDialog.
After the message goes to the parent to open the dialog, spin a nested event loop

while(something) {
NS_ProcessNextEvent(nullptr, true);
}

Add an async message from the parent to signal when the dialog has been closed and pass the return value

I was crashing here at first, but that's because I wasn't using the right method signature on my RecvPrintDialogResult - I needed to use const nsresult&, and not straight up nsresult.

I have confirmed that I service the message and handle that message properly while in the nested event loop. Huzzah!

Have the proxy notice that the async message from the parent has been sent and exit the nested event loop.
The handler for that message in the child should then stash those values within the proxy instance and when the event loop exits, these values should be returned to the original caller.

Mystery:
From where did we fire NS_ERROR_NOT_AVAILABLE when attempting to print again after having entered the nested event loop in the child?

The error is coming from nsDocumentViewer::Print, right here:

// if we are printing another URL, then exit
// the reason we check here is because this method can be called while
// another is still in here (the printing dialog is a good example).
// the only time we can print more than one job at a time is the regression tests
if (GetIsPrinting()) {
// Let the user know we are not ready to print.
rv = NS_ERROR_NOT_AVAILABLE;
nsPrintEngine::ShowPrintErrorDialog(rv);
return rv;
}

Huh. So it looks like if the print engine is already in the midst of printing, we don't allow it again.

It looks like in the non-e10s case, we have different mPrintEngines per nsDocumentViewer. So a new print engine is constructed, which isn't already attempting to print, it seems. Do we share nsDocumentViewer's when in e10s mode?

Ah, heh - I must have been doing something wrong before. I can totally open dialogs in both windows now.

¯\_(ツ)_/¯

So, this is interesting… it looks like if I have multiple windows with printing dialogs open, I cannot dismiss older dialogs (either with OK or Cancel/Close) until the younger ones have been cleared. Then, the events are serviced.

That's for Linux/GTK, anyway. Not so on Windows.

Hypothesis: on Windows (where I can have two printing dialogs, and close the first before the second), I think that my current code is flawed such that if I initiate a print job from the first window/dialog, that I'll actually end up printing the document in the second window. I have a potential solution for this, but I'd like to prove that it's correct.

Test:
Open Window A, and Window B. Load distinctive documents in each.
Open print dialog in A.
Open print dialog in B.
Initiate print job in A.
See if the printed document is actually the one loaded in B.

Weird - crashing on Linux again because of Duplex.

It looks like when the print dialog is closed, the duplex is set to some error value inside it. What? Weird… we're getting a different GtkPrintSettings back from the dialog. Is this true without my patch here?

Testing...

Yes, this appears to be a problem here. :/

jimm wants me to investigate the possibility of altering the ShowPrintDialog interface so that the nsPrintEngine doesn't need the results from the print dialog right away - essentially, making the dialog call asynchronous, and avoiding the spin of the nested event loop. Let's look into that now.

What are the possibilities here? Where do we enter the whole enchilada?

This happens inside nsPrintEngine::DoCommonPrint:

if (!printSilently) {
nsCOMPtr<nsIPrintingPromptService> printPromptService(do_GetService(kPrintingPromptService));
if (printPromptService) {
nsIDOMWindow *domWin = mDocument->GetWindow();
NS_ENSURE_TRUE(domWin, NS_ERROR_FAILURE);

// Platforms not implementing a given dialog for the service may
// return NS_ERROR_NOT_IMPLEMENTED or an error code.
//
// NS_ERROR_NOT_IMPLEMENTED indicates they want default behavior
// Any other error code means we must bail out
//
nsCOMPtr<nsIWebBrowserPrint> wbp(do_QueryInterface(mDocViewerPrint));
rv = printPromptService->ShowPrintDialog(domWin, wbp,
mPrt->mPrintSettings);
//
// ShowPrintDialog triggers an event loop which means we can't assume
// that the state of this->{anything} matches the state we've checked
// above. Including that a given {thing} is non null.
if (!mPrt) {
return NS_ERROR_FAILURE;
}

So this code assumes we're going to be spinning an event loop. It's designed to operate that way.

Well shoot, now here's the thing:

In the single-process case, we have to block - the dialogs spin their event loops and block us. We'd need to branch in the multi-process case, like:

Notice that we're multi-process, call the parent to show the print progress dialog, and then have the child stash some state. When the parent responds with values, we need to re-enter nsPrintEngine and pick up that state from where we left off. Eww..

Ok, talked to jimm about this. I think we're going to punt on the idea of not spinning an event loop.

So I'm going to use a LinkedList instead of an Array, since we're likely only ever going to have one item that gets added and then removed, and we just need to compare prompt IDs. An Array would mean appending, and crawling up the indexes, and I …. think that means wasted memory?

Bah. Whatever. A LinkedList it is.

I don't think we need to make opening the print progress dialog async - we're not blocked on user input or the lifetime of the window; we're blocked on the opening of the window itself. It's possible that we want to spin an event loop in the child for that small pocket of time, but I really don't think it's a big deal at this point.

Hm - a comment from smaug that I never really paid enough attention to:

"Couldn't you create a PrintDialog protocol, and then in __delete__ continue. While you're waiting __delete__ you'd call NS_ProcessNextEvent"

Let's think about that for a second, because maybe that simplifies my code somewhat (it might eliminate my need for the LinkedList, for example).

Suppose I had a PrintDialog protocol, and when I send the async message up to the parent, I have a PrintDialogChild get instantiated and passed up (which becomes the PrintDialogParent in the parent process). When the parent process gets past the dialog, maybe the result comes down through the PrintDialogParent to the PrintDialogChild, which holds onto the information and then gets _delete_ called on it...

Then we somehow need to keep the child end of things alive even after _delete_ is called on it, so that the spinning event loop knows to exit and retrieve the info.

Ok, so I think it could be done, but I don't actually think it's a better solution right now. It actually sounds a little more complicated.

Well. Maybe I'm wrong. Let's try smaug's way. Maybe it'll be neater and cleaner. Let's just try it.

So the tradeoff seems to be that I have to do less manual mapping between "results" and nested event loops. The IPC infrastructure takes care of that. I seem to gain more files though - but they're pretty simple. I think I prefer smaug's approach. Gonna fold it in.

Try push.

Another try push because I'm a fool and had a nonsense fragment in the trychooser message commit.

Landed!

TODO:
Investigate potentiality for race conditions with multiple dialog windows closing, multiple nested event loops and settings / results being dispatched to the wrong caller.
File a bug to find a different solution than spinning an event loop in the child process. Filed this bug.