Bug 1090448 - Make e10s printing work on Linux
Tricky - it looks like there's a native GTK dialog (orchestrated by nsPrintDialogGTK.cpp), and a fallback XUL printer dialog if we can't get a handle on the native stuff.

Let's do the native dialog first.

The native dialog case

So, like before, the native dialog imports some settings from an nsIPrintSettings (likely casting them to an nsPrintSettingsGTK), and then spins a modal event loop before exporting new settings.

The things the dialog seems to want to get from the nsIPrintSettings… yeah, it casts it to an nsPrintSettingsGTK, and then extracts the GtkPrintSettings* from it. Also the GtkPageSetup.

Then gets a "shrink to fit" bool, "print background colours" bool, "print background images" bool.

With those last three bools, it sets dialog checkboxes to various states based on their value. Then it sets the other settings with the GtkPrintSettings* and the same goes for the GtkPageSetup*.

So… what is a GtkPrintSettings, and can I serialize it?

http://developer.gimp.org/api/2.0/gtk/GtkPrintSettings.html#gtk-print-settings-foreach

This looks useful - I can iterate each key-value pair...

Huh… here's a snippet of code I found from here :

gtk_print_settings_foreach (settings, print_settings_add_to_key_file, key_file);

static void
print_settings_add_to_key_file (const gchar *key,
const gchar *value,
gpointer     data)
{
GKeyFile *key_file = data;

g_key_file_set_value (key_file, PRINT_SETTINGS_NAME, key, value);
}

So… so I think every key and value can be serialized to a string this way? Okay….

Pretty sure we can serialize the GtkPrinter* by just getting the name and sending it over. I'm pretty sure constructing a GtkPrinter with gtk_printer_new on the child side with that name will work.

OVER TWO MONTHS PASS

Aaaaaand we're back. This is an M5 bug now, and just hit the top of my stack. So let's get back to it.

What I'd like, ideally, is parity with our current e10s printing support for Windows and OS X. At the very least, anyhow.

So, let's get a game plan together here.

The child process asks for the print settings dialog. Ugh, it looks like the child is blocked while we wait for the settings to come back down. Not great. That's bug 1090439. Let's not worry about that just this second.

So we synchronously ask the parent for information in nsPrintingPromptServiceProxy::ShowPrintDialog.

Parent receives it in PrintingParent::RecvShowPrintDialog. Gets the nsIPrintOptions service, and creates a new nsIPrintSettings. Deserialize the default settings that came up from the child. Then we show the dialog, which blocks until it returns some value.

Then we serialize the settings we got back from the dialog into a PrintData again, and send them down to the child.

Initial work that needs to be studied is bug 1082579

https://dxr.mozilla.org/mozilla-central/source/embedding/components/printingui/ipc/PrintingParent.cpp#68
https://dxr.mozilla.org/mozilla-central/source/widget/gtk/nsPrintOptionsGTK.cpp
https://dxr.mozilla.org/mozilla-central/source/widget/windows/nsPrintOptionsWin.cpp

https://dxr.mozilla.org/mozilla-central/source/widget/gtk/nsPrintSettingsGTK.cpp
https://dxr.mozilla.org/mozilla-central/source/widget/gtk/nsDeviceContextSpecG.cpp
https://dxr.mozilla.org/mozilla-central/source/widget/gtk/nsPrintDialogGTK.cpp#126
https://dxr.mozilla.org/mozilla-central/source/widget/gtk/nsPrintDialogGTK.h

Is it good enough to just serialize the name of the printer, and find it on the content side?

Are the gtk-print-settings easily serializable? Settable?

What _exactly_ is it that GTK needs from the front-end that we don't already serialize?

Interestingly, nsPrintDialogGTK already seems to have the ability to "export" and "import" GTK-specific settings from "NS" (Netscape?) print settings structures. This code is oooooooooold.

"    /* Code to copy between GTK and NS print settings structures.
* In the following,
*   "Import" means to copy from NS to GTK
*   "Export" means to copy from GTK to NS
*/
"

from: https://hg.mozilla.org/mozilla-central/file/29b05d283b00/widget/gtk/nsPrintDialogGTK.cpp#l144 for ImportSettings, ExportSettings.

So let's see what Export does.


Also, let's just see "what happens" when I try to print on Linux right now. A crash? Let's find out.

Yep. A crash. I guess that's not so surprising. Probably something in RecvShowPrintDialog. Wait, no that's wrong - we successfully exit from the parent process. The problem is in the content process after we return to it.

#0  __strlen_sse2_bsf () at ../sysdeps/i386/i686/multiarch/strlen-sse2-bsf.S:50
#1  0xafdc3da8 in nsCharTraits<char>::length (aStr=0x0) at /media/Projects/mozilla/mozilla-central/xpcom/string/nsCharTraits.h:498
#2  0xafdc4d01 in nsDependentCString::nsDependentCString (this=0xbff36584, aData=0x0) at /media/Projects/mozilla/mozilla-central/xpcom/string/nsTDependentString.h:54
#3  0xb269bb9c in nsDeviceContextSpecGTK::GetSurfaceForPrinter (this=0xa5bf0000, aSurface=0xa989c838) at /media/Projects/mozilla/mozilla-central/widget/gtk/nsDeviceContextSpecG.cpp:177
#4  0xb0c37fad in nsDeviceContext::InitForPrinting (this=0xa989c800, aDevice=0xa5bf0000) at /media/Projects/mozilla/mozilla-central/gfx/src/nsDeviceContext.cpp:491
#5  0xb2cbc9e8 in nsPrintEngine::DoCommonPrint (this=0xa5967a10, aIsPrintPreview=false, aPrintSettings=0x0, aWebProgressListener=0x0, aDoc=0xa5a331f4)
at /media/Projects/mozilla/mozilla-central/layout/printing/nsPrintEngine.cpp:663
#6  0xb2cbb5d9 in nsPrintEngine::CommonPrint (this=0xa5967a10, aIsPrintPreview=false, aPrintSettings=0x0, aWebProgressListener=0x0, aDoc=0xa5a331f4)
at /media/Projects/mozilla/mozilla-central/layout/printing/nsPrintEngine.cpp:419
#7  0xb2cbcf22 in nsPrintEngine::Print (this=0xa5967a10, aPrintSettings=0x0, aWebProgressListener=0x0) at /media/Projects/mozilla/mozilla-central/layout/printing/nsPrintEngine.cpp:781

