author | Jan Horak <jhorak@redhat.com> |
Thu, 05 Apr 2018 16:44:35 +0200 | |
changeset 414049 | c470cb1ec203f838faabf078fc8c2e16fafecc1d |
parent 414048 | e2d6c52a232e4d3a4d80719b97d53c42bfc09d64 |
child 414050 | a6bd62970a9fb094f223d99fbdf3dac25ec2f42d |
push id | 33857 |
push user | ncsoregi@mozilla.com |
push date | Tue, 17 Apr 2018 21:54:38 +0000 |
treeherder | mozilla-central@1a1223d74b7b [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | stransky |
bugs | 1411589 |
milestone | 61.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/widget/gtk/nsPrintDialogGTK.cpp +++ b/widget/gtk/nsPrintDialogGTK.cpp @@ -19,17 +19,30 @@ #include "nsIFile.h" #include "nsIStringBundle.h" #include "nsIPrintSettingsService.h" #include "nsIDOMWindow.h" #include "nsPIDOMWindow.h" #include "nsIBaseWindow.h" #include "nsIDocShellTreeItem.h" #include "nsIDocShell.h" +#include "nsIGIOService.h" #include "WidgetUtils.h" +#include "nsIObserverService.h" + +// for gdk_x11_window_get_xid +#include <gdk/gdkx.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <gio/gunixfdlist.h> + +// for dlsym +#include <dlfcn.h> +#include "MainThreadUtils.h" using namespace mozilla; using namespace mozilla::widget; static const char header_footer_tags[][4] = {"", "&T", "&U", "&D", "&P", "&PT"}; #define CUSTOM_VALUE_INDEX gint(ArrayLength(header_footer_tags)) @@ -508,24 +521,525 @@ nsPrintDialogServiceGTK::~nsPrintDialogS } NS_IMETHODIMP nsPrintDialogServiceGTK::Init() { return NS_OK; } +// Used to obtain window handle. The portal use this handle +// to ensure that print dialog is modal. +typedef void (*WindowHandleExported) (GtkWindow *window, + const char *handle, + gpointer user_data); + +typedef void (*GtkWindowHandleExported) (GtkWindow *window, + const char *handle, + gpointer user_data); +#ifdef MOZ_WAYLAND +typedef struct { + GtkWindow *window; + WindowHandleExported callback; + gpointer user_data; +} WaylandWindowHandleExportedData; + +static void +wayland_window_handle_exported (GdkWindow *window, + const char *wayland_handle_str, + gpointer user_data) +{ + WaylandWindowHandleExportedData *data = + static_cast<WaylandWindowHandleExportedData*>(user_data); + char *handle_str; + + handle_str = g_strdup_printf ("wayland:%s", wayland_handle_str); + data->callback (data->window, handle_str, data->user_data); + g_free (handle_str); +} +#endif + +// Get window handle for the portal, taken from gtk/gtkwindow.c +// (currently not exported) +static gboolean +window_export_handle(GtkWindow *window, + GtkWindowHandleExported callback, + gpointer user_data) +{ + if (GDK_IS_X11_DISPLAY(gtk_widget_get_display(GTK_WIDGET(window)))) + { + GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window)); + char *handle_str; + guint32 xid = (guint32) gdk_x11_window_get_xid(gdk_window); + + handle_str = g_strdup_printf("x11:%x", xid); + callback(window, handle_str, user_data); + g_free(handle_str); + return true; + } +#ifdef MOZ_WAYLAND + else + { + GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window)); + WaylandWindowHandleExportedData *data; + + data = g_new0(WaylandWindowHandleExportedData, 1); + data->window = window; + data->callback = callback; + data->user_data = user_data; + + static auto s_gdk_wayland_window_export_handle = + reinterpret_cast<gboolean (*)(GdkWindow*, GdkWaylandWindowExported, + gpointer, GDestroyNotify)> + (dlsym(RTLD_DEFAULT, "gdk_wayland_window_export_handle")); + if (!s_gdk_wayland_window_export_handle || + !s_gdk_wayland_window_export_handle(gdk_window, + wayland_window_handle_exported, + data, g_free)) { + g_free (data); + return false; + } else { + return true; + } + } +#endif + + g_warning("Couldn't export handle, unsupported windowing system"); + + return false; +} +/** + * Communication class with the GTK print portal handler + * + * To print document from flatpak we need to use print portal because + * printers are not directly accessible in the sandboxed environment. + * + * At first we request portal to show the print dialog to let user choose + * printer settings. We use DBUS interface for that (PreparePrint method). + * + * Next we force application to print to temporary file and after the writing + * to the file is finished we pass its file descriptor to the portal. + * Portal will pass duplicate of the file descriptor to the printer which + * user selected before (by DBUS Print method). + * + * Since DBUS communication is done async while nsPrintDialogServiceGTK::Show + * is expecting sync execution, we need to create a new GMainLoop during the + * print portal dialog is running. The loop is stopped after the dialog + * is closed. + */ +class nsFlatpakPrintPortal: public nsIObserver +{ + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + public: + explicit nsFlatpakPrintPortal(nsPrintSettingsGTK* aPrintSettings); + nsresult PreparePrintRequest(GtkWindow* aWindow); + static void OnWindowExportHandleDone(GtkWindow *aWindow, + const char* aWindowHandleStr, + gpointer aUserData); + void PreparePrint(GtkWindow* aWindow, const char* aWindowHandleStr); + static void OnPreparePrintResponse(GDBusConnection *connection, + const char *sender_name, + const char *object_path, + const char *interface_name, + const char *signal_name, + GVariant *parameters, + gpointer data); + GtkPrintOperationResult GetResult(); + private: + virtual ~nsFlatpakPrintPortal(); + void FinishPrintDialog(GVariant* parameters); + nsCOMPtr<nsPrintSettingsGTK> mPrintAndPageSettings; + GDBusProxy* mProxy; + guint32 mToken; + GMainLoop* mLoop; + GtkPrintOperationResult mResult; + guint mResponseSignalId; + GtkWindow* mParentWindow; +}; + +NS_IMPL_ISUPPORTS(nsFlatpakPrintPortal, nsIObserver) + +nsFlatpakPrintPortal::nsFlatpakPrintPortal(nsPrintSettingsGTK* aPrintSettings): + mPrintAndPageSettings(aPrintSettings), + mProxy(nullptr), + mLoop(nullptr), + mParentWindow(nullptr) +{ +} + +/** + * Creates GDBusProxy, query for window handle and create a new GMainLoop. + * + * The GMainLoop is to be run from GetResult() and be quitted during + * FinishPrintDialog. + * + * @param aWindow toplevel application window which is used as parent of print + * dialog + */ +nsresult +nsFlatpakPrintPortal::PreparePrintRequest(GtkWindow* aWindow) +{ + NS_PRECONDITION(aWindow, "aWindow must not be null"); + NS_PRECONDITION(mPrintAndPageSettings, "mPrintAndPageSettings must not be null"); + + GError* error = nullptr; + mProxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + nullptr, + "org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Print", + nullptr, + &error); + if (mProxy == nullptr) { + NS_WARNING(nsPrintfCString("Unable to create dbus proxy: %s", error->message).get()); + g_error_free(error); + return NS_ERROR_FAILURE; + } + + // The window handler is returned async, we will continue by PreparePrint method + // when it is returned. + if (!window_export_handle(aWindow, + &nsFlatpakPrintPortal::OnWindowExportHandleDone, this)) { + NS_WARNING("Unable to get window handle for creating modal print dialog."); + return NS_ERROR_FAILURE; + } + + mLoop = g_main_loop_new (NULL, FALSE); + return NS_OK; +} + +void +nsFlatpakPrintPortal::OnWindowExportHandleDone(GtkWindow* aWindow, + const char* aWindowHandleStr, + gpointer aUserData) +{ + nsFlatpakPrintPortal* printPortal = static_cast<nsFlatpakPrintPortal*>(aUserData); + printPortal->PreparePrint(aWindow, aWindowHandleStr); +} + +/** + * Ask print portal to show the print dialog. + * + * Print and page settings and window handle are passed to the portal to prefill + * last used settings. + */ +void +nsFlatpakPrintPortal::PreparePrint(GtkWindow* aWindow, const char* aWindowHandleStr) +{ + GtkPrintSettings* gtkSettings = mPrintAndPageSettings->GetGtkPrintSettings(); + GtkPageSetup* pageSetup = mPrintAndPageSettings->GetGtkPageSetup(); + + // We need to remember GtkWindow to unexport window handle after it is + // no longer needed by the portal dialog (apply only on non-X11 sessions). + if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) { + mParentWindow = aWindow; + } + + GVariantBuilder opt_builder; + g_variant_builder_init(&opt_builder, G_VARIANT_TYPE_VARDICT); + char* token = g_strdup_printf("mozilla%d", g_random_int_range (0, G_MAXINT)); + g_variant_builder_add(&opt_builder, "{sv}", "handle_token", + g_variant_new_string(token)); + g_free(token); + GVariant* options = g_variant_builder_end(&opt_builder); + static auto s_gtk_print_settings_to_gvariant = + reinterpret_cast<GVariant* (*)(GtkPrintSettings*)> + (dlsym(RTLD_DEFAULT, "gtk_print_settings_to_gvariant")); + static auto s_gtk_page_setup_to_gvariant = + reinterpret_cast<GVariant* (*)(GtkPageSetup *)> + (dlsym(RTLD_DEFAULT, "gtk_page_setup_to_gvariant")); + if (!s_gtk_print_settings_to_gvariant || !s_gtk_page_setup_to_gvariant) { + mResult = GTK_PRINT_OPERATION_RESULT_ERROR; + FinishPrintDialog(nullptr); + return; + } + + // Get translated window title + nsCOMPtr<nsIStringBundleService> bundleSvc = + do_GetService(NS_STRINGBUNDLE_CONTRACTID); + nsCOMPtr<nsIStringBundle> printBundle; + bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties", + getter_AddRefs(printBundle)); + nsAutoString intlPrintTitle; + printBundle->GetStringFromName("printTitleGTK", intlPrintTitle); + + GError* error = nullptr; + GVariant *ret = g_dbus_proxy_call_sync(mProxy, + "PreparePrint", + g_variant_new ("(ss@a{sv}@a{sv}@a{sv})", + aWindowHandleStr, + NS_ConvertUTF16toUTF8(intlPrintTitle).get(), // Title of the window + s_gtk_print_settings_to_gvariant(gtkSettings), + s_gtk_page_setup_to_gvariant(pageSetup), + options), + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + &error); + if (ret == nullptr) { + NS_WARNING(nsPrintfCString("Unable to call dbus proxy: %s", error->message).get()); + g_error_free (error); + mResult = GTK_PRINT_OPERATION_RESULT_ERROR; + FinishPrintDialog(nullptr); + return; + } + + const char* handle = nullptr; + g_variant_get (ret, "(&o)", &handle); + if (strcmp (aWindowHandleStr, handle) != 0) + { + aWindowHandleStr = g_strdup (handle); + g_dbus_connection_signal_unsubscribe( + g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)), mResponseSignalId); + } + mResponseSignalId = + g_dbus_connection_signal_subscribe( + g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)), + "org.freedesktop.portal.Desktop", + "org.freedesktop.portal.Request", + "Response", + aWindowHandleStr, + NULL, + G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, + &nsFlatpakPrintPortal::OnPreparePrintResponse, + this, NULL); + +} + +void +nsFlatpakPrintPortal::OnPreparePrintResponse(GDBusConnection *connection, + const char *sender_name, + const char *object_path, + const char *interface_name, + const char *signal_name, + GVariant *parameters, + gpointer data) +{ + nsFlatpakPrintPortal* printPortal = static_cast<nsFlatpakPrintPortal*>(data); + printPortal->FinishPrintDialog(parameters); +} + +/** + * When the dialog is accepted, read print and page settings and token. + * + * Token is later used for printing portal as print operation identifier. + * Print and page settings are modified in-place and stored to + * mPrintAndPageSettings. + */ +void +nsFlatpakPrintPortal::FinishPrintDialog(GVariant* parameters) +{ + // This ends GetResult() method + if (mLoop) { + g_main_loop_quit (mLoop); + mLoop = nullptr; + } + + if (!parameters) { + // mResult should be already defined + return; + } + + guint32 response; + GVariant *options; + + g_variant_get (parameters, "(u@a{sv})", &response, &options); + mResult = GTK_PRINT_OPERATION_RESULT_CANCEL; + if (response == 0) { + GVariant *v; + + char *filename; + char *uri; + v = g_variant_lookup_value (options, "settings", G_VARIANT_TYPE_VARDICT); + static auto s_gtk_print_settings_new_from_gvariant = + reinterpret_cast<GtkPrintSettings* (*)(GVariant*)> + (dlsym(RTLD_DEFAULT, "gtk_print_settings_new_from_gvariant")); + + GtkPrintSettings* printSettings = s_gtk_print_settings_new_from_gvariant(v); + g_variant_unref (v); + + v = g_variant_lookup_value (options, "page-setup", G_VARIANT_TYPE_VARDICT); + static auto s_gtk_page_setup_new_from_gvariant = + reinterpret_cast<GtkPageSetup* (*)(GVariant*)> + (dlsym(RTLD_DEFAULT, "gtk_page_setup_new_from_gvariant")); + GtkPageSetup* pageSetup = s_gtk_page_setup_new_from_gvariant(v); + g_variant_unref (v); + + g_variant_lookup (options, "token", "u", &mToken); + + // Force printing to file because only filedescriptor of the file + // can be passed to portal + int fd = g_file_open_tmp("gtkprintXXXXXX", &filename, NULL); + uri = g_filename_to_uri(filename, NULL, NULL); + gtk_print_settings_set(printSettings, GTK_PRINT_SETTINGS_OUTPUT_URI, uri); + g_free (uri); + close (fd); + + // Save native settings in the session object + mPrintAndPageSettings->SetGtkPrintSettings(printSettings); + mPrintAndPageSettings->SetGtkPageSetup(pageSetup); + + // Portal consumes PDF file + mPrintAndPageSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatPDF); + + // We need to set to print to file + mPrintAndPageSettings->SetPrintToFile(true); + + mResult = GTK_PRINT_OPERATION_RESULT_APPLY; + } +} + +/** + * Get result of the print dialog. + * + * This call blocks until FinishPrintDialog is called. + * + */ +GtkPrintOperationResult +nsFlatpakPrintPortal::GetResult() { + // If the mLoop has not been initialized we haven't go thru PreparePrint method + if (!NS_IsMainThread() || !mLoop) { + return GTK_PRINT_OPERATION_RESULT_ERROR; + } + // Calling g_main_loop_run stops current code until g_main_loop_quit is called + g_main_loop_run(mLoop); + + // Free resources we've allocated in order to show print dialog. +#ifdef MOZ_WAYLAND + if (mParentWindow) { + GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(mParentWindow)); + static auto s_gdk_wayland_window_unexport_handle = + reinterpret_cast<void (*)(GdkWindow*)> + (dlsym(RTLD_DEFAULT, "gdk_wayland_window_unexport_handle")); + if (s_gdk_wayland_window_unexport_handle) { + s_gdk_wayland_window_unexport_handle(gdk_window); + } + } +#endif + return mResult; +} + +/** + * Send file descriptor of the file which contains document to the portal to + * finish the print operation. + */ +NS_IMETHODIMP +nsFlatpakPrintPortal::Observe(nsISupports *aObject, const char * aTopic, + const char16_t * aData) +{ + // Check that written file match to the stored filename in case multiple + // print operations are in progress. + nsAutoString filenameStr; + mPrintAndPageSettings->GetToFileName(filenameStr); + if (!nsDependentString(aData).Equals(filenameStr)) { + // Different file is finished, not for this instance + return NS_OK; + } + int fd, idx; + fd = open(NS_ConvertUTF16toUTF8(filenameStr).get(), O_RDONLY|O_CLOEXEC); + static auto s_g_unix_fd_list_new = + reinterpret_cast<GUnixFDList* (*)(void)> + (dlsym(RTLD_DEFAULT, "g_unix_fd_list_new")); + NS_ASSERTION(s_g_unix_fd_list_new, "Cannot find g_unix_fd_list_new function."); + + GUnixFDList *fd_list = s_g_unix_fd_list_new(); + static auto s_g_unix_fd_list_append = + reinterpret_cast<gint (*)(GUnixFDList*, gint, GError**)> + (dlsym(RTLD_DEFAULT, "g_unix_fd_list_append")); + idx = s_g_unix_fd_list_append(fd_list, fd, NULL); + close(fd); + + GVariantBuilder opt_builder; + g_variant_builder_init(&opt_builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&opt_builder, "{sv}", "token", + g_variant_new_uint32(mToken)); + g_dbus_proxy_call_with_unix_fd_list( + mProxy, + "Print", + g_variant_new("(ssh@a{sv})", + "", /* window */ + "Print", /* title */ + idx, + g_variant_builder_end(&opt_builder)), + G_DBUS_CALL_FLAGS_NONE, + -1, + fd_list, + NULL, + NULL, // TODO portal result cb function + nullptr); // data + g_object_unref(fd_list); + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + // Let the nsFlatpakPrintPortal instance die + os->RemoveObserver(this, "print-to-file-finished"); + return NS_OK; +} + +nsFlatpakPrintPortal::~nsFlatpakPrintPortal() { + if (mProxy) + g_object_unref(mProxy); + if (mLoop) + g_main_loop_quit(mLoop); +} + NS_IMETHODIMP nsPrintDialogServiceGTK::Show(nsPIDOMWindowOuter *aParent, nsIPrintSettings *aSettings, nsIWebBrowserPrint *aWebBrowserPrint) { NS_PRECONDITION(aParent, "aParent must not be null"); NS_PRECONDITION(aSettings, "aSettings must not be null"); + // Check for the flatpak portal first + nsCOMPtr<nsIGIOService> giovfs = + do_GetService(NS_GIOSERVICE_CONTRACTID); + bool shouldUsePortal; + giovfs->ShouldUseFlatpakPortal(&shouldUsePortal); + if (shouldUsePortal && gtk_check_version(3, 22, 0) == nullptr) { + nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent); + NS_ASSERTION(widget, "Need a widget for dialog to be modal."); + GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget); + NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal."); + + + nsCOMPtr<nsPrintSettingsGTK> printSettingsGTK(do_QueryInterface(aSettings)); + RefPtr<nsFlatpakPrintPortal> fpPrintPortal = + new nsFlatpakPrintPortal(printSettingsGTK); + + nsresult rv = fpPrintPortal->PreparePrintRequest(gtkParent); + NS_ENSURE_SUCCESS(rv, rv); + + // This blocks until nsFlatpakPrintPortal::FinishPrintDialog is called + GtkPrintOperationResult printDialogResult = fpPrintPortal->GetResult(); + + rv = NS_OK; + switch (printDialogResult) { + case GTK_PRINT_OPERATION_RESULT_APPLY: + { + nsCOMPtr<nsIObserver> observer = do_QueryInterface(fpPrintPortal); + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + NS_ENSURE_STATE(os); + // Observer waits until notified that the file with the content + // to print has been written. + rv = os->AddObserver(observer, "print-to-file-finished", false); + NS_ENSURE_SUCCESS(rv, rv); + break; + } + case GTK_PRINT_OPERATION_RESULT_CANCEL: + rv = NS_ERROR_ABORT; + break; + default: + NS_WARNING("Unexpected response"); + rv = NS_ERROR_ABORT; + } + return rv; + } + nsPrintDialogWidgetGTK printDialog(aParent, aSettings); nsresult rv = printDialog.ImportSettings(aSettings); NS_ENSURE_SUCCESS(rv, rv); const gint response = printDialog.Run(); // Handle the result