1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include <gtk/gtk.h>
7 #include <gtk/gtkunixprint.h>
8 #include <stdlib.h>
9 
10 #include "mozilla/ArrayUtils.h"
11 #include "mozilla/Services.h"
12 
13 #include "MozContainer.h"
14 #include "nsIPrintSettings.h"
15 #include "nsIWidget.h"
16 #include "nsPrintDialogGTK.h"
17 #include "nsPrintSettingsGTK.h"
18 #include "nsString.h"
19 #include "nsReadableUtils.h"
20 #include "nsIStringBundle.h"
21 #include "nsIPrintSettingsService.h"
22 #include "nsPIDOMWindow.h"
23 #include "nsIGIOService.h"
24 #include "WidgetUtils.h"
25 #include "WidgetUtilsGtk.h"
26 #include "nsIObserverService.h"
27 
28 // for gdk_x11_window_get_xid
29 #include <gdk/gdkx.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <fcntl.h>
33 #include <gio/gunixfdlist.h>
34 
35 // for dlsym
36 #include <dlfcn.h>
37 #include "MainThreadUtils.h"
38 
39 using namespace mozilla;
40 using namespace mozilla::widget;
41 
42 static const char header_footer_tags[][4] = {"", "&T", "&U", "&D", "&P", "&PT"};
43 
44 #define CUSTOM_VALUE_INDEX gint(ArrayLength(header_footer_tags))
45 
get_gtk_window_for_nsiwidget(nsIWidget * widget)46 static GtkWindow* get_gtk_window_for_nsiwidget(nsIWidget* widget) {
47   return GTK_WINDOW(widget->GetNativeData(NS_NATIVE_SHELLWIDGET));
48 }
49 
ShowCustomDialog(GtkComboBox * changed_box,gpointer user_data)50 static void ShowCustomDialog(GtkComboBox* changed_box, gpointer user_data) {
51   if (gtk_combo_box_get_active(changed_box) != CUSTOM_VALUE_INDEX) {
52     g_object_set_data(G_OBJECT(changed_box), "previous-active",
53                       GINT_TO_POINTER(gtk_combo_box_get_active(changed_box)));
54     return;
55   }
56 
57   GtkWindow* printDialog = GTK_WINDOW(user_data);
58   nsCOMPtr<nsIStringBundleService> bundleSvc =
59       do_GetService(NS_STRINGBUNDLE_CONTRACTID);
60 
61   nsCOMPtr<nsIStringBundle> printBundle;
62   bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties",
63                           getter_AddRefs(printBundle));
64   nsAutoString intlString;
65 
66   printBundle->GetStringFromName("headerFooterCustom", intlString);
67   GtkWidget* prompt_dialog = gtk_dialog_new_with_buttons(
68       NS_ConvertUTF16toUTF8(intlString).get(), printDialog,
69       (GtkDialogFlags)(GTK_DIALOG_MODAL), GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
70       GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, nullptr);
71   gtk_dialog_set_default_response(GTK_DIALOG(prompt_dialog),
72                                   GTK_RESPONSE_ACCEPT);
73   gtk_dialog_set_alternative_button_order(
74       GTK_DIALOG(prompt_dialog), GTK_RESPONSE_ACCEPT, GTK_RESPONSE_REJECT, -1);
75 
76   printBundle->GetStringFromName("customHeaderFooterPrompt", intlString);
77   GtkWidget* custom_label =
78       gtk_label_new(NS_ConvertUTF16toUTF8(intlString).get());
79   GtkWidget* custom_entry = gtk_entry_new();
80   GtkWidget* question_icon =
81       gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
82 
83   // To be convenient, prefill the textbox with the existing value, if any, and
84   // select it all so they can easily both edit it and type in a new one.
85   const char* current_text =
86       (const char*)g_object_get_data(G_OBJECT(changed_box), "custom-text");
87   if (current_text) {
88     gtk_entry_set_text(GTK_ENTRY(custom_entry), current_text);
89     gtk_editable_select_region(GTK_EDITABLE(custom_entry), 0, -1);
90   }
91   gtk_entry_set_activates_default(GTK_ENTRY(custom_entry), TRUE);
92 
93   GtkWidget* custom_vbox = gtk_vbox_new(TRUE, 2);
94   gtk_box_pack_start(GTK_BOX(custom_vbox), custom_label, FALSE, FALSE, 0);
95   gtk_box_pack_start(GTK_BOX(custom_vbox), custom_entry, FALSE, FALSE,
96                      5);  // Make entry 5px underneath label
97   GtkWidget* custom_hbox = gtk_hbox_new(FALSE, 2);
98   gtk_box_pack_start(GTK_BOX(custom_hbox), question_icon, FALSE, FALSE, 0);
99   gtk_box_pack_start(GTK_BOX(custom_hbox), custom_vbox, FALSE, FALSE,
100                      10);  // Make question icon 10px away from content
101   gtk_container_set_border_width(GTK_CONTAINER(custom_hbox), 2);
102   gtk_widget_show_all(custom_hbox);
103 
104   gtk_box_pack_start(
105       GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(prompt_dialog))),
106       custom_hbox, FALSE, FALSE, 0);
107   gint diag_response = gtk_dialog_run(GTK_DIALOG(prompt_dialog));
108 
109   if (diag_response == GTK_RESPONSE_ACCEPT) {
110     const gchar* response_text = gtk_entry_get_text(GTK_ENTRY(custom_entry));
111     g_object_set_data_full(G_OBJECT(changed_box), "custom-text",
112                            strdup(response_text), (GDestroyNotify)free);
113     g_object_set_data(G_OBJECT(changed_box), "previous-active",
114                       GINT_TO_POINTER(CUSTOM_VALUE_INDEX));
115   } else {
116     // Go back to the previous index
117     gint previous_active = GPOINTER_TO_INT(
118         g_object_get_data(G_OBJECT(changed_box), "previous-active"));
119     gtk_combo_box_set_active(changed_box, previous_active);
120   }
121 
122   gtk_widget_destroy(prompt_dialog);
123 }
124 
125 class nsPrintDialogWidgetGTK {
126  public:
127   nsPrintDialogWidgetGTK(nsPIDOMWindowOuter* aParent,
128                          nsIPrintSettings* aPrintSettings);
~nsPrintDialogWidgetGTK()129   ~nsPrintDialogWidgetGTK() { gtk_widget_destroy(dialog); }
130   NS_ConvertUTF16toUTF8 GetUTF8FromBundle(const char* aKey);
131   gint Run();
132 
133   nsresult ImportSettings(nsIPrintSettings* aNSSettings);
134   nsresult ExportSettings(nsIPrintSettings* aNSSettings);
135 
136  private:
137   GtkWidget* dialog;
138   GtkWidget* shrink_to_fit_toggle;
139   GtkWidget* print_bg_colors_toggle;
140   GtkWidget* print_bg_images_toggle;
141   GtkWidget* selection_only_toggle;
142   GtkWidget* header_dropdown[3];  // {left, center, right}
143   GtkWidget* footer_dropdown[3];
144 
145   nsCOMPtr<nsIStringBundle> printBundle;
146 
147   bool useNativeSelection;
148 
149   GtkWidget* ConstructHeaderFooterDropdown(const char16_t* currentString);
150   const char* OptionWidgetToString(GtkWidget* dropdown);
151 
152   /* Code to copy between GTK and NS print settings structures.
153    * In the following,
154    *   "Import" means to copy from NS to GTK
155    *   "Export" means to copy from GTK to NS
156    */
157   void ExportHeaderFooter(nsIPrintSettings* aNS);
158 };
159 
nsPrintDialogWidgetGTK(nsPIDOMWindowOuter * aParent,nsIPrintSettings * aSettings)160 nsPrintDialogWidgetGTK::nsPrintDialogWidgetGTK(nsPIDOMWindowOuter* aParent,
161                                                nsIPrintSettings* aSettings) {
162   nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
163   NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
164   GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
165   NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");
166 
167   nsCOMPtr<nsIStringBundleService> bundleSvc =
168       do_GetService(NS_STRINGBUNDLE_CONTRACTID);
169   bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties",
170                           getter_AddRefs(printBundle));
171 
172   dialog = gtk_print_unix_dialog_new(GetUTF8FromBundle("printTitleGTK").get(),
173                                      gtkParent);
174 
175   gtk_print_unix_dialog_set_manual_capabilities(
176       GTK_PRINT_UNIX_DIALOG(dialog),
177       GtkPrintCapabilities(
178           GTK_PRINT_CAPABILITY_COPIES | GTK_PRINT_CAPABILITY_COLLATE |
179           GTK_PRINT_CAPABILITY_REVERSE | GTK_PRINT_CAPABILITY_SCALE |
180           GTK_PRINT_CAPABILITY_GENERATE_PDF));
181 
182   // The vast majority of magic numbers in this widget construction are padding.
183   // e.g. for the set_border_width below, 12px matches that of just about every
184   // other window.
185   GtkWidget* custom_options_tab = gtk_vbox_new(FALSE, 0);
186   gtk_container_set_border_width(GTK_CONTAINER(custom_options_tab), 12);
187   GtkWidget* tab_label =
188       gtk_label_new(GetUTF8FromBundle("optionsTabLabelGTK").get());
189 
190   // Check buttons for shrink-to-fit and print selection
191   GtkWidget* check_buttons_container = gtk_vbox_new(TRUE, 2);
192   shrink_to_fit_toggle = gtk_check_button_new_with_mnemonic(
193       GetUTF8FromBundle("shrinkToFit").get());
194   gtk_box_pack_start(GTK_BOX(check_buttons_container), shrink_to_fit_toggle,
195                      FALSE, FALSE, 0);
196 
197   // GTK+2.18 and above allow us to add a "Selection" option to the main
198   // settings screen, rather than adding an option on a custom tab like we must
199   // do on older versions.
200   bool canSelectText = aSettings->GetIsPrintSelectionRBEnabled();
201   if (gtk_major_version > 2 ||
202       (gtk_major_version == 2 && gtk_minor_version >= 18)) {
203     useNativeSelection = true;
204     g_object_set(dialog, "support-selection", TRUE, "has-selection",
205                  canSelectText, "embed-page-setup", TRUE, nullptr);
206   } else {
207     useNativeSelection = false;
208     selection_only_toggle = gtk_check_button_new_with_mnemonic(
209         GetUTF8FromBundle("selectionOnly").get());
210     gtk_widget_set_sensitive(selection_only_toggle, canSelectText);
211     gtk_box_pack_start(GTK_BOX(check_buttons_container), selection_only_toggle,
212                        FALSE, FALSE, 0);
213   }
214 
215   // Check buttons for printing background
216   GtkWidget* appearance_buttons_container = gtk_vbox_new(TRUE, 2);
217   print_bg_colors_toggle = gtk_check_button_new_with_mnemonic(
218       GetUTF8FromBundle("printBGColors").get());
219   print_bg_images_toggle = gtk_check_button_new_with_mnemonic(
220       GetUTF8FromBundle("printBGImages").get());
221   gtk_box_pack_start(GTK_BOX(appearance_buttons_container),
222                      print_bg_colors_toggle, FALSE, FALSE, 0);
223   gtk_box_pack_start(GTK_BOX(appearance_buttons_container),
224                      print_bg_images_toggle, FALSE, FALSE, 0);
225 
226   // "Appearance" options label, bold and center-aligned
227   GtkWidget* appearance_label = gtk_label_new(nullptr);
228   char* pangoMarkup = g_markup_printf_escaped(
229       "<b>%s</b>", GetUTF8FromBundle("printBGOptions").get());
230   gtk_label_set_markup(GTK_LABEL(appearance_label), pangoMarkup);
231   g_free(pangoMarkup);
232   gtk_misc_set_alignment(GTK_MISC(appearance_label), 0, 0);
233 
234   GtkWidget* appearance_container = gtk_alignment_new(0, 0, 0, 0);
235   gtk_alignment_set_padding(GTK_ALIGNMENT(appearance_container), 8, 0, 12, 0);
236   gtk_container_add(GTK_CONTAINER(appearance_container),
237                     appearance_buttons_container);
238 
239   GtkWidget* appearance_vertical_squasher = gtk_vbox_new(FALSE, 0);
240   gtk_box_pack_start(GTK_BOX(appearance_vertical_squasher), appearance_label,
241                      FALSE, FALSE, 0);
242   gtk_box_pack_start(GTK_BOX(appearance_vertical_squasher),
243                      appearance_container, FALSE, FALSE, 0);
244 
245   // "Header & Footer" options label, bold and center-aligned
246   GtkWidget* header_footer_label = gtk_label_new(nullptr);
247   pangoMarkup = g_markup_printf_escaped(
248       "<b>%s</b>", GetUTF8FromBundle("headerFooter").get());
249   gtk_label_set_markup(GTK_LABEL(header_footer_label), pangoMarkup);
250   g_free(pangoMarkup);
251   gtk_misc_set_alignment(GTK_MISC(header_footer_label), 0, 0);
252 
253   GtkWidget* header_footer_container = gtk_alignment_new(0, 0, 0, 0);
254   gtk_alignment_set_padding(GTK_ALIGNMENT(header_footer_container), 8, 0, 12,
255                             0);
256 
257   // --- Table for making the header and footer options ---
258   GtkWidget* header_footer_table = gtk_table_new(3, 3, FALSE);  // 3x3 table
259   nsString header_footer_str[3];
260 
261   aSettings->GetHeaderStrLeft(header_footer_str[0]);
262   aSettings->GetHeaderStrCenter(header_footer_str[1]);
263   aSettings->GetHeaderStrRight(header_footer_str[2]);
264 
265   for (unsigned int i = 0; i < ArrayLength(header_dropdown); i++) {
266     header_dropdown[i] =
267         ConstructHeaderFooterDropdown(header_footer_str[i].get());
268     // Those 4 magic numbers in the middle provide the position in the table.
269     // The last two numbers mean 2 px padding on every side.
270     gtk_table_attach(GTK_TABLE(header_footer_table), header_dropdown[i], i,
271                      (i + 1), 0, 1, (GtkAttachOptions)0, (GtkAttachOptions)0, 2,
272                      2);
273   }
274 
275   const char labelKeys[][7] = {"left", "center", "right"};
276   for (unsigned int i = 0; i < ArrayLength(labelKeys); i++) {
277     gtk_table_attach(GTK_TABLE(header_footer_table),
278                      gtk_label_new(GetUTF8FromBundle(labelKeys[i]).get()), i,
279                      (i + 1), 1, 2, (GtkAttachOptions)0, (GtkAttachOptions)0, 2,
280                      2);
281   }
282 
283   aSettings->GetFooterStrLeft(header_footer_str[0]);
284   aSettings->GetFooterStrCenter(header_footer_str[1]);
285   aSettings->GetFooterStrRight(header_footer_str[2]);
286 
287   for (unsigned int i = 0; i < ArrayLength(footer_dropdown); i++) {
288     footer_dropdown[i] =
289         ConstructHeaderFooterDropdown(header_footer_str[i].get());
290     gtk_table_attach(GTK_TABLE(header_footer_table), footer_dropdown[i], i,
291                      (i + 1), 2, 3, (GtkAttachOptions)0, (GtkAttachOptions)0, 2,
292                      2);
293   }
294   // ---
295 
296   gtk_container_add(GTK_CONTAINER(header_footer_container),
297                     header_footer_table);
298 
299   GtkWidget* header_footer_vertical_squasher = gtk_vbox_new(FALSE, 0);
300   gtk_box_pack_start(GTK_BOX(header_footer_vertical_squasher),
301                      header_footer_label, FALSE, FALSE, 0);
302   gtk_box_pack_start(GTK_BOX(header_footer_vertical_squasher),
303                      header_footer_container, FALSE, FALSE, 0);
304 
305   // Construction of everything
306   gtk_box_pack_start(GTK_BOX(custom_options_tab), check_buttons_container,
307                      FALSE, FALSE, 10);  // 10px padding
308   gtk_box_pack_start(GTK_BOX(custom_options_tab), appearance_vertical_squasher,
309                      FALSE, FALSE, 10);
310   gtk_box_pack_start(GTK_BOX(custom_options_tab),
311                      header_footer_vertical_squasher, FALSE, FALSE, 0);
312 
313   gtk_print_unix_dialog_add_custom_tab(GTK_PRINT_UNIX_DIALOG(dialog),
314                                        custom_options_tab, tab_label);
315   gtk_widget_show_all(custom_options_tab);
316 }
317 
GetUTF8FromBundle(const char * aKey)318 NS_ConvertUTF16toUTF8 nsPrintDialogWidgetGTK::GetUTF8FromBundle(
319     const char* aKey) {
320   nsAutoString intlString;
321   printBundle->GetStringFromName(aKey, intlString);
322   return NS_ConvertUTF16toUTF8(
323       intlString);  // Return the actual object so we don't lose reference
324 }
325 
OptionWidgetToString(GtkWidget * dropdown)326 const char* nsPrintDialogWidgetGTK::OptionWidgetToString(GtkWidget* dropdown) {
327   gint index = gtk_combo_box_get_active(GTK_COMBO_BOX(dropdown));
328 
329   NS_ASSERTION(index <= CUSTOM_VALUE_INDEX,
330                "Index of dropdown is higher than expected!");
331 
332   if (index == CUSTOM_VALUE_INDEX)
333     return (const char*)g_object_get_data(G_OBJECT(dropdown), "custom-text");
334   return header_footer_tags[index];
335 }
336 
Run()337 gint nsPrintDialogWidgetGTK::Run() {
338   const gint response = gtk_dialog_run(GTK_DIALOG(dialog));
339   gtk_widget_hide(dialog);
340   return response;
341 }
342 
ExportHeaderFooter(nsIPrintSettings * aNS)343 void nsPrintDialogWidgetGTK::ExportHeaderFooter(nsIPrintSettings* aNS) {
344   const char* header_footer_str;
345   header_footer_str = OptionWidgetToString(header_dropdown[0]);
346   aNS->SetHeaderStrLeft(NS_ConvertUTF8toUTF16(header_footer_str));
347 
348   header_footer_str = OptionWidgetToString(header_dropdown[1]);
349   aNS->SetHeaderStrCenter(NS_ConvertUTF8toUTF16(header_footer_str));
350 
351   header_footer_str = OptionWidgetToString(header_dropdown[2]);
352   aNS->SetHeaderStrRight(NS_ConvertUTF8toUTF16(header_footer_str));
353 
354   header_footer_str = OptionWidgetToString(footer_dropdown[0]);
355   aNS->SetFooterStrLeft(NS_ConvertUTF8toUTF16(header_footer_str));
356 
357   header_footer_str = OptionWidgetToString(footer_dropdown[1]);
358   aNS->SetFooterStrCenter(NS_ConvertUTF8toUTF16(header_footer_str));
359 
360   header_footer_str = OptionWidgetToString(footer_dropdown[2]);
361   aNS->SetFooterStrRight(NS_ConvertUTF8toUTF16(header_footer_str));
362 }
363 
ImportSettings(nsIPrintSettings * aNSSettings)364 nsresult nsPrintDialogWidgetGTK::ImportSettings(nsIPrintSettings* aNSSettings) {
365   MOZ_ASSERT(aNSSettings, "aSettings must not be null");
366   NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
367 
368   nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
369   if (!aNSSettingsGTK) return NS_ERROR_FAILURE;
370 
371   GtkPrintSettings* settings = aNSSettingsGTK->GetGtkPrintSettings();
372   GtkPageSetup* setup = aNSSettingsGTK->GetGtkPageSetup();
373 
374   bool geckoBool;
375   aNSSettings->GetShrinkToFit(&geckoBool);
376   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(shrink_to_fit_toggle),
377                                geckoBool);
378 
379   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_colors_toggle),
380                                aNSSettings->GetPrintBGColors());
381 
382   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_images_toggle),
383                                aNSSettings->GetPrintBGImages());
384 
385   gtk_print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(dialog), settings);
386   gtk_print_unix_dialog_set_page_setup(GTK_PRINT_UNIX_DIALOG(dialog), setup);
387 
388   return NS_OK;
389 }
390 
ExportSettings(nsIPrintSettings * aNSSettings)391 nsresult nsPrintDialogWidgetGTK::ExportSettings(nsIPrintSettings* aNSSettings) {
392   MOZ_ASSERT(aNSSettings, "aSettings must not be null");
393   NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
394 
395   GtkPrintSettings* settings =
396       gtk_print_unix_dialog_get_settings(GTK_PRINT_UNIX_DIALOG(dialog));
397   GtkPageSetup* setup =
398       gtk_print_unix_dialog_get_page_setup(GTK_PRINT_UNIX_DIALOG(dialog));
399   GtkPrinter* printer =
400       gtk_print_unix_dialog_get_selected_printer(GTK_PRINT_UNIX_DIALOG(dialog));
401   if (settings && setup && printer) {
402     ExportHeaderFooter(aNSSettings);
403 
404     aNSSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatNative);
405 
406     // Print-to-file is true by default. This must be turned off or else
407     // printing won't occur! (We manually copy the spool file when this flag is
408     // set, because we love our embedders) Even if it is print-to-file in GTK's
409     // case, GTK does The Right Thing when we send the job.
410     aNSSettings->SetPrintToFile(false);
411 
412     aNSSettings->SetShrinkToFit(
413         gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(shrink_to_fit_toggle)));
414 
415     aNSSettings->SetPrintBGColors(gtk_toggle_button_get_active(
416         GTK_TOGGLE_BUTTON(print_bg_colors_toggle)));
417     aNSSettings->SetPrintBGImages(gtk_toggle_button_get_active(
418         GTK_TOGGLE_BUTTON(print_bg_images_toggle)));
419 
420     // Try to save native settings in the session object
421     nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
422     if (aNSSettingsGTK) {
423       aNSSettingsGTK->SetGtkPrintSettings(settings);
424       aNSSettingsGTK->SetGtkPageSetup(setup);
425       aNSSettingsGTK->SetGtkPrinter(printer);
426       bool printSelectionOnly;
427       if (useNativeSelection) {
428         _GtkPrintPages pageSetting =
429             (_GtkPrintPages)gtk_print_settings_get_print_pages(settings);
430         printSelectionOnly = (pageSetting == _GTK_PRINT_PAGES_SELECTION);
431       } else {
432         printSelectionOnly = gtk_toggle_button_get_active(
433             GTK_TOGGLE_BUTTON(selection_only_toggle));
434       }
435       aNSSettingsGTK->SetPrintSelectionOnly(printSelectionOnly);
436     }
437   }
438 
439   if (settings) g_object_unref(settings);
440   return NS_OK;
441 }
442 
ConstructHeaderFooterDropdown(const char16_t * currentString)443 GtkWidget* nsPrintDialogWidgetGTK::ConstructHeaderFooterDropdown(
444     const char16_t* currentString) {
445   GtkWidget* dropdown = gtk_combo_box_text_new();
446   const char hf_options[][22] = {"headerFooterBlank", "headerFooterTitle",
447                                  "headerFooterURL",   "headerFooterDate",
448                                  "headerFooterPage",  "headerFooterPageTotal",
449                                  "headerFooterCustom"};
450 
451   for (unsigned int i = 0; i < ArrayLength(hf_options); i++) {
452     gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(dropdown), nullptr,
453                               GetUTF8FromBundle(hf_options[i]).get());
454   }
455 
456   bool shouldBeCustom = true;
457   NS_ConvertUTF16toUTF8 currentStringUTF8(currentString);
458 
459   for (unsigned int i = 0; i < ArrayLength(header_footer_tags); i++) {
460     if (!strcmp(currentStringUTF8.get(), header_footer_tags[i])) {
461       gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), i);
462       g_object_set_data(G_OBJECT(dropdown), "previous-active",
463                         GINT_TO_POINTER(i));
464       shouldBeCustom = false;
465       break;
466     }
467   }
468 
469   if (shouldBeCustom) {
470     gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), CUSTOM_VALUE_INDEX);
471     g_object_set_data(G_OBJECT(dropdown), "previous-active",
472                       GINT_TO_POINTER(CUSTOM_VALUE_INDEX));
473     char* custom_string = strdup(currentStringUTF8.get());
474     g_object_set_data_full(G_OBJECT(dropdown), "custom-text", custom_string,
475                            (GDestroyNotify)free);
476   }
477 
478   g_signal_connect(dropdown, "changed", (GCallback)ShowCustomDialog, dialog);
479   return dropdown;
480 }
481 
482 NS_IMPL_ISUPPORTS(nsPrintDialogServiceGTK, nsIPrintDialogService)
483 
484 nsPrintDialogServiceGTK::nsPrintDialogServiceGTK() = default;
485 
486 nsPrintDialogServiceGTK::~nsPrintDialogServiceGTK() = default;
487 
488 NS_IMETHODIMP
Init()489 nsPrintDialogServiceGTK::Init() { return NS_OK; }
490 
491 // Used to obtain window handle. The portal use this handle
492 // to ensure that print dialog is modal.
493 typedef void (*WindowHandleExported)(GtkWindow* window, const char* handle,
494                                      gpointer user_data);
495 
496 typedef void (*GtkWindowHandleExported)(GtkWindow* window, const char* handle,
497                                         gpointer user_data);
498 #ifdef MOZ_WAYLAND
499 #  if !GTK_CHECK_VERSION(3, 22, 0)
500 typedef void (*GdkWaylandWindowExported)(GdkWindow* window, const char* handle,
501                                          gpointer user_data);
502 #  endif
503 
504 typedef struct {
505   GtkWindow* window;
506   WindowHandleExported callback;
507   gpointer user_data;
508 } WaylandWindowHandleExportedData;
509 
wayland_window_handle_exported(GdkWindow * window,const char * wayland_handle_str,gpointer user_data)510 static void wayland_window_handle_exported(GdkWindow* window,
511                                            const char* wayland_handle_str,
512                                            gpointer user_data) {
513   WaylandWindowHandleExportedData* data =
514       static_cast<WaylandWindowHandleExportedData*>(user_data);
515   char* handle_str;
516 
517   handle_str = g_strdup_printf("wayland:%s", wayland_handle_str);
518   data->callback(data->window, handle_str, data->user_data);
519   g_free(handle_str);
520 }
521 #endif
522 
523 // Get window handle for the portal, taken from gtk/gtkwindow.c
524 // (currently not exported)
window_export_handle(GtkWindow * window,GtkWindowHandleExported callback,gpointer user_data)525 static gboolean window_export_handle(GtkWindow* window,
526                                      GtkWindowHandleExported callback,
527                                      gpointer user_data) {
528   if (GdkIsX11Display()) {
529     GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
530     char* handle_str;
531     guint32 xid = (guint32)gdk_x11_window_get_xid(gdk_window);
532 
533     handle_str = g_strdup_printf("x11:%x", xid);
534     callback(window, handle_str, user_data);
535     g_free(handle_str);
536     return true;
537   }
538 #ifdef MOZ_WAYLAND
539   else if (GdkIsWaylandDisplay()) {
540     GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
541     WaylandWindowHandleExportedData* data;
542 
543     data = g_new0(WaylandWindowHandleExportedData, 1);
544     data->window = window;
545     data->callback = callback;
546     data->user_data = user_data;
547 
548     static auto s_gdk_wayland_window_export_handle =
549         reinterpret_cast<gboolean (*)(GdkWindow*, GdkWaylandWindowExported,
550                                       gpointer, GDestroyNotify)>(
551             dlsym(RTLD_DEFAULT, "gdk_wayland_window_export_handle"));
552     if (!s_gdk_wayland_window_export_handle ||
553         !s_gdk_wayland_window_export_handle(
554             gdk_window, wayland_window_handle_exported, data, g_free)) {
555       g_free(data);
556       return false;
557     }
558     return true;
559   }
560 #endif
561 
562   g_warning("Couldn't export handle, unsupported windowing system");
563 
564   return false;
565 }
566 /**
567  * Communication class with the GTK print portal handler
568  *
569  * To print document from flatpak we need to use print portal because
570  * printers are not directly accessible in the sandboxed environment.
571  *
572  * At first we request portal to show the print dialog to let user choose
573  * printer settings. We use DBUS interface for that (PreparePrint method).
574  *
575  * Next we force application to print to temporary file and after the writing
576  * to the file is finished we pass its file descriptor to the portal.
577  * Portal will pass duplicate of the file descriptor to the printer which
578  * user selected before (by DBUS Print method).
579  *
580  * Since DBUS communication is done async while nsPrintDialogServiceGTK::Show
581  * is expecting sync execution, we need to create a new GMainLoop during the
582  * print portal dialog is running. The loop is stopped after the dialog
583  * is closed.
584  */
585 class nsFlatpakPrintPortal : public nsIObserver {
586   NS_DECL_ISUPPORTS
587   NS_DECL_NSIOBSERVER
588  public:
589   explicit nsFlatpakPrintPortal(nsPrintSettingsGTK* aPrintSettings);
590   nsresult PreparePrintRequest(GtkWindow* aWindow);
591   static void OnWindowExportHandleDone(GtkWindow* aWindow,
592                                        const char* aWindowHandleStr,
593                                        gpointer aUserData);
594   void PreparePrint(GtkWindow* aWindow, const char* aWindowHandleStr);
595   static void OnPreparePrintResponse(GDBusConnection* connection,
596                                      const char* sender_name,
597                                      const char* object_path,
598                                      const char* interface_name,
599                                      const char* signal_name,
600                                      GVariant* parameters, gpointer data);
601   GtkPrintOperationResult GetResult();
602 
603  private:
604   virtual ~nsFlatpakPrintPortal();
605   void FinishPrintDialog(GVariant* parameters);
606   nsCOMPtr<nsPrintSettingsGTK> mPrintAndPageSettings;
607   GDBusProxy* mProxy;
608   guint32 mToken;
609   GMainLoop* mLoop;
610   GtkPrintOperationResult mResult;
611   guint mResponseSignalId;
612   GtkWindow* mParentWindow;
613 };
614 
NS_IMPL_ISUPPORTS(nsFlatpakPrintPortal,nsIObserver)615 NS_IMPL_ISUPPORTS(nsFlatpakPrintPortal, nsIObserver)
616 
617 nsFlatpakPrintPortal::nsFlatpakPrintPortal(nsPrintSettingsGTK* aPrintSettings)
618     : mPrintAndPageSettings(aPrintSettings),
619       mProxy(nullptr),
620       mLoop(nullptr),
621       mResponseSignalId(0),
622       mParentWindow(nullptr) {}
623 
624 /**
625  * Creates GDBusProxy, query for window handle and create a new GMainLoop.
626  *
627  * The GMainLoop is to be run from GetResult() and be quitted during
628  * FinishPrintDialog.
629  *
630  * @param aWindow toplevel application window which is used as parent of print
631  *                dialog
632  */
PreparePrintRequest(GtkWindow * aWindow)633 nsresult nsFlatpakPrintPortal::PreparePrintRequest(GtkWindow* aWindow) {
634   MOZ_ASSERT(aWindow, "aWindow must not be null");
635   MOZ_ASSERT(mPrintAndPageSettings, "mPrintAndPageSettings must not be null");
636 
637   GError* error = nullptr;
638   mProxy = g_dbus_proxy_new_for_bus_sync(
639       G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, nullptr,
640       "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop",
641       "org.freedesktop.portal.Print", nullptr, &error);
642   if (mProxy == nullptr) {
643     NS_WARNING(
644         nsPrintfCString("Unable to create dbus proxy: %s", error->message)
645             .get());
646     g_error_free(error);
647     return NS_ERROR_FAILURE;
648   }
649 
650   // The window handler is returned async, we will continue by PreparePrint
651   // method when it is returned.
652   if (!window_export_handle(
653           aWindow, &nsFlatpakPrintPortal::OnWindowExportHandleDone, this)) {
654     NS_WARNING("Unable to get window handle for creating modal print dialog.");
655     return NS_ERROR_FAILURE;
656   }
657 
658   mLoop = g_main_loop_new(NULL, FALSE);
659   return NS_OK;
660 }
661 
OnWindowExportHandleDone(GtkWindow * aWindow,const char * aWindowHandleStr,gpointer aUserData)662 void nsFlatpakPrintPortal::OnWindowExportHandleDone(
663     GtkWindow* aWindow, const char* aWindowHandleStr, gpointer aUserData) {
664   nsFlatpakPrintPortal* printPortal =
665       static_cast<nsFlatpakPrintPortal*>(aUserData);
666   printPortal->PreparePrint(aWindow, aWindowHandleStr);
667 }
668 
669 /**
670  * Ask print portal to show the print dialog.
671  *
672  * Print and page settings and window handle are passed to the portal to prefill
673  * last used settings.
674  */
PreparePrint(GtkWindow * aWindow,const char * aWindowHandleStr)675 void nsFlatpakPrintPortal::PreparePrint(GtkWindow* aWindow,
676                                         const char* aWindowHandleStr) {
677   GtkPrintSettings* gtkSettings = mPrintAndPageSettings->GetGtkPrintSettings();
678   GtkPageSetup* pageSetup = mPrintAndPageSettings->GetGtkPageSetup();
679 
680   // We need to remember GtkWindow to unexport window handle after it is
681   // no longer needed by the portal dialog (apply only on non-X11 sessions).
682   if (GdkIsWaylandDisplay()) {
683     mParentWindow = aWindow;
684   }
685 
686   GVariantBuilder opt_builder;
687   g_variant_builder_init(&opt_builder, G_VARIANT_TYPE_VARDICT);
688   char* token = g_strdup_printf("mozilla%d", g_random_int_range(0, G_MAXINT));
689   g_variant_builder_add(&opt_builder, "{sv}", "handle_token",
690                         g_variant_new_string(token));
691   g_free(token);
692   GVariant* options = g_variant_builder_end(&opt_builder);
693   static auto s_gtk_print_settings_to_gvariant =
694       reinterpret_cast<GVariant* (*)(GtkPrintSettings*)>(
695           dlsym(RTLD_DEFAULT, "gtk_print_settings_to_gvariant"));
696   static auto s_gtk_page_setup_to_gvariant =
697       reinterpret_cast<GVariant* (*)(GtkPageSetup*)>(
698           dlsym(RTLD_DEFAULT, "gtk_page_setup_to_gvariant"));
699   if (!s_gtk_print_settings_to_gvariant || !s_gtk_page_setup_to_gvariant) {
700     mResult = GTK_PRINT_OPERATION_RESULT_ERROR;
701     FinishPrintDialog(nullptr);
702     return;
703   }
704 
705   // Get translated window title
706   nsCOMPtr<nsIStringBundleService> bundleSvc =
707       do_GetService(NS_STRINGBUNDLE_CONTRACTID);
708   nsCOMPtr<nsIStringBundle> printBundle;
709   bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties",
710                           getter_AddRefs(printBundle));
711   nsAutoString intlPrintTitle;
712   printBundle->GetStringFromName("printTitleGTK", intlPrintTitle);
713 
714   GError* error = nullptr;
715   GVariant* ret = g_dbus_proxy_call_sync(
716       mProxy, "PreparePrint",
717       g_variant_new(
718           "(ss@a{sv}@a{sv}@a{sv})", aWindowHandleStr,
719           NS_ConvertUTF16toUTF8(intlPrintTitle).get(),  // Title of the window
720           s_gtk_print_settings_to_gvariant(gtkSettings),
721           s_gtk_page_setup_to_gvariant(pageSetup), options),
722       G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
723   if (ret == nullptr) {
724     NS_WARNING(
725         nsPrintfCString("Unable to call dbus proxy: %s", error->message).get());
726     g_error_free(error);
727     mResult = GTK_PRINT_OPERATION_RESULT_ERROR;
728     FinishPrintDialog(nullptr);
729     return;
730   }
731 
732   const char* handle = nullptr;
733   g_variant_get(ret, "(&o)", &handle);
734   if (strcmp(aWindowHandleStr, handle) != 0) {
735     aWindowHandleStr = g_strdup(handle);
736     if (mResponseSignalId) {
737       g_dbus_connection_signal_unsubscribe(
738           g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)), mResponseSignalId);
739     }
740   }
741   mResponseSignalId = g_dbus_connection_signal_subscribe(
742       g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)),
743       "org.freedesktop.portal.Desktop", "org.freedesktop.portal.Request",
744       "Response", aWindowHandleStr, NULL, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
745       &nsFlatpakPrintPortal::OnPreparePrintResponse, this, NULL);
746 }
747 
OnPreparePrintResponse(GDBusConnection * connection,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer data)748 void nsFlatpakPrintPortal::OnPreparePrintResponse(
749     GDBusConnection* connection, const char* sender_name,
750     const char* object_path, const char* interface_name,
751     const char* signal_name, GVariant* parameters, gpointer data) {
752   nsFlatpakPrintPortal* printPortal = static_cast<nsFlatpakPrintPortal*>(data);
753   printPortal->FinishPrintDialog(parameters);
754 }
755 
756 /**
757  * When the dialog is accepted, read print and page settings and token.
758  *
759  * Token is later used for printing portal as print operation identifier.
760  * Print and page settings are modified in-place and stored to
761  * mPrintAndPageSettings.
762  */
FinishPrintDialog(GVariant * parameters)763 void nsFlatpakPrintPortal::FinishPrintDialog(GVariant* parameters) {
764   // This ends GetResult() method
765   if (mLoop) {
766     g_main_loop_quit(mLoop);
767     mLoop = nullptr;
768   }
769 
770   if (!parameters) {
771     // mResult should be already defined
772     return;
773   }
774 
775   guint32 response;
776   GVariant* options;
777 
778   g_variant_get(parameters, "(u@a{sv})", &response, &options);
779   mResult = GTK_PRINT_OPERATION_RESULT_CANCEL;
780   if (response == 0) {
781     GVariant* v =
782         g_variant_lookup_value(options, "settings", G_VARIANT_TYPE_VARDICT);
783     static auto s_gtk_print_settings_new_from_gvariant =
784         reinterpret_cast<GtkPrintSettings* (*)(GVariant*)>(
785             dlsym(RTLD_DEFAULT, "gtk_print_settings_new_from_gvariant"));
786 
787     GtkPrintSettings* printSettings = s_gtk_print_settings_new_from_gvariant(v);
788     g_variant_unref(v);
789 
790     v = g_variant_lookup_value(options, "page-setup", G_VARIANT_TYPE_VARDICT);
791     static auto s_gtk_page_setup_new_from_gvariant =
792         reinterpret_cast<GtkPageSetup* (*)(GVariant*)>(
793             dlsym(RTLD_DEFAULT, "gtk_page_setup_new_from_gvariant"));
794     GtkPageSetup* pageSetup = s_gtk_page_setup_new_from_gvariant(v);
795     g_variant_unref(v);
796 
797     g_variant_lookup(options, "token", "u", &mToken);
798 
799     // Save native settings in the session object
800     mPrintAndPageSettings->SetGtkPrintSettings(printSettings);
801     mPrintAndPageSettings->SetGtkPageSetup(pageSetup);
802 
803     // Portal consumes PDF file
804     mPrintAndPageSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatPDF);
805 
806     // We need to set to print to file
807     mPrintAndPageSettings->SetPrintToFile(true);
808 
809     mResult = GTK_PRINT_OPERATION_RESULT_APPLY;
810   }
811 }
812 
813 /**
814  * Get result of the print dialog.
815  *
816  * This call blocks until FinishPrintDialog is called.
817  *
818  */
GetResult()819 GtkPrintOperationResult nsFlatpakPrintPortal::GetResult() {
820   // If the mLoop has not been initialized we haven't go thru PreparePrint
821   // method
822   if (!NS_IsMainThread() || !mLoop) {
823     return GTK_PRINT_OPERATION_RESULT_ERROR;
824   }
825   // Calling g_main_loop_run stops current code until g_main_loop_quit is called
826   g_main_loop_run(mLoop);
827 
828   // Free resources we've allocated in order to show print dialog.
829 #ifdef MOZ_WAYLAND
830   if (mParentWindow) {
831     GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(mParentWindow));
832     static auto s_gdk_wayland_window_unexport_handle =
833         reinterpret_cast<void (*)(GdkWindow*)>(
834             dlsym(RTLD_DEFAULT, "gdk_wayland_window_unexport_handle"));
835     if (s_gdk_wayland_window_unexport_handle) {
836       s_gdk_wayland_window_unexport_handle(gdk_window);
837     }
838   }
839 #endif
840   return mResult;
841 }
842 
843 /**
844  * Send file descriptor of the file which contains document to the portal to
845  * finish the print operation.
846  */
847 NS_IMETHODIMP
Observe(nsISupports * aObject,const char * aTopic,const char16_t * aData)848 nsFlatpakPrintPortal::Observe(nsISupports* aObject, const char* aTopic,
849                               const char16_t* aData) {
850   // Check that written file match to the stored filename in case multiple
851   // print operations are in progress.
852   nsAutoString filenameStr;
853   mPrintAndPageSettings->GetToFileName(filenameStr);
854   if (!nsDependentString(aData).Equals(filenameStr)) {
855     // Different file is finished, not for this instance
856     return NS_OK;
857   }
858   int fd, idx;
859   fd = open(NS_ConvertUTF16toUTF8(filenameStr).get(), O_RDONLY | O_CLOEXEC);
860   static auto s_g_unix_fd_list_new = reinterpret_cast<GUnixFDList* (*)(void)>(
861       dlsym(RTLD_DEFAULT, "g_unix_fd_list_new"));
862   NS_ASSERTION(s_g_unix_fd_list_new,
863                "Cannot find g_unix_fd_list_new function.");
864 
865   GUnixFDList* fd_list = s_g_unix_fd_list_new();
866   static auto s_g_unix_fd_list_append =
867       reinterpret_cast<gint (*)(GUnixFDList*, gint, GError**)>(
868           dlsym(RTLD_DEFAULT, "g_unix_fd_list_append"));
869   idx = s_g_unix_fd_list_append(fd_list, fd, NULL);
870   close(fd);
871 
872   GVariantBuilder opt_builder;
873   g_variant_builder_init(&opt_builder, G_VARIANT_TYPE_VARDICT);
874   g_variant_builder_add(&opt_builder, "{sv}", "token",
875                         g_variant_new_uint32(mToken));
876   g_dbus_proxy_call_with_unix_fd_list(
877       mProxy, "Print",
878       g_variant_new("(ssh@a{sv})", "", /* window */
879                     "Print",           /* title */
880                     idx, g_variant_builder_end(&opt_builder)),
881       G_DBUS_CALL_FLAGS_NONE, -1, fd_list, NULL,
882       NULL,      // TODO portal result cb function
883       nullptr);  // data
884   g_object_unref(fd_list);
885 
886   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
887   // Let the nsFlatpakPrintPortal instance die
888   os->RemoveObserver(this, "print-to-file-finished");
889   return NS_OK;
890 }
891 
~nsFlatpakPrintPortal()892 nsFlatpakPrintPortal::~nsFlatpakPrintPortal() {
893   if (mProxy) {
894     if (mResponseSignalId) {
895       g_dbus_connection_signal_unsubscribe(
896           g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)), mResponseSignalId);
897     }
898     g_object_unref(mProxy);
899   }
900   if (mLoop) g_main_loop_quit(mLoop);
901 }
902 
903 NS_IMETHODIMP
Show(nsPIDOMWindowOuter * aParent,nsIPrintSettings * aSettings)904 nsPrintDialogServiceGTK::Show(nsPIDOMWindowOuter* aParent,
905                               nsIPrintSettings* aSettings) {
906   MOZ_ASSERT(aParent, "aParent must not be null");
907   MOZ_ASSERT(aSettings, "aSettings must not be null");
908 
909   // Check for the flatpak portal first
910   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
911   bool shouldUsePortal;
912   giovfs->ShouldUseFlatpakPortal(&shouldUsePortal);
913   if (shouldUsePortal && gtk_check_version(3, 22, 0) == nullptr) {
914     nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
915     NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
916     GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
917     NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");
918 
919     nsCOMPtr<nsPrintSettingsGTK> printSettingsGTK(do_QueryInterface(aSettings));
920     RefPtr<nsFlatpakPrintPortal> fpPrintPortal =
921         new nsFlatpakPrintPortal(printSettingsGTK);
922 
923     nsresult rv = fpPrintPortal->PreparePrintRequest(gtkParent);
924     NS_ENSURE_SUCCESS(rv, rv);
925 
926     // This blocks until nsFlatpakPrintPortal::FinishPrintDialog is called
927     GtkPrintOperationResult printDialogResult = fpPrintPortal->GetResult();
928 
929     switch (printDialogResult) {
930       case GTK_PRINT_OPERATION_RESULT_APPLY: {
931         nsCOMPtr<nsIObserverService> os =
932             mozilla::services::GetObserverService();
933         NS_ENSURE_STATE(os);
934         // Observer waits until notified that the file with the content
935         // to print has been written.
936         rv = os->AddObserver(fpPrintPortal, "print-to-file-finished", false);
937         NS_ENSURE_SUCCESS(rv, rv);
938         break;
939       }
940       case GTK_PRINT_OPERATION_RESULT_CANCEL:
941         rv = NS_ERROR_ABORT;
942         break;
943       default:
944         NS_WARNING("Unexpected response");
945         rv = NS_ERROR_ABORT;
946     }
947     return rv;
948   }
949 
950   nsPrintDialogWidgetGTK printDialog(aParent, aSettings);
951   nsresult rv = printDialog.ImportSettings(aSettings);
952 
953   NS_ENSURE_SUCCESS(rv, rv);
954 
955   const gint response = printDialog.Run();
956 
957   // Handle the result
958   switch (response) {
959     case GTK_RESPONSE_OK:  // Proceed
960       rv = printDialog.ExportSettings(aSettings);
961       break;
962 
963     case GTK_RESPONSE_CANCEL:
964     case GTK_RESPONSE_CLOSE:
965     case GTK_RESPONSE_DELETE_EVENT:
966     case GTK_RESPONSE_NONE:
967       rv = NS_ERROR_ABORT;
968       break;
969 
970     case GTK_RESPONSE_APPLY:  // Print preview
971     default:
972       NS_WARNING("Unexpected response");
973       rv = NS_ERROR_ABORT;
974   }
975   return rv;
976 }
977 
978 NS_IMETHODIMP
ShowPageSetup(nsPIDOMWindowOuter * aParent,nsIPrintSettings * aNSSettings)979 nsPrintDialogServiceGTK::ShowPageSetup(nsPIDOMWindowOuter* aParent,
980                                        nsIPrintSettings* aNSSettings) {
981   MOZ_ASSERT(aParent, "aParent must not be null");
982   MOZ_ASSERT(aNSSettings, "aSettings must not be null");
983   NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
984 
985   nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
986   NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
987   GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
988   NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");
989 
990   nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
991   if (!aNSSettingsGTK) return NS_ERROR_FAILURE;
992 
993   // We need to init the prefs here because aNSSettings in its current form is a
994   // dummy in both uses of the word
995   nsCOMPtr<nsIPrintSettingsService> psService =
996       do_GetService("@mozilla.org/gfx/printsettings-service;1");
997   if (psService) {
998     nsString printName;
999     aNSSettings->GetPrinterName(printName);
1000     if (printName.IsVoid()) {
1001       psService->GetLastUsedPrinterName(printName);
1002       aNSSettings->SetPrinterName(printName);
1003     }
1004     psService->InitPrintSettingsFromPrefs(aNSSettings, true,
1005                                           nsIPrintSettings::kInitSaveAll);
1006   }
1007 
1008   // Frustratingly, gtk_print_run_page_setup_dialog doesn't tell us whether
1009   // the user cancelled or confirmed the dialog! So to avoid needlessly
1010   // refreshing the preview when Page Setup was cancelled, we compare the
1011   // serializations of old and new settings; if they're the same, bail out.
1012   GtkPrintSettings* gtkSettings = aNSSettingsGTK->GetGtkPrintSettings();
1013   GtkPageSetup* oldPageSetup = aNSSettingsGTK->GetGtkPageSetup();
1014   GKeyFile* oldKeyFile = g_key_file_new();
1015   gtk_page_setup_to_key_file(oldPageSetup, oldKeyFile, nullptr);
1016   gsize oldLength;
1017   gchar* oldData = g_key_file_to_data(oldKeyFile, &oldLength, nullptr);
1018   g_key_file_free(oldKeyFile);
1019 
1020   GtkPageSetup* newPageSetup =
1021       gtk_print_run_page_setup_dialog(gtkParent, oldPageSetup, gtkSettings);
1022 
1023   GKeyFile* newKeyFile = g_key_file_new();
1024   gtk_page_setup_to_key_file(newPageSetup, newKeyFile, nullptr);
1025   gsize newLength;
1026   gchar* newData = g_key_file_to_data(newKeyFile, &newLength, nullptr);
1027   g_key_file_free(newKeyFile);
1028 
1029   bool unchanged =
1030       (oldLength == newLength && !memcmp(oldData, newData, oldLength));
1031   g_free(oldData);
1032   g_free(newData);
1033   if (unchanged) {
1034     g_object_unref(newPageSetup);
1035     return NS_ERROR_ABORT;
1036   }
1037 
1038   aNSSettingsGTK->SetGtkPageSetup(newPageSetup);
1039 
1040   // Now newPageSetup has a refcount of 2 (SetGtkPageSetup will addref), put it
1041   // to 1 so if this gets replaced we don't leak.
1042   g_object_unref(newPageSetup);
1043 
1044   if (psService)
1045     psService->SavePrintSettingsToPrefs(
1046         aNSSettings, true,
1047         nsIPrintSettings::kInitSaveOrientation |
1048             nsIPrintSettings::kInitSavePaperSize |
1049             nsIPrintSettings::kInitSaveUnwriteableMargins);
1050 
1051   return NS_OK;
1052 }
1053