Here's the incriminating line in nsDeviceContextSpecG.cpp:

} else if (nsDependentCString(fmtGTK).EqualsIgnoreCase("pdf")) {

fmtGTK is probably nullptr. It's set earlier in that same method here:

const gchar* fmtGTK = gtk_print_settings_get(mGtkPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT);

Right, so we've not properly serialized/deserialized that value.

Ok, I've added the serialize / deserialize functions to nsPrintOptionsGTK. I think this is where most of the work is going to be.

Serialization is, I think, going to be easy, but verbose. I just need to grab everything inside the GtkPrintSettings that is relevant, stuff it into the PrintData. No biggie.

Deserialization… how will that work. When I deserialize in PrintingParent, I have a newly created nsIPrintSettingsGTK that I pass in. When I deserialize in the nsPrintingPromptServiceProxy, I actually pass in the old nsIPrintSettingsGTK that we'd sent up, and have it be overwritten.

The nsPrintingPromptServiceProxy is fine. But is there anything I need to do to initialize a newly created nsIPrintSettingsGTK in the parent before I deserialize? Let's check...

Ok, on construction of an nsPrintSettingsGTK, we instantiate mPrintSettings, so that's good… ok, and someone later on, I guess, sets mGTKPrinter via SetGtkPrinter. So I need to do that too, I guess...

There are a bunch of reimplementations of getters and settings in nsPrintSettingsGTK that do reading and writing from the GtkPrintSettings - so I might be getting a bunch of serialization for free already.

Yeah, lots are freebies! Nice. The ones I don't seem to get for free, I've added to the list of things to serialize / deserialize.

There's also, I believe, straight-up accessing the mPrintSettings, so there might be even more to serialize / deserialize… let's look at where the mPrintSettings get accessed.

Huh. So we do have direct access to the mGtkPrintSettings object, and fiddling there, but really, I can't trust that Gecko is telling me everything that's being used here by the user. Really, I kinda need to serialize every damn thing that's not already done by nsPrintSettingsImpl. *sigh*. Ok, what are those things.

I'll list what I can hold inside an GtkPrintSettings, and strike out the things that we got for free because nsIPrintSettings wrapped the getters / setters and our nsPrintOptions serialization already works with them...


const gchar *      gtk_print_settings_get_printer ()
void      gtk_print_settings_set_printer ()

GtkPageOrientation      gtk_print_settings_get_orientation ()
void      gtk_print_settings_set_orientation ()
Remark: It looks like this is being set in a GtkPageSetup thing, but I might as well send it over as well.

GtkPaperSize *      gtk_print_settings_get_paper_size ()
void      gtk_print_settings_set_paper_size ()

Remark: Crap - GtkPaperSize is a complicated object that I'll need to serialize. :( (Actually - it turns out that it's not so bad! - read below!)

gdouble      gtk_print_settings_get_paper_width ()
void      gtk_print_settings_set_paper_width ()

gdouble      gtk_print_settings_get_paper_height ()
void      gtk_print_settings_set_paper_height ()

gboolean      gtk_print_settings_get_use_color ()
void      gtk_print_settings_set_use_color ()

gboolean      gtk_print_settings_get_collate ()
void      gtk_print_settings_set_collate ()

gboolean      gtk_print_settings_get_reverse ()
void      gtk_print_settings_set_reverse ()

GtkPrintDuplex      gtk_print_settings_get_duplex ()
void      gtk_print_settings_set_duplex ()

GtkPrintQuality      gtk_print_settings_get_quality ()
void      gtk_print_settings_set_quality ()

gint      gtk_print_settings_get_n_copies ()
void      gtk_print_settings_set_n_copies ()

gint      gtk_print_settings_get_number_up ()
void      gtk_print_settings_set_number_up ()

GtkNumberUpLayout      gtk_print_settings_get_number_up_layout ()
void      gtk_print_settings_set_number_up_layout ()

gint      gtk_print_settings_get_resolution ()
void      gtk_print_settings_set_resolution ()

void      gtk_print_settings_set_resolution_xy ()
gint      gtk_print_settings_get_resolution_x ()
gint      gtk_print_settings_get_resolution_y ()

gdouble      gtk_print_settings_get_printer_lpi ()
void      gtk_print_settings_set_printer_lpi ()

gdouble      gtk_print_settings_get_scale ()
void      gtk_print_settings_set_scale ()

GtkPrintPages      gtk_print_settings_get_print_pages ()
void      gtk_print_settings_set_print_pages ()

GtkPageRange *      gtk_print_settings_get_page_ranges ()
void      gtk_print_settings_set_page_ranges ()

