diff -up thunderbird-60.3.0/widget/gtk/gtk3drawing.cpp.wayland thunderbird-60.3.0/widget/gtk/gtk3drawing.cpp --- thunderbird-60.3.0/widget/gtk/gtk3drawing.cpp.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/gtk3drawing.cpp 2018-11-20 12:04:43.731787368 +0100 @@ -24,7 +24,7 @@ static gboolean checkbox_check_state; static gboolean notebook_has_tab_gap; static ScrollbarGTKMetrics sScrollbarMetrics[2]; -static ScrollbarGTKMetrics sScrollbarMetricsActive[2]; +static ScrollbarGTKMetrics sActiveScrollbarMetrics[2]; static ToggleGTKMetrics sCheckboxMetrics; static ToggleGTKMetrics sRadioMetrics; static ToolbarGTKMetrics sToolbarMetrics; @@ -39,6 +39,28 @@ static ToolbarGTKMetrics sToolbarMetrics #endif static GtkBorder +operator-(const GtkBorder& first, const GtkBorder& second) +{ + GtkBorder result; + result.left = first.left - second.left; + result.right = first.right - second.right; + result.top = first.top - second.top; + result.bottom = first.bottom - second.bottom; + return result; +} + +static GtkBorder +operator+(const GtkBorder& first, const GtkBorder& second) +{ + GtkBorder result; + result.left = first.left + second.left; + result.right = first.right + second.right; + result.top = first.top + second.top; + result.bottom = first.bottom + second.bottom; + return result; +} + +static GtkBorder operator+=(GtkBorder& first, const GtkBorder& second) { first.left += second.left; @@ -84,7 +106,7 @@ moz_gtk_add_style_border(GtkStyleContext { GtkBorder border; - gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border); + gtk_style_context_get_border(style, gtk_style_context_get_state(style), &border); *left += border.left; *right += border.right; @@ -98,7 +120,7 @@ moz_gtk_add_style_padding(GtkStyleContex { GtkBorder padding; - gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding); + gtk_style_context_get_padding(style, gtk_style_context_get_state(style), &padding); *left += padding.left; *right += padding.right; @@ -193,8 +215,8 @@ moz_gtk_refresh() sScrollbarMetrics[GTK_ORIENTATION_HORIZONTAL].initialized = false; sScrollbarMetrics[GTK_ORIENTATION_VERTICAL].initialized = false; - sScrollbarMetricsActive[GTK_ORIENTATION_HORIZONTAL].initialized = false; - sScrollbarMetricsActive[GTK_ORIENTATION_VERTICAL].initialized = false; + sActiveScrollbarMetrics[GTK_ORIENTATION_HORIZONTAL].initialized = false; + sActiveScrollbarMetrics[GTK_ORIENTATION_VERTICAL].initialized = false; sCheckboxMetrics.initialized = false; sRadioMetrics.initialized = false; sToolbarMetrics.initialized = false; @@ -229,8 +251,8 @@ moz_gtk_get_focus_outline_size(GtkStyleC { GtkBorder border; GtkBorder padding; - gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border); - gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding); + gtk_style_context_get_border(style, gtk_style_context_get_state(style), &border); + gtk_style_context_get_padding(style, gtk_style_context_get_state(style), &padding); *focus_h_width = border.left + padding.left; *focus_v_width = border.top + padding.top; return MOZ_GTK_SUCCESS; @@ -688,8 +710,8 @@ calculate_button_inner_rect(GtkWidget* b style = gtk_widget_get_style_context(button); /* This mirrors gtkbutton's child positioning */ - gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border); - gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding); + gtk_style_context_get_border(style, gtk_style_context_get_state(style), &border); + gtk_style_context_get_padding(style, gtk_style_context_get_state(style), &padding); inner_rect->x = rect->x + border.left + padding.left; inner_rect->y = rect->y + padding.top + border.top; @@ -1008,19 +1030,21 @@ moz_gtk_scrollbar_thumb_paint(WidgetNode GtkTextDirection direction) { GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state); + GtkStyleContext* style = GetStyleContext(widget, direction, state_flags); + + GtkOrientation orientation = (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL) ? + GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL; GdkRectangle rect = *aRect; - GtkStyleContext* style = GetStyleContext(widget, direction, state_flags); - InsetByMargin(&rect, style); - gtk_render_slider(style, cr, - rect.x, - rect.y, - rect.width, - rect.height, - (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL) ? - GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL); + const ScrollbarGTKMetrics* metrics = + (state->depressed || state->active || state->inHover) ? + GetActiveScrollbarMetrics(orientation) : + GetScrollbarMetrics(orientation); + Inset(&rect, metrics->margin.thumb); + gtk_render_slider(style, cr, rect.x, rect.y, rect.width, rect.height, + orientation); return MOZ_GTK_SUCCESS; } @@ -1217,7 +1241,7 @@ moz_gtk_entry_paint(cairo_t *cr, GdkRect GtkStyleContext* style) { gint x = rect->x, y = rect->y, width = rect->width, height = rect->height; - int draw_focus_outline_only = state->depressed; // NS_THEME_FOCUS_OUTLINE + int draw_focus_outline_only = state->depressed; // StyleAppearance::FocusOutline if (draw_focus_outline_only) { // Inflate the given 'rect' with the focus outline size. @@ -1621,7 +1645,7 @@ moz_gtk_toolbar_separator_paint(cairo_t rect->height * (end_fraction - start_fraction)); } else { GtkBorder padding; - gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding); + gtk_style_context_get_padding(style, gtk_style_context_get_state(style), &padding); paint_width = padding.left; if (paint_width > rect->width) @@ -1790,7 +1814,7 @@ moz_gtk_get_tab_thickness(GtkStyleContex return 0; /* tabs do not overdraw the tabpanel border with "no gap" style */ GtkBorder border; - gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border); + gtk_style_context_get_border(style, gtk_style_context_get_state(style), &border); if (border.top < 2) return 2; /* some themes don't set ythickness correctly */ @@ -2127,7 +2151,7 @@ moz_gtk_menu_separator_paint(cairo_t *cr GtkBorder padding; style = GetStyleContext(MOZ_GTK_MENUSEPARATOR, direction); - gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding); + gtk_style_context_get_padding(style, gtk_style_context_get_state(style), &padding); x = rect->x; y = rect->y; @@ -2433,7 +2457,7 @@ moz_gtk_get_widget_border(WidgetNodeType NULL); if (!wide_separators) { - gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, + gtk_style_context_get_border(style, gtk_style_context_get_state(style), &border); separator_width = border.left; } @@ -2606,13 +2630,13 @@ moz_gtk_get_tab_border(gint* left, gint* } else { GtkBorder margin; - gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin); + gtk_style_context_get_margin(style, gtk_style_context_get_state(style), &margin); *left += margin.left; *right += margin.right; if (flags & MOZ_GTK_TAB_FIRST) { style = GetStyleContext(MOZ_GTK_NOTEBOOK_HEADER, direction); - gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin); + gtk_style_context_get_margin(style, gtk_style_context_get_state(style), &margin); *left += margin.left; *right += margin.right; } @@ -2687,7 +2711,7 @@ moz_gtk_get_toolbar_separator_width(gint "separator-width", &separator_width, NULL); /* Just in case... */ - gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border); + gtk_style_context_get_border(style, gtk_style_context_get_state(style), &border); *size = MAX(*size, (wide_separators ? separator_width : border.left)); return MOZ_GTK_SUCCESS; } @@ -2718,7 +2742,7 @@ moz_gtk_get_menu_separator_height(gint * gint separator_height; GtkBorder padding; GtkStyleContext* style = GetStyleContext(MOZ_GTK_MENUSEPARATOR); - gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding); + gtk_style_context_get_padding(style, gtk_style_context_get_state(style), &padding); gtk_style_context_save(style); gtk_style_context_add_class(style, GTK_STYLE_CLASS_SEPARATOR); @@ -2750,8 +2774,8 @@ moz_gtk_get_entry_min_height(gint* heigh GtkBorder border; GtkBorder padding; - gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border); - gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding); + gtk_style_context_get_border(style, gtk_style_context_get_state(style), &border); + gtk_style_context_get_padding(style, gtk_style_context_get_state(style), &padding); *height += (border.top + border.bottom + padding.top + padding.bottom); } @@ -2901,23 +2925,17 @@ GetToggleMetrics(bool isRadio) return metrics; } -const ScrollbarGTKMetrics* -GetScrollbarMetrics(GtkOrientation aOrientation, bool aActive) +static void +InitScrollbarMetrics(ScrollbarGTKMetrics* aMetrics, + GtkOrientation aOrientation, + GtkStateFlags aStateFlags) { - auto metrics = aActive ? &sScrollbarMetricsActive[aOrientation] : - &sScrollbarMetrics[aOrientation]; - if (metrics->initialized) - return metrics; - - metrics->initialized = true; - WidgetNodeType scrollbar = aOrientation == GTK_ORIENTATION_HORIZONTAL ? MOZ_GTK_SCROLLBAR_HORIZONTAL : MOZ_GTK_SCROLLBAR_VERTICAL; gboolean backward, forward, secondary_backward, secondary_forward; GtkStyleContext* style = GetStyleContext(scrollbar, GTK_TEXT_DIR_NONE, - aActive ? GTK_STATE_FLAG_PRELIGHT : - GTK_STATE_FLAG_NORMAL); + aStateFlags); gtk_style_context_get_style(style, "has-backward-stepper", &backward, "has-forward-stepper", &forward, @@ -2938,15 +2956,15 @@ GetScrollbarMetrics(GtkOrientation aOrie "min-slider-length", &min_slider_size, nullptr); - metrics->size.thumb = + aMetrics->size.thumb = SizeFromLengthAndBreadth(aOrientation, min_slider_size, slider_width); - metrics->size.button = + aMetrics->size.button = SizeFromLengthAndBreadth(aOrientation, stepper_size, slider_width); // overall scrollbar gint breadth = slider_width + 2 * trough_border; // Require room for the slider in the track if we don't have buttons. gint length = hasButtons ? 0 : min_slider_size + 2 * trough_border; - metrics->size.scrollbar = + aMetrics->size.scrollbar = SizeFromLengthAndBreadth(aOrientation, length, breadth); // Borders on the major axis are set on the outermost scrollbar @@ -2956,23 +2974,24 @@ GetScrollbarMetrics(GtkOrientation aOrie // receives mouse events, as in GTK. // Other borders have been zero-initialized. if (aOrientation == GTK_ORIENTATION_HORIZONTAL) { - metrics->border.scrollbar.left = - metrics->border.scrollbar.right = - metrics->border.track.top = - metrics->border.track.bottom = trough_border; + aMetrics->border.scrollbar.left = + aMetrics->border.scrollbar.right = + aMetrics->border.track.top = + aMetrics->border.track.bottom = trough_border; } else { - metrics->border.scrollbar.top = - metrics->border.scrollbar.bottom = - metrics->border.track.left = - metrics->border.track.right = trough_border; + aMetrics->border.scrollbar.top = + aMetrics->border.scrollbar.bottom = + aMetrics->border.track.left = + aMetrics->border.track.right = trough_border; } - return metrics; + // We're done here for Gtk+ < 3.20... + return; } // GTK version > 3.20 // scrollbar - metrics->border.scrollbar = GetMarginBorderPadding(style); + aMetrics->border.scrollbar = GetMarginBorderPadding(style); WidgetNodeType contents, track, thumb; if (aOrientation == GTK_ORIENTATION_HORIZONTAL) { @@ -3003,72 +3022,102 @@ GetScrollbarMetrics(GtkOrientation aOrie */ // thumb - style = CreateStyleContextWithStates(thumb, GTK_TEXT_DIR_NONE, - aActive ? GTK_STATE_FLAG_PRELIGHT : - GTK_STATE_FLAG_NORMAL); - metrics->size.thumb = GetMinMarginBox(style); + style = CreateStyleContextWithStates(thumb, GTK_TEXT_DIR_NONE, aStateFlags); + aMetrics->size.thumb = GetMinMarginBox(style); + gtk_style_context_get_margin(style, gtk_style_context_get_state(style), + &aMetrics->margin.thumb); g_object_unref(style); // track - style = CreateStyleContextWithStates(track, GTK_TEXT_DIR_NONE, - aActive ? GTK_STATE_FLAG_PRELIGHT : - GTK_STATE_FLAG_NORMAL); - metrics->border.track = GetMarginBorderPadding(style); - MozGtkSize trackMinSize = GetMinContentBox(style) + metrics->border.track; - MozGtkSize trackSizeForThumb = metrics->size.thumb + metrics->border.track; + style = CreateStyleContextWithStates(track, GTK_TEXT_DIR_NONE, aStateFlags); + aMetrics->border.track = GetMarginBorderPadding(style); + MozGtkSize trackMinSize = GetMinContentBox(style) + aMetrics->border.track; + MozGtkSize trackSizeForThumb = aMetrics->size.thumb + aMetrics->border.track; g_object_unref(style); // button if (hasButtons) { style = CreateStyleContextWithStates(MOZ_GTK_SCROLLBAR_BUTTON, - GTK_TEXT_DIR_NONE, - aActive ? GTK_STATE_FLAG_PRELIGHT : - GTK_STATE_FLAG_NORMAL); - metrics->size.button = GetMinMarginBox(style); + GTK_TEXT_DIR_NONE, aStateFlags); + aMetrics->size.button = GetMinMarginBox(style); g_object_unref(style); } else { - metrics->size.button = {0, 0}; + aMetrics->size.button = {0, 0}; } if (aOrientation == GTK_ORIENTATION_HORIZONTAL) { - metrics->size.button.Rotate(); + aMetrics->size.button.Rotate(); // If the track is wider than necessary for the thumb, including when // the buttons will cause Gecko to expand the track to fill // available breadth, then add to the track border to prevent Gecko // from expanding the thumb to fill available breadth. gint extra = std::max(trackMinSize.height, - metrics->size.button.height) - trackSizeForThumb.height; + aMetrics->size.button.height) - trackSizeForThumb.height; if (extra > 0) { // If extra is odd, then the thumb is 0.5 pixels above // center as in gtk_range_compute_slider_position(). - metrics->border.track.top += extra / 2; - metrics->border.track.bottom += extra - extra / 2; + aMetrics->border.track.top += extra / 2; + aMetrics->border.track.bottom += extra - extra / 2; // Update size for change in border. trackSizeForThumb.height += extra; } } else { gint extra = std::max(trackMinSize.width, - metrics->size.button.width) - trackSizeForThumb.width; + aMetrics->size.button.width) - trackSizeForThumb.width; if (extra > 0) { // If extra is odd, then the thumb is 0.5 pixels to the left // of center as in gtk_range_compute_slider_position(). - metrics->border.track.left += extra / 2; - metrics->border.track.right += extra - extra / 2; + aMetrics->border.track.left += extra / 2; + aMetrics->border.track.right += extra - extra / 2; trackSizeForThumb.width += extra; } } style = CreateStyleContextWithStates(contents, GTK_TEXT_DIR_NONE, - aActive ? GTK_STATE_FLAG_PRELIGHT : - GTK_STATE_FLAG_NORMAL); + aStateFlags); GtkBorder contentsBorder = GetMarginBorderPadding(style); g_object_unref(style); - metrics->size.scrollbar = - trackSizeForThumb + contentsBorder + metrics->border.scrollbar; + aMetrics->size.scrollbar = + trackSizeForThumb + contentsBorder + aMetrics->border.scrollbar; +} + +const ScrollbarGTKMetrics* +GetScrollbarMetrics(GtkOrientation aOrientation) +{ + auto metrics = &sScrollbarMetrics[aOrientation]; + if (!metrics->initialized) { + InitScrollbarMetrics(metrics, aOrientation, GTK_STATE_FLAG_NORMAL); + + // We calculate thumb margin here because it's composited from + // thumb class margin + difference margin between active and inactive + // scrollbars. It's a workaround which alows us to emulate + // overlay scrollbars for some Gtk+ themes (Ubuntu/Ambiance), + // when an inactive scrollbar thumb is smaller than the active one. + const ScrollbarGTKMetrics *metricsActive = + GetActiveScrollbarMetrics(aOrientation); + + if (metrics->size.thumb < metricsActive->size.thumb) { + metrics->margin.thumb += + (metrics->border.scrollbar + metrics->border.track) - + (metricsActive->border.scrollbar + metricsActive->border.track); + } - return metrics; + metrics->initialized = true; + } + return metrics; +} + +const ScrollbarGTKMetrics* +GetActiveScrollbarMetrics(GtkOrientation aOrientation) +{ + auto metrics = &sActiveScrollbarMetrics[aOrientation]; + if (!metrics->initialized) { + InitScrollbarMetrics(metrics, aOrientation, GTK_STATE_FLAG_PRELIGHT); + metrics->initialized = true; + } + return metrics; } /* @@ -3078,6 +3127,16 @@ GetScrollbarMetrics(GtkOrientation aOrie bool GetCSDDecorationSize(GtkWindow *aGtkWindow, GtkBorder* aDecorationSize) { + // Available on GTK 3.20+. + static auto sGtkRenderBackgroundGetClip = + (void (*)(GtkStyleContext*, gdouble, gdouble, gdouble, gdouble, GdkRectangle*)) + dlsym(RTLD_DEFAULT, "gtk_render_background_get_clip"); + + if (!sGtkRenderBackgroundGetClip) { + *aDecorationSize = {0,0,0,0}; + return false; + } + GtkStyleContext* context = gtk_widget_get_style_context(GTK_WIDGET(aGtkWindow)); bool solidDecorations = gtk_style_context_has_class(context, "solid-csd"); context = GetStyleContext(solidDecorations ? @@ -3091,54 +3150,32 @@ GetCSDDecorationSize(GtkWindow *aGtkWind gtk_style_context_get_padding(context, state, &padding); *aDecorationSize += padding; - // Available on GTK 3.20+. - static auto sGtkRenderBackgroundGetClip = - (void (*)(GtkStyleContext*, gdouble, gdouble, gdouble, gdouble, GdkRectangle*)) - dlsym(RTLD_DEFAULT, "gtk_render_background_get_clip"); GtkBorder margin; gtk_style_context_get_margin(context, state, &margin); - GtkBorder extents = {0, 0, 0, 0}; - if (sGtkRenderBackgroundGetClip) { - /* Get shadow extents but combine with style margin; use the bigger value. - */ - GdkRectangle clip; - sGtkRenderBackgroundGetClip(context, 0, 0, 0, 0, &clip); - - extents.top = -clip.y; - extents.right = clip.width + clip.x; - extents.bottom = clip.height + clip.y; - extents.left = -clip.x; - - // Margin is used for resize grip size - it's not present on - // popup windows. - if (gtk_window_get_window_type(aGtkWindow) != GTK_WINDOW_POPUP) { - extents.top = MAX(extents.top, margin.top); - extents.right = MAX(extents.right, margin.right); - extents.bottom = MAX(extents.bottom, margin.bottom); - extents.left = MAX(extents.left, margin.left); - } - } else { - /* If we can't get shadow extents use decoration-resize-handle instead - * as a workaround. This is inspired by update_border_windows() - * from gtkwindow.c although this is not 100% accurate as we emulate - * the extents here. - */ - gint handle; - gtk_widget_style_get(GetWidget(MOZ_GTK_WINDOW), - "decoration-resize-handle", &handle, - NULL); - - extents.top = handle + margin.top; - extents.right = handle + margin.right; - extents.bottom = handle + margin.bottom; - extents.left = handle + margin.left; + /* Get shadow extents but combine with style margin; use the bigger value. + */ + GdkRectangle clip; + sGtkRenderBackgroundGetClip(context, 0, 0, 0, 0, &clip); + + GtkBorder extents; + extents.top = -clip.y; + extents.right = clip.width + clip.x; + extents.bottom = clip.height + clip.y; + extents.left = -clip.x; + + // Margin is used for resize grip size - it's not present on + // popup windows. + if (gtk_window_get_window_type(aGtkWindow) != GTK_WINDOW_POPUP) { + extents.top = MAX(extents.top, margin.top); + extents.right = MAX(extents.right, margin.right); + extents.bottom = MAX(extents.bottom, margin.bottom); + extents.left = MAX(extents.left, margin.left); } *aDecorationSize += extents; - - return (sGtkRenderBackgroundGetClip != nullptr); + return true; } /* cairo_t *cr argument has to be a system-cairo. */ diff -up thunderbird-60.3.0/widget/gtk/GtkCompositorWidget.cpp.wayland thunderbird-60.3.0/widget/gtk/GtkCompositorWidget.cpp --- thunderbird-60.3.0/widget/gtk/GtkCompositorWidget.cpp.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/GtkCompositorWidget.cpp 2018-11-20 12:04:43.731787368 +0100 @@ -40,7 +40,9 @@ GtkCompositorWidget::GtkCompositorWidget // Grab the window's visual and depth XWindowAttributes windowAttrs; - XGetWindowAttributes(mXDisplay, mXWindow, &windowAttrs); + if (!XGetWindowAttributes(mXDisplay, mXWindow, &windowAttrs)) { + NS_WARNING("GtkCompositorWidget(): XGetWindowAttributes() failed!"); + } Visual* visual = windowAttrs.visual; int depth = windowAttrs.depth; @@ -50,8 +52,8 @@ GtkCompositorWidget::GtkCompositorWidget mXDisplay, mXWindow, visual, - depth - ); + depth, + aInitData.Shaped()); } mClientSize = aInitData.InitialClientSize(); } diff -up thunderbird-60.3.0/widget/gtk/gtkdrawing.h.wayland thunderbird-60.3.0/widget/gtk/gtkdrawing.h --- thunderbird-60.3.0/widget/gtk/gtkdrawing.h.wayland 2018-10-30 12:45:37.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/gtkdrawing.h 2018-11-20 12:04:43.731787368 +0100 @@ -83,6 +83,9 @@ typedef struct { GtkBorder scrollbar; GtkBorder track; } border; + struct { + GtkBorder thumb; + } margin; } ScrollbarGTKMetrics; typedef struct { @@ -502,11 +505,17 @@ moz_gtk_get_scalethumb_metrics(GtkOrient /** * Get the metrics in GTK pixels for a scrollbar. * aOrientation: [IN] the scrollbar orientation - * aActive: [IN] Metricts for scrollbar with mouse pointer over it. - * */ const ScrollbarGTKMetrics* -GetScrollbarMetrics(GtkOrientation aOrientation, bool aActive = false); +GetScrollbarMetrics(GtkOrientation aOrientation); + +/** + * Get the metrics in GTK pixels for a scrollbar which is active + * (selected by mouse pointer). + * aOrientation: [IN] the scrollbar orientation + */ +const ScrollbarGTKMetrics* +GetActiveScrollbarMetrics(GtkOrientation aOrientation); /** * Get the desired size of a dropdown arrow button diff -up thunderbird-60.3.0/widget/gtk/IMContextWrapper.cpp.wayland thunderbird-60.3.0/widget/gtk/IMContextWrapper.cpp --- thunderbird-60.3.0/widget/gtk/IMContextWrapper.cpp.wayland 2018-10-30 12:45:34.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/IMContextWrapper.cpp 2018-11-20 12:04:43.732787365 +0100 @@ -14,6 +14,7 @@ #include "mozilla/Likely.h" #include "mozilla/MiscEvents.h" #include "mozilla/Preferences.h" +#include "mozilla/Telemetry.h" #include "mozilla/TextEventDispatcher.h" #include "mozilla/TextEvents.h" #include "WritingModes.h" @@ -59,6 +60,73 @@ GetEventType(GdkEventKey* aKeyEvent) } } +class GetEventStateName : public nsAutoCString +{ +public: + explicit GetEventStateName(guint aState, + IMContextWrapper::IMContextID aIMContextID = + IMContextWrapper::IMContextID::eUnknown) + { + if (aState & GDK_SHIFT_MASK) { + AppendModifier("shift"); + } + if (aState & GDK_CONTROL_MASK) { + AppendModifier("control"); + } + if (aState & GDK_MOD1_MASK) { + AppendModifier("mod1"); + } + if (aState & GDK_MOD2_MASK) { + AppendModifier("mod2"); + } + if (aState & GDK_MOD3_MASK) { + AppendModifier("mod3"); + } + if (aState & GDK_MOD4_MASK) { + AppendModifier("mod4"); + } + if (aState & GDK_MOD4_MASK) { + AppendModifier("mod5"); + } + if (aState & GDK_MOD4_MASK) { + AppendModifier("mod5"); + } + switch (aIMContextID) { + case IMContextWrapper::IMContextID::eIBus: + static const guint IBUS_HANDLED_MASK = 1 << 24; + static const guint IBUS_IGNORED_MASK = 1 << 25; + if (aState & IBUS_HANDLED_MASK) { + AppendModifier("IBUS_HANDLED_MASK"); + } + if (aState & IBUS_IGNORED_MASK) { + AppendModifier("IBUS_IGNORED_MASK"); + } + break; + case IMContextWrapper::IMContextID::eFcitx: + static const guint FcitxKeyState_HandledMask = 1 << 24; + static const guint FcitxKeyState_IgnoredMask = 1 << 25; + if (aState & FcitxKeyState_HandledMask) { + AppendModifier("FcitxKeyState_HandledMask"); + } + if (aState & FcitxKeyState_IgnoredMask) { + AppendModifier("FcitxKeyState_IgnoredMask"); + } + break; + default: + break; + } + } + +private: + void AppendModifier(const char* aModifierName) + { + if (!IsEmpty()) { + AppendLiteral(" + "); + } + Append(aModifierName); + } +}; + class GetWritingModeName : public nsAutoCString { public: @@ -281,12 +349,17 @@ IMContextWrapper::IMContextWrapper(nsWin , mCompositionStart(UINT32_MAX) , mProcessingKeyEvent(nullptr) , mCompositionState(eCompositionState_NotComposing) + , mIMContextID(IMContextID::eUnknown) , mIsIMFocused(false) + , mFallbackToKeyEvent(false) + , mKeyboardEventWasDispatched(false) , mIsDeletingSurrounding(false) , mLayoutChanged(false) , mSetCursorPositionOnKeyEvent(true) , mPendingResettingIMContext(false) , mRetrieveSurroundingSignalReceived(false) + , mMaybeInDeadKeySequence(false) + , mIsIMInAsyncKeyHandlingMode(false) { static bool sFirstInstance = true; if (sFirstInstance) { @@ -299,15 +372,101 @@ IMContextWrapper::IMContextWrapper(nsWin Init(); } +static bool +IsIBusInSyncMode() +{ + // See ibus_im_context_class_init() in client/gtk2/ibusimcontext.c + // https://github.com/ibus/ibus/blob/86963f2f94d1e4fc213b01c2bc2ba9dcf4b22219/client/gtk2/ibusimcontext.c#L610 + const char* env = PR_GetEnv("IBUS_ENABLE_SYNC_MODE"); + + // See _get_boolean_env() in client/gtk2/ibusimcontext.c + // https://github.com/ibus/ibus/blob/86963f2f94d1e4fc213b01c2bc2ba9dcf4b22219/client/gtk2/ibusimcontext.c#L520-L537 + if (!env) { + return false; + } + nsDependentCString envStr(env); + if (envStr.IsEmpty() || + envStr.EqualsLiteral("0") || + envStr.EqualsLiteral("false") || + envStr.EqualsLiteral("False") || + envStr.EqualsLiteral("FALSE")) { + return false; + } + return true; +} + +static bool +GetFcitxBoolEnv(const char* aEnv) +{ + // See fcitx_utils_get_boolean_env in src/lib/fcitx-utils/utils.c + // https://github.com/fcitx/fcitx/blob/0c87840dc7d9460c2cb5feaeefec299d0d3d62ec/src/lib/fcitx-utils/utils.c#L721-L736 + const char* env = PR_GetEnv(aEnv); + if (!env) { + return false; + } + nsDependentCString envStr(env); + if (envStr.IsEmpty() || + envStr.EqualsLiteral("0") || + envStr.EqualsLiteral("false")) { + return false; + } + return true; +} + +static bool +IsFcitxInSyncMode() +{ + // See fcitx_im_context_class_init() in src/frontend/gtk2/fcitximcontext.c + // https://github.com/fcitx/fcitx/blob/78b98d9230dc9630e99d52e3172bdf440ffd08c4/src/frontend/gtk2/fcitximcontext.c#L395-L398 + return GetFcitxBoolEnv("IBUS_ENABLE_SYNC_MODE") || + GetFcitxBoolEnv("FCITX_ENABLE_SYNC_MODE"); +} + +nsDependentCSubstring +IMContextWrapper::GetIMName() const +{ + const char* contextIDChar = + gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(mContext)); + if (!contextIDChar) { + return nsDependentCSubstring(); + } + + nsDependentCSubstring im(contextIDChar, strlen(contextIDChar)); + + // If the context is XIM, actual engine must be specified with + // |XMODIFIERS=@im=foo|. + const char* xmodifiersChar = PR_GetEnv("XMODIFIERS"); + if (!im.EqualsLiteral("xim") || !xmodifiersChar) { + return im; + } + + nsDependentCString xmodifiers(xmodifiersChar); + int32_t atIMValueStart = xmodifiers.Find("@im=") + 4; + if (atIMValueStart < 4 || + xmodifiers.Length() <= static_cast(atIMValueStart)) { + return im; + } + + int32_t atIMValueEnd = + xmodifiers.Find("@", false, atIMValueStart); + if (atIMValueEnd > atIMValueStart) { + return nsDependentCSubstring(xmodifiersChar + atIMValueStart, + atIMValueEnd - atIMValueStart); + } + + if (atIMValueEnd == kNotFound) { + return nsDependentCSubstring(xmodifiersChar + atIMValueStart, + strlen(xmodifiersChar) - atIMValueStart); + } + + return im; +} + void IMContextWrapper::Init() { - MOZ_LOG(gGtkIMLog, LogLevel::Info, - ("0x%p Init(), mOwnerWindow=0x%p", - this, mOwnerWindow)); - MozContainer* container = mOwnerWindow->GetMozContainer(); - NS_PRECONDITION(container, "container is null"); + MOZ_ASSERT(container, "container is null"); GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container)); // Overwrite selection colors of the window before associating the window @@ -333,6 +492,47 @@ IMContextWrapper::Init() G_CALLBACK(IMContextWrapper::OnStartCompositionCallback), this); g_signal_connect(mContext, "preedit_end", G_CALLBACK(IMContextWrapper::OnEndCompositionCallback), this); + nsDependentCSubstring im = GetIMName(); + if (im.EqualsLiteral("ibus")) { + mIMContextID = IMContextID::eIBus; + mIsIMInAsyncKeyHandlingMode = !IsIBusInSyncMode(); + // Although ibus has key snooper mode, it's forcibly disabled on Firefox + // in default settings by its whitelist since we always send key events + // to IME before handling shortcut keys. The whitelist can be + // customized with env, IBUS_NO_SNOOPER_APPS, but we don't need to + // support such rare cases for reducing maintenance cost. + mIsKeySnooped = false; + } else if (im.EqualsLiteral("fcitx")) { + mIMContextID = IMContextID::eFcitx; + mIsIMInAsyncKeyHandlingMode = !IsFcitxInSyncMode(); + // Although Fcitx has key snooper mode similar to ibus, it's also + // disabled on Firefox in default settings by its whitelist. The + // whitelist can be customized with env, IBUS_NO_SNOOPER_APPS or + // FCITX_NO_SNOOPER_APPS, but we don't need to support such rare cases + // for reducing maintenance cost. + mIsKeySnooped = false; + } else if (im.EqualsLiteral("uim")) { + mIMContextID = IMContextID::eUim; + mIsIMInAsyncKeyHandlingMode = false; + // We cannot know if uim uses key snooper since it's build option of + // uim. Therefore, we need to retrieve the consideration from the + // pref for making users and distributions allowed to choose their + // preferred value. + mIsKeySnooped = + Preferences::GetBool("intl.ime.hack.uim.using_key_snooper", true); + } else if (im.EqualsLiteral("scim")) { + mIMContextID = IMContextID::eScim; + mIsIMInAsyncKeyHandlingMode = false; + mIsKeySnooped = false; + } else if (im.EqualsLiteral("iiim")) { + mIMContextID = IMContextID::eIIIMF; + mIsIMInAsyncKeyHandlingMode = false; + mIsKeySnooped = false; + } else { + mIMContextID = IMContextID::eUnknown; + mIsIMInAsyncKeyHandlingMode = false; + mIsKeySnooped = false; + } // Simple context if (sUseSimpleContext) { @@ -361,6 +561,18 @@ IMContextWrapper::Init() // Dummy context mDummyContext = gtk_im_multicontext_new(); gtk_im_context_set_client_window(mDummyContext, gdkWindow); + + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p Init(), mOwnerWindow=%p, mContext=%p (im=\"%s\"), " + "mIsIMInAsyncKeyHandlingMode=%s, mIsKeySnooped=%s, " + "mSimpleContext=%p, mDummyContext=%p, " + "gtk_im_multicontext_get_context_id()=\"%s\", " + "PR_GetEnv(\"XMODIFIERS\")=\"%s\"", + this, mOwnerWindow, mContext, nsAutoCString(im).get(), + ToChar(mIsIMInAsyncKeyHandlingMode), ToChar(mIsKeySnooped), + mSimpleContext, mDummyContext, + gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(mContext)), + PR_GetEnv("XMODIFIERS"))); } /* static */ @@ -469,7 +681,7 @@ IMContextWrapper::OnDestroyWindow(nsWind "mOwnerWindow=0x%p, mLastFocusedModule=0x%p", this, aWindow, mLastFocusedWindow, mOwnerWindow, sLastFocusedContext)); - NS_PRECONDITION(aWindow, "aWindow must not be null"); + MOZ_ASSERT(aWindow, "aWindow must not be null"); if (mLastFocusedWindow == aWindow) { EndIMEComposition(aWindow); @@ -524,46 +736,46 @@ IMContextWrapper::OnDestroyWindow(nsWind mOwnerWindow = nullptr; mLastFocusedWindow = nullptr; mInputContext.mIMEState.mEnabled = IMEState::DISABLED; + mPostingKeyEvents.Clear(); MOZ_LOG(gGtkIMLog, LogLevel::Debug, ("0x%p OnDestroyWindow(), succeeded, Completely destroyed", this)); } -// Work around gtk bug http://bugzilla.gnome.org/show_bug.cgi?id=483223: -// (and the similar issue of GTK+ IIIM) -// The GTK+ XIM and IIIM modules register handlers for the "closed" signal -// on the display, but: -// * The signal handlers are not disconnected when the module is unloaded. -// -// The GTK+ XIM module has another problem: -// * When the signal handler is run (with the module loaded) it tries -// XFree (and fails) on a pointer that did not come from Xmalloc. -// -// To prevent these modules from being unloaded, use static variables to -// hold ref of GtkIMContext class. -// For GTK+ XIM module, to prevent the signal handler from being run, -// find the signal handlers and remove them. -// -// GtkIMContextXIMs share XOpenIM connections and display closed signal -// handlers (where possible). - void IMContextWrapper::PrepareToDestroyContext(GtkIMContext* aContext) { - GtkIMContext *slave = nullptr; //TODO GTK3 - if (!slave) { - return; - } - - GType slaveType = G_TYPE_FROM_INSTANCE(slave); - const gchar *im_type_name = g_type_name(slaveType); - if (strcmp(im_type_name, "GtkIMContextIIIM") == 0) { - // Add a reference to prevent the IIIM module from being unloaded - static gpointer gtk_iiim_context_class = - g_type_class_ref(slaveType); - // Mute unused variable warning: - (void)gtk_iiim_context_class; + if (mIMContextID == IMContextID::eIIIMF) { + // IIIM module registers handlers for the "closed" signal on the + // display, but the signal handler is not disconnected when the module + // is unloaded. To prevent the module from being unloaded, use static + // variable to hold reference of slave context class declared by IIIM. + // Note that this does not grab any instance, it grabs the "class". + static gpointer sGtkIIIMContextClass = nullptr; + if (!sGtkIIIMContextClass) { + // We retrieved slave context class with g_type_name() and actual + // slave context instance when our widget was GTK2. That must be + // _GtkIMContext::priv::slave in GTK3. However, _GtkIMContext::priv + // is an opacity struct named _GtkIMMulticontextPrivate, i.e., it's + // not exposed by GTK3. Therefore, we cannot access the instance + // safely. So, we need to retrieve the slave context class with + // g_type_from_name("GtkIMContextIIIM") directly (anyway, we needed + // to compare the class name with "GtkIMContextIIIM"). + GType IIMContextType = g_type_from_name("GtkIMContextIIIM"); + if (IIMContextType) { + sGtkIIIMContextClass = g_type_class_ref(IIMContextType); + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p PrepareToDestroyContext(), added to reference to " + "GtkIMContextIIIM class to prevent it from being unloaded", + this)); + } else { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p PrepareToDestroyContext(), FAILED to prevent the " + "IIIM module from being uploaded", + this)); + } + } } } @@ -603,9 +815,9 @@ IMContextWrapper::OnBlurWindow(nsWindow* bool IMContextWrapper::OnKeyEvent(nsWindow* aCaller, GdkEventKey* aEvent, - bool aKeyDownEventWasSent /* = false */) + bool aKeyboardEventWasDispatched /* = false */) { - NS_PRECONDITION(aEvent, "aEvent must be non-null"); + MOZ_ASSERT(aEvent, "aEvent must be non-null"); if (!mInputContext.mIMEState.MaybeEditable() || MOZ_UNLIKELY(IsDestroyed())) { @@ -613,13 +825,24 @@ IMContextWrapper::OnKeyEvent(nsWindow* a } MOZ_LOG(gGtkIMLog, LogLevel::Info, - ("0x%p OnKeyEvent(aCaller=0x%p, aKeyDownEventWasSent=%s), " - "mCompositionState=%s, current context=0x%p, active context=0x%p, " - "aEvent(0x%p): { type=%s, keyval=%s, unicode=0x%X }", - this, aCaller, ToChar(aKeyDownEventWasSent), + ("0x%p OnKeyEvent(aCaller=0x%p, " + "aEvent(0x%p): { type=%s, keyval=%s, unicode=0x%X, state=%s, " + "time=%u, hardware_keycode=%u, group=%u }, " + "aKeyboardEventWasDispatched=%s)", + this, aCaller, aEvent, GetEventType(aEvent), + gdk_keyval_name(aEvent->keyval), + gdk_keyval_to_unicode(aEvent->keyval), + GetEventStateName(aEvent->state, mIMContextID).get(), + aEvent->time, aEvent->hardware_keycode, aEvent->group, + ToChar(aKeyboardEventWasDispatched))); + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnKeyEvent(), mMaybeInDeadKeySequence=%s, " + "mCompositionState=%s, current context=%p, active context=%p, " + "mIMContextID=%s, mIsIMInAsyncKeyHandlingMode=%s", + this, ToChar(mMaybeInDeadKeySequence), GetCompositionStateName(), GetCurrentContext(), GetActiveContext(), - aEvent, GetEventType(aEvent), gdk_keyval_name(aEvent->keyval), - gdk_keyval_to_unicode(aEvent->keyval))); + GetIMContextIDName(mIMContextID), + ToChar(mIsIMInAsyncKeyHandlingMode))); if (aCaller != mLastFocusedWindow) { MOZ_LOG(gGtkIMLog, LogLevel::Error, @@ -644,49 +867,158 @@ IMContextWrapper::OnKeyEvent(nsWindow* a mSetCursorPositionOnKeyEvent = false; } - mKeyDownEventWasSent = aKeyDownEventWasSent; - mFilterKeyEvent = true; + // Let's support dead key event even if active keyboard layout also + // supports complicated composition like CJK IME. + bool isDeadKey = + KeymapWrapper::ComputeDOMKeyNameIndex(aEvent) == KEY_NAME_INDEX_Dead; + mMaybeInDeadKeySequence |= isDeadKey; + + // If current context is mSimpleContext, both ibus and fcitx handles key + // events synchronously. So, only when current context is mContext which + // is GtkIMMulticontext, the key event may be handled by IME asynchronously. + bool maybeHandledAsynchronously = + mIsIMInAsyncKeyHandlingMode && currentContext == mContext; + + // If IM is ibus or fcitx and it handles key events asynchronously, + // they mark aEvent->state as "handled by me" when they post key event + // to another process. Unfortunately, we need to check this hacky + // flag because it's difficult to store all pending key events by + // an array or a hashtable. + if (maybeHandledAsynchronously) { + switch (mIMContextID) { + case IMContextID::eIBus: + // ibus won't send back key press events in a dead key sequcne. + if (mMaybeInDeadKeySequence && aEvent->type == GDK_KEY_PRESS) { + maybeHandledAsynchronously = false; + break; + } + // ibus handles key events synchronously if focused editor is + // or |ime-mode: disabled;|. + if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) { + maybeHandledAsynchronously = false; + break; + } + // See src/ibustypes.h + static const guint IBUS_IGNORED_MASK = 1 << 25; + // If IBUS_IGNORED_MASK was set to aEvent->state, the event + // has already been handled by another process and it wasn't + // used by IME. + if (aEvent->state & IBUS_IGNORED_MASK) { + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnKeyEvent(), aEvent->state has " + "IBUS_IGNORED_MASK, so, it won't be handled " + "asynchronously anymore. Removing posted events from " + "the queue", + this)); + maybeHandledAsynchronously = false; + mPostingKeyEvents.RemoveEvent(aEvent); + break; + } + break; + case IMContextID::eFcitx: + // fcitx won't send back key press events in a dead key sequcne. + if (mMaybeInDeadKeySequence && aEvent->type == GDK_KEY_PRESS) { + maybeHandledAsynchronously = false; + break; + } + + // fcitx handles key events asynchronously even if focused + // editor cannot use IME actually. + + // See src/lib/fcitx-utils/keysym.h + static const guint FcitxKeyState_IgnoredMask = 1 << 25; + // If FcitxKeyState_IgnoredMask was set to aEvent->state, + // the event has already been handled by another process and + // it wasn't used by IME. + if (aEvent->state & FcitxKeyState_IgnoredMask) { + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnKeyEvent(), aEvent->state has " + "FcitxKeyState_IgnoredMask, so, it won't be handled " + "asynchronously anymore. Removing posted events from " + "the queue", + this)); + maybeHandledAsynchronously = false; + mPostingKeyEvents.RemoveEvent(aEvent); + break; + } + break; + default: + MOZ_ASSERT_UNREACHABLE("IME may handle key event " + "asyncrhonously, but not yet confirmed if it comes agian " + "actually"); + } + } + + mKeyboardEventWasDispatched = aKeyboardEventWasDispatched; + mFallbackToKeyEvent = false; mProcessingKeyEvent = aEvent; gboolean isFiltered = gtk_im_context_filter_keypress(currentContext, aEvent); - mProcessingKeyEvent = nullptr; - // We filter the key event if the event was not committed (because - // it's probably part of a composition) or if the key event was - // committed _and_ changed. This way we still let key press - // events go through as simple key press events instead of - // composed characters. - bool filterThisEvent = isFiltered && mFilterKeyEvent; - - if (IsComposingOnCurrentContext() && !isFiltered) { - if (aEvent->type == GDK_KEY_PRESS) { - if (!mDispatchedCompositionString.IsEmpty()) { - // If there is composition string, we shouldn't dispatch - // any keydown events during composition. - filterThisEvent = true; - } else { - // A Hangul input engine for SCIM doesn't emit preedit_end - // signal even when composition string becomes empty. On the - // other hand, we should allow to make composition with empty - // string for other languages because there *might* be such - // IM. For compromising this issue, we should dispatch - // compositionend event, however, we don't need to reset IM - // actually. - DispatchCompositionCommitEvent(currentContext, &EmptyString()); - filterThisEvent = false; - } - } else { - // Key release event may not be consumed by IM, however, we - // shouldn't dispatch any keyup event during composition. - filterThisEvent = true; + // The caller of this shouldn't handle aEvent anymore if we've dispatched + // composition events or modified content with other events. + bool filterThisEvent = isFiltered && !mFallbackToKeyEvent; + + if (IsComposingOnCurrentContext() && !isFiltered && + aEvent->type == GDK_KEY_PRESS && + mDispatchedCompositionString.IsEmpty()) { + // A Hangul input engine for SCIM doesn't emit preedit_end + // signal even when composition string becomes empty. On the + // other hand, we should allow to make composition with empty + // string for other languages because there *might* be such + // IM. For compromising this issue, we should dispatch + // compositionend event, however, we don't need to reset IM + // actually. + // NOTE: Don't dispatch key events as "processed by IME" since + // we need to dispatch keyboard events as IME wasn't handled it. + mProcessingKeyEvent = nullptr; + DispatchCompositionCommitEvent(currentContext, &EmptyString()); + mProcessingKeyEvent = aEvent; + // In this case, even though we handle the keyboard event here, + // but we should dispatch keydown event as + filterThisEvent = false; + } + + if (filterThisEvent && !mKeyboardEventWasDispatched) { + // If IME handled the key event but we've not dispatched eKeyDown nor + // eKeyUp event yet, we need to dispatch here unless the key event is + // now being handled by other IME process. + if (!maybeHandledAsynchronously) { + MaybeDispatchKeyEventAsProcessedByIME(eVoidEvent); + // Be aware, the widget might have been gone here. + } + // If we need to wait reply from IM, IM may send some signals to us + // without sending the key event again. In such case, we need to + // dispatch keyboard events with a copy of aEvent. Therefore, we + // need to use information of this key event to dispatch an KeyDown + // or eKeyUp event later. + else { + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnKeyEvent(), putting aEvent into the queue...", + this)); + mPostingKeyEvents.PutEvent(aEvent); } } + mProcessingKeyEvent = nullptr; + + if (aEvent->type == GDK_KEY_PRESS && !filterThisEvent) { + // If the key event hasn't been handled by active IME nor keyboard + // layout, we can assume that the dead key sequence has been or was + // ended. Note that we should not reset it when the key event is + // GDK_KEY_RELEASE since it may not be filtered by active keyboard + // layout even in composition. + mMaybeInDeadKeySequence = false; + } + MOZ_LOG(gGtkIMLog, LogLevel::Debug, ("0x%p OnKeyEvent(), succeeded, filterThisEvent=%s " - "(isFiltered=%s, mFilterKeyEvent=%s), mCompositionState=%s", + "(isFiltered=%s, mFallbackToKeyEvent=%s, " + "maybeHandledAsynchronously=%s), mCompositionState=%s, " + "mMaybeInDeadKeySequence=%s", this, ToChar(filterThisEvent), ToChar(isFiltered), - ToChar(mFilterKeyEvent), GetCompositionStateName())); + ToChar(mFallbackToKeyEvent), ToChar(maybeHandledAsynchronously), + GetCompositionStateName(), ToChar(mMaybeInDeadKeySequence))); return filterThisEvent; } @@ -1005,6 +1337,10 @@ IMContextWrapper::Focus() sLastFocusedContext = this; + // Forget all posted key events when focus is moved since they shouldn't + // be fired in different editor. + mPostingKeyEvents.Clear(); + gtk_im_context_focus_in(currentContext); mIsIMFocused = true; mSetCursorPositionOnKeyEvent = true; @@ -1400,6 +1736,7 @@ IMContextWrapper::OnCommitCompositionNat { const gchar emptyStr = 0; const gchar *commitString = aUTF8Char ? aUTF8Char : &emptyStr; + NS_ConvertUTF8toUTF16 utf16CommitString(commitString); MOZ_LOG(gGtkIMLog, LogLevel::Info, ("0x%p OnCommitCompositionNative(aContext=0x%p), " @@ -1422,7 +1759,7 @@ IMContextWrapper::OnCommitCompositionNat // signal, we would dispatch compositionstart, text, compositionend // events with empty string. Of course, they are unnecessary events // for Web applications and our editor. - if (!IsComposingOn(aContext) && !commitString[0]) { + if (!IsComposingOn(aContext) && utf16CommitString.IsEmpty()) { MOZ_LOG(gGtkIMLog, LogLevel::Warning, ("0x%p OnCommitCompositionNative(), Warning, does nothing " "because has not started composition and commit string is empty", @@ -1431,11 +1768,14 @@ IMContextWrapper::OnCommitCompositionNat } // If IME doesn't change their keyevent that generated this commit, - // don't send it through XIM - just send it as a normal key press - // event. + // we should treat that IME didn't handle the key event because + // web applications want to receive "keydown" and "keypress" event + // in such case. // NOTE: While a key event is being handled, this might be caused on // current context. Otherwise, this may be caused on active context. - if (!IsComposingOn(aContext) && mProcessingKeyEvent && + if (!IsComposingOn(aContext) && + mProcessingKeyEvent && + mProcessingKeyEvent->type == GDK_KEY_PRESS && aContext == GetCurrentContext()) { char keyval_utf8[8]; /* should have at least 6 bytes of space */ gint keyval_utf8_len; @@ -1445,12 +1785,80 @@ IMContextWrapper::OnCommitCompositionNat keyval_utf8_len = g_unichar_to_utf8(keyval_unicode, keyval_utf8); keyval_utf8[keyval_utf8_len] = '\0'; + // If committing string is exactly same as a character which is + // produced by the key, eKeyDown and eKeyPress event should be + // dispatched by the caller of OnKeyEvent() normally. Note that + // mMaybeInDeadKeySequence will be set to false by OnKeyEvent() + // since we set mFallbackToKeyEvent to true here. if (!strcmp(commitString, keyval_utf8)) { MOZ_LOG(gGtkIMLog, LogLevel::Info, ("0x%p OnCommitCompositionNative(), " "we'll send normal key event", this)); - mFilterKeyEvent = false; + mFallbackToKeyEvent = true; + return; + } + + // If we're in a dead key sequence, commit string is a character in + // the BMP and mProcessingKeyEvent produces some characters but it's + // not same as committing string, we should dispatch an eKeyPress + // event from here. + WidgetKeyboardEvent keyDownEvent(true, eKeyDown, + mLastFocusedWindow); + KeymapWrapper::InitKeyEvent(keyDownEvent, mProcessingKeyEvent, false); + if (mMaybeInDeadKeySequence && + utf16CommitString.Length() == 1 && + keyDownEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) { + mKeyboardEventWasDispatched = true; + // Anyway, we're not in dead key sequence anymore. + mMaybeInDeadKeySequence = false; + + RefPtr dispatcher = GetTextEventDispatcher(); + nsresult rv = dispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gGtkIMLog, LogLevel::Error, + ("0x%p OnCommitCompositionNative(), FAILED, " + "due to BeginNativeInputTransaction() failure", + this)); + return; + } + + // First, dispatch eKeyDown event. + keyDownEvent.mKeyValue = utf16CommitString; + nsEventStatus status = nsEventStatus_eIgnore; + bool dispatched = + dispatcher->DispatchKeyboardEvent(eKeyDown, keyDownEvent, + status, mProcessingKeyEvent); + if (!dispatched || status == nsEventStatus_eConsumeNoDefault) { + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnCommitCompositionNative(), " + "doesn't dispatch eKeyPress event because the preceding " + "eKeyDown event was not dispatched or was consumed", + this)); + return; + } + if (mLastFocusedWindow != keyDownEvent.mWidget || + mLastFocusedWindow->Destroyed()) { + MOZ_LOG(gGtkIMLog, LogLevel::Warning, + ("0x%p OnCommitCompositionNative(), Warning, " + "stop dispatching eKeyPress event because the preceding " + "eKeyDown event caused changing focused widget or " + "destroyed", + this)); + return; + } + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnCommitCompositionNative(), " + "dispatched eKeyDown event for the committed character", + this)); + + // Next, dispatch eKeyPress event. + dispatcher->MaybeDispatchKeypressEvents(keyDownEvent, status, + mProcessingKeyEvent); + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p OnCommitCompositionNative(), " + "dispatched eKeyPress event for the committed character", + this)); return; } } @@ -1470,7 +1878,7 @@ IMContextWrapper::GetCompositionString(G gtk_im_context_get_preedit_string(aContext, &preedit_string, &feedback_list, &cursor_pos); if (preedit_string && *preedit_string) { - CopyUTF8toUTF16(preedit_string, aCompositionString); + CopyUTF8toUTF16(MakeStringSpan(preedit_string), aCompositionString); } else { aCompositionString.Truncate(); } @@ -1485,6 +1893,173 @@ IMContextWrapper::GetCompositionString(G } bool +IMContextWrapper::MaybeDispatchKeyEventAsProcessedByIME( + EventMessage aFollowingEvent) +{ + if (!mLastFocusedWindow) { + return false; + } + + if (!mIsKeySnooped && + ((!mProcessingKeyEvent && mPostingKeyEvents.IsEmpty()) || + (mProcessingKeyEvent && mKeyboardEventWasDispatched))) { + return true; + } + + // A "keydown" or "keyup" event handler may change focus with the + // following event. In such case, we need to cancel this composition. + // So, we need to store IM context now because mComposingContext may be + // overwritten with different context if calling this method recursively. + // Note that we don't need to grab the context here because |context| + // will be used only for checking if it's same as mComposingContext. + GtkIMContext* oldCurrentContext = GetCurrentContext(); + GtkIMContext* oldComposingContext = mComposingContext; + + RefPtr lastFocusedWindow(mLastFocusedWindow); + + if (mProcessingKeyEvent || !mPostingKeyEvents.IsEmpty()) { + if (mProcessingKeyEvent) { + mKeyboardEventWasDispatched = true; + } + // If we're not handling a key event synchronously, the signal may be + // sent by IME without sending key event to us. In such case, we + // should dispatch keyboard event for the last key event which was + // posted to other IME process. + GdkEventKey* sourceEvent = + mProcessingKeyEvent ? mProcessingKeyEvent : + mPostingKeyEvents.GetFirstEvent(); + + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p MaybeDispatchKeyEventAsProcessedByIME(" + "aFollowingEvent=%s), dispatch %s %s " + "event: { type=%s, keyval=%s, unicode=0x%X, state=%s, " + "time=%u, hardware_keycode=%u, group=%u }", + this, ToChar(aFollowingEvent), + ToChar(sourceEvent->type == GDK_KEY_PRESS ? eKeyDown : eKeyUp), + mProcessingKeyEvent ? "processing" : "posted", + GetEventType(sourceEvent), gdk_keyval_name(sourceEvent->keyval), + gdk_keyval_to_unicode(sourceEvent->keyval), + GetEventStateName(sourceEvent->state, mIMContextID).get(), + sourceEvent->time, sourceEvent->hardware_keycode, + sourceEvent->group)); + + // Let's dispatch eKeyDown event or eKeyUp event now. Note that only + // when we're not in a dead key composition, we should mark the + // eKeyDown and eKeyUp event as "processed by IME" since we should + // expose raw keyCode and key value to web apps the key event is a + // part of a dead key sequence. + // FYI: We should ignore if default of preceding keydown or keyup + // event is prevented since even on the other browsers, web + // applications cannot cancel the following composition event. + // Spec bug: https://github.com/w3c/uievents/issues/180 + bool isCancelled; + lastFocusedWindow->DispatchKeyDownOrKeyUpEvent(sourceEvent, + !mMaybeInDeadKeySequence, + &isCancelled); + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), keydown or keyup " + "event is dispatched", + this)); + + if (!mProcessingKeyEvent) { + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), removing first " + "event from the queue", + this)); + mPostingKeyEvents.RemoveEvent(sourceEvent); + } + } else { + MOZ_ASSERT(mIsKeySnooped); + // Currently, we support key snooper mode of uim only. + MOZ_ASSERT(mIMContextID == IMContextID::eUim); + // uim sends "preedit_start" signal and "preedit_changed" separately + // at starting composition, "commit" and "preedit_end" separately at + // committing composition. + + // Currently, we should dispatch only fake eKeyDown event because + // we cannot decide which is the last signal of each key operation + // and Chromium also dispatches only "keydown" event in this case. + bool dispatchFakeKeyDown = false; + switch (aFollowingEvent) { + case eCompositionStart: + case eCompositionCommit: + case eCompositionCommitAsIs: + dispatchFakeKeyDown = true; + break; + // XXX Unfortunately, I don't have a good idea to prevent to + // dispatch redundant eKeyDown event for eCompositionStart + // immediately after "delete_surrounding" signal. However, + // not dispatching eKeyDown event is worse than dispatching + // redundant eKeyDown events. + case eContentCommandDelete: + dispatchFakeKeyDown = true; + break; + // We need to prevent to dispatch redundant eKeyDown event for + // eCompositionChange immediately after eCompositionStart. So, + // We should not dispatch eKeyDown event if dispatched composition + // string is still empty string. + case eCompositionChange: + dispatchFakeKeyDown = !mDispatchedCompositionString.IsEmpty(); + break; + default: + MOZ_ASSERT_UNREACHABLE("Do you forget to handle the case?"); + break; + } + + if (dispatchFakeKeyDown) { + WidgetKeyboardEvent fakeKeyDownEvent(true, eKeyDown, + lastFocusedWindow); + fakeKeyDownEvent.mKeyCode = NS_VK_PROCESSKEY; + fakeKeyDownEvent.mKeyNameIndex = KEY_NAME_INDEX_Process; + // It's impossible to get physical key information in this case but + // this should be okay since web apps shouldn't do anything with + // physical key information during composition. + fakeKeyDownEvent.mCodeNameIndex = CODE_NAME_INDEX_UNKNOWN; + + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p MaybeDispatchKeyEventAsProcessedByIME(" + "aFollowingEvent=%s), dispatch fake eKeyDown event", + this, ToChar(aFollowingEvent))); + + bool isCancelled; + lastFocusedWindow->DispatchKeyDownOrKeyUpEvent(fakeKeyDownEvent, + &isCancelled); + MOZ_LOG(gGtkIMLog, LogLevel::Info, + ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), " + "fake keydown event is dispatched", + this)); + } + } + + if (lastFocusedWindow->IsDestroyed() || + lastFocusedWindow != mLastFocusedWindow) { + MOZ_LOG(gGtkIMLog, LogLevel::Warning, + ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), Warning, the " + "focused widget was destroyed/changed by a key event", + this)); + return false; + } + + // If the dispatched keydown event caused moving focus and that also + // caused changing active context, we need to cancel composition here. + if (GetCurrentContext() != oldCurrentContext) { + MOZ_LOG(gGtkIMLog, LogLevel::Warning, + ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), Warning, the key " + "event causes changing active IM context", + this)); + if (mComposingContext == oldComposingContext) { + // Only when the context is still composing, we should call + // ResetIME() here. Otherwise, it should've already been + // cleaned up. + ResetIME(); + } + return false; + } + + return true; +} + +bool IMContextWrapper::DispatchCompositionStart(GtkIMContext* aContext) { MOZ_LOG(gGtkIMLog, LogLevel::Info, @@ -1528,50 +2103,16 @@ IMContextWrapper::DispatchCompositionSta mCompositionStart = mSelection.mOffset; mDispatchedCompositionString.Truncate(); - if (mProcessingKeyEvent && !mKeyDownEventWasSent && - mProcessingKeyEvent->type == GDK_KEY_PRESS) { - // A keydown event handler may change focus with the following keydown - // event. In such case, we need to cancel this composition. So, we - // need to store IM context now because mComposingContext may be - // overwritten with different context if calling this method - // recursively. - // Note that we don't need to grab the context here because |context| - // will be used only for checking if it's same as mComposingContext. - GtkIMContext* context = mComposingContext; - - // If this composition is started by a native keydown event, we need to - // dispatch our keydown event here (before composition start). - bool isCancelled; - mLastFocusedWindow->DispatchKeyDownEvent(mProcessingKeyEvent, - &isCancelled); - MOZ_LOG(gGtkIMLog, LogLevel::Debug, - ("0x%p DispatchCompositionStart(), preceding keydown event is " - "dispatched", + // If this composition is started by a key press, we need to dispatch + // eKeyDown or eKeyUp event before dispatching eCompositionStart event. + // Note that dispatching a keyboard event which is marked as "processed + // by IME" is okay since Chromium also dispatches keyboard event as so. + if (!MaybeDispatchKeyEventAsProcessedByIME(eCompositionStart)) { + MOZ_LOG(gGtkIMLog, LogLevel::Warning, + ("0x%p DispatchCompositionStart(), Warning, " + "MaybeDispatchKeyEventAsProcessedByIME() returned false", this)); - if (lastFocusedWindow->IsDestroyed() || - lastFocusedWindow != mLastFocusedWindow) { - MOZ_LOG(gGtkIMLog, LogLevel::Warning, - ("0x%p DispatchCompositionStart(), Warning, the focused " - "widget was destroyed/changed by keydown event", - this)); - return false; - } - - // If the dispatched keydown event caused moving focus and that also - // caused changing active context, we need to cancel composition here. - if (GetCurrentContext() != context) { - MOZ_LOG(gGtkIMLog, LogLevel::Warning, - ("0x%p DispatchCompositionStart(), Warning, the preceding " - "keydown event causes changing active IM context", - this)); - if (mComposingContext == context) { - // Only when the context is still composing, we should call - // ResetIME() here. Otherwise, it should've already been - // cleaned up. - ResetIME(); - } - return false; - } + return false; } RefPtr dispatcher = GetTextEventDispatcher(); @@ -1584,6 +2125,25 @@ IMContextWrapper::DispatchCompositionSta return false; } + static bool sHasSetTelemetry = false; + if (!sHasSetTelemetry) { + sHasSetTelemetry = true; + NS_ConvertUTF8toUTF16 im(GetIMName()); + // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp + if (im.Length() > 72) { + if (NS_IS_LOW_SURROGATE(im[72 - 1]) && + NS_IS_HIGH_SURROGATE(im[72 - 2])) { + im.Truncate(72 - 2); + } else { + im.Truncate(72 - 1); + } + // U+2026 is "..." + im.Append(char16_t(0x2026)); + } + Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_LINUX, + im, true); + } + MOZ_LOG(gGtkIMLog, LogLevel::Debug, ("0x%p DispatchCompositionStart(), dispatching " "compositionstart... (mCompositionStart=%u)", @@ -1629,6 +2189,15 @@ IMContextWrapper::DispatchCompositionCha return false; } } + // If this composition string change caused by a key press, we need to + // dispatch eKeyDown or eKeyUp before dispatching eCompositionChange event. + else if (!MaybeDispatchKeyEventAsProcessedByIME(eCompositionChange)) { + MOZ_LOG(gGtkIMLog, LogLevel::Warning, + ("0x%p DispatchCompositionChangeEvent(), Warning, " + "MaybeDispatchKeyEventAsProcessedByIME() returned false", + this)); + return false; + } RefPtr dispatcher = GetTextEventDispatcher(); nsresult rv = dispatcher->BeginNativeInputTransaction(); @@ -1718,6 +2287,14 @@ IMContextWrapper::DispatchCompositionCom return false; } + // TODO: We need special care to handle request to commit composition + // by content while we're committing composition because we have + // commit string information now but IME may not have composition + // anymore. Therefore, we may not be able to handle commit as + // expected. However, this is rare case because this situation + // never occurs with remote content. So, it's okay to fix this + // issue later. (Perhaps, TextEventDisptcher should do it for + // all platforms. E.g., creating WillCommitComposition()?) if (!IsComposing()) { if (!aCommitString || aCommitString->IsEmpty()) { MOZ_LOG(gGtkIMLog, LogLevel::Error, @@ -1734,6 +2311,17 @@ IMContextWrapper::DispatchCompositionCom return false; } } + // If this commit caused by a key press, we need to dispatch eKeyDown or + // eKeyUp before dispatching composition events. + else if (!MaybeDispatchKeyEventAsProcessedByIME( + aCommitString ? eCompositionCommit : eCompositionCommitAsIs)) { + MOZ_LOG(gGtkIMLog, LogLevel::Warning, + ("0x%p DispatchCompositionCommitEvent(), Warning, " + "MaybeDispatchKeyEventAsProcessedByIME() returned false", + this)); + mCompositionState = eCompositionState_NotComposing; + return false; + } RefPtr dispatcher = GetTextEventDispatcher(); nsresult rv = dispatcher->BeginNativeInputTransaction(); @@ -1755,6 +2343,11 @@ IMContextWrapper::DispatchCompositionCom mSelection.mWritingMode); mCompositionState = eCompositionState_NotComposing; + // Reset dead key sequence too because GTK doesn't support dead key chain + // (i.e., a key press doesn't cause both producing some characters and + // restarting new dead key sequence at one time). So, committing + // composition means end of a dead key sequence. + mMaybeInDeadKeySequence = false; mCompositionStart = UINT32_MAX; mCompositionTargetRange.Clear(); mDispatchedCompositionString.Truncate(); @@ -2427,6 +3020,16 @@ IMContextWrapper::DeleteText(GtkIMContex this)); return NS_ERROR_FAILURE; } + + // If this deleting text caused by a key press, we need to dispatch + // eKeyDown or eKeyUp before dispatching eContentCommandDelete event. + if (!MaybeDispatchKeyEventAsProcessedByIME(eContentCommandDelete)) { + MOZ_LOG(gGtkIMLog, LogLevel::Warning, + ("0x%p DeleteText(), Warning, " + "MaybeDispatchKeyEventAsProcessedByIME() returned false", + this)); + return NS_ERROR_FAILURE; + } // Delete the selection WidgetContentCommandEvent contentCommandEvent(true, eContentCommandDelete, diff -up thunderbird-60.3.0/widget/gtk/IMContextWrapper.h.wayland thunderbird-60.3.0/widget/gtk/IMContextWrapper.h --- thunderbird-60.3.0/widget/gtk/IMContextWrapper.h.wayland 2018-10-30 12:45:34.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/IMContextWrapper.h 2018-11-20 12:04:43.733787362 +0100 @@ -70,13 +70,26 @@ public: // OnThemeChanged is called when desktop theme is changed. static void OnThemeChanged(); - // OnKeyEvent is called when aWindow gets a native key press event or a - // native key release event. If this returns TRUE, the key event was - // filtered by IME. Otherwise, this returns FALSE. - // NOTE: When the keypress event starts composition, this returns TRUE but - // this dispatches keydown event before compositionstart event. + /** + * OnKeyEvent() is called when aWindow gets a native key press event or a + * native key release event. If this returns true, the key event was + * filtered by IME. Otherwise, this returns false. + * NOTE: When the native key press event starts composition, this returns + * true but dispatches an eKeyDown event or eKeyUp event before + * dispatching composition events or content command event. + * + * @param aWindow A window on which user operate the + * key. + * @param aEvent A native key press or release + * event. + * @param aKeyboardEventWasDispatched true if eKeyDown or eKeyUp event + * for aEvent has already been + * dispatched. In this case, + * this class doesn't dispatch + * keyboard event anymore. + */ bool OnKeyEvent(nsWindow* aWindow, GdkEventKey* aEvent, - bool aKeyDownEventWasSent = false); + bool aKeyboardEventWasDispatched = false); // IME related nsIWidget methods. nsresult EndIMEComposition(nsWindow* aCaller); @@ -89,6 +102,43 @@ public: TextEventDispatcher* GetTextEventDispatcher(); + // TODO: Typically, new IM comes every several years. And now, our code + // becomes really IM behavior dependent. So, perhaps, we need prefs + // to control related flags for IM developers. + enum class IMContextID : uint8_t + { + eFcitx, + eIBus, + eIIIMF, + eScim, + eUim, + eUnknown, + }; + + static const char* GetIMContextIDName(IMContextID aIMContextID) + { + switch (aIMContextID) { + case IMContextID::eFcitx: + return "eFcitx"; + case IMContextID::eIBus: + return "eIBus"; + case IMContextID::eIIIMF: + return "eIIIMF"; + case IMContextID::eScim: + return "eScim"; + case IMContextID::eUim: + return "eUim"; + default: + return "eUnknown"; + } + } + + /** + * GetIMName() returns IM name associated with mContext. If the context is + * xim, this look for actual engine from XMODIFIERS environment variable. + */ + nsDependentCSubstring GetIMName() const; + protected: ~IMContextWrapper(); @@ -144,6 +194,100 @@ protected: // event. GdkEventKey* mProcessingKeyEvent; + /** + * GdkEventKeyQueue stores *copy* of GdkEventKey instances. However, this + * must be safe to our usecase since it has |time| and the value should not + * be same as older event. + */ + class GdkEventKeyQueue final + { + public: + ~GdkEventKeyQueue() { Clear(); } + + void Clear() + { + if (!mEvents.IsEmpty()) { + RemoveEventsAt(0, mEvents.Length()); + } + } + + /** + * PutEvent() puts new event into the queue. + */ + void PutEvent(const GdkEventKey* aEvent) + { + GdkEventKey* newEvent = + reinterpret_cast( + gdk_event_copy(reinterpret_cast(aEvent))); + newEvent->state &= GDK_MODIFIER_MASK; + mEvents.AppendElement(newEvent); + } + + /** + * RemoveEvent() removes oldest same event and its preceding events + * from the queue. + */ + void RemoveEvent(const GdkEventKey* aEvent) + { + size_t index = IndexOf(aEvent); + if (NS_WARN_IF(index == mEvents.NoIndex)) { + return; + } + RemoveEventsAt(0, index + 1); + } + + /** + * FirstEvent() returns oldest event in the queue. + */ + GdkEventKey* GetFirstEvent() const + { + if (mEvents.IsEmpty()) { + return nullptr; + } + return mEvents[0]; + } + + bool IsEmpty() const { return mEvents.IsEmpty(); } + + private: + nsTArray mEvents; + + void RemoveEventsAt(size_t aStart, size_t aCount) + { + for (size_t i = aStart; i < aStart + aCount; i++) { + gdk_event_free(reinterpret_cast(mEvents[i])); + } + mEvents.RemoveElementsAt(aStart, aCount); + } + + size_t IndexOf(const GdkEventKey* aEvent) const + { + static_assert(!(GDK_MODIFIER_MASK & (1 << 24)), + "We assumes 25th bit is used by some IM, but used by GDK"); + static_assert(!(GDK_MODIFIER_MASK & (1 << 25)), + "We assumes 26th bit is used by some IM, but used by GDK"); + for (size_t i = 0; i < mEvents.Length(); i++) { + GdkEventKey* event = mEvents[i]; + // It must be enough to compare only type, time, keyval and + // part of state. Note that we cannot compaire two events + // simply since IME may have changed unused bits of state. + if (event->time == aEvent->time) { + if (NS_WARN_IF(event->type != aEvent->type) || + NS_WARN_IF(event->keyval != aEvent->keyval) || + NS_WARN_IF(event->state != + (aEvent->state & GDK_MODIFIER_MASK))) { + continue; + } + } + return i; + } + return mEvents.NoIndex; + } + }; + // OnKeyEvent() append mPostingKeyEvents when it believes that a key event + // is posted to other IME process. + GdkEventKeyQueue mPostingKeyEvents; + struct Range { uint32_t mOffset; @@ -167,7 +311,8 @@ protected: Range mCompositionTargetRange; // mCompositionState indicates current status of composition. - enum eCompositionState { + enum eCompositionState : uint8_t + { eCompositionState_NotComposing, eCompositionState_CompositionStartDispatched, eCompositionState_CompositionChangeEventDispatched @@ -219,6 +364,10 @@ protected: } } + // mIMContextID indicates the ID of mContext. This is actually indicates + // IM which user selected. + IMContextID mIMContextID; + struct Selection final { nsString mString; @@ -268,16 +417,20 @@ protected: // mIsIMFocused is set to TRUE when we call gtk_im_context_focus_in(). And // it's set to FALSE when we call gtk_im_context_focus_out(). bool mIsIMFocused; - // mFilterKeyEvent is used by OnKeyEvent(). If the commit event should - // be processed as simple key event, this is set to TRUE by the commit - // handler. - bool mFilterKeyEvent; - // mKeyDownEventWasSent is used by OnKeyEvent() and - // DispatchCompositionStart(). DispatchCompositionStart() dispatches - // a keydown event if the composition start is caused by a native - // keypress event. If this is true, the keydown event has been dispatched. - // Then, DispatchCompositionStart() doesn't dispatch keydown event. - bool mKeyDownEventWasSent; + // mFallbackToKeyEvent is set to false when this class starts to handle + // a native key event (at that time, mProcessingKeyEvent is set to the + // native event). If active IME just commits composition with a character + // which is produced by the key with current keyboard layout, this is set + // to true. + bool mFallbackToKeyEvent; + // mKeyboardEventWasDispatched is used by OnKeyEvent() and + // MaybeDispatchKeyEventAsProcessedByIME(). + // MaybeDispatchKeyEventAsProcessedByIME() dispatches an eKeyDown or + // eKeyUp event event if the composition is caused by a native + // key press event. If this is true, a keyboard event has been dispatched + // for the native event. If so, MaybeDispatchKeyEventAsProcessedByIME() + // won't dispatch keyboard event anymore. + bool mKeyboardEventWasDispatched; // mIsDeletingSurrounding is true while OnDeleteSurroundingNative() is // trying to delete the surrounding text. bool mIsDeletingSurrounding; @@ -298,6 +451,24 @@ protected: // mRetrieveSurroundingSignalReceived is true after "retrieve_surrounding" // signal is received until selection is changed in Gecko. bool mRetrieveSurroundingSignalReceived; + // mMaybeInDeadKeySequence is set to true when we detect a dead key press + // and set to false when we're sure dead key sequence has been finished. + // Note that we cannot detect which key event causes ending a dead key + // sequence. For example, when you press dead key grave with ibus Spanish + // keyboard layout, it just consumes the key event when we call + // gtk_im_context_filter_keypress(). Then, pressing "Escape" key cancels + // the dead key sequence but we don't receive any signal and it's consumed + // by gtk_im_context_filter_keypress() normally. On the other hand, when + // pressing "Shift" key causes exactly same behavior but dead key sequence + // isn't finished yet. + bool mMaybeInDeadKeySequence; + // mIsIMInAsyncKeyHandlingMode is set to true if we know that IM handles + // key events asynchronously. I.e., filtered key event may come again + // later. + bool mIsIMInAsyncKeyHandlingMode; + // mIsKeySnooped is set to true if IM uses key snooper to listen key events. + // In such case, we won't receive key events if IME consumes the event. + bool mIsKeySnooped; // sLastFocusedContext is a pointer to the last focused instance of this // class. When a instance is destroyed and sLastFocusedContext refers it, @@ -448,12 +619,31 @@ protected: * Following methods dispatch gecko events. Then, the focused widget * can be destroyed, and also it can be stolen focus. If they returns * FALSE, callers cannot continue the composition. + * - MaybeDispatchKeyEventAsProcessedByIME * - DispatchCompositionStart * - DispatchCompositionChangeEvent * - DispatchCompositionCommitEvent */ /** + * Dispatch an eKeyDown or eKeyUp event whose mKeyCode value is + * NS_VK_PROCESSKEY and mKeyNameIndex is KEY_NAME_INDEX_Process if + * we're not in a dead key sequence, mProcessingKeyEvent is nullptr + * but mPostingKeyEvents is not empty or mProcessingKeyEvent is not + * nullptr and mKeyboardEventWasDispatched is still false. If this + * dispatches a keyboard event, this sets mKeyboardEventWasDispatched + * to true. + * + * @param aFollowingEvent The following event message. + * @return If the caller can continue to handle + * composition, returns true. Otherwise, + * false. For example, if focus is moved + * by dispatched keyboard event, returns + * false. + */ + bool MaybeDispatchKeyEventAsProcessedByIME(EventMessage aFollowingEvent); + + /** * Dispatches a composition start event. * * @param aContext A GtkIMContext which is being handled. diff -up thunderbird-60.3.0/widget/gtk/InProcessGtkCompositorWidget.h.wayland thunderbird-60.3.0/widget/gtk/InProcessGtkCompositorWidget.h --- thunderbird-60.3.0/widget/gtk/InProcessGtkCompositorWidget.h.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/InProcessGtkCompositorWidget.h 2018-11-20 12:04:43.733787362 +0100 @@ -3,8 +3,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef widget_gtk_InProcessGtkCompositorWidgetParent_h -#define widget_gtk_InProcessGtkCompositorWidgetParent_h +#ifndef widget_gtk_InProcessGtkCompositorWidget_h +#define widget_gtk_InProcessGtkCompositorWidget_h #include "GtkCompositorWidget.h" @@ -28,4 +28,4 @@ public: } // namespace widget } // namespace mozilla -#endif // widget_gtk_InProcessGtkCompositorWidgetParent_h +#endif // widget_gtk_InProcessGtkCompositorWidget_h diff -up thunderbird-60.3.0/widget/gtk/moz.build.wayland thunderbird-60.3.0/widget/gtk/moz.build --- thunderbird-60.3.0/widget/gtk/moz.build.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/moz.build 2018-11-20 12:09:34.756903818 +0100 @@ -123,6 +123,7 @@ include('/ipc/chromium/chromium-config.m FINAL_LIBRARY = 'xul' LOCAL_INCLUDES += [ + '/layout/base', '/layout/generic', '/layout/xul', '/other-licenses/atk-1.0', diff -up thunderbird-60.3.0/widget/gtk/mozcontainer.cpp.wayland thunderbird-60.3.0/widget/gtk/mozcontainer.cpp --- thunderbird-60.3.0/widget/gtk/mozcontainer.cpp.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/mozcontainer.cpp 2018-11-20 12:04:43.733787362 +0100 @@ -10,6 +10,7 @@ #ifdef MOZ_WAYLAND #include #include +#include #endif #include #include @@ -207,6 +208,12 @@ moz_container_init (MozContainer *contai #if defined(MOZ_WAYLAND) { + container->subcompositor = nullptr; + container->surface = nullptr; + container->subsurface = nullptr; + container->eglwindow = nullptr; + container->parent_surface_committed = false; + GdkDisplay *gdk_display = gtk_widget_get_display(GTK_WIDGET(container)); if (GDK_IS_WAYLAND_DISPLAY (gdk_display)) { // Available as of GTK 3.8+ @@ -225,12 +232,21 @@ moz_container_init (MozContainer *contai } #if defined(MOZ_WAYLAND) +static void +moz_container_commited_handler(GdkFrameClock *clock, MozContainer *container) +{ + container->parent_surface_committed = true; + g_signal_handler_disconnect(clock, + container->parent_surface_committed_handler); + container->parent_surface_committed_handler = 0; +} + /* We want to draw to GdkWindow owned by mContainer from Compositor thread but * Gtk+ can be used in main thread only. So we create wayland wl_surface * and attach it as an overlay to GdkWindow. * * see gtk_clutter_embed_ensure_subsurface() at gtk-clutter-embed.c -* for reference. + * for reference. */ static gboolean moz_container_map_surface(MozContainer *container) @@ -242,6 +258,9 @@ moz_container_map_surface(MozContainer * static auto sGdkWaylandWindowGetWlSurface = (wl_surface *(*)(GdkWindow *)) dlsym(RTLD_DEFAULT, "gdk_wayland_window_get_wl_surface"); + static auto sGdkWindowGetFrameClock = + (GdkFrameClock *(*)(GdkWindow *)) + dlsym(RTLD_DEFAULT, "gdk_window_get_frame_clock"); GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(container)); if (GDK_IS_X11_DISPLAY(display)) @@ -250,6 +269,18 @@ moz_container_map_surface(MozContainer * if (container->subsurface && container->surface) return true; + if (!container->parent_surface_committed) { + if (!container->parent_surface_committed_handler) { + GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container)); + GdkFrameClock *clock = sGdkWindowGetFrameClock(window); + container->parent_surface_committed_handler = + g_signal_connect_after(clock, "after-paint", + G_CALLBACK(moz_container_commited_handler), + container); + } + return false; + } + if (!container->surface) { struct wl_compositor *compositor; compositor = sGdkWaylandDisplayGetWlCompositor(display); @@ -289,8 +320,22 @@ moz_container_map_surface(MozContainer * static void moz_container_unmap_surface(MozContainer *container) { + g_clear_pointer(&container->eglwindow, wl_egl_window_destroy); g_clear_pointer(&container->subsurface, wl_subsurface_destroy); g_clear_pointer(&container->surface, wl_surface_destroy); + + if (container->parent_surface_committed_handler) { + static auto sGdkWindowGetFrameClock = + (GdkFrameClock *(*)(GdkWindow *)) + dlsym(RTLD_DEFAULT, "gdk_window_get_frame_clock"); + GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container)); + GdkFrameClock *clock = sGdkWindowGetFrameClock(window); + + g_signal_handler_disconnect(clock, + container->parent_surface_committed_handler); + container->parent_surface_committed_handler = 0; + } + container->parent_surface_committed = false; } #endif @@ -434,6 +479,11 @@ moz_container_size_allocate (GtkWidget gdk_window_get_position(gtk_widget_get_window(widget), &x, &y); wl_subsurface_set_position(container->subsurface, x, y); } + if (container->eglwindow) { + wl_egl_window_resize(container->eglwindow, + allocation->width, allocation->height, + 0, 0); + } #endif } @@ -555,8 +605,40 @@ moz_container_get_wl_surface(MozContaine return nullptr; moz_container_map_surface(container); + // Set the scale factor for the buffer right after we create it. + if (container->surface) { + static auto sGdkWindowGetScaleFactorPtr = (gint (*)(GdkWindow*)) + dlsym(RTLD_DEFAULT, "gdk_window_get_scale_factor"); + if (sGdkWindowGetScaleFactorPtr && window) { + gint scaleFactor = (*sGdkWindowGetScaleFactorPtr)(window); + wl_surface_set_buffer_scale(container->surface, scaleFactor); + } + } } return container->surface; } + +struct wl_egl_window * +moz_container_get_wl_egl_window(MozContainer *container) +{ + if (!container->eglwindow) { + struct wl_surface *wlsurf = moz_container_get_wl_surface(container); + if (!wlsurf) + return nullptr; + + GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(container)); + container->eglwindow + = wl_egl_window_create(wlsurf, + gdk_window_get_width(window), + gdk_window_get_height(window)); + } + return container->eglwindow; +} + +gboolean +moz_container_has_wl_egl_window(MozContainer *container) +{ + return container->eglwindow ? true : false; +} #endif diff -up thunderbird-60.3.0/widget/gtk/mozcontainer.h.wayland thunderbird-60.3.0/widget/gtk/mozcontainer.h --- thunderbird-60.3.0/widget/gtk/mozcontainer.h.wayland 2018-10-30 12:45:34.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/mozcontainer.h 2018-11-20 12:04:43.733787362 +0100 @@ -72,6 +72,9 @@ struct _MozContainer struct wl_subcompositor *subcompositor; struct wl_surface *surface; struct wl_subsurface *subsurface; + struct wl_egl_window *eglwindow; + gboolean parent_surface_committed; + gulong parent_surface_committed_handler; #endif }; @@ -95,6 +98,8 @@ void moz_container_move ( #ifdef MOZ_WAYLAND struct wl_surface* moz_container_get_wl_surface(MozContainer *container); +struct wl_egl_window* moz_container_get_wl_egl_window(MozContainer *container); +gboolean moz_container_has_wl_egl_window(MozContainer *container); #endif #endif /* __MOZ_CONTAINER_H__ */ diff -up thunderbird-60.3.0/widget/gtk/mozgtk/mozgtk.c.wayland thunderbird-60.3.0/widget/gtk/mozgtk/mozgtk.c --- thunderbird-60.3.0/widget/gtk/mozgtk/mozgtk.c.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/mozgtk/mozgtk.c 2018-11-20 12:04:43.734787359 +0100 @@ -135,6 +135,8 @@ STUB(gdk_x11_get_xatom_by_name) STUB(gdk_x11_get_xatom_by_name_for_display) STUB(gdk_x11_lookup_xdisplay) STUB(gdk_x11_screen_get_xscreen) +STUB(gdk_x11_screen_get_screen_number) +STUB(gdk_x11_screen_lookup_visual) STUB(gdk_x11_screen_supports_net_wm_hint) STUB(gdk_x11_visual_get_xvisual) STUB(gdk_x11_window_foreign_new_for_display) @@ -266,6 +268,7 @@ STUB(gtk_im_context_set_client_window) STUB(gtk_im_context_set_cursor_location) STUB(gtk_im_context_set_surrounding) STUB(gtk_im_context_simple_new) +STUB(gtk_im_multicontext_get_context_id) STUB(gtk_im_multicontext_get_type) STUB(gtk_im_multicontext_new) STUB(gtk_info_bar_get_type) diff -up thunderbird-60.3.0/widget/gtk/mozwayland/mozwayland.c.wayland thunderbird-60.3.0/widget/gtk/mozwayland/mozwayland.c --- thunderbird-60.3.0/widget/gtk/mozwayland/mozwayland.c.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/mozwayland/mozwayland.c 2018-11-20 12:04:43.734787359 +0100 @@ -271,3 +271,21 @@ wl_log_set_handler_client(wl_log_func_t { } +MOZ_EXPORT struct wl_egl_window * +wl_egl_window_create(struct wl_surface *surface, + int width, int height) +{ + return NULL; +} + +MOZ_EXPORT void +wl_egl_window_destroy(struct wl_egl_window *egl_window) +{ +} + +MOZ_EXPORT void +wl_egl_window_resize(struct wl_egl_window *egl_window, + int width, int height, + int dx, int dy) +{ +} diff -up thunderbird-60.3.0/widget/gtk/nsAppShell.cpp.wayland thunderbird-60.3.0/widget/gtk/nsAppShell.cpp --- thunderbird-60.3.0/widget/gtk/nsAppShell.cpp.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsAppShell.cpp 2018-11-20 12:04:43.734787359 +0100 @@ -14,7 +14,8 @@ #include "nsWindow.h" #include "mozilla/Logging.h" #include "prenv.h" -#include "mozilla/HangMonitor.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/Hal.h" #include "mozilla/Unused.h" #include "mozilla/WidgetUtils.h" #include "GeckoProfiler.h" @@ -46,13 +47,14 @@ static GPollFunc sPollFunc; static gint PollWrapper(GPollFD *ufds, guint nfsd, gint timeout_) { - mozilla::HangMonitor::Suspend(); + mozilla::BackgroundHangMonitor().NotifyWait(); gint result; { + AUTO_PROFILER_LABEL("PollWrapper", IDLE); AUTO_PROFILER_THREAD_SLEEP; result = (*sPollFunc)(ufds, nfsd, timeout_); } - mozilla::HangMonitor::NotifyActivity(); + mozilla::BackgroundHangMonitor().NotifyActivity(); return result; } @@ -133,6 +135,8 @@ nsAppShell::EventProcessorCallback(GIOCh nsAppShell::~nsAppShell() { + mozilla::hal::Shutdown(); + if (mTag) g_source_remove(mTag); if (mPipeFDs[0]) @@ -150,6 +154,8 @@ nsAppShell::Init() // is a no-op. g_type_init(); + mozilla::hal::Init(); + #ifdef MOZ_ENABLE_DBUS if (XRE_IsParentProcess()) { nsCOMPtr powerManagerService = diff -up thunderbird-60.3.0/widget/gtk/nsClipboard.cpp.wayland thunderbird-60.3.0/widget/gtk/nsClipboard.cpp --- thunderbird-60.3.0/widget/gtk/nsClipboard.cpp.wayland 2018-10-30 12:45:34.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsClipboard.cpp 2018-11-20 12:04:43.734787359 +0100 @@ -671,11 +671,9 @@ void ConvertHTMLtoUCS2(const char* data, *unicodeData = reinterpret_cast (moz_xmalloc((outUnicodeLen + sizeof('\0')) * sizeof(char16_t))); - if (*unicodeData) { - memcpy(*unicodeData, data + sizeof(char16_t), - outUnicodeLen * sizeof(char16_t)); - (*unicodeData)[outUnicodeLen] = '\0'; - } + memcpy(*unicodeData, data + sizeof(char16_t), + outUnicodeLen * sizeof(char16_t)); + (*unicodeData)[outUnicodeLen] = '\0'; } else if (charset.EqualsLiteral("UNKNOWN")) { outUnicodeLen = 0; return; @@ -701,27 +699,25 @@ void ConvertHTMLtoUCS2(const char* data, if (needed.value()) { *unicodeData = reinterpret_cast( moz_xmalloc((needed.value() + 1) * sizeof(char16_t))); - if (*unicodeData) { - uint32_t result; - size_t read; - size_t written; - bool hadErrors; - Tie(result, read, written, hadErrors) = - decoder->DecodeToUTF16(AsBytes(MakeSpan(data, dataLength)), - MakeSpan(*unicodeData, needed.value()), - true); - MOZ_ASSERT(result == kInputEmpty); - MOZ_ASSERT(read == size_t(dataLength)); - MOZ_ASSERT(written <= needed.value()); - Unused << hadErrors; + uint32_t result; + size_t read; + size_t written; + bool hadErrors; + Tie(result, read, written, hadErrors) = + decoder->DecodeToUTF16(AsBytes(MakeSpan(data, dataLength)), + MakeSpan(*unicodeData, needed.value()), + true); + MOZ_ASSERT(result == kInputEmpty); + MOZ_ASSERT(read == size_t(dataLength)); + MOZ_ASSERT(written <= needed.value()); + Unused << hadErrors; #ifdef DEBUG_CLIPBOARD - if (read != dataLength) - printf("didn't consume all the bytes\n"); + if (read != dataLength) + printf("didn't consume all the bytes\n"); #endif - outUnicodeLen = written; - // null terminate. - (*unicodeData)[outUnicodeLen] = '\0'; - } + outUnicodeLen = written; + // null terminate. + (*unicodeData)[outUnicodeLen] = '\0'; } // if valid length } } diff -up thunderbird-60.3.0/widget/gtk/nsClipboardWayland.cpp.wayland thunderbird-60.3.0/widget/gtk/nsClipboardWayland.cpp --- thunderbird-60.3.0/widget/gtk/nsClipboardWayland.cpp.wayland 2018-10-30 12:45:34.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsClipboardWayland.cpp 2018-11-20 12:04:43.735787356 +0100 @@ -23,6 +23,7 @@ #include "mozilla/Services.h" #include "mozilla/RefPtr.h" #include "mozilla/TimeStamp.h" +#include "nsDragService.h" #include "imgIContainer.h" @@ -46,6 +47,44 @@ nsRetrievalContextWayland::sTextMimeType "COMPOUND_TEXT" }; +static inline GdkDragAction +wl_to_gdk_actions(uint32_t dnd_actions) +{ + GdkDragAction actions = GdkDragAction(0); + + if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + actions = GdkDragAction(actions|GDK_ACTION_COPY); + if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + actions = GdkDragAction(actions|GDK_ACTION_MOVE); + + return actions; +} + +static inline uint32_t +gdk_to_wl_actions(GdkDragAction action) +{ + uint32_t dnd_actions = 0; + + if (action & (GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_PRIVATE)) + dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + if (action & GDK_ACTION_MOVE) + dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + + return dnd_actions; +} + +static GtkWidget* +get_gtk_widget_for_wl_surface(struct wl_surface *surface) +{ + GdkWindow *gdkParentWindow = + static_cast(wl_surface_get_user_data(surface)); + + gpointer user_data = nullptr; + gdk_window_get_user_data(gdkParentWindow, &user_data); + + return GTK_WIDGET(user_data); +} + void DataOffer::AddMIMEType(const char *aMimeType) { @@ -114,7 +153,7 @@ DataOffer::GetData(wl_display* aDisplay, GIOChannel *channel = g_io_channel_unix_new(pipe_fd[0]); GError* error = nullptr; - char* clipboardData; + char* clipboardData = nullptr; g_io_channel_set_encoding(channel, nullptr, &error); if (!error) { @@ -155,6 +194,61 @@ WaylandDataOffer::RequestDataTransfer(co return false; } +void +WaylandDataOffer::DragOfferAccept(const char* aMimeType, uint32_t aTime) +{ + wl_data_offer_accept(mWaylandDataOffer, aTime, aMimeType); +} + +/* We follow logic of gdk_wayland_drag_context_commit_status()/gdkdnd-wayland.c + * here. + */ +void +WaylandDataOffer::SetDragStatus(GdkDragAction aAction, uint32_t aTime) +{ + uint32_t dnd_actions = gdk_to_wl_actions(aAction); + uint32_t all_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | + WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + + wl_data_offer_set_actions(mWaylandDataOffer, all_actions, dnd_actions); + + /* Workaround Wayland D&D architecture here. To get the data_device_drop() + signal (which routes to nsDragService::GetData() call) we need to + accept at least one mime type before data_device_leave(). + + Real wl_data_offer_accept() for actualy requested data mime type is + called from nsDragService::GetData(). + */ + if (mTargetMIMETypes[0]) { + wl_data_offer_accept(mWaylandDataOffer, aTime, + gdk_atom_name(mTargetMIMETypes[0])); + } +} + +void +WaylandDataOffer::SetSelectedDragAction(uint32_t aWaylandAction) +{ + mSelectedDragAction = aWaylandAction; +} + +GdkDragAction +WaylandDataOffer::GetSelectedDragAction() +{ + return wl_to_gdk_actions(mSelectedDragAction); +} + +void +WaylandDataOffer::SetAvailableDragActions(uint32_t aWaylandActions) +{ + mAvailableDragAction = aWaylandActions; +} + +GdkDragAction +WaylandDataOffer::GetAvailableDragActions() +{ + return wl_to_gdk_actions(mAvailableDragAction); +} + static void data_offer_offer (void *data, struct wl_data_offer *wl_data_offer, @@ -164,25 +258,39 @@ data_offer_offer (void * offer->AddMIMEType(type); } +/* Advertise all available drag and drop actions from source. + * We don't use that but follow gdk_wayland_drag_context_commit_status() + * from gdkdnd-wayland.c here. + */ static void data_offer_source_actions(void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions) { + auto *offer = static_cast(data); + offer->SetAvailableDragActions(source_actions); } +/* Advertise recently selected drag and drop action by compositor, based + * on source actions and user choice (key modifiers, etc.). + */ static void data_offer_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) { + auto *offer = static_cast(data); + offer->SetSelectedDragAction(dnd_action); } /* wl_data_offer callback description: * * data_offer_offer - Is called for each MIME type available at wl_data_offer. - * data_offer_source_actions - Exposes all available D&D actions. - * data_offer_action - Expose one actually selected D&D action. + * data_offer_source_actions - This event indicates the actions offered by + * the data source. + * data_offer_action - This event indicates the action selected by + * the compositor after matching the source/destination + * side actions. */ static const struct wl_data_offer_listener data_offer_listener = { data_offer_offer, @@ -246,12 +354,94 @@ PrimaryDataOffer::~PrimaryDataOffer(void } } +NS_IMPL_ISUPPORTS(nsWaylandDragContext, nsISupports); + +nsWaylandDragContext::nsWaylandDragContext(WaylandDataOffer* aDataOffer, + wl_display *aDisplay) + : mDataOffer(aDataOffer) + , mDisplay(aDisplay) + , mTime(0) + , mGtkWidget(nullptr) + , mX(0) + , mY(0) +{ +} + +void +nsWaylandDragContext::DropDataEnter(GtkWidget* aGtkWidget, uint32_t aTime, + nscoord aX, nscoord aY) +{ + mTime = aTime; + mGtkWidget = aGtkWidget; + mX = aX; + mY = aY; +} + void -nsRetrievalContextWayland::RegisterDataOffer(wl_data_offer *aWaylandDataOffer) +nsWaylandDragContext::DropMotion(uint32_t aTime, nscoord aX, nscoord aY) +{ + mTime = aTime; + mX = aX; + mY = aY; +} + +void +nsWaylandDragContext::GetLastDropInfo(uint32_t *aTime, nscoord *aX, nscoord *aY) +{ + *aTime = mTime; + *aX = mX; + *aY = mY; +} + +void +nsWaylandDragContext::SetDragStatus(GdkDragAction aAction) +{ + mDataOffer->SetDragStatus(aAction, mTime); +} + +GdkDragAction +nsWaylandDragContext::GetSelectedDragAction() +{ + GdkDragAction gdkAction = mDataOffer->GetSelectedDragAction(); + + // We emulate gdk_drag_context_get_actions() here. + if (!gdkAction) { + gdkAction = mDataOffer->GetAvailableDragActions(); + } + + return gdkAction; +} + +GList* +nsWaylandDragContext::GetTargets() +{ + int targetNums; + GdkAtom *atoms = mDataOffer->GetTargets(&targetNums); + + GList* targetList = nullptr; + for (int i = 0; i < targetNums; i++) { + targetList = g_list_append(targetList, GDK_ATOM_TO_POINTER(atoms[i])); + } + + return targetList; +} + +char* +nsWaylandDragContext::GetData(const char* aMimeType, uint32_t* aContentLength) +{ + mDataOffer->DragOfferAccept(aMimeType, mTime); + return mDataOffer->GetData(mDisplay, aMimeType, aContentLength); +} + +void +nsRetrievalContextWayland::RegisterNewDataOffer(wl_data_offer *aWaylandDataOffer) { DataOffer* dataOffer = static_cast(g_hash_table_lookup(mActiveOffers, aWaylandDataOffer)); + MOZ_ASSERT(dataOffer == nullptr, + "Registered WaylandDataOffer already exists. Wayland protocol error?"); + if (!dataOffer) { dataOffer = new WaylandDataOffer(aWaylandDataOffer); g_hash_table_insert(mActiveOffers, aWaylandDataOffer, dataOffer); @@ -259,12 +449,15 @@ nsRetrievalContextWayland::RegisterDataO } void -nsRetrievalContextWayland::RegisterDataOffer( +nsRetrievalContextWayland::RegisterNewDataOffer( gtk_primary_selection_offer *aPrimaryDataOffer) { DataOffer* dataOffer = static_cast(g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer)); + MOZ_ASSERT(dataOffer == nullptr, + "Registered PrimaryDataOffer already exists. Wayland protocol error?"); + if (!dataOffer) { dataOffer = new PrimaryDataOffer(aPrimaryDataOffer); g_hash_table_insert(mActiveOffers, aPrimaryDataOffer, dataOffer); @@ -274,6 +467,9 @@ nsRetrievalContextWayland::RegisterDataO void nsRetrievalContextWayland::SetClipboardDataOffer(wl_data_offer *aWaylandDataOffer) { + // Delete existing clipboard data offer + mClipboardOffer = nullptr; + DataOffer* dataOffer = static_cast(g_hash_table_lookup(mActiveOffers, aWaylandDataOffer)); @@ -288,10 +484,12 @@ void nsRetrievalContextWayland::SetPrimaryDataOffer( gtk_primary_selection_offer *aPrimaryDataOffer) { - if (aPrimaryDataOffer == nullptr) { - // Release any primary offer we have. - mPrimaryOffer = nullptr; - } else { + // Release any primary offer we have. + mPrimaryOffer = nullptr; + + // aPrimaryDataOffer can be null which means we lost + // the mouse selection. + if (aPrimaryDataOffer) { DataOffer* dataOffer = static_cast(g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer)); @@ -304,12 +502,31 @@ nsRetrievalContextWayland::SetPrimaryDat } void -nsRetrievalContextWayland::ClearDataOffers(void) +nsRetrievalContextWayland::AddDragAndDropDataOffer(wl_data_offer *aDropDataOffer) { - if (mClipboardOffer) - mClipboardOffer = nullptr; - if (mPrimaryOffer) - mPrimaryOffer = nullptr; + // Remove any existing D&D contexts. + mDragContext = nullptr; + + WaylandDataOffer* dataOffer = + static_cast(g_hash_table_lookup(mActiveOffers, + aDropDataOffer)); + NS_ASSERTION(dataOffer, "We're missing drag and drop data offer!"); + if (dataOffer) { + g_hash_table_remove(mActiveOffers, aDropDataOffer); + mDragContext = new nsWaylandDragContext(dataOffer, mDisplay); + } +} + +nsWaylandDragContext* +nsRetrievalContextWayland::GetDragContext(void) +{ + return mDragContext; +} + +void +nsRetrievalContextWayland::ClearDragAndDropDataOffer(void) +{ + mDragContext = nullptr; } // We have a new fresh data content. @@ -321,7 +538,7 @@ data_device_data_offer (void { nsRetrievalContextWayland *context = static_cast(data); - context->RegisterDataOffer(offer); + context->RegisterNewDataOffer(offer); } // The new fresh data content is clipboard. @@ -341,31 +558,78 @@ data_device_enter (void struct wl_data_device *data_device, uint32_t time, struct wl_surface *surface, - int32_t x, - int32_t y, + int32_t x_fixed, + int32_t y_fixed, struct wl_data_offer *offer) { + nsRetrievalContextWayland *context = + static_cast(data); + context->AddDragAndDropDataOffer(offer); + + nsWaylandDragContext* dragContext = context->GetDragContext(); + + GtkWidget* gtkWidget = get_gtk_widget_for_wl_surface(surface); + if (!gtkWidget) { + NS_WARNING("DragAndDrop: Unable to get GtkWidget for wl_surface!"); + return; + } + + LOGDRAG(("nsWindow data_device_enter for GtkWidget %p\n", + (void*)gtkWidget)); + + dragContext->DropDataEnter(gtkWidget, time, + wl_fixed_to_int(x_fixed), + wl_fixed_to_int(y_fixed)); } static void data_device_leave (void *data, struct wl_data_device *data_device) { + nsRetrievalContextWayland *context = + static_cast(data); + + nsWaylandDragContext* dropContext = context->GetDragContext(); + WindowDragLeaveHandler(dropContext->GetWidget()); + + context->ClearDragAndDropDataOffer(); } static void data_device_motion (void *data, struct wl_data_device *data_device, uint32_t time, - int32_t x, - int32_t y) + int32_t x_fixed, + int32_t y_fixed) { + nsRetrievalContextWayland *context = + static_cast(data); + + nsWaylandDragContext* dropContext = context->GetDragContext(); + + nscoord x = wl_fixed_to_int(x_fixed); + nscoord y = wl_fixed_to_int(y_fixed); + dropContext->DropMotion(time, x, y); + + WindowDragMotionHandler(dropContext->GetWidget(), nullptr, + dropContext, x, y, time); } static void data_device_drop (void *data, struct wl_data_device *data_device) { + nsRetrievalContextWayland *context = + static_cast(data); + + nsWaylandDragContext* dropContext = context->GetDragContext(); + + uint32_t time; + nscoord x, y; + dropContext->GetLastDropInfo(&time, &x, &y); + + WindowDragDropHandler(dropContext->GetWidget(), nullptr, dropContext, + x, y, time); } /* wl_data_device callback description: @@ -405,7 +669,7 @@ primary_selection_data_offer (void // create and add listener nsRetrievalContextWayland *context = static_cast(data); - context->RegisterDataOffer(gtk_primary_offer); + context->RegisterNewDataOffer(gtk_primary_offer); } static void @@ -418,6 +682,18 @@ primary_selection_selection (void context->SetPrimaryDataOffer(gtk_primary_offer); } +/* gtk_primary_selection_device callback description: + * + * primary_selection_data_offer - It's called when there's a new + * gtk_primary_selection_offer available. + * We need to attach gtk_primary_selection_offer_listener + * to it to get available MIME types. + * + * primary_selection_selection - It's called when the new gtk_primary_selection_offer + * is a primary selection content. It can be also called with + * gtk_primary_selection_offer = null which means there's no + * primary selection. + */ static const struct gtk_primary_selection_device_listener primary_selection_device_listener = { primary_selection_data_offer, @@ -430,81 +706,6 @@ nsRetrievalContextWayland::HasSelectionS return mPrimarySelectionDataDeviceManager != nullptr; } -static void -keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, - uint32_t format, int fd, uint32_t size) -{ -} - -static void -keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, - uint32_t serial, struct wl_surface *surface, - struct wl_array *keys) -{ -} - -static void -keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, - uint32_t serial, struct wl_surface *surface) -{ - // We lost focus so our clipboard data are outdated - nsRetrievalContextWayland *context = - static_cast(data); - - context->ClearDataOffers(); -} - -static void -keyboard_handle_key(void *data, struct wl_keyboard *keyboard, - uint32_t serial, uint32_t time, uint32_t key, - uint32_t state) -{ -} - -static void -keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, - uint32_t serial, uint32_t mods_depressed, - uint32_t mods_latched, uint32_t mods_locked, - uint32_t group) -{ -} - -static const struct wl_keyboard_listener keyboard_listener = { - keyboard_handle_keymap, - keyboard_handle_enter, - keyboard_handle_leave, - keyboard_handle_key, - keyboard_handle_modifiers, -}; - -void -nsRetrievalContextWayland::ConfigureKeyboard(wl_seat_capability caps) -{ - // ConfigureKeyboard() is called when wl_seat configuration is changed. - // We look for the keyboard only, get it when is't available and release it - // when it's lost (we don't have focus for instance). - if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { - mKeyboard = wl_seat_get_keyboard(mSeat); - wl_keyboard_add_listener(mKeyboard, &keyboard_listener, this); - } else if (mKeyboard && !(caps & WL_SEAT_CAPABILITY_KEYBOARD)) { - wl_keyboard_destroy(mKeyboard); - mKeyboard = nullptr; - } -} - -static void -seat_handle_capabilities(void *data, struct wl_seat *seat, - unsigned int caps) -{ - nsRetrievalContextWayland *context = - static_cast(data); - context->ConfigureKeyboard((wl_seat_capability)caps); -} - -static const struct wl_seat_listener seat_listener = { - seat_handle_capabilities, -}; - void nsRetrievalContextWayland::InitDataDeviceManager(wl_registry *registry, uint32_t id, @@ -530,7 +731,6 @@ nsRetrievalContextWayland::InitSeat(wl_r void *data) { mSeat = (wl_seat*)wl_registry_bind(registry, id, &wl_seat_interface, 1); - wl_seat_add_listener(mSeat, &seat_listener, data); } static void @@ -573,6 +773,7 @@ nsRetrievalContextWayland::nsRetrievalCo , mActiveOffers(g_hash_table_new(NULL, NULL)) , mClipboardOffer(nullptr) , mPrimaryOffer(nullptr) + , mDragContext(nullptr) , mClipboardRequestNumber(0) , mClipboardData(nullptr) , mClipboardDataLength(0) @@ -616,8 +817,21 @@ nsRetrievalContextWayland::nsRetrievalCo mInitialized = true; } +static gboolean +offer_hash_remove(gpointer wl_offer, gpointer aDataOffer, gpointer user_data) +{ +#ifdef DEBUG + nsPrintfCString msg("nsRetrievalContextWayland(): leaked nsDataOffer %p\n", + aDataOffer); + NS_WARNING(msg.get()); +#endif + delete static_cast(aDataOffer); + return true; +} + nsRetrievalContextWayland::~nsRetrievalContextWayland(void) { + g_hash_table_foreach_remove(mActiveOffers, offer_hash_remove, nullptr); g_hash_table_destroy(mActiveOffers); } @@ -667,12 +881,14 @@ nsRetrievalContextWayland::TransferFastT int aClipboardRequestNumber, GtkSelectionData *aSelectionData) { if (mClipboardRequestNumber == aClipboardRequestNumber) { - mClipboardDataLength = gtk_selection_data_get_length(aSelectionData); - if (mClipboardDataLength > 0) { + int dataLength = gtk_selection_data_get_length(aSelectionData); + if (dataLength > 0) { + mClipboardDataLength = dataLength; mClipboardData = reinterpret_cast( - g_malloc(sizeof(char)*mClipboardDataLength)); + g_malloc(sizeof(char)*(mClipboardDataLength+1))); memcpy(mClipboardData, gtk_selection_data_get_data(aSelectionData), sizeof(char)*mClipboardDataLength); + mClipboardData[mClipboardDataLength] = '\0'; } } else { NS_WARNING("Received obsoleted clipboard data!"); @@ -727,7 +943,7 @@ nsRetrievalContextWayland::GetClipboardT if (!dataOffer) return nullptr; - for (unsigned int i = 0; i < sizeof(sTextMimeTypes); i++) { + for (unsigned int i = 0; i < TEXT_MIME_TYPES_NUM; i++) { if (dataOffer->HasTarget(sTextMimeTypes[i])) { uint32_t unused; return GetClipboardData(sTextMimeTypes[i], aWhichClipboard, diff -up thunderbird-60.3.0/widget/gtk/nsClipboardWayland.h.wayland thunderbird-60.3.0/widget/gtk/nsClipboardWayland.h --- thunderbird-60.3.0/widget/gtk/nsClipboardWayland.h.wayland 2018-10-30 12:45:34.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsClipboardWayland.h 2018-11-20 12:04:43.735787356 +0100 @@ -32,6 +32,7 @@ public: private: virtual bool RequestDataTransfer(const char* aMimeType, int fd) = 0; +protected: nsTArray mTargetMIMETypes; }; @@ -40,25 +41,66 @@ class WaylandDataOffer : public DataOffe public: WaylandDataOffer(wl_data_offer* aWaylandDataOffer); -private: + void DragOfferAccept(const char* aMimeType, uint32_t aTime); + void SetDragStatus(GdkDragAction aAction, uint32_t aTime); + + GdkDragAction GetSelectedDragAction(); + void SetSelectedDragAction(uint32_t aWaylandAction); + + void SetAvailableDragActions(uint32_t aWaylandActions); + GdkDragAction GetAvailableDragActions(); + virtual ~WaylandDataOffer(); +private: bool RequestDataTransfer(const char* aMimeType, int fd) override; wl_data_offer* mWaylandDataOffer; + uint32_t mSelectedDragAction; + uint32_t mAvailableDragAction; }; class PrimaryDataOffer : public DataOffer { public: PrimaryDataOffer(gtk_primary_selection_offer* aPrimaryDataOffer); + void SetAvailableDragActions(uint32_t aWaylandActions) {}; -private: virtual ~PrimaryDataOffer(); +private: bool RequestDataTransfer(const char* aMimeType, int fd) override; gtk_primary_selection_offer* mPrimaryDataOffer; }; +class nsWaylandDragContext : public nsISupports +{ + NS_DECL_ISUPPORTS + +public: + nsWaylandDragContext(WaylandDataOffer* aWaylandDataOffer, + wl_display *aDisplay); + + void DropDataEnter(GtkWidget* aGtkWidget, uint32_t aTime, + nscoord aX, nscoord aY); + void DropMotion(uint32_t aTime, nscoord aX, nscoord aY); + void GetLastDropInfo(uint32_t *aTime, nscoord *aX, nscoord *aY); + + void SetDragStatus(GdkDragAction action); + GdkDragAction GetSelectedDragAction(); + + GtkWidget* GetWidget() { return mGtkWidget; } + GList* GetTargets(); + char* GetData(const char* aMimeType, uint32_t* aContentLength); +private: + virtual ~nsWaylandDragContext() {}; + + nsAutoPtr mDataOffer; + wl_display* mDisplay; + uint32_t mTime; + GtkWidget* mGtkWidget; + nscoord mX, mY; +}; + class nsRetrievalContextWayland : public nsRetrievalContext { public: @@ -74,15 +116,16 @@ public: int* aTargetNum) override; virtual bool HasSelectionSupport(void) override; - void RegisterDataOffer(wl_data_offer *aWaylandDataOffer); - void RegisterDataOffer(gtk_primary_selection_offer *aPrimaryDataOffer); + void RegisterNewDataOffer(wl_data_offer *aWaylandDataOffer); + void RegisterNewDataOffer(gtk_primary_selection_offer *aPrimaryDataOffer); void SetClipboardDataOffer(wl_data_offer *aWaylandDataOffer); void SetPrimaryDataOffer(gtk_primary_selection_offer *aPrimaryDataOffer); + void AddDragAndDropDataOffer(wl_data_offer *aWaylandDataOffer); + nsWaylandDragContext* GetDragContext(); - void ClearDataOffers(); + void ClearDragAndDropDataOffer(); - void ConfigureKeyboard(wl_seat_capability caps); void TransferFastTrackClipboard(int aClipboardRequestNumber, GtkSelectionData *aSelectionData); @@ -103,6 +146,7 @@ private: GHashTable* mActiveOffers; nsAutoPtr mClipboardOffer; nsAutoPtr mPrimaryOffer; + RefPtr mDragContext; int mClipboardRequestNumber; char* mClipboardData; diff -up thunderbird-60.3.0/widget/gtk/nsDeviceContextSpecG.cpp.wayland thunderbird-60.3.0/widget/gtk/nsDeviceContextSpecG.cpp --- thunderbird-60.3.0/widget/gtk/nsDeviceContextSpecG.cpp.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsDeviceContextSpecG.cpp 2018-11-20 12:04:43.735787356 +0100 @@ -33,6 +33,9 @@ #include #include +// To check if we need to use flatpak portal for printing +#include "nsIGIOService.h" + using namespace mozilla; using mozilla::gfx::IntSize; @@ -355,6 +358,20 @@ NS_IMETHODIMP nsDeviceContextSpecGTK::En // If you're not familiar with umasks, they contain the bits of what NOT to set in the permissions // (thats because files and directories have different numbers of bits for their permissions) destFile->SetPermissions(0666 & ~(mask)); + + // Notify flatpak printing portal that file is completely written + nsCOMPtr giovfs = + do_GetService(NS_GIOSERVICE_CONTRACTID); + bool shouldUsePortal; + if (giovfs) { + giovfs->ShouldUseFlatpakPortal(&shouldUsePortal); + if (shouldUsePortal) { + // Use the name of the file for printing to match with nsFlatpakPrintPortal + nsCOMPtr os = mozilla::services::GetObserverService(); + // Pass filename to be sure that observer process the right data + os->NotifyObservers(nullptr, "print-to-file-finished", targetPath.get()); + } + } } return NS_OK; } @@ -421,7 +438,7 @@ nsPrinterEnumeratorGTK::InitPrintSetting } if (path) { - CopyUTF8toUTF16(path, filename); + CopyUTF8toUTF16(MakeStringSpan(path), filename); filename.AppendLiteral("/mozilla.pdf"); } else { filename.AssignLiteral("mozilla.pdf"); diff -up thunderbird-60.3.0/widget/gtk/nsDragService.cpp.wayland thunderbird-60.3.0/widget/gtk/nsDragService.cpp --- thunderbird-60.3.0/widget/gtk/nsDragService.cpp.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsDragService.cpp 2018-11-20 12:04:43.736787353 +0100 @@ -34,7 +34,6 @@ #include "nsPresContext.h" #include "nsIContent.h" #include "nsIDocument.h" -#include "nsISelection.h" #include "nsViewManager.h" #include "nsIFrame.h" #include "nsGtkUtils.h" @@ -43,6 +42,9 @@ #include "gfxPlatform.h" #include "ScreenHelperGTK.h" #include "nsArrayUtils.h" +#ifdef MOZ_WAYLAND +#include "nsClipboardWayland.h" +#endif using namespace mozilla; using namespace mozilla::gfx; @@ -99,6 +101,10 @@ invisibleSourceDragDataGet(GtkWidget nsDragService::nsDragService() : mScheduledTask(eDragTaskNone) , mTaskSource(0) +#ifdef MOZ_WAYLAND + , mPendingWaylandDragContext(nullptr) + , mTargetWaylandDragContext(nullptr) +#endif { // We have to destroy the hidden widget before the event loop stops // running. @@ -179,7 +185,7 @@ nsDragService::Observe(nsISupports *aSub } TargetResetData(); } else { - NS_NOTREACHED("unexpected topic"); + MOZ_ASSERT_UNREACHABLE("unexpected topic"); return NS_ERROR_UNEXPECTED; } @@ -269,13 +275,12 @@ OnSourceGrabEventAfter(GtkWidget *widget } static GtkWindow* -GetGtkWindow(nsIDOMDocument *aDocument) +GetGtkWindow(nsIDocument *aDocument) { - nsCOMPtr doc = do_QueryInterface(aDocument); - if (!doc) + if (!aDocument) return nullptr; - nsCOMPtr presShell = doc->GetShell(); + nsCOMPtr presShell = aDocument->GetShell(); if (!presShell) return nullptr; @@ -304,10 +309,9 @@ GetGtkWindow(nsIDOMDocument *aDocument) // nsIDragService NS_IMETHODIMP -nsDragService::InvokeDragSession(nsIDOMNode *aDOMNode, +nsDragService::InvokeDragSession(nsINode *aDOMNode, const nsACString& aPrincipalURISpec, nsIArray * aArrayTransferables, - nsIScriptableRegion * aRegion, uint32_t aActionType, nsContentPolicyType aContentPolicyType = nsIContentPolicy::TYPE_OTHER) @@ -323,14 +327,14 @@ nsDragService::InvokeDragSession(nsIDOMN return nsBaseDragService::InvokeDragSession(aDOMNode, aPrincipalURISpec, aArrayTransferables, - aRegion, aActionType, + aActionType, aContentPolicyType); } // nsBaseDragService nsresult nsDragService::InvokeDragSessionImpl(nsIArray* aArrayTransferables, - nsIScriptableRegion* aRegion, + const Maybe& aRegion, uint32_t aActionType) { // make sure that we have an array of transferables to use @@ -346,9 +350,6 @@ nsDragService::InvokeDragSessionImpl(nsI if (!sourceList) return NS_OK; - // stored temporarily until the drag-begin signal has been received - mSourceRegion = aRegion; - // save our action type GdkDragAction action = GDK_ACTION_DEFAULT; @@ -391,8 +392,6 @@ nsDragService::InvokeDragSessionImpl(nsI 1, &event); - mSourceRegion = nullptr; - nsresult rv; if (context) { StartDragSession(); @@ -516,6 +515,9 @@ nsDragService::EndDragSession(bool aDone // We're done with the drag context. mTargetDragContextForRemote = nullptr; +#ifdef MOZ_WAYLAND + mTargetWaylandDragContextForRemote = nullptr; +#endif return nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers); } @@ -636,6 +638,14 @@ nsDragService::GetNumDropItems(uint32_t return NS_OK; } +#ifdef MOZ_WAYLAND + // TODO: Wayland implementation of text/uri-list. + if (!mTargetDragContext) { + *aNumItems = 1; + return NS_OK; + } +#endif + bool isList = IsTargetContextList(); if (isList) mSourceDataItems->GetLength(aNumItems); @@ -1027,9 +1037,18 @@ nsDragService::IsDataFlavorSupported(con } // check the target context vs. this flavor, one at a time - GList *tmp; - for (tmp = gdk_drag_context_list_targets(mTargetDragContext); - tmp; tmp = tmp->next) { + GList *tmp = nullptr; + if (mTargetDragContext) { + tmp = gdk_drag_context_list_targets(mTargetDragContext); + } +#ifdef MOZ_WAYLAND + else if (mTargetWaylandDragContext) { + tmp = mTargetWaylandDragContext->GetTargets(); + } + GList *tmp_head = tmp; +#endif + + for (; tmp; tmp = tmp->next) { /* Bug 331198 */ GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data); gchar *name = nullptr; @@ -1074,6 +1093,15 @@ nsDragService::IsDataFlavorSupported(con } g_free(name); } + +#ifdef MOZ_WAYLAND + // mTargetWaylandDragContext->GetTargets allocates the list + // so we need to free it here. + if (!mTargetDragContext && tmp_head) { + g_list_free(tmp_head); + } +#endif + return NS_OK; } @@ -1105,6 +1133,36 @@ nsDragService::ReplyToDragMotion(GdkDrag gdk_drag_status(aDragContext, action, mTargetTime); } +#ifdef MOZ_WAYLAND +void +nsDragService::ReplyToDragMotion(nsWaylandDragContext* aDragContext) +{ + MOZ_LOG(sDragLm, LogLevel::Debug, + ("nsDragService::ReplyToDragMotion %d", mCanDrop)); + + GdkDragAction action = (GdkDragAction)0; + if (mCanDrop) { + // notify the dragger if we can drop + switch (mDragAction) { + case DRAGDROP_ACTION_COPY: + action = GDK_ACTION_COPY; + break; + case DRAGDROP_ACTION_LINK: + action = GDK_ACTION_LINK; + break; + case DRAGDROP_ACTION_NONE: + action = (GdkDragAction)0; + break; + default: + action = GDK_ACTION_MOVE; + break; + } + } + + aDragContext->SetDragStatus(action); +} +#endif + void nsDragService::TargetDataReceived(GtkWidget *aWidget, GdkDragContext *aContext, @@ -1136,6 +1194,12 @@ nsDragService::IsTargetContextList(void) { bool retval = false; +#ifdef MOZ_WAYLAND + // TODO: We need a wayland implementation here. + if (!mTargetDragContext) + return retval; +#endif + // gMimeListType drags only work for drags within a single process. The // gtk_drag_get_source_widget() function will return nullptr if the source // of the drag is another app, so we use it to check if a gMimeListType @@ -1174,17 +1238,28 @@ nsDragService::GetTargetDragData(GdkAtom mTargetDragContext.get())); // reset our target data areas TargetResetData(); - gtk_drag_get_data(mTargetWidget, mTargetDragContext, aFlavor, mTargetTime); - MOZ_LOG(sDragLm, LogLevel::Debug, ("about to start inner iteration.")); - PRTime entryTime = PR_Now(); - while (!mTargetDragDataReceived && mDoingDrag) { - // check the number of iterations - MOZ_LOG(sDragLm, LogLevel::Debug, ("doing iteration...\n")); - PR_Sleep(20*PR_TicksPerSecond()/1000); /* sleep for 20 ms/iteration */ - if (PR_Now()-entryTime > NS_DND_TIMEOUT) break; - gtk_main_iteration(); + if (mTargetDragContext) { + gtk_drag_get_data(mTargetWidget, mTargetDragContext, aFlavor, mTargetTime); + + MOZ_LOG(sDragLm, LogLevel::Debug, ("about to start inner iteration.")); + PRTime entryTime = PR_Now(); + while (!mTargetDragDataReceived && mDoingDrag) { + // check the number of iterations + MOZ_LOG(sDragLm, LogLevel::Debug, ("doing iteration...\n")); + PR_Sleep(20*PR_TicksPerSecond()/1000); /* sleep for 20 ms/iteration */ + if (PR_Now()-entryTime > NS_DND_TIMEOUT) break; + gtk_main_iteration(); + } + } +#ifdef MOZ_WAYLAND + else { + mTargetDragData = + mTargetWaylandDragContext->GetData(gdk_atom_name(aFlavor), + &mTargetDragDataLen); + mTargetDragDataReceived = true; } +#endif MOZ_LOG(sDragLm, LogLevel::Debug, ("finished inner iteration\n")); } @@ -1435,7 +1510,7 @@ nsDragService::SourceEndDragSession(GdkD } // Schedule the appropriate drag end dom events. - Schedule(eDragTaskSourceEnd, nullptr, nullptr, LayoutDeviceIntPoint(), 0); + Schedule(eDragTaskSourceEnd, nullptr, nullptr, nullptr, LayoutDeviceIntPoint(), 0); } static void @@ -1642,8 +1717,8 @@ void nsDragService::SetDragIcon(GdkDragC LayoutDeviceIntRect dragRect; nsPresContext* pc; RefPtr surface; - DrawDrag(mSourceNode, mSourceRegion, mScreenPosition, - &dragRect, &surface, &pc); + DrawDrag(mSourceNode, mRegion, + mScreenPosition, &dragRect, &surface, &pc); if (!pc) return; @@ -1785,9 +1860,10 @@ invisibleSourceDragEnd(GtkWidget gboolean nsDragService::ScheduleMotionEvent(nsWindow *aWindow, GdkDragContext *aDragContext, + nsWaylandDragContext *aWaylandDragContext, LayoutDeviceIntPoint aWindowPoint, guint aTime) { - if (mScheduledTask == eDragTaskMotion) { + if (aDragContext && mScheduledTask == eDragTaskMotion) { // The drag source has sent another motion message before we've // replied to the previous. That shouldn't happen with Xdnd. The // spec for Motif drags is less clear, but we'll just update the @@ -1798,7 +1874,7 @@ nsDragService::ScheduleMotionEvent(nsWin // Returning TRUE means we'll reply with a status message, unless we first // get a leave. - return Schedule(eDragTaskMotion, aWindow, aDragContext, + return Schedule(eDragTaskMotion, aWindow, aDragContext, aWaylandDragContext, aWindowPoint, aTime); } @@ -1808,7 +1884,8 @@ nsDragService::ScheduleLeaveEvent() // We don't know at this stage whether a drop signal will immediately // follow. If the drop signal gets sent it will happen before we return // to the main loop and the scheduled leave task will be replaced. - if (!Schedule(eDragTaskLeave, nullptr, nullptr, LayoutDeviceIntPoint(), 0)) { + if (!Schedule(eDragTaskLeave, nullptr, nullptr, nullptr, + LayoutDeviceIntPoint(), 0)) { NS_WARNING("Drag leave after drop"); } } @@ -1816,10 +1893,11 @@ nsDragService::ScheduleLeaveEvent() gboolean nsDragService::ScheduleDropEvent(nsWindow *aWindow, GdkDragContext *aDragContext, + nsWaylandDragContext *aWaylandDragContext, LayoutDeviceIntPoint aWindowPoint, guint aTime) { if (!Schedule(eDragTaskDrop, aWindow, - aDragContext, aWindowPoint, aTime)) { + aDragContext, aWaylandDragContext, aWindowPoint, aTime)) { NS_WARNING("Additional drag drop ignored"); return FALSE; } @@ -1833,6 +1911,7 @@ nsDragService::ScheduleDropEvent(nsWindo gboolean nsDragService::Schedule(DragTask aTask, nsWindow *aWindow, GdkDragContext *aDragContext, + nsWaylandDragContext *aWaylandDragContext, LayoutDeviceIntPoint aWindowPoint, guint aTime) { // If there is an existing leave or motion task scheduled, then that @@ -1851,6 +1930,9 @@ nsDragService::Schedule(DragTask aTask, mScheduledTask = aTask; mPendingWindow = aWindow; mPendingDragContext = aDragContext; +#ifdef MOZ_WAYLAND + mPendingWaylandDragContext = aWaylandDragContext; +#endif mPendingWindowPoint = aWindowPoint; mPendingTime = aTime; @@ -1927,6 +2009,9 @@ nsDragService::RunScheduledTask() // succeeed. mTargetWidget = mTargetWindow->GetMozContainerWidget(); mTargetDragContext.steal(mPendingDragContext); +#ifdef MOZ_WAYLAND + mTargetWaylandDragContext = mPendingWaylandDragContext.forget(); +#endif mTargetTime = mPendingTime; // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model @@ -1958,10 +2043,20 @@ nsDragService::RunScheduledTask() if (task == eDragTaskMotion) { if (TakeDragEventDispatchedToChildProcess()) { mTargetDragContextForRemote = mTargetDragContext; +#ifdef MOZ_WAYLAND + mTargetWaylandDragContextForRemote = mTargetWaylandDragContext; +#endif } else { // Reply to tell the source whether we can drop and what // action would be taken. - ReplyToDragMotion(mTargetDragContext); + if (mTargetDragContext) { + ReplyToDragMotion(mTargetDragContext); + } +#ifdef MOZ_WAYLAND + else if (mTargetWaylandDragContext) { + ReplyToDragMotion(mTargetWaylandDragContext); + } +#endif } } } @@ -1972,8 +2067,10 @@ nsDragService::RunScheduledTask() // Perhaps we should set the del parameter to TRUE when the drag // action is move, but we don't know whether the data was successfully // transferred. - gtk_drag_finish(mTargetDragContext, success, - /* del = */ FALSE, mTargetTime); + if (mTargetDragContext) { + gtk_drag_finish(mTargetDragContext, success, + /* del = */ FALSE, mTargetTime); + } // This drag is over, so clear out our reference to the previous // window. @@ -1986,6 +2083,9 @@ nsDragService::RunScheduledTask() // We're done with the drag context. mTargetWidget = nullptr; mTargetDragContext = nullptr; +#ifdef MOZ_WAYLAND + mTargetWaylandDragContext = nullptr; +#endif // If we got another drag signal while running the sheduled task, that // must have happened while running a nested event loop. Leave the task @@ -2015,7 +2115,16 @@ nsDragService::UpdateDragAction() // default is to do nothing int action = nsIDragService::DRAGDROP_ACTION_NONE; - GdkDragAction gdkAction = gdk_drag_context_get_actions(mTargetDragContext); + GdkDragAction gdkAction = GDK_ACTION_DEFAULT; + if (mTargetDragContext) { + gdkAction = gdk_drag_context_get_actions(mTargetDragContext); + } +#ifdef MOZ_WAYLAND + else if (mTargetWaylandDragContext) { + // We got the selected D&D action from compositor on Wayland. + gdkAction = mTargetWaylandDragContext->GetSelectedDragAction(); + } +#endif // set the default just in case nothing matches below if (gdkAction & GDK_ACTION_DEFAULT) @@ -2044,6 +2153,12 @@ nsDragService::UpdateDragEffect() ReplyToDragMotion(mTargetDragContextForRemote); mTargetDragContextForRemote = nullptr; } +#ifdef MOZ_WAYLAND + else if (mTargetWaylandDragContextForRemote) { + ReplyToDragMotion(mTargetWaylandDragContextForRemote); + mTargetWaylandDragContextForRemote = nullptr; + } +#endif return NS_OK; } diff -up thunderbird-60.3.0/widget/gtk/nsDragService.h.wayland thunderbird-60.3.0/widget/gtk/nsDragService.h --- thunderbird-60.3.0/widget/gtk/nsDragService.h.wayland 2018-10-30 12:45:37.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsDragService.h 2018-11-20 12:04:43.736787353 +0100 @@ -14,6 +14,7 @@ #include class nsWindow; +class nsWaylandDragContext; namespace mozilla { namespace gfx { @@ -60,13 +61,12 @@ public: // nsBaseDragService virtual nsresult InvokeDragSessionImpl(nsIArray* anArrayTransferables, - nsIScriptableRegion* aRegion, + const mozilla::Maybe& aRegion, uint32_t aActionType) override; // nsIDragService - NS_IMETHOD InvokeDragSession (nsIDOMNode *aDOMNode, + NS_IMETHOD InvokeDragSession (nsINode *aDOMNode, const nsACString& aPrincipalURISpec, nsIArray * anArrayTransferables, - nsIScriptableRegion * aRegion, uint32_t aActionType, nsContentPolicyType aContentPolicyType) override; NS_IMETHOD StartDragSession() override; @@ -98,11 +98,13 @@ public: gboolean ScheduleMotionEvent(nsWindow *aWindow, GdkDragContext *aDragContext, + nsWaylandDragContext* aPendingWaylandDragContext, mozilla::LayoutDeviceIntPoint aWindowPoint, guint aTime); void ScheduleLeaveEvent(); gboolean ScheduleDropEvent(nsWindow *aWindow, GdkDragContext *aDragContext, + nsWaylandDragContext* aPendingWaylandDragContext, mozilla::LayoutDeviceIntPoint aWindowPoint, guint aTime); @@ -158,6 +160,9 @@ private: RefPtr mPendingWindow; mozilla::LayoutDeviceIntPoint mPendingWindowPoint; nsCountedRef mPendingDragContext; +#ifdef MOZ_WAYLAND + RefPtr mPendingWaylandDragContext; +#endif guint mPendingTime; // mTargetWindow and mTargetWindowPoint record the position of the last @@ -169,9 +174,15 @@ private: // motion or drop events. mTime records the corresponding timestamp. nsCountedRef mTargetWidget; nsCountedRef mTargetDragContext; +#ifdef MOZ_WAYLAND + RefPtr mTargetWaylandDragContext; +#endif // mTargetDragContextForRemote is set while waiting for a reply from // a child process. nsCountedRef mTargetDragContextForRemote; +#ifdef MOZ_WAYLAND + RefPtr mTargetWaylandDragContextForRemote; +#endif guint mTargetTime; // is it OK to drop on us? @@ -197,8 +208,6 @@ private: // our source data items nsCOMPtr mSourceDataItems; - nsCOMPtr mSourceRegion; - // get a list of the sources in gtk's format GtkTargetList *GetSourceList(void); @@ -212,6 +221,7 @@ private: gboolean Schedule(DragTask aTask, nsWindow *aWindow, GdkDragContext *aDragContext, + nsWaylandDragContext* aPendingWaylandDragContext, mozilla::LayoutDeviceIntPoint aWindowPoint, guint aTime); // Callback for g_idle_add_full() to run mScheduledTask. @@ -220,9 +230,11 @@ private: void UpdateDragAction(); void DispatchMotionEvents(); void ReplyToDragMotion(GdkDragContext* aDragContext); +#ifdef MOZ_WAYLAND + void ReplyToDragMotion(nsWaylandDragContext* aDragContext); +#endif gboolean DispatchDropEvent(); static uint32_t GetCurrentModifiers(); }; #endif // nsDragService_h__ - diff -up thunderbird-60.3.0/widget/gtk/nsFilePicker.cpp.wayland thunderbird-60.3.0/widget/gtk/nsFilePicker.cpp --- thunderbird-60.3.0/widget/gtk/nsFilePicker.cpp.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsFilePicker.cpp 2018-11-20 12:04:43.736787353 +0100 @@ -346,7 +346,7 @@ nsFilePicker::GetFiles(nsISimpleEnumerat NS_ENSURE_ARG_POINTER(aFiles); if (mMode == nsIFilePicker::modeOpenMultiple) { - return NS_NewArrayEnumerator(aFiles, mFiles); + return NS_NewArrayEnumerator(aFiles, mFiles, NS_GET_IID(nsIFile)); } return NS_ERROR_FAILURE; diff -up thunderbird-60.3.0/widget/gtk/nsGtkKeyUtils.cpp.wayland thunderbird-60.3.0/widget/gtk/nsGtkKeyUtils.cpp --- thunderbird-60.3.0/widget/gtk/nsGtkKeyUtils.cpp.wayland 2018-10-30 12:45:34.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsGtkKeyUtils.cpp 2018-11-20 12:04:43.736787353 +0100 @@ -28,6 +28,10 @@ #include "mozilla/MouseEvents.h" #include "mozilla/TextEvents.h" +#ifdef MOZ_WAYLAND +#include +#endif + namespace mozilla { namespace widget { @@ -195,7 +199,11 @@ KeymapWrapper::Init() memset(mModifierMasks, 0, sizeof(mModifierMasks)); if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) - InitBySystemSettings(); + InitBySystemSettingsX11(); +#ifdef MOZ_WAYLAND + else + InitBySystemSettingsWayland(); +#endif gdk_window_add_filter(nullptr, FilterEvents, this); @@ -275,10 +283,10 @@ KeymapWrapper::InitXKBExtension() } void -KeymapWrapper::InitBySystemSettings() +KeymapWrapper::InitBySystemSettingsX11() { MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, - ("%p InitBySystemSettings, mGdkKeymap=%p", + ("%p InitBySystemSettingsX11, mGdkKeymap=%p", this, mGdkKeymap)); Display* display = @@ -439,6 +447,208 @@ KeymapWrapper::InitBySystemSettings() XFree(xkeymap); } +#ifdef MOZ_WAYLAND +void +KeymapWrapper::SetModifierMask(xkb_keymap *aKeymap, ModifierIndex aModifierIndex, + const char* aModifierName) +{ + static auto sXkbKeymapModGetIndex = + (xkb_mod_index_t (*)(struct xkb_keymap *, const char *)) + dlsym(RTLD_DEFAULT, "xkb_keymap_mod_get_index"); + + xkb_mod_index_t index = sXkbKeymapModGetIndex(aKeymap, aModifierName); + if (index != XKB_MOD_INVALID) { + mModifierMasks[aModifierIndex] = (1 << index); + } +} + +void +KeymapWrapper::SetModifierMasks(xkb_keymap *aKeymap) +{ + KeymapWrapper* keymapWrapper = GetInstance(); + + // This mapping is derived from get_xkb_modifiers() at gdkkeys-wayland.c + keymapWrapper->SetModifierMask(aKeymap, INDEX_NUM_LOCK, XKB_MOD_NAME_NUM); + keymapWrapper->SetModifierMask(aKeymap, INDEX_ALT, XKB_MOD_NAME_ALT); + keymapWrapper->SetModifierMask(aKeymap, INDEX_META, "Meta"); + keymapWrapper->SetModifierMask(aKeymap, INDEX_SUPER, "Super"); + keymapWrapper->SetModifierMask(aKeymap, INDEX_HYPER, "Hyper"); + + keymapWrapper->SetModifierMask(aKeymap, INDEX_SCROLL_LOCK, "ScrollLock"); + keymapWrapper->SetModifierMask(aKeymap, INDEX_LEVEL3, "Level3"); + keymapWrapper->SetModifierMask(aKeymap, INDEX_LEVEL5, "Level5"); + + MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, + ("%p KeymapWrapper::SetModifierMasks, CapsLock=0x%X, NumLock=0x%X, " + "ScrollLock=0x%X, Level3=0x%X, Level5=0x%X, " + "Shift=0x%X, Ctrl=0x%X, Alt=0x%X, Meta=0x%X, Super=0x%X, Hyper=0x%X", + keymapWrapper, + keymapWrapper->GetModifierMask(CAPS_LOCK), + keymapWrapper->GetModifierMask(NUM_LOCK), + keymapWrapper->GetModifierMask(SCROLL_LOCK), + keymapWrapper->GetModifierMask(LEVEL3), + keymapWrapper->GetModifierMask(LEVEL5), + keymapWrapper->GetModifierMask(SHIFT), + keymapWrapper->GetModifierMask(CTRL), + keymapWrapper->GetModifierMask(ALT), + keymapWrapper->GetModifierMask(META), + keymapWrapper->GetModifierMask(SUPER), + keymapWrapper->GetModifierMask(HYPER))); +} + +/* This keymap routine is derived from weston-2.0.0/clients/simple-im.c +*/ +static void +keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int fd, uint32_t size) +{ + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + close(fd); + return; + } + + char *mapString = (char *)mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (mapString == MAP_FAILED) { + close(fd); + return; + } + + static auto sXkbContextNew = + (struct xkb_context *(*)(enum xkb_context_flags)) + dlsym(RTLD_DEFAULT, "xkb_context_new"); + static auto sXkbKeymapNewFromString = + (struct xkb_keymap *(*)(struct xkb_context *, const char *, + enum xkb_keymap_format, enum xkb_keymap_compile_flags)) + dlsym(RTLD_DEFAULT, "xkb_keymap_new_from_string"); + + struct xkb_context *xkb_context = sXkbContextNew(XKB_CONTEXT_NO_FLAGS); + struct xkb_keymap *keymap = + sXkbKeymapNewFromString(xkb_context, mapString, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS); + + munmap(mapString, size); + close(fd); + + if (!keymap) { + NS_WARNING("keyboard_handle_keymap(): Failed to compile keymap!\n"); + return; + } + + KeymapWrapper::SetModifierMasks(keymap); + + static auto sXkbKeymapUnRef = + (void(*)(struct xkb_keymap *)) + dlsym(RTLD_DEFAULT, "xkb_keymap_unref"); + sXkbKeymapUnRef(keymap); + + static auto sXkbContextUnref = + (void(*)(struct xkb_context *)) + dlsym(RTLD_DEFAULT, "xkb_context_unref"); + sXkbContextUnref(xkb_context); +} + +static void +keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) +{ +} + +static void +keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface) +{ +} + +static void +keyboard_handle_key(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t time, uint32_t key, + uint32_t state) +{ +} + +static void +keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) +{ +} + +static const struct wl_keyboard_listener keyboard_listener = { + keyboard_handle_keymap, + keyboard_handle_enter, + keyboard_handle_leave, + keyboard_handle_key, + keyboard_handle_modifiers, +}; + +static void +seat_handle_capabilities(void *data, struct wl_seat *seat, + unsigned int caps) +{ + static wl_keyboard *keyboard = nullptr; + + if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { + keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(keyboard, &keyboard_listener, nullptr); + } else if (keyboard && !(caps & WL_SEAT_CAPABILITY_KEYBOARD)) { + wl_keyboard_destroy(keyboard); + keyboard = nullptr; + } +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, +}; + +static void +gdk_registry_handle_global(void *data, + struct wl_registry *registry, + uint32_t id, + const char *interface, + uint32_t version) +{ + if (strcmp(interface, "wl_seat") == 0) { + wl_seat *seat = + (wl_seat*)wl_registry_bind(registry, id, &wl_seat_interface, 1); + wl_seat_add_listener(seat, &seat_listener, data); + } +} + +static void +gdk_registry_handle_global_remove(void *data, + struct wl_registry *registry, + uint32_t id) +{ +} + +static const struct wl_registry_listener keyboard_registry_listener = { + gdk_registry_handle_global, + gdk_registry_handle_global_remove +}; + +void +KeymapWrapper::InitBySystemSettingsWayland() +{ + // Available as of GTK 3.8+ + static auto sGdkWaylandDisplayGetWlDisplay = + (wl_display *(*)(GdkDisplay *)) + dlsym(RTLD_DEFAULT, "gdk_wayland_display_get_wl_display"); + + wl_display *display = + sGdkWaylandDisplayGetWlDisplay(gdk_display_get_default()); + wl_registry_add_listener(wl_display_get_registry(display), + &keyboard_registry_listener, this); + + // Call wl_display_roundtrip() twice to make sure all + // callbacks are processed. + wl_display_roundtrip(display); + wl_display_roundtrip(display); +} +#endif + KeymapWrapper::~KeymapWrapper() { gdk_window_remove_filter(nullptr, FilterEvents, this); @@ -931,14 +1141,19 @@ KeymapWrapper::ComputeDOMCodeNameIndex(c /* static */ void KeymapWrapper::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent, - GdkEventKey* aGdkKeyEvent) + GdkEventKey* aGdkKeyEvent, + bool aIsProcessedByIME) { + MOZ_ASSERT(!aIsProcessedByIME || aKeyEvent.mMessage != eKeyPress, + "If the key event is handled by IME, keypress event shouldn't be fired"); + KeymapWrapper* keymapWrapper = GetInstance(); aKeyEvent.mCodeNameIndex = ComputeDOMCodeNameIndex(aGdkKeyEvent); MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING); aKeyEvent.mKeyNameIndex = - keymapWrapper->ComputeDOMKeyNameIndex(aGdkKeyEvent); + aIsProcessedByIME ? KEY_NAME_INDEX_Process : + keymapWrapper->ComputeDOMKeyNameIndex(aGdkKeyEvent); if (aKeyEvent.mKeyNameIndex == KEY_NAME_INDEX_Unidentified) { uint32_t charCode = GetCharCodeFor(aGdkKeyEvent); if (!charCode) { @@ -951,10 +1166,11 @@ KeymapWrapper::InitKeyEvent(WidgetKeyboa AppendUCS4ToUTF16(charCode, aKeyEvent.mKeyValue); } } - aKeyEvent.mKeyCode = ComputeDOMKeyCode(aGdkKeyEvent); - if (aKeyEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING || - aKeyEvent.mMessage != eKeyPress) { + if (aIsProcessedByIME) { + aKeyEvent.mKeyCode = NS_VK_PROCESSKEY; + } else if (aKeyEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING || + aKeyEvent.mMessage != eKeyPress) { aKeyEvent.mKeyCode = ComputeDOMKeyCode(aGdkKeyEvent); } else { aKeyEvent.mKeyCode = 0; @@ -1405,6 +1621,14 @@ void KeymapWrapper::WillDispatchKeyboardEventInternal(WidgetKeyboardEvent& aKeyEvent, GdkEventKey* aGdkKeyEvent) { + if (!aGdkKeyEvent) { + // If aGdkKeyEvent is nullptr, we're trying to dispatch a fake keyboard + // event in such case, we don't need to set alternative char codes. + // So, we don't need to do nothing here. This case is typically we're + // dispatching eKeyDown or eKeyUp event during composition. + return; + } + uint32_t charCode = GetCharCodeFor(aGdkKeyEvent); if (!charCode) { MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, diff -up thunderbird-60.3.0/widget/gtk/nsGtkKeyUtils.h.wayland thunderbird-60.3.0/widget/gtk/nsGtkKeyUtils.h --- thunderbird-60.3.0/widget/gtk/nsGtkKeyUtils.h.wayland 2018-10-30 12:45:34.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsGtkKeyUtils.h 2018-11-20 12:04:43.737787350 +0100 @@ -13,6 +13,10 @@ #include #include +#ifdef MOZ_WAYLAND +#include +#include +#endif namespace mozilla { namespace widget { @@ -131,9 +135,11 @@ public: * @param aKeyEvent It's an WidgetKeyboardEvent which needs to be * initialized. * @param aGdkKeyEvent A native GDK key event. + * @param aIsProcessedByIME true if aGdkKeyEvent is handled by IME. */ static void InitKeyEvent(WidgetKeyboardEvent& aKeyEvent, - GdkEventKey* aGdkKeyEvent); + GdkEventKey* aGdkKeyEvent, + bool aIsProcessedByIME); /** * WillDispatchKeyboardEvent() is called via @@ -148,6 +154,14 @@ public: static void WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyEvent, GdkEventKey* aGdkKeyEvent); +#ifdef MOZ_WAYLAND + /** + * Utility function to set all supported modifier masks + * from xkb_keymap. We call that from Wayland backend routines. + */ + static void SetModifierMasks(xkb_keymap *aKeymap); +#endif + /** * Destroys the singleton KeymapWrapper instance, if it exists. */ @@ -172,7 +186,10 @@ protected: */ void Init(); void InitXKBExtension(); - void InitBySystemSettings(); + void InitBySystemSettingsX11(); +#ifdef MOZ_WAYLAND + void InitBySystemSettingsWayland(); +#endif /** * mModifierKeys stores each hardware key information. @@ -374,6 +391,15 @@ protected: */ void WillDispatchKeyboardEventInternal(WidgetKeyboardEvent& aKeyEvent, GdkEventKey* aGdkKeyEvent); + +#ifdef MOZ_WAYLAND + /** + * Utility function to set Xkb modifier key mask. + */ + void SetModifierMask(xkb_keymap *aKeymap, + ModifierIndex aModifierIndex, + const char* aModifierName); +#endif }; } // namespace widget diff -up thunderbird-60.3.0/widget/gtk/nsLookAndFeel.cpp.wayland thunderbird-60.3.0/widget/gtk/nsLookAndFeel.cpp --- thunderbird-60.3.0/widget/gtk/nsLookAndFeel.cpp.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsLookAndFeel.cpp 2018-11-20 12:04:43.737787350 +0100 @@ -18,6 +18,7 @@ #include #include "gfxPlatformGtk.h" +#include "mozilla/FontPropertyTypes.h" #include "ScreenHelperGTK.h" #include "gtkdrawing.h" @@ -31,7 +32,9 @@ #include #include "WidgetStyleCache.h" #include "prenv.h" +#include "nsCSSColorUtils.h" +using namespace mozilla; using mozilla::LookAndFeel; #define GDK_COLOR_TO_NS_RGB(c) \ @@ -182,7 +185,7 @@ GetBorderColors(GtkStyleContext* aContex // GTK has an initial value of zero for border-widths, and so themes // need to explicitly set border-widths to make borders visible. GtkBorder border; - gtk_style_context_get_border(aContext, GTK_STATE_FLAG_NORMAL, &border); + gtk_style_context_get_border(aContext, state, &border); visible = border.top != 0 || border.right != 0 || border.bottom != 0 || border.left != 0; } @@ -213,6 +216,58 @@ GetBorderColors(GtkStyleContext* aContex return ret; } +// Finds ideal cell highlight colors used for unfocused+selected cells distinct +// from both Highlight, used as focused+selected background, and the listbox +// background which is assumed to be similar to -moz-field +nsresult +nsLookAndFeel::InitCellHighlightColors() { + // NS_SUFFICIENT_LUMINOSITY_DIFFERENCE is the a11y standard for text + // on a background. Use 20% of that standard since we have a background + // on top of another background + int32_t minLuminosityDifference = NS_SUFFICIENT_LUMINOSITY_DIFFERENCE / 5; + int32_t backLuminosityDifference = NS_LUMINOSITY_DIFFERENCE( + mMozWindowBackground, mMozFieldBackground); + if (backLuminosityDifference >= minLuminosityDifference) { + mMozCellHighlightBackground = mMozWindowBackground; + mMozCellHighlightText = mMozWindowText; + return NS_OK; + } + + uint16_t hue, sat, luminance; + uint8_t alpha; + mMozCellHighlightBackground = mMozFieldBackground; + mMozCellHighlightText = mMozFieldText; + + NS_RGB2HSV(mMozCellHighlightBackground, hue, sat, luminance, alpha); + + uint16_t step = 30; + // Lighten the color if the color is very dark + if (luminance <= step) { + luminance += step; + } + // Darken it if it is very light + else if (luminance >= 255 - step) { + luminance -= step; + } + // Otherwise, compute what works best depending on the text luminance. + else { + uint16_t textHue, textSat, textLuminance; + uint8_t textAlpha; + NS_RGB2HSV(mMozCellHighlightText, textHue, textSat, textLuminance, + textAlpha); + // Text is darker than background, use a lighter shade + if (textLuminance < luminance) { + luminance += step; + } + // Otherwise, use a darker shade + else { + luminance -= step; + } + } + NS_HSV2RGB(mMozCellHighlightBackground, hue, sat, luminance, alpha); + return NS_OK; +} + void nsLookAndFeel::NativeInit() { @@ -269,7 +324,6 @@ nsLookAndFeel::NativeGetColor(ColorID aI case eColorID_IMESelectedRawTextBackground: case eColorID_IMESelectedConvertedTextBackground: case eColorID__moz_dragtargetzone: - case eColorID__moz_cellhighlight: case eColorID__moz_html_cellhighlight: case eColorID_highlight: // preference selected item, aColor = mTextSelectedBackground; @@ -279,10 +333,15 @@ nsLookAndFeel::NativeGetColor(ColorID aI case eColorID_IMESelectedRawTextForeground: case eColorID_IMESelectedConvertedTextForeground: case eColorID_highlighttext: - case eColorID__moz_cellhighlighttext: case eColorID__moz_html_cellhighlighttext: aColor = mTextSelectedText; break; + case eColorID__moz_cellhighlight: + aColor = mMozCellHighlightBackground; + break; + case eColorID__moz_cellhighlighttext: + aColor = mMozCellHighlightText; + break; case eColorID_Widget3DHighlight: aColor = NS_RGB(0xa0,0xa0,0xa0); break; @@ -668,6 +727,17 @@ nsLookAndFeel::GetIntImpl(IntID aID, int EnsureInit(); aResult = mCSDCloseButton; break; + case eIntID_PrefersReducedMotion: { + GtkSettings *settings; + gboolean enableAnimations; + + settings = gtk_settings_get_default(); + g_object_get(settings, + "gtk-enable-animations", + &enableAnimations, nullptr); + aResult = enableAnimations ? 0 : 1; + break; + } default: aResult = 0; res = NS_ERROR_FAILURE; @@ -708,7 +778,7 @@ GetSystemFontInfo(GtkStyleContext *aStyl nsString *aFontName, gfxFontStyle *aFontStyle) { - aFontStyle->style = NS_FONT_STYLE_NORMAL; + aFontStyle->style = FontSlantStyle::Normal(); // As in // https://git.gnome.org/browse/gtk+/tree/gtk/gtkwidget.c?h=3.22.19#n10333 @@ -722,10 +792,10 @@ GetSystemFontInfo(GtkStyleContext *aStyl NS_ConvertUTF8toUTF16 family(pango_font_description_get_family(desc)); *aFontName = quote + family + quote; - aFontStyle->weight = pango_font_description_get_weight(desc); + aFontStyle->weight = FontWeight(pango_font_description_get_weight(desc)); // FIXME: Set aFontStyle->stretch correctly! - aFontStyle->stretch = NS_FONT_STRETCH_NORMAL; + aFontStyle->stretch = FontStretch::Normal(); float size = float(pango_font_description_get_size(desc)) / PANGO_SCALE; @@ -1009,6 +1079,9 @@ nsLookAndFeel::EnsureInit() mOddCellBackground = GDK_RGBA_TO_NS_RGBA(color); gtk_style_context_restore(style); + // Compute cell highlight colors + InitCellHighlightColors(); + // GtkFrame has a "border" subnode on which Adwaita draws the border. // Some themes do not draw on this node but draw a border on the widget // root node, so check the root node if no border is found on the border diff -up thunderbird-60.3.0/widget/gtk/nsLookAndFeel.h.wayland thunderbird-60.3.0/widget/gtk/nsLookAndFeel.h --- thunderbird-60.3.0/widget/gtk/nsLookAndFeel.h.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsLookAndFeel.h 2018-11-20 12:04:43.737787350 +0100 @@ -77,6 +77,8 @@ protected: nscolor mMozWindowActiveBorder; nscolor mMozWindowInactiveBorder; nscolor mMozWindowInactiveCaption; + nscolor mMozCellHighlightBackground; + nscolor mMozCellHighlightText; nscolor mTextSelectedText; nscolor mTextSelectedBackground; nscolor mMozScrollbar; @@ -91,6 +93,9 @@ protected: bool mInitialized; void EnsureInit(); + +private: + nsresult InitCellHighlightColors(); }; #endif diff -up thunderbird-60.3.0/widget/gtk/nsNativeThemeGTK.cpp.wayland thunderbird-60.3.0/widget/gtk/nsNativeThemeGTK.cpp --- thunderbird-60.3.0/widget/gtk/nsNativeThemeGTK.cpp.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsNativeThemeGTK.cpp 2018-11-20 12:04:43.738787347 +0100 @@ -4,7 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsNativeThemeGTK.h" -#include "nsThemeConstants.h" +#include "nsStyleConsts.h" #include "gtkdrawing.h" #include "ScreenHelperGTK.h" @@ -39,6 +39,8 @@ #include "mozilla/gfx/HelpersCairo.h" #include "mozilla/gfx/PathHelpers.h" #include "mozilla/Preferences.h" +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/StaticPrefs.h" #ifdef MOZ_X11 # ifdef CAIRO_HAS_XLIB_SURFACE @@ -119,7 +121,7 @@ nsNativeThemeGTK::Observe(nsISupports *a if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) { moz_gtk_shutdown(); } else { - NS_NOTREACHED("unexpected topic"); + MOZ_ASSERT_UNREACHABLE("unexpected topic"); return NS_ERROR_UNEXPECTED; } @@ -149,41 +151,43 @@ static bool IsFrameContentNodeInNamespac return content->IsInNamespace(aNamespace); } -static bool IsWidgetTypeDisabled(uint8_t* aDisabledVector, uint8_t aWidgetType) { - MOZ_ASSERT(aWidgetType < ThemeWidgetType_COUNT); - return (aDisabledVector[aWidgetType >> 3] & (1 << (aWidgetType & 7))) != 0; +static bool IsWidgetTypeDisabled(uint8_t* aDisabledVector, StyleAppearance aWidgetType) { + auto type = static_cast(aWidgetType); + MOZ_ASSERT(type < static_cast(mozilla::StyleAppearance::Count)); + return (aDisabledVector[type >> 3] & (1 << (type & 7))) != 0; } -static void SetWidgetTypeDisabled(uint8_t* aDisabledVector, uint8_t aWidgetType) { - MOZ_ASSERT(aWidgetType < ThemeWidgetType_COUNT); - aDisabledVector[aWidgetType >> 3] |= (1 << (aWidgetType & 7)); +static void SetWidgetTypeDisabled(uint8_t* aDisabledVector, StyleAppearance aWidgetType) { + auto type = static_cast(aWidgetType); + MOZ_ASSERT(type < static_cast(mozilla::StyleAppearance::Count)); + aDisabledVector[type >> 3] |= (1 << (type & 7)); } static inline uint16_t -GetWidgetStateKey(uint8_t aWidgetType, GtkWidgetState *aWidgetState) +GetWidgetStateKey(StyleAppearance aWidgetType, GtkWidgetState *aWidgetState) { return (aWidgetState->active | aWidgetState->focused << 1 | aWidgetState->inHover << 2 | aWidgetState->disabled << 3 | aWidgetState->isDefault << 4 | - aWidgetType << 5); + static_cast(aWidgetType) << 5); } static bool IsWidgetStateSafe(uint8_t* aSafeVector, - uint8_t aWidgetType, - GtkWidgetState *aWidgetState) + StyleAppearance aWidgetType, + GtkWidgetState *aWidgetState) { - MOZ_ASSERT(aWidgetType < ThemeWidgetType_COUNT); + MOZ_ASSERT(static_cast(aWidgetType) < static_cast(mozilla::StyleAppearance::Count)); uint16_t key = GetWidgetStateKey(aWidgetType, aWidgetState); return (aSafeVector[key >> 3] & (1 << (key & 7))) != 0; } static void SetWidgetStateSafe(uint8_t *aSafeVector, - uint8_t aWidgetType, + StyleAppearance aWidgetType, GtkWidgetState *aWidgetState) { - MOZ_ASSERT(aWidgetType < ThemeWidgetType_COUNT); + MOZ_ASSERT(static_cast(aWidgetType) < static_cast(mozilla::StyleAppearance::Count)); uint16_t key = GetWidgetStateKey(aWidgetType, aWidgetState); aSafeVector[key >> 3] |= (1 << (key & 7)); } @@ -213,33 +217,38 @@ nsNativeThemeGTK::GetTabMarginPixels(nsI } static bool ShouldScrollbarButtonBeDisabled(int32_t aCurpos, int32_t aMaxpos, - uint8_t aWidgetType) + StyleAppearance aWidgetType) { - return ((aCurpos == 0 && (aWidgetType == NS_THEME_SCROLLBARBUTTON_UP || - aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT)) - || (aCurpos == aMaxpos && (aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN || - aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT))); + return ((aCurpos == 0 && (aWidgetType == StyleAppearance::ScrollbarbuttonUp || + aWidgetType == StyleAppearance::ScrollbarbuttonLeft)) + || (aCurpos == aMaxpos && (aWidgetType == StyleAppearance::ScrollbarbuttonDown || + aWidgetType == StyleAppearance::ScrollbarbuttonRight))); } bool -nsNativeThemeGTK::GetGtkWidgetAndState(uint8_t aWidgetType, nsIFrame* aFrame, +nsNativeThemeGTK::GetGtkWidgetAndState(StyleAppearance aWidgetType, nsIFrame* aFrame, WidgetNodeType& aGtkWidgetType, GtkWidgetState* aState, gint* aWidgetFlags) { + if (aWidgetType == StyleAppearance::MenulistButton && + StaticPrefs::layout_css_webkit_appearance_enabled()) { + aWidgetType = StyleAppearance::Menulist; + } + if (aState) { // For XUL checkboxes and radio buttons, the state of the parent // determines our state. nsIFrame *stateFrame = aFrame; - if (aFrame && ((aWidgetFlags && (aWidgetType == NS_THEME_CHECKBOX || - aWidgetType == NS_THEME_RADIO)) || - aWidgetType == NS_THEME_CHECKBOX_LABEL || - aWidgetType == NS_THEME_RADIO_LABEL)) { + if (aFrame && ((aWidgetFlags && (aWidgetType == StyleAppearance::Checkbox || + aWidgetType == StyleAppearance::Radio)) || + aWidgetType == StyleAppearance::CheckboxLabel || + aWidgetType == StyleAppearance::RadioLabel)) { nsAtom* atom = nullptr; if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) { - if (aWidgetType == NS_THEME_CHECKBOX_LABEL || - aWidgetType == NS_THEME_RADIO_LABEL) { + if (aWidgetType == StyleAppearance::CheckboxLabel || + aWidgetType == StyleAppearance::RadioLabel) { // Adjust stateFrame so GetContentState finds the correct state. stateFrame = aFrame = aFrame->GetParent()->GetParent(); } else { @@ -249,8 +258,8 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u } if (aWidgetFlags) { if (!atom) { - atom = (aWidgetType == NS_THEME_CHECKBOX || - aWidgetType == NS_THEME_CHECKBOX_LABEL) ? nsGkAtoms::checked + atom = (aWidgetType == StyleAppearance::Checkbox || + aWidgetType == StyleAppearance::CheckboxLabel) ? nsGkAtoms::checked : nsGkAtoms::selected; } *aWidgetFlags = CheckBooleanAttr(aFrame, atom); @@ -258,7 +267,7 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u } else { if (aWidgetFlags) { *aWidgetFlags = 0; - HTMLInputElement* inputElt = HTMLInputElement::FromContent(aFrame->GetContent()); + HTMLInputElement* inputElt = HTMLInputElement::FromNode(aFrame->GetContent()); if (inputElt && inputElt->Checked()) *aWidgetFlags |= MOZ_GTK_WIDGET_CHECKED; @@ -266,12 +275,12 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u *aWidgetFlags |= MOZ_GTK_WIDGET_INCONSISTENT; } } - } else if (aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN || - aWidgetType == NS_THEME_TREEHEADERSORTARROW || - aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS || - aWidgetType == NS_THEME_BUTTON_ARROW_NEXT || - aWidgetType == NS_THEME_BUTTON_ARROW_UP || - aWidgetType == NS_THEME_BUTTON_ARROW_DOWN) { + } else if (aWidgetType == StyleAppearance::ToolbarbuttonDropdown || + aWidgetType == StyleAppearance::Treeheadersortarrow || + aWidgetType == StyleAppearance::ButtonArrowPrevious || + aWidgetType == StyleAppearance::ButtonArrowNext || + aWidgetType == StyleAppearance::ButtonArrowUp || + aWidgetType == StyleAppearance::ButtonArrowDown) { // The state of an arrow comes from its parent. stateFrame = aFrame = aFrame->GetParent(); } @@ -287,7 +296,7 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u aState->canDefault = FALSE; // XXX fix me aState->depressed = FALSE; - if (aWidgetType == NS_THEME_FOCUS_OUTLINE) { + if (aWidgetType == StyleAppearance::FocusOutline) { aState->disabled = FALSE; aState->active = FALSE; aState->inHover = FALSE; @@ -296,15 +305,16 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u aState->focused = TRUE; aState->depressed = TRUE; // see moz_gtk_entry_paint() - } else if (aWidgetType == NS_THEME_BUTTON || - aWidgetType == NS_THEME_TOOLBARBUTTON || - aWidgetType == NS_THEME_DUALBUTTON || - aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN || - aWidgetType == NS_THEME_MENULIST || - aWidgetType == NS_THEME_MENULIST_BUTTON) { + } else if (aWidgetType == StyleAppearance::Button || + aWidgetType == StyleAppearance::Toolbarbutton || + aWidgetType == StyleAppearance::Dualbutton || + aWidgetType == StyleAppearance::ToolbarbuttonDropdown || + aWidgetType == StyleAppearance::Menulist || + aWidgetType == StyleAppearance::MenulistButton || + aWidgetType == StyleAppearance::MozMenulistButton) { aState->active &= aState->inHover; - } else if (aWidgetType == NS_THEME_TREETWISTY || - aWidgetType == NS_THEME_TREETWISTYOPEN) { + } else if (aWidgetType == StyleAppearance::Treetwisty || + aWidgetType == StyleAppearance::Treetwistyopen) { nsTreeBodyFrame *treeBodyFrame = do_QueryFrame(aFrame); if (treeBodyFrame) { const mozilla::AtomArray& atoms = @@ -318,22 +328,22 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u // For these widget types, some element (either a child or parent) // actually has element focus, so we check the focused attribute // to see whether to draw in the focused state. - if (aWidgetType == NS_THEME_NUMBER_INPUT || - aWidgetType == NS_THEME_TEXTFIELD || - aWidgetType == NS_THEME_TEXTFIELD_MULTILINE || - aWidgetType == NS_THEME_MENULIST_TEXTFIELD || - aWidgetType == NS_THEME_SPINNER_TEXTFIELD || - aWidgetType == NS_THEME_RADIO_CONTAINER || - aWidgetType == NS_THEME_RADIO_LABEL) { + if (aWidgetType == StyleAppearance::NumberInput || + aWidgetType == StyleAppearance::Textfield || + aWidgetType == StyleAppearance::TextfieldMultiline || + aWidgetType == StyleAppearance::MenulistTextfield || + aWidgetType == StyleAppearance::SpinnerTextfield || + aWidgetType == StyleAppearance::RadioContainer || + aWidgetType == StyleAppearance::RadioLabel) { aState->focused = IsFocused(aFrame); - } else if (aWidgetType == NS_THEME_RADIO || - aWidgetType == NS_THEME_CHECKBOX) { + } else if (aWidgetType == StyleAppearance::Radio || + aWidgetType == StyleAppearance::Checkbox) { // In XUL, checkboxes and radios shouldn't have focus rings, their labels do aState->focused = FALSE; } - if (aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL || - aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) { + if (aWidgetType == StyleAppearance::ScrollbarthumbVertical || + aWidgetType == StyleAppearance::ScrollbarthumbHorizontal) { // for scrollbars we need to go up two to go from the thumb to // the slider to the actual scrollbar object nsIFrame *tmpFrame = aFrame->GetParent()->GetParent(); @@ -348,10 +358,10 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u } } - if (aWidgetType == NS_THEME_SCROLLBARBUTTON_UP || - aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN || - aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT || - aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT) { + if (aWidgetType == StyleAppearance::ScrollbarbuttonUp || + aWidgetType == StyleAppearance::ScrollbarbuttonDown || + aWidgetType == StyleAppearance::ScrollbarbuttonLeft || + aWidgetType == StyleAppearance::ScrollbarbuttonRight) { // set the state to disabled when the scrollbar is scrolled to // the beginning or the end, depending on the button type. int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0); @@ -369,7 +379,8 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u if (aWidgetFlags) { *aWidgetFlags = GetScrollbarButtonType(aFrame); - if (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP < 2) + if (static_cast(aWidgetType) - + static_cast(StyleAppearance::ScrollbarbuttonUp) < 2) *aWidgetFlags |= MOZ_GTK_STEPPER_VERTICAL; } } @@ -379,11 +390,11 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u // menus which are children of a menu bar are only marked as prelight // if they are open, not on normal hover. - if (aWidgetType == NS_THEME_MENUITEM || - aWidgetType == NS_THEME_CHECKMENUITEM || - aWidgetType == NS_THEME_RADIOMENUITEM || - aWidgetType == NS_THEME_MENUSEPARATOR || - aWidgetType == NS_THEME_MENUARROW) { + if (aWidgetType == StyleAppearance::Menuitem || + aWidgetType == StyleAppearance::Checkmenuitem || + aWidgetType == StyleAppearance::Radiomenuitem || + aWidgetType == StyleAppearance::Menuseparator || + aWidgetType == StyleAppearance::Menuarrow) { bool isTopLevel = false; nsMenuFrame *menuFrame = do_QueryFrame(aFrame); if (menuFrame) { @@ -398,8 +409,8 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u aState->active = FALSE; - if (aWidgetType == NS_THEME_CHECKMENUITEM || - aWidgetType == NS_THEME_RADIOMENUITEM) { + if (aWidgetType == StyleAppearance::Checkmenuitem || + aWidgetType == StyleAppearance::Radiomenuitem) { *aWidgetFlags = 0; if (aFrame && aFrame->GetContent() && aFrame->GetContent()->IsElement()) { @@ -412,12 +423,13 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u // A button with drop down menu open or an activated toggle button // should always appear depressed. - if (aWidgetType == NS_THEME_BUTTON || - aWidgetType == NS_THEME_TOOLBARBUTTON || - aWidgetType == NS_THEME_DUALBUTTON || - aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN || - aWidgetType == NS_THEME_MENULIST || - aWidgetType == NS_THEME_MENULIST_BUTTON) { + if (aWidgetType == StyleAppearance::Button || + aWidgetType == StyleAppearance::Toolbarbutton || + aWidgetType == StyleAppearance::Dualbutton || + aWidgetType == StyleAppearance::ToolbarbuttonDropdown || + aWidgetType == StyleAppearance::Menulist || + aWidgetType == StyleAppearance::MenulistButton || + aWidgetType == StyleAppearance::MozMenulistButton) { bool menuOpen = IsOpenButton(aFrame); aState->depressed = IsCheckedButton(aFrame) || menuOpen; // we must not highlight buttons with open drop down menus on hover. @@ -426,79 +438,81 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u // When the input field of the drop down button has focus, some themes // should draw focus for the drop down button as well. - if (aWidgetType == NS_THEME_MENULIST_BUTTON && aWidgetFlags) { + if ((aWidgetType == StyleAppearance::MenulistButton || + aWidgetType == StyleAppearance::MozMenulistButton) && + aWidgetFlags) { *aWidgetFlags = CheckBooleanAttr(aFrame, nsGkAtoms::parentfocused); } } } switch (aWidgetType) { - case NS_THEME_BUTTON: + case StyleAppearance::Button: if (aWidgetFlags) *aWidgetFlags = GTK_RELIEF_NORMAL; aGtkWidgetType = MOZ_GTK_BUTTON; break; - case NS_THEME_TOOLBARBUTTON: - case NS_THEME_DUALBUTTON: + case StyleAppearance::Toolbarbutton: + case StyleAppearance::Dualbutton: if (aWidgetFlags) *aWidgetFlags = GTK_RELIEF_NONE; aGtkWidgetType = MOZ_GTK_TOOLBAR_BUTTON; break; - case NS_THEME_FOCUS_OUTLINE: + case StyleAppearance::FocusOutline: aGtkWidgetType = MOZ_GTK_ENTRY; break; - case NS_THEME_CHECKBOX: - case NS_THEME_RADIO: - aGtkWidgetType = (aWidgetType == NS_THEME_RADIO) ? MOZ_GTK_RADIOBUTTON : MOZ_GTK_CHECKBUTTON; - break; - case NS_THEME_SCROLLBARBUTTON_UP: - case NS_THEME_SCROLLBARBUTTON_DOWN: - case NS_THEME_SCROLLBARBUTTON_LEFT: - case NS_THEME_SCROLLBARBUTTON_RIGHT: + case StyleAppearance::Checkbox: + case StyleAppearance::Radio: + aGtkWidgetType = (aWidgetType == StyleAppearance::Radio) ? MOZ_GTK_RADIOBUTTON : MOZ_GTK_CHECKBUTTON; + break; + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: aGtkWidgetType = MOZ_GTK_SCROLLBAR_BUTTON; break; - case NS_THEME_SCROLLBAR_VERTICAL: + case StyleAppearance::ScrollbarVertical: aGtkWidgetType = MOZ_GTK_SCROLLBAR_VERTICAL; if (GetWidgetTransparency(aFrame, aWidgetType) == eOpaque) *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE; else *aWidgetFlags = 0; break; - case NS_THEME_SCROLLBAR_HORIZONTAL: + case StyleAppearance::ScrollbarHorizontal: aGtkWidgetType = MOZ_GTK_SCROLLBAR_HORIZONTAL; if (GetWidgetTransparency(aFrame, aWidgetType) == eOpaque) *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE; else *aWidgetFlags = 0; break; - case NS_THEME_SCROLLBARTRACK_HORIZONTAL: + case StyleAppearance::ScrollbartrackHorizontal: aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL; break; - case NS_THEME_SCROLLBARTRACK_VERTICAL: + case StyleAppearance::ScrollbartrackVertical: aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL; break; - case NS_THEME_SCROLLBARTHUMB_VERTICAL: + case StyleAppearance::ScrollbarthumbVertical: aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_VERTICAL; break; - case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + case StyleAppearance::ScrollbarthumbHorizontal: aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL; break; - case NS_THEME_INNER_SPIN_BUTTON: + case StyleAppearance::InnerSpinButton: aGtkWidgetType = MOZ_GTK_INNER_SPIN_BUTTON; break; - case NS_THEME_SPINNER: + case StyleAppearance::Spinner: aGtkWidgetType = MOZ_GTK_SPINBUTTON; break; - case NS_THEME_SPINNER_UPBUTTON: + case StyleAppearance::SpinnerUpbutton: aGtkWidgetType = MOZ_GTK_SPINBUTTON_UP; break; - case NS_THEME_SPINNER_DOWNBUTTON: + case StyleAppearance::SpinnerDownbutton: aGtkWidgetType = MOZ_GTK_SPINBUTTON_DOWN; break; - case NS_THEME_SPINNER_TEXTFIELD: + case StyleAppearance::SpinnerTextfield: aGtkWidgetType = MOZ_GTK_SPINBUTTON_ENTRY; break; - case NS_THEME_RANGE: + case StyleAppearance::Range: { if (IsRangeHorizontal(aFrame)) { if (aWidgetFlags) @@ -511,7 +525,7 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u } break; } - case NS_THEME_RANGE_THUMB: + case StyleAppearance::RangeThumb: { if (IsRangeHorizontal(aFrame)) { if (aWidgetFlags) @@ -524,51 +538,51 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u } break; } - case NS_THEME_SCALE_HORIZONTAL: + case StyleAppearance::ScaleHorizontal: if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL; aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL; break; - case NS_THEME_SCALETHUMB_HORIZONTAL: + case StyleAppearance::ScalethumbHorizontal: if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL; aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL; break; - case NS_THEME_SCALE_VERTICAL: + case StyleAppearance::ScaleVertical: if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_VERTICAL; aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL; break; - case NS_THEME_SEPARATOR: + case StyleAppearance::Separator: aGtkWidgetType = MOZ_GTK_TOOLBAR_SEPARATOR; break; - case NS_THEME_SCALETHUMB_VERTICAL: + case StyleAppearance::ScalethumbVertical: if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_VERTICAL; aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL; break; - case NS_THEME_TOOLBARGRIPPER: + case StyleAppearance::Toolbargripper: aGtkWidgetType = MOZ_GTK_GRIPPER; break; - case NS_THEME_RESIZER: + case StyleAppearance::Resizer: aGtkWidgetType = MOZ_GTK_RESIZER; break; - case NS_THEME_NUMBER_INPUT: - case NS_THEME_TEXTFIELD: + case StyleAppearance::NumberInput: + case StyleAppearance::Textfield: aGtkWidgetType = MOZ_GTK_ENTRY; break; - case NS_THEME_TEXTFIELD_MULTILINE: + case StyleAppearance::TextfieldMultiline: #ifdef MOZ_WIDGET_GTK aGtkWidgetType = MOZ_GTK_TEXT_VIEW; #else aGtkWidgetType = MOZ_GTK_ENTRY; #endif break; - case NS_THEME_LISTBOX: - case NS_THEME_TREEVIEW: + case StyleAppearance::Listbox: + case StyleAppearance::Treeview: aGtkWidgetType = MOZ_GTK_TREEVIEW; break; - case NS_THEME_TREEHEADERCELL: + case StyleAppearance::Treeheadercell: if (aWidgetFlags) { // In this case, the flag denotes whether the header is the sorted one or not if (GetTreeSortDirection(aFrame) == eTreeSortDirection_Natural) @@ -578,7 +592,7 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u } aGtkWidgetType = MOZ_GTK_TREE_HEADER_CELL; break; - case NS_THEME_TREEHEADERSORTARROW: + case StyleAppearance::Treeheadersortarrow: if (aWidgetFlags) { switch (GetTreeSortDirection(aFrame)) { case eTreeSortDirection_Ascending: @@ -598,74 +612,75 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u } aGtkWidgetType = MOZ_GTK_TREE_HEADER_SORTARROW; break; - case NS_THEME_TREETWISTY: + case StyleAppearance::Treetwisty: aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER; if (aWidgetFlags) *aWidgetFlags = GTK_EXPANDER_COLLAPSED; break; - case NS_THEME_TREETWISTYOPEN: + case StyleAppearance::Treetwistyopen: aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER; if (aWidgetFlags) *aWidgetFlags = GTK_EXPANDER_EXPANDED; break; - case NS_THEME_MENULIST: + case StyleAppearance::Menulist: aGtkWidgetType = MOZ_GTK_DROPDOWN; if (aWidgetFlags) *aWidgetFlags = IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML); break; - case NS_THEME_MENULIST_TEXT: + case StyleAppearance::MenulistText: return false; // nothing to do, but prevents the bg from being drawn - case NS_THEME_MENULIST_TEXTFIELD: + case StyleAppearance::MenulistTextfield: aGtkWidgetType = MOZ_GTK_DROPDOWN_ENTRY; break; - case NS_THEME_MENULIST_BUTTON: + case StyleAppearance::MenulistButton: + case StyleAppearance::MozMenulistButton: aGtkWidgetType = MOZ_GTK_DROPDOWN_ARROW; break; - case NS_THEME_TOOLBARBUTTON_DROPDOWN: - case NS_THEME_BUTTON_ARROW_DOWN: - case NS_THEME_BUTTON_ARROW_UP: - case NS_THEME_BUTTON_ARROW_NEXT: - case NS_THEME_BUTTON_ARROW_PREVIOUS: + case StyleAppearance::ToolbarbuttonDropdown: + case StyleAppearance::ButtonArrowDown: + case StyleAppearance::ButtonArrowUp: + case StyleAppearance::ButtonArrowNext: + case StyleAppearance::ButtonArrowPrevious: aGtkWidgetType = MOZ_GTK_TOOLBARBUTTON_ARROW; if (aWidgetFlags) { *aWidgetFlags = GTK_ARROW_DOWN; - if (aWidgetType == NS_THEME_BUTTON_ARROW_UP) + if (aWidgetType == StyleAppearance::ButtonArrowUp) *aWidgetFlags = GTK_ARROW_UP; - else if (aWidgetType == NS_THEME_BUTTON_ARROW_NEXT) + else if (aWidgetType == StyleAppearance::ButtonArrowNext) *aWidgetFlags = GTK_ARROW_RIGHT; - else if (aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS) + else if (aWidgetType == StyleAppearance::ButtonArrowPrevious) *aWidgetFlags = GTK_ARROW_LEFT; } break; - case NS_THEME_CHECKBOX_CONTAINER: + case StyleAppearance::CheckboxContainer: aGtkWidgetType = MOZ_GTK_CHECKBUTTON_CONTAINER; break; - case NS_THEME_RADIO_CONTAINER: + case StyleAppearance::RadioContainer: aGtkWidgetType = MOZ_GTK_RADIOBUTTON_CONTAINER; break; - case NS_THEME_CHECKBOX_LABEL: + case StyleAppearance::CheckboxLabel: aGtkWidgetType = MOZ_GTK_CHECKBUTTON_LABEL; break; - case NS_THEME_RADIO_LABEL: + case StyleAppearance::RadioLabel: aGtkWidgetType = MOZ_GTK_RADIOBUTTON_LABEL; break; - case NS_THEME_TOOLBAR: + case StyleAppearance::Toolbar: aGtkWidgetType = MOZ_GTK_TOOLBAR; break; - case NS_THEME_TOOLTIP: + case StyleAppearance::Tooltip: aGtkWidgetType = MOZ_GTK_TOOLTIP; break; - case NS_THEME_STATUSBARPANEL: - case NS_THEME_RESIZERPANEL: + case StyleAppearance::Statusbarpanel: + case StyleAppearance::Resizerpanel: aGtkWidgetType = MOZ_GTK_FRAME; break; - case NS_THEME_PROGRESSBAR: - case NS_THEME_PROGRESSBAR_VERTICAL: + case StyleAppearance::Progressbar: + case StyleAppearance::ProgressbarVertical: aGtkWidgetType = MOZ_GTK_PROGRESSBAR; break; - case NS_THEME_PROGRESSCHUNK: - case NS_THEME_PROGRESSCHUNK_VERTICAL: + case StyleAppearance::Progresschunk: + case StyleAppearance::ProgresschunkVertical: { nsIFrame* stateFrame = aFrame->GetParent(); EventStates eventStates = GetContentState(stateFrame, aWidgetType); @@ -677,17 +692,17 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u : MOZ_GTK_PROGRESS_CHUNK; } break; - case NS_THEME_TAB_SCROLL_ARROW_BACK: - case NS_THEME_TAB_SCROLL_ARROW_FORWARD: + case StyleAppearance::TabScrollArrowBack: + case StyleAppearance::TabScrollArrowForward: if (aWidgetFlags) - *aWidgetFlags = aWidgetType == NS_THEME_TAB_SCROLL_ARROW_BACK ? + *aWidgetFlags = aWidgetType == StyleAppearance::TabScrollArrowBack ? GTK_ARROW_LEFT : GTK_ARROW_RIGHT; aGtkWidgetType = MOZ_GTK_TAB_SCROLLARROW; break; - case NS_THEME_TABPANELS: + case StyleAppearance::Tabpanels: aGtkWidgetType = MOZ_GTK_TABPANELS; break; - case NS_THEME_TAB: + case StyleAppearance::Tab: { if (IsBottomTab(aFrame)) { aGtkWidgetType = MOZ_GTK_TAB_BOTTOM; @@ -709,19 +724,19 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u } } break; - case NS_THEME_SPLITTER: + case StyleAppearance::Splitter: if (IsHorizontal(aFrame)) aGtkWidgetType = MOZ_GTK_SPLITTER_VERTICAL; else aGtkWidgetType = MOZ_GTK_SPLITTER_HORIZONTAL; break; - case NS_THEME_MENUBAR: + case StyleAppearance::Menubar: aGtkWidgetType = MOZ_GTK_MENUBAR; break; - case NS_THEME_MENUPOPUP: + case StyleAppearance::Menupopup: aGtkWidgetType = MOZ_GTK_MENUPOPUP; break; - case NS_THEME_MENUITEM: + case StyleAppearance::Menuitem: { nsMenuFrame *menuFrame = do_QueryFrame(aFrame); if (menuFrame && menuFrame->IsOnMenuBar()) { @@ -731,41 +746,41 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u } aGtkWidgetType = MOZ_GTK_MENUITEM; break; - case NS_THEME_MENUSEPARATOR: + case StyleAppearance::Menuseparator: aGtkWidgetType = MOZ_GTK_MENUSEPARATOR; break; - case NS_THEME_MENUARROW: + case StyleAppearance::Menuarrow: aGtkWidgetType = MOZ_GTK_MENUARROW; break; - case NS_THEME_CHECKMENUITEM: + case StyleAppearance::Checkmenuitem: aGtkWidgetType = MOZ_GTK_CHECKMENUITEM; break; - case NS_THEME_RADIOMENUITEM: + case StyleAppearance::Radiomenuitem: aGtkWidgetType = MOZ_GTK_RADIOMENUITEM; break; - case NS_THEME_WINDOW: - case NS_THEME_DIALOG: + case StyleAppearance::Window: + case StyleAppearance::Dialog: aGtkWidgetType = MOZ_GTK_WINDOW; break; - case NS_THEME_GTK_INFO_BAR: + case StyleAppearance::MozGtkInfoBar: aGtkWidgetType = MOZ_GTK_INFO_BAR; break; - case NS_THEME_WINDOW_TITLEBAR: + case StyleAppearance::MozWindowTitlebar: aGtkWidgetType = MOZ_GTK_HEADER_BAR; break; - case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + case StyleAppearance::MozWindowTitlebarMaximized: aGtkWidgetType = MOZ_GTK_HEADER_BAR_MAXIMIZED; break; - case NS_THEME_WINDOW_BUTTON_CLOSE: + case StyleAppearance::MozWindowButtonClose: aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_CLOSE; break; - case NS_THEME_WINDOW_BUTTON_MINIMIZE: + case StyleAppearance::MozWindowButtonMinimize: aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE; break; - case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case StyleAppearance::MozWindowButtonMaximize: aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE; break; - case NS_THEME_WINDOW_BUTTON_RESTORE: + case StyleAppearance::MozWindowButtonRestore: aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE; break; default: @@ -1025,7 +1040,8 @@ DrawThemeWithCairo(gfxContext* aContext, } bool -nsNativeThemeGTK::GetExtraSizeForWidget(nsIFrame* aFrame, uint8_t aWidgetType, +nsNativeThemeGTK::GetExtraSizeForWidget(nsIFrame* aFrame, + StyleAppearance aWidgetType, nsIntMargin* aExtra) { *aExtra = nsIntMargin(0,0,0,0); @@ -1033,14 +1049,14 @@ nsNativeThemeGTK::GetExtraSizeForWidget( // GTK2 themes (Ximian Industrial, Bluecurve, Misty, at least); // We modify the frame's overflow area. See bug 297508. switch (aWidgetType) { - case NS_THEME_SCROLLBARTHUMB_VERTICAL: + case StyleAppearance::ScrollbarthumbVertical: aExtra->top = aExtra->bottom = 1; break; - case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + case StyleAppearance::ScrollbarthumbHorizontal: aExtra->left = aExtra->right = 1; break; - case NS_THEME_BUTTON : + case StyleAppearance::Button : { if (IsDefaultButton(aFrame)) { // Some themes draw a default indicator outside the widget, @@ -1055,14 +1071,14 @@ nsNativeThemeGTK::GetExtraSizeForWidget( } return false; } - case NS_THEME_FOCUS_OUTLINE: + case StyleAppearance::FocusOutline: { moz_gtk_get_focus_outline_size(&aExtra->left, &aExtra->top); aExtra->right = aExtra->left; aExtra->bottom = aExtra->top; break; } - case NS_THEME_TAB : + case StyleAppearance::Tab : { if (!IsSelectedTab(aFrame)) return false; @@ -1097,7 +1113,7 @@ nsNativeThemeGTK::GetExtraSizeForWidget( NS_IMETHODIMP nsNativeThemeGTK::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame, - uint8_t aWidgetType, + StyleAppearance aWidgetType, const nsRect& aRect, const nsRect& aDirtyRect) { @@ -1191,8 +1207,8 @@ nsNativeThemeGTK::DrawWidgetBackground(g #ifdef DEBUG printf("GTK theme failed for widget type %d, error was %d, state was " "[active=%d,focused=%d,inHover=%d,disabled=%d]\n", - aWidgetType, gLastGdkError, state.active, state.focused, - state.inHover, state.disabled); + static_cast(aWidgetType), gLastGdkError, state.active, + state.focused, state.inHover, state.disabled); #endif NS_WARNING("GTK theme failed; disabling unsafe widget"); SetWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType); @@ -1221,16 +1237,16 @@ nsNativeThemeGTK::CreateWebRenderCommand const mozilla::layers::StackingContextHelper& aSc, mozilla::layers::WebRenderLayerManager* aManager, nsIFrame* aFrame, - uint8_t aWidgetType, + StyleAppearance aWidgetType, const nsRect& aRect) { nsPresContext* presContext = aFrame->PresContext(); - wr::LayoutRect bounds = aSc.ToRelativeLayoutRect( + wr::LayoutRect bounds = wr::ToRoundedLayoutRect( LayoutDeviceRect::FromAppUnits(aRect, presContext->AppUnitsPerDevPixel())); switch (aWidgetType) { - case NS_THEME_WINDOW: - case NS_THEME_DIALOG: + case StyleAppearance::Window: + case StyleAppearance::Dialog: aBuilder.PushRect(bounds, bounds, true, wr::ToColorF(Color::FromABGR( LookAndFeel::GetColor(LookAndFeel::eColorID_WindowBackground, @@ -1243,7 +1259,7 @@ nsNativeThemeGTK::CreateWebRenderCommand } WidgetNodeType -nsNativeThemeGTK::NativeThemeToGtkTheme(uint8_t aWidgetType, nsIFrame* aFrame) +nsNativeThemeGTK::NativeThemeToGtkTheme(StyleAppearance aWidgetType, nsIFrame* aFrame) { WidgetNodeType gtkWidgetType; gint unusedFlags; @@ -1258,9 +1274,10 @@ nsNativeThemeGTK::NativeThemeToGtkTheme( } void -nsNativeThemeGTK::GetCachedWidgetBorder(nsIFrame* aFrame, uint8_t aWidgetType, +nsNativeThemeGTK::GetCachedWidgetBorder(nsIFrame* aFrame, + StyleAppearance aWidgetType, GtkTextDirection aDirection, - nsIntMargin* aResult) + LayoutDeviceIntMargin* aResult) { aResult->SizeTo(0, 0, 0, 0); @@ -1277,7 +1294,7 @@ nsNativeThemeGTK::GetCachedWidgetBorder( } else { moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top, &aResult->right, &aResult->bottom, aDirection); - if (aWidgetType != MOZ_GTK_DROPDOWN) { // depends on aDirection + if (gtkWidgetType != MOZ_GTK_DROPDOWN) { // depends on aDirection mBorderCacheValid[cacheIndex] |= cacheBit; mBorderCache[gtkWidgetType] = *aResult; } @@ -1285,49 +1302,52 @@ nsNativeThemeGTK::GetCachedWidgetBorder( } } -NS_IMETHODIMP -nsNativeThemeGTK::GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame, - uint8_t aWidgetType, nsIntMargin* aResult) +LayoutDeviceIntMargin +nsNativeThemeGTK::GetWidgetBorder(nsDeviceContext* aContext, + nsIFrame* aFrame, + StyleAppearance aWidgetType) { + LayoutDeviceIntMargin result; GtkTextDirection direction = GetTextDirection(aFrame); - aResult->top = aResult->left = aResult->right = aResult->bottom = 0; switch (aWidgetType) { - case NS_THEME_SCROLLBAR_HORIZONTAL: - case NS_THEME_SCROLLBAR_VERTICAL: + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::ScrollbarVertical: { GtkOrientation orientation = - aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL ? + aWidgetType == StyleAppearance::ScrollbarHorizontal ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL; - const ScrollbarGTKMetrics* metrics = GetScrollbarMetrics(orientation, true); + const ScrollbarGTKMetrics* metrics = + GetActiveScrollbarMetrics(orientation); const GtkBorder& border = metrics->border.scrollbar; - aResult->top = border.top; - aResult->right = border.right; - aResult->bottom = border.bottom; - aResult->left = border.left; + result.top = border.top; + result.right = border.right; + result.bottom = border.bottom; + result.left = border.left; } break; - case NS_THEME_SCROLLBARTRACK_HORIZONTAL: - case NS_THEME_SCROLLBARTRACK_VERTICAL: + case StyleAppearance::ScrollbartrackHorizontal: + case StyleAppearance::ScrollbartrackVertical: { GtkOrientation orientation = - aWidgetType == NS_THEME_SCROLLBARTRACK_HORIZONTAL ? + aWidgetType == StyleAppearance::ScrollbartrackHorizontal ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL; - const ScrollbarGTKMetrics* metrics = GetScrollbarMetrics(orientation, true); + const ScrollbarGTKMetrics* metrics = + GetActiveScrollbarMetrics(orientation); const GtkBorder& border = metrics->border.track; - aResult->top = border.top; - aResult->right = border.right; - aResult->bottom = border.bottom; - aResult->left = border.left; + result.top = border.top; + result.right = border.right; + result.bottom = border.bottom; + result.left = border.left; } break; - case NS_THEME_TOOLBOX: + case StyleAppearance::Toolbox: // gtk has no toolbox equivalent. So, although we map toolbox to // gtk's 'toolbar' for purposes of painting the widget background, // we don't use the toolbar border for toolbox. break; - case NS_THEME_DUALBUTTON: + case StyleAppearance::Dualbutton: // TOOLBAR_DUAL_BUTTON is an interesting case. We want a border to draw // around the entire button + dropdown, and also an inner border if you're // over the button part. But, we want the inner button to be right up @@ -1335,23 +1355,23 @@ nsNativeThemeGTK::GetWidgetBorder(nsDevi // To make this happen, we draw a button border for the outer button, // but don't reserve any space for it. break; - case NS_THEME_TAB: + case StyleAppearance::Tab: { WidgetNodeType gtkWidgetType; gint flags; if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr, - &flags)) - return NS_OK; - - moz_gtk_get_tab_border(&aResult->left, &aResult->top, - &aResult->right, &aResult->bottom, direction, + &flags)) { + return result; + } + moz_gtk_get_tab_border(&result.left, &result.top, + &result.right, &result.bottom, direction, (GtkTabFlags)flags, gtkWidgetType); } break; - case NS_THEME_MENUITEM: - case NS_THEME_CHECKMENUITEM: - case NS_THEME_RADIOMENUITEM: + case StyleAppearance::Menuitem: + case StyleAppearance::Checkmenuitem: + case StyleAppearance::Radiomenuitem: // For regular menuitems, we will be using GetWidgetPadding instead of // GetWidgetBorder to pad up the widget's internals; other menuitems // will need to fall through and use the default case as before. @@ -1360,50 +1380,57 @@ nsNativeThemeGTK::GetWidgetBorder(nsDevi MOZ_FALLTHROUGH; default: { - GetCachedWidgetBorder(aFrame, aWidgetType, direction, aResult); + GetCachedWidgetBorder(aFrame, aWidgetType, direction, &result); } } gint scale = GetMonitorScaleFactor(aFrame); - aResult->top *= scale; - aResult->right *= scale; - aResult->bottom *= scale; - aResult->left *= scale; - return NS_OK; + result.top *= scale; + result.right *= scale; + result.bottom *= scale; + result.left *= scale; + return result; } bool nsNativeThemeGTK::GetWidgetPadding(nsDeviceContext* aContext, - nsIFrame* aFrame, uint8_t aWidgetType, - nsIntMargin* aResult) -{ + nsIFrame* aFrame, + StyleAppearance aWidgetType, + LayoutDeviceIntMargin* aResult) +{ + if (aWidgetType == StyleAppearance::MenulistButton && + StaticPrefs::layout_css_webkit_appearance_enabled()) { + aWidgetType = StyleAppearance::Menulist; + } + switch (aWidgetType) { - case NS_THEME_BUTTON_FOCUS: - case NS_THEME_TOOLBARBUTTON: - case NS_THEME_WINDOW_BUTTON_CLOSE: - case NS_THEME_WINDOW_BUTTON_MINIMIZE: - case NS_THEME_WINDOW_BUTTON_MAXIMIZE: - case NS_THEME_WINDOW_BUTTON_RESTORE: - case NS_THEME_DUALBUTTON: - case NS_THEME_TAB_SCROLL_ARROW_BACK: - case NS_THEME_TAB_SCROLL_ARROW_FORWARD: - case NS_THEME_MENULIST_BUTTON: - case NS_THEME_TOOLBARBUTTON_DROPDOWN: - case NS_THEME_BUTTON_ARROW_UP: - case NS_THEME_BUTTON_ARROW_DOWN: - case NS_THEME_BUTTON_ARROW_NEXT: - case NS_THEME_BUTTON_ARROW_PREVIOUS: - case NS_THEME_RANGE_THUMB: + case StyleAppearance::ButtonFocus: + case StyleAppearance::Toolbarbutton: + case StyleAppearance::MozWindowButtonClose: + case StyleAppearance::MozWindowButtonMinimize: + case StyleAppearance::MozWindowButtonMaximize: + case StyleAppearance::MozWindowButtonRestore: + case StyleAppearance::Dualbutton: + case StyleAppearance::TabScrollArrowBack: + case StyleAppearance::TabScrollArrowForward: + case StyleAppearance::MenulistButton: + case StyleAppearance::MozMenulistButton: + case StyleAppearance::ToolbarbuttonDropdown: + case StyleAppearance::ButtonArrowUp: + case StyleAppearance::ButtonArrowDown: + case StyleAppearance::ButtonArrowNext: + case StyleAppearance::ButtonArrowPrevious: + case StyleAppearance::RangeThumb: // Radios and checkboxes return a fixed size in GetMinimumWidgetSize // and have a meaningful baseline, so they can't have // author-specified padding. - case NS_THEME_CHECKBOX: - case NS_THEME_RADIO: + case StyleAppearance::Checkbox: + case StyleAppearance::Radio: aResult->SizeTo(0, 0, 0, 0); return true; - case NS_THEME_MENUITEM: - case NS_THEME_CHECKMENUITEM: - case NS_THEME_RADIOMENUITEM: + case StyleAppearance::Menuitem: + case StyleAppearance::Checkmenuitem: + case StyleAppearance::Radiomenuitem: { // Menubar and menulist have their padding specified in CSS. if (!IsRegularMenuItem(aFrame)) @@ -1413,8 +1440,7 @@ nsNativeThemeGTK::GetWidgetPadding(nsDev aResult); gint horizontal_padding; - - if (aWidgetType == NS_THEME_MENUITEM) + if (aWidgetType == StyleAppearance::Menuitem) moz_gtk_menuitem_get_horizontal_padding(&horizontal_padding); else moz_gtk_checkmenuitem_get_horizontal_padding(&horizontal_padding); @@ -1430,6 +1456,8 @@ nsNativeThemeGTK::GetWidgetPadding(nsDev return true; } + default: + break; } return false; @@ -1437,7 +1465,8 @@ nsNativeThemeGTK::GetWidgetPadding(nsDev bool nsNativeThemeGTK::GetWidgetOverflow(nsDeviceContext* aContext, - nsIFrame* aFrame, uint8_t aWidgetType, + nsIFrame* aFrame, + StyleAppearance aWidgetType, nsRect* aOverflowRect) { nsIntMargin extraSize; @@ -1456,37 +1485,43 @@ nsNativeThemeGTK::GetWidgetOverflow(nsDe NS_IMETHODIMP nsNativeThemeGTK::GetMinimumWidgetSize(nsPresContext* aPresContext, - nsIFrame* aFrame, uint8_t aWidgetType, + nsIFrame* aFrame, + StyleAppearance aWidgetType, LayoutDeviceIntSize* aResult, bool* aIsOverridable) { aResult->width = aResult->height = 0; *aIsOverridable = true; + if (aWidgetType == StyleAppearance::MenulistButton && + StaticPrefs::layout_css_webkit_appearance_enabled()) { + aWidgetType = StyleAppearance::Menulist; + } + switch (aWidgetType) { - case NS_THEME_SCROLLBARBUTTON_UP: - case NS_THEME_SCROLLBARBUTTON_DOWN: + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: { const ScrollbarGTKMetrics* metrics = - GetScrollbarMetrics(GTK_ORIENTATION_VERTICAL, true); + GetActiveScrollbarMetrics(GTK_ORIENTATION_VERTICAL); aResult->width = metrics->size.button.width; aResult->height = metrics->size.button.height; *aIsOverridable = false; } break; - case NS_THEME_SCROLLBARBUTTON_LEFT: - case NS_THEME_SCROLLBARBUTTON_RIGHT: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: { const ScrollbarGTKMetrics* metrics = - GetScrollbarMetrics(GTK_ORIENTATION_HORIZONTAL, true); + GetActiveScrollbarMetrics(GTK_ORIENTATION_HORIZONTAL); aResult->width = metrics->size.button.width; aResult->height = metrics->size.button.height; *aIsOverridable = false; } break; - case NS_THEME_SPLITTER: + case StyleAppearance::Splitter: { gint metrics; if (IsHorizontal(aFrame)) { @@ -1501,8 +1536,8 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n *aIsOverridable = false; } break; - case NS_THEME_SCROLLBAR_HORIZONTAL: - case NS_THEME_SCROLLBAR_VERTICAL: + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::ScrollbarVertical: { /* While we enforce a minimum size for the thumb, this is ignored * for the some scrollbars if buttons are hidden (bug 513006) because @@ -1510,28 +1545,30 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n * or track. So add a minimum size to the track as well to prevent a * 0-width scrollbar. */ GtkOrientation orientation = - aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL ? + aWidgetType == StyleAppearance::ScrollbarHorizontal ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL; - const ScrollbarGTKMetrics* metrics = GetScrollbarMetrics(orientation, true); + const ScrollbarGTKMetrics* metrics = + GetActiveScrollbarMetrics(orientation); aResult->width = metrics->size.scrollbar.width; aResult->height = metrics->size.scrollbar.height; } break; - case NS_THEME_SCROLLBARTHUMB_VERTICAL: - case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarthumbHorizontal: { GtkOrientation orientation = - aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL ? + aWidgetType == StyleAppearance::ScrollbarthumbHorizontal ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL; - const ScrollbarGTKMetrics* metrics = GetScrollbarMetrics(orientation, true); + const ScrollbarGTKMetrics* metrics = + GetActiveScrollbarMetrics(orientation); aResult->width = metrics->size.thumb.width; aResult->height = metrics->size.thumb.height; *aIsOverridable = false; } break; - case NS_THEME_RANGE_THUMB: + case StyleAppearance::RangeThumb: { gint thumb_length, thumb_height; @@ -1546,7 +1583,7 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n *aIsOverridable = false; } break; - case NS_THEME_RANGE: + case StyleAppearance::Range: { gint scale_width, scale_height; @@ -1559,12 +1596,12 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n *aIsOverridable = true; } break; - case NS_THEME_SCALETHUMB_HORIZONTAL: - case NS_THEME_SCALETHUMB_VERTICAL: + case StyleAppearance::ScalethumbHorizontal: + case StyleAppearance::ScalethumbVertical: { gint thumb_length, thumb_height; - if (aWidgetType == NS_THEME_SCALETHUMB_VERTICAL) { + if (aWidgetType == StyleAppearance::ScalethumbVertical) { moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_length, &thumb_height); aResult->width = thumb_height; aResult->height = thumb_length; @@ -1577,21 +1614,22 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n *aIsOverridable = false; } break; - case NS_THEME_TAB_SCROLL_ARROW_BACK: - case NS_THEME_TAB_SCROLL_ARROW_FORWARD: + case StyleAppearance::TabScrollArrowBack: + case StyleAppearance::TabScrollArrowForward: { moz_gtk_get_tab_scroll_arrow_size(&aResult->width, &aResult->height); *aIsOverridable = false; } break; - case NS_THEME_MENULIST_BUTTON: + case StyleAppearance::MenulistButton: + case StyleAppearance::MozMenulistButton: { moz_gtk_get_combo_box_entry_button_size(&aResult->width, &aResult->height); *aIsOverridable = false; } break; - case NS_THEME_MENUSEPARATOR: + case StyleAppearance::Menuseparator: { gint separator_height; @@ -1601,26 +1639,26 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n *aIsOverridable = false; } break; - case NS_THEME_CHECKBOX: - case NS_THEME_RADIO: + case StyleAppearance::Checkbox: + case StyleAppearance::Radio: { - const ToggleGTKMetrics* metrics = GetToggleMetrics(aWidgetType == NS_THEME_RADIO); + const ToggleGTKMetrics* metrics = GetToggleMetrics(aWidgetType == StyleAppearance::Radio); aResult->width = metrics->minSizeWithBorder.width; aResult->height = metrics->minSizeWithBorder.height; } break; - case NS_THEME_TOOLBARBUTTON_DROPDOWN: - case NS_THEME_BUTTON_ARROW_UP: - case NS_THEME_BUTTON_ARROW_DOWN: - case NS_THEME_BUTTON_ARROW_NEXT: - case NS_THEME_BUTTON_ARROW_PREVIOUS: + case StyleAppearance::ToolbarbuttonDropdown: + case StyleAppearance::ButtonArrowUp: + case StyleAppearance::ButtonArrowDown: + case StyleAppearance::ButtonArrowNext: + case StyleAppearance::ButtonArrowPrevious: { moz_gtk_get_arrow_size(MOZ_GTK_TOOLBARBUTTON_ARROW, &aResult->width, &aResult->height); *aIsOverridable = false; } break; - case NS_THEME_WINDOW_BUTTON_CLOSE: + case StyleAppearance::MozWindowButtonClose: { const ToolbarButtonGTKMetrics* metrics = GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_CLOSE); @@ -1628,7 +1666,7 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n aResult->height = metrics->minSizeWithBorderMargin.height; break; } - case NS_THEME_WINDOW_BUTTON_MINIMIZE: + case StyleAppearance::MozWindowButtonMinimize: { const ToolbarButtonGTKMetrics* metrics = GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE); @@ -1636,8 +1674,8 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n aResult->height = metrics->minSizeWithBorderMargin.height; break; } - case NS_THEME_WINDOW_BUTTON_MAXIMIZE: - case NS_THEME_WINDOW_BUTTON_RESTORE: + case StyleAppearance::MozWindowButtonMaximize: + case StyleAppearance::MozWindowButtonRestore: { const ToolbarButtonGTKMetrics* metrics = GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE); @@ -1645,16 +1683,16 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n aResult->height = metrics->minSizeWithBorderMargin.height; break; } - case NS_THEME_CHECKBOX_CONTAINER: - case NS_THEME_RADIO_CONTAINER: - case NS_THEME_CHECKBOX_LABEL: - case NS_THEME_RADIO_LABEL: - case NS_THEME_BUTTON: - case NS_THEME_MENULIST: - case NS_THEME_TOOLBARBUTTON: - case NS_THEME_TREEHEADERCELL: + case StyleAppearance::CheckboxContainer: + case StyleAppearance::RadioContainer: + case StyleAppearance::CheckboxLabel: + case StyleAppearance::RadioLabel: + case StyleAppearance::Button: + case StyleAppearance::Menulist: + case StyleAppearance::Toolbarbutton: + case StyleAppearance::Treeheadercell: { - if (aWidgetType == NS_THEME_MENULIST) { + if (aWidgetType == StyleAppearance::Menulist) { // Include the arrow size. moz_gtk_get_arrow_size(MOZ_GTK_DROPDOWN, &aResult->width, &aResult->height); @@ -1663,21 +1701,21 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n // descendants; the value returned here will not be helpful, but the // box model may consider border and padding with child minimum sizes. - nsIntMargin border; + LayoutDeviceIntMargin border; GetCachedWidgetBorder(aFrame, aWidgetType, GetTextDirection(aFrame), &border); aResult->width += border.left + border.right; aResult->height += border.top + border.bottom; } break; #ifdef MOZ_WIDGET_GTK - case NS_THEME_NUMBER_INPUT: - case NS_THEME_TEXTFIELD: + case StyleAppearance::NumberInput: + case StyleAppearance::Textfield: { moz_gtk_get_entry_min_height(&aResult->height); } break; #endif - case NS_THEME_SEPARATOR: + case StyleAppearance::Separator: { gint separator_width; @@ -1686,26 +1724,26 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n aResult->width = separator_width; } break; - case NS_THEME_INNER_SPIN_BUTTON: - case NS_THEME_SPINNER: + case StyleAppearance::InnerSpinButton: + case StyleAppearance::Spinner: // hard code these sizes aResult->width = 14; aResult->height = 26; break; - case NS_THEME_TREEHEADERSORTARROW: - case NS_THEME_SPINNER_UPBUTTON: - case NS_THEME_SPINNER_DOWNBUTTON: + case StyleAppearance::Treeheadersortarrow: + case StyleAppearance::SpinnerUpbutton: + case StyleAppearance::SpinnerDownbutton: // hard code these sizes aResult->width = 14; aResult->height = 13; break; - case NS_THEME_RESIZER: + case StyleAppearance::Resizer: // same as Windows to make our lives easier aResult->width = aResult->height = 15; *aIsOverridable = false; break; - case NS_THEME_TREETWISTY: - case NS_THEME_TREETWISTYOPEN: + case StyleAppearance::Treetwisty: + case StyleAppearance::Treetwistyopen: { gint expander_size; @@ -1714,6 +1752,8 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n *aIsOverridable = false; } break; + default: + break; } *aResult = *aResult * GetMonitorScaleFactor(aFrame); @@ -1722,41 +1762,42 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n } NS_IMETHODIMP -nsNativeThemeGTK::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType, +nsNativeThemeGTK::WidgetStateChanged(nsIFrame* aFrame, + StyleAppearance aWidgetType, nsAtom* aAttribute, bool* aShouldRepaint, const nsAttrValue* aOldValue) { // Some widget types just never change state. - if (aWidgetType == NS_THEME_TOOLBOX || - aWidgetType == NS_THEME_TOOLBAR || - aWidgetType == NS_THEME_STATUSBAR || - aWidgetType == NS_THEME_STATUSBARPANEL || - aWidgetType == NS_THEME_RESIZERPANEL || - aWidgetType == NS_THEME_PROGRESSCHUNK || - aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL || - aWidgetType == NS_THEME_PROGRESSBAR || - aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL || - aWidgetType == NS_THEME_MENUBAR || - aWidgetType == NS_THEME_MENUPOPUP || - aWidgetType == NS_THEME_TOOLTIP || - aWidgetType == NS_THEME_MENUSEPARATOR || - aWidgetType == NS_THEME_WINDOW || - aWidgetType == NS_THEME_DIALOG) { + if (aWidgetType == StyleAppearance::Toolbox || + aWidgetType == StyleAppearance::Toolbar || + aWidgetType == StyleAppearance::Statusbar || + aWidgetType == StyleAppearance::Statusbarpanel || + aWidgetType == StyleAppearance::Resizerpanel || + aWidgetType == StyleAppearance::Progresschunk || + aWidgetType == StyleAppearance::ProgresschunkVertical || + aWidgetType == StyleAppearance::Progressbar || + aWidgetType == StyleAppearance::ProgressbarVertical || + aWidgetType == StyleAppearance::Menubar || + aWidgetType == StyleAppearance::Menupopup || + aWidgetType == StyleAppearance::Tooltip || + aWidgetType == StyleAppearance::Menuseparator || + aWidgetType == StyleAppearance::Window || + aWidgetType == StyleAppearance::Dialog) { *aShouldRepaint = false; return NS_OK; } - if ((aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL || - aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) && + if ((aWidgetType == StyleAppearance::ScrollbarthumbVertical || + aWidgetType == StyleAppearance::ScrollbarthumbHorizontal) && aAttribute == nsGkAtoms::active) { *aShouldRepaint = true; return NS_OK; } - if ((aWidgetType == NS_THEME_SCROLLBARBUTTON_UP || - aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN || - aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT || - aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT) && + if ((aWidgetType == StyleAppearance::ScrollbarbuttonUp || + aWidgetType == StyleAppearance::ScrollbarbuttonDown || + aWidgetType == StyleAppearance::ScrollbarbuttonLeft || + aWidgetType == StyleAppearance::ScrollbarbuttonRight) && (aAttribute == nsGkAtoms::curpos || aAttribute == nsGkAtoms::maxpos)) { // If 'curpos' has changed and we are passed its old value, we can @@ -1820,120 +1861,135 @@ nsNativeThemeGTK::ThemeChanged() NS_IMETHODIMP_(bool) nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame, - uint8_t aWidgetType) + StyleAppearance aWidgetType) { if (IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType)) return false; + if (IsWidgetScrollbarPart(aWidgetType)) { + ComputedStyle* cs = nsLayoutUtils::StyleForScrollbar(aFrame); + if (cs->StyleUI()->HasCustomScrollbars() || + // We cannot handle thin scrollbar on GTK+ widget directly as well. + cs->StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::Thin) { + return false; + } + } + + if (aWidgetType == StyleAppearance::MenulistButton && + StaticPrefs::layout_css_webkit_appearance_enabled()) { + aWidgetType = StyleAppearance::Menulist; + } + switch (aWidgetType) { // Combobox dropdowns don't support native theming in vertical mode. - case NS_THEME_MENULIST: - case NS_THEME_MENULIST_TEXT: - case NS_THEME_MENULIST_TEXTFIELD: + case StyleAppearance::Menulist: + case StyleAppearance::MenulistText: + case StyleAppearance::MenulistTextfield: if (aFrame && aFrame->GetWritingMode().IsVertical()) { return false; } MOZ_FALLTHROUGH; - case NS_THEME_BUTTON: - case NS_THEME_BUTTON_FOCUS: - case NS_THEME_RADIO: - case NS_THEME_CHECKBOX: - case NS_THEME_TOOLBOX: // N/A - case NS_THEME_TOOLBAR: - case NS_THEME_TOOLBARBUTTON: - case NS_THEME_DUALBUTTON: // so we can override the border with 0 - case NS_THEME_TOOLBARBUTTON_DROPDOWN: - case NS_THEME_BUTTON_ARROW_UP: - case NS_THEME_BUTTON_ARROW_DOWN: - case NS_THEME_BUTTON_ARROW_NEXT: - case NS_THEME_BUTTON_ARROW_PREVIOUS: - case NS_THEME_SEPARATOR: - case NS_THEME_TOOLBARGRIPPER: - case NS_THEME_STATUSBAR: - case NS_THEME_STATUSBARPANEL: - case NS_THEME_RESIZERPANEL: - case NS_THEME_RESIZER: - case NS_THEME_LISTBOX: - // case NS_THEME_LISTITEM: - case NS_THEME_TREEVIEW: - // case NS_THEME_TREEITEM: - case NS_THEME_TREETWISTY: - // case NS_THEME_TREELINE: - // case NS_THEME_TREEHEADER: - case NS_THEME_TREEHEADERCELL: - case NS_THEME_TREEHEADERSORTARROW: - case NS_THEME_TREETWISTYOPEN: - case NS_THEME_PROGRESSBAR: - case NS_THEME_PROGRESSCHUNK: - case NS_THEME_PROGRESSBAR_VERTICAL: - case NS_THEME_PROGRESSCHUNK_VERTICAL: - case NS_THEME_TAB: - // case NS_THEME_TABPANEL: - case NS_THEME_TABPANELS: - case NS_THEME_TAB_SCROLL_ARROW_BACK: - case NS_THEME_TAB_SCROLL_ARROW_FORWARD: - case NS_THEME_TOOLTIP: - case NS_THEME_INNER_SPIN_BUTTON: - case NS_THEME_SPINNER: - case NS_THEME_SPINNER_UPBUTTON: - case NS_THEME_SPINNER_DOWNBUTTON: - case NS_THEME_SPINNER_TEXTFIELD: - // case NS_THEME_SCROLLBAR: (n/a for gtk) - // case NS_THEME_SCROLLBAR_SMALL: (n/a for gtk) - case NS_THEME_SCROLLBARBUTTON_UP: - case NS_THEME_SCROLLBARBUTTON_DOWN: - case NS_THEME_SCROLLBARBUTTON_LEFT: - case NS_THEME_SCROLLBARBUTTON_RIGHT: - case NS_THEME_SCROLLBAR_HORIZONTAL: - case NS_THEME_SCROLLBAR_VERTICAL: - case NS_THEME_SCROLLBARTRACK_HORIZONTAL: - case NS_THEME_SCROLLBARTRACK_VERTICAL: - case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: - case NS_THEME_SCROLLBARTHUMB_VERTICAL: - case NS_THEME_NUMBER_INPUT: - case NS_THEME_TEXTFIELD: - case NS_THEME_TEXTFIELD_MULTILINE: - case NS_THEME_RANGE: - case NS_THEME_RANGE_THUMB: - case NS_THEME_SCALE_HORIZONTAL: - case NS_THEME_SCALETHUMB_HORIZONTAL: - case NS_THEME_SCALE_VERTICAL: - case NS_THEME_SCALETHUMB_VERTICAL: - // case NS_THEME_SCALETHUMBSTART: - // case NS_THEME_SCALETHUMBEND: - // case NS_THEME_SCALETHUMBTICK: - case NS_THEME_CHECKBOX_CONTAINER: - case NS_THEME_RADIO_CONTAINER: - case NS_THEME_CHECKBOX_LABEL: - case NS_THEME_RADIO_LABEL: - case NS_THEME_MENUBAR: - case NS_THEME_MENUPOPUP: - case NS_THEME_MENUITEM: - case NS_THEME_MENUARROW: - case NS_THEME_MENUSEPARATOR: - case NS_THEME_CHECKMENUITEM: - case NS_THEME_RADIOMENUITEM: - case NS_THEME_SPLITTER: - case NS_THEME_WINDOW: - case NS_THEME_DIALOG: + case StyleAppearance::Button: + case StyleAppearance::ButtonFocus: + case StyleAppearance::Radio: + case StyleAppearance::Checkbox: + case StyleAppearance::Toolbox: // N/A + case StyleAppearance::Toolbar: + case StyleAppearance::Toolbarbutton: + case StyleAppearance::Dualbutton: // so we can override the border with 0 + case StyleAppearance::ToolbarbuttonDropdown: + case StyleAppearance::ButtonArrowUp: + case StyleAppearance::ButtonArrowDown: + case StyleAppearance::ButtonArrowNext: + case StyleAppearance::ButtonArrowPrevious: + case StyleAppearance::Separator: + case StyleAppearance::Toolbargripper: + case StyleAppearance::Statusbar: + case StyleAppearance::Statusbarpanel: + case StyleAppearance::Resizerpanel: + case StyleAppearance::Resizer: + case StyleAppearance::Listbox: + // case StyleAppearance::Listitem: + case StyleAppearance::Treeview: + // case StyleAppearance::Treeitem: + case StyleAppearance::Treetwisty: + // case StyleAppearance::Treeline: + // case StyleAppearance::Treeheader: + case StyleAppearance::Treeheadercell: + case StyleAppearance::Treeheadersortarrow: + case StyleAppearance::Treetwistyopen: + case StyleAppearance::Progressbar: + case StyleAppearance::Progresschunk: + case StyleAppearance::ProgressbarVertical: + case StyleAppearance::ProgresschunkVertical: + case StyleAppearance::Tab: + // case StyleAppearance::Tabpanel: + case StyleAppearance::Tabpanels: + case StyleAppearance::TabScrollArrowBack: + case StyleAppearance::TabScrollArrowForward: + case StyleAppearance::Tooltip: + case StyleAppearance::InnerSpinButton: + case StyleAppearance::Spinner: + case StyleAppearance::SpinnerUpbutton: + case StyleAppearance::SpinnerDownbutton: + case StyleAppearance::SpinnerTextfield: + // case StyleAppearance::Scrollbar: (n/a for gtk) + // case StyleAppearance::ScrollbarSmall: (n/a for gtk) + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::ScrollbartrackHorizontal: + case StyleAppearance::ScrollbartrackVertical: + case StyleAppearance::ScrollbarthumbHorizontal: + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::NumberInput: + case StyleAppearance::Textfield: + case StyleAppearance::TextfieldMultiline: + case StyleAppearance::Range: + case StyleAppearance::RangeThumb: + case StyleAppearance::ScaleHorizontal: + case StyleAppearance::ScalethumbHorizontal: + case StyleAppearance::ScaleVertical: + case StyleAppearance::ScalethumbVertical: + // case StyleAppearance::Scalethumbstart: + // case StyleAppearance::Scalethumbend: + // case StyleAppearance::Scalethumbtick: + case StyleAppearance::CheckboxContainer: + case StyleAppearance::RadioContainer: + case StyleAppearance::CheckboxLabel: + case StyleAppearance::RadioLabel: + case StyleAppearance::Menubar: + case StyleAppearance::Menupopup: + case StyleAppearance::Menuitem: + case StyleAppearance::Menuarrow: + case StyleAppearance::Menuseparator: + case StyleAppearance::Checkmenuitem: + case StyleAppearance::Radiomenuitem: + case StyleAppearance::Splitter: + case StyleAppearance::Window: + case StyleAppearance::Dialog: #ifdef MOZ_WIDGET_GTK - case NS_THEME_GTK_INFO_BAR: + case StyleAppearance::MozGtkInfoBar: #endif return !IsWidgetStyled(aPresContext, aFrame, aWidgetType); - case NS_THEME_WINDOW_BUTTON_CLOSE: - case NS_THEME_WINDOW_BUTTON_MINIMIZE: - case NS_THEME_WINDOW_BUTTON_MAXIMIZE: - case NS_THEME_WINDOW_BUTTON_RESTORE: - case NS_THEME_WINDOW_TITLEBAR: - case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + case StyleAppearance::MozWindowButtonClose: + case StyleAppearance::MozWindowButtonMinimize: + case StyleAppearance::MozWindowButtonMaximize: + case StyleAppearance::MozWindowButtonRestore: + case StyleAppearance::MozWindowTitlebar: + case StyleAppearance::MozWindowTitlebarMaximized: // GtkHeaderBar is available on GTK 3.10+, which is used for styling // title bars and title buttons. return gtk_check_version(3, 10, 0) == nullptr && !IsWidgetStyled(aPresContext, aFrame, aWidgetType); - case NS_THEME_MENULIST_BUTTON: + case StyleAppearance::MenulistButton: + case StyleAppearance::MozMenulistButton: if (aFrame && aFrame->GetWritingMode().IsVertical()) { return false; } @@ -1942,37 +1998,50 @@ nsNativeThemeGTK::ThemeSupportsWidget(ns return (!aFrame || IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) && !IsWidgetStyled(aPresContext, aFrame, aWidgetType); - case NS_THEME_FOCUS_OUTLINE: + case StyleAppearance::FocusOutline: return true; + default: + break; } return false; } NS_IMETHODIMP_(bool) -nsNativeThemeGTK::WidgetIsContainer(uint8_t aWidgetType) +nsNativeThemeGTK::WidgetIsContainer(StyleAppearance aWidgetType) { + if (aWidgetType == StyleAppearance::MenulistButton && + StaticPrefs::layout_css_webkit_appearance_enabled()) { + aWidgetType = StyleAppearance::Menulist; + } + // XXXdwh At some point flesh all of this out. - if (aWidgetType == NS_THEME_MENULIST_BUTTON || - aWidgetType == NS_THEME_RADIO || - aWidgetType == NS_THEME_RANGE_THUMB || - aWidgetType == NS_THEME_CHECKBOX || - aWidgetType == NS_THEME_TAB_SCROLL_ARROW_BACK || - aWidgetType == NS_THEME_TAB_SCROLL_ARROW_FORWARD || - aWidgetType == NS_THEME_BUTTON_ARROW_UP || - aWidgetType == NS_THEME_BUTTON_ARROW_DOWN || - aWidgetType == NS_THEME_BUTTON_ARROW_NEXT || - aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS) + if (aWidgetType == StyleAppearance::MenulistButton || + aWidgetType == StyleAppearance::MozMenulistButton || + aWidgetType == StyleAppearance::Radio || + aWidgetType == StyleAppearance::RangeThumb || + aWidgetType == StyleAppearance::Checkbox || + aWidgetType == StyleAppearance::TabScrollArrowBack || + aWidgetType == StyleAppearance::TabScrollArrowForward || + aWidgetType == StyleAppearance::ButtonArrowUp || + aWidgetType == StyleAppearance::ButtonArrowDown || + aWidgetType == StyleAppearance::ButtonArrowNext || + aWidgetType == StyleAppearance::ButtonArrowPrevious) return false; return true; } bool -nsNativeThemeGTK::ThemeDrawsFocusForWidget(uint8_t aWidgetType) +nsNativeThemeGTK::ThemeDrawsFocusForWidget(StyleAppearance aWidgetType) { - if (aWidgetType == NS_THEME_MENULIST || - aWidgetType == NS_THEME_BUTTON || - aWidgetType == NS_THEME_TREEHEADERCELL) + if (aWidgetType == StyleAppearance::MenulistButton && + StaticPrefs::layout_css_webkit_appearance_enabled()) { + aWidgetType = StyleAppearance::Menulist; + } + + if (aWidgetType == StyleAppearance::Menulist || + aWidgetType == StyleAppearance::Button || + aWidgetType == StyleAppearance::Treeheadercell) return true; return false; @@ -1985,16 +2054,17 @@ nsNativeThemeGTK::ThemeNeedsComboboxDrop } nsITheme::Transparency -nsNativeThemeGTK::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType) +nsNativeThemeGTK::GetWidgetTransparency(nsIFrame* aFrame, + StyleAppearance aWidgetType) { switch (aWidgetType) { // These widgets always draw a default background. - case NS_THEME_MENUPOPUP: - case NS_THEME_WINDOW: - case NS_THEME_DIALOG: + case StyleAppearance::Menupopup: + case StyleAppearance::Window: + case StyleAppearance::Dialog: return eOpaque; - case NS_THEME_SCROLLBAR_VERTICAL: - case NS_THEME_SCROLLBAR_HORIZONTAL: + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::ScrollbarHorizontal: #ifdef MOZ_WIDGET_GTK // Make scrollbar tracks opaque on the window's scroll frame to prevent // leaf layers from overlapping. See bug 1179780. @@ -2006,9 +2076,10 @@ nsNativeThemeGTK::GetWidgetTransparency( return eOpaque; // Tooltips use gtk_paint_flat_box() on Gtk2 // but are shaped on Gtk3 - case NS_THEME_TOOLTIP: + case StyleAppearance::Tooltip: return eTransparent; + default: + return eUnknownTransparency; } - return eUnknownTransparency; } diff -up thunderbird-60.3.0/widget/gtk/nsNativeThemeGTK.h.wayland thunderbird-60.3.0/widget/gtk/nsNativeThemeGTK.h --- thunderbird-60.3.0/widget/gtk/nsNativeThemeGTK.h.wayland 2018-10-30 12:45:37.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsNativeThemeGTK.h 2018-11-20 12:04:43.739787343 +0100 @@ -11,7 +11,7 @@ #include "nsAtom.h" #include "nsIObserver.h" #include "nsNativeTheme.h" -#include "nsThemeConstants.h" +#include "nsStyleConsts.h" #include #include "gtkdrawing.h" @@ -26,7 +26,7 @@ public: // The nsITheme interface. NS_IMETHOD DrawWidgetBackground(gfxContext* aContext, - nsIFrame* aFrame, uint8_t aWidgetType, + nsIFrame* aFrame, WidgetType aWidgetType, const nsRect& aRect, const nsRect& aDirtyRect) override; @@ -35,29 +35,29 @@ public: const mozilla::layers::StackingContextHelper& aSc, mozilla::layers::WebRenderLayerManager* aManager, nsIFrame* aFrame, - uint8_t aWidgetType, + WidgetType aWidgetType, const nsRect& aRect) override; - NS_IMETHOD GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame, - uint8_t aWidgetType, - nsIntMargin* aResult) override; - - virtual bool GetWidgetPadding(nsDeviceContext* aContext, - nsIFrame* aFrame, - uint8_t aWidgetType, - nsIntMargin* aResult) override; + MOZ_MUST_USE LayoutDeviceIntMargin GetWidgetBorder(nsDeviceContext* aContext, + nsIFrame* aFrame, + WidgetType aWidgetType) override; + + bool GetWidgetPadding(nsDeviceContext* aContext, + nsIFrame* aFrame, + WidgetType aWidgetType, + LayoutDeviceIntMargin* aResult) override; virtual bool GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame, - uint8_t aWidgetType, + WidgetType aWidgetType, nsRect* aOverflowRect) override; NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, - nsIFrame* aFrame, uint8_t aWidgetType, + nsIFrame* aFrame, WidgetType aWidgetType, mozilla::LayoutDeviceIntSize* aResult, bool* aIsOverridable) override; - NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType, + NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, WidgetType aWidgetType, nsAtom* aAttribute, bool* aShouldRepaint, const nsAttrValue* aOldValue) override; @@ -66,16 +66,16 @@ public: NS_IMETHOD_(bool) ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame, - uint8_t aWidgetType) override; + WidgetType aWidgetType) override; - NS_IMETHOD_(bool) WidgetIsContainer(uint8_t aWidgetType) override; + NS_IMETHOD_(bool) WidgetIsContainer(WidgetType aWidgetType) override; - NS_IMETHOD_(bool) ThemeDrawsFocusForWidget(uint8_t aWidgetType) override; + NS_IMETHOD_(bool) ThemeDrawsFocusForWidget(WidgetType aWidgetType) override; virtual bool ThemeNeedsComboboxDropmarker() override; virtual Transparency GetWidgetTransparency(nsIFrame* aFrame, - uint8_t aWidgetType) override; + WidgetType aWidgetType) override; nsNativeThemeGTK(); protected: @@ -84,26 +84,27 @@ protected: private: GtkTextDirection GetTextDirection(nsIFrame* aFrame); gint GetTabMarginPixels(nsIFrame* aFrame); - bool GetGtkWidgetAndState(uint8_t aWidgetType, nsIFrame* aFrame, + bool GetGtkWidgetAndState(WidgetType aWidgetType, nsIFrame* aFrame, WidgetNodeType& aGtkWidgetType, GtkWidgetState* aState, gint* aWidgetFlags); - bool GetExtraSizeForWidget(nsIFrame* aFrame, uint8_t aWidgetType, + bool GetExtraSizeForWidget(nsIFrame* aFrame, WidgetType aWidgetType, nsIntMargin* aExtra); void RefreshWidgetWindow(nsIFrame* aFrame); - WidgetNodeType NativeThemeToGtkTheme(uint8_t aWidgetType, nsIFrame* aFrame); + WidgetNodeType NativeThemeToGtkTheme(WidgetType aWidgetType, nsIFrame* aFrame); - uint8_t mDisabledWidgetTypes[(ThemeWidgetType_COUNT + 7) / 8]; - uint8_t mSafeWidgetStates[ThemeWidgetType_COUNT * 4]; // 32 bits per widget + uint8_t mDisabledWidgetTypes[(static_cast(mozilla::StyleAppearance::Count) + 7) / 8]; + uint8_t mSafeWidgetStates[static_cast(mozilla::StyleAppearance::Count) * 4]; // 32 bits per widget static const char* sDisabledEngines[]; // Because moz_gtk_get_widget_border can be slow, we cache its results // by widget type. Each bit in mBorderCacheValid says whether the // corresponding entry in mBorderCache is valid. - void GetCachedWidgetBorder(nsIFrame* aFrame, uint8_t aWidgetType, - GtkTextDirection aDirection, nsIntMargin* aResult); + void GetCachedWidgetBorder(nsIFrame* aFrame, WidgetType aWidgetType, + GtkTextDirection aDirection, + LayoutDeviceIntMargin* aResult); uint8_t mBorderCacheValid[(MOZ_GTK_WIDGET_NODE_COUNT + 7) / 8]; - nsIntMargin mBorderCache[MOZ_GTK_WIDGET_NODE_COUNT]; + LayoutDeviceIntMargin mBorderCache[MOZ_GTK_WIDGET_NODE_COUNT]; }; #endif diff -up thunderbird-60.3.0/widget/gtk/nsPrintDialogGTK.cpp.wayland thunderbird-60.3.0/widget/gtk/nsPrintDialogGTK.cpp --- thunderbird-60.3.0/widget/gtk/nsPrintDialogGTK.cpp.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsPrintDialogGTK.cpp 2018-11-20 12:04:43.739787343 +0100 @@ -24,7 +24,20 @@ #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 +#include +#include +#include +#include + +// for dlsym +#include +#include "MainThreadUtils.h" using namespace mozilla; using namespace mozilla::widget; @@ -387,7 +400,7 @@ nsPrintDialogWidgetGTK::ExportHeaderFoot nsresult nsPrintDialogWidgetGTK::ImportSettings(nsIPrintSettings *aNSSettings) { - NS_PRECONDITION(aNSSettings, "aSettings must not be null"); + MOZ_ASSERT(aNSSettings, "aSettings must not be null"); NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE); nsCOMPtr aNSSettingsGTK(do_QueryInterface(aNSSettings)); @@ -416,7 +429,7 @@ nsPrintDialogWidgetGTK::ImportSettings(n nsresult nsPrintDialogWidgetGTK::ExportSettings(nsIPrintSettings *aNSSettings) { - NS_PRECONDITION(aNSSettings, "aSettings must not be null"); + MOZ_ASSERT(aNSSettings, "aSettings must not be null"); NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE); GtkPrintSettings* settings = gtk_print_unix_dialog_get_settings(GTK_PRINT_UNIX_DIALOG(dialog)); @@ -513,13 +526,522 @@ 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(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 + (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 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), + mResponseSignalId(0), + 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) +{ + MOZ_ASSERT(aWindow, "aWindow must not be null"); + MOZ_ASSERT(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(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 + (dlsym(RTLD_DEFAULT, "gtk_print_settings_to_gvariant")); + static auto s_gtk_page_setup_to_gvariant = + reinterpret_cast + (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 bundleSvc = + do_GetService(NS_STRINGBUNDLE_CONTRACTID); + nsCOMPtr 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); + if (mResponseSignalId) { + 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(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 + (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 + (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 + (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 + (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 + (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 os = mozilla::services::GetObserverService(); + // Let the nsFlatpakPrintPortal instance die + os->RemoveObserver(this, "print-to-file-finished"); + return NS_OK; +} + +nsFlatpakPrintPortal::~nsFlatpakPrintPortal() { + if (mProxy) { + if (mResponseSignalId) { + g_dbus_connection_signal_unsubscribe( + g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)), mResponseSignalId); + } + 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"); + MOZ_ASSERT(aParent, "aParent must not be null"); + MOZ_ASSERT(aSettings, "aSettings must not be null"); + + // Check for the flatpak portal first + nsCOMPtr giovfs = + do_GetService(NS_GIOSERVICE_CONTRACTID); + bool shouldUsePortal; + giovfs->ShouldUseFlatpakPortal(&shouldUsePortal); + if (shouldUsePortal && gtk_check_version(3, 22, 0) == nullptr) { + nsCOMPtr 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 printSettingsGTK(do_QueryInterface(aSettings)); + RefPtr 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 observer = do_QueryInterface(fpPrintPortal); + nsCOMPtr 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); @@ -553,8 +1075,8 @@ NS_IMETHODIMP nsPrintDialogServiceGTK::ShowPageSetup(nsPIDOMWindowOuter *aParent, nsIPrintSettings *aNSSettings) { - NS_PRECONDITION(aParent, "aParent must not be null"); - NS_PRECONDITION(aNSSettings, "aSettings must not be null"); + MOZ_ASSERT(aParent, "aParent must not be null"); + MOZ_ASSERT(aNSSettings, "aSettings must not be null"); NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE); nsCOMPtr widget = WidgetUtils::DOMWindowToWidget(aParent); diff -up thunderbird-60.3.0/widget/gtk/nsSound.cpp.wayland thunderbird-60.3.0/widget/gtk/nsSound.cpp --- thunderbird-60.3.0/widget/gtk/nsSound.cpp.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsSound.cpp 2018-11-20 12:04:43.739787343 +0100 @@ -417,43 +417,3 @@ NS_IMETHODIMP nsSound::PlayEventSound(ui } return NS_OK; } - -NS_IMETHODIMP nsSound::PlaySystemSound(const nsAString &aSoundAlias) -{ - if (NS_IsMozAliasSound(aSoundAlias)) { - NS_WARNING("nsISound::playSystemSound is called with \"_moz_\" events, they are obsolete, use nsISound::playEventSound instead"); - uint32_t eventId; - if (aSoundAlias.Equals(NS_SYSSOUND_ALERT_DIALOG)) - eventId = EVENT_ALERT_DIALOG_OPEN; - else if (aSoundAlias.Equals(NS_SYSSOUND_CONFIRM_DIALOG)) - eventId = EVENT_CONFIRM_DIALOG_OPEN; - else if (aSoundAlias.Equals(NS_SYSSOUND_MAIL_BEEP)) - eventId = EVENT_NEW_MAIL_RECEIVED; - else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_EXECUTE)) - eventId = EVENT_MENU_EXECUTE; - else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_POPUP)) - eventId = EVENT_MENU_POPUP; - else - return NS_OK; - return PlayEventSound(eventId); - } - - nsresult rv; - nsCOMPtr fileURI; - - // create a nsIFile and then a nsIFileURL from that - nsCOMPtr soundFile; - rv = NS_NewLocalFile(aSoundAlias, true, - getter_AddRefs(soundFile)); - NS_ENSURE_SUCCESS(rv,rv); - - rv = NS_NewFileURI(getter_AddRefs(fileURI), soundFile); - NS_ENSURE_SUCCESS(rv,rv); - - nsCOMPtr fileURL = do_QueryInterface(fileURI,&rv); - NS_ENSURE_SUCCESS(rv,rv); - - rv = Play(fileURL); - - return rv; -} diff -up thunderbird-60.3.0/widget/gtk/nsWidgetFactory.cpp.wayland thunderbird-60.3.0/widget/gtk/nsWidgetFactory.cpp --- thunderbird-60.3.0/widget/gtk/nsWidgetFactory.cpp.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsWidgetFactory.cpp 2018-11-20 12:04:43.739787343 +0100 @@ -27,6 +27,7 @@ #ifdef MOZ_WIDGET_GTK #include "nsApplicationChooser.h" #endif +#include "TaskbarProgress.h" #include "nsColorPicker.h" #include "nsFilePicker.h" #include "nsSound.h" @@ -60,7 +61,6 @@ using namespace mozilla; using namespace mozilla::widget; -NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindow) NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable) NS_GENERIC_FACTORY_CONSTRUCTOR(nsBidiKeyboard) NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter) @@ -72,6 +72,7 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsISound, nsSound::GetInstance) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(ScreenManager, ScreenManager::GetAddRefedSingleton) NS_GENERIC_FACTORY_CONSTRUCTOR(nsImageToPixbuf) +NS_GENERIC_FACTORY_CONSTRUCTOR(TaskbarProgress) // from nsWindow.cpp @@ -196,14 +197,13 @@ nsClipboardConstructor(nsISupports *aOut return inst->QueryInterface(aIID, aResult); } -NS_DEFINE_NAMED_CID(NS_WINDOW_CID); -NS_DEFINE_NAMED_CID(NS_CHILD_CID); NS_DEFINE_NAMED_CID(NS_APPSHELL_CID); NS_DEFINE_NAMED_CID(NS_COLORPICKER_CID); NS_DEFINE_NAMED_CID(NS_FILEPICKER_CID); #ifdef MOZ_WIDGET_GTK NS_DEFINE_NAMED_CID(NS_APPLICATIONCHOOSER_CID); #endif +NS_DEFINE_NAMED_CID(NS_GTK_TASKBARPROGRESS_CID); NS_DEFINE_NAMED_CID(NS_SOUND_CID); NS_DEFINE_NAMED_CID(NS_TRANSFERABLE_CID); #ifdef MOZ_X11 @@ -230,14 +230,13 @@ NS_DEFINE_NAMED_CID(NS_GFXINFO_CID); static const mozilla::Module::CIDEntry kWidgetCIDs[] = { - { &kNS_WINDOW_CID, false, nullptr, nsWindowConstructor }, - { &kNS_CHILD_CID, false, nullptr, nsWindowConstructor }, { &kNS_APPSHELL_CID, false, nullptr, nsAppShellConstructor, Module::ALLOW_IN_GPU_PROCESS }, { &kNS_COLORPICKER_CID, false, nullptr, nsColorPickerConstructor, Module::MAIN_PROCESS_ONLY }, { &kNS_FILEPICKER_CID, false, nullptr, nsFilePickerConstructor, Module::MAIN_PROCESS_ONLY }, #ifdef MOZ_WIDGET_GTK { &kNS_APPLICATIONCHOOSER_CID, false, nullptr, nsApplicationChooserConstructor, Module::MAIN_PROCESS_ONLY }, #endif + { &kNS_GTK_TASKBARPROGRESS_CID, false, nullptr, TaskbarProgressConstructor}, { &kNS_SOUND_CID, false, nullptr, nsISoundConstructor, Module::MAIN_PROCESS_ONLY }, { &kNS_TRANSFERABLE_CID, false, nullptr, nsTransferableConstructor }, #ifdef MOZ_X11 @@ -266,14 +265,13 @@ static const mozilla::Module::CIDEntry k }; static const mozilla::Module::ContractIDEntry kWidgetContracts[] = { - { "@mozilla.org/widget/window/gtk;1", &kNS_WINDOW_CID }, - { "@mozilla.org/widgets/child_window/gtk;1", &kNS_CHILD_CID }, { "@mozilla.org/widget/appshell/gtk;1", &kNS_APPSHELL_CID, Module::ALLOW_IN_GPU_PROCESS }, { "@mozilla.org/colorpicker;1", &kNS_COLORPICKER_CID, Module::MAIN_PROCESS_ONLY }, { "@mozilla.org/filepicker;1", &kNS_FILEPICKER_CID, Module::MAIN_PROCESS_ONLY }, #ifdef MOZ_WIDGET_GTK { "@mozilla.org/applicationchooser;1", &kNS_APPLICATIONCHOOSER_CID, Module::MAIN_PROCESS_ONLY }, #endif + { "@mozilla.org/widget/taskbarprogress/gtk;1", &kNS_GTK_TASKBARPROGRESS_CID }, { "@mozilla.org/sound;1", &kNS_SOUND_CID, Module::MAIN_PROCESS_ONLY }, { "@mozilla.org/widget/transferable;1", &kNS_TRANSFERABLE_CID }, #ifdef MOZ_X11 diff -up thunderbird-60.3.0/widget/gtk/nsWindow.cpp.wayland thunderbird-60.3.0/widget/gtk/nsWindow.cpp --- thunderbird-60.3.0/widget/gtk/nsWindow.cpp.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsWindow.cpp 2018-11-20 12:04:43.741787337 +0100 @@ -18,6 +18,7 @@ #include "mozilla/TouchEvents.h" #include "mozilla/UniquePtrExtensions.h" #include "mozilla/WidgetUtils.h" +#include "mozilla/dom/WheelEventBinding.h" #include #include "GeckoProfiler.h" @@ -25,7 +26,7 @@ #include "prlink.h" #include "nsGTKToolkit.h" #include "nsIRollupListener.h" -#include "nsIDOMNode.h" +#include "nsINode.h" #include "nsWidgetsCID.h" #include "nsDragService.h" @@ -56,6 +57,7 @@ #if defined(MOZ_WAYLAND) #include +#include "nsView.h" #endif #include "nsGkAtoms.h" @@ -116,6 +118,7 @@ using namespace mozilla::widget; #include "mozilla/layers/CompositorThread.h" #ifdef MOZ_X11 +#include "GLContextGLX.h" // for GLContextGLX::FindVisual() #include "GtkCompositorWidget.h" #include "gfxXlibSurface.h" #include "WindowSurfaceX11Image.h" @@ -129,8 +132,6 @@ using namespace mozilla::widget; #include "nsShmImage.h" #include "gtkdrawing.h" -#include "nsIDOMWheelEvent.h" - #include "NativeKeyBindings.h" #include @@ -140,6 +141,7 @@ using namespace mozilla::gfx; using namespace mozilla::widget; using namespace mozilla::layers; using mozilla::gl::GLContext; +using mozilla::gl::GLContextGLX; // Don't put more than this many rects in the dirty region, just fluff // out to the bounding-box if there are more @@ -155,7 +157,8 @@ const gint kEvents = GDK_EXPOSURE_MASK | #endif GDK_SCROLL_MASK | GDK_POINTER_MOTION_MASK | - GDK_PROPERTY_CHANGE_MASK; + GDK_PROPERTY_CHANGE_MASK | + GDK_FOCUS_CHANGE_MASK; /* utility functions */ static bool is_mouse_in_window(GdkWindow* aWindow, @@ -210,7 +213,7 @@ static void hierarchy_changed_cb GtkWidget *previous_toplevel); static gboolean window_state_event_cb (GtkWidget *widget, GdkEventWindowState *event); -static void theme_changed_cb (GtkSettings *settings, +static void settings_changed_cb (GtkSettings *settings, GParamSpec *pspec, nsWindow *data); static void check_resize_cb (GtkContainer* container, @@ -481,6 +484,8 @@ nsWindow::nsWindow() mPendingConfigures = 0; mCSDSupportLevel = CSD_SUPPORT_NONE; mDrawInTitlebar = false; + + mHasAlphaVisual = false; } nsWindow::~nsWindow() @@ -630,7 +635,7 @@ EnsureInvisibleContainer() static void CheckDestroyInvisibleContainer() { - NS_PRECONDITION(gInvisibleContainer, "oh, no"); + MOZ_ASSERT(gInvisibleContainer, "oh, no"); if (!gdk_window_peek_children(gtk_widget_get_window(gInvisibleContainer))) { // No children, so not in use. @@ -731,7 +736,7 @@ nsWindow::Destroy() ClearCachedResources(); g_signal_handlers_disconnect_by_func(gtk_settings_get_default(), - FuncToGpointer(theme_changed_cb), + FuncToGpointer(settings_changed_cb), this); nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener(); @@ -838,11 +843,48 @@ nsWindow::GetDesktopToDeviceScale() return DesktopToLayoutDeviceScale(1.0); } +DesktopToLayoutDeviceScale +nsWindow::GetDesktopToDeviceScaleByScreen() +{ +#ifdef MOZ_WAYLAND + GdkDisplay* gdkDisplay = gdk_display_get_default(); + // In Wayland there's no way to get absolute position of the window and use it to + // determine the screen factor of the monitor on which the window is placed. + // The window is notified of the current scale factor but not at this point, + // so the GdkScaleFactor can return wrong value which can lead to wrong popup + // placement. + // We need to use parent's window scale factor for the new one. + if (GDK_IS_WAYLAND_DISPLAY(gdkDisplay)) { + nsView* view = nsView::GetViewFor(this); + if (view) { + nsView* parentView = view->GetParent(); + if (parentView) { + nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr); + if (parentWidget) { + return DesktopToLayoutDeviceScale(parentWidget->RoundsWidgetCoordinatesTo()); + } else { + NS_WARNING("Widget has no parent"); + } + } + } else { + NS_WARNING("Cannot find widget view"); + } + } +#endif + return nsBaseWidget::GetDesktopToDeviceScale(); +} + void nsWindow::SetParent(nsIWidget *aNewParent) { - if (mContainer || !mGdkWindow) { - NS_NOTREACHED("nsWindow::SetParent called illegally"); + if (!mGdkWindow) { + MOZ_ASSERT_UNREACHABLE("The native window has already been destroyed"); + return; + } + + if (mContainer) { + // FIXME bug 1469183 + NS_ERROR("nsWindow should not have a container here"); return; } @@ -886,7 +928,7 @@ nsWindow::WidgetTypeSupportsAcceleration void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) { - NS_PRECONDITION(aNewParent, ""); + MOZ_ASSERT(aNewParent, "null widget"); NS_ASSERTION(!mIsDestroyed, ""); NS_ASSERTION(!static_cast(aNewParent)->mIsDestroyed, ""); @@ -1517,7 +1559,7 @@ nsWindow::GetClientBounds() void nsWindow::UpdateClientOffset() { - AUTO_PROFILER_LABEL("nsWindow::UpdateClientOffset", GRAPHICS); + AUTO_PROFILER_LABEL("nsWindow::UpdateClientOffset", OTHER); if (!mIsTopLevel || !mShell || !mIsX11Display || gtk_window_get_window_type(GTK_WINDOW(mShell)) == GTK_WINDOW_POPUP) { @@ -1738,6 +1780,15 @@ nsWindow::GetNativeData(uint32_t aDataTy case NS_NATIVE_COMPOSITOR_DISPLAY: return gfxPlatformGtk::GetPlatform()->GetCompositorDisplay(); #endif // MOZ_X11 + case NS_NATIVE_EGL_WINDOW: { + if (mIsX11Display) + return mGdkWindow ? (void*)GDK_WINDOW_XID(mGdkWindow) : nullptr; +#ifdef MOZ_WAYLAND + if (mContainer) + return moz_container_get_wl_egl_window(mContainer); +#endif + return nullptr; + } default: NS_WARNING("nsWindow::GetNativeData called with bad value"); return nullptr; @@ -2057,6 +2108,12 @@ nsWindow::OnExposeEvent(cairo_t *cr) if (!mGdkWindow || mIsFullyObscured || !mHasMappedToplevel) return FALSE; +#ifdef MOZ_WAYLAND + // Window does not have visible wl_surface yet. + if (!mIsX11Display && !GetWaylandSurface()) + return FALSE; +#endif + nsIWidgetListener *listener = GetListener(); if (!listener) return FALSE; @@ -2112,10 +2169,7 @@ nsWindow::OnExposeEvent(cairo_t *cr) bool shaped = false; if (eTransparencyTransparent == GetTransparencyMode()) { - GdkScreen *screen = gdk_window_get_screen(mGdkWindow); - if (gdk_screen_is_composited(screen) && - gdk_window_get_visual(mGdkWindow) == - gdk_screen_get_rgba_visual(screen)) { + if (mHasAlphaVisual) { // Remove possible shape mask from when window manger was not // previously compositing. static_cast(GetTopLevelWidget())-> @@ -2216,12 +2270,9 @@ nsWindow::OnExposeEvent(cairo_t *cr) bool painted = false; { if (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC) { - GdkScreen *screen = gdk_window_get_screen(mGdkWindow); if (GetTransparencyMode() == eTransparencyTransparent && layerBuffering == BufferMode::BUFFER_NONE && - gdk_screen_is_composited(screen) && - gdk_window_get_visual(mGdkWindow) == - gdk_screen_get_rgba_visual(screen)) { + mHasAlphaVisual) { // If our draw target is unbuffered and we use an alpha channel, // clear the image beforehand to ensure we don't get artifacts from a // reused SHM image. See bug 1258086. @@ -2354,7 +2405,7 @@ nsWindow::OnConfigureEvent(GtkWidget *aW // // These windows should not be moved by the window manager, and so any // change in position is a result of our direction. mBounds has - // already been set in Move() or Resize(), and that is more + // already been set in std::move() or Resize(), and that is more // up-to-date than the position in the ConfigureNotify event if the // event is from an earlier window move. // @@ -2888,7 +2939,7 @@ nsWindow::OnContainerFocusOutEvent(GdkEv bool shouldRollup = !dragSession; if (!shouldRollup) { // we also roll up when a drag is from a different application - nsCOMPtr sourceNode; + nsCOMPtr sourceNode; dragSession->GetSourceNode(getter_AddRefs(sourceNode)); shouldRollup = (sourceNode == nullptr); } @@ -2915,8 +2966,8 @@ bool nsWindow::DispatchCommandEvent(nsAtom* aCommand) { nsEventStatus status; - WidgetCommandEvent event(true, nsGkAtoms::onAppCommand, aCommand, this); - DispatchEvent(&event, status); + WidgetCommandEvent appCommandEvent(true, aCommand, this); + DispatchEvent(&appCommandEvent, status); return TRUE; } @@ -2938,30 +2989,44 @@ IsCtrlAltTab(GdkEventKey *aEvent) } bool -nsWindow::DispatchKeyDownEvent(GdkEventKey *aEvent, bool *aCancelled) +nsWindow::DispatchKeyDownOrKeyUpEvent(GdkEventKey* aEvent, + bool aIsProcessedByIME, + bool* aIsCancelled) { - NS_PRECONDITION(aCancelled, "aCancelled must not be null"); + MOZ_ASSERT(aIsCancelled, "aIsCancelled must not be nullptr"); - *aCancelled = false; + *aIsCancelled = false; - if (IsCtrlAltTab(aEvent)) { + if (aEvent->type == GDK_KEY_PRESS && IsCtrlAltTab(aEvent)) { return false; } + EventMessage message = + aEvent->type == GDK_KEY_PRESS ? eKeyDown : eKeyUp; + WidgetKeyboardEvent keyEvent(true, message, this); + KeymapWrapper::InitKeyEvent(keyEvent, aEvent, aIsProcessedByIME); + return DispatchKeyDownOrKeyUpEvent(keyEvent, aIsCancelled); +} +bool +nsWindow::DispatchKeyDownOrKeyUpEvent(WidgetKeyboardEvent& aKeyboardEvent, + bool* aIsCancelled) +{ + MOZ_ASSERT(aIsCancelled, "aIsCancelled must not be nullptr"); + + *aIsCancelled = false; + RefPtr dispatcher = GetTextEventDispatcher(); nsresult rv = dispatcher->BeginNativeInputTransaction(); if (NS_WARN_IF(NS_FAILED(rv))) { return FALSE; } - WidgetKeyboardEvent keydownEvent(true, eKeyDown, this); - KeymapWrapper::InitKeyEvent(keydownEvent, aEvent); nsEventStatus status = nsEventStatus_eIgnore; bool dispatched = - dispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, - status, aEvent); - *aCancelled = (status == nsEventStatus_eConsumeNoDefault); - return dispatched ? TRUE : FALSE; + dispatcher->DispatchKeyboardEvent(aKeyboardEvent.mMessage, + aKeyboardEvent, status, nullptr); + *aIsCancelled = (status == nsEventStatus_eConsumeNoDefault); + return dispatched; } WidgetEventTime @@ -3046,7 +3111,7 @@ nsWindow::OnKeyPressEvent(GdkEventKey *a // KEYDOWN -> KEYPRESS -> KEYUP -> KEYDOWN -> KEYPRESS -> KEYUP... bool isKeyDownCancelled = false; - if (DispatchKeyDownEvent(aEvent, &isKeyDownCancelled) && + if (DispatchKeyDownOrKeyUpEvent(aEvent, false, &isKeyDownCancelled) && (MOZ_UNLIKELY(mIsDestroyed) || isKeyDownCancelled)) { return TRUE; } @@ -3095,7 +3160,7 @@ nsWindow::OnKeyPressEvent(GdkEventKey *a } WidgetKeyboardEvent keypressEvent(true, eKeyPress, this); - KeymapWrapper::InitKeyEvent(keypressEvent, aEvent); + KeymapWrapper::InitKeyEvent(keypressEvent, aEvent, false); // before we dispatch a key, check if it's the context menu key. // If so, send a context menu key event instead. @@ -3179,7 +3244,7 @@ nsWindow::MaybeDispatchContextMenuEvent( } gboolean -nsWindow::OnKeyReleaseEvent(GdkEventKey *aEvent) +nsWindow::OnKeyReleaseEvent(GdkEventKey* aEvent) { LOGFOCUS(("OnKeyReleaseEvent [%p]\n", (void *)this)); @@ -3187,17 +3252,11 @@ nsWindow::OnKeyReleaseEvent(GdkEventKey return TRUE; } - RefPtr dispatcher = GetTextEventDispatcher(); - nsresult rv = dispatcher->BeginNativeInputTransaction(); - if (NS_WARN_IF(NS_FAILED(rv))) { - return false; + bool isCancelled = false; + if (NS_WARN_IF(!DispatchKeyDownOrKeyUpEvent(aEvent, false, &isCancelled))) { + return FALSE; } - WidgetKeyboardEvent keyupEvent(true, eKeyUp, this); - KeymapWrapper::InitKeyEvent(keyupEvent, aEvent); - nsEventStatus status = nsEventStatus_eIgnore; - dispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status, aEvent); - return TRUE; } @@ -3214,7 +3273,7 @@ nsWindow::OnScrollEvent(GdkEventScroll * return; #endif WidgetWheelEvent wheelEvent(true, eWheel, this); - wheelEvent.mDeltaMode = nsIDOMWheelEvent::DOM_DELTA_LINE; + wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_LINE; switch (aEvent->direction) { #if GTK_CHECK_VERSION(3,4,0) case GDK_SCROLL_SMOOTH: @@ -3318,6 +3377,33 @@ nsWindow::OnWindowStateEvent(GtkWidget * } // else the widget is a shell widget. + // The block below is a bit evil. + // + // When a window is resized before it is shown, gtk_window_resize() delays + // resizes until the window is shown. If gtk_window_state_event() sees a + // GDK_WINDOW_STATE_MAXIMIZED change [1] before the window is shown, then + // gtk_window_compute_configure_request_size() ignores the values from the + // resize [2]. See bug 1449166 for an example of how this could happen. + // + // [1] https://gitlab.gnome.org/GNOME/gtk/blob/3.22.30/gtk/gtkwindow.c#L7967 + // [2] https://gitlab.gnome.org/GNOME/gtk/blob/3.22.30/gtk/gtkwindow.c#L9377 + // + // In order to provide a sensible size for the window when the user exits + // maximized state, we hide the GDK_WINDOW_STATE_MAXIMIZED change from + // gtk_window_state_event() so as to trick GTK into using the values from + // gtk_window_resize() in its configure request. + // + // We instead notify gtk_window_state_event() of the maximized state change + // once the window is shown. + if (!mIsShown) { + aEvent->changed_mask = static_cast + (aEvent->changed_mask & ~GDK_WINDOW_STATE_MAXIMIZED); + } else if (aEvent->changed_mask & GDK_WINDOW_STATE_WITHDRAWN && + aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) { + aEvent->changed_mask = static_cast + (aEvent->changed_mask | GDK_WINDOW_STATE_MAXIMIZED); + } + // We don't care about anything but changes in the maximized/icon/fullscreen // states if ((aEvent->changed_mask @@ -3404,6 +3490,7 @@ nsWindow::OnDPIChanged() // Update menu's font size etc presShell->ThemeChanged(); } + mWidgetListener->UIResolutionChanged(); } } @@ -3611,6 +3698,8 @@ nsWindow::Create(nsIWidget* aParent, nsWindow *parentnsWindow = nullptr; GtkWidget *eventWidget = nullptr; bool drawToContainer = false; + bool needsAlphaVisual = (mWindowType == eWindowType_popup && + aInitData->mSupportTranslucency); if (aParent) { parentnsWindow = static_cast(aParent); @@ -3661,23 +3750,47 @@ nsWindow::Create(nsIWidget* aParent, } mShell = gtk_window_new(type); - bool useAlphaVisual = (mWindowType == eWindowType_popup && - aInitData->mSupportTranslucency); - - // mozilla.widget.use-argb-visuals is a hidden pref defaulting to false - // to allow experimentation - if (Preferences::GetBool("mozilla.widget.use-argb-visuals", false)) - useAlphaVisual = true; - - // We need to select an ARGB visual here instead of in - // SetTransparencyMode() because it has to be done before the - // widget is realized. An ARGB visual is only useful if we - // are on a compositing window manager. - if (useAlphaVisual) { - GdkScreen *screen = gtk_widget_get_screen(mShell); - if (gdk_screen_is_composited(screen)) { - GdkVisual *visual = gdk_screen_get_rgba_visual(screen); - gtk_widget_set_visual(mShell, visual); +#ifdef MOZ_X11 + // Ensure gfxPlatform is initialized, since that is what initializes + // gfxVars, used below. + Unused << gfxPlatform::GetPlatform(); + + bool useWebRender = gfx::gfxVars::UseWebRender() && + AllowWebRenderForThisWindow(); + + // If using WebRender on X11, we need to select a visual with a depth buffer, + // as well as an alpha channel if transparency is requested. This must be done + // before the widget is realized. + if (mIsX11Display && useWebRender) { + auto display = + GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(mShell)); + auto screen = gtk_widget_get_screen(mShell); + int screenNumber = GDK_SCREEN_XNUMBER(screen); + int visualId = 0; + if (GLContextGLX::FindVisual(display, screenNumber, + useWebRender, needsAlphaVisual, + &visualId)) { + // If we're using CSD, rendering will go through mContainer, but + // it will inherit this visual as it is a child of mShell. + gtk_widget_set_visual(mShell, + gdk_x11_screen_lookup_visual(screen, + visualId)); + mHasAlphaVisual = needsAlphaVisual; + } else { + NS_WARNING("We're missing X11 Visual for WebRender!"); + } + } else +#endif // MOZ_X11 + { + if (needsAlphaVisual) { + GdkScreen *screen = gtk_widget_get_screen(mShell); + if (gdk_screen_is_composited(screen)) { + GdkVisual *visual = gdk_screen_get_rgba_visual(screen); + if (visual) { + gtk_widget_set_visual(mShell, visual); + mHasAlphaVisual = true; + } + } } } @@ -3784,7 +3897,7 @@ nsWindow::Create(nsIWidget* aParent, // it explicitly now. gtk_widget_realize(mShell); - /* There are two cases here: + /* There are several cases here: * * 1) We're running on Gtk+ without client side decorations. * Content is rendered to mShell window and we listen @@ -3859,17 +3972,7 @@ nsWindow::Create(nsIWidget* aParent, // If the window were to get unredirected, there could be visible // tearing because Gecko does not align its framebuffer updates with // vblank. - if (mIsX11Display) { - gulong value = 2; // Opt out of unredirection - GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL); - gdk_property_change(gtk_widget_get_window(mShell), - gdk_atom_intern("_NET_WM_BYPASS_COMPOSITOR", FALSE), - cardinal_atom, - 32, // format - GDK_PROP_MODE_REPLACE, - (guchar*)&value, - 1); - } + SetCompositorHint(GTK_WIDGET_COMPOSIDED_ENABLED); #endif } break; @@ -3949,10 +4052,13 @@ nsWindow::Create(nsIWidget* aParent, GtkSettings* default_settings = gtk_settings_get_default(); g_signal_connect_after(default_settings, "notify::gtk-theme-name", - G_CALLBACK(theme_changed_cb), this); + G_CALLBACK(settings_changed_cb), this); g_signal_connect_after(default_settings, "notify::gtk-font-name", - G_CALLBACK(theme_changed_cb), this); + G_CALLBACK(settings_changed_cb), this); + g_signal_connect_after(default_settings, + "notify::gtk-enable-animations", + G_CALLBACK(settings_changed_cb), this); } if (mContainer) { @@ -4070,8 +4176,10 @@ nsWindow::Create(nsIWidget* aParent, GdkVisual* gdkVisual = gdk_window_get_visual(mGdkWindow); mXVisual = gdk_x11_visual_get_xvisual(gdkVisual); mXDepth = gdk_visual_get_depth(gdkVisual); + bool shaped = needsAlphaVisual && !mHasAlphaVisual; - mSurfaceProvider.Initialize(mXDisplay, mXWindow, mXVisual, mXDepth); + mSurfaceProvider.Initialize(mXDisplay, mXWindow, mXVisual, mXDepth, + shaped); } #ifdef MOZ_WAYLAND else if (!mIsX11Display) { @@ -4083,60 +4191,70 @@ nsWindow::Create(nsIWidget* aParent, } void -nsWindow::SetWindowClass(const nsAString &xulWinType) +nsWindow::RefreshWindowClass(void) { - if (!mShell) - return; + if (mGtkWindowTypeName.IsEmpty() || mGtkWindowRoleName.IsEmpty()) + return; - const char *res_class = gdk_get_program_class(); - if (!res_class) - return; + GdkWindow* gdkWindow = gtk_widget_get_window(mShell); + gdk_window_set_role(gdkWindow, mGtkWindowRoleName.get()); - char *res_name = ToNewCString(xulWinType); - if (!res_name) - return; +#ifdef MOZ_X11 + if (mIsX11Display) { + XClassHint *class_hint = XAllocClassHint(); + if (!class_hint) { + return; + } + const char *res_class = gdk_get_program_class(); + if (!res_class) + return; + + class_hint->res_name = const_cast(mGtkWindowTypeName.get()); + class_hint->res_class = const_cast(res_class); + + // Can't use gtk_window_set_wmclass() for this; it prints + // a warning & refuses to make the change. + GdkDisplay *display = gdk_display_get_default(); + XSetClassHint(GDK_DISPLAY_XDISPLAY(display), + gdk_x11_window_get_xid(gdkWindow), + class_hint); + XFree(class_hint); + } +#endif /* MOZ_X11 */ +} - const char *role = nullptr; +void +nsWindow::SetWindowClass(const nsAString &xulWinType) +{ + if (!mShell) + return; - // Parse res_name into a name and role. Characters other than - // [A-Za-z0-9_-] are converted to '_'. Anything after the first - // colon is assigned to role; if there's no colon, assign the - // whole thing to both role and res_name. - for (char *c = res_name; *c; c++) { - if (':' == *c) { - *c = 0; - role = c + 1; - } - else if (!isascii(*c) || (!isalnum(*c) && ('_' != *c) && ('-' != *c))) - *c = '_'; - } - res_name[0] = toupper(res_name[0]); - if (!role) role = res_name; + char *res_name = ToNewCString(xulWinType); + if (!res_name) + return; - GdkWindow* gdkWindow = gtk_widget_get_window(mShell); - gdk_window_set_role(gdkWindow, role); + const char *role = nullptr; -#ifdef MOZ_X11 - if (mIsX11Display) { - XClassHint *class_hint = XAllocClassHint(); - if (!class_hint) { - free(res_name); - return; + // Parse res_name into a name and role. Characters other than + // [A-Za-z0-9_-] are converted to '_'. Anything after the first + // colon is assigned to role; if there's no colon, assign the + // whole thing to both role and res_name. + for (char *c = res_name; *c; c++) { + if (':' == *c) { + *c = 0; + role = c + 1; } - class_hint->res_name = res_name; - class_hint->res_class = const_cast(res_class); + else if (!isascii(*c) || (!isalnum(*c) && ('_' != *c) && ('-' != *c))) + *c = '_'; + } + res_name[0] = toupper(res_name[0]); + if (!role) role = res_name; - // Can't use gtk_window_set_wmclass() for this; it prints - // a warning & refuses to make the change. - GdkDisplay *display = gdk_display_get_default(); - XSetClassHint(GDK_DISPLAY_XDISPLAY(display), - gdk_x11_window_get_xid(gdkWindow), - class_hint); - XFree(class_hint); - } -#endif /* MOZ_X11 */ + mGtkWindowTypeName = res_name; + mGtkWindowRoleName = role; + free(res_name); - free(res_name); + RefreshWindowClass(); } void @@ -4162,6 +4280,8 @@ nsWindow::NativeResize() size.width, size.height)); if (mIsTopLevel) { + MOZ_ASSERT(size.width > 0 && size.height > 0, + "Can't resize window smaller than 1x1."); gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height); } else if (mContainer) { @@ -4207,6 +4327,8 @@ nsWindow::NativeMoveResize() NativeShow(false); } NativeMove(); + + return; } GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size()); @@ -4219,6 +4341,8 @@ nsWindow::NativeMoveResize() // x and y give the position of the window manager frame top-left. gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y); // This sets the client window size. + MOZ_ASSERT(size.width > 0 && size.height > 0, + "Can't resize window smaller than 1x1."); gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height); } else if (mContainer) { @@ -4271,6 +4395,16 @@ nsWindow::NativeShow(bool aAction) } } else { +#ifdef MOZ_WAYLAND + if (mContainer && moz_container_has_wl_egl_window(mContainer)) { + // Because wl_egl_window is destroyed on moz_container_unmap(), + // the current compositor cannot use it anymore. To avoid crash, + // destroy the compositor & recreate a new compositor on next + // expose event. + DestroyLayerManager(); + } +#endif + if (mIsTopLevel) { // Workaround window freezes on GTK versions before 3.21.2 by // ensuring that configure events get dispatched to windows before @@ -4946,6 +5080,8 @@ FullscreenTransitionWindow::FullscreenTr gdk_screen_get_monitor_geometry(screen, monitorNum, &monitorRect); gtk_window_set_screen(gtkWin, screen); gtk_window_move(gtkWin, monitorRect.x, monitorRect.y); + MOZ_ASSERT(monitorRect.width > 0 && monitorRect.height > 0, + "Can't resize window smaller than 1x1."); gtk_window_resize(gtkWin, monitorRect.width, monitorRect.height); GdkColor bgColor; @@ -5951,7 +6087,7 @@ window_state_event_cb (GtkWidget *widget } static void -theme_changed_cb (GtkSettings *settings, GParamSpec *pspec, nsWindow *data) +settings_changed_cb (GtkSettings *settings, GParamSpec *pspec, nsWindow *data) { RefPtr window = data; window->ThemeChanged(); @@ -6032,13 +6168,13 @@ nsWindow::InitDragEvent(WidgetDragEvent KeymapWrapper::InitInputEvent(aEvent, modifierState); } -static gboolean -drag_motion_event_cb(GtkWidget *aWidget, - GdkDragContext *aDragContext, - gint aX, - gint aY, - guint aTime, - gpointer aData) +gboolean +WindowDragMotionHandler(GtkWidget *aWidget, + GdkDragContext *aDragContext, + nsWaylandDragContext *aWaylandDragContext, + gint aX, + gint aY, + guint aTime) { RefPtr window = get_window_for_gtk_widget(aWidget); if (!window) @@ -6063,15 +6199,24 @@ drag_motion_event_cb(GtkWidget *aWidget, RefPtr dragService = nsDragService::GetInstance(); return dragService-> - ScheduleMotionEvent(innerMostWindow, aDragContext, + ScheduleMotionEvent(innerMostWindow, aDragContext, aWaylandDragContext, point, aTime); } -static void -drag_leave_event_cb(GtkWidget *aWidget, - GdkDragContext *aDragContext, - guint aTime, - gpointer aData) +static gboolean +drag_motion_event_cb(GtkWidget *aWidget, + GdkDragContext *aDragContext, + gint aX, + gint aY, + guint aTime, + gpointer aData) +{ + return WindowDragMotionHandler(aWidget, aDragContext, nullptr, + aX, aY, aTime); +} + +void +WindowDragLeaveHandler(GtkWidget *aWidget) { RefPtr window = get_window_for_gtk_widget(aWidget); if (!window) @@ -6104,14 +6249,22 @@ drag_leave_event_cb(GtkWidget *aWidget, dragService->ScheduleLeaveEvent(); } +static void +drag_leave_event_cb(GtkWidget *aWidget, + GdkDragContext *aDragContext, + guint aTime, + gpointer aData) +{ + WindowDragLeaveHandler(aWidget); +} -static gboolean -drag_drop_event_cb(GtkWidget *aWidget, - GdkDragContext *aDragContext, - gint aX, - gint aY, - guint aTime, - gpointer aData) +gboolean +WindowDragDropHandler(GtkWidget *aWidget, + GdkDragContext *aDragContext, + nsWaylandDragContext *aWaylandDragContext, + gint aX, + gint aY, + guint aTime) { RefPtr window = get_window_for_gtk_widget(aWidget); if (!window) @@ -6136,10 +6289,21 @@ drag_drop_event_cb(GtkWidget *aWidget, RefPtr dragService = nsDragService::GetInstance(); return dragService-> - ScheduleDropEvent(innerMostWindow, aDragContext, + ScheduleDropEvent(innerMostWindow, aDragContext, aWaylandDragContext, point, aTime); } +static gboolean +drag_drop_event_cb(GtkWidget *aWidget, + GdkDragContext *aDragContext, + gint aX, + gint aY, + guint aTime, + gpointer aData) +{ + return WindowDragDropHandler(aWidget, aDragContext, nullptr, aX, aY, aTime); +} + static void drag_data_received_event_cb(GtkWidget *aWidget, GdkDragContext *aDragContext, @@ -6554,12 +6718,6 @@ nsWindow::GetLayerManager(PLayerTransact return mLayerManager; } - if (!mLayerManager && !IsComposited() && - eTransparencyTransparent == GetTransparencyMode()) - { - mLayerManager = CreateBasicLayerManager(); - } - return nsBaseWidget::GetLayerManager(aShadowManager, aBackendHint, aPersistence); } @@ -6601,6 +6759,13 @@ nsWindow::ClearCachedResources() void nsWindow::UpdateClientOffsetForCSDWindow() { + // We update window offset on X11 as the window position is calculated + // relatively to mShell. We don't do that on Wayland as our wl_subsurface + // is attached to mContainer and mShell is ignored. + if (!mIsX11Display) { + return; + } + // _NET_FRAME_EXTENTS is not set on client decorated windows, // so we need to read offset between mContainer and toplevel mShell // window. @@ -6692,6 +6857,15 @@ nsWindow::SetDrawsInTitlebar(bool aState mNeedsShow = true; NativeResize(); + // Label mShell toplevel window so property_notify_event_cb callback + // can find its way home. + g_object_set_data(G_OBJECT(gtk_widget_get_window(mShell)), + "nsWindow", this); +#ifdef MOZ_X11 + SetCompositorHint(GTK_WIDGET_COMPOSIDED_ENABLED); +#endif + RefreshWindowClass(); + // When we use system titlebar setup managed by Gtk+ we also get // _NET_FRAME_EXTENTS property for our toplevel window so we can't // update the client offset it here. @@ -6998,6 +7172,8 @@ nsWindow::GetSystemCSDSupportLevel() { // KDE Plasma } else if (strstr(currentDesktop, "KDE") != nullptr) { sCSDSupportLevel = CSD_SUPPORT_CLIENT; + } else if (strstr(currentDesktop, "Enlightenment") != nullptr) { + sCSDSupportLevel = CSD_SUPPORT_CLIENT; } else if (strstr(currentDesktop, "LXDE") != nullptr) { sCSDSupportLevel = CSD_SUPPORT_CLIENT; } else if (strstr(currentDesktop, "openbox") != nullptr) { @@ -7014,6 +7190,8 @@ nsWindow::GetSystemCSDSupportLevel() { sCSDSupportLevel = CSD_SUPPORT_SYSTEM; } else if (strstr(currentDesktop, "LXQt") != nullptr) { sCSDSupportLevel = CSD_SUPPORT_SYSTEM; + } else if (strstr(currentDesktop, "Deepin") != nullptr) { + sCSDSupportLevel = CSD_SUPPORT_SYSTEM; } else { // Release or beta builds are not supposed to be broken // so disable titlebar rendering on untested/unknown systems. @@ -7066,26 +7244,19 @@ nsWindow::RoundsWidgetCoordinatesTo() void nsWindow::GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData) { + // Make sure the window XID is propagated to X server, we can fail otherwise + // in GPU process (Bug 1401634). + if (mXDisplay && mXWindow != X11None) { + XFlush(mXDisplay); + } + *aInitData = mozilla::widget::GtkCompositorWidgetInitData( (mXWindow != X11None) ? mXWindow : (uintptr_t)nullptr, mXDisplay ? nsCString(XDisplayString(mXDisplay)) : nsCString(), + mIsTransparent && !mHasAlphaVisual, GetClientSize()); } -bool -nsWindow::IsComposited() const -{ - if (!mGdkWindow) { - NS_WARNING("nsWindow::HasARGBVisual called before realization!"); - return false; - } - - GdkScreen* gdkScreen = gdk_screen_get_default(); - return gdk_screen_is_composited(gdkScreen) && - (gdk_window_get_visual(mGdkWindow) - == gdk_screen_get_rgba_visual(gdkScreen)); -} - #ifdef MOZ_WAYLAND wl_display* nsWindow::GetWaylandDisplay() @@ -7110,3 +7281,120 @@ nsWindow::GetWaylandSurface() return nullptr; } #endif + +#ifdef MOZ_X11 +/* XApp progress support currently works by setting a property + * on a window with this Atom name. A supporting window manager + * will notice this and pass it along to whatever handling has + * been implemented on that end (e.g. passing it on to a taskbar + * widget.) There is no issue if WM support is lacking, this is + * simply ignored in that case. + * + * See https://github.com/linuxmint/xapps/blob/master/libxapp/xapp-gtk-window.c + * for further details. + */ + +#define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS" + +static void +set_window_hint_cardinal (Window xid, + const gchar *atom_name, + gulong cardinal) +{ + GdkDisplay *display; + + display = gdk_display_get_default (); + + if (cardinal > 0) + { + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), + xid, + gdk_x11_get_xatom_by_name_for_display (display, atom_name), + XA_CARDINAL, 32, + PropModeReplace, + (guchar *) &cardinal, 1); + } + else + { + XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), + xid, + gdk_x11_get_xatom_by_name_for_display (display, atom_name)); + } +} +#endif // MOZ_X11 + +void +nsWindow::SetProgress(unsigned long progressPercent) +{ +#ifdef MOZ_X11 + + if (!mIsX11Display) { + return; + } + + if (!mShell) { + return; + } + + progressPercent = MIN(progressPercent, 100); + + set_window_hint_cardinal(GDK_WINDOW_XID(gtk_widget_get_window(mShell)), + PROGRESS_HINT, + progressPercent); +#endif // MOZ_X11 +} + +#ifdef MOZ_X11 +void +nsWindow::SetCompositorHint(WindowComposeRequest aState) +{ + if (mIsX11Display) { + gulong value = aState; + GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL); + gdk_property_change(gtk_widget_get_window(mShell), + gdk_atom_intern("_NET_WM_BYPASS_COMPOSITOR", FALSE), + cardinal_atom, + 32, // format + GDK_PROP_MODE_REPLACE, + (guchar*)&value, + 1); + } +} +#endif + +nsresult +nsWindow::SetSystemFont(const nsCString& aFontName) +{ + GtkSettings* settings = gtk_settings_get_default(); + g_object_set(settings, "gtk-font-name", aFontName.get(), nullptr); + return NS_OK; +} + +nsresult +nsWindow::GetSystemFont(nsCString& aFontName) +{ + GtkSettings* settings = gtk_settings_get_default(); + gchar* fontName = nullptr; + g_object_get(settings, + "gtk-font-name", &fontName, + nullptr); + if (fontName) { + aFontName.Assign(fontName); + g_free(fontName); + } + return NS_OK; +} + +already_AddRefed +nsIWidget::CreateTopLevelWindow() +{ + nsCOMPtr window = new nsWindow(); + return window.forget(); +} + +already_AddRefed +nsIWidget::CreateChildWindow() +{ + nsCOMPtr window = new nsWindow(); + return window.forget(); +} diff -up thunderbird-60.3.0/widget/gtk/nsWindow.h.wayland thunderbird-60.3.0/widget/gtk/nsWindow.h --- thunderbird-60.3.0/widget/gtk/nsWindow.h.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/nsWindow.h 2018-11-20 12:04:43.741787337 +0100 @@ -66,6 +66,21 @@ extern mozilla::LazyLogModule gWidgetDra #endif /* MOZ_LOGGING */ +#ifdef MOZ_WAYLAND +class nsWaylandDragContext; + +gboolean +WindowDragMotionHandler(GtkWidget *aWidget, GdkDragContext *aDragContext, + nsWaylandDragContext *aWaylandDragContext, + gint aX, gint aY, guint aTime); +gboolean +WindowDragDropHandler(GtkWidget *aWidget, GdkDragContext *aDragContext, + nsWaylandDragContext *aWaylandDragContext, gint aX, gint aY, + guint aTime); +void +WindowDragLeaveHandler(GtkWidget *aWidget); +#endif + class gfxPattern; namespace mozilla { @@ -78,6 +93,7 @@ class nsWindow final : public nsBaseWidg public: typedef mozilla::gfx::DrawTarget DrawTarget; typedef mozilla::WidgetEventTime WidgetEventTime; + typedef mozilla::WidgetKeyboardEvent WidgetKeyboardEvent; typedef mozilla::widget::PlatformCompositorWidgetDelegate PlatformCompositorWidgetDelegate; nsWindow(); @@ -108,6 +124,7 @@ public: virtual float GetDPI() override; virtual double GetDefaultScaleInternal() override; mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() override; + mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScaleByScreen() override; virtual void SetParent(nsIWidget* aNewParent) override; virtual void SetModal(bool aModal) override; virtual bool IsVisible() const override; @@ -115,8 +132,7 @@ public: int32_t *aX, int32_t *aY) override; virtual void SetSizeConstraints(const SizeConstraints& aConstraints) override; - virtual void Move(double aX, - double aY) override; + virtual void Move(double aX, double aY) override; virtual void Show (bool aState) override; virtual void Resize (double aWidth, double aHeight, @@ -228,6 +244,8 @@ public: virtual void EndRemoteDrawingInRegion(mozilla::gfx::DrawTarget* aDrawTarget, LayoutDeviceIntRegion& aInvalidRegion) override; + void SetProgress(unsigned long progressPercent); + private: void UpdateAlpha(mozilla::gfx::SourceSurface* aSourceSurface, nsIntRect aBoundsRect); @@ -278,10 +296,32 @@ public: guint aTime); static void UpdateDragStatus (GdkDragContext *aDragContext, nsIDragService *aDragService); - // If this dispatched the keydown event actually, this returns TRUE, - // otherwise, FALSE. - bool DispatchKeyDownEvent(GdkEventKey *aEvent, - bool *aIsCancelled); + /** + * DispatchKeyDownOrKeyUpEvent() dispatches eKeyDown or eKeyUp event. + * + * @param aEvent A native GDK_KEY_PRESS or GDK_KEY_RELEASE + * event. + * @param aProcessedByIME true if the event is handled by IME. + * @param aIsCancelled [Out] true if the default is prevented. + * @return true if eKeyDown event is actually dispatched. + * Otherwise, false. + */ + bool DispatchKeyDownOrKeyUpEvent(GdkEventKey* aEvent, + bool aProcessedByIME, + bool* aIsCancelled); + + /** + * DispatchKeyDownOrKeyUpEvent() dispatches eKeyDown or eKeyUp event. + * + * @param aEvent An eKeyDown or eKeyUp event. This will be + * dispatched as is. + * @param aIsCancelled [Out] true if the default is prevented. + * @return true if eKeyDown event is actually dispatched. + * Otherwise, false. + */ + bool DispatchKeyDownOrKeyUpEvent(WidgetKeyboardEvent& aEvent, + bool* aIsCancelled); + WidgetEventTime GetWidgetEventTime(guint32 aEventTime); mozilla::TimeStamp GetEventTimeStamp(guint32 aEventTime); mozilla::CurrentX11TimeGetter* GetCurrentTimeGetter(); @@ -375,6 +415,9 @@ public: virtual bool WidgetTypeSupportsAcceleration() override; + nsresult SetSystemFont(const nsCString& aFontName) override; + nsresult GetSystemFont(nsCString& aFontName) override; + typedef enum { CSD_SUPPORT_SYSTEM, // CSD including shadows CSD_SUPPORT_CLIENT, // CSD without shadows CSD_SUPPORT_NONE, // WM does not support CSD at all @@ -452,13 +495,24 @@ private: gint* aRootX, gint* aRootY); void ClearCachedResources(); nsIWidgetListener* GetListener(); - bool IsComposited() const; void UpdateClientOffsetForCSDWindow(); nsWindow* GetTransientForWindowIfPopup(); bool IsHandlingTouchSequence(GdkEventSequence* aSequence); +#ifdef MOZ_X11 + typedef enum { GTK_WIDGET_COMPOSIDED_DEFAULT = 0, + GTK_WIDGET_COMPOSIDED_DISABLED = 1, + GTK_WIDGET_COMPOSIDED_ENABLED = 2 + } WindowComposeRequest; + + void SetCompositorHint(WindowComposeRequest aState); +#endif + nsCString mGtkWindowTypeName; + nsCString mGtkWindowRoleName; + void RefreshWindowClass(); + GtkWidget *mShell; MozContainer *mContainer; GdkWindow *mGdkWindow; @@ -558,6 +612,9 @@ private: // full translucency at this time; each pixel is either fully opaque // or fully transparent. gchar* mTransparencyBitmap; + // True when we're on compositing window manager and this + // window is using visual with alpha channel. + bool mHasAlphaVisual; // all of our DND stuff void InitDragEvent(mozilla::WidgetDragEvent& aEvent); diff -up thunderbird-60.3.0/widget/gtk/PlatformWidgetTypes.ipdlh.wayland thunderbird-60.3.0/widget/gtk/PlatformWidgetTypes.ipdlh --- thunderbird-60.3.0/widget/gtk/PlatformWidgetTypes.ipdlh.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/PlatformWidgetTypes.ipdlh 2018-11-20 12:04:43.741787337 +0100 @@ -15,6 +15,7 @@ struct GtkCompositorWidgetInitData { uintptr_t XWindow; nsCString XDisplayString; + bool Shaped; LayoutDeviceIntSize InitialClientSize; }; diff -up thunderbird-60.3.0/widget/gtk/ScreenHelperGTK.cpp.wayland thunderbird-60.3.0/widget/gtk/ScreenHelperGTK.cpp --- thunderbird-60.3.0/widget/gtk/ScreenHelperGTK.cpp.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/ScreenHelperGTK.cpp 2018-11-20 12:04:43.741787337 +0100 @@ -197,7 +197,7 @@ ScreenHelperGTK::RefreshScreens() } ScreenManager& screenManager = ScreenManager::GetSingleton(); - screenManager.Refresh(Move(screenList)); + screenManager.Refresh(std::move(screenList)); } } // namespace widget diff -up thunderbird-60.3.0/widget/gtk/WindowSurfaceProvider.cpp.wayland thunderbird-60.3.0/widget/gtk/WindowSurfaceProvider.cpp --- thunderbird-60.3.0/widget/gtk/WindowSurfaceProvider.cpp.wayland 2018-10-30 12:45:34.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/WindowSurfaceProvider.cpp 2018-11-20 12:04:43.741787337 +0100 @@ -31,6 +31,7 @@ WindowSurfaceProvider::WindowSurfaceProv #ifdef MOZ_WAYLAND , mWidget(nullptr) #endif + , mIsShaped(false) { } @@ -38,7 +39,8 @@ void WindowSurfaceProvider::Initialize( Display* aDisplay, Window aWindow, Visual* aVisual, - int aDepth) + int aDepth, + bool aIsShaped) { // We should not be initialized MOZ_ASSERT(!mXDisplay); @@ -50,6 +52,7 @@ void WindowSurfaceProvider::Initialize( mXWindow = aWindow; mXVisual = aVisual; mXDepth = aDepth; + mIsShaped = aIsShaped; mIsX11Display = true; } @@ -88,21 +91,22 @@ WindowSurfaceProvider::CreateWindowSurfa // 3. XPutImage #ifdef MOZ_WIDGET_GTK - if (gfxVars::UseXRender()) { + if (!mIsShaped && gfxVars::UseXRender()) { LOGDRAW(("Drawing to nsWindow %p using XRender\n", (void*)this)); return MakeUnique(mXDisplay, mXWindow, mXVisual, mXDepth); } #endif // MOZ_WIDGET_GTK #ifdef MOZ_HAVE_SHMIMAGE - if (nsShmImage::UseShm()) { + if (!mIsShaped && nsShmImage::UseShm()) { LOGDRAW(("Drawing to nsWindow %p using MIT-SHM\n", (void*)this)); return MakeUnique(mXDisplay, mXWindow, mXVisual, mXDepth); } #endif // MOZ_HAVE_SHMIMAGE LOGDRAW(("Drawing to nsWindow %p using XPutImage\n", (void*)this)); - return MakeUnique(mXDisplay, mXWindow, mXVisual, mXDepth); + return MakeUnique(mXDisplay, mXWindow, mXVisual, + mXDepth, mIsShaped); } already_AddRefed @@ -125,7 +129,7 @@ WindowSurfaceProvider::StartRemoteDrawin // We can't use WindowSurfaceX11Image fallback on Wayland but // Lock() call on WindowSurfaceWayland should never fail. gfxWarningOnce() << "Failed to lock WindowSurface, falling back to XPutImage backend."; - mWindowSurface = MakeUnique(mXDisplay, mXWindow, mXVisual, mXDepth); + mWindowSurface = MakeUnique(mXDisplay, mXWindow, mXVisual, mXDepth, mIsShaped); dt = mWindowSurface->Lock(aInvalidRegion); } return dt.forget(); diff -up thunderbird-60.3.0/widget/gtk/WindowSurfaceProvider.h.wayland thunderbird-60.3.0/widget/gtk/WindowSurfaceProvider.h --- thunderbird-60.3.0/widget/gtk/WindowSurfaceProvider.h.wayland 2018-10-30 12:45:34.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/WindowSurfaceProvider.h 2018-11-20 12:04:43.742787334 +0100 @@ -17,6 +17,7 @@ #include #endif #include // for Window, Display, Visual, etc. +#include "X11UndefineNone.h" class nsWindow; @@ -43,7 +44,8 @@ public: Display* aDisplay, Window aWindow, Visual* aVisual, - int aDepth); + int aDepth, + bool aIsShaped); #ifdef MOZ_WAYLAND void Initialize(nsWindow *aWidget); @@ -75,6 +77,7 @@ private: #ifdef MOZ_WAYLAND nsWindow* mWidget; #endif + bool mIsShaped; }; } // namespace widget diff -up thunderbird-60.3.0/widget/gtk/WindowSurfaceWayland.cpp.wayland thunderbird-60.3.0/widget/gtk/WindowSurfaceWayland.cpp --- thunderbird-60.3.0/widget/gtk/WindowSurfaceWayland.cpp.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/WindowSurfaceWayland.cpp 2018-11-20 12:04:43.742787334 +0100 @@ -151,8 +151,9 @@ static nsWaylandDisplay* WaylandDisplayG static void WaylandDisplayRelease(wl_display *aDisplay); static void WaylandDisplayLoop(wl_display *aDisplay); -// TODO: is the 60pfs loop correct? -#define EVENT_LOOP_DELAY (1000/60) +// TODO: Bug 1467125 - We need to integrate wl_display_dispatch_queue_pending() with +// compositor event loop. +#define EVENT_LOOP_DELAY (1000/240) // Get WaylandDisplay for given wl_display and actual calling thread. static nsWaylandDisplay* @@ -304,6 +305,7 @@ nsWaylandDisplay::nsWaylandDisplay(wl_di : mThreadId(PR_GetCurrentThread()) // gfx::SurfaceFormat::B8G8R8A8 is a basic Wayland format // and is always present. + // TODO: Provide also format without alpha (Bug 1470126). , mFormat(gfx::SurfaceFormat::B8G8R8A8) , mShm(nullptr) , mDisplay(aDisplay) @@ -522,7 +524,7 @@ WindowBackBuffer::Detach() } bool -WindowBackBuffer::SetImageDataFromBackBuffer( +WindowBackBuffer::SetImageDataFromBuffer( class WindowBackBuffer* aSourceBuffer) { if (!IsMatchingSize(aSourceBuffer)) { @@ -535,11 +537,9 @@ WindowBackBuffer::SetImageDataFromBackBu } already_AddRefed -WindowBackBuffer::Lock(const LayoutDeviceIntRegion& aRegion) +WindowBackBuffer::Lock() { - gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect(); - gfx::IntSize lockSize(bounds.XMost(), bounds.YMost()); - + gfx::IntSize lockSize(mWidth, mHeight); return gfxPlatform::CreateDrawTargetForData(static_cast(mShmPool.GetImageData()), lockSize, BUFFER_BPP * mWidth, @@ -551,6 +551,8 @@ frame_callback_handler(void *data, struc { auto surface = reinterpret_cast(data); surface->FrameCallbackHandler(); + + gfxPlatformGtk::GetPlatform()->SetWaylandLastVsync(time); } static const struct wl_callback_listener frame_listener = { @@ -560,26 +562,40 @@ static const struct wl_callback_listener WindowSurfaceWayland::WindowSurfaceWayland(nsWindow *aWindow) : mWindow(aWindow) , mWaylandDisplay(WaylandDisplayGet(aWindow->GetWaylandDisplay())) - , mFrontBuffer(nullptr) - , mBackBuffer(nullptr) + , mWaylandBuffer(nullptr) + , mBackupBuffer(nullptr) , mFrameCallback(nullptr) - , mFrameCallbackSurface(nullptr) + , mLastCommittedSurface(nullptr) , mDisplayThreadMessageLoop(MessageLoop::current()) - , mDelayedCommit(false) - , mFullScreenDamage(false) + , mDelayedCommitHandle(nullptr) + , mDrawToWaylandBufferDirectly(true) + , mPendingCommit(false) + , mWaylandBufferFullScreenDamage(false) , mIsMainThread(NS_IsMainThread()) + , mNeedScaleFactorUpdate(true) { } WindowSurfaceWayland::~WindowSurfaceWayland() { - delete mFrontBuffer; - delete mBackBuffer; + if (mPendingCommit) { + NS_WARNING("Deleted WindowSurfaceWayland with a pending commit!"); + } + + if (mDelayedCommitHandle) { + // Delete reference to this to prevent WaylandBufferDelayCommitHandler() + // operate on released this. mDelayedCommitHandle itself will + // be released at WaylandBufferDelayCommitHandler(). + *mDelayedCommitHandle = nullptr; + } if (mFrameCallback) { wl_callback_destroy(mFrameCallback); } + delete mWaylandBuffer; + delete mBackupBuffer; + if (!mIsMainThread) { // We can be destroyed from main thread even though we was created/used // in compositor thread. We have to unref/delete WaylandDisplay in compositor @@ -593,162 +609,309 @@ WindowSurfaceWayland::~WindowSurfaceWayl } } -void -WindowSurfaceWayland::UpdateScaleFactor() -{ - wl_surface* waylandSurface = mWindow->GetWaylandSurface(); - if (waylandSurface) { - wl_surface_set_buffer_scale(waylandSurface, mWindow->GdkScaleFactor()); - } -} - WindowBackBuffer* -WindowSurfaceWayland::GetBufferToDraw(int aWidth, int aHeight) +WindowSurfaceWayland::GetWaylandBufferToDraw(int aWidth, int aHeight) { - if (!mFrontBuffer) { - mFrontBuffer = new WindowBackBuffer(mWaylandDisplay, aWidth, aHeight); - mBackBuffer = new WindowBackBuffer(mWaylandDisplay, aWidth, aHeight); - return mFrontBuffer; + if (!mWaylandBuffer) { + mWaylandBuffer = new WindowBackBuffer(mWaylandDisplay, aWidth, aHeight); + mBackupBuffer = new WindowBackBuffer(mWaylandDisplay, aWidth, aHeight); + return mWaylandBuffer; } - if (!mFrontBuffer->IsAttached()) { - if (!mFrontBuffer->IsMatchingSize(aWidth, aHeight)) { - mFrontBuffer->Resize(aWidth, aHeight); + if (!mWaylandBuffer->IsAttached()) { + if (!mWaylandBuffer->IsMatchingSize(aWidth, aHeight)) { + mWaylandBuffer->Resize(aWidth, aHeight); // There's a chance that scale factor has been changed // when buffer size changed - UpdateScaleFactor(); + mNeedScaleFactorUpdate = true; } - return mFrontBuffer; + return mWaylandBuffer; } // Front buffer is used by compositor, draw to back buffer - if (mBackBuffer->IsAttached()) { + if (mBackupBuffer->IsAttached()) { NS_WARNING("No drawing buffer available"); return nullptr; } - MOZ_ASSERT(!mDelayedCommit, + MOZ_ASSERT(!mPendingCommit, "Uncommitted buffer switch, screen artifacts ahead."); - WindowBackBuffer *tmp = mFrontBuffer; - mFrontBuffer = mBackBuffer; - mBackBuffer = tmp; + WindowBackBuffer *tmp = mWaylandBuffer; + mWaylandBuffer = mBackupBuffer; + mBackupBuffer = tmp; - if (mBackBuffer->IsMatchingSize(aWidth, aHeight)) { + if (mBackupBuffer->IsMatchingSize(aWidth, aHeight)) { // Former front buffer has the same size as a requested one. // Gecko may expect a content already drawn on screen so copy // existing data to the new buffer. - mFrontBuffer->SetImageDataFromBackBuffer(mBackBuffer); + mWaylandBuffer->SetImageDataFromBuffer(mBackupBuffer); // When buffer switches we need to damage whole screen // (https://bugzilla.redhat.com/show_bug.cgi?id=1418260) - mFullScreenDamage = true; + mWaylandBufferFullScreenDamage = true; } else { // Former buffer has different size from the new request. Only resize // the new buffer and leave gecko to render new whole content. - mFrontBuffer->Resize(aWidth, aHeight); + mWaylandBuffer->Resize(aWidth, aHeight); + } + + return mWaylandBuffer; +} + +already_AddRefed +WindowSurfaceWayland::LockWaylandBuffer(int aWidth, int aHeight) +{ + WindowBackBuffer* buffer = GetWaylandBufferToDraw(aWidth, aHeight); + if (buffer) { + return buffer->Lock(); + } + + NS_WARNING("WindowSurfaceWayland::LockWaylandBuffer(): No buffer available"); + return nullptr; +} + +already_AddRefed +WindowSurfaceWayland::LockImageSurface(const gfx::IntSize& aLockSize) +{ + if (!mImageSurface || mImageSurface->CairoStatus() || + !(aLockSize <= mImageSurface->GetSize())) { + mImageSurface = new gfxImageSurface(aLockSize, + SurfaceFormatToImageFormat(mWaylandDisplay->GetSurfaceFormat())); + if (mImageSurface->CairoStatus()) { + return nullptr; + } } - return mFrontBuffer; + return gfxPlatform::CreateDrawTargetForData(mImageSurface->Data(), + mImageSurface->GetSize(), + mImageSurface->Stride(), + mWaylandDisplay->GetSurfaceFormat()); } +/* + There are some situations which can happen here: + + A) Lock() is called to whole surface. In that case we don't need + to clip/buffer the drawing and we can return wl_buffer directly + for drawing. + - mWaylandBuffer is available - that's an ideal situation. + - mWaylandBuffer is locked by compositor - flip buffers and draw. + - if we can't flip buffers - go B) + + B) Lock() is requested for part(s) of screen. We need to provide temporary + surface to draw into and copy result (clipped) to target wl_surface. + */ already_AddRefed WindowSurfaceWayland::Lock(const LayoutDeviceIntRegion& aRegion) { MOZ_ASSERT(mIsMainThread == NS_IsMainThread()); - // We allocate back buffer to widget size but return only - // portion requested by aRegion. - LayoutDeviceIntRect rect = mWindow->GetBounds(); - WindowBackBuffer* buffer = GetBufferToDraw(rect.width, - rect.height); - if (!buffer) { - NS_WARNING("No drawing buffer available"); - return nullptr; + LayoutDeviceIntRect screenRect = mWindow->GetBounds(); + gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect(); + gfx::IntSize lockSize(bounds.XMost(), bounds.YMost()); + + // Are we asked for entire nsWindow to draw? + mDrawToWaylandBufferDirectly = (aRegion.GetNumRects() == 1 && + bounds.x == 0 && bounds.y == 0 && + lockSize.width == screenRect.width && + lockSize.height == screenRect.height); + + if (mDrawToWaylandBufferDirectly) { + RefPtr dt = LockWaylandBuffer(screenRect.width, + screenRect.height); + if (dt) { + return dt.forget(); + } + + // We don't have any front buffer available. Try indirect drawing + // to mImageSurface which is mirrored to front buffer at commit. + mDrawToWaylandBufferDirectly = false; } - return buffer->Lock(aRegion); + return LockImageSurface(lockSize); +} + +bool +WindowSurfaceWayland::CommitImageSurfaceToWaylandBuffer(const LayoutDeviceIntRegion& aRegion) +{ + MOZ_ASSERT(!mDrawToWaylandBufferDirectly); + + LayoutDeviceIntRect screenRect = mWindow->GetBounds(); + gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect(); + + gfx::Rect rect(bounds); + if (rect.IsEmpty()) { + return false; + } + + RefPtr dt = LockWaylandBuffer(screenRect.width, + screenRect.height); + RefPtr surf = + gfx::Factory::CreateSourceSurfaceForCairoSurface(mImageSurface->CairoSurface(), + mImageSurface->GetSize(), + mImageSurface->Format()); + if (!dt || !surf) { + return false; + } + + uint32_t numRects = aRegion.GetNumRects(); + if (numRects != 1) { + AutoTArray rects; + rects.SetCapacity(numRects); + for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { + rects.AppendElement(iter.Get().ToUnknownRect()); + } + dt->PushDeviceSpaceClipRects(rects.Elements(), rects.Length()); + } + + dt->DrawSurface(surf, rect, rect); + + if (numRects != 1) { + dt->PopClip(); + } + + return true; +} + +static void +WaylandBufferDelayCommitHandler(WindowSurfaceWayland **aSurface) +{ + if (*aSurface) { + (*aSurface)->DelayedCommitHandler(); + } else { + // Referenced WindowSurfaceWayland is already deleted. + // Do nothing but just release the mDelayedCommitHandle allocated at + // WindowSurfaceWayland::CommitWaylandBuffer(). + free(aSurface); + } } void -WindowSurfaceWayland::Commit(const LayoutDeviceIntRegion& aInvalidRegion) +WindowSurfaceWayland::CommitWaylandBuffer() { - MOZ_ASSERT(mIsMainThread == NS_IsMainThread()); + MOZ_ASSERT(mPendingCommit, "Committing empty surface!"); wl_surface* waylandSurface = mWindow->GetWaylandSurface(); if (!waylandSurface) { - // Target window is already destroyed - don't bother to render there. + // Target window is not created yet - delay the commit. This can happen only + // when the window is newly created and there's no active + // frame callback pending. + MOZ_ASSERT(!mFrameCallback || waylandSurface != mLastCommittedSurface, + "Missing wayland surface at frame callback!"); + + // Do nothing if there's already mDelayedCommitHandle pending. + if (!mDelayedCommitHandle) { + mDelayedCommitHandle = static_cast( + moz_xmalloc(sizeof(*mDelayedCommitHandle))); + *mDelayedCommitHandle = this; + + MessageLoop::current()->PostDelayedTask( + NewRunnableFunction("WaylandBackBufferCommit", + &WaylandBufferDelayCommitHandler, + mDelayedCommitHandle), + EVENT_LOOP_DELAY); + } return; } wl_proxy_set_queue((struct wl_proxy *)waylandSurface, mWaylandDisplay->GetEventQueue()); - if (mFullScreenDamage) { + // We have an active frame callback request so handle it. + if (mFrameCallback) { + if (waylandSurface == mLastCommittedSurface) { + // We have an active frame callback pending from our recent surface. + // It means we should defer the commit to FrameCallbackHandler(). + return; + } + // If our stored wl_surface does not match the actual one it means the frame + // callback is no longer active and we should release it. + wl_callback_destroy(mFrameCallback); + mFrameCallback = nullptr; + mLastCommittedSurface = nullptr; + } + + if (mWaylandBufferFullScreenDamage) { LayoutDeviceIntRect rect = mWindow->GetBounds(); wl_surface_damage(waylandSurface, 0, 0, rect.width, rect.height); - mFullScreenDamage = false; + mWaylandBufferFullScreenDamage = false; } else { - for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) { + gint scaleFactor = mWindow->GdkScaleFactor(); + for (auto iter = mWaylandBufferDamage.RectIter(); !iter.Done(); iter.Next()) { const mozilla::LayoutDeviceIntRect &r = iter.Get(); - wl_surface_damage(waylandSurface, r.x, r.y, r.width, r.height); + // We need to remove the scale factor because the wl_surface_damage + // also multiplies by current scale factor. + wl_surface_damage(waylandSurface, r.x/scaleFactor, r.y/scaleFactor, + r.width/scaleFactor, r.height/scaleFactor); } } - // Frame callback is always connected to actual wl_surface. When the surface - // is unmapped/deleted the frame callback is never called. Unfortunatelly - // we don't know if the frame callback is not going to be called. - // But our mozcontainer code deletes wl_surface when the GdkWindow is hidden - // creates a new one when is visible. - if (mFrameCallback && mFrameCallbackSurface == waylandSurface) { - // Do nothing here - we have a valid wl_surface and the buffer will be - // commited to compositor in next frame callback event. - mDelayedCommit = true; - return; - } else { - if (mFrameCallback) { - // Delete frame callback connected to obsoleted wl_surface. - wl_callback_destroy(mFrameCallback); - } + // Clear all back buffer damage as we're committing + // all requested regions. + mWaylandBufferDamage.SetEmpty(); - mFrameCallback = wl_surface_frame(waylandSurface); - wl_callback_add_listener(mFrameCallback, &frame_listener, this); - mFrameCallbackSurface = waylandSurface; - - // There's no pending frame callback so we can draw immediately - // and create frame callback for possible subsequent drawing. - mFrontBuffer->Attach(waylandSurface); - mDelayedCommit = false; + mFrameCallback = wl_surface_frame(waylandSurface); + wl_callback_add_listener(mFrameCallback, &frame_listener, this); + + if (mNeedScaleFactorUpdate || mLastCommittedSurface != waylandSurface) { + wl_surface_set_buffer_scale(waylandSurface, mWindow->GdkScaleFactor()); + mNeedScaleFactorUpdate = false; + } + + mWaylandBuffer->Attach(waylandSurface); + mLastCommittedSurface = waylandSurface; + + // There's no pending commit, all changes are sent to compositor. + mPendingCommit = false; +} + +void +WindowSurfaceWayland::Commit(const LayoutDeviceIntRegion& aInvalidRegion) +{ + MOZ_ASSERT(mIsMainThread == NS_IsMainThread()); + + // We have new content at mImageSurface - copy data to mWaylandBuffer first. + if (!mDrawToWaylandBufferDirectly) { + CommitImageSurfaceToWaylandBuffer(aInvalidRegion); } + + // If we're not at fullscreen damage add drawing area from aInvalidRegion + if (!mWaylandBufferFullScreenDamage) { + mWaylandBufferDamage.OrWith(aInvalidRegion); + } + + // We're ready to commit. + mPendingCommit = true; + CommitWaylandBuffer(); } void WindowSurfaceWayland::FrameCallbackHandler() { MOZ_ASSERT(mIsMainThread == NS_IsMainThread()); + MOZ_ASSERT(mFrameCallback != nullptr, + "FrameCallbackHandler() called without valid frame callback!"); + MOZ_ASSERT(mLastCommittedSurface != nullptr, + "FrameCallbackHandler() called without valid wl_surface!"); - if (mFrameCallback) { - wl_callback_destroy(mFrameCallback); - mFrameCallback = nullptr; - mFrameCallbackSurface = nullptr; + wl_callback_destroy(mFrameCallback); + mFrameCallback = nullptr; + + if (mPendingCommit) { + CommitWaylandBuffer(); } +} - if (mDelayedCommit) { - wl_surface* waylandSurface = mWindow->GetWaylandSurface(); - if (!waylandSurface) { - // Target window is already destroyed - don't bother to render there. - NS_WARNING("No drawing buffer available"); - return; - } - wl_proxy_set_queue((struct wl_proxy *)waylandSurface, - mWaylandDisplay->GetEventQueue()); +void +WindowSurfaceWayland::DelayedCommitHandler() +{ + MOZ_ASSERT(mDelayedCommitHandle != nullptr, "Missing mDelayedCommitHandle!"); - // Send pending surface to compositor and register frame callback - // for possible subsequent drawing. - mFrameCallback = wl_surface_frame(waylandSurface); - wl_callback_add_listener(mFrameCallback, &frame_listener, this); - mFrameCallbackSurface = waylandSurface; + *mDelayedCommitHandle = nullptr; + free(mDelayedCommitHandle); + mDelayedCommitHandle = nullptr; - mFrontBuffer->Attach(waylandSurface); - mDelayedCommit = false; + if (mPendingCommit) { + CommitWaylandBuffer(); } } diff -up thunderbird-60.3.0/widget/gtk/WindowSurfaceWayland.h.wayland thunderbird-60.3.0/widget/gtk/WindowSurfaceWayland.h --- thunderbird-60.3.0/widget/gtk/WindowSurfaceWayland.h.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/WindowSurfaceWayland.h 2018-11-20 12:04:43.742787334 +0100 @@ -8,6 +8,7 @@ #define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_WAYLAND_H #include +#include "mozilla/gfx/Types.h" namespace mozilla { namespace widget { @@ -66,14 +67,14 @@ public: WindowBackBuffer(nsWaylandDisplay* aDisplay, int aWidth, int aHeight); ~WindowBackBuffer(); - already_AddRefed Lock(const LayoutDeviceIntRegion& aRegion); + already_AddRefed Lock(); void Attach(wl_surface* aSurface); void Detach(); bool IsAttached() { return mAttached; } bool Resize(int aWidth, int aHeight); - bool SetImageDataFromBackBuffer(class WindowBackBuffer* aSourceBuffer); + bool SetImageDataFromBuffer(class WindowBackBuffer* aSourceBuffer); bool IsMatchingSize(int aWidth, int aHeight) { @@ -110,22 +111,32 @@ public: already_AddRefed Lock(const LayoutDeviceIntRegion& aRegion) override; void Commit(const LayoutDeviceIntRegion& aInvalidRegion) final; void FrameCallbackHandler(); + void DelayedCommitHandler(); private: - WindowBackBuffer* GetBufferToDraw(int aWidth, int aHeight); - void UpdateScaleFactor(); + WindowBackBuffer* GetWaylandBufferToDraw(int aWidth, int aHeight); + + already_AddRefed LockWaylandBuffer(int aWidth, int aHeight); + already_AddRefed LockImageSurface(const gfx::IntSize& aLockSize); + bool CommitImageSurfaceToWaylandBuffer(const LayoutDeviceIntRegion& aRegion); + void CommitWaylandBuffer(); // TODO: Do we need to hold a reference to nsWindow object? nsWindow* mWindow; nsWaylandDisplay* mWaylandDisplay; - WindowBackBuffer* mFrontBuffer; - WindowBackBuffer* mBackBuffer; + WindowBackBuffer* mWaylandBuffer; + LayoutDeviceIntRegion mWaylandBufferDamage; + WindowBackBuffer* mBackupBuffer; + RefPtr mImageSurface; wl_callback* mFrameCallback; - wl_surface* mFrameCallbackSurface; + wl_surface* mLastCommittedSurface; MessageLoop* mDisplayThreadMessageLoop; - bool mDelayedCommit; - bool mFullScreenDamage; + WindowSurfaceWayland** mDelayedCommitHandle; + bool mDrawToWaylandBufferDirectly; + bool mPendingCommit; + bool mWaylandBufferFullScreenDamage; bool mIsMainThread; + bool mNeedScaleFactorUpdate; }; } // namespace widget diff -up thunderbird-60.3.0/widget/gtk/WindowSurfaceX11Image.cpp.wayland thunderbird-60.3.0/widget/gtk/WindowSurfaceX11Image.cpp --- thunderbird-60.3.0/widget/gtk/WindowSurfaceX11Image.cpp.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/WindowSurfaceX11Image.cpp 2018-11-20 12:04:43.743787331 +0100 @@ -12,19 +12,42 @@ #include "gfxPlatform.h" #include "gfx2DGlue.h" +#include + namespace mozilla { namespace widget { +// gfxImageSurface pixel format configuration. +#define SHAPED_IMAGE_SURFACE_BPP 4 +#ifdef IS_BIG_ENDIAN + #define SHAPED_IMAGE_SURFACE_ALPHA_INDEX 0 +#else + #define SHAPED_IMAGE_SURFACE_ALPHA_INDEX 3 +#endif + WindowSurfaceX11Image::WindowSurfaceX11Image(Display* aDisplay, Window aWindow, Visual* aVisual, - unsigned int aDepth) + unsigned int aDepth, + bool aIsShaped) : WindowSurfaceX11(aDisplay, aWindow, aVisual, aDepth) + , mTransparencyBitmap(nullptr) + , mTransparencyBitmapWidth(0) + , mTransparencyBitmapHeight(0) + , mIsShaped(aIsShaped) { } WindowSurfaceX11Image::~WindowSurfaceX11Image() { + if (mTransparencyBitmap) { + delete[] mTransparencyBitmap; + + Display* xDisplay = mWindowSurface->XDisplay(); + Window xDrawable = mWindowSurface->XDrawable(); + + XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, X11None, ShapeSet); + } } already_AddRefed @@ -50,6 +73,13 @@ WindowSurfaceX11Image::Lock(const Layout gfx::SurfaceFormat::X8R8G8B8_UINT32; } + // Use alpha image format for shaped window as we derive + // the shape bitmap from alpha channel. Must match SHAPED_IMAGE_SURFACE_BPP + // and SHAPED_IMAGE_SURFACE_ALPHA_INDEX. + if (mIsShaped) { + format = gfx::SurfaceFormat::A8R8G8B8_UINT32; + } + mImageSurface = new gfxImageSurface(size, format); if (mImageSurface->CairoStatus()) { return nullptr; @@ -82,6 +112,132 @@ WindowSurfaceX11Image::Lock(const Layout ImageFormatToSurfaceFormat(format)); } +// The transparency bitmap routines are derived form the ones at nsWindow.cpp. +// The difference here is that we compose to RGBA image and then create +// the shape mask from final image alpha channel. +static inline int32_t +GetBitmapStride(int32_t width) +{ + return (width+7)/8; +} + +static bool +ChangedMaskBits(gchar* aMaskBits, int32_t aMaskWidth, int32_t aMaskHeight, + const nsIntRect& aRect, uint8_t* aImageData) +{ + int32_t stride = aMaskWidth*SHAPED_IMAGE_SURFACE_BPP; + int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost(); + int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth); + for (y = aRect.y; y < yMax; y++) { + gchar* maskBytes = aMaskBits + y*maskBytesPerRow; + uint8_t* alphas = aImageData; + for (x = aRect.x; x < xMax; x++) { + bool newBit = *(alphas+SHAPED_IMAGE_SURFACE_ALPHA_INDEX) > 0x7f; + alphas += SHAPED_IMAGE_SURFACE_BPP; + + gchar maskByte = maskBytes[x >> 3]; + bool maskBit = (maskByte & (1 << (x & 7))) != 0; + + if (maskBit != newBit) { + return true; + } + } + aImageData += stride; + } + + return false; +} + +static void +UpdateMaskBits(gchar* aMaskBits, int32_t aMaskWidth, int32_t aMaskHeight, + const nsIntRect& aRect, uint8_t* aImageData) +{ + int32_t stride = aMaskWidth*SHAPED_IMAGE_SURFACE_BPP; + int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost(); + int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth); + for (y = aRect.y; y < yMax; y++) { + gchar* maskBytes = aMaskBits + y*maskBytesPerRow; + uint8_t* alphas = aImageData; + for (x = aRect.x; x < xMax; x++) { + bool newBit = *(alphas+SHAPED_IMAGE_SURFACE_ALPHA_INDEX) > 0x7f; + alphas += SHAPED_IMAGE_SURFACE_BPP; + + gchar mask = 1 << (x & 7); + gchar maskByte = maskBytes[x >> 3]; + // Note: '-newBit' turns 0 into 00...00 and 1 into 11...11 + maskBytes[x >> 3] = (maskByte & ~mask) | (-newBit & mask); + } + aImageData += stride; + } +} + +void +WindowSurfaceX11Image::ResizeTransparencyBitmap(int aWidth, int aHeight) +{ + int32_t actualSize = + GetBitmapStride(mTransparencyBitmapWidth)*mTransparencyBitmapHeight; + int32_t newSize = GetBitmapStride(aWidth)*aHeight; + + if (actualSize < newSize) { + delete[] mTransparencyBitmap; + mTransparencyBitmap = new gchar[newSize]; + } + + mTransparencyBitmapWidth = aWidth; + mTransparencyBitmapHeight = aHeight; +} + +void +WindowSurfaceX11Image::ApplyTransparencyBitmap() +{ + gfx::IntSize size = mWindowSurface->GetSize(); + bool maskChanged = true; + + if (!mTransparencyBitmap) { + mTransparencyBitmapWidth = size.width; + mTransparencyBitmapHeight = size.height; + + int32_t byteSize = + GetBitmapStride(mTransparencyBitmapWidth)*mTransparencyBitmapHeight; + mTransparencyBitmap = new gchar[byteSize]; + } else { + bool sizeChanged = (size.width != mTransparencyBitmapWidth || + size.height != mTransparencyBitmapHeight); + + if (sizeChanged) { + ResizeTransparencyBitmap(size.width, size.height); + } else { + maskChanged = ChangedMaskBits(mTransparencyBitmap, + mTransparencyBitmapWidth, mTransparencyBitmapHeight, + nsIntRect(0, 0, size.width, size.height), + (uint8_t*)mImageSurface->Data()); + } + } + + if (maskChanged) { + UpdateMaskBits(mTransparencyBitmap, + mTransparencyBitmapWidth, + mTransparencyBitmapHeight, + nsIntRect(0, 0, size.width, size.height), + (uint8_t*)mImageSurface->Data()); + + // We use X11 calls where possible, because GDK handles expose events + // for shaped windows in a way that's incompatible with us (Bug 635903). + // It doesn't occur when the shapes are set through X. + Display* xDisplay = mWindowSurface->XDisplay(); + Window xDrawable = mWindowSurface->XDrawable(); + Pixmap maskPixmap = XCreateBitmapFromData(xDisplay, + xDrawable, + mTransparencyBitmap, + mTransparencyBitmapWidth, + mTransparencyBitmapHeight); + XShapeCombineMask(xDisplay, xDrawable, + ShapeBounding, 0, 0, + maskPixmap, ShapeSet); + XFreePixmap(xDisplay, maskPixmap); + } +} + void WindowSurfaceX11Image::Commit(const LayoutDeviceIntRegion& aInvalidRegion) { @@ -112,6 +268,10 @@ WindowSurfaceX11Image::Commit(const Layo dt->PushDeviceSpaceClipRects(rects.Elements(), rects.Length()); } + if (mIsShaped) { + ApplyTransparencyBitmap(); + } + dt->DrawSurface(surf, rect, rect); if (numRects != 1) { diff -up thunderbird-60.3.0/widget/gtk/WindowSurfaceX11Image.h.wayland thunderbird-60.3.0/widget/gtk/WindowSurfaceX11Image.h --- thunderbird-60.3.0/widget/gtk/WindowSurfaceX11Image.h.wayland 2018-10-30 12:45:35.000000000 +0100 +++ thunderbird-60.3.0/widget/gtk/WindowSurfaceX11Image.h 2018-11-20 12:04:43.743787331 +0100 @@ -19,7 +19,7 @@ namespace widget { class WindowSurfaceX11Image : public WindowSurfaceX11 { public: WindowSurfaceX11Image(Display* aDisplay, Window aWindow, Visual* aVisual, - unsigned int aDepth); + unsigned int aDepth, bool aIsShaped); ~WindowSurfaceX11Image(); already_AddRefed Lock(const LayoutDeviceIntRegion& aRegion) override; @@ -27,8 +27,16 @@ public: bool IsFallback() const override { return true; } private: + void ResizeTransparencyBitmap(int aWidth, int aHeight); + void ApplyTransparencyBitmap(); + RefPtr mWindowSurface; RefPtr mImageSurface; + + gchar* mTransparencyBitmap; + int32_t mTransparencyBitmapWidth; + int32_t mTransparencyBitmapHeight; + bool mIsShaped; }; } // namespace widget