GtkPageSet      gtk_print_settings_get_page_set ()
void      gtk_print_settings_set_page_set ()

const gchar *      gtk_print_settings_get_default_source ()
void      gtk_print_settings_set_default_source ()

const gchar *      gtk_print_settings_get_media_type ()
void      gtk_print_settings_set_media_type ()

const gchar *      gtk_print_settings_get_dither ()
void      gtk_print_settings_set_dither ()

const gchar *      gtk_print_settings_get_finishings ()
void      gtk_print_settings_set_finishings ()

const gchar *      gtk_print_settings_get_output_bin ()
void      gtk_print_settings_set_output_bin ()


What… what is the relationship between GtkPageSetup and GtkPaperSize?

GtkPaperSize seems to be something you can get from GtkPageSetup… and you can set a GtkPaperSize on it too with gtk_page_setup_set_paper_size. And then there's gtk_page_setup_set_paper_size_and_default_margins. Ahhhhh - a GtkPaperSize represents the default paper sizes that exist within the system. So I think I can just serialize the name, and send it over. Right? YES - I can get the name of the GtkPaperSize, and then when I instantiate one, I can pass in that name, and it'll assume the right values. Good! OH! And Paper Name is already being serialized and sent over by nsPrintSettingsImpl! \o/

So, when deserializing, I think I just need to create a custom GtkPaperSize with the name I was passed, and then set that on the GtkPageSetup.

I can use SetGtkPageSetup to save the GtkPageSetup, which contains the GtkPaperSize information, to the GtkPrintSettings.

Last piece of the puzzle, I think. What the hell is a GtkPrintBackend?

I… I don't think it matters. Instead of instantiating the GtkPrinter, I'll enumerate one until I find the one the user specified.

Roadblock :

Apparently, when deserializing the PrintSettings on the parent side (the first time, just passing up the default values), we fail an assertion for SetDuplex:


MOZ_ASSERT(aDuplex >= GTK_PRINT_DUPLEX_SIMPLEX &&
aDuplex <= GTK_PRINT_DUPLEX_VERTICAL,
"value is out of bounds for GtkPrintDuplex enum");

So somehow we're getting an invalid duplex passed up from the child. Maybe we have a bad default there, and we're just passing in random memory. Let's see...

Ok, on parent side, according to gdb, duplex is:

$4 = (int32_t &) @0xbfa86068: -1376538432

Yeah, that doesn't sound right.

GTK_PRINT_DUPLEX_SIMPLEX is belongs to an enum with only 3 values. So we're wayyyyy out of range here.

Ah, so the problem was on the child side. The GtkPrintSettings, on initialization, doesn't have a duplex value set, so GetDuplex returns NS_ERROR_FAILURE. NS_ERROR_FAILURE maps to -1376538432 as a signed int, and we dumbly were serializing that into the duplex field in the PrintData.

So, new TODO: I need to make nsPrintOptionsImpl smartly serialize things that might not exist. That means coming up with some sane defaults.

No. Wait. That's too complicated. Wayyyyy to complicated. The far simpler solution is just to have nsPrintSettingsGTK default to having duplex set to GTK_PRINT_DUPLEX_SIMPLEX (which apparently means "no duplex"). Yeah, jeebus, that's way better.

Wooo - just by serializing the GTKPrinter, I got something out:



But.. I'm crashing on shutdown. I'm crashing because I'm failing an assertion:

#0  0xb770f424 in __kernel_vsyscall ()
#1  0xb7485366 in nanosleep () at ../sysdeps/unix/syscall-template.S:81
#2  0xb748510d in __sleep (seconds=0) at ../sysdeps/unix/sysv/linux/sleep.c:137
#3  0xb2a307d0 in ah_crap_handler (signum=6) at /media/Projects/mozilla/mozilla-central/toolkit/xre/nsSigHandlers.cpp:101
#4  0xb2a14034 in nsProfileLock::FatalSignalHandler (signo=6, info=0xbff5808c, context=0xbff5810c) at /media/Projects/mozilla/mozilla-central/profile/dirserviceprovider/nsProfileLock.cpp:190
#5  <signal handler called>
#6  0xb770f424 in __kernel_vsyscall ()
#7  0xb73fd827 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#8  0xb7400c53 in __GI_abort () at abort.c:89
#9  0xb73f6977 in __assert_fail_base (fmt=0xb7534b94 "%s%s%s:%u: %s%sAssertion `%s' failed.\n%n", assertion=assertion@entry=0xae4d437f "hash_table->live_entries == 0",
file=file@entry=0xae4d42e0 "/build/buildd/cairo-1.13.0~20140204/src/cairo-hash.c", line=line@entry=217, function=function@entry=0xae4d44b0 "_cairo_hash_table_destroy") at assert.c:92
#10 0xb73f6a27 in __GI___assert_fail (assertion=0xae4d437f "hash_table->live_entries == 0", file=0xae4d42e0 "/build/buildd/cairo-1.13.0~20140204/src/cairo-hash.c", line=217, function=0xae4d44b0 "_cairo_hash_table_destroy")
at assert.c:101
#11 0xae41de6e in ?? () from /usr/lib/i386-linux-gnu/libcairo.so.2
#12 0xae471a03 in ?? () from /usr/lib/i386-linux-gnu/libcairo.so.2
#13 0xae414a4d in cairo_debug_reset_static_data () from /usr/lib/i386-linux-gnu/libcairo.so.2
#14 0xb2a242b5 in MOZ_gdk_display_close (display=0xb71d10a0) at /media/Projects/mozilla/mozilla-central/toolkit/xre/nsAppRunner.cpp:2804
#15 0xb2a285fa in XREMain::XRE_main (this=0xbff5884c, argc=4, argv=0xbff59b84, aAppData=0xbff589bc) at /media/Projects/mozilla/mozilla-central/toolkit/xre/nsAppRunner.cpp:4310
#16 0xb2a286d9 in XRE_main (argc=4, argv=0xbff59b84, aAppData=0xbff589bc, aFlags=0) at /media/Projects/mozilla/mozilla-central/toolkit/xre/nsAppRunner.cpp:4456
#17 0x0804bcdf in do_main (argc=4, argv=0xbff59b84, xreDirectory=0xb715f480) at /media/Projects/mozilla/mozilla-central/browser/app/nsBrowserApp.cpp:294
#18 0x0804c18b in main (argc=4, argv=0xbff59b84) at /media/Projects/mozilla/mozilla-central/browser/app/nsBrowserApp.cpp:667

Note that this is happening in the parent process. I'm going to guess that this is likely our mGtkPrinter being leaked? I think so. I don't even need to initiate a print to cause this crash. I just need to open the dialog - which causes the deserialization on the parent side which refs the new printer.

OH - I might need to unref the old printer! … wait, that's not the case - nsPrintSettingsGTK::SetGtkPrinter automatically unrefs the old printer before swapping in the new one.

Let me get rid of the mGTKPrinter bits anyways to see if that helps.

Ah, it doesn't. I'm leaking somewhere else. Let's work it backwards...

Facts:
  1. The leak is happening in the parent process
  2. The leak doesn't require me to initiate a print job in order to occur - I just need to open a printing dialog
  3. The leak detection occurs in cairo_debug_reset_static_data, of all places. That makes me think it's some gtk_ foo not being free'd.

I'm going to try to use valgrind to narrow things down. Going to be slow as ass, but it seems like the best solution as opposed to guessing.

This document tells me that I need to add some stuff to my .mozconfig (--disable-jemalloc and --enable-valgrind), so I'm waiting for that to build. In the meantime,

==20134== Thread 1:
==20134== Conditional jump or move depends on uninitialised value(s)
==20134==    at 0x8E4B088: Pickle::WriteBool(bool) (pickle.h:103)
==20134==    by 0x8E4B14C: IPC::ParamTraitsFundamental<bool>::Write(IPC::Message*, bool const&) (ipc_message_utils.h:138)
==20134==    by 0x93C7B1F: void IPC::WriteParam<bool>(IPC::Message*, bool const&) (ipc_message_utils.h:115)
==20134==    by 0x93CC5C1: void mozilla::embedding::PPrintingParent::Write<bool>(bool const&, IPC::Message*) (PPrintingParent.h:255)
==20134==    by 0x93C4657: mozilla::embedding::PPrintingParent::Write(mozilla::embedding::PrintData const&, IPC::Message*) (PPrintingParent.cpp:585)
==20134==    by 0x93C3B13: mozilla::embedding::PPrintingParent::OnMessageReceived(IPC::Message const&, IPC::Message*&) (PPrintingParent.cpp:349)
==20134==    by 0x96324CD: mozilla::dom::PContentParent::OnMessageReceived(IPC::Message const&, IPC::Message*&) (PContentParent.cpp:4874)
==20134==    by 0x92C5B08: mozilla::ipc::MessageChannel::DispatchSyncMessage(IPC::Message const&) (MessageChannel.cpp:1197)
==20134==    by 0x92C588B: mozilla::ipc::MessageChannel::DispatchMessage(IPC::Message const&) (MessageChannel.cpp:1154)
==20134==    by 0x92C57CF: mozilla::ipc::MessageChannel::OnMaybeDequeueOne() (MessageChannel.cpp:1142)
==20134==    by 0x92D7B0B: void DispatchToMethod<mozilla::ipc::MessageChannel, bool (mozilla::ipc::MessageChannel::*)()>(mozilla::ipc::MessageChannel*, bool (mozilla::ipc::MessageChannel::*)(), Tuple0 const&) (tuple.h:383)
==20134==    by 0x92D777A: RunnableMethod<mozilla::ipc::MessageChannel, bool (mozilla::ipc::MessageChannel::*)(), Tuple0>::Run() (task.h:310)
==20134==
==20134== Thread 4 Gecko_IOThread:
==20134== Syscall param sendmsg(msg.msg_iov[0]) points to uninitialised byte(s)
==20134==    at 0x4066328: sendmsg (socket.S:98)
==20134==    by 0x926B823: IPC::Channel::ChannelImpl::ProcessOutgoingMessages() ( ipc_channel_posix.cc:719 )
==20134==    by 0x926BAB8: IPC::Channel::ChannelImpl::Send(IPC::Message*) ( ipc_channel_posix.cc:792 )
==20134==    by 0x926C1E8: IPC::Channel::Send(IPC::Message*) ( ipc_channel_posix.cc:997 )
==20134==    by 0x92D78E1: void DispatchToMethod<IPC::Channel, bool (IPC::Channel::*)(IPC::Message*), IPC::Message*>(IPC::Channel*, bool (IPC::Channel::*)(IPC::Message*), Tuple1<IPC::Message*> const&) (tuple.h:393)
==20134==    by 0x92D7342: RunnableMethod<IPC::Channel, bool (IPC::Channel::*)(IPC::Message*), Tuple1<IPC::Message*> >::Run() (task.h:310)
==20134==    by 0x9279F74: MessageLoop::RunTask(Task*) ( message_loop.cc:361 )
==20134==    by 0x9279FDA: MessageLoop::DeferOrRunPendingTask(MessageLoop::PendingTask const&) ( message_loop.cc:369 )
==20134==    by 0x927A36E: MessageLoop::DoWork() ( message_loop.cc:447 )
==20134==    by 0x9259B9B: base::MessagePumpLibevent::Run(base::MessagePump::Delegate*) ( message_pump_libevent.cc:311 )
==20134==    by 0x9279A58: MessageLoop::RunInternal() ( message_loop.cc:233 )
==20134==    by 0x92799E6: MessageLoop::RunHandler() ( message_loop.cc:226 )
==20134==  Address 0x17e4ba54 is 44 bytes inside a block of size 1,024 alloc'd
==20134==    at 0x402C324: realloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==20134==    by 0x927C9A3: Pickle::Resize(unsigned int) ( pickle.cc:635 )
==20134==    by 0x927C31B: Pickle::BeginWrite(unsigned int, unsigned int) ( pickle.cc:513 )
==20134==    by 0x927C599: Pickle::WriteBytes(void const*, int, unsigned int) ( pickle.cc:558 )
==20134==    by 0x8E4B129: Pickle::WriteUInt64(unsigned long long) (pickle.h:139)
==20134==    by 0x8E4B0F2: Pickle::WriteSize(unsigned int) (pickle.h:127)
==20134==    by 0x8E4B18A: IPC::ParamTraitsLibC<unsigned int>::Write(IPC::Message*, unsigned int const&) (ipc_message_utils.h:344)
==20134==    by 0x92F8422: void IPC::WriteParam<unsigned int>(IPC::Message*, unsigned int const&) (ipc_message_utils.h:115)
==20134==    by 0x92F8AA9: IPC::ParamTraits<nsAString_internal>::Write(IPC::Message*, nsAString_internal const&) (IPCMessageUtils.h:337)
==20134==    by 0x93C7C25: void IPC::WriteParam<nsString>(IPC::Message*, nsString const&) (ipc_message_utils.h:115)
==20134==    by 0x93CC697: void mozilla::embedding::PPrintingParent::Write<nsString>(nsString const&, IPC::Message*) (PPrintingParent.h:255)
==20134==    by 0x93C4BE2: mozilla::embedding::PPrintingParent::Write(mozilla::embedding::PrintData const&, IPC::Message*) (PPrintingParent.cpp:628)
==20134==


==20134== 3,575 (896 direct, 2,679 indirect) bytes in 8 blocks are definitely lost in loss record 20,610 of 21,200
==20134==    at 0x4DA6BB1: g_type_create_instance (in /usr/lib/i386-linux-gnu/libgobject-2.0.so.0.4000.0)
==20134==    by 0x4D898FD: ??? (in /usr/lib/i386-linux-gnu/libgobject-2.0.so.0.4000.0)
==20134==    by 0x4D8BD96: g_object_new_valist (in /usr/lib/i386-linux-gnu/libgobject-2.0.so.0.4000.0)
==20134==    by 0x4D8BFEF: g_object_new (in /usr/lib/i386-linux-gnu/libgobject-2.0.so.0.4000.0)
==20134==    by 0x245E642B: gtk_printer_cups_new (in /usr/lib/i386-linux-gnu/gtk-2.0/2.10.0/printbackends/libprintbackend-cups.so)
==20134==    by 0x245E34B8: ??? (in /usr/lib/i386-linux-gnu/gtk-2.0/2.10.0/printbackends/libprintbackend-cups.so)
==20134==    by 0x245E4BD2: ??? (in /usr/lib/i386-linux-gnu/gtk-2.0/2.10.0/printbackends/libprintbackend-cups.so)
==20134==    by 0x245E3846: ??? (in /usr/lib/i386-linux-gnu/gtk-2.0/2.10.0/printbackends/libprintbackend-cups.so)
==20134==    by 0x4E100A6: g_main_context_dispatch (in /lib/i386-linux-gnu/libglib-2.0.so.0.4000.0)
==20134==    by 0x4E10467: ??? (in /lib/i386-linux-gnu/libglib-2.0.so.0.4000.0)
==20134==    by 0x4E10527: g_main_context_iteration (in /lib/i386-linux-gnu/libglib-2.0.so.0.4000.0)
==20134==    by 0xB62DCF7: nsAppShell::ProcessNextNativeEvent(bool) (nsAppShell.cpp:156)
==20134==

This looks like it's been around for a while! https://bugzilla.mozilla.org/show_bug.cgi?id=1129557

I'm tried to bisect and haven't found a good state - I went back to around Firefox 4.

So, decision: ignore the leak. It's not my bag, baby.

OK, attack plan:

Rename nsPrintOptionsImpl::SerializeToPrintSettings to nsPrintOptionsImpl::SerializeCommon and take default value struct.
Make nsPrintOptionsWin and nsPrintOptionsGTK use nsPrintOptionsImpl::SerializeCommon, each passing in a struct of defaults.
Serialize GtkPaperSize stuff
Deserialize it and generate custom GtkPaperSize with the same dimensions, names, etc.
Serialize GtkPageSetup stuff
When deserializing, create a new GtkPrintSettings, and set the GtkPageSetup on it with SetGtkPageSetup
Serialize all GtkPrintSettings stuff
Deserialize all of the GtkPrintSettings stuff
Deserialize the GtkPrinter by enumerating the printers until I find one with the same name
Stuff all that shit into the nsPrintSettingsGtk

GtkPaperSize (via gtk_paper_size_new_custom)
gtk_paper_size_get_name (const gchar *) - this is already serialized as paperName
gtk_paper_size_set_name (gchar *)
gtk_paper_size_get_display_name (gchar *)
gtk_paper_size_set_display_name (gchar *)


Things to serialize / deserialize:
GtkPaperSize
GtkPageSetup
GtkPrinter (already being sent over by nsPrintSettingsImpl, but needs deserialization!)
InitUnwriteableMargin? "Re-initialize mUnwriteableMargin with values from mPageSetup. Should be called whenever mPageSetup is initialized or overwritten.
GtkPrintSettings (see below)

GtkPrintSettings to make sure we transfer:
gtk_print_settings_get_orientation (enum GtkPageOrientation)
gtk_print_settings_set_orientation (enum GtkPageOrientation)
gtk_print_settings_get_paper_width (gdouble)
gtk_print_settings_set_paper_width (gdouble)
gtk_print_settings_get_paper_height (gdouble)
gtk_print_settings_set_paper_height (gdouble)
gtk_print_settings_get_collate (gboolean)
gtk_print_settings_set_collate (gboolean)
gtk_print_settings_get_quality (enum GtkPrintQuality)
gtk_print_settings_set_quality (enum GtkPrintQuality)
gtk_print_settings_get_number_up (gint)
gtk_print_settings_set_number_up (gint)
gtk_print_settings_get_number_up_layout (enum GtkNumberUpLayout)
gtk_print_settings_set_number_up_layout (enum GtkNumberUpLayout)
gtk_print_settings_set_resolution_xy (gint)
gtk_print_settings_get_resolution_x (gint)
gtk_print_settings_get_resolution_y (gint)
gtk_print_settings_get_printer_lpi (gdouble)
gtk_print_settings_set_printer_lpi (gdouble)
gtk_print_settings_get_page_set (enum GtkPageSet)
gtk_print_settings_set_page_set (enum GtkPageSet)
gtk_print_settings_get_default_source (const gchar * )
gtk_print_settings_set_default_source (const gchar * )
gtk_print_settings_get_media_type (const gchar * )
gtk_print_settings_set_media_type (const gchar * )
gtk_print_settings_get_dither (const gchar * )
gtk_print_settings_set_dither (const gchar * )
gtk_print_settings_get_finishings (const gchar * )
gtk_print_settings_set_finishings (const gchar * )
gtk_print_settings_get_output_bin (const gchar * )
gtk_print_settings_set_output_bin (const gchar * )
GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT
GTK_PRINT_SETTINGS_OUTPUT_URI
GTK_PRINT_SETTINGS_PAPER_FORMAT (set by GtkPaperSize via gtk_print_settings_set_paper_size)
GTK_PRINT_SETTINGS_PAPER_WIDTH (set by GtkPaperSize via gtk_print_settings_set_paper_size)
GTK_PRINT_SETTINGS_PAPER_HEIGHT (set by GtkPaperSize via gtk_print_settings_set_paper_size)

GtkPageSetup to make sure we transfer:
gtk_page_setup_set_paper_size_and_default_margins (GtkPaperSize) (this needs to be constructed first with the right GtkPaperSize name!)
gtk_page_setup_get_orientation (enum GtkPageOrientation)
gtk_page_setup_set_orientation (enum GtkPageOrientation)
gtk_page_setup_get_top_margin (gdouble)
gtk_page_setup_set_top_margin (gdouble)
gtk_page_setup_get_bottom_margin (gdouble)
gtk_page_setup_set_bottom_margin (gdouble)
gtk_page_setup_get_left_margin (gdouble)
gtk_page_setup_set_left_margin (gdouble)
gtk_page_setup_get_right_margin (gdouble)
gtk_page_setup_set_right_margin (gdouble)


Feedback from karlt:

I wonder why all these details need to be accessed on both the content and
browser sides. Could there be a cross-process object that is opaque on the
content side? Is there only a subset of this info that is needed by layout?

I don't have good ideas on how to deal with finding the GtkPrinter
synchronously. If it needs to pass between processes then perhaps determining
the GtkPrinter can be delayed until printing, which is already an async
process, I assume.

GtkPrintSettings is basically a hash table of strings keyed by strings. All
the accessor methods just convert between string and the particular format.
The keys aren't necessarily limited to the strings with GTK_PRINT_SETTINGS\_\*
defines. "The predefined keys try to use shared values as much as possible so
that moving such a document between systems still works", but I suspect
special printer backends might use other values. However, strings are
reasonably easy to serialize anyway, I assume, so, if this needs to be passed
between processes, then I suspect it would be simpler to use
gtk_print_settings_foreach() and gtk_print_settings_set(), with an array of
pairs of strings in PrintData.

I haven't yet looked at how GtkPageSetup and GtkPaperSize are used. I wonder
why the GtkPaperSize doesn't come from gtk_print_settings_get_paper_size().

I think we need to do whatever is necessary to avoid running arbitrary events in DeserializeToPrintSettings(). Nested event loops are evil. They unroll only in FILO order, and they surprise callers who don't expect the world to change while they weren't watching.

Is it necessary to pass the printer between content and browser processes? Is it the printer selection or the printing that happens in the content process? Could they both happen in the browser process?

Replace custom GTK things in PPrinting.ipdl's PrintData with an array of string pairs.

Ok, create a struct with key and value nsCStrings, and then have PrintData hold onto an array of those structs.

Serialize GtkPrintSettings back and forth by iterating the keys / values.
Find a way of getting at the GtkPrinter without enumerating them

Grawrrr,  GtkPrintBackend is woefully documented. But I think I can make this work...

I can emulate enumerate_printers, like this:

void
gtk_enumerate_printers (GtkPrinterFunc func,
gpointer data,
GDestroyNotify destroy,
gboolean wait)
{
PrinterList *printer_list;
GList *node, *next;
GtkPrintBackend *backend;

printer_list = g_new0 (PrinterList, 1);

printer_list->func = func;
printer_list->data = data;
printer_list->destroy = destroy;

if (g_module_supported ())
printer_list->backends = gtk_print_backend_load_modules ();

if (printer_list->backends == NULL)
{
free_printer_list (printer_list);
return;
}

for (node = printer_list->backends; node != NULL; node = next)
{
next = node->next;
backend = GTK_PRINT_BACKEND (node->data);
if (list_printers_init (printer_list, backend))
return;
}

if (wait && printer_list->backends)
{
printer_list->loop = g_main_loop_new (NULL, FALSE);

GDK_THREADS_LEAVE ();
g_main_loop_run (printer_list->loop);
GDK_THREADS_ENTER ();
}
}

Then don't forget to g_list_free(printer_list->backends);!

Evernote screwed up the formatting, but you get it. Basically I create a backend list, iterate that list, and for each backend, I can use gtk_print_backend_find_printer.

As soon as I find one that matches, I bail out. Easy… peasy… I think. *sigh*, why didn't GTK just supply this.

Graaarwwrwaeraera fuck.

The gtk_print_backend stuff isn't accessible. It's all private. I can't use this stuff! :(((((

karlt suggests looking into whether or not we can delay printer assignment until the very end of printing, when things are already asynchronous. I'll look into that next.

So here I am again, ass deep in nsPrintEngine.cpp.

15:04 (mconley) karlt: ping, if you're awake
15:05 (karlt) hi mconley
15:05 (mconley) karlt: hi!
15:05 (mconley) so I'm waist deep into GTK printing stuff
15:05 (mconley) and it's a pretty sad story
15:06 (mconley) karlt: I was just looking at your suggestion for waiting until we've kicked off the print job in order to enumerate the printers asynchronously
15:06 (mconley) so as to not spin yet another event loop
15:07 (mconley) it looks as if nsDeviceContextSpecG constructs a GtkPrintJob right at the start of the printing work, in nsDeviceContextSpecGTK::BeginDocument, and needs that in order to send the job to the printer
15:07 (karlt) hmm
15:07 (mconley) I'm wondering if you have any suggestions on how best to break this up to make the enumeration asynchrounous
15:07 (mconley) or
15:08 karlt looks for GtkPrintJob docs
15:08 (mconley) failing that, if you had any ideas on how else I could query for the printers - perhaps by accessing the GtkPrintBackend stuff (although from what I can tell, that's mostly private)
15:10 (karlt) yes, i looked at the print backend stuff too, and it seemed to be out of reach, on first impressions at least
15:10 (mconley) mucking about in that private stuff is also probably not a great idea. :/
15:16 (karlt) mconley: so the print dialog runs in the parent process, then the content process uses info from the dialog to draw and send to the printer?
15:16 (mconley) karlt: yep
15:16 (mconley) the content process has no ability to open windows
15:16 (mconley) which is why it asks the parent to do it
15:17 (mconley) however, the backend has not yet been rejigged to actually kick off the printing in the parent.
15:17 (mconley) though that's a long-term goal
15:17 (karlt) ok, print dialog in the parent makes sense, and i understand you want something for now in the content process
15:17 mconley nods
15:18 (karlt) mconley: mPrintJob in  nsDeviceContextSpecGTK seems to be unused until EndDocument(), which does the gtk_print_job_send()
15:19 (mconley) karlt: ah, yes. I suppose in EndDocument, we can try to grab the printer... if that errors out, it's a bit of a shame to do all of that processing only to die there
15:19 (mconley) but yes, that's one point where we could do the query for printers
15:21 (karlt) mconley: currently BeginDocument only errors out if !GTK_IS_PRINTER(mGtkPrinter); i'm thinking that if we got a GtkPrinter in the parent process from the print dialog, and so have its name, we can reasonably expect to find that in the content process
15:21 (mconley) true
15:21 (mconley) karlt: hrm - mGtkPrinter is also needed here: https://dxr.mozilla.org/mozilla-central/source/widget/gtk/nsDeviceContextSpecG.cpp#161
15:22 (karlt) oh, i see
15:22 heycam|away is now known as heycam
15:23 (karlt) mconley: could we get that info in the parent?
15:24 ferjm has left IRC (Quit: Textual IRC Client: www.textualapp.com )
15:26 (karlt) and store it in the nsPrintSettingsGTK?
15:30 mconley thinks
15:30 (mconley) for the multi-process case, yes, that works
15:30 (mconley) karlt: it sounds like we might have some single-process and multi-process branching in here now though. :/
15:32 (karlt) mconley: i think we could use the same code for supports pdf etc, and branch on mGtkPrinter when creating the print job
15:33 (karlt) if you can spawn an event to find the printer, then that event can run the event loop knowing that there is nothing on the stack that will be disrupted by other events that run
15:34 (karlt) nested event loops still only unroll in filo order, but the code can be written so that the effects of that are only a deeper stack
15:34 milan has left IRC (Connection closed)
15:35 milan has joined (milan@ moz-i5m.05u.207.66.IP )
15:37 mconley thinks
15:37 dbaron has joined (dbaron@ moz-m26.8gt.193.116.IP )
15:37 ChanServ has changed mode: +qo dbaron dbaron
15:41 (mconley) karlt: ok, I understand the first bit to re-use the accepts-pdf stuff in the print settings object for single and multi-process - that makes sense
15:42 (mconley) karlt: when we branch in BeginDocument, we'll start looking for a printer - we'll call enumerate_printers but not block?
15:42 (mconley) if that's the case, what happens if EndDocument gets called before we're done enumerating the printers?
15:43 (karlt) nsPrintDialogWidgetGTK::ExportSettings() might be the place to set the output format to ps or pdf according to the printer
15:43 mattwoodrow|away is now known as mattwoodrow
15:44 (karlt) mconley: the simplest is probably to move gtk_print_job_new out of BeginDocument, probably for both single and multiprocess
15:44 (karlt) so instead
15:45 (karlt) EndDocument spawns the event to find a printer and then call print_job_send
15:46 (mconley) ah
15:47 (mconley) karlt: ok, yes, I think I can work with that. Thanks!

Ok… I think this will work. In sum:

Make it so that nsDeviceContextSpecG does not need to know about the GtkPrinter until EndDocument is called, at which time it asynchronously queries for the right printer, and sends the job off.

This will mean ripping out some old stuff that reads the mGTKPrinter from the nsIPrintSettings, and instead sending that information through nsPrintSettingsGTK instead.

Add SupportsPDF getter and setter to nsPrintSettingsGTK
Set the SupportsPDF setter in nsPrintDialogWidgetGTK::ExportSettings
In the Serialization/Deserialization methods, get and set SupportsPDF
Change the code in nsDeviceContextSpecGTK::GetSurfaceForPrinter to use SupportsPDF instead of mGtkPrinter.

Make nsDeviceContextSpecGTK::EndDocument asynchronously fetch the printer if one doesn't already exist
Move the creation of the GtkPrintJob out of BeginDocument to EndDocument

file:///Users/mikeconley/Projects/gtk2-html-2.24.25/GtkPrintJob.html#GtkPrintJobCompleteFunc

void
(*GtkPrintJobCompleteFunc) (GtkPrintJob *print_job,
gpointer user_data,
GError *error);

file:///Users/mikeconley/Projects/gtk2-html-2.24.25/GtkPrinter.html#GtkPrinterFunc

gboolean
(*GtkPrinterFunc) (GtkPrinter *printer,
gpointer data);


static void job_complete we'll do the same thing.


printer_enumerator

In nsPrintOptionsGTK.h remove PPrinting.h, and forwards declare the PrintData struct instead
Either remove the using namespace in nsPrintOptionsGTK.cpp, or actually make use of it when referring to PrintData
Try calling SetGtkPrintSettings after deserialization, in DeserializeToPrintSettings, and remove the SetDuplex inside the nsPrintSettingsGTK Initializer
Get SerializeGTKPrintSettingsToPrintData out of the nsPrintSettingsGTK header
Remove unused gtkPageSetup and gtkPaperSize from deserialization method
In DeserializeToPrintSettings, instead of re-using the GtkPrintSettings, destroy it and create a new one.

Why isn't the printer name getting sent down to the content process properly?

The problem was that the printer name was getting wiped out, since we rely on the mGtkPrintSettings inside nsIPrintSettings to hold on to the printer name. After deserializing a bunch of stuff in nsPrintOptionsGTK::DeserializeToPrintSettings, I was chucking out the GtkPrintSettings inside nsIPrintSettings and replacing it with one that just had the GTK-specific settings set on it.

The solution was to create the new GtkPrintSettings and set it on the nsIPrintSettings _before_ attempting to deserialize the PrintData from the parent. That way, we can properly get the printer name and enumerate/find the target printer.

In nsDeviceContextSpecGTK, make the printer enumerator and start print job methods private or protected. Probably private.
Remove acceptsPDF bool from PrintData, and have nsPrintDialogGTK set the output format instead. So basically, stash this value in the output format.
Move the printer enumerator in nsDeviceContextSpecGTK into a runnable
Replace nsCOMPtr<nsIPrintSettings> mPrintSettings with nsCOMPtr<nsPrintSettingsGTK> mPrintSettings in nsDeviceContextSpecGTK
"NS_ConvertUTF16toUTF8 is a class holding an extra copy of the string, so please use mTitle.Truncate() and AppendUTF16toUTF8(aTitle, mTitle)"
Rename KeyValue to CStringKeyValue?
Address dw-dev's feedback from bug 1088070

EndDocument() is called from the nsPrintData destructor, so I don't think we
can assume that this is a safe place to run the event loop. Instead dispatch
an event that will call gtk_enumerate_printers(). There could be issues with
asynchronicity if GetSurfaceForPrinter() calls are repeated after
EndDocument(), so either assert this doesn't happen, or pass mSpoolFile
to the event.
Do the above… maybe ask karlt what he means here.

Figure out better naming inside nsDeviceContextSpecGTK? mPrintSettingsGTK vs mGtkPrintSettings is crazy confusing. No longer needed since I just have the one mPrintSettings now.
Flesh out comment in nsDeviceContextSpecGTK::EndDocument clearing up what the hell is going on

TODO
Stow GTK-specific print settings inside PrintData in PPrinting.ipdl
Set print.enable_e10s_testing flag to true for Linux
Handle the XUL dialog case (this appears to be mostly unused code…)

Yes! r+ from karlt! Now a try push for safety...

Amusement
https://twitter.com/snorp/status/562999621484838914
https://gist.github.com/eed3si9n/3920236