summaryrefslogtreecommitdiffstats
path: root/widget/gtk
diff options
context:
space:
mode:
Diffstat (limited to 'widget/gtk')
-rw-r--r--widget/gtk/CompositorWidgetChild.cpp45
-rw-r--r--widget/gtk/CompositorWidgetChild.h38
-rw-r--r--widget/gtk/CompositorWidgetParent.cpp48
-rw-r--r--widget/gtk/CompositorWidgetParent.h37
-rw-r--r--widget/gtk/IMContextWrapper.cpp2359
-rw-r--r--widget/gtk/IMContextWrapper.h481
-rw-r--r--widget/gtk/InProcessX11CompositorWidget.cpp34
-rw-r--r--widget/gtk/InProcessX11CompositorWidget.h30
-rw-r--r--widget/gtk/NativeKeyBindings.cpp374
-rw-r--r--widget/gtk/NativeKeyBindings.h49
-rw-r--r--widget/gtk/PCompositorWidget.ipdl30
-rw-r--r--widget/gtk/PlatformWidgetTypes.ipdlh24
-rw-r--r--widget/gtk/WakeLockListener.cpp365
-rw-r--r--widget/gtk/WakeLockListener.h53
-rw-r--r--widget/gtk/WidgetStyleCache.cpp1162
-rw-r--r--widget/gtk/WidgetStyleCache.h48
-rw-r--r--widget/gtk/WidgetTraceEvent.cpp78
-rw-r--r--widget/gtk/WidgetUtilsGtk.cpp51
-rw-r--r--widget/gtk/WidgetUtilsGtk.h25
-rw-r--r--widget/gtk/WindowSurfaceProvider.cpp114
-rw-r--r--widget/gtk/WindowSurfaceProvider.h69
-rw-r--r--widget/gtk/WindowSurfaceX11.cpp64
-rw-r--r--widget/gtk/WindowSurfaceX11.h39
-rw-r--r--widget/gtk/WindowSurfaceX11Image.cpp119
-rw-r--r--widget/gtk/WindowSurfaceX11Image.h38
-rw-r--r--widget/gtk/WindowSurfaceXRender.cpp83
-rw-r--r--widget/gtk/WindowSurfaceXRender.h36
-rw-r--r--widget/gtk/X11CompositorWidget.cpp108
-rw-r--r--widget/gtk/X11CompositorWidget.h69
-rw-r--r--widget/gtk/compat-gtk3/gdk/gdkversionmacros.h31
-rw-r--r--widget/gtk/compat-gtk3/gtk/gtkenums.h28
-rw-r--r--widget/gtk/compat/gdk/gdkdnd.h33
-rw-r--r--widget/gtk/compat/gdk/gdkkeysyms.h266
-rw-r--r--widget/gtk/compat/gdk/gdkvisual.h17
-rw-r--r--widget/gtk/compat/gdk/gdkwindow.h33
-rw-r--r--widget/gtk/compat/gdk/gdkx.h51
-rw-r--r--widget/gtk/compat/glib/gmem.h56
-rw-r--r--widget/gtk/compat/gtk/gtkwidget.h50
-rw-r--r--widget/gtk/compat/gtk/gtkwindow.h30
-rw-r--r--widget/gtk/crashtests/540078-1.xhtml2
-rw-r--r--widget/gtk/crashtests/673390-1.html1
-rw-r--r--widget/gtk/crashtests/crashtests.list2
-rw-r--r--widget/gtk/gtk2drawing.c3502
-rw-r--r--widget/gtk/gtk3drawing.cpp2843
-rw-r--r--widget/gtk/gtkdrawing.h542
-rw-r--r--widget/gtk/maiRedundantObjectFactory.c93
-rw-r--r--widget/gtk/maiRedundantObjectFactory.h32
-rw-r--r--widget/gtk/moz.build141
-rw-r--r--widget/gtk/mozcontainer.c418
-rw-r--r--widget/gtk/mozcontainer.h86
-rw-r--r--widget/gtk/mozgtk/gtk2/moz.build40
-rw-r--r--widget/gtk/mozgtk/gtk3/moz.build38
-rw-r--r--widget/gtk/mozgtk/moz.build7
-rw-r--r--widget/gtk/mozgtk/mozgtk.c634
-rw-r--r--widget/gtk/mozgtk/stub/moz.build16
-rw-r--r--widget/gtk/nsAppShell.cpp271
-rw-r--r--widget/gtk/nsAppShell.h37
-rw-r--r--widget/gtk/nsApplicationChooser.cpp128
-rw-r--r--widget/gtk/nsApplicationChooser.h32
-rw-r--r--widget/gtk/nsBidiKeyboard.cpp55
-rw-r--r--widget/gtk/nsBidiKeyboard.h26
-rw-r--r--widget/gtk/nsCUPSShim.cpp59
-rw-r--r--widget/gtk/nsCUPSShim.h86
-rw-r--r--widget/gtk/nsClipboard.cpp1046
-rw-r--r--widget/gtk/nsClipboard.h58
-rw-r--r--widget/gtk/nsColorPicker.cpp252
-rw-r--r--widget/gtk/nsColorPicker.h73
-rw-r--r--widget/gtk/nsDeviceContextSpecG.cpp498
-rw-r--r--widget/gtk/nsDeviceContextSpecG.h76
-rw-r--r--widget/gtk/nsDragService.cpp2109
-rw-r--r--widget/gtk/nsDragService.h226
-rw-r--r--widget/gtk/nsFilePicker.cpp610
-rw-r--r--widget/gtk/nsFilePicker.h82
-rw-r--r--widget/gtk/nsGTKToolkit.h54
-rw-r--r--widget/gtk/nsGtkCursors.h405
-rw-r--r--widget/gtk/nsGtkKeyUtils.cpp1483
-rw-r--r--widget/gtk/nsGtkKeyUtils.h360
-rw-r--r--widget/gtk/nsGtkUtils.h24
-rw-r--r--widget/gtk/nsIImageToPixbuf.h34
-rw-r--r--widget/gtk/nsIdleServiceGTK.cpp132
-rw-r--r--widget/gtk/nsIdleServiceGTK.h52
-rw-r--r--widget/gtk/nsImageToPixbuf.cpp120
-rw-r--r--widget/gtk/nsImageToPixbuf.h46
-rw-r--r--widget/gtk/nsLookAndFeel.cpp1465
-rw-r--r--widget/gtk/nsLookAndFeel.h91
-rw-r--r--widget/gtk/nsNativeThemeGTK.cpp1994
-rw-r--r--widget/gtk/nsNativeThemeGTK.h93
-rw-r--r--widget/gtk/nsPSPrinters.cpp123
-rw-r--r--widget/gtk/nsPSPrinters.h53
-rw-r--r--widget/gtk/nsPaperPS.cpp43
-rw-r--r--widget/gtk/nsPaperPS.h93
-rw-r--r--widget/gtk/nsPrintDialogGTK.cpp609
-rw-r--r--widget/gtk/nsPrintDialogGTK.h39
-rw-r--r--widget/gtk/nsPrintOptionsGTK.cpp102
-rw-r--r--widget/gtk/nsPrintOptionsGTK.h40
-rw-r--r--widget/gtk/nsPrintSettingsGTK.cpp813
-rw-r--r--widget/gtk/nsPrintSettingsGTK.h151
-rw-r--r--widget/gtk/nsScreenGtk.cpp207
-rw-r--r--widget/gtk/nsScreenGtk.h57
-rw-r--r--widget/gtk/nsScreenManagerGtk.cpp366
-rw-r--r--widget/gtk/nsScreenManagerGtk.h52
-rw-r--r--widget/gtk/nsSound.cpp444
-rw-r--r--widget/gtk/nsSound.h35
-rw-r--r--widget/gtk/nsToolkit.cpp34
-rw-r--r--widget/gtk/nsWidgetFactory.cpp331
-rw-r--r--widget/gtk/nsWindow.cpp7036
-rw-r--r--widget/gtk/nsWindow.h580
107 files changed, 38347 insertions, 0 deletions
diff --git a/widget/gtk/CompositorWidgetChild.cpp b/widget/gtk/CompositorWidgetChild.cpp
new file mode 100644
index 000000000..066251060
--- /dev/null
+++ b/widget/gtk/CompositorWidgetChild.cpp
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "CompositorWidgetChild.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace widget {
+
+CompositorWidgetChild::CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+ RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver)
+ : mVsyncDispatcher(aVsyncDispatcher)
+ , mVsyncObserver(aVsyncObserver)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+}
+
+CompositorWidgetChild::~CompositorWidgetChild()
+{
+}
+
+bool
+CompositorWidgetChild::RecvObserveVsync()
+{
+ mVsyncDispatcher->SetCompositorVsyncObserver(mVsyncObserver);
+ return true;
+}
+
+bool
+CompositorWidgetChild::RecvUnobserveVsync()
+{
+ mVsyncDispatcher->SetCompositorVsyncObserver(nullptr);
+ return true;
+}
+
+void
+CompositorWidgetChild::NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize)
+{
+ Unused << SendNotifyClientSizeChanged(aClientSize);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/CompositorWidgetChild.h b/widget/gtk/CompositorWidgetChild.h
new file mode 100644
index 000000000..403b90506
--- /dev/null
+++ b/widget/gtk/CompositorWidgetChild.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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_CompositorWidgetChild_h
+#define widget_gtk_CompositorWidgetChild_h
+
+#include "X11CompositorWidget.h"
+#include "mozilla/widget/PCompositorWidgetChild.h"
+#include "mozilla/widget/CompositorWidgetVsyncObserver.h"
+
+namespace mozilla {
+namespace widget {
+
+class CompositorWidgetChild final
+ : public PCompositorWidgetChild
+ , public CompositorWidgetDelegate
+{
+public:
+ CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+ RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver);
+ ~CompositorWidgetChild() override;
+
+ bool RecvObserveVsync() override;
+ bool RecvUnobserveVsync() override;
+
+ void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) override;
+
+private:
+ RefPtr<CompositorVsyncDispatcher> mVsyncDispatcher;
+ RefPtr<CompositorWidgetVsyncObserver> mVsyncObserver;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_gtk_CompositorWidgetChild_h
diff --git a/widget/gtk/CompositorWidgetParent.cpp b/widget/gtk/CompositorWidgetParent.cpp
new file mode 100644
index 000000000..c882f4f08
--- /dev/null
+++ b/widget/gtk/CompositorWidgetParent.cpp
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "CompositorWidgetParent.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace widget {
+
+CompositorWidgetParent::CompositorWidgetParent(const CompositorWidgetInitData& aInitData)
+ : X11CompositorWidget(aInitData)
+{
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU);
+}
+
+CompositorWidgetParent::~CompositorWidgetParent()
+{
+}
+
+void
+CompositorWidgetParent::ObserveVsync(VsyncObserver* aObserver)
+{
+ if (aObserver) {
+ Unused << SendObserveVsync();
+ } else {
+ Unused << SendUnobserveVsync();
+ }
+ mVsyncObserver = aObserver;
+}
+
+RefPtr<VsyncObserver>
+CompositorWidgetParent::GetVsyncObserver() const
+{
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU);
+ return mVsyncObserver;
+}
+
+bool
+CompositorWidgetParent::RecvNotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize)
+{
+ NotifyClientSizeChanged(aClientSize);
+ return true;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/CompositorWidgetParent.h b/widget/gtk/CompositorWidgetParent.h
new file mode 100644
index 000000000..e80c0f8b2
--- /dev/null
+++ b/widget/gtk/CompositorWidgetParent.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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_CompositorWidgetParent_h
+#define widget_gtk_CompositorWidgetParent_h
+
+#include "X11CompositorWidget.h"
+#include "mozilla/widget/PCompositorWidgetParent.h"
+
+namespace mozilla {
+namespace widget {
+
+class CompositorWidgetParent final
+ : public PCompositorWidgetParent,
+ public X11CompositorWidget
+{
+public:
+ explicit CompositorWidgetParent(const CompositorWidgetInitData& aInitData);
+ ~CompositorWidgetParent() override;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override { }
+
+ void ObserveVsync(VsyncObserver* aObserver) override;
+ RefPtr<VsyncObserver> GetVsyncObserver() const override;
+
+ bool RecvNotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) override;
+
+private:
+ RefPtr<VsyncObserver> mVsyncObserver;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_gtk_CompositorWidgetParent_h
diff --git a/widget/gtk/IMContextWrapper.cpp b/widget/gtk/IMContextWrapper.cpp
new file mode 100644
index 000000000..58d7a3681
--- /dev/null
+++ b/widget/gtk/IMContextWrapper.cpp
@@ -0,0 +1,2359 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "mozilla/Logging.h"
+#include "prtime.h"
+
+#include "IMContextWrapper.h"
+#include "nsGtkKeyUtils.h"
+#include "nsWindow.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "WritingModes.h"
+
+namespace mozilla {
+namespace widget {
+
+LazyLogModule gGtkIMLog("nsGtkIMModuleWidgets");
+
+static inline const char*
+ToChar(bool aBool)
+{
+ return aBool ? "true" : "false";
+}
+
+static const char*
+GetEnabledStateName(uint32_t aState)
+{
+ switch (aState) {
+ case IMEState::DISABLED:
+ return "DISABLED";
+ case IMEState::ENABLED:
+ return "ENABLED";
+ case IMEState::PASSWORD:
+ return "PASSWORD";
+ case IMEState::PLUGIN:
+ return "PLUG_IN";
+ default:
+ return "UNKNOWN ENABLED STATUS!!";
+ }
+}
+
+static const char*
+GetEventType(GdkEventKey* aKeyEvent)
+{
+ switch (aKeyEvent->type) {
+ case GDK_KEY_PRESS:
+ return "GDK_KEY_PRESS";
+ case GDK_KEY_RELEASE:
+ return "GDK_KEY_RELEASE";
+ default:
+ return "Unknown";
+ }
+}
+
+class GetWritingModeName : public nsAutoCString
+{
+public:
+ explicit GetWritingModeName(const WritingMode& aWritingMode)
+ {
+ if (!aWritingMode.IsVertical()) {
+ AssignLiteral("Horizontal");
+ return;
+ }
+ if (aWritingMode.IsVerticalLR()) {
+ AssignLiteral("Vertical (LTR)");
+ return;
+ }
+ AssignLiteral("Vertical (RTL)");
+ }
+ virtual ~GetWritingModeName() {}
+};
+
+class GetTextRangeStyleText final : public nsAutoCString
+{
+public:
+ explicit GetTextRangeStyleText(const TextRangeStyle& aStyle)
+ {
+ if (!aStyle.IsDefined()) {
+ AssignLiteral("{ IsDefined()=false }");
+ return;
+ }
+
+ if (aStyle.IsLineStyleDefined()) {
+ AppendLiteral("{ mLineStyle=");
+ AppendLineStyle(aStyle.mLineStyle);
+ if (aStyle.IsUnderlineColorDefined()) {
+ AppendLiteral(", mUnderlineColor=");
+ AppendColor(aStyle.mUnderlineColor);
+ } else {
+ AppendLiteral(", IsUnderlineColorDefined=false");
+ }
+ } else {
+ AppendLiteral("{ IsLineStyleDefined()=false");
+ }
+
+ if (aStyle.IsForegroundColorDefined()) {
+ AppendLiteral(", mForegroundColor=");
+ AppendColor(aStyle.mForegroundColor);
+ } else {
+ AppendLiteral(", IsForegroundColorDefined()=false");
+ }
+
+ if (aStyle.IsBackgroundColorDefined()) {
+ AppendLiteral(", mBackgroundColor=");
+ AppendColor(aStyle.mBackgroundColor);
+ } else {
+ AppendLiteral(", IsBackgroundColorDefined()=false");
+ }
+
+ AppendLiteral(" }");
+ }
+ void AppendLineStyle(uint8_t aLineStyle)
+ {
+ switch (aLineStyle) {
+ case TextRangeStyle::LINESTYLE_NONE:
+ AppendLiteral("LINESTYLE_NONE");
+ break;
+ case TextRangeStyle::LINESTYLE_SOLID:
+ AppendLiteral("LINESTYLE_SOLID");
+ break;
+ case TextRangeStyle::LINESTYLE_DOTTED:
+ AppendLiteral("LINESTYLE_DOTTED");
+ break;
+ case TextRangeStyle::LINESTYLE_DASHED:
+ AppendLiteral("LINESTYLE_DASHED");
+ break;
+ case TextRangeStyle::LINESTYLE_DOUBLE:
+ AppendLiteral("LINESTYLE_DOUBLE");
+ break;
+ case TextRangeStyle::LINESTYLE_WAVY:
+ AppendLiteral("LINESTYLE_WAVY");
+ break;
+ default:
+ AppendPrintf("Invalid(0x%02X)", aLineStyle);
+ break;
+ }
+ }
+ void AppendColor(nscolor aColor)
+ {
+ AppendPrintf("{ R=0x%02X, G=0x%02X, B=0x%02X, A=0x%02X }",
+ NS_GET_R(aColor), NS_GET_G(aColor), NS_GET_B(aColor),
+ NS_GET_A(aColor));
+ }
+ virtual ~GetTextRangeStyleText() {};
+};
+
+const static bool kUseSimpleContextDefault = MOZ_WIDGET_GTK == 2;
+
+/******************************************************************************
+ * IMContextWrapper
+ ******************************************************************************/
+
+IMContextWrapper* IMContextWrapper::sLastFocusedContext = nullptr;
+bool IMContextWrapper::sUseSimpleContext;
+
+NS_IMPL_ISUPPORTS(IMContextWrapper,
+ TextEventDispatcherListener,
+ nsISupportsWeakReference)
+
+IMContextWrapper::IMContextWrapper(nsWindow* aOwnerWindow)
+ : mOwnerWindow(aOwnerWindow)
+ , mLastFocusedWindow(nullptr)
+ , mContext(nullptr)
+ , mSimpleContext(nullptr)
+ , mDummyContext(nullptr)
+ , mComposingContext(nullptr)
+ , mCompositionStart(UINT32_MAX)
+ , mProcessingKeyEvent(nullptr)
+ , mCompositionState(eCompositionState_NotComposing)
+ , mIsIMFocused(false)
+ , mIsDeletingSurrounding(false)
+ , mLayoutChanged(false)
+ , mSetCursorPositionOnKeyEvent(true)
+ , mPendingResettingIMContext(false)
+ , mRetrieveSurroundingSignalReceived(false)
+{
+ static bool sFirstInstance = true;
+ if (sFirstInstance) {
+ sFirstInstance = false;
+ sUseSimpleContext =
+ Preferences::GetBool(
+ "intl.ime.use_simple_context_on_password_field",
+ kUseSimpleContextDefault);
+ }
+ Init();
+}
+
+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");
+ GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container));
+
+ // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails.
+ // So, we don't need to check the result.
+
+ // Normal context.
+ mContext = gtk_im_multicontext_new();
+ gtk_im_context_set_client_window(mContext, gdkWindow);
+ g_signal_connect(mContext, "preedit_changed",
+ G_CALLBACK(IMContextWrapper::OnChangeCompositionCallback), this);
+ g_signal_connect(mContext, "retrieve_surrounding",
+ G_CALLBACK(IMContextWrapper::OnRetrieveSurroundingCallback), this);
+ g_signal_connect(mContext, "delete_surrounding",
+ G_CALLBACK(IMContextWrapper::OnDeleteSurroundingCallback), this);
+ g_signal_connect(mContext, "commit",
+ G_CALLBACK(IMContextWrapper::OnCommitCompositionCallback), this);
+ g_signal_connect(mContext, "preedit_start",
+ G_CALLBACK(IMContextWrapper::OnStartCompositionCallback), this);
+ g_signal_connect(mContext, "preedit_end",
+ G_CALLBACK(IMContextWrapper::OnEndCompositionCallback), this);
+
+ // Simple context
+ if (sUseSimpleContext) {
+ mSimpleContext = gtk_im_context_simple_new();
+ gtk_im_context_set_client_window(mSimpleContext, gdkWindow);
+ g_signal_connect(mSimpleContext, "preedit_changed",
+ G_CALLBACK(&IMContextWrapper::OnChangeCompositionCallback),
+ this);
+ g_signal_connect(mSimpleContext, "retrieve_surrounding",
+ G_CALLBACK(&IMContextWrapper::OnRetrieveSurroundingCallback),
+ this);
+ g_signal_connect(mSimpleContext, "delete_surrounding",
+ G_CALLBACK(&IMContextWrapper::OnDeleteSurroundingCallback),
+ this);
+ g_signal_connect(mSimpleContext, "commit",
+ G_CALLBACK(&IMContextWrapper::OnCommitCompositionCallback),
+ this);
+ g_signal_connect(mSimpleContext, "preedit_start",
+ G_CALLBACK(IMContextWrapper::OnStartCompositionCallback),
+ this);
+ g_signal_connect(mSimpleContext, "preedit_end",
+ G_CALLBACK(IMContextWrapper::OnEndCompositionCallback),
+ this);
+ }
+
+ // Dummy context
+ mDummyContext = gtk_im_multicontext_new();
+ gtk_im_context_set_client_window(mDummyContext, gdkWindow);
+}
+
+IMContextWrapper::~IMContextWrapper()
+{
+ if (this == sLastFocusedContext) {
+ sLastFocusedContext = nullptr;
+ }
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p ~IMContextWrapper()", this));
+}
+
+NS_IMETHODIMP
+IMContextWrapper::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification)
+{
+ switch (aNotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION:
+ case REQUEST_TO_CANCEL_COMPOSITION: {
+ nsWindow* window =
+ static_cast<nsWindow*>(aTextEventDispatcher->GetWidget());
+ return EndIMEComposition(window);
+ }
+ case NOTIFY_IME_OF_FOCUS:
+ OnFocusChangeInGecko(true);
+ return NS_OK;
+ case NOTIFY_IME_OF_BLUR:
+ OnFocusChangeInGecko(false);
+ return NS_OK;
+ case NOTIFY_IME_OF_POSITION_CHANGE:
+ OnLayoutChange();
+ return NS_OK;
+ case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
+ OnUpdateComposition();
+ return NS_OK;
+ case NOTIFY_IME_OF_SELECTION_CHANGE: {
+ nsWindow* window =
+ static_cast<nsWindow*>(aTextEventDispatcher->GetWidget());
+ OnSelectionChange(window, aNotification);
+ return NS_OK;
+ }
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+}
+
+NS_IMETHODIMP_(void)
+IMContextWrapper::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher)
+{
+ // XXX When input transaction is being stolen by add-on, what should we do?
+}
+
+NS_IMETHODIMP_(void)
+IMContextWrapper::WillDispatchKeyboardEvent(
+ TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress,
+ void* aData)
+{
+ KeymapWrapper::WillDispatchKeyboardEvent(aKeyboardEvent,
+ static_cast<GdkEventKey*>(aData));
+}
+
+TextEventDispatcher*
+IMContextWrapper::GetTextEventDispatcher()
+{
+ if (NS_WARN_IF(!mLastFocusedWindow)) {
+ return nullptr;
+ }
+ TextEventDispatcher* dispatcher =
+ mLastFocusedWindow->GetTextEventDispatcher();
+ // nsIWidget::GetTextEventDispatcher() shouldn't return nullptr.
+ MOZ_RELEASE_ASSERT(dispatcher);
+ return dispatcher;
+}
+
+nsIMEUpdatePreference
+IMContextWrapper::GetIMEUpdatePreference() const
+{
+ // While a plugin has focus, IMContextWrapper doesn't need any
+ // notifications.
+ if (mInputContext.mIMEState.mEnabled == IMEState::PLUGIN) {
+ return nsIMEUpdatePreference();
+ }
+
+ nsIMEUpdatePreference::Notifications notifications =
+ nsIMEUpdatePreference::NOTIFY_NOTHING;
+ // If it's not enabled, we don't need position change notification.
+ if (IsEnabled()) {
+ notifications |= nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE;
+ }
+ nsIMEUpdatePreference updatePreference(notifications);
+ return updatePreference;
+}
+
+void
+IMContextWrapper::OnDestroyWindow(nsWindow* aWindow)
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnDestroyWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, "
+ "mOwnerWindow=0x%p, mLastFocusedModule=0x%p",
+ this, aWindow, mLastFocusedWindow, mOwnerWindow, sLastFocusedContext));
+
+ NS_PRECONDITION(aWindow, "aWindow must not be null");
+
+ if (mLastFocusedWindow == aWindow) {
+ EndIMEComposition(aWindow);
+ if (mIsIMFocused) {
+ Blur();
+ }
+ mLastFocusedWindow = nullptr;
+ }
+
+ if (mOwnerWindow != aWindow) {
+ return;
+ }
+
+ if (sLastFocusedContext == this) {
+ sLastFocusedContext = nullptr;
+ }
+
+ /**
+ * NOTE:
+ * The given window is the owner of this, so, we must release the
+ * contexts now. But that might be referred from other nsWindows
+ * (they are children of this. But we don't know why there are the
+ * cases). So, we need to clear the pointers that refers to contexts
+ * and this if the other referrers are still alive. See bug 349727.
+ */
+ if (mContext) {
+ PrepareToDestroyContext(mContext);
+ gtk_im_context_set_client_window(mContext, nullptr);
+ g_object_unref(mContext);
+ mContext = nullptr;
+ }
+
+ if (mSimpleContext) {
+ gtk_im_context_set_client_window(mSimpleContext, nullptr);
+ g_object_unref(mSimpleContext);
+ mSimpleContext = nullptr;
+ }
+
+ if (mDummyContext) {
+ // mContext and mDummyContext have the same slaveType and signal_data
+ // so no need for another workaround_gtk_im_display_closed.
+ gtk_im_context_set_client_window(mDummyContext, nullptr);
+ g_object_unref(mDummyContext);
+ mDummyContext = nullptr;
+ }
+
+ if (NS_WARN_IF(mComposingContext)) {
+ g_object_unref(mComposingContext);
+ mComposingContext = nullptr;
+ }
+
+ mOwnerWindow = nullptr;
+ mLastFocusedWindow = nullptr;
+ mInputContext.mIMEState.mEnabled = IMEState::DISABLED;
+
+ 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)
+{
+#if (MOZ_WIDGET_GTK == 2)
+ GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT(aContext);
+ GtkIMContext *slave = multicontext->slave;
+#else
+ GtkIMContext *slave = nullptr; //TODO GTK3
+#endif
+ 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;
+ }
+}
+
+void
+IMContextWrapper::OnFocusWindow(nsWindow* aWindow)
+{
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnFocusWindow(aWindow=0x%p), mLastFocusedWindow=0x%p",
+ this, aWindow, mLastFocusedWindow));
+ mLastFocusedWindow = aWindow;
+ Focus();
+}
+
+void
+IMContextWrapper::OnBlurWindow(nsWindow* aWindow)
+{
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnBlurWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, "
+ "mIsIMFocused=%s",
+ this, aWindow, mLastFocusedWindow, ToChar(mIsIMFocused)));
+
+ if (!mIsIMFocused || mLastFocusedWindow != aWindow) {
+ return;
+ }
+
+ Blur();
+}
+
+bool
+IMContextWrapper::OnKeyEvent(nsWindow* aCaller,
+ GdkEventKey* aEvent,
+ bool aKeyDownEventWasSent /* = false */)
+{
+ NS_PRECONDITION(aEvent, "aEvent must be non-null");
+
+ if (!mInputContext.mIMEState.MaybeEditable() ||
+ MOZ_UNLIKELY(IsDestroyed())) {
+ return false;
+ }
+
+ 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),
+ GetCompositionStateName(), GetCurrentContext(), GetActiveContext(),
+ aEvent, GetEventType(aEvent), gdk_keyval_name(aEvent->keyval),
+ gdk_keyval_to_unicode(aEvent->keyval)));
+
+ if (aCaller != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnKeyEvent(), FAILED, the caller isn't focused "
+ "window, mLastFocusedWindow=0x%p",
+ this, mLastFocusedWindow));
+ return false;
+ }
+
+ // Even if old IM context has composition, key event should be sent to
+ // current context since the user expects so.
+ GtkIMContext* currentContext = GetCurrentContext();
+ if (MOZ_UNLIKELY(!currentContext)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnKeyEvent(), FAILED, there are no context",
+ this));
+ return false;
+ }
+
+ if (mSetCursorPositionOnKeyEvent) {
+ SetCursorPosition(currentContext);
+ mSetCursorPositionOnKeyEvent = false;
+ }
+
+ mKeyDownEventWasSent = aKeyDownEventWasSent;
+ mFilterKeyEvent = true;
+ 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;
+ }
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p OnKeyEvent(), succeeded, filterThisEvent=%s "
+ "(isFiltered=%s, mFilterKeyEvent=%s), mCompositionState=%s",
+ this, ToChar(filterThisEvent), ToChar(isFiltered),
+ ToChar(mFilterKeyEvent), GetCompositionStateName()));
+
+ return filterThisEvent;
+}
+
+void
+IMContextWrapper::OnFocusChangeInGecko(bool aFocus)
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnFocusChangeInGecko(aFocus=%s), "
+ "mCompositionState=%s, mIsIMFocused=%s",
+ this, ToChar(aFocus), GetCompositionStateName(),
+ ToChar(mIsIMFocused)));
+
+ // We shouldn't carry over the removed string to another editor.
+ mSelectedString.Truncate();
+ mSelection.Clear();
+}
+
+void
+IMContextWrapper::ResetIME()
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p ResetIME(), mCompositionState=%s, mIsIMFocused=%s",
+ this, GetCompositionStateName(), ToChar(mIsIMFocused)));
+
+ GtkIMContext* activeContext = GetActiveContext();
+ if (MOZ_UNLIKELY(!activeContext)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p ResetIME(), FAILED, there are no context",
+ this));
+ return;
+ }
+
+ RefPtr<IMContextWrapper> kungFuDeathGrip(this);
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+
+ mPendingResettingIMContext = false;
+ gtk_im_context_reset(activeContext);
+
+ // The last focused window might have been destroyed by a DOM event handler
+ // which was called by us during a call of gtk_im_context_reset().
+ if (!lastFocusedWindow ||
+ NS_WARN_IF(lastFocusedWindow != mLastFocusedWindow) ||
+ lastFocusedWindow->Destroyed()) {
+ return;
+ }
+
+ nsAutoString compositionString;
+ GetCompositionString(activeContext, compositionString);
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p ResetIME() called gtk_im_context_reset(), "
+ "activeContext=0x%p, mCompositionState=%s, compositionString=%s, "
+ "mIsIMFocused=%s",
+ this, activeContext, GetCompositionStateName(),
+ NS_ConvertUTF16toUTF8(compositionString).get(),
+ ToChar(mIsIMFocused)));
+
+ // XXX IIIMF (ATOK X3 which is one of the Language Engine of it is still
+ // used in Japan!) sends only "preedit_changed" signal with empty
+ // composition string synchronously. Therefore, if composition string
+ // is now empty string, we should assume that the IME won't send
+ // "commit" signal.
+ if (IsComposing() && compositionString.IsEmpty()) {
+ // WARNING: The widget might have been gone after this.
+ DispatchCompositionCommitEvent(activeContext, &EmptyString());
+ }
+}
+
+nsresult
+IMContextWrapper::EndIMEComposition(nsWindow* aCaller)
+{
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return NS_OK;
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p EndIMEComposition(aCaller=0x%p), "
+ "mCompositionState=%s",
+ this, aCaller, GetCompositionStateName()));
+
+ if (aCaller != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p EndIMEComposition(), FAILED, the caller isn't "
+ "focused window, mLastFocusedWindow=0x%p",
+ this, mLastFocusedWindow));
+ return NS_OK;
+ }
+
+ if (!IsComposing()) {
+ return NS_OK;
+ }
+
+ // Currently, GTK has API neither to commit nor to cancel composition
+ // forcibly. Therefore, TextComposition will recompute commit string for
+ // the request even if native IME will cause unexpected commit string.
+ // So, we don't need to emulate commit or cancel composition with
+ // proper composition events.
+ // XXX ResetIME() might not enough for finishing compositoin on some
+ // environments. We should emulate focus change too because some IMEs
+ // may commit or cancel composition at blur.
+ ResetIME();
+
+ return NS_OK;
+}
+
+void
+IMContextWrapper::OnLayoutChange()
+{
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ if (IsComposing()) {
+ SetCursorPosition(GetActiveContext());
+ } else {
+ // If not composing, candidate window position is updated before key
+ // down
+ mSetCursorPositionOnKeyEvent = true;
+ }
+ mLayoutChanged = true;
+}
+
+void
+IMContextWrapper::OnUpdateComposition()
+{
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ if (!IsComposing()) {
+ // Composition has been committed. So we need update selection for
+ // caret later
+ mSelection.Clear();
+ EnsureToCacheSelection();
+ mSetCursorPositionOnKeyEvent = true;
+ }
+
+ // If we've already set candidate window position, we don't need to update
+ // the position with update composition notification.
+ if (!mLayoutChanged) {
+ SetCursorPosition(GetActiveContext());
+ }
+}
+
+void
+IMContextWrapper::SetInputContext(nsWindow* aCaller,
+ const InputContext* aContext,
+ const InputContextAction* aAction)
+{
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p SetInputContext(aCaller=0x%p, aContext={ mIMEState={ "
+ "mEnabled=%s }, mHTMLInputType=%s })",
+ this, aCaller, GetEnabledStateName(aContext->mIMEState.mEnabled),
+ NS_ConvertUTF16toUTF8(aContext->mHTMLInputType).get()));
+
+ if (aCaller != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetInputContext(), FAILED, "
+ "the caller isn't focused window, mLastFocusedWindow=0x%p",
+ this, mLastFocusedWindow));
+ return;
+ }
+
+ if (!mContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetInputContext(), FAILED, "
+ "there are no context",
+ this));
+ return;
+ }
+
+
+ if (sLastFocusedContext != this) {
+ mInputContext = *aContext;
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p SetInputContext(), succeeded, "
+ "but we're not active",
+ this));
+ return;
+ }
+
+ bool changingEnabledState =
+ aContext->mIMEState.mEnabled != mInputContext.mIMEState.mEnabled ||
+ aContext->mHTMLInputType != mInputContext.mHTMLInputType;
+
+ // Release current IME focus if IME is enabled.
+ if (changingEnabledState && mInputContext.mIMEState.MaybeEditable()) {
+ EndIMEComposition(mLastFocusedWindow);
+ Blur();
+ }
+
+ mInputContext = *aContext;
+
+ if (changingEnabledState) {
+#if (MOZ_WIDGET_GTK == 3)
+ static bool sInputPurposeSupported = !gtk_check_version(3, 6, 0);
+ if (sInputPurposeSupported && mInputContext.mIMEState.MaybeEditable()) {
+ GtkIMContext* currentContext = GetCurrentContext();
+ if (currentContext) {
+ GtkInputPurpose purpose = GTK_INPUT_PURPOSE_FREE_FORM;
+ const nsString& inputType = mInputContext.mHTMLInputType;
+ // Password case has difficult issue. Desktop IMEs disable
+ // composition if input-purpose is password.
+ // For disabling IME on |ime-mode: disabled;|, we need to check
+ // mEnabled value instead of inputType value. This hack also
+ // enables composition on
+ // <input type="password" style="ime-mode: enabled;">.
+ // This is right behavior of ime-mode on desktop.
+ //
+ // On the other hand, IME for tablet devices may provide a
+ // specific software keyboard for password field. If so,
+ // the behavior might look strange on both:
+ // <input type="text" style="ime-mode: disabled;">
+ // <input type="password" style="ime-mode: enabled;">
+ //
+ // Temporarily, we should focus on desktop environment for now.
+ // I.e., let's ignore tablet devices for now. When somebody
+ // reports actual trouble on tablet devices, we should try to
+ // look for a way to solve actual problem.
+ if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
+ purpose = GTK_INPUT_PURPOSE_PASSWORD;
+ } else if (inputType.EqualsLiteral("email")) {
+ purpose = GTK_INPUT_PURPOSE_EMAIL;
+ } else if (inputType.EqualsLiteral("url")) {
+ purpose = GTK_INPUT_PURPOSE_URL;
+ } else if (inputType.EqualsLiteral("tel")) {
+ purpose = GTK_INPUT_PURPOSE_PHONE;
+ } else if (inputType.EqualsLiteral("number")) {
+ purpose = GTK_INPUT_PURPOSE_NUMBER;
+ }
+
+ g_object_set(currentContext, "input-purpose", purpose, nullptr);
+ }
+ }
+#endif // #if (MOZ_WIDGET_GTK == 3)
+
+ // Even when aState is not enabled state, we need to set IME focus.
+ // Because some IMs are updating the status bar of them at this time.
+ // Be aware, don't use aWindow here because this method shouldn't move
+ // focus actually.
+ Focus();
+
+ // XXX Should we call Blur() when it's not editable? E.g., it might be
+ // better to close VKB automatically.
+ }
+}
+
+InputContext
+IMContextWrapper::GetInputContext()
+{
+ mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
+ return mInputContext;
+}
+
+GtkIMContext*
+IMContextWrapper::GetCurrentContext() const
+{
+ if (IsEnabled()) {
+ return mContext;
+ }
+ if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
+ return mSimpleContext;
+ }
+ return mDummyContext;
+}
+
+bool
+IMContextWrapper::IsValidContext(GtkIMContext* aContext) const
+{
+ if (!aContext) {
+ return false;
+ }
+ return aContext == mContext ||
+ aContext == mSimpleContext ||
+ aContext == mDummyContext;
+}
+
+bool
+IMContextWrapper::IsEnabled() const
+{
+ return mInputContext.mIMEState.mEnabled == IMEState::ENABLED ||
+ mInputContext.mIMEState.mEnabled == IMEState::PLUGIN ||
+ (!sUseSimpleContext &&
+ mInputContext.mIMEState.mEnabled == IMEState::PASSWORD);
+}
+
+void
+IMContextWrapper::Focus()
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p Focus(), sLastFocusedContext=0x%p",
+ this, sLastFocusedContext));
+
+ if (mIsIMFocused) {
+ NS_ASSERTION(sLastFocusedContext == this,
+ "We're not active, but the IM was focused?");
+ return;
+ }
+
+ GtkIMContext* currentContext = GetCurrentContext();
+ if (!currentContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p Focus(), FAILED, there are no context",
+ this));
+ return;
+ }
+
+ if (sLastFocusedContext && sLastFocusedContext != this) {
+ sLastFocusedContext->Blur();
+ }
+
+ sLastFocusedContext = this;
+
+ gtk_im_context_focus_in(currentContext);
+ mIsIMFocused = true;
+ mSetCursorPositionOnKeyEvent = true;
+
+ if (!IsEnabled()) {
+ // We should release IME focus for uim and scim.
+ // These IMs are using snooper that is released at losing focus.
+ Blur();
+ }
+}
+
+void
+IMContextWrapper::Blur()
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p Blur(), mIsIMFocused=%s",
+ this, ToChar(mIsIMFocused)));
+
+ if (!mIsIMFocused) {
+ return;
+ }
+
+ GtkIMContext* currentContext = GetCurrentContext();
+ if (!currentContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p Blur(), FAILED, there are no context",
+ this));
+ return;
+ }
+
+ gtk_im_context_focus_out(currentContext);
+ mIsIMFocused = false;
+}
+
+void
+IMContextWrapper::OnSelectionChange(nsWindow* aCaller,
+ const IMENotification& aIMENotification)
+{
+ mSelection.Assign(aIMENotification);
+ bool retrievedSurroundingSignalReceived =
+ mRetrieveSurroundingSignalReceived;
+ mRetrieveSurroundingSignalReceived = false;
+
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ const IMENotification::SelectionChangeDataBase& selectionChangeData =
+ aIMENotification.mSelectionChangeData;
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnSelectionChange(aCaller=0x%p, aIMENotification={ "
+ "mSelectionChangeData={ mOffset=%u, Length()=%u, mReversed=%s, "
+ "mWritingMode=%s, mCausedByComposition=%s, "
+ "mCausedBySelectionEvent=%s, mOccurredDuringComposition=%s "
+ "} }), mCompositionState=%s, mIsDeletingSurrounding=%s, "
+ "mRetrieveSurroundingSignalReceived=%s",
+ this, aCaller, selectionChangeData.mOffset,
+ selectionChangeData.Length(),
+ ToChar(selectionChangeData.mReversed),
+ GetWritingModeName(selectionChangeData.GetWritingMode()).get(),
+ ToChar(selectionChangeData.mCausedByComposition),
+ ToChar(selectionChangeData.mCausedBySelectionEvent),
+ ToChar(selectionChangeData.mOccurredDuringComposition),
+ GetCompositionStateName(), ToChar(mIsDeletingSurrounding),
+ ToChar(retrievedSurroundingSignalReceived)));
+
+ if (aCaller != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnSelectionChange(), FAILED, "
+ "the caller isn't focused window, mLastFocusedWindow=0x%p",
+ this, mLastFocusedWindow));
+ return;
+ }
+
+ if (!IsComposing()) {
+ // Now we have no composition (mostly situation on calling this method)
+ // If we have it, it will set by
+ // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED.
+ mSetCursorPositionOnKeyEvent = true;
+ }
+
+ // The focused editor might have placeholder text with normal text node.
+ // In such case, the text node must be removed from a compositionstart
+ // event handler. So, we're dispatching eCompositionStart,
+ // we should ignore selection change notification.
+ if (mCompositionState == eCompositionState_CompositionStartDispatched) {
+ if (NS_WARN_IF(!mSelection.IsValid())) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnSelectionChange(), FAILED, "
+ "new offset is too large, cannot keep composing",
+ this));
+ } else {
+ // Modify the selection start offset with new offset.
+ mCompositionStart = mSelection.mOffset;
+ // XXX We should modify mSelectedString? But how?
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p OnSelectionChange(), ignored, mCompositionStart "
+ "is updated to %u, the selection change doesn't cause "
+ "resetting IM context",
+ this, mCompositionStart));
+ // And don't reset the IM context.
+ return;
+ }
+ // Otherwise, reset the IM context due to impossible to keep composing.
+ }
+
+ // If the selection change is caused by deleting surrounding text,
+ // we shouldn't need to notify IME of selection change.
+ if (mIsDeletingSurrounding) {
+ return;
+ }
+
+ bool occurredBeforeComposition =
+ IsComposing() && !selectionChangeData.mOccurredDuringComposition &&
+ !selectionChangeData.mCausedByComposition;
+ if (occurredBeforeComposition) {
+ mPendingResettingIMContext = true;
+ }
+
+ // When the selection change is caused by dispatching composition event,
+ // selection set event and/or occurred before starting current composition,
+ // we shouldn't notify IME of that and commit existing composition.
+ if (!selectionChangeData.mCausedByComposition &&
+ !selectionChangeData.mCausedBySelectionEvent &&
+ !occurredBeforeComposition) {
+ // Hack for ibus-pinyin. ibus-pinyin will synthesize a set of
+ // composition which commits with empty string after calling
+ // gtk_im_context_reset(). Therefore, selecting text causes
+ // unexpectedly removing it. For preventing it but not breaking the
+ // other IMEs which use surrounding text, we should call it only when
+ // surrounding text has been retrieved after last selection range was
+ // set. If it's not retrieved, that means that current IME doesn't
+ // have any content cache, so, it must not need the notification of
+ // selection change.
+ if (IsComposing() || retrievedSurroundingSignalReceived) {
+ ResetIME();
+ }
+ }
+}
+
+/* static */
+void
+IMContextWrapper::OnStartCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule)
+{
+ aModule->OnStartCompositionNative(aContext);
+}
+
+void
+IMContextWrapper::OnStartCompositionNative(GtkIMContext* aContext)
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnStartCompositionNative(aContext=0x%p), "
+ "current context=0x%p",
+ this, aContext, GetCurrentContext()));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ if (GetCurrentContext() != aContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnStartCompositionNative(), FAILED, "
+ "given context doesn't match",
+ this));
+ return;
+ }
+
+ mComposingContext = static_cast<GtkIMContext*>(g_object_ref(aContext));
+
+ if (!DispatchCompositionStart(aContext)) {
+ return;
+ }
+ mCompositionTargetRange.mOffset = mCompositionStart;
+ mCompositionTargetRange.mLength = 0;
+}
+
+/* static */
+void
+IMContextWrapper::OnEndCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule)
+{
+ aModule->OnEndCompositionNative(aContext);
+}
+
+void
+IMContextWrapper::OnEndCompositionNative(GtkIMContext* aContext)
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnEndCompositionNative(aContext=0x%p)",
+ this, aContext));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ // Note that if this is called after focus move, the context may different
+ // from any our owning context.
+ if (!IsValidContext(aContext)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnEndCompositionNative(), FAILED, "
+ "given context doesn't match with any context",
+ this));
+ return;
+ }
+
+ g_object_unref(mComposingContext);
+ mComposingContext = nullptr;
+
+ // If we already handled the commit event, we should do nothing here.
+ if (IsComposing()) {
+ if (!DispatchCompositionCommitEvent(aContext)) {
+ // If the widget is destroyed, we should do nothing anymore.
+ return;
+ }
+ }
+
+ if (mPendingResettingIMContext) {
+ ResetIME();
+ }
+}
+
+/* static */
+void
+IMContextWrapper::OnChangeCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule)
+{
+ aModule->OnChangeCompositionNative(aContext);
+}
+
+void
+IMContextWrapper::OnChangeCompositionNative(GtkIMContext* aContext)
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnChangeCompositionNative(aContext=0x%p)",
+ this, aContext));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ // Note that if this is called after focus move, the context may different
+ // from any our owning context.
+ if (!IsValidContext(aContext)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnChangeCompositionNative(), FAILED, "
+ "given context doesn't match with any context",
+ this));
+ return;
+ }
+
+ nsAutoString compositionString;
+ GetCompositionString(aContext, compositionString);
+ if (!IsComposing() && compositionString.IsEmpty()) {
+ mDispatchedCompositionString.Truncate();
+ return; // Don't start the composition with empty string.
+ }
+
+ // Be aware, widget can be gone
+ DispatchCompositionChangeEvent(aContext, compositionString);
+}
+
+/* static */
+gboolean
+IMContextWrapper::OnRetrieveSurroundingCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule)
+{
+ return aModule->OnRetrieveSurroundingNative(aContext);
+}
+
+gboolean
+IMContextWrapper::OnRetrieveSurroundingNative(GtkIMContext* aContext)
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnRetrieveSurroundingNative(aContext=0x%p), "
+ "current context=0x%p",
+ this, aContext, GetCurrentContext()));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ if (GetCurrentContext() != aContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnRetrieveSurroundingNative(), FAILED, "
+ "given context doesn't match",
+ this));
+ return FALSE;
+ }
+
+ nsAutoString uniStr;
+ uint32_t cursorPos;
+ if (NS_FAILED(GetCurrentParagraph(uniStr, cursorPos))) {
+ return FALSE;
+ }
+
+ NS_ConvertUTF16toUTF8 utf8Str(nsDependentSubstring(uniStr, 0, cursorPos));
+ uint32_t cursorPosInUTF8 = utf8Str.Length();
+ AppendUTF16toUTF8(nsDependentSubstring(uniStr, cursorPos), utf8Str);
+ gtk_im_context_set_surrounding(aContext, utf8Str.get(), utf8Str.Length(),
+ cursorPosInUTF8);
+ mRetrieveSurroundingSignalReceived = true;
+ return TRUE;
+}
+
+/* static */
+gboolean
+IMContextWrapper::OnDeleteSurroundingCallback(GtkIMContext* aContext,
+ gint aOffset,
+ gint aNChars,
+ IMContextWrapper* aModule)
+{
+ return aModule->OnDeleteSurroundingNative(aContext, aOffset, aNChars);
+}
+
+gboolean
+IMContextWrapper::OnDeleteSurroundingNative(GtkIMContext* aContext,
+ gint aOffset,
+ gint aNChars)
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnDeleteSurroundingNative(aContext=0x%p, aOffset=%d, "
+ "aNChar=%d), current context=0x%p",
+ this, aContext, aOffset, aNChars, GetCurrentContext()));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ if (GetCurrentContext() != aContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnDeleteSurroundingNative(), FAILED, "
+ "given context doesn't match",
+ this));
+ return FALSE;
+ }
+
+ AutoRestore<bool> saveDeletingSurrounding(mIsDeletingSurrounding);
+ mIsDeletingSurrounding = true;
+ if (NS_SUCCEEDED(DeleteText(aContext, aOffset, (uint32_t)aNChars))) {
+ return TRUE;
+ }
+
+ // failed
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnDeleteSurroundingNative(), FAILED, "
+ "cannot delete text",
+ this));
+ return FALSE;
+}
+
+/* static */
+void
+IMContextWrapper::OnCommitCompositionCallback(GtkIMContext* aContext,
+ const gchar* aString,
+ IMContextWrapper* aModule)
+{
+ aModule->OnCommitCompositionNative(aContext, aString);
+}
+
+void
+IMContextWrapper::OnCommitCompositionNative(GtkIMContext* aContext,
+ const gchar* aUTF8Char)
+{
+ const gchar emptyStr = 0;
+ const gchar *commitString = aUTF8Char ? aUTF8Char : &emptyStr;
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnCommitCompositionNative(aContext=0x%p), "
+ "current context=0x%p, active context=0x%p, commitString=\"%s\", "
+ "mProcessingKeyEvent=0x%p, IsComposingOn(aContext)=%s",
+ this, aContext, GetCurrentContext(), GetActiveContext(), commitString,
+ mProcessingKeyEvent, ToChar(IsComposingOn(aContext))));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ if (!IsValidContext(aContext)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnCommitCompositionNative(), FAILED, "
+ "given context doesn't match",
+ this));
+ return;
+ }
+
+ // If we are not in composition and committing with empty string,
+ // we need to do nothing because if we continued to handle this
+ // 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]) {
+ return;
+ }
+
+ // 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.
+ // 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 &&
+ aContext == GetCurrentContext()) {
+ char keyval_utf8[8]; /* should have at least 6 bytes of space */
+ gint keyval_utf8_len;
+ guint32 keyval_unicode;
+
+ keyval_unicode = gdk_keyval_to_unicode(mProcessingKeyEvent->keyval);
+ keyval_utf8_len = g_unichar_to_utf8(keyval_unicode, keyval_utf8);
+ keyval_utf8[keyval_utf8_len] = '\0';
+
+ if (!strcmp(commitString, keyval_utf8)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnCommitCompositionNative(), "
+ "we'll send normal key event",
+ this));
+ mFilterKeyEvent = false;
+ return;
+ }
+ }
+
+ NS_ConvertUTF8toUTF16 str(commitString);
+ // Be aware, widget can be gone
+ DispatchCompositionCommitEvent(aContext, &str);
+}
+
+void
+IMContextWrapper::GetCompositionString(GtkIMContext* aContext,
+ nsAString& aCompositionString)
+{
+ gchar *preedit_string;
+ gint cursor_pos;
+ PangoAttrList *feedback_list;
+ gtk_im_context_get_preedit_string(aContext, &preedit_string,
+ &feedback_list, &cursor_pos);
+ if (preedit_string && *preedit_string) {
+ CopyUTF8toUTF16(preedit_string, aCompositionString);
+ } else {
+ aCompositionString.Truncate();
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p GetCompositionString(aContext=0x%p), "
+ "aCompositionString=\"%s\"",
+ this, aContext, preedit_string));
+
+ pango_attr_list_unref(feedback_list);
+ g_free(preedit_string);
+}
+
+bool
+IMContextWrapper::DispatchCompositionStart(GtkIMContext* aContext)
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p DispatchCompositionStart(aContext=0x%p)",
+ this, aContext));
+
+ if (IsComposing()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, "
+ "we're already in composition",
+ this));
+ return true;
+ }
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, "
+ "there are no focused window in this module",
+ this));
+ return false;
+ }
+
+ if (NS_WARN_IF(!EnsureToCacheSelection())) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, "
+ "cannot query the selection offset",
+ this));
+ return false;
+ }
+
+ // Keep the last focused window alive
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+
+ // XXX The composition start point might be changed by composition events
+ // even though we strongly hope it doesn't happen.
+ // Every composition event should have the start offset for the result
+ // because it may high cost if we query the offset every time.
+ mCompositionStart = mSelection.mOffset;
+ mDispatchedCompositionString.Truncate();
+
+ if (mProcessingKeyEvent && !mKeyDownEventWasSent &&
+ mProcessingKeyEvent->type == GDK_KEY_PRESS) {
+ // 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(), FAILED, keydown event "
+ "is dispatched",
+ this));
+ if (lastFocusedWindow->IsDestroyed() ||
+ lastFocusedWindow != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, the focused "
+ "widget was destroyed/changed by keydown event",
+ this));
+ return false;
+ }
+ }
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, "
+ "due to BeginNativeInputTransaction() failure",
+ this));
+ return false;
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p DispatchCompositionStart(), dispatching "
+ "compositionstart... (mCompositionStart=%u)",
+ this, mCompositionStart));
+ mCompositionState = eCompositionState_CompositionStartDispatched;
+ nsEventStatus status;
+ dispatcher->StartComposition(status);
+ if (lastFocusedWindow->IsDestroyed() ||
+ lastFocusedWindow != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, the focused "
+ "widget was destroyed/changed by compositionstart event",
+ this));
+ return false;
+ }
+
+ return true;
+}
+
+bool
+IMContextWrapper::DispatchCompositionChangeEvent(
+ GtkIMContext* aContext,
+ const nsAString& aCompositionString)
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p DispatchCompositionChangeEvent(aContext=0x%p)",
+ this, aContext));
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "there are no focused window in this module",
+ this));
+ return false;
+ }
+
+ if (!IsComposing()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p DispatchCompositionChangeEvent(), the composition "
+ "wasn't started, force starting...",
+ this));
+ if (!DispatchCompositionStart(aContext)) {
+ return false;
+ }
+ }
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "due to BeginNativeInputTransaction() failure",
+ this));
+ return false;
+ }
+
+ // Store the selected string which will be removed by following
+ // compositionchange event.
+ if (mCompositionState == eCompositionState_CompositionStartDispatched) {
+ if (NS_WARN_IF(!EnsureToCacheSelection(&mSelectedString))) {
+ // XXX How should we behave in this case??
+ } else {
+ // XXX We should assume, for now, any web applications don't change
+ // selection at handling this compositionchange event.
+ mCompositionStart = mSelection.mOffset;
+ }
+ }
+
+ RefPtr<TextRangeArray> rangeArray =
+ CreateTextRangeArray(aContext, aCompositionString);
+
+ rv = dispatcher->SetPendingComposition(aCompositionString, rangeArray);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "due to SetPendingComposition() failure",
+ this));
+ return false;
+ }
+
+ mCompositionState = eCompositionState_CompositionChangeEventDispatched;
+
+ // We cannot call SetCursorPosition for e10s-aware.
+ // DispatchEvent is async on e10s, so composition rect isn't updated now
+ // on tab parent.
+ mDispatchedCompositionString = aCompositionString;
+ mLayoutChanged = false;
+ mCompositionTargetRange.mOffset =
+ mCompositionStart + rangeArray->TargetClauseOffset();
+ mCompositionTargetRange.mLength = rangeArray->TargetClauseLength();
+
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+ nsEventStatus status;
+ rv = dispatcher->FlushPendingComposition(status);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "due to FlushPendingComposition() failure",
+ this));
+ return false;
+ }
+
+ if (lastFocusedWindow->IsDestroyed() ||
+ lastFocusedWindow != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, the "
+ "focused widget was destroyed/changed by "
+ "compositionchange event",
+ this));
+ return false;
+ }
+ return true;
+}
+
+bool
+IMContextWrapper::DispatchCompositionCommitEvent(
+ GtkIMContext* aContext,
+ const nsAString* aCommitString)
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p DispatchCompositionCommitEvent(aContext=0x%p, "
+ "aCommitString=0x%p, (\"%s\"))",
+ this, aContext, aCommitString,
+ aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : ""));
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionCommitEvent(), FAILED, "
+ "there are no focused window in this module",
+ this));
+ return false;
+ }
+
+ if (!IsComposing()) {
+ if (!aCommitString || aCommitString->IsEmpty()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionCommitEvent(), FAILED, "
+ "there is no composition and empty commit string",
+ this));
+ return true;
+ }
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p DispatchCompositionCommitEvent(), "
+ "the composition wasn't started, force starting...",
+ this));
+ if (!DispatchCompositionStart(aContext)) {
+ return false;
+ }
+ }
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionCommitEvent(), FAILED, "
+ "due to BeginNativeInputTransaction() failure",
+ this));
+ return false;
+ }
+
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+
+ mCompositionState = eCompositionState_NotComposing;
+ mCompositionStart = UINT32_MAX;
+ mCompositionTargetRange.Clear();
+ mDispatchedCompositionString.Truncate();
+
+ nsEventStatus status;
+ rv = dispatcher->CommitComposition(status, aCommitString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "due to CommitComposition() failure",
+ this));
+ return false;
+ }
+
+ if (lastFocusedWindow->IsDestroyed() ||
+ lastFocusedWindow != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionCommitEvent(), FAILED, "
+ "the focused widget was destroyed/changed by "
+ "compositioncommit event",
+ this));
+ return false;
+ }
+
+ return true;
+}
+
+already_AddRefed<TextRangeArray>
+IMContextWrapper::CreateTextRangeArray(GtkIMContext* aContext,
+ const nsAString& aCompositionString)
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p CreateTextRangeArray(aContext=0x%p, "
+ "aCompositionString=\"%s\" (Length()=%u))",
+ this, aContext, NS_ConvertUTF16toUTF8(aCompositionString).get(),
+ aCompositionString.Length()));
+
+ RefPtr<TextRangeArray> textRangeArray = new TextRangeArray();
+
+ gchar *preedit_string;
+ gint cursor_pos_in_chars;
+ PangoAttrList *feedback_list;
+ gtk_im_context_get_preedit_string(aContext, &preedit_string,
+ &feedback_list, &cursor_pos_in_chars);
+ if (!preedit_string || !*preedit_string) {
+ if (!aCompositionString.IsEmpty()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p CreateTextRangeArray(), FAILED, due to "
+ "preedit_string is null",
+ this));
+ }
+ pango_attr_list_unref(feedback_list);
+ g_free(preedit_string);
+ return textRangeArray.forget();
+ }
+
+ // Convert caret offset from offset in characters to offset in UTF-16
+ // string. If we couldn't proper offset in UTF-16 string, we should
+ // assume that the caret is at the end of the composition string.
+ uint32_t caretOffsetInUTF16 = aCompositionString.Length();
+ if (NS_WARN_IF(cursor_pos_in_chars < 0)) {
+ // Note that this case is undocumented. We should assume that the
+ // caret is at the end of the composition string.
+ } else if (cursor_pos_in_chars == 0) {
+ caretOffsetInUTF16 = 0;
+ } else {
+ gchar* charAfterCaret =
+ g_utf8_offset_to_pointer(preedit_string, cursor_pos_in_chars);
+ if (NS_WARN_IF(!charAfterCaret)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p CreateTextRangeArray(), failed to get UTF-8 "
+ "string before the caret (cursor_pos_in_chars=%d)",
+ this, cursor_pos_in_chars));
+ } else {
+ glong caretOffset = 0;
+ gunichar2* utf16StrBeforeCaret =
+ g_utf8_to_utf16(preedit_string, charAfterCaret - preedit_string,
+ nullptr, &caretOffset, nullptr);
+ if (NS_WARN_IF(!utf16StrBeforeCaret) ||
+ NS_WARN_IF(caretOffset < 0)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p CreateTextRangeArray(), WARNING, failed to "
+ "convert to UTF-16 string before the caret "
+ "(cursor_pos_in_chars=%d, caretOffset=%d)",
+ this, cursor_pos_in_chars, caretOffset));
+ } else {
+ caretOffsetInUTF16 = static_cast<uint32_t>(caretOffset);
+ uint32_t compositionStringLength = aCompositionString.Length();
+ if (NS_WARN_IF(caretOffsetInUTF16 > compositionStringLength)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p CreateTextRangeArray(), WARNING, "
+ "caretOffsetInUTF16=%u is larger than "
+ "compositionStringLength=%u",
+ this, caretOffsetInUTF16, compositionStringLength));
+ caretOffsetInUTF16 = compositionStringLength;
+ }
+ }
+ if (utf16StrBeforeCaret) {
+ g_free(utf16StrBeforeCaret);
+ }
+ }
+ }
+
+ PangoAttrIterator* iter;
+ iter = pango_attr_list_get_iterator(feedback_list);
+ if (!iter) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p CreateTextRangeArray(), FAILED, iterator couldn't "
+ "be allocated",
+ this));
+ pango_attr_list_unref(feedback_list);
+ g_free(preedit_string);
+ return textRangeArray.forget();
+ }
+
+ uint32_t minOffsetOfClauses = aCompositionString.Length();
+ do {
+ TextRange range;
+ if (!SetTextRange(iter, preedit_string, caretOffsetInUTF16, range)) {
+ continue;
+ }
+ MOZ_ASSERT(range.Length());
+ minOffsetOfClauses = std::min(minOffsetOfClauses, range.mStartOffset);
+ textRangeArray->AppendElement(range);
+ } while (pango_attr_iterator_next(iter));
+
+ // If the IME doesn't define clause from the start of the composition,
+ // we should insert dummy clause information since TextRangeArray assumes
+ // that there must be a clause whose start is 0 when there is one or
+ // more clauses.
+ if (minOffsetOfClauses) {
+ TextRange dummyClause;
+ dummyClause.mStartOffset = 0;
+ dummyClause.mEndOffset = minOffsetOfClauses;
+ dummyClause.mRangeType = TextRangeType::eRawClause;
+ textRangeArray->InsertElementAt(0, dummyClause);
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p CreateTextRangeArray(), inserting a dummy clause "
+ "at the beginning of the composition string mStartOffset=%u, "
+ "mEndOffset=%u, mRangeType=%s",
+ this, dummyClause.mStartOffset, dummyClause.mEndOffset,
+ ToChar(dummyClause.mRangeType)));
+ }
+
+ TextRange range;
+ range.mStartOffset = range.mEndOffset = caretOffsetInUTF16;
+ range.mRangeType = TextRangeType::eCaret;
+ textRangeArray->AppendElement(range);
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p CreateTextRangeArray(), mStartOffset=%u, "
+ "mEndOffset=%u, mRangeType=%s",
+ this, range.mStartOffset, range.mEndOffset,
+ ToChar(range.mRangeType)));
+
+ pango_attr_iterator_destroy(iter);
+ pango_attr_list_unref(feedback_list);
+ g_free(preedit_string);
+
+ return textRangeArray.forget();
+}
+
+/* static */
+nscolor
+IMContextWrapper::ToNscolor(PangoAttrColor* aPangoAttrColor)
+{
+ PangoColor& pangoColor = aPangoAttrColor->color;
+ uint8_t r = pangoColor.red / 0x100;
+ uint8_t g = pangoColor.green / 0x100;
+ uint8_t b = pangoColor.blue / 0x100;
+ return NS_RGB(r, g, b);
+}
+
+bool
+IMContextWrapper::SetTextRange(PangoAttrIterator* aPangoAttrIter,
+ const gchar* aUTF8CompositionString,
+ uint32_t aUTF16CaretOffset,
+ TextRange& aTextRange) const
+{
+ // Set the range offsets in UTF-16 string.
+ gint utf8ClauseStart, utf8ClauseEnd;
+ pango_attr_iterator_range(aPangoAttrIter, &utf8ClauseStart, &utf8ClauseEnd);
+ if (utf8ClauseStart == utf8ClauseEnd) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetTextRange(), FAILED, due to collapsed range",
+ this));
+ return false;
+ }
+
+ if (!utf8ClauseStart) {
+ aTextRange.mStartOffset = 0;
+ } else {
+ glong utf16PreviousClausesLength;
+ gunichar2* utf16PreviousClausesString =
+ g_utf8_to_utf16(aUTF8CompositionString, utf8ClauseStart, nullptr,
+ &utf16PreviousClausesLength, nullptr);
+
+ if (NS_WARN_IF(!utf16PreviousClausesString)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetTextRange(), FAILED, due to g_utf8_to_utf16() "
+ "failure (retrieving previous string of current clause)",
+ this));
+ return false;
+ }
+
+ aTextRange.mStartOffset = utf16PreviousClausesLength;
+ g_free(utf16PreviousClausesString);
+ }
+
+ glong utf16CurrentClauseLength;
+ gunichar2* utf16CurrentClauseString =
+ g_utf8_to_utf16(aUTF8CompositionString + utf8ClauseStart,
+ utf8ClauseEnd - utf8ClauseStart,
+ nullptr, &utf16CurrentClauseLength, nullptr);
+
+ if (NS_WARN_IF(!utf16CurrentClauseString)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetTextRange(), FAILED, due to g_utf8_to_utf16() "
+ "failure (retrieving current clause)",
+ this));
+ return false;
+ }
+
+ // iBus Chewing IME tells us that there is an empty clause at the end of
+ // the composition string but we should ignore it since our code doesn't
+ // assume that there is an empty clause.
+ if (!utf16CurrentClauseLength) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p SetTextRange(), FAILED, due to current clause length "
+ "is 0",
+ this));
+ return false;
+ }
+
+ aTextRange.mEndOffset = aTextRange.mStartOffset + utf16CurrentClauseLength;
+ g_free(utf16CurrentClauseString);
+ utf16CurrentClauseString = nullptr;
+
+ // Set styles
+ TextRangeStyle& style = aTextRange.mRangeStyle;
+
+ // Underline
+ PangoAttrInt* attrUnderline =
+ reinterpret_cast<PangoAttrInt*>(
+ pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_UNDERLINE));
+ if (attrUnderline) {
+ switch (attrUnderline->value) {
+ case PANGO_UNDERLINE_NONE:
+ style.mLineStyle = TextRangeStyle::LINESTYLE_NONE;
+ break;
+ case PANGO_UNDERLINE_DOUBLE:
+ style.mLineStyle = TextRangeStyle::LINESTYLE_DOUBLE;
+ break;
+ case PANGO_UNDERLINE_ERROR:
+ style.mLineStyle = TextRangeStyle::LINESTYLE_WAVY;
+ break;
+ case PANGO_UNDERLINE_SINGLE:
+ case PANGO_UNDERLINE_LOW:
+ style.mLineStyle = TextRangeStyle::LINESTYLE_SOLID;
+ break;
+ default:
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p SetTextRange(), retrieved unknown underline "
+ "style: %d",
+ this, attrUnderline->value));
+ style.mLineStyle = TextRangeStyle::LINESTYLE_SOLID;
+ break;
+ }
+ style.mDefinedStyles |= TextRangeStyle::DEFINED_LINESTYLE;
+
+ // Underline color
+ PangoAttrColor* attrUnderlineColor =
+ reinterpret_cast<PangoAttrColor*>(
+ pango_attr_iterator_get(aPangoAttrIter,
+ PANGO_ATTR_UNDERLINE_COLOR));
+ if (attrUnderlineColor) {
+ style.mUnderlineColor = ToNscolor(attrUnderlineColor);
+ style.mDefinedStyles |= TextRangeStyle::DEFINED_UNDERLINE_COLOR;
+ }
+ } else {
+ style.mLineStyle = TextRangeStyle::LINESTYLE_NONE;
+ style.mDefinedStyles |= TextRangeStyle::DEFINED_LINESTYLE;
+ }
+
+ // Don't set colors if they are not specified. They should be computed by
+ // textframe if only one of the colors are specified.
+
+ // Foreground color (text color)
+ PangoAttrColor* attrForeground =
+ reinterpret_cast<PangoAttrColor*>(
+ pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_FOREGROUND));
+ if (attrForeground) {
+ style.mForegroundColor = ToNscolor(attrForeground);
+ style.mDefinedStyles |= TextRangeStyle::DEFINED_FOREGROUND_COLOR;
+ }
+
+ // Background color
+ PangoAttrColor* attrBackground =
+ reinterpret_cast<PangoAttrColor*>(
+ pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_BACKGROUND));
+ if (attrBackground) {
+ style.mBackgroundColor = ToNscolor(attrBackground);
+ style.mDefinedStyles |= TextRangeStyle::DEFINED_BACKGROUND_COLOR;
+ }
+
+ /**
+ * We need to judge the meaning of the clause for a11y. Before we support
+ * IME specific composition string style, we used following rules:
+ *
+ * 1: If attrUnderline and attrForground are specified, we assumed the
+ * clause is TextRangeType::eSelectedClause.
+ * 2: If only attrUnderline is specified, we assumed the clause is
+ * TextRangeType::eConvertedClause.
+ * 3: If only attrForground is specified, we assumed the clause is
+ * TextRangeType::eSelectedRawClause.
+ * 4: If neither attrUnderline nor attrForeground is specified, we assumed
+ * the clause is TextRangeType::eRawClause.
+ *
+ * However, this rules are odd since there can be two or more selected
+ * clauses. Additionally, our old rules caused that IME developers/users
+ * cannot specify composition string style as they want.
+ *
+ * So, we shouldn't guess the meaning from its visual style.
+ */
+
+ if (!attrUnderline && !attrForeground && !attrBackground) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p SetTextRange(), FAILED, due to no attr, "
+ "aTextRange= { mStartOffset=%u, mEndOffset=%u }",
+ this, aTextRange.mStartOffset, aTextRange.mEndOffset));
+ return false;
+ }
+
+ // If the range covers whole of composition string and the caret is at
+ // the end of the composition string, the range is probably not converted.
+ if (!utf8ClauseStart &&
+ utf8ClauseEnd == static_cast<gint>(strlen(aUTF8CompositionString)) &&
+ aTextRange.mEndOffset == aUTF16CaretOffset) {
+ aTextRange.mRangeType = TextRangeType::eRawClause;
+ }
+ // Typically, the caret is set at the start of the selected clause.
+ // So, if the caret is in the clause, we can assume that the clause is
+ // selected.
+ else if (aTextRange.mStartOffset <= aUTF16CaretOffset &&
+ aTextRange.mEndOffset > aUTF16CaretOffset) {
+ aTextRange.mRangeType = TextRangeType::eSelectedClause;
+ }
+ // Otherwise, we should assume that the clause is converted but not
+ // selected.
+ else {
+ aTextRange.mRangeType = TextRangeType::eConvertedClause;
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p SetTextRange(), succeeded, aTextRange= { "
+ "mStartOffset=%u, mEndOffset=%u, mRangeType=%s, mRangeStyle=%s }",
+ this, aTextRange.mStartOffset, aTextRange.mEndOffset,
+ ToChar(aTextRange.mRangeType),
+ GetTextRangeStyleText(aTextRange.mRangeStyle).get()));
+
+ return true;
+}
+
+void
+IMContextWrapper::SetCursorPosition(GtkIMContext* aContext)
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p SetCursorPosition(aContext=0x%p), "
+ "mCompositionTargetRange={ mOffset=%u, mLength=%u }"
+ "mSelection={ mOffset=%u, mLength=%u, mWritingMode=%s }",
+ this, aContext, mCompositionTargetRange.mOffset,
+ mCompositionTargetRange.mLength,
+ mSelection.mOffset, mSelection.mLength,
+ GetWritingModeName(mSelection.mWritingMode).get()));
+
+ bool useCaret = false;
+ if (!mCompositionTargetRange.IsValid()) {
+ if (!mSelection.IsValid()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetCursorPosition(), FAILED, "
+ "mCompositionTargetRange and mSelection are invalid",
+ this));
+ return;
+ }
+ useCaret = true;
+ }
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetCursorPosition(), FAILED, due to no focused "
+ "window",
+ this));
+ return;
+ }
+
+ if (MOZ_UNLIKELY(!aContext)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetCursorPosition(), FAILED, due to no context",
+ this));
+ return;
+ }
+
+ WidgetQueryContentEvent charRect(true,
+ useCaret ? eQueryCaretRect :
+ eQueryTextRect,
+ mLastFocusedWindow);
+ if (useCaret) {
+ charRect.InitForQueryCaretRect(mSelection.mOffset);
+ } else {
+ if (mSelection.mWritingMode.IsVertical()) {
+ // For preventing the candidate window to overlap the target
+ // clause, we should set fake (typically, very tall) caret rect.
+ uint32_t length = mCompositionTargetRange.mLength ?
+ mCompositionTargetRange.mLength : 1;
+ charRect.InitForQueryTextRect(mCompositionTargetRange.mOffset,
+ length);
+ } else {
+ charRect.InitForQueryTextRect(mCompositionTargetRange.mOffset, 1);
+ }
+ }
+ InitEvent(charRect);
+ nsEventStatus status;
+ mLastFocusedWindow->DispatchEvent(&charRect, status);
+ if (!charRect.mSucceeded) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetCursorPosition(), FAILED, %s was failed",
+ this, useCaret ? "eQueryCaretRect" : "eQueryTextRect"));
+ return;
+ }
+
+ nsWindow* rootWindow =
+ static_cast<nsWindow*>(mLastFocusedWindow->GetTopLevelWidget());
+
+ // Get the position of the rootWindow in screen.
+ LayoutDeviceIntPoint root = rootWindow->WidgetToScreenOffset();
+
+ // Get the position of IM context owner window in screen.
+ LayoutDeviceIntPoint owner = mOwnerWindow->WidgetToScreenOffset();
+
+ // Compute the caret position in the IM owner window.
+ LayoutDeviceIntRect rect = charRect.mReply.mRect + root - owner;
+ rect.width = 0;
+ GdkRectangle area = rootWindow->DevicePixelsToGdkRectRoundOut(rect);
+
+ gtk_im_context_set_cursor_location(aContext, &area);
+}
+
+nsresult
+IMContextWrapper::GetCurrentParagraph(nsAString& aText,
+ uint32_t& aCursorPos)
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p GetCurrentParagraph(), mCompositionState=%s",
+ this, GetCompositionStateName()));
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p GetCurrentParagraph(), FAILED, there are no "
+ "focused window in this module",
+ this));
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsEventStatus status;
+
+ uint32_t selOffset = mCompositionStart;
+ uint32_t selLength = mSelectedString.Length();
+
+ // If focused editor doesn't have composition string, we should use
+ // current selection.
+ if (!EditorHasCompositionString()) {
+ // Query cursor position & selection
+ if (NS_WARN_IF(!EnsureToCacheSelection())) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p GetCurrentParagraph(), FAILED, due to no "
+ "valid selection information",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ selOffset = mSelection.mOffset;
+ selLength = mSelection.mLength;
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p GetCurrentParagraph(), selOffset=%u, selLength=%u",
+ this, selOffset, selLength));
+
+ // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
+ // we cannot support this request when the current offset is larger
+ // than INT32_MAX.
+ if (selOffset > INT32_MAX || selLength > INT32_MAX ||
+ selOffset + selLength > INT32_MAX) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p GetCurrentParagraph(), FAILED, The selection is "
+ "out of range",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get all text contents of the focused editor
+ WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
+ mLastFocusedWindow);
+ queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
+ mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
+ NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE);
+
+ nsAutoString textContent(queryTextContentEvent.mReply.mString);
+ if (selOffset + selLength > textContent.Length()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p GetCurrentParagraph(), FAILED, The selection is "
+ "invalid, textContent.Length()=%u",
+ this, textContent.Length()));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Remove composing string and restore the selected string because
+ // GtkEntry doesn't remove selected string until committing, however,
+ // our editor does it. We should emulate the behavior for IME.
+ if (EditorHasCompositionString() &&
+ mDispatchedCompositionString != mSelectedString) {
+ textContent.Replace(mCompositionStart,
+ mDispatchedCompositionString.Length(), mSelectedString);
+ }
+
+ // Get only the focused paragraph, by looking for newlines
+ int32_t parStart = (selOffset == 0) ? 0 :
+ textContent.RFind("\n", false, selOffset - 1, -1) + 1;
+ int32_t parEnd = textContent.Find("\n", false, selOffset + selLength, -1);
+ if (parEnd < 0) {
+ parEnd = textContent.Length();
+ }
+ aText = nsDependentSubstring(textContent, parStart, parEnd - parStart);
+ aCursorPos = selOffset - uint32_t(parStart);
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p GetCurrentParagraph(), succeeded, aText=%s, "
+ "aText.Length()=%u, aCursorPos=%u",
+ this, NS_ConvertUTF16toUTF8(aText).get(),
+ aText.Length(), aCursorPos));
+
+ return NS_OK;
+}
+
+nsresult
+IMContextWrapper::DeleteText(GtkIMContext* aContext,
+ int32_t aOffset,
+ uint32_t aNChars)
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p DeleteText(aContext=0x%p, aOffset=%d, aNChars=%u), "
+ "mCompositionState=%s",
+ this, aContext, aOffset, aNChars, GetCompositionStateName()));
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, there are no focused window "
+ "in this module",
+ this));
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (!aNChars) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, aNChars must not be zero",
+ this));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+ nsEventStatus status;
+
+ // First, we should cancel current composition because editor cannot
+ // handle changing selection and deleting text.
+ uint32_t selOffset;
+ bool wasComposing = IsComposing();
+ bool editorHadCompositionString = EditorHasCompositionString();
+ if (wasComposing) {
+ selOffset = mCompositionStart;
+ if (!DispatchCompositionCommitEvent(aContext, &mSelectedString)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, quitting from DeletText",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ if (NS_WARN_IF(!EnsureToCacheSelection())) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, due to no valid selection "
+ "information",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+ selOffset = mSelection.mOffset;
+ }
+
+ // Get all text contents of the focused editor
+ WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
+ mLastFocusedWindow);
+ queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
+ mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
+ NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE);
+ if (queryTextContentEvent.mReply.mString.IsEmpty()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, there is no contents",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ConvertUTF16toUTF8 utf8Str(
+ nsDependentSubstring(queryTextContentEvent.mReply.mString,
+ 0, selOffset));
+ glong offsetInUTF8Characters =
+ g_utf8_strlen(utf8Str.get(), utf8Str.Length()) + aOffset;
+ if (offsetInUTF8Characters < 0) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, aOffset is too small for "
+ "current cursor pos (computed offset: %d)",
+ this, offsetInUTF8Characters));
+ return NS_ERROR_FAILURE;
+ }
+
+ AppendUTF16toUTF8(
+ nsDependentSubstring(queryTextContentEvent.mReply.mString, selOffset),
+ utf8Str);
+ glong countOfCharactersInUTF8 =
+ g_utf8_strlen(utf8Str.get(), utf8Str.Length());
+ glong endInUTF8Characters =
+ offsetInUTF8Characters + aNChars;
+ if (countOfCharactersInUTF8 < endInUTF8Characters) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, aNChars is too large for "
+ "current contents (content length: %d, computed end offset: %d)",
+ this, countOfCharactersInUTF8, endInUTF8Characters));
+ return NS_ERROR_FAILURE;
+ }
+
+ gchar* charAtOffset =
+ g_utf8_offset_to_pointer(utf8Str.get(), offsetInUTF8Characters);
+ gchar* charAtEnd =
+ g_utf8_offset_to_pointer(utf8Str.get(), endInUTF8Characters);
+
+ // Set selection to delete
+ WidgetSelectionEvent selectionEvent(true, eSetSelection,
+ mLastFocusedWindow);
+
+ nsDependentCSubstring utf8StrBeforeOffset(utf8Str, 0,
+ charAtOffset - utf8Str.get());
+ selectionEvent.mOffset =
+ NS_ConvertUTF8toUTF16(utf8StrBeforeOffset).Length();
+
+ nsDependentCSubstring utf8DeletingStr(utf8Str,
+ utf8StrBeforeOffset.Length(),
+ charAtEnd - charAtOffset);
+ selectionEvent.mLength =
+ NS_ConvertUTF8toUTF16(utf8DeletingStr).Length();
+
+ selectionEvent.mReversed = false;
+ selectionEvent.mExpandToClusterBoundary = false;
+ lastFocusedWindow->DispatchEvent(&selectionEvent, status);
+
+ if (!selectionEvent.mSucceeded ||
+ lastFocusedWindow != mLastFocusedWindow ||
+ lastFocusedWindow->Destroyed()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, setting selection caused "
+ "focus change or window destroyed",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Delete the selection
+ WidgetContentCommandEvent contentCommandEvent(true, eContentCommandDelete,
+ mLastFocusedWindow);
+ mLastFocusedWindow->DispatchEvent(&contentCommandEvent, status);
+
+ if (!contentCommandEvent.mSucceeded ||
+ lastFocusedWindow != mLastFocusedWindow ||
+ lastFocusedWindow->Destroyed()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, deleting the selection caused "
+ "focus change or window destroyed",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!wasComposing) {
+ return NS_OK;
+ }
+
+ // Restore the composition at new caret position.
+ if (!DispatchCompositionStart(aContext)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, resterting composition start",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!editorHadCompositionString) {
+ return NS_OK;
+ }
+
+ nsAutoString compositionString;
+ GetCompositionString(aContext, compositionString);
+ if (!DispatchCompositionChangeEvent(aContext, compositionString)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, restoring composition string",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void
+IMContextWrapper::InitEvent(WidgetGUIEvent& aEvent)
+{
+ aEvent.mTime = PR_Now() / 1000;
+}
+
+bool
+IMContextWrapper::EnsureToCacheSelection(nsAString* aSelectedString)
+{
+ if (aSelectedString) {
+ aSelectedString->Truncate();
+ }
+
+ if (mSelection.IsValid() &&
+ (!mSelection.Collapsed() || !aSelectedString)) {
+ return true;
+ }
+
+ if (NS_WARN_IF(!mLastFocusedWindow)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p EnsureToCacheSelection(), FAILED, due to "
+ "no focused window",
+ this));
+ return false;
+ }
+
+ nsEventStatus status;
+ WidgetQueryContentEvent selection(true, eQuerySelectedText,
+ mLastFocusedWindow);
+ InitEvent(selection);
+ mLastFocusedWindow->DispatchEvent(&selection, status);
+ if (NS_WARN_IF(!selection.mSucceeded)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p EnsureToCacheSelection(), FAILED, due to "
+ "failure of query selection event",
+ this));
+ return false;
+ }
+
+ mSelection.Assign(selection);
+ if (!mSelection.IsValid()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p EnsureToCacheSelection(), FAILED, due to "
+ "failure of query selection event (invalid result)",
+ this));
+ return false;
+ }
+
+ if (!mSelection.Collapsed() && aSelectedString) {
+ aSelectedString->Assign(selection.mReply.mString);
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p EnsureToCacheSelection(), Succeeded, mSelection="
+ "{ mOffset=%u, mLength=%u, mWritingMode=%s }",
+ this, mSelection.mOffset, mSelection.mLength,
+ GetWritingModeName(mSelection.mWritingMode).get()));
+ return true;
+}
+
+/******************************************************************************
+ * IMContextWrapper::Selection
+ ******************************************************************************/
+
+void
+IMContextWrapper::Selection::Assign(const IMENotification& aIMENotification)
+{
+ MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_SELECTION_CHANGE);
+ mOffset = aIMENotification.mSelectionChangeData.mOffset;
+ mLength = aIMENotification.mSelectionChangeData.Length();
+ mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
+}
+
+void
+IMContextWrapper::Selection::Assign(const WidgetQueryContentEvent& aEvent)
+{
+ MOZ_ASSERT(aEvent.mMessage == eQuerySelectedText);
+ MOZ_ASSERT(aEvent.mSucceeded);
+ mOffset = aEvent.mReply.mOffset;
+ mLength = aEvent.mReply.mString.Length();
+ mWritingMode = aEvent.GetWritingMode();
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/IMContextWrapper.h b/widget/gtk/IMContextWrapper.h
new file mode 100644
index 000000000..e869c160a
--- /dev/null
+++ b/widget/gtk/IMContextWrapper.h
@@ -0,0 +1,481 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 IMContextWrapper_h_
+#define IMContextWrapper_h_
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsIWidget.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TextEventDispatcherListener.h"
+#include "WritingModes.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+class IMContextWrapper final : public TextEventDispatcherListener
+{
+public:
+ // TextEventDispatcherListener implementation
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) override;
+ NS_IMETHOD_(void) OnRemovedFrom(
+ TextEventDispatcher* aTextEventDispatcher) override;
+ NS_IMETHOD_(void) WillDispatchKeyboardEvent(
+ TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress,
+ void* aData) override;
+
+public:
+ // aOwnerWindow is a pointer of the owner window. When aOwnerWindow is
+ // destroyed, the related IME contexts are released (i.e., IME cannot be
+ // used with the instance after that).
+ explicit IMContextWrapper(nsWindow* aOwnerWindow);
+
+ // "Enabled" means the users can use all IMEs.
+ // I.e., the focus is in the normal editors.
+ bool IsEnabled() const;
+
+ nsIMEUpdatePreference GetIMEUpdatePreference() const;
+
+ // OnFocusWindow is a notification that aWindow is going to be focused.
+ void OnFocusWindow(nsWindow* aWindow);
+ // OnBlurWindow is a notification that aWindow is going to be unfocused.
+ void OnBlurWindow(nsWindow* aWindow);
+ // OnDestroyWindow is a notification that aWindow is going to be destroyed.
+ void OnDestroyWindow(nsWindow* aWindow);
+ // OnFocusChangeInGecko is a notification that an editor gets focus.
+ void OnFocusChangeInGecko(bool aFocus);
+ // OnSelectionChange is a notification that selection (caret) is changed
+ // in the focused editor.
+ void OnSelectionChange(nsWindow* aCaller,
+ const IMENotification& aIMENotification);
+
+ // 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.
+ bool OnKeyEvent(nsWindow* aWindow, GdkEventKey* aEvent,
+ bool aKeyDownEventWasSent = false);
+
+ // IME related nsIWidget methods.
+ nsresult EndIMEComposition(nsWindow* aCaller);
+ void SetInputContext(nsWindow* aCaller,
+ const InputContext* aContext,
+ const InputContextAction* aAction);
+ InputContext GetInputContext();
+ void OnUpdateComposition();
+ void OnLayoutChange();
+
+ TextEventDispatcher* GetTextEventDispatcher();
+
+protected:
+ ~IMContextWrapper();
+
+ // Owner of an instance of this class. This should be top level window.
+ // The owner window must release the contexts when it's destroyed because
+ // the IME contexts need the native window. If OnDestroyWindow() is called
+ // with the owner window, it'll release IME contexts. Otherwise, it'll
+ // just clean up any existing composition if it's related to the destroying
+ // child window.
+ nsWindow* mOwnerWindow;
+
+ // A last focused window in this class's context.
+ nsWindow* mLastFocusedWindow;
+
+ // Actual context. This is used for handling the user's input.
+ GtkIMContext* mContext;
+
+ // mSimpleContext is used for the password field and
+ // the |ime-mode: disabled;| editors if sUseSimpleContext is true.
+ // These editors disable IME. But dead keys should work. Fortunately,
+ // the simple IM context of GTK2 support only them.
+ GtkIMContext* mSimpleContext;
+
+ // mDummyContext is a dummy context and will be used in Focus()
+ // when the state of mEnabled means disabled. This context's IME state is
+ // always "closed", so it closes IME forcedly.
+ GtkIMContext* mDummyContext;
+
+ // mComposingContext is not nullptr while one of mContext, mSimpleContext
+ // and mDummyContext has composition.
+ // XXX: We don't assume that two or more context have composition same time.
+ GtkIMContext* mComposingContext;
+
+ // IME enabled state and other things defined in InputContext.
+ // Use following helper methods if you don't need the detail of the status.
+ InputContext mInputContext;
+
+ // mCompositionStart is the start offset of the composition string in the
+ // current content. When <textarea> or <input> have focus, it means offset
+ // from the first character of them. When a HTML editor has focus, it
+ // means offset from the first character of the root element of the editor.
+ uint32_t mCompositionStart;
+
+ // mDispatchedCompositionString is the latest composition string which
+ // was dispatched by compositionupdate event.
+ nsString mDispatchedCompositionString;
+
+ // mSelectedString is the selected string which was removed by first
+ // compositionchange event.
+ nsString mSelectedString;
+
+ // OnKeyEvent() temporarily sets mProcessingKeyEvent to the given native
+ // event.
+ GdkEventKey* mProcessingKeyEvent;
+
+ struct Range
+ {
+ uint32_t mOffset;
+ uint32_t mLength;
+
+ Range()
+ : mOffset(UINT32_MAX)
+ , mLength(UINT32_MAX)
+ {
+ }
+
+ bool IsValid() const { return mOffset != UINT32_MAX; }
+ void Clear()
+ {
+ mOffset = UINT32_MAX;
+ mLength = UINT32_MAX;
+ }
+ };
+
+ // current target offset and length of IME composition
+ Range mCompositionTargetRange;
+
+ // mCompositionState indicates current status of composition.
+ enum eCompositionState {
+ eCompositionState_NotComposing,
+ eCompositionState_CompositionStartDispatched,
+ eCompositionState_CompositionChangeEventDispatched
+ };
+ eCompositionState mCompositionState;
+
+ bool IsComposing() const
+ {
+ return (mCompositionState != eCompositionState_NotComposing);
+ }
+
+ bool IsComposingOn(GtkIMContext* aContext) const
+ {
+ return IsComposing() && mComposingContext == aContext;
+ }
+
+ bool IsComposingOnCurrentContext() const
+ {
+ return IsComposingOn(GetCurrentContext());
+ }
+
+ bool EditorHasCompositionString()
+ {
+ return (mCompositionState ==
+ eCompositionState_CompositionChangeEventDispatched);
+ }
+
+ /**
+ * Checks if aContext is valid context for handling composition.
+ *
+ * @param aContext An IM context which is specified by native
+ * composition events.
+ * @return true if the context is valid context for
+ * handling composition. Otherwise, false.
+ */
+ bool IsValidContext(GtkIMContext* aContext) const;
+
+ const char* GetCompositionStateName()
+ {
+ switch (mCompositionState) {
+ case eCompositionState_NotComposing:
+ return "NotComposing";
+ case eCompositionState_CompositionStartDispatched:
+ return "CompositionStartDispatched";
+ case eCompositionState_CompositionChangeEventDispatched:
+ return "CompositionChangeEventDispatched";
+ default:
+ return "InvaildState";
+ }
+ }
+
+ struct Selection final
+ {
+ uint32_t mOffset;
+ uint32_t mLength;
+ WritingMode mWritingMode;
+
+ Selection()
+ : mOffset(UINT32_MAX)
+ , mLength(UINT32_MAX)
+ {
+ }
+
+ void Clear()
+ {
+ mOffset = UINT32_MAX;
+ mLength = UINT32_MAX;
+ mWritingMode = WritingMode();
+ }
+
+ void Assign(const IMENotification& aIMENotification);
+ void Assign(const WidgetQueryContentEvent& aSelectedTextEvent);
+
+ bool IsValid() const { return mOffset != UINT32_MAX; }
+ bool Collapsed() const { return !mLength; }
+ uint32_t EndOffset() const
+ {
+ if (NS_WARN_IF(!IsValid())) {
+ return UINT32_MAX;
+ }
+ CheckedInt<uint32_t> endOffset =
+ CheckedInt<uint32_t>(mOffset) + mLength;
+ if (NS_WARN_IF(!endOffset.isValid())) {
+ return UINT32_MAX;
+ }
+ return endOffset.value();
+ }
+ } mSelection;
+ bool EnsureToCacheSelection(nsAString* aSelectedString = nullptr);
+
+ // 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;
+ // mIsDeletingSurrounding is true while OnDeleteSurroundingNative() is
+ // trying to delete the surrounding text.
+ bool mIsDeletingSurrounding;
+ // mLayoutChanged is true after OnLayoutChange() is called. This is reset
+ // when eCompositionChange is being dispatched.
+ bool mLayoutChanged;
+ // mSetCursorPositionOnKeyEvent true when caret rect or position is updated
+ // with no composition. If true, we update candidate window position
+ // before key down
+ bool mSetCursorPositionOnKeyEvent;
+ // mPendingResettingIMContext becomes true if selection change notification
+ // is received during composition but the selection change occurred before
+ // starting the composition. In such case, we cannot notify IME of
+ // selection change during composition because we don't want to commit
+ // the composition in such case. However, we should notify IME of the
+ // selection change after the composition is committed.
+ bool mPendingResettingIMContext;
+ // mRetrieveSurroundingSignalReceived is true after "retrieve_surrounding"
+ // signal is received until selection is changed in Gecko.
+ bool mRetrieveSurroundingSignalReceived;
+
+ // sLastFocusedContext is a pointer to the last focused instance of this
+ // class. When a instance is destroyed and sLastFocusedContext refers it,
+ // this is cleared. So, this refers valid pointer always.
+ static IMContextWrapper* sLastFocusedContext;
+
+ // sUseSimpleContext indeicates if password editors and editors with
+ // |ime-mode: disabled;| should use GtkIMContextSimple.
+ // If true, they use GtkIMContextSimple. Otherwise, not.
+ static bool sUseSimpleContext;
+
+ // Callback methods for native IME events. These methods should call
+ // the related instance methods simply.
+ static gboolean OnRetrieveSurroundingCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule);
+ static gboolean OnDeleteSurroundingCallback(GtkIMContext* aContext,
+ gint aOffset,
+ gint aNChars,
+ IMContextWrapper* aModule);
+ static void OnCommitCompositionCallback(GtkIMContext* aContext,
+ const gchar* aString,
+ IMContextWrapper* aModule);
+ static void OnChangeCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule);
+ static void OnStartCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule);
+ static void OnEndCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule);
+
+ // The instance methods for the native IME events.
+ gboolean OnRetrieveSurroundingNative(GtkIMContext* aContext);
+ gboolean OnDeleteSurroundingNative(GtkIMContext* aContext,
+ gint aOffset,
+ gint aNChars);
+ void OnCommitCompositionNative(GtkIMContext* aContext,
+ const gchar* aString);
+ void OnChangeCompositionNative(GtkIMContext* aContext);
+ void OnStartCompositionNative(GtkIMContext* aContext);
+ void OnEndCompositionNative(GtkIMContext* aContext);
+
+ /**
+ * GetCurrentContext() returns current IM context which is chosen with the
+ * enabled state.
+ * WARNING:
+ * When this class receives some signals for a composition after focus
+ * is moved in Gecko, the result of this may be different from given
+ * context by the signals.
+ */
+ GtkIMContext* GetCurrentContext() const;
+
+ /**
+ * GetActiveContext() returns a composing context or current context.
+ */
+ GtkIMContext* GetActiveContext() const
+ {
+ return mComposingContext ? mComposingContext : GetCurrentContext();
+ }
+
+ // If the owner window and IM context have been destroyed, returns TRUE.
+ bool IsDestroyed() { return !mOwnerWindow; }
+
+ // Sets focus to the instance of this class.
+ void Focus();
+
+ // Steals focus from the instance of this class.
+ void Blur();
+
+ // Initializes the instance.
+ void Init();
+
+ // Reset the current composition of IME. All native composition events
+ // during this processing are ignored.
+ void ResetIME();
+
+ // Gets the current composition string by the native APIs.
+ void GetCompositionString(GtkIMContext* aContext,
+ nsAString& aCompositionString);
+
+ /**
+ * Generates our text range array from current composition string.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @param aCompositionString The data to be dispatched with
+ * compositionchange event.
+ */
+ already_AddRefed<TextRangeArray>
+ CreateTextRangeArray(GtkIMContext* aContext,
+ const nsAString& aCompositionString);
+
+ /**
+ * SetTextRange() initializes aTextRange with aPangoAttrIter.
+ *
+ * @param aPangoAttrIter An iter which represents a clause of the
+ * composition string.
+ * @param aUTF8CompositionString The whole composition string (UTF-8).
+ * @param aUTF16CaretOffset The caret offset in the composition
+ * string encoded as UTF-16.
+ * @param aTextRange The result.
+ * @return true if this initializes aTextRange.
+ * Otherwise, false.
+ */
+ bool SetTextRange(PangoAttrIterator* aPangoAttrIter,
+ const gchar* aUTF8CompositionString,
+ uint32_t aUTF16CaretOffset,
+ TextRange& aTextRange) const;
+
+ /**
+ * ToNscolor() converts the PangoColor in aPangoAttrColor to nscolor.
+ */
+ static nscolor ToNscolor(PangoAttrColor* aPangoAttrColor);
+
+ /**
+ * Move the candidate window with "fake" cursor position.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ */
+ void SetCursorPosition(GtkIMContext* aContext);
+
+ // Queries the current selection offset of the window.
+ uint32_t GetSelectionOffset(nsWindow* aWindow);
+
+ // Get current paragraph text content and cursor position
+ nsresult GetCurrentParagraph(nsAString& aText, uint32_t& aCursorPos);
+
+ /**
+ * Delete text portion
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @param aOffset Start offset of the range to delete.
+ * @param aNChars Count of characters to delete. It depends
+ * on |g_utf8_strlen()| what is one character.
+ */
+ nsresult DeleteText(GtkIMContext* aContext,
+ int32_t aOffset,
+ uint32_t aNChars);
+
+ // Initializes the GUI event.
+ void InitEvent(WidgetGUIEvent& aEvent);
+
+ // Called before destroying the context to work around some platform bugs.
+ void PrepareToDestroyContext(GtkIMContext* aContext);
+
+ /**
+ * WARNING:
+ * 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.
+ * - DispatchCompositionStart
+ * - DispatchCompositionChangeEvent
+ * - DispatchCompositionCommitEvent
+ */
+
+ /**
+ * Dispatches a composition start event.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @return true if the focused widget is neither
+ * destroyed nor changed. Otherwise, false.
+ */
+ bool DispatchCompositionStart(GtkIMContext* aContext);
+
+ /**
+ * Dispatches a compositionchange event.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @param aCompositionString New composition string.
+ * @return true if the focused widget is neither
+ * destroyed nor changed. Otherwise, false.
+ */
+ bool DispatchCompositionChangeEvent(GtkIMContext* aContext,
+ const nsAString& aCompositionString);
+
+ /**
+ * Dispatches a compositioncommit event or compositioncommitasis event.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @param aCommitString If this is nullptr, the composition will
+ * be committed with last dispatched data.
+ * Otherwise, the composition will be
+ * committed with this value.
+ * @return true if the focused widget is neither
+ * destroyed nor changed. Otherwise, false.
+ */
+ bool DispatchCompositionCommitEvent(
+ GtkIMContext* aContext,
+ const nsAString* aCommitString = nullptr);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef IMContextWrapper_h_
diff --git a/widget/gtk/InProcessX11CompositorWidget.cpp b/widget/gtk/InProcessX11CompositorWidget.cpp
new file mode 100644
index 000000000..9580b3150
--- /dev/null
+++ b/widget/gtk/InProcessX11CompositorWidget.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "InProcessX11CompositorWidget.h"
+
+#include "nsWindow.h"
+
+namespace mozilla {
+namespace widget {
+
+/* static */ RefPtr<CompositorWidget>
+CompositorWidget::CreateLocal(const CompositorWidgetInitData& aInitData, nsIWidget* aWidget)
+{
+ return new InProcessX11CompositorWidget(aInitData, static_cast<nsWindow*>(aWidget));
+}
+
+InProcessX11CompositorWidget::InProcessX11CompositorWidget(const CompositorWidgetInitData& aInitData,
+ nsWindow* aWindow)
+ : X11CompositorWidget(aInitData, aWindow)
+{
+}
+
+void
+InProcessX11CompositorWidget::ObserveVsync(VsyncObserver* aObserver)
+{
+ if (RefPtr<CompositorVsyncDispatcher> cvd = mWidget->GetCompositorVsyncDispatcher()) {
+ cvd->SetCompositorVsyncObserver(aObserver);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/InProcessX11CompositorWidget.h b/widget/gtk/InProcessX11CompositorWidget.h
new file mode 100644
index 000000000..7f4077451
--- /dev/null
+++ b/widget/gtk/InProcessX11CompositorWidget.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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_InProcessX11CompositorWidgetParent_h
+#define widget_gtk_InProcessX11CompositorWidgetParent_h
+
+#include "X11CompositorWidget.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+class InProcessX11CompositorWidget final : public X11CompositorWidget
+{
+public:
+ InProcessX11CompositorWidget(const CompositorWidgetInitData& aInitData,
+ nsWindow* aWindow);
+
+ // CompositorWidgetDelegate
+
+ void ObserveVsync(VsyncObserver* aObserver) override;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_gtk_InProcessX11CompositorWidgetParent_h
diff --git a/widget/gtk/NativeKeyBindings.cpp b/widget/gtk/NativeKeyBindings.cpp
new file mode 100644
index 000000000..55a3508e2
--- /dev/null
+++ b/widget/gtk/NativeKeyBindings.cpp
@@ -0,0 +1,374 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/TextEvents.h"
+
+#include "NativeKeyBindings.h"
+#include "nsString.h"
+#include "nsMemory.h"
+#include "nsGtkKeyUtils.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdk.h>
+
+namespace mozilla {
+namespace widget {
+
+static nsIWidget::DoCommandCallback gCurrentCallback;
+static void *gCurrentCallbackData;
+static bool gHandled;
+
+// Common GtkEntry and GtkTextView signals
+static void
+copy_clipboard_cb(GtkWidget *w, gpointer user_data)
+{
+ gCurrentCallback(CommandCopy, gCurrentCallbackData);
+ g_signal_stop_emission_by_name(w, "copy_clipboard");
+ gHandled = true;
+}
+
+static void
+cut_clipboard_cb(GtkWidget *w, gpointer user_data)
+{
+ gCurrentCallback(CommandCut, gCurrentCallbackData);
+ g_signal_stop_emission_by_name(w, "cut_clipboard");
+ gHandled = true;
+}
+
+// GTK distinguishes between display lines (wrapped, as they appear on the
+// screen) and paragraphs, which are runs of text terminated by a newline.
+// We don't have this distinction, so we always use editor's notion of
+// lines, which are newline-terminated.
+
+static const Command sDeleteCommands[][2] = {
+ // backward, forward
+ { CommandDeleteCharBackward, CommandDeleteCharForward }, // CHARS
+ { CommandDeleteWordBackward, CommandDeleteWordForward }, // WORD_ENDS
+ { CommandDeleteWordBackward, CommandDeleteWordForward }, // WORDS
+ { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // LINES
+ { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // LINE_ENDS
+ { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // PARAGRAPH_ENDS
+ { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // PARAGRAPHS
+ // This deletes from the end of the previous word to the beginning of the
+ // next word, but only if the caret is not in a word.
+ // XXX need to implement in editor
+ { CommandDoNothing, CommandDoNothing } // WHITESPACE
+};
+
+static void
+delete_from_cursor_cb(GtkWidget *w, GtkDeleteType del_type,
+ gint count, gpointer user_data)
+{
+ g_signal_stop_emission_by_name(w, "delete_from_cursor");
+ bool forward = count > 0;
+
+#if (MOZ_WIDGET_GTK == 3)
+ // Ignore GTK's Ctrl-K keybinding introduced in GTK 3.14 and removed in
+ // 3.18 if the user has custom bindings set. See bug 1176929.
+ if (del_type == GTK_DELETE_PARAGRAPH_ENDS && forward && GTK_IS_ENTRY(w) &&
+ !gtk_check_version(3, 14, 1) && gtk_check_version(3, 17, 9)) {
+ GtkStyleContext* context = gtk_widget_get_style_context(w);
+ GtkStateFlags flags = gtk_widget_get_state_flags(w);
+
+ GPtrArray* array;
+ gtk_style_context_get(context, flags, "gtk-key-bindings", &array, nullptr);
+ if (!array)
+ return;
+ g_ptr_array_unref(array);
+ }
+#endif
+
+ gHandled = true;
+ if (uint32_t(del_type) >= ArrayLength(sDeleteCommands)) {
+ // unsupported deletion type
+ return;
+ }
+
+ if (del_type == GTK_DELETE_WORDS) {
+ // This works like word_ends, except we first move the caret to the
+ // beginning/end of the current word.
+ if (forward) {
+ gCurrentCallback(CommandWordNext, gCurrentCallbackData);
+ gCurrentCallback(CommandWordPrevious, gCurrentCallbackData);
+ } else {
+ gCurrentCallback(CommandWordPrevious, gCurrentCallbackData);
+ gCurrentCallback(CommandWordNext, gCurrentCallbackData);
+ }
+ } else if (del_type == GTK_DELETE_DISPLAY_LINES ||
+ del_type == GTK_DELETE_PARAGRAPHS) {
+
+ // This works like display_line_ends, except we first move the caret to the
+ // beginning/end of the current line.
+ if (forward) {
+ gCurrentCallback(CommandBeginLine, gCurrentCallbackData);
+ } else {
+ gCurrentCallback(CommandEndLine, gCurrentCallbackData);
+ }
+ }
+
+ Command command = sDeleteCommands[del_type][forward];
+ if (!command) {
+ return; // unsupported command
+ }
+
+ unsigned int absCount = Abs(count);
+ for (unsigned int i = 0; i < absCount; ++i) {
+ gCurrentCallback(command, gCurrentCallbackData);
+ }
+}
+
+static const Command sMoveCommands[][2][2] = {
+ // non-extend { backward, forward }, extend { backward, forward }
+ // GTK differentiates between logical position, which is prev/next,
+ // and visual position, which is always left/right.
+ // We should fix this to work the same way for RTL text input.
+ { // LOGICAL_POSITIONS
+ { CommandCharPrevious, CommandCharNext },
+ { CommandSelectCharPrevious, CommandSelectCharNext }
+ },
+ { // VISUAL_POSITIONS
+ { CommandCharPrevious, CommandCharNext },
+ { CommandSelectCharPrevious, CommandSelectCharNext }
+ },
+ { // WORDS
+ { CommandWordPrevious, CommandWordNext },
+ { CommandSelectWordPrevious, CommandSelectWordNext }
+ },
+ { // DISPLAY_LINES
+ { CommandLinePrevious, CommandLineNext },
+ { CommandSelectLinePrevious, CommandSelectLineNext }
+ },
+ { // DISPLAY_LINE_ENDS
+ { CommandBeginLine, CommandEndLine },
+ { CommandSelectBeginLine, CommandSelectEndLine }
+ },
+ { // PARAGRAPHS
+ { CommandLinePrevious, CommandLineNext },
+ { CommandSelectLinePrevious, CommandSelectLineNext }
+ },
+ { // PARAGRAPH_ENDS
+ { CommandBeginLine, CommandEndLine },
+ { CommandSelectBeginLine, CommandSelectEndLine }
+ },
+ { // PAGES
+ { CommandMovePageUp, CommandMovePageDown },
+ { CommandSelectPageUp, CommandSelectPageDown }
+ },
+ { // BUFFER_ENDS
+ { CommandMoveTop, CommandMoveBottom },
+ { CommandSelectTop, CommandSelectBottom }
+ },
+ { // HORIZONTAL_PAGES (unsupported)
+ { CommandDoNothing, CommandDoNothing },
+ { CommandDoNothing, CommandDoNothing }
+ }
+};
+
+static void
+move_cursor_cb(GtkWidget *w, GtkMovementStep step, gint count,
+ gboolean extend_selection, gpointer user_data)
+{
+ g_signal_stop_emission_by_name(w, "move_cursor");
+ gHandled = true;
+ bool forward = count > 0;
+ if (uint32_t(step) >= ArrayLength(sMoveCommands)) {
+ // unsupported movement type
+ return;
+ }
+
+ Command command = sMoveCommands[step][extend_selection][forward];
+ if (!command) {
+ return; // unsupported command
+ }
+
+ unsigned int absCount = Abs(count);
+ for (unsigned int i = 0; i < absCount; ++i) {
+ gCurrentCallback(command, gCurrentCallbackData);
+ }
+}
+
+static void
+paste_clipboard_cb(GtkWidget *w, gpointer user_data)
+{
+ gCurrentCallback(CommandPaste, gCurrentCallbackData);
+ g_signal_stop_emission_by_name(w, "paste_clipboard");
+ gHandled = true;
+}
+
+// GtkTextView-only signals
+static void
+select_all_cb(GtkWidget *w, gboolean select, gpointer user_data)
+{
+ gCurrentCallback(CommandSelectAll, gCurrentCallbackData);
+ g_signal_stop_emission_by_name(w, "select_all");
+ gHandled = true;
+}
+
+NativeKeyBindings* NativeKeyBindings::sInstanceForSingleLineEditor = nullptr;
+NativeKeyBindings* NativeKeyBindings::sInstanceForMultiLineEditor = nullptr;
+
+// static
+NativeKeyBindings*
+NativeKeyBindings::GetInstance(NativeKeyBindingsType aType)
+{
+ switch (aType) {
+ case nsIWidget::NativeKeyBindingsForSingleLineEditor:
+ if (!sInstanceForSingleLineEditor) {
+ sInstanceForSingleLineEditor = new NativeKeyBindings();
+ sInstanceForSingleLineEditor->Init(aType);
+ }
+ return sInstanceForSingleLineEditor;
+
+ default:
+ // fallback to multiline editor case in release build
+ MOZ_FALLTHROUGH_ASSERT("aType is invalid or not yet implemented");
+ case nsIWidget::NativeKeyBindingsForMultiLineEditor:
+ case nsIWidget::NativeKeyBindingsForRichTextEditor:
+ if (!sInstanceForMultiLineEditor) {
+ sInstanceForMultiLineEditor = new NativeKeyBindings();
+ sInstanceForMultiLineEditor->Init(aType);
+ }
+ return sInstanceForMultiLineEditor;
+ }
+}
+
+// static
+void
+NativeKeyBindings::Shutdown()
+{
+ delete sInstanceForSingleLineEditor;
+ sInstanceForSingleLineEditor = nullptr;
+ delete sInstanceForMultiLineEditor;
+ sInstanceForMultiLineEditor = nullptr;
+}
+
+void
+NativeKeyBindings::Init(NativeKeyBindingsType aType)
+{
+ switch (aType) {
+ case nsIWidget::NativeKeyBindingsForSingleLineEditor:
+ mNativeTarget = gtk_entry_new();
+ break;
+ default:
+ mNativeTarget = gtk_text_view_new();
+ if (gtk_major_version > 2 ||
+ (gtk_major_version == 2 && (gtk_minor_version > 2 ||
+ (gtk_minor_version == 2 &&
+ gtk_micro_version >= 2)))) {
+ // select_all only exists in gtk >= 2.2.2. Prior to that,
+ // ctrl+a is bound to (move to beginning, select to end).
+ g_signal_connect(mNativeTarget, "select_all",
+ G_CALLBACK(select_all_cb), this);
+ }
+ break;
+ }
+
+ g_object_ref_sink(mNativeTarget);
+
+ g_signal_connect(mNativeTarget, "copy_clipboard",
+ G_CALLBACK(copy_clipboard_cb), this);
+ g_signal_connect(mNativeTarget, "cut_clipboard",
+ G_CALLBACK(cut_clipboard_cb), this);
+ g_signal_connect(mNativeTarget, "delete_from_cursor",
+ G_CALLBACK(delete_from_cursor_cb), this);
+ g_signal_connect(mNativeTarget, "move_cursor",
+ G_CALLBACK(move_cursor_cb), this);
+ g_signal_connect(mNativeTarget, "paste_clipboard",
+ G_CALLBACK(paste_clipboard_cb), this);
+}
+
+NativeKeyBindings::~NativeKeyBindings()
+{
+ gtk_widget_destroy(mNativeTarget);
+ g_object_unref(mNativeTarget);
+}
+
+bool
+NativeKeyBindings::Execute(const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData)
+{
+ // If the native key event is set, it must be synthesized for tests.
+ // We just ignore such events because this behavior depends on system
+ // settings.
+ if (!aEvent.mNativeKeyEvent) {
+ // It must be synthesized event or dispatched DOM event from chrome.
+ return false;
+ }
+
+ guint keyval;
+
+ if (aEvent.mCharCode) {
+ keyval = gdk_unicode_to_keyval(aEvent.mCharCode);
+ } else {
+ keyval =
+ static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)->keyval;
+ }
+
+ if (ExecuteInternal(aEvent, aCallback, aCallbackData, keyval)) {
+ return true;
+ }
+
+ for (uint32_t i = 0; i < aEvent.mAlternativeCharCodes.Length(); ++i) {
+ uint32_t ch = aEvent.IsShift() ?
+ aEvent.mAlternativeCharCodes[i].mShiftedCharCode :
+ aEvent.mAlternativeCharCodes[i].mUnshiftedCharCode;
+ if (ch && ch != aEvent.mCharCode) {
+ keyval = gdk_unicode_to_keyval(ch);
+ if (ExecuteInternal(aEvent, aCallback, aCallbackData, keyval)) {
+ return true;
+ }
+ }
+ }
+
+/*
+gtk_bindings_activate_event is preferable, but it has unresolved bug:
+http://bugzilla.gnome.org/show_bug.cgi?id=162726
+The bug was already marked as FIXED. However, somebody reports that the
+bug still exists.
+Also gtk_bindings_activate may work with some non-shortcuts operations
+(todo: check it). See bug 411005 and bug 406407.
+
+Code, which should be used after fixing GNOME bug 162726:
+
+ gtk_bindings_activate_event(GTK_OBJECT(mNativeTarget),
+ static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent));
+*/
+
+ return false;
+}
+
+bool
+NativeKeyBindings::ExecuteInternal(const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData,
+ guint aKeyval)
+{
+ guint modifiers =
+ static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)->state;
+
+ gCurrentCallback = aCallback;
+ gCurrentCallbackData = aCallbackData;
+
+ gHandled = false;
+#if (MOZ_WIDGET_GTK == 2)
+ gtk_bindings_activate(GTK_OBJECT(mNativeTarget),
+ aKeyval, GdkModifierType(modifiers));
+#else
+ gtk_bindings_activate(G_OBJECT(mNativeTarget),
+ aKeyval, GdkModifierType(modifiers));
+#endif
+
+ gCurrentCallback = nullptr;
+ gCurrentCallbackData = nullptr;
+
+ return gHandled;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/NativeKeyBindings.h b/widget/gtk/NativeKeyBindings.h
new file mode 100644
index 000000000..f1632f9c5
--- /dev/null
+++ b/widget/gtk/NativeKeyBindings.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 mozilla_widget_NativeKeyBindings_h_
+#define mozilla_widget_NativeKeyBindings_h_
+
+#include <gtk/gtk.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "nsIWidget.h"
+
+namespace mozilla {
+namespace widget {
+
+class NativeKeyBindings final
+{
+ typedef nsIWidget::NativeKeyBindingsType NativeKeyBindingsType;
+ typedef nsIWidget::DoCommandCallback DoCommandCallback;
+
+public:
+ static NativeKeyBindings* GetInstance(NativeKeyBindingsType aType);
+ static void Shutdown();
+
+ void Init(NativeKeyBindingsType aType);
+
+ bool Execute(const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData);
+
+private:
+ ~NativeKeyBindings();
+
+ bool ExecuteInternal(const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData,
+ guint aKeyval);
+
+ GtkWidget* mNativeTarget;
+
+ static NativeKeyBindings* sInstanceForSingleLineEditor;
+ static NativeKeyBindings* sInstanceForMultiLineEditor;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_NativeKeyBindings_h_
diff --git a/widget/gtk/PCompositorWidget.ipdl b/widget/gtk/PCompositorWidget.ipdl
new file mode 100644
index 000000000..178fe78e4
--- /dev/null
+++ b/widget/gtk/PCompositorWidget.ipdl
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+include protocol PCompositorBridge;
+
+using mozilla::LayoutDeviceIntSize from "Units.h";
+
+namespace mozilla {
+namespace widget {
+
+sync protocol PCompositorWidget
+{
+ manager PCompositorBridge;
+
+parent:
+ async __delete__();
+
+ async NotifyClientSizeChanged(LayoutDeviceIntSize aClientSize);
+
+child:
+
+ async ObserveVsync();
+ async UnobserveVsync();
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/PlatformWidgetTypes.ipdlh b/widget/gtk/PlatformWidgetTypes.ipdlh
new file mode 100644
index 000000000..145d39546
--- /dev/null
+++ b/widget/gtk/PlatformWidgetTypes.ipdlh
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+// This file is a stub, for platforms that do not yet support out-of-process
+// compositing or do not need specialized types to do so.
+
+using mozilla::LayoutDeviceIntSize from "Units.h";
+
+namespace mozilla {
+namespace widget {
+
+struct CompositorWidgetInitData
+{
+ uintptr_t XWindow;
+ nsCString XDisplayString;
+
+ LayoutDeviceIntSize InitialClientSize;
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/WakeLockListener.cpp b/widget/gtk/WakeLockListener.cpp
new file mode 100644
index 000000000..54bad17ff
--- /dev/null
+++ b/widget/gtk/WakeLockListener.cpp
@@ -0,0 +1,365 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#ifdef MOZ_ENABLE_DBUS
+
+#include "WakeLockListener.h"
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include "mozilla/ipc/DBusMessageRefPtr.h"
+#include "mozilla/ipc/DBusPendingCallRefPtr.h"
+
+#define FREEDESKTOP_SCREENSAVER_TARGET "org.freedesktop.ScreenSaver"
+#define FREEDESKTOP_SCREENSAVER_OBJECT "/ScreenSaver"
+#define FREEDESKTOP_SCREENSAVER_INTERFACE "org.freedesktop.ScreenSaver"
+
+#define SESSION_MANAGER_TARGET "org.gnome.SessionManager"
+#define SESSION_MANAGER_OBJECT "/org/gnome/SessionManager"
+#define SESSION_MANAGER_INTERFACE "org.gnome.SessionManager"
+
+#define DBUS_TIMEOUT (-1)
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(WakeLockListener, nsIDOMMozWakeLockListener)
+
+WakeLockListener* WakeLockListener::sSingleton = nullptr;
+
+
+enum DesktopEnvironment {
+ FreeDesktop,
+ GNOME,
+ Unsupported,
+};
+
+class WakeLockTopic
+{
+public:
+ WakeLockTopic(const nsAString& aTopic, DBusConnection* aConnection)
+ : mTopic(NS_ConvertUTF16toUTF8(aTopic))
+ , mConnection(aConnection)
+ , mDesktopEnvironment(FreeDesktop)
+ , mInhibitRequest(0)
+ , mShouldInhibit(false)
+ , mWaitingForReply(false)
+ {
+ }
+
+ nsresult InhibitScreensaver(void);
+ nsresult UninhibitScreensaver(void);
+
+private:
+ bool SendInhibit();
+ bool SendUninhibit();
+
+ bool SendFreeDesktopInhibitMessage();
+ bool SendGNOMEInhibitMessage();
+ bool SendMessage(DBusMessage* aMessage);
+
+ static void ReceiveInhibitReply(DBusPendingCall* aPending, void* aUserData);
+ void InhibitFailed();
+ void InhibitSucceeded(uint32_t aInhibitRequest);
+
+ nsCString mTopic;
+ RefPtr<DBusConnection> mConnection;
+
+ DesktopEnvironment mDesktopEnvironment;
+
+ uint32_t mInhibitRequest;
+
+ bool mShouldInhibit;
+ bool mWaitingForReply;
+};
+
+
+bool
+WakeLockTopic::SendMessage(DBusMessage* aMessage)
+{
+ // send message and get a handle for a reply
+ RefPtr<DBusPendingCall> reply;
+ dbus_connection_send_with_reply(mConnection, aMessage,
+ reply.StartAssignment(),
+ DBUS_TIMEOUT);
+ if (!reply) {
+ return false;
+ }
+
+ dbus_pending_call_set_notify(reply, &ReceiveInhibitReply, this, NULL);
+
+ return true;
+}
+
+bool
+WakeLockTopic::SendFreeDesktopInhibitMessage()
+{
+ RefPtr<DBusMessage> message = already_AddRefed<DBusMessage>(
+ dbus_message_new_method_call(FREEDESKTOP_SCREENSAVER_TARGET,
+ FREEDESKTOP_SCREENSAVER_OBJECT,
+ FREEDESKTOP_SCREENSAVER_INTERFACE,
+ "Inhibit"));
+
+ if (!message) {
+ return false;
+ }
+
+ const char* app = g_get_prgname();
+ const char* topic = mTopic.get();
+ dbus_message_append_args(message,
+ DBUS_TYPE_STRING, &app,
+ DBUS_TYPE_STRING, &topic,
+ DBUS_TYPE_INVALID);
+
+ return SendMessage(message);
+}
+
+bool
+WakeLockTopic::SendGNOMEInhibitMessage()
+{
+ RefPtr<DBusMessage> message = already_AddRefed<DBusMessage>(
+ dbus_message_new_method_call(SESSION_MANAGER_TARGET,
+ SESSION_MANAGER_OBJECT,
+ SESSION_MANAGER_INTERFACE,
+ "Inhibit"));
+
+ if (!message) {
+ return false;
+ }
+
+ static const uint32_t xid = 0;
+ static const uint32_t flags = (1 << 3); // Inhibit idle
+ const char* app = g_get_prgname();
+ const char* topic = mTopic.get();
+ dbus_message_append_args(message,
+ DBUS_TYPE_STRING, &app,
+ DBUS_TYPE_UINT32, &xid,
+ DBUS_TYPE_STRING, &topic,
+ DBUS_TYPE_UINT32, &flags,
+ DBUS_TYPE_INVALID);
+
+ return SendMessage(message);
+}
+
+
+bool
+WakeLockTopic::SendInhibit()
+{
+ bool sendOk = false;
+
+ switch (mDesktopEnvironment)
+ {
+ case FreeDesktop:
+ sendOk = SendFreeDesktopInhibitMessage();
+ break;
+ case GNOME:
+ sendOk = SendGNOMEInhibitMessage();
+ break;
+ case Unsupported:
+ return false;
+ }
+
+ if (sendOk) {
+ mWaitingForReply = true;
+ }
+
+ return sendOk;
+}
+
+bool
+WakeLockTopic::SendUninhibit()
+{
+ RefPtr<DBusMessage> message;
+
+ if (mDesktopEnvironment == FreeDesktop) {
+ message = already_AddRefed<DBusMessage>(
+ dbus_message_new_method_call(FREEDESKTOP_SCREENSAVER_TARGET,
+ FREEDESKTOP_SCREENSAVER_OBJECT,
+ FREEDESKTOP_SCREENSAVER_INTERFACE,
+ "UnInhibit"));
+ } else if (mDesktopEnvironment == GNOME) {
+ message = already_AddRefed<DBusMessage>(
+ dbus_message_new_method_call(SESSION_MANAGER_TARGET,
+ SESSION_MANAGER_OBJECT,
+ SESSION_MANAGER_INTERFACE,
+ "Uninhibit"));
+ }
+
+ if (!message) {
+ return false;
+ }
+
+ dbus_message_append_args(message,
+ DBUS_TYPE_UINT32, &mInhibitRequest,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_send(mConnection, message, nullptr);
+ dbus_connection_flush(mConnection);
+
+ mInhibitRequest = 0;
+
+ return true;
+}
+
+nsresult
+WakeLockTopic::InhibitScreensaver()
+{
+ if (mShouldInhibit) {
+ // Screensaver is inhibited. Nothing to do here.
+ return NS_OK;
+ }
+
+ mShouldInhibit = true;
+
+ if (mWaitingForReply) {
+ // We already have a screensaver inhibit request pending. This can happen
+ // if InhibitScreensaver is called, then UninhibitScreensaver, then
+ // InhibitScreensaver again quickly.
+ return NS_OK;
+ }
+
+ return SendInhibit() ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult
+WakeLockTopic::UninhibitScreensaver()
+{
+ if (!mShouldInhibit) {
+ // Screensaver isn't inhibited. Nothing to do here.
+ return NS_OK;
+ }
+
+ mShouldInhibit = false;
+
+ if (mWaitingForReply) {
+ // If we're still waiting for a response to our inhibit request, we can't
+ // do anything until we get a dbus message back. The callbacks below will
+ // check |mShouldInhibit| and act accordingly.
+ return NS_OK;
+ }
+
+ return SendUninhibit() ? NS_OK : NS_ERROR_FAILURE;
+}
+
+void
+WakeLockTopic::InhibitFailed()
+{
+ mWaitingForReply = false;
+
+ if (mDesktopEnvironment == FreeDesktop) {
+ mDesktopEnvironment = GNOME;
+ } else {
+ NS_ASSERTION(mDesktopEnvironment == GNOME, "Unknown desktop environment");
+ mDesktopEnvironment = Unsupported;
+ mShouldInhibit = false;
+ }
+
+ if (!mShouldInhibit) {
+ // We were interrupted by UninhibitScreensaver() before we could find the
+ // correct desktop environment.
+ return;
+ }
+
+ SendInhibit();
+}
+
+void
+WakeLockTopic::InhibitSucceeded(uint32_t aInhibitRequest)
+{
+ mWaitingForReply = false;
+ mInhibitRequest = aInhibitRequest;
+
+ if (!mShouldInhibit) {
+ // We successfully inhibited the screensaver, but UninhibitScreensaver()
+ // was called while we were waiting for a reply.
+ SendUninhibit();
+ }
+}
+
+/* static */ void
+WakeLockTopic::ReceiveInhibitReply(DBusPendingCall* pending, void* user_data)
+{
+ if (!WakeLockListener::GetSingleton(false)) {
+ // The WakeLockListener (and therefore our topic) was deleted while we were
+ // waiting for a reply.
+ return;
+ }
+
+ WakeLockTopic* self = static_cast<WakeLockTopic*>(user_data);
+
+ RefPtr<DBusMessage> msg = already_AddRefed<DBusMessage>(
+ dbus_pending_call_steal_reply(pending));
+ if (!msg) {
+ return;
+ }
+
+ if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
+ uint32_t inhibitRequest;
+
+ if (dbus_message_get_args(msg, nullptr, DBUS_TYPE_UINT32,
+ &inhibitRequest, DBUS_TYPE_INVALID)) {
+ self->InhibitSucceeded(inhibitRequest);
+ }
+ } else {
+ self->InhibitFailed();
+ }
+}
+
+
+WakeLockListener::WakeLockListener()
+ : mConnection(already_AddRefed<DBusConnection>(
+ dbus_bus_get(DBUS_BUS_SESSION, nullptr)))
+{
+ if (mConnection) {
+ dbus_connection_set_exit_on_disconnect(mConnection, false);
+ dbus_connection_setup_with_g_main(mConnection, nullptr);
+ }
+}
+
+/* static */ WakeLockListener*
+WakeLockListener::GetSingleton(bool aCreate)
+{
+ if (!sSingleton && aCreate) {
+ sSingleton = new WakeLockListener();
+ sSingleton->AddRef();
+ }
+
+ return sSingleton;
+}
+
+/* static */ void
+WakeLockListener::Shutdown()
+{
+ sSingleton->Release();
+ sSingleton = nullptr;
+}
+
+nsresult
+WakeLockListener::Callback(const nsAString& topic, const nsAString& state)
+{
+ if (!mConnection) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if(!topic.Equals(NS_LITERAL_STRING("screen")))
+ return NS_OK;
+
+ WakeLockTopic* topicLock = mTopics.Get(topic);
+ if (!topicLock) {
+ topicLock = new WakeLockTopic(topic, mConnection);
+ mTopics.Put(topic, topicLock);
+ }
+
+ // Treat "locked-background" the same as "unlocked" on desktop linux.
+ bool shouldLock = state.EqualsLiteral("locked-foreground");
+
+ return shouldLock ?
+ topicLock->InhibitScreensaver() :
+ topicLock->UninhibitScreensaver();
+}
+
+#endif
diff --git a/widget/gtk/WakeLockListener.h b/widget/gtk/WakeLockListener.h
new file mode 100644
index 000000000..fc7281822
--- /dev/null
+++ b/widget/gtk/WakeLockListener.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include <unistd.h>
+
+#ifndef __WakeLockListener_h__
+#define __WakeLockListener_h__
+
+#include "nsHashKeys.h"
+#include "nsClassHashtable.h"
+
+#include "nsIDOMWakeLockListener.h"
+
+#ifdef MOZ_ENABLE_DBUS
+#include "mozilla/ipc/DBusConnectionRefPtr.h"
+#endif
+
+class WakeLockTopic;
+
+/**
+ * Receives WakeLock events and simply passes it on to the right WakeLockTopic
+ * to inhibit the screensaver.
+ */
+class WakeLockListener final : public nsIDOMMozWakeLockListener
+{
+public:
+ NS_DECL_ISUPPORTS;
+
+ static WakeLockListener* GetSingleton(bool aCreate = true);
+ static void Shutdown();
+
+ virtual nsresult Callback(const nsAString& topic,
+ const nsAString& state) override;
+
+private:
+ WakeLockListener();
+ ~WakeLockListener() = default;
+
+ static WakeLockListener* sSingleton;
+
+#ifdef MOZ_ENABLE_DBUS
+ RefPtr<DBusConnection> mConnection;
+#endif
+ // Map of topic names to |WakeLockTopic|s.
+ // We assume a small, finite-sized set of topics.
+ nsClassHashtable<nsStringHashKey, WakeLockTopic> mTopics;
+};
+
+#endif // __WakeLockListener_h__
diff --git a/widget/gtk/WidgetStyleCache.cpp b/widget/gtk/WidgetStyleCache.cpp
new file mode 100644
index 000000000..fd099681f
--- /dev/null
+++ b/widget/gtk/WidgetStyleCache.cpp
@@ -0,0 +1,1162 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include <dlfcn.h>
+#include <gtk/gtk.h>
+#include "WidgetStyleCache.h"
+#include "gtkdrawing.h"
+
+#define STATE_FLAG_DIR_LTR (1U << 7)
+#define STATE_FLAG_DIR_RTL (1U << 8)
+#if GTK_CHECK_VERSION(3,8,0)
+static_assert(GTK_STATE_FLAG_DIR_LTR == STATE_FLAG_DIR_LTR &&
+ GTK_STATE_FLAG_DIR_RTL == STATE_FLAG_DIR_RTL,
+ "incorrect direction state flags");
+#endif
+
+static GtkWidget* sWidgetStorage[MOZ_GTK_WIDGET_NODE_COUNT];
+static GtkStyleContext* sStyleStorage[MOZ_GTK_WIDGET_NODE_COUNT];
+
+static bool sStyleContextNeedsRestore;
+#ifdef DEBUG
+static GtkStyleContext* sCurrentStyleContext;
+#endif
+static GtkStyleContext*
+GetWidgetRootStyle(WidgetNodeType aNodeType);
+static GtkStyleContext*
+GetCssNodeStyleInternal(WidgetNodeType aNodeType);
+
+static GtkWidget*
+CreateWindowWidget()
+{
+ GtkWidget *widget = gtk_window_new(GTK_WINDOW_POPUP);
+ gtk_widget_set_name(widget, "MozillaGtkWidget");
+ return widget;
+}
+
+static GtkWidget*
+CreateWindowContainerWidget()
+{
+ GtkWidget *widget = gtk_fixed_new();
+ gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_WINDOW)), widget);
+ return widget;
+}
+
+static void
+AddToWindowContainer(GtkWidget* widget)
+{
+ gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_WINDOW_CONTAINER)), widget);
+}
+
+static GtkWidget*
+CreateScrollbarWidget(WidgetNodeType aWidgetType, GtkOrientation aOrientation)
+{
+ GtkWidget* widget = gtk_scrollbar_new(aOrientation, nullptr);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateCheckboxWidget()
+{
+ GtkWidget* widget = gtk_check_button_new_with_label("M");
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateRadiobuttonWidget()
+{
+ GtkWidget* widget = gtk_radio_button_new_with_label(nullptr, "M");
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateMenuBarWidget()
+{
+ GtkWidget* widget = gtk_menu_bar_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateMenuPopupWidget()
+{
+ GtkWidget* widget = gtk_menu_new();
+ gtk_menu_attach_to_widget(GTK_MENU(widget), GetWidget(MOZ_GTK_WINDOW),
+ nullptr);
+ return widget;
+}
+
+static GtkWidget*
+CreateProgressWidget()
+{
+ GtkWidget* widget = gtk_progress_bar_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateTooltipWidget()
+{
+ MOZ_ASSERT(gtk_check_version(3, 20, 0) != nullptr,
+ "CreateTooltipWidget should be used for Gtk < 3.20 only.");
+ GtkWidget* widget = CreateWindowWidget();
+ GtkStyleContext* style = gtk_widget_get_style_context(widget);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_TOOLTIP);
+ return widget;
+}
+
+static GtkWidget*
+CreateExpanderWidget()
+{
+ GtkWidget* widget = gtk_expander_new("M");
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateFrameWidget()
+{
+ GtkWidget* widget = gtk_frame_new(nullptr);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateGripperWidget()
+{
+ GtkWidget* widget = gtk_handle_box_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateToolbarWidget()
+{
+ GtkWidget* widget = gtk_toolbar_new();
+ gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_GRIPPER)), widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateToolbarSeparatorWidget()
+{
+ GtkWidget* widget = GTK_WIDGET(gtk_separator_tool_item_new());
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateInfoBarWidget()
+{
+ GtkWidget* widget = gtk_info_bar_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateButtonWidget()
+{
+ GtkWidget* widget = gtk_button_new_with_label("M");
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateToggleButtonWidget()
+{
+ GtkWidget* widget = gtk_toggle_button_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateButtonArrowWidget()
+{
+ GtkWidget* widget = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
+ gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_TOGGLE_BUTTON)), widget);
+ gtk_widget_show(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateSpinWidget()
+{
+ GtkWidget* widget = gtk_spin_button_new(nullptr, 1, 0);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateEntryWidget()
+{
+ GtkWidget* widget = gtk_entry_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateComboBoxWidget()
+{
+ GtkWidget* widget = gtk_combo_box_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+typedef struct
+{
+ GType type;
+ GtkWidget** widget;
+} GtkInnerWidgetInfo;
+
+static void
+GetInnerWidget(GtkWidget* widget, gpointer client_data)
+{
+ auto info = static_cast<GtkInnerWidgetInfo*>(client_data);
+
+ if (G_TYPE_CHECK_INSTANCE_TYPE(widget, info->type)) {
+ *info->widget = widget;
+ }
+}
+
+static GtkWidget*
+CreateComboBoxButtonWidget()
+{
+ GtkWidget* comboBox = GetWidget(MOZ_GTK_COMBOBOX);
+ GtkWidget* comboBoxButton = nullptr;
+
+ /* Get its inner Button */
+ GtkInnerWidgetInfo info = { GTK_TYPE_TOGGLE_BUTTON,
+ &comboBoxButton };
+ gtk_container_forall(GTK_CONTAINER(comboBox),
+ GetInnerWidget, &info);
+
+ if (!comboBoxButton) {
+ /* Shouldn't be reached with current internal gtk implementation; we
+ * use a generic toggle button as last resort fallback to avoid
+ * crashing. */
+ comboBoxButton = GetWidget(MOZ_GTK_TOGGLE_BUTTON);
+ } else {
+ /* We need to have pointers to the inner widgets (button, separator, arrow)
+ * of the ComboBox to get the correct rendering from theme engines which
+ * special cases their look. Since the inner layout can change, we ask GTK
+ * to NULL our pointers when they are about to become invalid because the
+ * corresponding widgets don't exist anymore. It's the role of
+ * g_object_add_weak_pointer().
+ * Note that if we don't find the inner widgets (which shouldn't happen), we
+ * fallback to use generic "non-inner" widgets, and they don't need that kind
+ * of weak pointer since they are explicit children of gProtoLayout and as
+ * such GTK holds a strong reference to them. */
+ g_object_add_weak_pointer(G_OBJECT(comboBoxButton),
+ reinterpret_cast<gpointer *>(sWidgetStorage) +
+ MOZ_GTK_COMBOBOX_BUTTON);
+ }
+
+ return comboBoxButton;
+}
+
+static GtkWidget*
+CreateComboBoxArrowWidget()
+{
+ GtkWidget* comboBoxButton = GetWidget(MOZ_GTK_COMBOBOX_BUTTON);
+ GtkWidget* comboBoxArrow = nullptr;
+
+ /* Get the widgets inside the Button */
+ GtkWidget* buttonChild = gtk_bin_get_child(GTK_BIN(comboBoxButton));
+ if (GTK_IS_BOX(buttonChild)) {
+ /* appears-as-list = FALSE, cell-view = TRUE; the button
+ * contains an hbox. This hbox is there because the ComboBox
+ * needs to place a cell renderer, a separator, and an arrow in
+ * the button when appears-as-list is FALSE. */
+ GtkInnerWidgetInfo info = { GTK_TYPE_ARROW,
+ &comboBoxArrow };
+ gtk_container_forall(GTK_CONTAINER(buttonChild),
+ GetInnerWidget, &info);
+ } else if (GTK_IS_ARROW(buttonChild)) {
+ /* appears-as-list = TRUE, or cell-view = FALSE;
+ * the button only contains an arrow */
+ comboBoxArrow = buttonChild;
+ }
+
+ if (!comboBoxArrow) {
+ /* Shouldn't be reached with current internal gtk implementation;
+ * we gButtonArrowWidget as last resort fallback to avoid
+ * crashing. */
+ comboBoxArrow = GetWidget(MOZ_GTK_BUTTON_ARROW);
+ } else {
+ g_object_add_weak_pointer(G_OBJECT(comboBoxArrow),
+ reinterpret_cast<gpointer *>(sWidgetStorage) +
+ MOZ_GTK_COMBOBOX_ARROW);
+ }
+
+ return comboBoxArrow;
+}
+
+static GtkWidget*
+CreateComboBoxSeparatorWidget()
+{
+ // Ensure to search for separator only once as it can fail
+ // TODO - it won't initialize after ResetWidgetCache() call
+ static bool isMissingSeparator = false;
+ if (isMissingSeparator)
+ return nullptr;
+
+ /* Get the widgets inside the Button */
+ GtkWidget* comboBoxSeparator = nullptr;
+ GtkWidget* buttonChild =
+ gtk_bin_get_child(GTK_BIN(GetWidget(MOZ_GTK_COMBOBOX_BUTTON)));
+ if (GTK_IS_BOX(buttonChild)) {
+ /* appears-as-list = FALSE, cell-view = TRUE; the button
+ * contains an hbox. This hbox is there because the ComboBox
+ * needs to place a cell renderer, a separator, and an arrow in
+ * the button when appears-as-list is FALSE. */
+ GtkInnerWidgetInfo info = { GTK_TYPE_SEPARATOR,
+ &comboBoxSeparator };
+ gtk_container_forall(GTK_CONTAINER(buttonChild),
+ GetInnerWidget, &info);
+ }
+
+ if (comboBoxSeparator) {
+ g_object_add_weak_pointer(G_OBJECT(comboBoxSeparator),
+ reinterpret_cast<gpointer *>(sWidgetStorage) +
+ MOZ_GTK_COMBOBOX_SEPARATOR);
+ } else {
+ /* comboBoxSeparator may be NULL
+ * when "appears-as-list" = TRUE or "cell-view" = FALSE;
+ * if there is no separator, then we just won't paint it. */
+ isMissingSeparator = true;
+ }
+
+ return comboBoxSeparator;
+}
+
+static GtkWidget*
+CreateComboBoxEntryWidget()
+{
+ GtkWidget* widget = gtk_combo_box_new_with_entry();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateComboBoxEntryTextareaWidget()
+{
+ GtkWidget* comboBoxTextarea = nullptr;
+
+ /* Get its inner Entry and Button */
+ GtkInnerWidgetInfo info = { GTK_TYPE_ENTRY,
+ &comboBoxTextarea };
+ gtk_container_forall(GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_ENTRY)),
+ GetInnerWidget, &info);
+
+ if (!comboBoxTextarea) {
+ comboBoxTextarea = GetWidget(MOZ_GTK_ENTRY);
+ } else {
+ g_object_add_weak_pointer(G_OBJECT(comboBoxTextarea),
+ reinterpret_cast<gpointer *>(sWidgetStorage) +
+ MOZ_GTK_COMBOBOX_ENTRY);
+ }
+
+ return comboBoxTextarea;
+}
+
+static GtkWidget*
+CreateComboBoxEntryButtonWidget()
+{
+ GtkWidget* comboBoxButton = nullptr;
+
+ /* Get its inner Entry and Button */
+ GtkInnerWidgetInfo info = { GTK_TYPE_TOGGLE_BUTTON,
+ &comboBoxButton };
+ gtk_container_forall(GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_ENTRY)),
+ GetInnerWidget, &info);
+
+ if (!comboBoxButton) {
+ comboBoxButton = GetWidget(MOZ_GTK_TOGGLE_BUTTON);
+ } else {
+ g_object_add_weak_pointer(G_OBJECT(comboBoxButton),
+ reinterpret_cast<gpointer *>(sWidgetStorage) +
+ MOZ_GTK_COMBOBOX_ENTRY_BUTTON);
+ }
+
+ return comboBoxButton;
+}
+
+static GtkWidget*
+CreateComboBoxEntryArrowWidget()
+{
+ GtkWidget* comboBoxArrow = nullptr;
+
+ /* Get the Arrow inside the Button */
+ GtkWidget* buttonChild =
+ gtk_bin_get_child(GTK_BIN(GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON)));
+
+ if (GTK_IS_BOX(buttonChild)) {
+ /* appears-as-list = FALSE, cell-view = TRUE; the button
+ * contains an hbox. This hbox is there because the ComboBox
+ * needs to place a cell renderer, a separator, and an arrow in
+ * the button when appears-as-list is FALSE. */
+ GtkInnerWidgetInfo info = { GTK_TYPE_ARROW,
+ &comboBoxArrow };
+ gtk_container_forall(GTK_CONTAINER(buttonChild),
+ GetInnerWidget, &info);
+ } else if (GTK_IS_ARROW(buttonChild)) {
+ /* appears-as-list = TRUE, or cell-view = FALSE;
+ * the button only contains an arrow */
+ comboBoxArrow = buttonChild;
+ }
+
+ if (!comboBoxArrow) {
+ /* Shouldn't be reached with current internal gtk implementation;
+ * we gButtonArrowWidget as last resort fallback to avoid
+ * crashing. */
+ comboBoxArrow = GetWidget(MOZ_GTK_BUTTON_ARROW);
+ } else {
+ g_object_add_weak_pointer(G_OBJECT(comboBoxArrow),
+ reinterpret_cast<gpointer *>(sWidgetStorage) +
+ MOZ_GTK_COMBOBOX_ENTRY_ARROW);
+ }
+
+ return comboBoxArrow;
+}
+
+static GtkWidget*
+CreateScrolledWindowWidget()
+{
+ GtkWidget* widget = gtk_scrolled_window_new(nullptr, nullptr);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateTextViewWidget()
+{
+ GtkWidget* widget = gtk_text_view_new();
+ gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_SCROLLED_WINDOW)),
+ widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateMenuSeparatorWidget()
+{
+ GtkWidget* widget = gtk_separator_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(GetWidget(MOZ_GTK_MENUPOPUP)),
+ widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateTreeViewWidget()
+{
+ GtkWidget* widget = gtk_tree_view_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateTreeHeaderCellWidget()
+{
+ /*
+ * Some GTK engines paint the first and last cell
+ * of a TreeView header with a highlight.
+ * Since we do not know where our widget will be relative
+ * to the other buttons in the TreeView header, we must
+ * paint it as a button that is between two others,
+ * thus ensuring it is neither the first or last button
+ * in the header.
+ * GTK doesn't give us a way to do this explicitly,
+ * so we must paint with a button that is between two
+ * others.
+ */
+ GtkTreeViewColumn* firstTreeViewColumn;
+ GtkTreeViewColumn* middleTreeViewColumn;
+ GtkTreeViewColumn* lastTreeViewColumn;
+
+ GtkWidget *treeView = GetWidget(MOZ_GTK_TREEVIEW);
+
+ /* Create and append our three columns */
+ firstTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(firstTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(treeView),
+ firstTreeViewColumn);
+
+ middleTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(middleTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(treeView),
+ middleTreeViewColumn);
+
+ lastTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(lastTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(treeView),
+ lastTreeViewColumn);
+
+ /* Use the middle column's header for our button */
+ return gtk_tree_view_column_get_button(middleTreeViewColumn);
+}
+
+static GtkWidget*
+CreateTreeHeaderSortArrowWidget()
+{
+ /* TODO, but it can't be NULL */
+ GtkWidget* widget = gtk_button_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateHPanedWidget()
+{
+ GtkWidget* widget = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateVPanedWidget()
+{
+ GtkWidget* widget = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateScaleWidget(GtkOrientation aOrientation)
+{
+ GtkWidget* widget = gtk_scale_new(aOrientation, nullptr);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateNotebookWidget()
+{
+ GtkWidget* widget = gtk_notebook_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget*
+CreateWidget(WidgetNodeType aWidgetType)
+{
+ switch (aWidgetType) {
+ case MOZ_GTK_WINDOW:
+ return CreateWindowWidget();
+ case MOZ_GTK_WINDOW_CONTAINER:
+ return CreateWindowContainerWidget();
+ case MOZ_GTK_CHECKBUTTON_CONTAINER:
+ return CreateCheckboxWidget();
+ case MOZ_GTK_PROGRESSBAR:
+ return CreateProgressWidget();
+ case MOZ_GTK_RADIOBUTTON_CONTAINER:
+ return CreateRadiobuttonWidget();
+ case MOZ_GTK_SCROLLBAR_HORIZONTAL:
+ return CreateScrollbarWidget(aWidgetType,
+ GTK_ORIENTATION_HORIZONTAL);
+ case MOZ_GTK_SCROLLBAR_VERTICAL:
+ return CreateScrollbarWidget(aWidgetType,
+ GTK_ORIENTATION_VERTICAL);
+ case MOZ_GTK_MENUBAR:
+ return CreateMenuBarWidget();
+ case MOZ_GTK_MENUPOPUP:
+ return CreateMenuPopupWidget();
+ case MOZ_GTK_MENUSEPARATOR:
+ return CreateMenuSeparatorWidget();
+ case MOZ_GTK_EXPANDER:
+ return CreateExpanderWidget();
+ case MOZ_GTK_FRAME:
+ return CreateFrameWidget();
+ case MOZ_GTK_GRIPPER:
+ return CreateGripperWidget();
+ case MOZ_GTK_TOOLBAR:
+ return CreateToolbarWidget();
+ case MOZ_GTK_TOOLBAR_SEPARATOR:
+ return CreateToolbarSeparatorWidget();
+ case MOZ_GTK_INFO_BAR:
+ return CreateInfoBarWidget();
+ case MOZ_GTK_SPINBUTTON:
+ return CreateSpinWidget();
+ case MOZ_GTK_BUTTON:
+ return CreateButtonWidget();
+ case MOZ_GTK_TOGGLE_BUTTON:
+ return CreateToggleButtonWidget();
+ case MOZ_GTK_BUTTON_ARROW:
+ return CreateButtonArrowWidget();
+ case MOZ_GTK_ENTRY:
+ return CreateEntryWidget();
+ case MOZ_GTK_SCROLLED_WINDOW:
+ return CreateScrolledWindowWidget();
+ case MOZ_GTK_TEXT_VIEW:
+ return CreateTextViewWidget();
+ case MOZ_GTK_TREEVIEW:
+ return CreateTreeViewWidget();
+ case MOZ_GTK_TREE_HEADER_CELL:
+ return CreateTreeHeaderCellWidget();
+ case MOZ_GTK_TREE_HEADER_SORTARROW:
+ return CreateTreeHeaderSortArrowWidget();
+ case MOZ_GTK_SPLITTER_HORIZONTAL:
+ return CreateHPanedWidget();
+ case MOZ_GTK_SPLITTER_VERTICAL:
+ return CreateVPanedWidget();
+ case MOZ_GTK_SCALE_HORIZONTAL:
+ return CreateScaleWidget(GTK_ORIENTATION_HORIZONTAL);
+ case MOZ_GTK_SCALE_VERTICAL:
+ return CreateScaleWidget(GTK_ORIENTATION_VERTICAL);
+ case MOZ_GTK_NOTEBOOK:
+ return CreateNotebookWidget();
+ case MOZ_GTK_COMBOBOX:
+ return CreateComboBoxWidget();
+ case MOZ_GTK_COMBOBOX_BUTTON:
+ return CreateComboBoxButtonWidget();
+ case MOZ_GTK_COMBOBOX_ARROW:
+ return CreateComboBoxArrowWidget();
+ case MOZ_GTK_COMBOBOX_SEPARATOR:
+ return CreateComboBoxSeparatorWidget();
+ case MOZ_GTK_COMBOBOX_ENTRY:
+ return CreateComboBoxEntryWidget();
+ case MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA:
+ return CreateComboBoxEntryTextareaWidget();
+ case MOZ_GTK_COMBOBOX_ENTRY_BUTTON:
+ return CreateComboBoxEntryButtonWidget();
+ case MOZ_GTK_COMBOBOX_ENTRY_ARROW:
+ return CreateComboBoxEntryArrowWidget();
+ default:
+ /* Not implemented */
+ return nullptr;
+ }
+}
+
+GtkWidget*
+GetWidget(WidgetNodeType aWidgetType)
+{
+ GtkWidget* widget = sWidgetStorage[aWidgetType];
+ if (!widget) {
+ widget = CreateWidget(aWidgetType);
+ sWidgetStorage[aWidgetType] = widget;
+ }
+ return widget;
+}
+
+GtkStyleContext*
+CreateStyleForWidget(GtkWidget* aWidget, GtkStyleContext* aParentStyle)
+{
+ static auto sGtkWidgetClassGetCSSName =
+ reinterpret_cast<const char* (*)(GtkWidgetClass*)>
+ (dlsym(RTLD_DEFAULT, "gtk_widget_class_get_css_name"));
+
+ GtkWidgetClass *widgetClass = GTK_WIDGET_GET_CLASS(aWidget);
+ const gchar* name = sGtkWidgetClassGetCSSName ?
+ sGtkWidgetClassGetCSSName(widgetClass) : nullptr;
+
+ GtkStyleContext *context =
+ CreateCSSNode(name, aParentStyle, G_TYPE_FROM_CLASS(widgetClass));
+
+ // Classes are stored on the style context instead of the path so that any
+ // future gtk_style_context_save() will inherit classes on the head CSS
+ // node, in the same way as happens when called on a style context owned by
+ // a widget.
+ //
+ // Classes can be stored on a GtkCssNodeDeclaration and/or the path.
+ // gtk_style_context_save() reuses the GtkCssNodeDeclaration, and appends a
+ // new object to the path, without copying the classes from the old path
+ // head. The new head picks up classes from the GtkCssNodeDeclaration, but
+ // not the path. GtkWidgets store their classes on the
+ // GtkCssNodeDeclaration, so make sure to add classes there.
+ //
+ // Picking up classes from the style context also means that
+ // https://bugzilla.gnome.org/show_bug.cgi?id=767312, which can stop
+ // gtk_widget_path_append_for_widget() from finding classes in GTK 3.20,
+ // is not a problem.
+ GtkStyleContext* widgetStyle = gtk_widget_get_style_context(aWidget);
+ GList* classes = gtk_style_context_list_classes(widgetStyle);
+ for (GList* link = classes; link; link = link->next) {
+ gtk_style_context_add_class(context, static_cast<gchar*>(link->data));
+ }
+ g_list_free(classes);
+
+ // Release any floating reference on aWidget.
+ g_object_ref_sink(aWidget);
+ g_object_unref(aWidget);
+
+ return context;
+}
+
+static GtkStyleContext*
+CreateStyleForWidget(GtkWidget* aWidget, WidgetNodeType aParentType)
+{
+ return CreateStyleForWidget(aWidget, GetWidgetRootStyle(aParentType));
+}
+
+GtkStyleContext*
+CreateCSSNode(const char* aName, GtkStyleContext* aParentStyle, GType aType)
+{
+ static auto sGtkWidgetPathIterSetObjectName =
+ reinterpret_cast<void (*)(GtkWidgetPath *, gint, const char *)>
+ (dlsym(RTLD_DEFAULT, "gtk_widget_path_iter_set_object_name"));
+
+ GtkWidgetPath* path;
+ if (aParentStyle) {
+ path = gtk_widget_path_copy(gtk_style_context_get_path(aParentStyle));
+ // Copy classes from the parent style context to its corresponding node in
+ // the path, because GTK will only match against ancestor classes if they
+ // are on the path.
+ GList* classes = gtk_style_context_list_classes(aParentStyle);
+ for (GList* link = classes; link; link = link->next) {
+ gtk_widget_path_iter_add_class(path, -1, static_cast<gchar*>(link->data));
+ }
+ g_list_free(classes);
+ } else {
+ path = gtk_widget_path_new();
+ }
+
+ gtk_widget_path_append_type(path, aType);
+
+ if (sGtkWidgetPathIterSetObjectName) {
+ (*sGtkWidgetPathIterSetObjectName)(path, -1, aName);
+ }
+
+ GtkStyleContext *context = gtk_style_context_new();
+ gtk_style_context_set_path(context, path);
+ gtk_style_context_set_parent(context, aParentStyle);
+ gtk_widget_path_unref(path);
+
+ return context;
+}
+
+// Return a style context matching that of the root CSS node of a widget.
+// This is used by all GTK versions.
+static GtkStyleContext*
+GetWidgetRootStyle(WidgetNodeType aNodeType)
+{
+ GtkStyleContext* style = sStyleStorage[aNodeType];
+ if (style)
+ return style;
+
+ switch (aNodeType) {
+ case MOZ_GTK_MENUBARITEM:
+ style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUBAR);
+ break;
+ case MOZ_GTK_MENUITEM:
+ style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUPOPUP);
+ break;
+ case MOZ_GTK_IMAGEMENUITEM:
+ style = CreateStyleForWidget(gtk_image_menu_item_new(), MOZ_GTK_MENUPOPUP);
+ break;
+ case MOZ_GTK_CHECKMENUITEM_CONTAINER:
+ style = CreateStyleForWidget(gtk_check_menu_item_new(), MOZ_GTK_MENUPOPUP);
+ break;
+ case MOZ_GTK_RADIOMENUITEM_CONTAINER:
+ style = CreateStyleForWidget(gtk_radio_menu_item_new(nullptr),
+ MOZ_GTK_MENUPOPUP);
+ break;
+ default:
+ GtkWidget* widget = GetWidget(aNodeType);
+ MOZ_ASSERT(widget);
+ return gtk_widget_get_style_context(widget);
+ }
+
+ MOZ_ASSERT(style);
+ sStyleStorage[aNodeType] = style;
+ return style;
+}
+
+static GtkStyleContext*
+CreateChildCSSNode(const char* aName, WidgetNodeType aParentNodeType)
+{
+ return CreateCSSNode(aName, GetCssNodeStyleInternal(aParentNodeType));
+}
+
+static GtkStyleContext*
+GetWidgetStyleWithClass(WidgetNodeType aWidgetType, const gchar* aStyleClass)
+{
+ GtkStyleContext* style = GetWidgetRootStyle(aWidgetType);
+ gtk_style_context_save(style);
+ MOZ_ASSERT(!sStyleContextNeedsRestore);
+ sStyleContextNeedsRestore = true;
+ gtk_style_context_add_class(style, aStyleClass);
+ return style;
+}
+
+/* GetCssNodeStyleInternal is used by Gtk >= 3.20 */
+static GtkStyleContext*
+GetCssNodeStyleInternal(WidgetNodeType aNodeType)
+{
+ GtkStyleContext* style = sStyleStorage[aNodeType];
+ if (style)
+ return style;
+
+ switch (aNodeType) {
+ case MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL:
+ style = CreateChildCSSNode("contents",
+ MOZ_GTK_SCROLLBAR_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
+ MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
+ MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL:
+ style = CreateChildCSSNode("contents",
+ MOZ_GTK_SCROLLBAR_VERTICAL);
+ break;
+ case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
+ MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL);
+ break;
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
+ MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL);
+ break;
+ case MOZ_GTK_SCROLLBAR_BUTTON:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_BUTTON,
+ MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL);
+ break;
+ case MOZ_GTK_RADIOBUTTON:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_RADIO,
+ MOZ_GTK_RADIOBUTTON_CONTAINER);
+ break;
+ case MOZ_GTK_CHECKBUTTON:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_CHECK,
+ MOZ_GTK_CHECKBUTTON_CONTAINER);
+ break;
+ case MOZ_GTK_RADIOMENUITEM:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_RADIO,
+ MOZ_GTK_RADIOMENUITEM_CONTAINER);
+ break;
+ case MOZ_GTK_CHECKMENUITEM:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_CHECK,
+ MOZ_GTK_CHECKMENUITEM_CONTAINER);
+ break;
+ case MOZ_GTK_PROGRESS_TROUGH:
+ /* Progress bar background (trough) */
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
+ MOZ_GTK_PROGRESSBAR);
+ break;
+ case MOZ_GTK_PROGRESS_CHUNK:
+ style = CreateChildCSSNode("progress",
+ MOZ_GTK_PROGRESS_TROUGH);
+ break;
+ case MOZ_GTK_TOOLTIP:
+ // We create this from the path because GtkTooltipWindow is not public.
+ style = CreateCSSNode("tooltip", nullptr, GTK_TYPE_TOOLTIP);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND);
+ break;
+ case MOZ_GTK_GRIPPER:
+ // TODO - create from CSS node
+ return GetWidgetStyleWithClass(MOZ_GTK_GRIPPER,
+ GTK_STYLE_CLASS_GRIP);
+ case MOZ_GTK_INFO_BAR:
+ // TODO - create from CSS node
+ return GetWidgetStyleWithClass(MOZ_GTK_INFO_BAR,
+ GTK_STYLE_CLASS_INFO);
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ // TODO - create from CSS node
+ return GetWidgetStyleWithClass(MOZ_GTK_SPINBUTTON,
+ GTK_STYLE_CLASS_ENTRY);
+ case MOZ_GTK_SCROLLED_WINDOW:
+ // TODO - create from CSS node
+ return GetWidgetStyleWithClass(MOZ_GTK_SCROLLED_WINDOW,
+ GTK_STYLE_CLASS_FRAME);
+ case MOZ_GTK_TEXT_VIEW:
+ // TODO - create from CSS node
+ return GetWidgetStyleWithClass(MOZ_GTK_TEXT_VIEW,
+ GTK_STYLE_CLASS_VIEW);
+ case MOZ_GTK_FRAME_BORDER:
+ style = CreateChildCSSNode("border", MOZ_GTK_FRAME);
+ break;
+ case MOZ_GTK_TREEVIEW_VIEW:
+ // TODO - create from CSS node
+ return GetWidgetStyleWithClass(MOZ_GTK_TREEVIEW,
+ GTK_STYLE_CLASS_VIEW);
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ // TODO - create from CSS node
+ return GetWidgetStyleWithClass(MOZ_GTK_TREEVIEW,
+ GTK_STYLE_CLASS_EXPANDER);
+ case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL:
+ style = CreateChildCSSNode("separator",
+ MOZ_GTK_SPLITTER_HORIZONTAL);
+ break;
+ case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL:
+ style = CreateChildCSSNode("separator",
+ MOZ_GTK_SPLITTER_VERTICAL);
+ break;
+ case MOZ_GTK_SCALE_CONTENTS_HORIZONTAL:
+ style = CreateChildCSSNode("contents",
+ MOZ_GTK_SCALE_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCALE_CONTENTS_VERTICAL:
+ style = CreateChildCSSNode("contents",
+ MOZ_GTK_SCALE_VERTICAL);
+ break;
+ case MOZ_GTK_SCALE_TROUGH_HORIZONTAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
+ MOZ_GTK_SCALE_CONTENTS_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCALE_TROUGH_VERTICAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
+ MOZ_GTK_SCALE_CONTENTS_VERTICAL);
+ break;
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
+ MOZ_GTK_SCALE_TROUGH_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
+ MOZ_GTK_SCALE_TROUGH_VERTICAL);
+ break;
+ case MOZ_GTK_TAB_TOP:
+ {
+ // TODO - create from CSS node
+ style = GetWidgetStyleWithClass(MOZ_GTK_NOTEBOOK,
+ GTK_STYLE_CLASS_TOP);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
+ static_cast<GtkRegionFlags>(0));
+ return style;
+ }
+ case MOZ_GTK_TAB_BOTTOM:
+ {
+ // TODO - create from CSS node
+ style = GetWidgetStyleWithClass(MOZ_GTK_NOTEBOOK,
+ GTK_STYLE_CLASS_BOTTOM);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
+ static_cast<GtkRegionFlags>(0));
+ return style;
+ }
+ case MOZ_GTK_NOTEBOOK:
+ case MOZ_GTK_NOTEBOOK_HEADER:
+ case MOZ_GTK_TABPANELS:
+ case MOZ_GTK_TAB_SCROLLARROW:
+ {
+ // TODO - create from CSS node
+ GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK);
+ return gtk_widget_get_style_context(widget);
+ }
+ default:
+ return GetWidgetRootStyle(aNodeType);
+ }
+
+ MOZ_ASSERT(style, "missing style context for node type");
+ sStyleStorage[aNodeType] = style;
+ return style;
+}
+
+/* GetWidgetStyleInternal is used by Gtk < 3.20 */
+static GtkStyleContext*
+GetWidgetStyleInternal(WidgetNodeType aNodeType)
+{
+ switch (aNodeType) {
+ case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCROLLBAR_HORIZONTAL,
+ GTK_STYLE_CLASS_TROUGH);
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCROLLBAR_HORIZONTAL,
+ GTK_STYLE_CLASS_SLIDER);
+ case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL,
+ GTK_STYLE_CLASS_TROUGH);
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL,
+ GTK_STYLE_CLASS_SLIDER);
+ case MOZ_GTK_RADIOBUTTON:
+ return GetWidgetStyleWithClass(MOZ_GTK_RADIOBUTTON_CONTAINER,
+ GTK_STYLE_CLASS_RADIO);
+ case MOZ_GTK_CHECKBUTTON:
+ return GetWidgetStyleWithClass(MOZ_GTK_CHECKBUTTON_CONTAINER,
+ GTK_STYLE_CLASS_CHECK);
+ case MOZ_GTK_RADIOMENUITEM:
+ return GetWidgetStyleWithClass(MOZ_GTK_RADIOMENUITEM_CONTAINER,
+ GTK_STYLE_CLASS_RADIO);
+ case MOZ_GTK_CHECKMENUITEM:
+ return GetWidgetStyleWithClass(MOZ_GTK_CHECKMENUITEM_CONTAINER,
+ GTK_STYLE_CLASS_CHECK);
+ case MOZ_GTK_PROGRESS_TROUGH:
+ return GetWidgetStyleWithClass(MOZ_GTK_PROGRESSBAR,
+ GTK_STYLE_CLASS_TROUGH);
+ case MOZ_GTK_TOOLTIP: {
+ GtkStyleContext* style = sStyleStorage[aNodeType];
+ if (style)
+ return style;
+
+ // The tooltip style class is added first in CreateTooltipWidget() so
+ // that gtk_widget_path_append_for_widget() in CreateStyleForWidget()
+ // will find it.
+ GtkWidget* tooltipWindow = CreateTooltipWidget();
+ style = CreateStyleForWidget(tooltipWindow, nullptr);
+ gtk_widget_destroy(tooltipWindow); // Release GtkWindow self-reference.
+ sStyleStorage[aNodeType] = style;
+ return style;
+ }
+ case MOZ_GTK_GRIPPER:
+ return GetWidgetStyleWithClass(MOZ_GTK_GRIPPER,
+ GTK_STYLE_CLASS_GRIP);
+ case MOZ_GTK_INFO_BAR:
+ return GetWidgetStyleWithClass(MOZ_GTK_INFO_BAR,
+ GTK_STYLE_CLASS_INFO);
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ return GetWidgetStyleWithClass(MOZ_GTK_SPINBUTTON,
+ GTK_STYLE_CLASS_ENTRY);
+ case MOZ_GTK_SCROLLED_WINDOW:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCROLLED_WINDOW,
+ GTK_STYLE_CLASS_FRAME);
+ case MOZ_GTK_TEXT_VIEW:
+ return GetWidgetStyleWithClass(MOZ_GTK_TEXT_VIEW,
+ GTK_STYLE_CLASS_VIEW);
+ case MOZ_GTK_FRAME_BORDER:
+ return GetWidgetRootStyle(MOZ_GTK_FRAME);
+ case MOZ_GTK_TREEVIEW_VIEW:
+ return GetWidgetStyleWithClass(MOZ_GTK_TREEVIEW,
+ GTK_STYLE_CLASS_VIEW);
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ return GetWidgetStyleWithClass(MOZ_GTK_TREEVIEW,
+ GTK_STYLE_CLASS_EXPANDER);
+ case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SPLITTER_HORIZONTAL,
+ GTK_STYLE_CLASS_PANE_SEPARATOR);
+ case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SPLITTER_VERTICAL,
+ GTK_STYLE_CLASS_PANE_SEPARATOR);
+ case MOZ_GTK_SCALE_TROUGH_HORIZONTAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL,
+ GTK_STYLE_CLASS_TROUGH);
+ case MOZ_GTK_SCALE_TROUGH_VERTICAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCALE_VERTICAL,
+ GTK_STYLE_CLASS_TROUGH);
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL,
+ GTK_STYLE_CLASS_SLIDER);
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCALE_VERTICAL,
+ GTK_STYLE_CLASS_SLIDER);
+ case MOZ_GTK_TAB_TOP:
+ {
+ GtkStyleContext* style = GetWidgetStyleWithClass(MOZ_GTK_NOTEBOOK,
+ GTK_STYLE_CLASS_TOP);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
+ static_cast<GtkRegionFlags>(0));
+ return style;
+ }
+ case MOZ_GTK_TAB_BOTTOM:
+ {
+ GtkStyleContext* style = GetWidgetStyleWithClass(MOZ_GTK_NOTEBOOK,
+ GTK_STYLE_CLASS_BOTTOM);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
+ static_cast<GtkRegionFlags>(0));
+ return style;
+ }
+ case MOZ_GTK_NOTEBOOK:
+ case MOZ_GTK_NOTEBOOK_HEADER:
+ case MOZ_GTK_TABPANELS:
+ case MOZ_GTK_TAB_SCROLLARROW:
+ {
+ GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK);
+ return gtk_widget_get_style_context(widget);
+ }
+ default:
+ return GetWidgetRootStyle(aNodeType);
+ }
+}
+
+void
+ResetWidgetCache(void)
+{
+ MOZ_ASSERT(!sStyleContextNeedsRestore);
+#ifdef DEBUG
+ MOZ_ASSERT(!sCurrentStyleContext);
+#endif
+
+ for (int i = 0; i < MOZ_GTK_WIDGET_NODE_COUNT; i++) {
+ if (sStyleStorage[i])
+ g_object_unref(sStyleStorage[i]);
+ }
+ mozilla::PodArrayZero(sStyleStorage);
+
+ /* This will destroy all of our widgets */
+ if (sWidgetStorage[MOZ_GTK_WINDOW])
+ gtk_widget_destroy(sWidgetStorage[MOZ_GTK_WINDOW]);
+
+ /* Clear already freed arrays */
+ mozilla::PodArrayZero(sWidgetStorage);
+}
+
+GtkStyleContext*
+ClaimStyleContext(WidgetNodeType aNodeType, GtkTextDirection aDirection,
+ GtkStateFlags aStateFlags, StyleFlags aFlags)
+{
+ MOZ_ASSERT(!sStyleContextNeedsRestore);
+ GtkStyleContext* style;
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ style = GetWidgetStyleInternal(aNodeType);
+ } else {
+ style = GetCssNodeStyleInternal(aNodeType);
+ }
+#ifdef DEBUG
+ MOZ_ASSERT(!sCurrentStyleContext);
+ sCurrentStyleContext = style;
+#endif
+ bool stateChanged = false;
+ bool stateHasDirection = gtk_get_minor_version() >= 8;
+ GtkStateFlags oldState = gtk_style_context_get_state(style);
+ MOZ_ASSERT(!(aStateFlags & (STATE_FLAG_DIR_LTR|STATE_FLAG_DIR_RTL)));
+ unsigned newState = aStateFlags;
+ if (stateHasDirection) {
+ switch (aDirection) {
+ case GTK_TEXT_DIR_LTR:
+ newState |= STATE_FLAG_DIR_LTR;
+ break;
+ case GTK_TEXT_DIR_RTL:
+ newState |= STATE_FLAG_DIR_RTL;
+ break;
+ default:
+ MOZ_FALLTHROUGH_ASSERT("Bad GtkTextDirection");
+ case GTK_TEXT_DIR_NONE:
+ // GtkWidget uses a default direction if neither is explicitly
+ // specified, but here DIR_NONE is interpreted as meaning the
+ // direction is not important, so don't change the direction
+ // unnecessarily.
+ newState |= oldState & (STATE_FLAG_DIR_LTR|STATE_FLAG_DIR_RTL);
+ }
+ } else if (aDirection != GTK_TEXT_DIR_NONE) {
+ GtkTextDirection oldDirection = gtk_style_context_get_direction(style);
+ if (aDirection != oldDirection) {
+ gtk_style_context_set_direction(style, aDirection);
+ stateChanged = true;
+ }
+ }
+ if (oldState != newState) {
+ gtk_style_context_set_state(style, static_cast<GtkStateFlags>(newState));
+ stateChanged = true;
+ }
+ // This invalidate is necessary for unsaved style contexts from GtkWidgets
+ // in pre-3.18 GTK, because automatic invalidation of such contexts
+ // was delayed until a resize event runs.
+ //
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1272194#c7
+ //
+ // Avoid calling invalidate on saved contexts to avoid performing
+ // build_properties() (in 3.16 stylecontext.c) unnecessarily early.
+ if (stateChanged && !sStyleContextNeedsRestore) {
+ gtk_style_context_invalidate(style);
+ }
+ return style;
+}
+
+void
+ReleaseStyleContext(GtkStyleContext* aStyleContext)
+{
+ if (sStyleContextNeedsRestore) {
+ gtk_style_context_restore(aStyleContext);
+ }
+ sStyleContextNeedsRestore = false;
+#ifdef DEBUG
+ MOZ_ASSERT(sCurrentStyleContext == aStyleContext);
+ sCurrentStyleContext = nullptr;
+#endif
+}
diff --git a/widget/gtk/WidgetStyleCache.h b/widget/gtk/WidgetStyleCache.h
new file mode 100644
index 000000000..2cbb5f960
--- /dev/null
+++ b/widget/gtk/WidgetStyleCache.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 WidgetStyleCache_h
+#define WidgetStyleCache_h
+
+#include <gtk/gtk.h>
+#include "gtkdrawing.h"
+
+
+typedef unsigned StyleFlags;
+enum : StyleFlags {
+ NO_STYLE_FLAGS,
+ WHATEVER_MIGHT_BE_NEEDED = 1U << 0,
+};
+
+GtkWidget*
+GetWidget(WidgetNodeType aNodeType);
+
+/*
+ * Return a new style context based on aWidget, as a child of aParentStyle.
+ * If aWidget still has a floating reference, then it is sunk and released.
+ */
+GtkStyleContext*
+CreateStyleForWidget(GtkWidget* aWidget, GtkStyleContext* aParentStyle);
+
+GtkStyleContext*
+CreateCSSNode(const char* aName,
+ GtkStyleContext* aParentStyle,
+ GType aType = G_TYPE_NONE);
+
+// Callers must call ReleaseStyleContext() on the returned context.
+GtkStyleContext*
+ClaimStyleContext(WidgetNodeType aNodeType,
+ GtkTextDirection aDirection = GTK_TEXT_DIR_NONE,
+ GtkStateFlags aStateFlags = GTK_STATE_FLAG_NORMAL,
+ StyleFlags aFlags = NO_STYLE_FLAGS);
+void
+ReleaseStyleContext(GtkStyleContext* style);
+
+void
+ResetWidgetCache(void);
+
+#endif // WidgetStyleCache_h
diff --git a/widget/gtk/WidgetTraceEvent.cpp b/widget/gtk/WidgetTraceEvent.cpp
new file mode 100644
index 000000000..c09944ac0
--- /dev/null
+++ b/widget/gtk/WidgetTraceEvent.cpp
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "mozilla/WidgetTraceEvent.h"
+
+#include <glib.h>
+#include <mozilla/CondVar.h>
+#include <mozilla/Mutex.h>
+#include <stdio.h>
+
+using mozilla::CondVar;
+using mozilla::Mutex;
+using mozilla::MutexAutoLock;
+
+namespace {
+
+Mutex* sMutex = nullptr;
+CondVar* sCondVar = nullptr;
+bool sTracerProcessed = false;
+
+// This function is called from the main (UI) thread.
+gboolean TracerCallback(gpointer data)
+{
+ mozilla::SignalTracerThread();
+ return FALSE;
+}
+
+} // namespace
+
+namespace mozilla {
+
+bool InitWidgetTracing()
+{
+ sMutex = new Mutex("Event tracer thread mutex");
+ sCondVar = new CondVar(*sMutex, "Event tracer thread condvar");
+ return true;
+}
+
+void CleanUpWidgetTracing()
+{
+ delete sMutex;
+ delete sCondVar;
+ sMutex = nullptr;
+ sCondVar = nullptr;
+}
+
+// This function is called from the background tracer thread.
+bool FireAndWaitForTracerEvent()
+{
+ MOZ_ASSERT(sMutex && sCondVar, "Tracing not initialized!");
+
+ // Send a default-priority idle event through the
+ // event loop, and wait for it to finish.
+ MutexAutoLock lock(*sMutex);
+ MOZ_ASSERT(!sTracerProcessed, "Tracer synchronization state is wrong");
+ g_idle_add_full(G_PRIORITY_DEFAULT,
+ TracerCallback,
+ nullptr,
+ nullptr);
+ while (!sTracerProcessed)
+ sCondVar->Wait();
+ sTracerProcessed = false;
+ return true;
+}
+
+void SignalTracerThread()
+{
+ if (!sMutex || !sCondVar)
+ return;
+ MutexAutoLock lock(*sMutex);
+ if (!sTracerProcessed) {
+ sTracerProcessed = true;
+ sCondVar->Notify();
+ }
+}
+
+} // namespace mozilla
diff --git a/widget/gtk/WidgetUtilsGtk.cpp b/widget/gtk/WidgetUtilsGtk.cpp
new file mode 100644
index 000000000..393f66908
--- /dev/null
+++ b/widget/gtk/WidgetUtilsGtk.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "WidgetUtilsGtk.h"
+
+namespace mozilla {
+
+namespace widget {
+
+int32_t WidgetUtilsGTK::IsTouchDeviceSupportPresent()
+{
+#if GTK_CHECK_VERSION(3,4,0)
+ int32_t result = 0;
+ GdkDisplay* display = gdk_display_get_default();
+ if (!display) {
+ return 0;
+ }
+
+ GdkDeviceManager* manager = gdk_display_get_device_manager(display);
+ if (!manager) {
+ return 0;
+ }
+
+ GList* devices =
+ gdk_device_manager_list_devices(manager, GDK_DEVICE_TYPE_SLAVE);
+ GList* list = devices;
+
+ while (devices) {
+ GdkDevice* device = static_cast<GdkDevice*>(devices->data);
+ if (gdk_device_get_source(device) == GDK_SOURCE_TOUCHSCREEN) {
+ result = 1;
+ break;
+ }
+ devices = devices->next;
+ }
+
+ if (list) {
+ g_list_free(list);
+ }
+
+ return result;
+#else
+ return 0;
+#endif
+}
+
+} // namespace widget
+
+} // namespace mozilla
diff --git a/widget/gtk/WidgetUtilsGtk.h b/widget/gtk/WidgetUtilsGtk.h
new file mode 100644
index 000000000..8c132c11b
--- /dev/null
+++ b/widget/gtk/WidgetUtilsGtk.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 WidgetUtilsGtk_h__
+#define WidgetUtilsGtk_h__
+
+#include <stdint.h>
+
+namespace mozilla {
+namespace widget {
+
+class WidgetUtilsGTK
+{
+public:
+ /* See WidgetUtils::IsTouchDeviceSupportPresent(). */
+ static int32_t IsTouchDeviceSupportPresent();
+};
+
+} // namespace widget
+
+} // namespace mozilla
+
+#endif // WidgetUtilsGtk_h__
diff --git a/widget/gtk/WindowSurfaceProvider.cpp b/widget/gtk/WindowSurfaceProvider.cpp
new file mode 100644
index 000000000..526fe6a25
--- /dev/null
+++ b/widget/gtk/WindowSurfaceProvider.cpp
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "WindowSurfaceProvider.h"
+
+#include "gfxPlatformGtk.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "WindowSurfaceX11Image.h"
+#include "WindowSurfaceX11SHM.h"
+#include "WindowSurfaceXRender.h"
+
+namespace mozilla {
+namespace widget {
+
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+WindowSurfaceProvider::WindowSurfaceProvider()
+ : mXDisplay(nullptr)
+ , mXWindow(0)
+ , mXVisual(nullptr)
+ , mXDepth(0)
+ , mWindowSurface(nullptr)
+{
+}
+
+void WindowSurfaceProvider::Initialize(
+ Display* aDisplay,
+ Window aWindow,
+ Visual* aVisual,
+ int aDepth)
+{
+ // We should not be initialized
+ MOZ_ASSERT(!mXDisplay);
+
+ // This should also be a valid initialization
+ MOZ_ASSERT(aDisplay && aWindow != X11None && aVisual);
+
+ mXDisplay = aDisplay;
+ mXWindow = aWindow;
+ mXVisual = aVisual;
+ mXDepth = aDepth;
+}
+void WindowSurfaceProvider::CleanupResources()
+{
+ mWindowSurface = nullptr;
+}
+
+UniquePtr<WindowSurface>
+WindowSurfaceProvider::CreateWindowSurface()
+{
+ // We should be initialized
+ MOZ_ASSERT(mXDisplay);
+
+ // Blit to the window with the following priority:
+ // 1. XRender (iff XRender is enabled && we are in-process)
+ // 2. MIT-SHM
+ // 3. XPutImage
+
+#ifdef MOZ_WIDGET_GTK
+ if (gfxVars::UseXRender()) {
+ LOGDRAW(("Drawing to nsWindow %p using XRender\n", (void*)this));
+ return MakeUnique<WindowSurfaceXRender>(mXDisplay, mXWindow, mXVisual, mXDepth);
+ }
+#endif // MOZ_WIDGET_GTK
+
+#ifdef MOZ_HAVE_SHMIMAGE
+ if (nsShmImage::UseShm()) {
+ LOGDRAW(("Drawing to nsWindow %p using MIT-SHM\n", (void*)this));
+ return MakeUnique<WindowSurfaceX11SHM>(mXDisplay, mXWindow, mXVisual, mXDepth);
+ }
+#endif // MOZ_HAVE_SHMIMAGE
+
+ LOGDRAW(("Drawing to nsWindow %p using XPutImage\n", (void*)this));
+ return MakeUnique<WindowSurfaceX11Image>(mXDisplay, mXWindow, mXVisual, mXDepth);
+}
+
+already_AddRefed<gfx::DrawTarget>
+WindowSurfaceProvider::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion,
+ layers::BufferMode* aBufferMode)
+{
+ if (aInvalidRegion.IsEmpty())
+ return nullptr;
+
+ if (!mWindowSurface) {
+ mWindowSurface = CreateWindowSurface();
+ if (!mWindowSurface)
+ return nullptr;
+ }
+
+ *aBufferMode = BufferMode::BUFFER_NONE;
+ RefPtr<DrawTarget> dt = nullptr;
+ if (!(dt = mWindowSurface->Lock(aInvalidRegion)) &&
+ !mWindowSurface->IsFallback()) {
+ gfxWarningOnce() << "Failed to lock WindowSurface, falling back to XPutImage backend.";
+ mWindowSurface = MakeUnique<WindowSurfaceX11Image>(mXDisplay, mXWindow, mXVisual, mXDepth);
+ dt = mWindowSurface->Lock(aInvalidRegion);
+ }
+ return dt.forget();
+}
+
+void
+WindowSurfaceProvider::EndRemoteDrawingInRegion(gfx::DrawTarget* aDrawTarget,
+ LayoutDeviceIntRegion& aInvalidRegion)
+{
+ if (mWindowSurface)
+ mWindowSurface->Commit(aInvalidRegion);
+}
+
+} // namespace mozilla
+} // namespace widget
diff --git a/widget/gtk/WindowSurfaceProvider.h b/widget/gtk/WindowSurfaceProvider.h
new file mode 100644
index 000000000..73b23031e
--- /dev/null
+++ b/widget/gtk/WindowSurfaceProvider.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_PROVIDER_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_PROVIDER_H
+
+#include "mozilla/widget/WindowSurface.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/gfx/2D.h"
+#include "Units.h"
+
+#include <X11/Xlib.h> // for Window, Display, Visual, etc.
+
+namespace mozilla {
+namespace widget {
+
+/*
+ * Holds the logic for creating WindowSurface's for a GTK nsWindow.
+ * The main purpose of this class is to allow sharing of logic between
+ * nsWindow and X11CompositorWidget, for when OMTC is enabled or disabled.
+ */
+class WindowSurfaceProvider final
+{
+public:
+ WindowSurfaceProvider();
+
+ /**
+ * Initializes the WindowSurfaceProvider by giving it the window
+ * handle and display to attach to. WindowSurfaceProvider doesn't
+ * own the Display, Window, etc, and they must continue to exist
+ * while WindowSurfaceProvider is used.
+ */
+ void Initialize(
+ Display* aDisplay,
+ Window aWindow,
+ Visual* aVisual,
+ int aDepth);
+
+ /**
+ * Releases any surfaces created by this provider.
+ * This is used by X11CompositorWidget to get rid
+ * of resources before we close the display connection.
+ */
+ void CleanupResources();
+
+ already_AddRefed<gfx::DrawTarget>
+ StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion,
+ layers::BufferMode* aBufferMode);
+ void EndRemoteDrawingInRegion(gfx::DrawTarget* aDrawTarget,
+ LayoutDeviceIntRegion& aInvalidRegion);
+
+private:
+ UniquePtr<WindowSurface> CreateWindowSurface();
+
+ Display* mXDisplay;
+ Window mXWindow;
+ Visual* mXVisual;
+ int mXDepth;
+
+ UniquePtr<WindowSurface> mWindowSurface;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_PROVIDER_H
diff --git a/widget/gtk/WindowSurfaceX11.cpp b/widget/gtk/WindowSurfaceX11.cpp
new file mode 100644
index 000000000..32e3c43ef
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "WindowSurfaceX11.h"
+#include "gfxPlatform.h"
+#include "X11UndefineNone.h"
+
+namespace mozilla {
+namespace widget {
+
+WindowSurfaceX11::WindowSurfaceX11(Display* aDisplay,
+ Window aWindow,
+ Visual* aVisual,
+ unsigned int aDepth)
+ : mDisplay(aDisplay)
+ , mWindow(aWindow)
+ , mVisual(aVisual)
+ , mDepth(aDepth)
+ , mFormat(GetVisualFormat(aVisual, aDepth))
+{
+}
+
+/* static */
+gfx::SurfaceFormat
+WindowSurfaceX11::GetVisualFormat(const Visual* aVisual, unsigned int aDepth)
+{
+ switch (aDepth) {
+ case 32:
+ if (aVisual->red_mask == 0xff0000 &&
+ aVisual->green_mask == 0xff00 &&
+ aVisual->blue_mask == 0xff) {
+ return gfx::SurfaceFormat::B8G8R8A8;
+ }
+ break;
+ case 24:
+ // Only support the BGRX layout, and report it as BGRA to the compositor.
+ // The alpha channel will be discarded when we put the image.
+ // Cairo/pixman lacks some fast paths for compositing BGRX onto BGRA, so
+ // just report it as BGRX directly in that case.
+ if (aVisual->red_mask == 0xff0000 &&
+ aVisual->green_mask == 0xff00 &&
+ aVisual->blue_mask == 0xff) {
+ gfx::BackendType backend = gfxPlatform::GetPlatform()->GetDefaultContentBackend();
+ return backend == gfx::BackendType::CAIRO ? gfx::SurfaceFormat::B8G8R8X8
+ : gfx::SurfaceFormat::B8G8R8A8;
+ }
+ break;
+ case 16:
+ if (aVisual->red_mask == 0xf800 &&
+ aVisual->green_mask == 0x07e0 &&
+ aVisual->blue_mask == 0x1f) {
+ return gfx::SurfaceFormat::R5G6B5_UINT16;
+ }
+ break;
+ }
+
+ return gfx::SurfaceFormat::UNKNOWN;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/WindowSurfaceX11.h b/widget/gtk/WindowSurfaceX11.h
new file mode 100644
index 000000000..46bc10d10
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_H
+
+#ifdef MOZ_X11
+
+#include "mozilla/widget/WindowSurface.h"
+#include "mozilla/gfx/Types.h"
+
+#include <X11/Xlib.h>
+
+namespace mozilla {
+namespace widget {
+
+class WindowSurfaceX11 : public WindowSurface {
+public:
+ WindowSurfaceX11(Display* aDisplay, Window aWindow, Visual* aVisual,
+ unsigned int aDepth);
+
+protected:
+ static gfx::SurfaceFormat GetVisualFormat(const Visual* aVisual, unsigned int aDepth);
+
+ Display* const mDisplay;
+ const Window mWindow;
+ Visual* const mVisual;
+ const unsigned int mDepth;
+ const gfx::SurfaceFormat mFormat;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // MOZ_X11
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_H
diff --git a/widget/gtk/WindowSurfaceX11Image.cpp b/widget/gtk/WindowSurfaceX11Image.cpp
new file mode 100644
index 000000000..5de028804
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11Image.cpp
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "WindowSurfaceX11Image.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "gfxPlatform.h"
+#include "gfx2DGlue.h"
+
+namespace mozilla {
+namespace widget {
+
+WindowSurfaceX11Image::WindowSurfaceX11Image(Display* aDisplay,
+ Window aWindow,
+ Visual* aVisual,
+ unsigned int aDepth)
+ : WindowSurfaceX11(aDisplay, aWindow, aVisual, aDepth)
+{
+}
+
+WindowSurfaceX11Image::~WindowSurfaceX11Image()
+{
+}
+
+already_AddRefed<gfx::DrawTarget>
+WindowSurfaceX11Image::Lock(const LayoutDeviceIntRegion& aRegion)
+{
+ gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+ gfx::IntSize size(bounds.XMost(), bounds.YMost());
+
+ if (!mWindowSurface || mWindowSurface->CairoStatus() ||
+ !(size <= mWindowSurface->GetSize())) {
+ mWindowSurface = new gfxXlibSurface(mDisplay, mWindow, mVisual, size);
+ }
+ if (mWindowSurface->CairoStatus()) {
+ return nullptr;
+ }
+
+ if (!mImageSurface || mImageSurface->CairoStatus() ||
+ !(size <= mImageSurface->GetSize())) {
+ gfxImageFormat format = SurfaceFormatToImageFormat(mFormat);
+ if (format == gfx::SurfaceFormat::UNKNOWN) {
+ format = mDepth == 32 ?
+ gfx::SurfaceFormat::A8R8G8B8_UINT32 :
+ gfx::SurfaceFormat::X8R8G8B8_UINT32;
+ }
+
+ mImageSurface = new gfxImageSurface(size, format);
+ if (mImageSurface->CairoStatus()) {
+ return nullptr;
+ }
+ }
+
+ gfxImageFormat format = mImageSurface->Format();
+ // Cairo prefers compositing to BGRX instead of BGRA where possible.
+ if (format == gfx::SurfaceFormat::X8R8G8B8_UINT32) {
+ gfx::BackendType backend = gfxVars::ContentBackend();
+ if (!gfx::Factory::DoesBackendSupportDataDrawtarget(backend)) {
+#ifdef USE_SKIA
+ backend = gfx::BackendType::SKIA;
+#else
+ backend = gfx::BackendType::CAIRO;
+#endif
+ }
+ if (backend != gfx::BackendType::CAIRO) {
+ format = gfx::SurfaceFormat::A8R8G8B8_UINT32;
+ }
+ }
+
+ return gfxPlatform::CreateDrawTargetForData(mImageSurface->Data(),
+ mImageSurface->GetSize(),
+ mImageSurface->Stride(),
+ ImageFormatToSurfaceFormat(format));
+}
+
+void
+WindowSurfaceX11Image::Commit(const LayoutDeviceIntRegion& aInvalidRegion)
+{
+ RefPtr<gfx::DrawTarget> dt =
+ gfx::Factory::CreateDrawTargetForCairoSurface(mWindowSurface->CairoSurface(),
+ mWindowSurface->GetSize());
+ RefPtr<gfx::SourceSurface> surf =
+ gfx::Factory::CreateSourceSurfaceForCairoSurface(mImageSurface->CairoSurface(),
+ mImageSurface->GetSize(),
+ mImageSurface->Format());
+ if (!dt || !surf) {
+ return;
+ }
+
+ gfx::IntRect bounds = aInvalidRegion.GetBounds().ToUnknownRect();
+ gfx::Rect rect(0, 0, bounds.XMost(), bounds.YMost());
+ if (rect.IsEmpty()) {
+ return;
+ }
+
+ uint32_t numRects = aInvalidRegion.GetNumRects();
+ if (numRects != 1) {
+ AutoTArray<IntRect, 32> rects;
+ rects.SetCapacity(numRects);
+ for (auto iter = aInvalidRegion.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();
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/WindowSurfaceX11Image.h b/widget/gtk/WindowSurfaceX11Image.h
new file mode 100644
index 000000000..236e275d6
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11Image.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H
+
+#ifdef MOZ_X11
+
+#include "WindowSurfaceX11.h"
+#include "gfxXlibSurface.h"
+#include "gfxImageSurface.h"
+
+namespace mozilla {
+namespace widget {
+
+class WindowSurfaceX11Image : public WindowSurfaceX11 {
+public:
+ WindowSurfaceX11Image(Display* aDisplay, Window aWindow, Visual* aVisual,
+ unsigned int aDepth);
+ ~WindowSurfaceX11Image();
+
+ already_AddRefed<gfx::DrawTarget> Lock(const LayoutDeviceIntRegion& aRegion) override;
+ void Commit(const LayoutDeviceIntRegion& aInvalidRegion) override;
+ bool IsFallback() const override { return true; }
+
+private:
+ RefPtr<gfxXlibSurface> mWindowSurface;
+ RefPtr<gfxImageSurface> mImageSurface;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // MOZ_X11
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H
diff --git a/widget/gtk/WindowSurfaceXRender.cpp b/widget/gtk/WindowSurfaceXRender.cpp
new file mode 100644
index 000000000..015d623b3
--- /dev/null
+++ b/widget/gtk/WindowSurfaceXRender.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "WindowSurfaceXRender.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "gfxPlatform.h"
+
+namespace mozilla {
+namespace widget {
+
+WindowSurfaceXRender::WindowSurfaceXRender(Display* aDisplay,
+ Window aWindow,
+ Visual* aVisual,
+ unsigned int aDepth)
+ : WindowSurfaceX11(aDisplay, aWindow, aVisual, aDepth)
+ , mXlibSurface(nullptr)
+ , mGC(X11None)
+{
+}
+
+WindowSurfaceXRender::~WindowSurfaceXRender()
+{
+ if (mGC != X11None) {
+ XFreeGC(mDisplay, mGC);
+ }
+}
+
+already_AddRefed<gfx::DrawTarget>
+WindowSurfaceXRender::Lock(const LayoutDeviceIntRegion& aRegion)
+{
+ gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+ gfx::IntSize size(bounds.XMost(), bounds.YMost());
+ if (!mXlibSurface || mXlibSurface->CairoStatus() ||
+ !(size <= mXlibSurface->GetSize())) {
+ mXlibSurface = gfxXlibSurface::Create(DefaultScreenOfDisplay(mDisplay),
+ mVisual,
+ size,
+ mWindow);
+ }
+ if (!mXlibSurface || mXlibSurface->CairoStatus()) {
+ return nullptr;
+ }
+
+ return gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(mXlibSurface, size);
+}
+
+void
+WindowSurfaceXRender::Commit(const LayoutDeviceIntRegion& aInvalidRegion)
+{
+ AutoTArray<XRectangle, 32> xrects;
+ xrects.SetCapacity(aInvalidRegion.GetNumRects());
+
+ for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
+ const LayoutDeviceIntRect &r = iter.Get();
+ XRectangle xrect = { (short)r.x, (short)r.y, (unsigned short)r.width, (unsigned short)r.height };
+ xrects.AppendElement(xrect);
+ }
+
+ if (!mGC) {
+ mGC = XCreateGC(mDisplay, mWindow, 0, nullptr);
+ if (!mGC) {
+ NS_WARNING("Couldn't create X11 graphics context for window!");
+ return;
+ }
+ }
+
+ XSetClipRectangles(mDisplay, mGC, 0, 0, xrects.Elements(), xrects.Length(), YXBanded);
+
+ MOZ_ASSERT(mXlibSurface && mXlibSurface->CairoStatus() == 0,
+ "Attempted to commit invalid surface!");
+ gfx::IntRect bounds = aInvalidRegion.GetBounds().ToUnknownRect();
+ gfx::IntSize size(bounds.XMost(), bounds.YMost());
+ XCopyArea(mDisplay, mXlibSurface->XDrawable(), mWindow, mGC, bounds.x, bounds.y,
+ size.width, size.height, bounds.x, bounds.y);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/WindowSurfaceXRender.h b/widget/gtk/WindowSurfaceXRender.h
new file mode 100644
index 000000000..044fd3412
--- /dev/null
+++ b/widget/gtk/WindowSurfaceXRender.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_XRENDER_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_XRENDER_H
+
+#ifdef MOZ_X11
+
+#include "WindowSurfaceX11.h"
+#include "gfxXlibSurface.h"
+
+namespace mozilla {
+namespace widget {
+
+class WindowSurfaceXRender : public WindowSurfaceX11 {
+public:
+ WindowSurfaceXRender(Display* aDisplay, Window aWindow, Visual* aVisual,
+ unsigned int aDepth);
+ ~WindowSurfaceXRender();
+
+ already_AddRefed<gfx::DrawTarget> Lock(const LayoutDeviceIntRegion& aRegion) override;
+ void Commit(const LayoutDeviceIntRegion& aInvalidRegion) override;
+
+private:
+ RefPtr<gfxXlibSurface> mXlibSurface;
+ GC mGC;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // MOZ_X11
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_XRENDER_H
diff --git a/widget/gtk/X11CompositorWidget.cpp b/widget/gtk/X11CompositorWidget.cpp
new file mode 100644
index 000000000..05113a0c3
--- /dev/null
+++ b/widget/gtk/X11CompositorWidget.cpp
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "X11CompositorWidget.h"
+
+#include "gfxPlatformGtk.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/widget/InProcessCompositorWidget.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsWindow.h"
+
+namespace mozilla {
+namespace widget {
+
+X11CompositorWidget::X11CompositorWidget(const CompositorWidgetInitData& aInitData,
+ nsWindow* aWindow)
+ : mWidget(aWindow)
+{
+ // If we have a nsWindow, then grab the already existing display connection
+ // If we don't, then use the init data to connect to the display
+ if (aWindow) {
+ mXDisplay = aWindow->XDisplay();
+ } else {
+ mXDisplay = XOpenDisplay(aInitData.XDisplayString().get());
+ }
+ mXWindow = (Window)aInitData.XWindow();
+
+ // Grab the window's visual and depth
+ XWindowAttributes windowAttrs;
+ XGetWindowAttributes(mXDisplay, mXWindow, &windowAttrs);
+
+ Visual* visual = windowAttrs.visual;
+ int depth = windowAttrs.depth;
+
+ // Initialize the window surface provider
+ mProvider.Initialize(
+ mXDisplay,
+ mXWindow,
+ visual,
+ depth
+ );
+
+ mClientSize = aInitData.InitialClientSize();
+}
+
+X11CompositorWidget::~X11CompositorWidget()
+{
+ mProvider.CleanupResources();
+
+ // If we created our own display connection, we need to destroy it
+ if (!mWidget && mXDisplay) {
+ XCloseDisplay(mXDisplay);
+ mXDisplay = nullptr;
+ }
+}
+
+already_AddRefed<gfx::DrawTarget>
+X11CompositorWidget::StartRemoteDrawing()
+{
+ return nullptr;
+}
+void
+X11CompositorWidget::EndRemoteDrawing()
+{
+}
+
+already_AddRefed<gfx::DrawTarget>
+X11CompositorWidget::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion,
+ layers::BufferMode* aBufferMode)
+{
+ return mProvider.StartRemoteDrawingInRegion(aInvalidRegion,
+ aBufferMode);
+}
+
+void X11CompositorWidget::EndRemoteDrawingInRegion(gfx::DrawTarget* aDrawTarget,
+ LayoutDeviceIntRegion& aInvalidRegion)
+{
+ mProvider.EndRemoteDrawingInRegion(aDrawTarget,
+ aInvalidRegion);
+}
+
+nsIWidget* X11CompositorWidget::RealWidget()
+{
+ return mWidget;
+}
+
+void
+X11CompositorWidget::NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize)
+{
+ mClientSize = aClientSize;
+}
+
+LayoutDeviceIntSize
+X11CompositorWidget::GetClientSize()
+{
+ return mClientSize;
+}
+
+uintptr_t
+X11CompositorWidget::GetWidgetKey()
+{
+ return reinterpret_cast<uintptr_t>(mWidget);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/X11CompositorWidget.h b/widget/gtk/X11CompositorWidget.h
new file mode 100644
index 000000000..c0e0edeb3
--- /dev/null
+++ b/widget/gtk/X11CompositorWidget.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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_X11CompositorWidget_h
+#define widget_gtk_X11CompositorWidget_h
+
+#include "mozilla/widget/CompositorWidget.h"
+#include "WindowSurfaceProvider.h"
+
+class nsIWidget;
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+class CompositorWidgetDelegate
+{
+public:
+ virtual void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) = 0;
+};
+
+class X11CompositorWidget
+ : public CompositorWidget
+ , public CompositorWidgetDelegate
+{
+public:
+ X11CompositorWidget(const CompositorWidgetInitData& aInitData,
+ nsWindow* aWindow = nullptr);
+ ~X11CompositorWidget();
+
+ // CompositorWidget Overrides
+
+ already_AddRefed<gfx::DrawTarget> StartRemoteDrawing() override;
+ void EndRemoteDrawing() override;
+
+ already_AddRefed<gfx::DrawTarget>
+ StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion,
+ layers::BufferMode* aBufferMode) override;
+ void EndRemoteDrawingInRegion(gfx::DrawTarget* aDrawTarget,
+ LayoutDeviceIntRegion& aInvalidRegion) override;
+ uintptr_t GetWidgetKey() override;
+
+ void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) override;
+ LayoutDeviceIntSize GetClientSize() override;
+
+ nsIWidget* RealWidget() override;
+ X11CompositorWidget* AsX11() override { return this; }
+ CompositorWidgetDelegate* AsDelegate() override { return this; }
+
+ Display* XDisplay() const { return mXDisplay; }
+ Window XWindow() const { return mXWindow; }
+
+protected:
+ nsWindow* mWidget;
+
+private:
+ LayoutDeviceIntSize mClientSize;
+
+ Display* mXDisplay;
+ Window mXWindow;
+ WindowSurfaceProvider mProvider;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_gtk_X11CompositorWidget_h
diff --git a/widget/gtk/compat-gtk3/gdk/gdkversionmacros.h b/widget/gtk/compat-gtk3/gdk/gdkversionmacros.h
new file mode 100644
index 000000000..068c54b54
--- /dev/null
+++ b/widget/gtk/compat-gtk3/gdk/gdkversionmacros.h
@@ -0,0 +1,31 @@
+#ifndef GDKVERSIONMACROS_WRAPPER_H
+#define GDKVERSIONMACROS_WRAPPER_H
+
+/**
+ * Suppress all GTK3 deprecated warnings as deprecated functions are often
+ * used for GTK2 compatibility.
+ *
+ * GDK_VERSION_MIN_REQUIRED cannot be used to suppress warnings for functions
+ * deprecated in 3.0, but still needs to be set because gdkversionmacros.h
+ * asserts that GDK_VERSION_MAX_ALLOWED >= GDK_VERSION_MIN_REQUIRED and
+ * GDK_VERSION_MIN_REQUIRED >= GDK_VERSION_3_0.
+ *
+ * Setting GDK_DISABLE_DEPRECATION_WARNINGS would also disable
+ * GDK_UNAVAILABLE() warnings, which are useful.
+ */
+
+#define GDK_VERSION_MIN_REQUIRED GDK_VERSION_3_0
+
+#include_next <gdk/gdkversionmacros.h>
+
+/* GDK_AVAILABLE_IN_ALL was introduced in 3.10 */
+#ifndef GDK_AVAILABLE_IN_ALL
+#define GDK_AVAILABLE_IN_ALL
+#endif
+
+#undef GDK_DEPRECATED
+#define GDK_DEPRECATED GDK_AVAILABLE_IN_ALL
+#undef GDK_DEPRECATED_FOR
+#define GDK_DEPRECATED_FOR(f) GDK_AVAILABLE_IN_ALL
+
+#endif /* GDKVERSIONMACROS_WRAPPER_H */
diff --git a/widget/gtk/compat-gtk3/gtk/gtkenums.h b/widget/gtk/compat-gtk3/gtk/gtkenums.h
new file mode 100644
index 000000000..68e4d736d
--- /dev/null
+++ b/widget/gtk/compat-gtk3/gtk/gtkenums.h
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 GTKENUMS_WRAPPER_H
+#define GTKENUMS_WRAPPER_H
+
+#include_next <gtk/gtkenums.h>
+
+#include <gtk/gtkversion.h>
+
+#if !GTK_CHECK_VERSION(3, 6, 0)
+enum GtkInputPurpose
+{
+ GTK_INPUT_PURPOSE_FREE_FORM,
+ GTK_INPUT_PURPOSE_ALPHA,
+ GTK_INPUT_PURPOSE_DIGITS,
+ GTK_INPUT_PURPOSE_NUMBER,
+ GTK_INPUT_PURPOSE_PHONE,
+ GTK_INPUT_PURPOSE_URL,
+ GTK_INPUT_PURPOSE_EMAIL,
+ GTK_INPUT_PURPOSE_NAME,
+ GTK_INPUT_PURPOSE_PASSWORD,
+ GTK_INPUT_PURPOSE_PIN
+};
+#endif // 3.6.0
+
+#endif /* GTKENUMS_WRAPPER_H */
diff --git a/widget/gtk/compat/gdk/gdkdnd.h b/widget/gtk/compat/gdk/gdkdnd.h
new file mode 100644
index 000000000..e20cdea84
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkdnd.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 GDKDND_WRAPPER_H
+#define GDKDND_WRAPPER_H
+
+#define gdk_drag_context_get_actions gdk_drag_context_get_actions_
+#define gdk_drag_context_list_targets gdk_drag_context_list_targets_
+#define gdk_drag_context_get_dest_window gdk_drag_context_get_dest_window_
+#include_next <gdk/gdkdnd.h>
+#undef gdk_drag_context_get_actions
+#undef gdk_drag_context_list_targets
+#undef gdk_drag_context_get_dest_window
+
+static inline GdkDragAction
+gdk_drag_context_get_actions(GdkDragContext *context)
+{
+ return context->actions;
+}
+
+static inline GList *
+gdk_drag_context_list_targets(GdkDragContext *context)
+{
+ return context->targets;
+}
+
+static inline GdkWindow *
+gdk_drag_context_get_dest_window(GdkDragContext *context)
+{
+ return context->dest_window;
+}
+#endif /* GDKDND_WRAPPER_H */
diff --git a/widget/gtk/compat/gdk/gdkkeysyms.h b/widget/gtk/compat/gdk/gdkkeysyms.h
new file mode 100644
index 000000000..d310ae616
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkkeysyms.h
@@ -0,0 +1,266 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 GDKKEYSYMS_WRAPPER_H
+#define GDKKEYSYMS_WRAPPER_H
+
+#include_next <gdk/gdkkeysyms.h>
+
+#ifndef GDK_ISO_Level5_Shift
+#define GDK_ISO_Level5_Shift 0xFE11
+#endif
+
+#ifndef GDK_ISO_Level5_Latch
+#define GDK_ISO_Level5_Latch 0xFE12
+#endif
+
+#ifndef GDK_ISO_Level5_Lock
+#define GDK_ISO_Level5_Lock 0xFE13
+#endif
+
+#ifndef GDK_dead_greek
+#define GDK_dead_greek 0xFE8C
+#endif
+
+#ifndef GDK_ch
+#define GDK_ch 0xFEA0
+#endif
+
+#ifndef GDK_Ch
+#define GDK_Ch 0xFEA1
+#endif
+
+#ifndef GDK_CH
+#define GDK_CH 0xFEA2
+#endif
+
+#ifndef GDK_c_h
+#define GDK_c_h 0xFEA3
+#endif
+
+#ifndef GDK_C_h
+#define GDK_C_h 0xFEA4
+#endif
+
+#ifndef GDK_C_H
+#define GDK_C_H 0xFEA5
+#endif
+
+#ifndef GDK_MonBrightnessUp
+#define GDK_MonBrightnessUp 0x1008FF02
+#endif
+
+#ifndef GDK_MonBrightnessDown
+#define GDK_MonBrightnessDown 0x1008FF03
+#endif
+
+#ifndef GDK_AudioLowerVolume
+#define GDK_AudioLowerVolume 0x1008FF11
+#endif
+
+#ifndef GDK_AudioMute
+#define GDK_AudioMute 0x1008FF12
+#endif
+
+#ifndef GDK_AudioRaiseVolume
+#define GDK_AudioRaiseVolume 0x1008FF13
+#endif
+
+#ifndef GDK_AudioPlay
+#define GDK_AudioPlay 0x1008FF14
+#endif
+
+#ifndef GDK_AudioStop
+#define GDK_AudioStop 0x1008FF15
+#endif
+
+#ifndef GDK_AudioPrev
+#define GDK_AudioPrev 0x1008FF16
+#endif
+
+#ifndef GDK_AudioNext
+#define GDK_AudioNext 0x1008FF17
+#endif
+
+#ifndef GDK_HomePage
+#define GDK_HomePage 0x1008FF18
+#endif
+
+#ifndef GDK_Mail
+#define GDK_Mail 0x1008FF19
+#endif
+
+#ifndef GDK_Search
+#define GDK_Search 0x1008FF1B
+#endif
+
+#ifndef GDK_AudioRecord
+#define GDK_AudioRecord 0x1008FF1C
+#endif
+
+#ifndef GDK_Back
+#define GDK_Back 0x1008FF26
+#endif
+
+#ifndef GDK_Forward
+#define GDK_Forward 0x1008FF27
+#endif
+
+#ifndef GDK_Stop
+#define GDK_Stop 0x1008FF28
+#endif
+
+#ifndef GDK_Refresh
+#define GDK_Refresh 0x1008FF29
+#endif
+
+#ifndef GDK_PowerOff
+#define GDK_PowerOff 0x1008FF2A
+#endif
+
+#ifndef GDK_Eject
+#define GDK_Eject 0x1008FF2C
+#endif
+
+#ifndef GDK_AudioPause
+#define GDK_AudioPause 0x1008FF31
+#endif
+
+#ifndef GDK_BrightnessAdjust
+#define GDK_BrightnessAdjust 0x1008FF3B
+#endif
+
+#ifndef GDK_AudioRewind
+#define GDK_AudioRewind 0x1008FF3E
+#endif
+
+#ifndef GDK_Launch0
+#define GDK_Launch0 0x1008FF40
+#endif
+
+#ifndef GDK_Launch1
+#define GDK_Launch1 0x1008FF41
+#endif
+
+#ifndef GDK_Launch2
+#define GDK_Launch2 0x1008FF42
+#endif
+
+#ifndef GDK_Launch3
+#define GDK_Launch3 0x1008FF43
+#endif
+
+#ifndef GDK_Launch4
+#define GDK_Launch4 0x1008FF44
+#endif
+
+#ifndef GDK_Launch5
+#define GDK_Launch5 0x1008FF45
+#endif
+
+#ifndef GDK_Launch6
+#define GDK_Launch6 0x1008FF46
+#endif
+
+#ifndef GDK_Launch7
+#define GDK_Launch7 0x1008FF47
+#endif
+
+#ifndef GDK_Launch8
+#define GDK_Launch8 0x1008FF48
+#endif
+
+#ifndef GDK_Launch9
+#define GDK_Launch9 0x1008FF49
+#endif
+
+#ifndef GDK_LaunchA
+#define GDK_LaunchA 0x1008FF4A
+#endif
+
+#ifndef GDK_LaunchB
+#define GDK_LaunchB 0x1008FF4B
+#endif
+
+#ifndef GDK_LaunchC
+#define GDK_LaunchC 0x1008FF4C
+#endif
+
+#ifndef GDK_LaunchD
+#define GDK_LaunchD 0x1008FF4D
+#endif
+
+#ifndef GDK_LaunchE
+#define GDK_LaunchE 0x1008FF4E
+#endif
+
+#ifndef GDK_LaunchF
+#define GDK_LaunchF 0x1008FF4F
+#endif
+
+#ifndef GDK_Copy
+#define GDK_Copy 0x1008FF57
+#endif
+
+#ifndef GDK_Cut
+#define GDK_Cut 0x1008FF58
+#endif
+
+#ifndef GDK_Paste
+#define GDK_Paste 0x1008FF6D
+#endif
+
+#ifndef GDK_Reload
+#define GDK_Reload 0x1008FF73
+#endif
+
+#ifndef GDK_AudioRandomPlay
+#define GDK_AudioRandomPlay 0x1008FF99
+#endif
+
+#ifndef GDK_Subtitle
+#define GDK_Subtitle 0x1008FF9A
+#endif
+
+#ifndef GDK_Red
+#define GDK_Red 0x1008FFA3
+#endif
+
+#ifndef GDK_Green
+#define GDK_Green 0x1008FFA4
+#endif
+
+#ifndef GDK_Yellow
+#define GDK_Yellow 0x1008FFA5
+#endif
+
+#ifndef GDK_Blue
+#define GDK_Blue 0x1008FFA6
+#endif
+
+#ifndef GDK_TouchpadToggle
+#define GDK_TouchpadToggle 0x1008FFA9
+#endif
+
+#ifndef GDK_TouchpadOn
+#define GDK_TouchpadOn 0x1008FFB0
+#endif
+
+#ifndef GDK_TouchpadOff
+#define GDK_TouchpadOff 0x1008ffb1
+#endif
+
+#ifndef GDK_LogWindowTree
+#define GDK_LogWindowTree 0x1008FE24
+#endif
+
+#ifndef GDK_LogGrabInfo
+#define GDK_LogGrabInfo 0x1008FE25
+#endif
+
+#ifndef GDK_Sleep
+#define GDK_Sleep 0x1008FF2F
+#endif
+
+#endif /* GDKKEYSYMS_WRAPPER_H */
diff --git a/widget/gtk/compat/gdk/gdkvisual.h b/widget/gtk/compat/gdk/gdkvisual.h
new file mode 100644
index 000000000..6476b4494
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkvisual.h
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 GDKVISUAL_WRAPPER_H
+#define GDKVISUAL_WRAPPER_H
+
+#define gdk_visual_get_depth gdk_visual_get_depth_
+#include_next <gdk/gdkvisual.h>
+#undef gdk_visual_get_depth
+
+static inline gint
+gdk_visual_get_depth(GdkVisual *visual)
+{
+ return visual->depth;
+}
+#endif /* GDKVISUAL_WRAPPER_H */
diff --git a/widget/gtk/compat/gdk/gdkwindow.h b/widget/gtk/compat/gdk/gdkwindow.h
new file mode 100644
index 000000000..40efbb07d
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkwindow.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 GDKWINDOW_WRAPPER_H
+#define GDKWINDOW_WRAPPER_H
+
+#define gdk_window_get_display gdk_window_get_display_
+#define gdk_window_get_screen gdk_window_get_screen_
+#include_next <gdk/gdkwindow.h>
+#undef gdk_window_get_display
+#undef gdk_window_get_screen
+
+static inline GdkDisplay *
+gdk_window_get_display(GdkWindow *window)
+{
+ return gdk_drawable_get_display(GDK_DRAWABLE(window));
+}
+
+static inline GdkScreen*
+gdk_window_get_screen(GdkWindow *window)
+{
+ return gdk_drawable_get_screen (window);
+}
+
+#if GDK_PIXBUF_MAJOR == 2 && GDK_PIXBUF_MINOR < 18
+static inline gboolean
+gdk_window_is_destroyed(GdkWindow *window)
+{
+ return GDK_WINDOW_OBJECT(window)->destroyed;
+}
+#endif
+#endif /* GDKWINDOW_WRAPPER_H */
diff --git a/widget/gtk/compat/gdk/gdkx.h b/widget/gtk/compat/gdk/gdkx.h
new file mode 100644
index 000000000..240c12e30
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkx.h
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 GDKX_WRAPPER_H
+#define GDKX_WRAPPER_H
+
+#include <gtk/gtkversion.h>
+
+#define gdk_x11_window_foreign_new_for_display gdk_x11_window_foreign_new_for_display_
+#define gdk_x11_window_lookup_for_display gdk_x11_window_lookup_for_display_
+#define gdk_x11_window_get_xid gdk_x11_window_get_xid_
+#if !GTK_CHECK_VERSION(2,24,0)
+#define gdk_x11_set_sm_client_id gdk_x11_set_sm_client_id_
+#endif
+#include_next <gdk/gdkx.h>
+#undef gdk_x11_window_foreign_new_for_display
+#undef gdk_x11_window_lookup_for_display
+#undef gdk_x11_window_get_xid
+
+static inline GdkWindow *
+gdk_x11_window_foreign_new_for_display(GdkDisplay *display, Window window)
+{
+ return gdk_window_foreign_new_for_display(display, window);
+}
+
+static inline GdkWindow *
+gdk_x11_window_lookup_for_display(GdkDisplay *display, Window window)
+{
+ return gdk_window_lookup_for_display(display, window);
+}
+
+static inline Window
+gdk_x11_window_get_xid(GdkWindow *window)
+{
+ return(GDK_WINDOW_XWINDOW(window));
+}
+
+#ifndef GDK_IS_X11_DISPLAY
+#define GDK_IS_X11_DISPLAY(a) (true)
+#endif
+
+#if !GTK_CHECK_VERSION(2,24,0)
+#undef gdk_x11_set_sm_client_id
+static inline void
+gdk_x11_set_sm_client_id (const gchar *sm_client_id)
+{
+ gdk_set_sm_client_id(sm_client_id);
+}
+#endif
+#endif /* GDKX_WRAPPER_H */
diff --git a/widget/gtk/compat/glib/gmem.h b/widget/gtk/compat/glib/gmem.h
new file mode 100644
index 000000000..1e728197e
--- /dev/null
+++ b/widget/gtk/compat/glib/gmem.h
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 GMEM_WRAPPER_H
+#define GMEM_WRAPPER_H
+
+#define g_malloc_n g_malloc_n_
+#define g_malloc0_n g_malloc0_n_
+#define g_realloc_n g_realloc_n_
+#include_next <glib/gmem.h>
+#undef g_malloc_n
+#undef g_malloc0_n
+#undef g_realloc_n
+
+#include <glib/gmessages.h>
+
+#undef g_new
+#define g_new(type, num) \
+ ((type *) g_malloc_n((num), sizeof(type)))
+
+#undef g_new0
+#define g_new0(type, num) \
+ ((type *) g_malloc0_n((num), sizeof(type)))
+
+#undef g_renew
+#define g_renew(type, ptr, num) \
+ ((type *) g_realloc_n(ptr, (num), sizeof(type)))
+
+#define _CHECK_OVERFLOW(num, type_size) \
+ if (G_UNLIKELY(type_size > 0 && num > G_MAXSIZE / type_size)) { \
+ g_error("%s: overflow allocating %" G_GSIZE_FORMAT "*%" G_GSIZE_FORMAT " bytes", \
+ G_STRLOC, num, type_size); \
+ }
+
+static inline gpointer
+g_malloc_n(gsize num, gsize type_size)
+{
+ _CHECK_OVERFLOW(num, type_size)
+ return g_malloc(num * type_size);
+}
+
+static inline gpointer
+g_malloc0_n(gsize num, gsize type_size)
+{
+ _CHECK_OVERFLOW(num, type_size)
+ return g_malloc0(num * type_size);
+}
+
+static inline gpointer
+g_realloc_n(gpointer ptr, gsize num, gsize type_size)
+{
+ _CHECK_OVERFLOW(num, type_size)
+ return g_realloc(ptr, num * type_size);
+}
+#endif /* GMEM_WRAPPER_H */
diff --git a/widget/gtk/compat/gtk/gtkwidget.h b/widget/gtk/compat/gtk/gtkwidget.h
new file mode 100644
index 000000000..b1d4d1405
--- /dev/null
+++ b/widget/gtk/compat/gtk/gtkwidget.h
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 GTKWIDGET_WRAPPER_H
+#define GTKWIDGET_WRAPPER_H
+
+#define gtk_widget_set_mapped gtk_widget_set_mapped_
+#define gtk_widget_get_mapped gtk_widget_get_mapped_
+#define gtk_widget_set_realized gtk_widget_set_realized_
+#define gtk_widget_get_realized gtk_widget_get_realized_
+#include_next <gtk/gtkwidget.h>
+#undef gtk_widget_set_mapped
+#undef gtk_widget_get_mapped
+#undef gtk_widget_set_realized
+#undef gtk_widget_get_realized
+
+#include <gtk/gtkversion.h>
+
+static inline void
+gtk_widget_set_mapped(GtkWidget *widget, gboolean mapped)
+{
+ if (mapped)
+ GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
+ else
+ GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
+}
+
+static inline gboolean
+gtk_widget_get_mapped(GtkWidget *widget)
+{
+ return GTK_WIDGET_MAPPED (widget);
+}
+
+static inline void
+gtk_widget_set_realized(GtkWidget *widget, gboolean realized)
+{
+ if (realized)
+ GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
+ else
+ GTK_WIDGET_UNSET_FLAGS(widget, GTK_REALIZED);
+}
+
+static inline gboolean
+gtk_widget_get_realized(GtkWidget *widget)
+{
+ return GTK_WIDGET_REALIZED (widget);
+}
+
+#endif /* GTKWIDGET_WRAPPER_H */
diff --git a/widget/gtk/compat/gtk/gtkwindow.h b/widget/gtk/compat/gtk/gtkwindow.h
new file mode 100644
index 000000000..488061d40
--- /dev/null
+++ b/widget/gtk/compat/gtk/gtkwindow.h
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 GTKWINDOW_WRAPPER_H
+#define GTKWINDOW_WRAPPER_H
+
+#define gtk_window_group_get_current_grab gtk_window_group_get_current_grab_
+#define gtk_window_get_window_type gtk_window_get_window_type_
+#include_next <gtk/gtkwindow.h>
+#undef gtk_window_group_get_current_grab
+#undef gtk_window_get_window_type
+
+static inline GtkWidget *
+gtk_window_group_get_current_grab(GtkWindowGroup *window_group)
+{
+ if (!window_group->grabs)
+ return NULL;
+
+ return GTK_WIDGET(window_group->grabs->data);
+}
+
+static inline GtkWindowType
+gtk_window_get_window_type(GtkWindow *window)
+{
+ gint type;
+ g_object_get(window, "type", &type, (void*)NULL);
+ return (GtkWindowType)type;
+}
+#endif /* GTKWINDOW_WRAPPER_H */
diff --git a/widget/gtk/crashtests/540078-1.xhtml b/widget/gtk/crashtests/540078-1.xhtml
new file mode 100644
index 000000000..9effb4ed6
--- /dev/null
+++ b/widget/gtk/crashtests/540078-1.xhtml
@@ -0,0 +1,2 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><scrollcorner class="zebra"/></hbox><style style="display: none;">.zebra { -moz-appearance: checkbox; }</style></html>
+
diff --git a/widget/gtk/crashtests/673390-1.html b/widget/gtk/crashtests/673390-1.html
new file mode 100644
index 000000000..8463f67f0
--- /dev/null
+++ b/widget/gtk/crashtests/673390-1.html
@@ -0,0 +1 @@
+<div style="-moz-appearance: progresschunk; position: fixed"></div>
diff --git a/widget/gtk/crashtests/crashtests.list b/widget/gtk/crashtests/crashtests.list
new file mode 100644
index 000000000..9ae47c223
--- /dev/null
+++ b/widget/gtk/crashtests/crashtests.list
@@ -0,0 +1,2 @@
+load 540078-1.xhtml
+load 673390-1.html
diff --git a/widget/gtk/gtk2drawing.c b/widget/gtk/gtk2drawing.c
new file mode 100644
index 000000000..7fcac4de6
--- /dev/null
+++ b/widget/gtk/gtk2drawing.c
@@ -0,0 +1,3502 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+/*
+ * This file contains painting functions for each of the gtk2 widgets.
+ * Adapted from the gtkdrawing.c, and gtk+2.0 source.
+ */
+
+#include <gtk/gtk.h>
+#include <gdk/gdkprivate.h>
+#include <string.h>
+#include "gtkdrawing.h"
+#include "mozilla/Assertions.h"
+#include "prinrval.h"
+
+#include <math.h>
+#include <stdbool.h> // for MOZ_ASSERT_UNREACHABLE
+
+#define XTHICKNESS(style) (style->xthickness)
+#define YTHICKNESS(style) (style->ythickness)
+#define WINDOW_IS_MAPPED(window) ((window) && GDK_IS_WINDOW(window) && gdk_window_is_visible(window))
+
+static GtkWidget* gProtoWindow;
+static GtkWidget* gProtoLayout;
+static GtkWidget* gButtonWidget;
+static GtkWidget* gToggleButtonWidget;
+static GtkWidget* gButtonArrowWidget;
+static GtkWidget* gCheckboxWidget;
+static GtkWidget* gRadiobuttonWidget;
+static GtkWidget* gHorizScrollbarWidget;
+static GtkWidget* gVertScrollbarWidget;
+static GtkWidget* gSpinWidget;
+static GtkWidget* gHScaleWidget;
+static GtkWidget* gVScaleWidget;
+static GtkWidget* gEntryWidget;
+static GtkWidget* gComboBoxWidget;
+static GtkWidget* gComboBoxButtonWidget;
+static GtkWidget* gComboBoxArrowWidget;
+static GtkWidget* gComboBoxSeparatorWidget;
+static GtkWidget* gComboBoxEntryWidget;
+static GtkWidget* gComboBoxEntryTextareaWidget;
+static GtkWidget* gComboBoxEntryButtonWidget;
+static GtkWidget* gComboBoxEntryArrowWidget;
+static GtkWidget* gHandleBoxWidget;
+static GtkWidget* gToolbarWidget;
+static GtkWidget* gFrameWidget;
+static GtkWidget* gStatusbarWidget;
+static GtkWidget* gProgressWidget;
+static GtkWidget* gTabWidget;
+static GtkWidget* gTooltipWidget;
+static GtkWidget* gMenuBarWidget;
+static GtkWidget* gMenuBarItemWidget;
+static GtkWidget* gMenuPopupWidget;
+static GtkWidget* gMenuItemWidget;
+static GtkWidget* gImageMenuItemWidget;
+static GtkWidget* gCheckMenuItemWidget;
+static GtkWidget* gTreeViewWidget;
+static GtkTreeViewColumn* gMiddleTreeViewColumn;
+static GtkWidget* gTreeHeaderCellWidget;
+static GtkWidget* gTreeHeaderSortArrowWidget;
+static GtkWidget* gExpanderWidget;
+static GtkWidget* gToolbarSeparatorWidget;
+static GtkWidget* gMenuSeparatorWidget;
+static GtkWidget* gHPanedWidget;
+static GtkWidget* gVPanedWidget;
+static GtkWidget* gScrolledWindowWidget;
+
+static style_prop_t style_prop_func;
+static gboolean have_arrow_scaling;
+static gboolean is_initialized;
+
+/* Because we have such an unconventional way of drawing widgets, signal to the GTK theme engine
+ that they are drawing for Mozilla instead of a conventional GTK app so they can do any specific
+ things they may want to do. */
+static void
+moz_gtk_set_widget_name(GtkWidget* widget)
+{
+ gtk_widget_set_name(widget, "MozillaGtkWidget");
+}
+
+gint
+moz_gtk_enable_style_props(style_prop_t styleGetProp)
+{
+ style_prop_func = styleGetProp;
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_window_widget()
+{
+ if (!gProtoWindow) {
+ gProtoWindow = gtk_window_new(GTK_WINDOW_POPUP);
+ gtk_widget_realize(gProtoWindow);
+ moz_gtk_set_widget_name(gProtoWindow);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+setup_widget_prototype(GtkWidget* widget)
+{
+ ensure_window_widget();
+ if (!gProtoLayout) {
+ gProtoLayout = gtk_fixed_new();
+ gtk_container_add(GTK_CONTAINER(gProtoWindow), gProtoLayout);
+ }
+
+ gtk_container_add(GTK_CONTAINER(gProtoLayout), widget);
+ gtk_widget_realize(widget);
+ g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_button_widget()
+{
+ if (!gButtonWidget) {
+ gButtonWidget = gtk_button_new_with_label("M");
+ setup_widget_prototype(gButtonWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_hpaned_widget()
+{
+ if (!gHPanedWidget) {
+ gHPanedWidget = gtk_hpaned_new();
+ setup_widget_prototype(gHPanedWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_vpaned_widget()
+{
+ if (!gVPanedWidget) {
+ gVPanedWidget = gtk_vpaned_new();
+ setup_widget_prototype(gVPanedWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_toggle_button_widget()
+{
+ if (!gToggleButtonWidget) {
+ gToggleButtonWidget = gtk_toggle_button_new();
+ setup_widget_prototype(gToggleButtonWidget);
+ /* toggle button must be set active to get the right style on hover. */
+ GTK_TOGGLE_BUTTON(gToggleButtonWidget)->active = TRUE;
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_button_arrow_widget()
+{
+ if (!gButtonArrowWidget) {
+ ensure_toggle_button_widget();
+
+ gButtonArrowWidget = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
+ gtk_container_add(GTK_CONTAINER(gToggleButtonWidget), gButtonArrowWidget);
+ gtk_widget_realize(gButtonArrowWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_checkbox_widget()
+{
+ if (!gCheckboxWidget) {
+ gCheckboxWidget = gtk_check_button_new_with_label("M");
+ setup_widget_prototype(gCheckboxWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_radiobutton_widget()
+{
+ if (!gRadiobuttonWidget) {
+ gRadiobuttonWidget = gtk_radio_button_new_with_label(NULL, "M");
+ setup_widget_prototype(gRadiobuttonWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_scrollbar_widget()
+{
+ if (!gVertScrollbarWidget) {
+ gVertScrollbarWidget = gtk_vscrollbar_new(NULL);
+ setup_widget_prototype(gVertScrollbarWidget);
+ }
+ if (!gHorizScrollbarWidget) {
+ gHorizScrollbarWidget = gtk_hscrollbar_new(NULL);
+ setup_widget_prototype(gHorizScrollbarWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_spin_widget()
+{
+ if (!gSpinWidget) {
+ gSpinWidget = gtk_spin_button_new(NULL, 1, 0);
+ setup_widget_prototype(gSpinWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_scale_widget()
+{
+ if (!gHScaleWidget) {
+ gHScaleWidget = gtk_hscale_new(NULL);
+ setup_widget_prototype(gHScaleWidget);
+ }
+ if (!gVScaleWidget) {
+ gVScaleWidget = gtk_vscale_new(NULL);
+ setup_widget_prototype(gVScaleWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_entry_widget()
+{
+ if (!gEntryWidget) {
+ gEntryWidget = gtk_entry_new();
+ setup_widget_prototype(gEntryWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+/* We need to have pointers to the inner widgets (button, separator, arrow)
+ * of the ComboBox to get the correct rendering from theme engines which
+ * special cases their look. Since the inner layout can change, we ask GTK
+ * to NULL our pointers when they are about to become invalid because the
+ * corresponding widgets don't exist anymore. It's the role of
+ * g_object_add_weak_pointer().
+ * Note that if we don't find the inner widgets (which shouldn't happen), we
+ * fallback to use generic "non-inner" widgets, and they don't need that kind
+ * of weak pointer since they are explicit children of gProtoWindow and as
+ * such GTK holds a strong reference to them. */
+static void
+moz_gtk_get_combo_box_inner_button(GtkWidget *widget, gpointer client_data)
+{
+ if (GTK_IS_TOGGLE_BUTTON(widget)) {
+ gComboBoxButtonWidget = widget;
+ g_object_add_weak_pointer(G_OBJECT(widget),
+ (gpointer) &gComboBoxButtonWidget);
+ gtk_widget_realize(widget);
+ g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+}
+
+static void
+moz_gtk_get_combo_box_button_inner_widgets(GtkWidget *widget,
+ gpointer client_data)
+{
+ if (GTK_IS_SEPARATOR(widget)) {
+ gComboBoxSeparatorWidget = widget;
+ g_object_add_weak_pointer(G_OBJECT(widget),
+ (gpointer) &gComboBoxSeparatorWidget);
+ } else if (GTK_IS_ARROW(widget)) {
+ gComboBoxArrowWidget = widget;
+ g_object_add_weak_pointer(G_OBJECT(widget),
+ (gpointer) &gComboBoxArrowWidget);
+ } else
+ return;
+ gtk_widget_realize(widget);
+ g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+}
+
+static gint
+ensure_combo_box_widgets()
+{
+ GtkWidget* buttonChild;
+
+ if (gComboBoxButtonWidget && gComboBoxArrowWidget)
+ return MOZ_GTK_SUCCESS;
+
+ /* Create a ComboBox if needed */
+ if (!gComboBoxWidget) {
+ gComboBoxWidget = gtk_combo_box_new();
+ setup_widget_prototype(gComboBoxWidget);
+ }
+
+ /* Get its inner Button */
+ gtk_container_forall(GTK_CONTAINER(gComboBoxWidget),
+ moz_gtk_get_combo_box_inner_button,
+ NULL);
+
+ if (gComboBoxButtonWidget) {
+ /* Get the widgets inside the Button */
+ buttonChild = GTK_BIN(gComboBoxButtonWidget)->child;
+ if (GTK_IS_HBOX(buttonChild)) {
+ /* appears-as-list = FALSE, cell-view = TRUE; the button
+ * contains an hbox. This hbox is there because the ComboBox
+ * needs to place a cell renderer, a separator, and an arrow in
+ * the button when appears-as-list is FALSE. */
+ gtk_container_forall(GTK_CONTAINER(buttonChild),
+ moz_gtk_get_combo_box_button_inner_widgets,
+ NULL);
+ } else if(GTK_IS_ARROW(buttonChild)) {
+ /* appears-as-list = TRUE, or cell-view = FALSE;
+ * the button only contains an arrow */
+ gComboBoxArrowWidget = buttonChild;
+ g_object_add_weak_pointer(G_OBJECT(buttonChild), (gpointer)
+ &gComboBoxArrowWidget);
+ gtk_widget_realize(gComboBoxArrowWidget);
+ g_object_set_data(G_OBJECT(gComboBoxArrowWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ } else {
+ /* Shouldn't be reached with current internal gtk implementation; we
+ * use a generic toggle button as last resort fallback to avoid
+ * crashing. */
+ ensure_toggle_button_widget();
+ gComboBoxButtonWidget = gToggleButtonWidget;
+ }
+
+ if (!gComboBoxArrowWidget) {
+ /* Shouldn't be reached with current internal gtk implementation;
+ * we gButtonArrowWidget as last resort fallback to avoid
+ * crashing. */
+ ensure_button_arrow_widget();
+ gComboBoxArrowWidget = gButtonArrowWidget;
+ }
+
+ /* We don't test the validity of gComboBoxSeparatorWidget since there
+ * is none when "appears-as-list" = TRUE or "cell-view" = FALSE; if it
+ * is invalid we just won't paint it. */
+
+ return MOZ_GTK_SUCCESS;
+}
+
+/* We need to have pointers to the inner widgets (entry, button, arrow) of
+ * the ComboBoxEntry to get the correct rendering from theme engines which
+ * special cases their look. Since the inner layout can change, we ask GTK
+ * to NULL our pointers when they are about to become invalid because the
+ * corresponding widgets don't exist anymore. It's the role of
+ * g_object_add_weak_pointer().
+ * Note that if we don't find the inner widgets (which shouldn't happen), we
+ * fallback to use generic "non-inner" widgets, and they don't need that kind
+ * of weak pointer since they are explicit children of gProtoWindow and as
+ * such GTK holds a strong reference to them. */
+static void
+moz_gtk_get_combo_box_entry_inner_widgets(GtkWidget *widget,
+ gpointer client_data)
+{
+ if (GTK_IS_TOGGLE_BUTTON(widget)) {
+ gComboBoxEntryButtonWidget = widget;
+ g_object_add_weak_pointer(G_OBJECT(widget),
+ (gpointer) &gComboBoxEntryButtonWidget);
+ } else if (GTK_IS_ENTRY(widget)) {
+ gComboBoxEntryTextareaWidget = widget;
+ g_object_add_weak_pointer(G_OBJECT(widget),
+ (gpointer) &gComboBoxEntryTextareaWidget);
+ } else
+ return;
+ gtk_widget_realize(widget);
+ g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+}
+
+static void
+moz_gtk_get_combo_box_entry_arrow(GtkWidget *widget, gpointer client_data)
+{
+ if (GTK_IS_ARROW(widget)) {
+ gComboBoxEntryArrowWidget = widget;
+ g_object_add_weak_pointer(G_OBJECT(widget),
+ (gpointer) &gComboBoxEntryArrowWidget);
+ gtk_widget_realize(widget);
+ g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+}
+
+static gint
+ensure_combo_box_entry_widgets()
+{
+ GtkWidget* buttonChild;
+
+ if (gComboBoxEntryTextareaWidget &&
+ gComboBoxEntryButtonWidget &&
+ gComboBoxEntryArrowWidget)
+ return MOZ_GTK_SUCCESS;
+
+ /* Create a ComboBoxEntry if needed */
+ if (!gComboBoxEntryWidget) {
+ gComboBoxEntryWidget = gtk_combo_box_entry_new();
+ setup_widget_prototype(gComboBoxEntryWidget);
+ }
+
+ /* Get its inner Entry and Button */
+ gtk_container_forall(GTK_CONTAINER(gComboBoxEntryWidget),
+ moz_gtk_get_combo_box_entry_inner_widgets,
+ NULL);
+
+ if (!gComboBoxEntryTextareaWidget) {
+ ensure_entry_widget();
+ gComboBoxEntryTextareaWidget = gEntryWidget;
+ }
+
+ if (gComboBoxEntryButtonWidget) {
+ /* Get the Arrow inside the Button */
+ buttonChild = GTK_BIN(gComboBoxEntryButtonWidget)->child;
+ if (GTK_IS_HBOX(buttonChild)) {
+ /* appears-as-list = FALSE, cell-view = TRUE; the button
+ * contains an hbox. This hbox is there because ComboBoxEntry
+ * inherits from ComboBox which needs to place a cell renderer,
+ * a separator, and an arrow in the button when appears-as-list
+ * is FALSE. Here the hbox should only contain an arrow, since
+ * a ComboBoxEntry doesn't need all those widgets in the
+ * button. */
+ gtk_container_forall(GTK_CONTAINER(buttonChild),
+ moz_gtk_get_combo_box_entry_arrow,
+ NULL);
+ } else if(GTK_IS_ARROW(buttonChild)) {
+ /* appears-as-list = TRUE, or cell-view = FALSE;
+ * the button only contains an arrow */
+ gComboBoxEntryArrowWidget = buttonChild;
+ g_object_add_weak_pointer(G_OBJECT(buttonChild), (gpointer)
+ &gComboBoxEntryArrowWidget);
+ gtk_widget_realize(gComboBoxEntryArrowWidget);
+ g_object_set_data(G_OBJECT(gComboBoxEntryArrowWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ } else {
+ /* Shouldn't be reached with current internal gtk implementation;
+ * we use a generic toggle button as last resort fallback to avoid
+ * crashing. */
+ ensure_toggle_button_widget();
+ gComboBoxEntryButtonWidget = gToggleButtonWidget;
+ }
+
+ if (!gComboBoxEntryArrowWidget) {
+ /* Shouldn't be reached with current internal gtk implementation;
+ * we gButtonArrowWidget as last resort fallback to avoid
+ * crashing. */
+ ensure_button_arrow_widget();
+ gComboBoxEntryArrowWidget = gButtonArrowWidget;
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+
+static gint
+ensure_handlebox_widget()
+{
+ if (!gHandleBoxWidget) {
+ gHandleBoxWidget = gtk_handle_box_new();
+ setup_widget_prototype(gHandleBoxWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_toolbar_widget()
+{
+ if (!gToolbarWidget) {
+ ensure_handlebox_widget();
+ gToolbarWidget = gtk_toolbar_new();
+ gtk_container_add(GTK_CONTAINER(gHandleBoxWidget), gToolbarWidget);
+ gtk_widget_realize(gToolbarWidget);
+ g_object_set_data(G_OBJECT(gToolbarWidget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_toolbar_separator_widget()
+{
+ if (!gToolbarSeparatorWidget) {
+ ensure_toolbar_widget();
+ gToolbarSeparatorWidget = GTK_WIDGET(gtk_separator_tool_item_new());
+ setup_widget_prototype(gToolbarSeparatorWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_tooltip_widget()
+{
+ if (!gTooltipWidget) {
+ gTooltipWidget = gtk_window_new(GTK_WINDOW_POPUP);
+ gtk_widget_realize(gTooltipWidget);
+ moz_gtk_set_widget_name(gTooltipWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_tab_widget()
+{
+ if (!gTabWidget) {
+ gTabWidget = gtk_notebook_new();
+ setup_widget_prototype(gTabWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_progress_widget()
+{
+ if (!gProgressWidget) {
+ gProgressWidget = gtk_progress_bar_new();
+ setup_widget_prototype(gProgressWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_statusbar_widget()
+{
+ if (!gStatusbarWidget) {
+ gStatusbarWidget = gtk_statusbar_new();
+ setup_widget_prototype(gStatusbarWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_frame_widget()
+{
+ if (!gFrameWidget) {
+ ensure_statusbar_widget();
+ gFrameWidget = gtk_frame_new(NULL);
+ gtk_container_add(GTK_CONTAINER(gStatusbarWidget), gFrameWidget);
+ gtk_widget_realize(gFrameWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_menu_bar_widget()
+{
+ if (!gMenuBarWidget) {
+ gMenuBarWidget = gtk_menu_bar_new();
+ setup_widget_prototype(gMenuBarWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_menu_bar_item_widget()
+{
+ if (!gMenuBarItemWidget) {
+ ensure_menu_bar_widget();
+ gMenuBarItemWidget = gtk_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(gMenuBarWidget),
+ gMenuBarItemWidget);
+ gtk_widget_realize(gMenuBarItemWidget);
+ g_object_set_data(G_OBJECT(gMenuBarItemWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_menu_popup_widget()
+{
+ if (!gMenuPopupWidget) {
+ ensure_menu_bar_item_widget();
+ gMenuPopupWidget = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(gMenuBarItemWidget),
+ gMenuPopupWidget);
+ gtk_widget_realize(gMenuPopupWidget);
+ g_object_set_data(G_OBJECT(gMenuPopupWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_menu_item_widget()
+{
+ if (!gMenuItemWidget) {
+ ensure_menu_popup_widget();
+ gMenuItemWidget = gtk_menu_item_new_with_label("M");
+ gtk_menu_shell_append(GTK_MENU_SHELL(gMenuPopupWidget),
+ gMenuItemWidget);
+ gtk_widget_realize(gMenuItemWidget);
+ g_object_set_data(G_OBJECT(gMenuItemWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_image_menu_item_widget()
+{
+ if (!gImageMenuItemWidget) {
+ ensure_menu_popup_widget();
+ gImageMenuItemWidget = gtk_image_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(gMenuPopupWidget),
+ gImageMenuItemWidget);
+ gtk_widget_realize(gImageMenuItemWidget);
+ g_object_set_data(G_OBJECT(gImageMenuItemWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_menu_separator_widget()
+{
+ if (!gMenuSeparatorWidget) {
+ ensure_menu_popup_widget();
+ gMenuSeparatorWidget = gtk_separator_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(gMenuPopupWidget),
+ gMenuSeparatorWidget);
+ gtk_widget_realize(gMenuSeparatorWidget);
+ g_object_set_data(G_OBJECT(gMenuSeparatorWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_check_menu_item_widget()
+{
+ if (!gCheckMenuItemWidget) {
+ ensure_menu_popup_widget();
+ gCheckMenuItemWidget = gtk_check_menu_item_new_with_label("M");
+ gtk_menu_shell_append(GTK_MENU_SHELL(gMenuPopupWidget),
+ gCheckMenuItemWidget);
+ gtk_widget_realize(gCheckMenuItemWidget);
+ g_object_set_data(G_OBJECT(gCheckMenuItemWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_tree_view_widget()
+{
+ if (!gTreeViewWidget) {
+ gTreeViewWidget = gtk_tree_view_new();
+ setup_widget_prototype(gTreeViewWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_tree_header_cell_widget()
+{
+ if(!gTreeHeaderCellWidget) {
+ /*
+ * Some GTK engines paint the first and last cell
+ * of a TreeView header with a highlight.
+ * Since we do not know where our widget will be relative
+ * to the other buttons in the TreeView header, we must
+ * paint it as a button that is between two others,
+ * thus ensuring it is neither the first or last button
+ * in the header.
+ * GTK doesn't give us a way to do this explicitly,
+ * so we must paint with a button that is between two
+ * others.
+ */
+
+ GtkTreeViewColumn* firstTreeViewColumn;
+ GtkTreeViewColumn* lastTreeViewColumn;
+
+ ensure_tree_view_widget();
+
+ /* Create and append our three columns */
+ firstTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(firstTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(gTreeViewWidget), firstTreeViewColumn);
+
+ gMiddleTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(gMiddleTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(gTreeViewWidget),
+ gMiddleTreeViewColumn);
+
+ lastTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(lastTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(gTreeViewWidget), lastTreeViewColumn);
+
+ /* Use the middle column's header for our button */
+ gTreeHeaderCellWidget = gMiddleTreeViewColumn->button;
+ gTreeHeaderSortArrowWidget = gMiddleTreeViewColumn->arrow;
+ g_object_set_data(G_OBJECT(gTreeHeaderCellWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ g_object_set_data(G_OBJECT(gTreeHeaderSortArrowWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_expander_widget()
+{
+ if (!gExpanderWidget) {
+ gExpanderWidget = gtk_expander_new("M");
+ setup_widget_prototype(gExpanderWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_scrolled_window_widget()
+{
+ if (!gScrolledWindowWidget) {
+ gScrolledWindowWidget = gtk_scrolled_window_new(NULL, NULL);
+ setup_widget_prototype(gScrolledWindowWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static GtkStateType
+ConvertGtkState(GtkWidgetState* state)
+{
+ if (state->disabled)
+ return GTK_STATE_INSENSITIVE;
+ else if (state->depressed)
+ return (state->inHover ? GTK_STATE_PRELIGHT : GTK_STATE_ACTIVE);
+ else if (state->inHover)
+ return (state->active ? GTK_STATE_ACTIVE : GTK_STATE_PRELIGHT);
+ else
+ return GTK_STATE_NORMAL;
+}
+
+static gint
+TSOffsetStyleGCArray(GdkGC** gcs, gint xorigin, gint yorigin)
+{
+ int i;
+ /* there are 5 gc's in each array, for each of the widget states */
+ for (i = 0; i < 5; ++i)
+ gdk_gc_set_ts_origin(gcs[i], xorigin, yorigin);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+TSOffsetStyleGCs(GtkStyle* style, gint xorigin, gint yorigin)
+{
+ TSOffsetStyleGCArray(style->fg_gc, xorigin, yorigin);
+ TSOffsetStyleGCArray(style->bg_gc, xorigin, yorigin);
+ TSOffsetStyleGCArray(style->light_gc, xorigin, yorigin);
+ TSOffsetStyleGCArray(style->dark_gc, xorigin, yorigin);
+ TSOffsetStyleGCArray(style->mid_gc, xorigin, yorigin);
+ TSOffsetStyleGCArray(style->text_gc, xorigin, yorigin);
+ TSOffsetStyleGCArray(style->base_gc, xorigin, yorigin);
+ gdk_gc_set_ts_origin(style->black_gc, xorigin, yorigin);
+ gdk_gc_set_ts_origin(style->white_gc, xorigin, yorigin);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_init()
+{
+ GtkWidgetClass *entry_class;
+
+ if (is_initialized)
+ return MOZ_GTK_SUCCESS;
+
+ is_initialized = TRUE;
+ have_arrow_scaling = (gtk_major_version > 2 ||
+ (gtk_major_version == 2 && gtk_minor_version >= 12));
+
+ /* Add style property to GtkEntry.
+ * Adding the style property to the normal GtkEntry class means that it
+ * will work without issues inside GtkComboBox and for Spinbuttons. */
+ entry_class = g_type_class_ref(GTK_TYPE_ENTRY);
+ gtk_widget_class_install_style_property(entry_class,
+ g_param_spec_boolean("honors-transparent-bg-hint",
+ "Transparent BG enabling flag",
+ "If TRUE, the theme is able to draw the GtkEntry on non-prefilled background.",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ return MOZ_GTK_SUCCESS;
+}
+
+GdkColormap*
+moz_gtk_widget_get_colormap()
+{
+ /* Child widgets inherit the colormap from the GtkWindow. */
+ ensure_window_widget();
+ return gtk_widget_get_colormap(gProtoWindow);
+}
+
+gint
+moz_gtk_checkbox_get_metrics(gint* indicator_size, gint* indicator_spacing)
+{
+ ensure_checkbox_widget();
+
+ gtk_widget_style_get (gCheckboxWidget,
+ "indicator_size", indicator_size,
+ "indicator_spacing", indicator_spacing,
+ NULL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_radio_get_metrics(gint* indicator_size, gint* indicator_spacing)
+{
+ ensure_radiobutton_widget();
+
+ gtk_widget_style_get (gRadiobuttonWidget,
+ "indicator_size", indicator_size,
+ "indicator_spacing", indicator_spacing,
+ NULL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_focus_outline_size(gint* focus_h_width, gint* focus_v_width)
+{
+ gboolean interior_focus;
+ gint focus_width = 0;
+
+ ensure_entry_widget();
+ gtk_widget_style_get(gEntryWidget,
+ "interior-focus", &interior_focus,
+ "focus-line-width", &focus_width,
+ NULL);
+ if (interior_focus) {
+ *focus_h_width = XTHICKNESS(gEntryWidget->style) + focus_width;
+ *focus_v_width = YTHICKNESS(gEntryWidget->style) + focus_width;
+ } else {
+ *focus_h_width = focus_width;
+ *focus_v_width = focus_width;
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_widget_get_focus(GtkWidget* widget, gboolean* interior_focus,
+ gint* focus_width, gint* focus_pad)
+{
+ gtk_widget_style_get (widget,
+ "interior-focus", interior_focus,
+ "focus-line-width", focus_width,
+ "focus-padding", focus_pad,
+ NULL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_menuitem_get_horizontal_padding(gint* horizontal_padding)
+{
+ ensure_menu_item_widget();
+
+ gtk_widget_style_get (gMenuItemWidget,
+ "horizontal-padding", horizontal_padding,
+ NULL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_checkmenuitem_get_horizontal_padding(gint* horizontal_padding)
+{
+ ensure_check_menu_item_widget();
+
+ gtk_widget_style_get (gCheckMenuItemWidget,
+ "horizontal-padding", horizontal_padding,
+ NULL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_button_get_default_overflow(gint* border_top, gint* border_left,
+ gint* border_bottom, gint* border_right)
+{
+ GtkBorder* default_outside_border;
+
+ ensure_button_widget();
+ gtk_widget_style_get(gButtonWidget,
+ "default-outside-border", &default_outside_border,
+ NULL);
+
+ if (default_outside_border) {
+ *border_top = default_outside_border->top;
+ *border_left = default_outside_border->left;
+ *border_bottom = default_outside_border->bottom;
+ *border_right = default_outside_border->right;
+ gtk_border_free(default_outside_border);
+ } else {
+ *border_top = *border_left = *border_bottom = *border_right = 0;
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_button_get_default_border(gint* border_top, gint* border_left,
+ gint* border_bottom, gint* border_right)
+{
+ GtkBorder* default_border;
+
+ ensure_button_widget();
+ gtk_widget_style_get(gButtonWidget,
+ "default-border", &default_border,
+ NULL);
+
+ if (default_border) {
+ *border_top = default_border->top;
+ *border_left = default_border->left;
+ *border_bottom = default_border->bottom;
+ *border_right = default_border->right;
+ gtk_border_free(default_border);
+ } else {
+ /* see gtkbutton.c */
+ *border_top = *border_left = *border_bottom = *border_right = 1;
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_splitter_get_metrics(gint orientation, gint* size)
+{
+ if (orientation == GTK_ORIENTATION_HORIZONTAL) {
+ ensure_hpaned_widget();
+ gtk_widget_style_get(gHPanedWidget, "handle_size", size, NULL);
+ } else {
+ ensure_vpaned_widget();
+ gtk_widget_style_get(gVPanedWidget, "handle_size", size, NULL);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_button_get_inner_border(GtkWidget* widget, GtkBorder* inner_border)
+{
+ static const GtkBorder default_inner_border = { 1, 1, 1, 1 };
+ GtkBorder *tmp_border;
+
+ gtk_widget_style_get (widget, "inner-border", &tmp_border, NULL);
+
+ if (tmp_border) {
+ *inner_border = *tmp_border;
+ gtk_border_free(tmp_border);
+ }
+ else
+ *inner_border = default_inner_border;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_button_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkReliefStyle relief, GtkWidget* widget,
+ GtkTextDirection direction)
+{
+ GtkShadowType shadow_type;
+ GtkStyle* style = widget->style;
+ GtkStateType button_state = ConvertGtkState(state);
+ gint x = rect->x, y=rect->y, width=rect->width, height=rect->height;
+
+ gboolean interior_focus;
+ gint focus_width, focus_pad;
+
+ moz_gtk_widget_get_focus(widget, &interior_focus, &focus_width, &focus_pad);
+
+ if (WINDOW_IS_MAPPED(drawable)) {
+ gdk_window_set_back_pixmap(drawable, NULL, TRUE);
+ gdk_window_clear_area(drawable, cliprect->x, cliprect->y,
+ cliprect->width, cliprect->height);
+ }
+
+ gtk_widget_set_state(widget, button_state);
+ gtk_widget_set_direction(widget, direction);
+
+ if (state->isDefault)
+ GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_DEFAULT);
+
+ GTK_BUTTON(widget)->relief = relief;
+
+ /* Some theme engines love to cause us pain in that gtk_paint_focus is a
+ no-op on buttons and button-like widgets. They only listen to this flag. */
+ if (state->focused && !state->disabled)
+ GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
+
+ if (!interior_focus && state->focused) {
+ x += focus_width + focus_pad;
+ y += focus_width + focus_pad;
+ width -= 2 * (focus_width + focus_pad);
+ height -= 2 * (focus_width + focus_pad);
+ }
+
+ shadow_type = button_state == GTK_STATE_ACTIVE ||
+ state->depressed ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
+
+ if (state->isDefault && relief == GTK_RELIEF_NORMAL) {
+ /* handle default borders both outside and inside the button */
+ gint default_top, default_left, default_bottom, default_right;
+ moz_gtk_button_get_default_overflow(&default_top, &default_left,
+ &default_bottom, &default_right);
+ x -= default_left;
+ y -= default_top;
+ width += default_left + default_right;
+ height += default_top + default_bottom;
+ gtk_paint_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN, cliprect,
+ widget, "buttondefault", x, y, width, height);
+
+ moz_gtk_button_get_default_border(&default_top, &default_left,
+ &default_bottom, &default_right);
+ x += default_left;
+ y += default_top;
+ width -= (default_left + default_right);
+ height -= (default_top + default_bottom);
+ }
+
+ if (relief != GTK_RELIEF_NONE || state->depressed ||
+ (button_state != GTK_STATE_NORMAL &&
+ button_state != GTK_STATE_INSENSITIVE)) {
+ TSOffsetStyleGCs(style, x, y);
+ /* the following line can trigger an assertion (Crux theme)
+ file ../../gdk/gdkwindow.c: line 1846 (gdk_window_clear_area):
+ assertion `GDK_IS_WINDOW (window)' failed */
+ gtk_paint_box(style, drawable, button_state, shadow_type, cliprect,
+ widget, "button", x, y, width, height);
+ }
+
+ if (state->focused) {
+ if (interior_focus) {
+ x += widget->style->xthickness + focus_pad;
+ y += widget->style->ythickness + focus_pad;
+ width -= 2 * (widget->style->xthickness + focus_pad);
+ height -= 2 * (widget->style->ythickness + focus_pad);
+ } else {
+ x -= focus_width + focus_pad;
+ y -= focus_width + focus_pad;
+ width += 2 * (focus_width + focus_pad);
+ height += 2 * (focus_width + focus_pad);
+ }
+
+ TSOffsetStyleGCs(style, x, y);
+ gtk_paint_focus(style, drawable, button_state, cliprect,
+ widget, "button", x, y, width, height);
+ }
+
+ GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_DEFAULT);
+ GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_toggle_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ gboolean selected, gboolean inconsistent,
+ gboolean isradio, GtkTextDirection direction)
+{
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type = (selected)?GTK_SHADOW_IN:GTK_SHADOW_OUT;
+ gint indicator_size, indicator_spacing;
+ gint x, y, width, height;
+ gint focus_x, focus_y, focus_width, focus_height;
+ GtkWidget *w;
+ GtkStyle *style;
+
+ if (isradio) {
+ moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing);
+ w = gRadiobuttonWidget;
+ } else {
+ moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing);
+ w = gCheckboxWidget;
+ }
+
+ // XXX we should assert rect->height >= indicator_size too
+ // after bug 369581 is fixed.
+ MOZ_ASSERT(rect->width >= indicator_size,
+ "GetMinimumWidgetSize was ignored");
+
+ // Paint it center aligned in the rect.
+ x = rect->x + (rect->width - indicator_size) / 2;
+ y = rect->y + (rect->height - indicator_size) / 2;
+ width = indicator_size;
+ height = indicator_size;
+
+ focus_x = x - indicator_spacing;
+ focus_y = y - indicator_spacing;
+ focus_width = width + 2 * indicator_spacing;
+ focus_height = height + 2 * indicator_spacing;
+
+ style = w->style;
+ TSOffsetStyleGCs(style, x, y);
+
+ gtk_widget_set_sensitive(w, !state->disabled);
+ gtk_widget_set_direction(w, direction);
+ GTK_TOGGLE_BUTTON(w)->active = selected;
+
+ if (isradio) {
+ gtk_paint_option(style, drawable, state_type, shadow_type, cliprect,
+ gRadiobuttonWidget, "radiobutton", x, y,
+ width, height);
+ if (state->focused) {
+ gtk_paint_focus(style, drawable, GTK_STATE_ACTIVE, cliprect,
+ gRadiobuttonWidget, "radiobutton", focus_x, focus_y,
+ focus_width, focus_height);
+ }
+ }
+ else {
+ /*
+ * 'indeterminate' type on checkboxes. In GTK, the shadow type
+ * must also be changed for the state to be drawn.
+ */
+ if (inconsistent) {
+ gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(gCheckboxWidget), TRUE);
+ shadow_type = GTK_SHADOW_ETCHED_IN;
+ } else {
+ gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(gCheckboxWidget), FALSE);
+ }
+
+ gtk_paint_check(style, drawable, state_type, shadow_type, cliprect,
+ gCheckboxWidget, "checkbutton", x, y, width, height);
+ if (state->focused) {
+ gtk_paint_focus(style, drawable, GTK_STATE_ACTIVE, cliprect,
+ gCheckboxWidget, "checkbutton", focus_x, focus_y,
+ focus_width, focus_height);
+ }
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+calculate_button_inner_rect(GtkWidget* button, GdkRectangle* rect,
+ GdkRectangle* inner_rect,
+ GtkTextDirection direction,
+ gboolean ignore_focus)
+{
+ GtkBorder inner_border;
+ gboolean interior_focus;
+ gint focus_width, focus_pad;
+ GtkStyle* style;
+
+ style = button->style;
+
+ /* This mirrors gtkbutton's child positioning */
+ moz_gtk_button_get_inner_border(button, &inner_border);
+ moz_gtk_widget_get_focus(button, &interior_focus,
+ &focus_width, &focus_pad);
+
+ if (ignore_focus)
+ focus_width = focus_pad = 0;
+
+ inner_rect->x = rect->x + XTHICKNESS(style) + focus_width + focus_pad;
+ inner_rect->x += direction == GTK_TEXT_DIR_LTR ?
+ inner_border.left : inner_border.right;
+ inner_rect->y = rect->y + inner_border.top + YTHICKNESS(style) +
+ focus_width + focus_pad;
+ inner_rect->width = MAX(1, rect->width - inner_border.left -
+ inner_border.right - (XTHICKNESS(style) + focus_pad + focus_width) * 2);
+ inner_rect->height = MAX(1, rect->height - inner_border.top -
+ inner_border.bottom - (YTHICKNESS(style) + focus_pad + focus_width) * 2);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+calculate_arrow_rect(GtkWidget* arrow, GdkRectangle* rect,
+ GdkRectangle* arrow_rect, GtkTextDirection direction)
+{
+ /* defined in gtkarrow.c */
+ gfloat arrow_scaling = 0.7;
+ gfloat xalign, xpad;
+ gint extent;
+ GtkMisc* misc = GTK_MISC(arrow);
+
+ if (have_arrow_scaling)
+ gtk_widget_style_get(arrow, "arrow_scaling", &arrow_scaling, NULL);
+
+ extent = MIN((rect->width - misc->xpad * 2),
+ (rect->height - misc->ypad * 2)) * arrow_scaling;
+
+ xalign = direction == GTK_TEXT_DIR_LTR ? misc->xalign : 1.0 - misc->xalign;
+ xpad = misc->xpad + (rect->width - extent) * xalign;
+
+ arrow_rect->x = direction == GTK_TEXT_DIR_LTR ?
+ floor(rect->x + xpad) : ceil(rect->x + xpad);
+ arrow_rect->y = floor(rect->y + misc->ypad +
+ ((rect->height - extent) * misc->yalign));
+
+ arrow_rect->width = arrow_rect->height = extent;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_scrollbar_button_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkScrollbarButtonFlags flags,
+ GtkTextDirection direction)
+{
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type = (state->active) ?
+ GTK_SHADOW_IN : GTK_SHADOW_OUT;
+ GdkRectangle arrow_rect;
+ GtkStyle* style;
+ GtkWidget *scrollbar;
+ GtkArrowType arrow_type;
+ gint arrow_displacement_x, arrow_displacement_y;
+ const char* detail = (flags & MOZ_GTK_STEPPER_VERTICAL) ?
+ "vscrollbar" : "hscrollbar";
+
+ ensure_scrollbar_widget();
+
+ if (flags & MOZ_GTK_STEPPER_VERTICAL)
+ scrollbar = gVertScrollbarWidget;
+ else
+ scrollbar = gHorizScrollbarWidget;
+
+ gtk_widget_set_direction(scrollbar, direction);
+
+ /* Some theme engines (i.e., ClearLooks) check the scrollbar's allocation
+ to determine where it should paint rounded corners on the buttons.
+ We need to trick them into drawing the buttons the way we want them. */
+
+ scrollbar->allocation.x = rect->x;
+ scrollbar->allocation.y = rect->y;
+ scrollbar->allocation.width = rect->width;
+ scrollbar->allocation.height = rect->height;
+
+ if (flags & MOZ_GTK_STEPPER_VERTICAL) {
+ scrollbar->allocation.height *= 5;
+ if (flags & MOZ_GTK_STEPPER_DOWN) {
+ arrow_type = GTK_ARROW_DOWN;
+ if (flags & MOZ_GTK_STEPPER_BOTTOM)
+ scrollbar->allocation.y -= 4 * rect->height;
+ else
+ scrollbar->allocation.y -= rect->height;
+
+ } else {
+ arrow_type = GTK_ARROW_UP;
+ if (flags & MOZ_GTK_STEPPER_BOTTOM)
+ scrollbar->allocation.y -= 3 * rect->height;
+ }
+ } else {
+ scrollbar->allocation.width *= 5;
+ if (flags & MOZ_GTK_STEPPER_DOWN) {
+ arrow_type = GTK_ARROW_RIGHT;
+ if (flags & MOZ_GTK_STEPPER_BOTTOM)
+ scrollbar->allocation.x -= 4 * rect->width;
+ else
+ scrollbar->allocation.x -= rect->width;
+ } else {
+ arrow_type = GTK_ARROW_LEFT;
+ if (flags & MOZ_GTK_STEPPER_BOTTOM)
+ scrollbar->allocation.x -= 3 * rect->width;
+ }
+ }
+
+ style = scrollbar->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_paint_box(style, drawable, state_type, shadow_type, cliprect,
+ scrollbar, detail, rect->x, rect->y,
+ rect->width, rect->height);
+
+ arrow_rect.width = rect->width / 2;
+ arrow_rect.height = rect->height / 2;
+ arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2;
+ arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2;
+
+ if (state_type == GTK_STATE_ACTIVE) {
+ gtk_widget_style_get(scrollbar,
+ "arrow-displacement-x", &arrow_displacement_x,
+ "arrow-displacement-y", &arrow_displacement_y,
+ NULL);
+
+ arrow_rect.x += arrow_displacement_x;
+ arrow_rect.y += arrow_displacement_y;
+ }
+
+ gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect,
+ scrollbar, detail, arrow_type, TRUE, arrow_rect.x,
+ arrow_rect.y, arrow_rect.width, arrow_rect.height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_scrollbar_trough_paint(WidgetNodeType widget,
+ GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStyle* style;
+ GtkScrollbar *scrollbar;
+
+ ensure_scrollbar_widget();
+
+ if (widget == MOZ_GTK_SCROLLBAR_HORIZONTAL)
+ scrollbar = GTK_SCROLLBAR(gHorizScrollbarWidget);
+ else
+ scrollbar = GTK_SCROLLBAR(gVertScrollbarWidget);
+
+ gtk_widget_set_direction(GTK_WIDGET(scrollbar), direction);
+
+ style = GTK_WIDGET(scrollbar)->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_style_apply_default_background(style, drawable, TRUE, GTK_STATE_ACTIVE,
+ cliprect, rect->x, rect->y,
+ rect->width, rect->height);
+
+ gtk_paint_box(style, drawable, GTK_STATE_ACTIVE, GTK_SHADOW_IN, cliprect,
+ GTK_WIDGET(scrollbar), "trough", rect->x, rect->y,
+ rect->width, rect->height);
+
+ if (state->focused) {
+ gtk_paint_focus(style, drawable, GTK_STATE_ACTIVE, cliprect,
+ GTK_WIDGET(scrollbar), "trough",
+ rect->x, rect->y, rect->width, rect->height);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_scrollbar_thumb_paint(WidgetNodeType widget,
+ GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStateType state_type = (state->inHover || state->active) ?
+ GTK_STATE_PRELIGHT : GTK_STATE_NORMAL;
+ GtkShadowType shadow_type = GTK_SHADOW_OUT;
+ GtkStyle* style;
+ GtkScrollbar *scrollbar;
+ GtkAdjustment *adj;
+ gboolean activate_slider;
+
+ ensure_scrollbar_widget();
+
+ if (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL)
+ scrollbar = GTK_SCROLLBAR(gHorizScrollbarWidget);
+ else
+ scrollbar = GTK_SCROLLBAR(gVertScrollbarWidget);
+
+ gtk_widget_set_direction(GTK_WIDGET(scrollbar), direction);
+
+ /* Make sure to set the scrollbar range before painting so that
+ everything is drawn properly. At least the bluecurve (and
+ maybe other) themes don't draw the top or bottom black line
+ surrounding the scrollbar if the theme thinks that it's butted
+ up against the scrollbar arrows. Note the increases of the
+ clip rect below. */
+ adj = gtk_range_get_adjustment(GTK_RANGE(scrollbar));
+
+ if (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL) {
+ adj->page_size = rect->width;
+ }
+ else {
+ adj->page_size = rect->height;
+ }
+
+ adj->lower = 0;
+ adj->value = state->curpos;
+ adj->upper = state->maxpos;
+ gtk_adjustment_changed(adj);
+
+ style = GTK_WIDGET(scrollbar)->style;
+
+ gtk_widget_style_get(GTK_WIDGET(scrollbar), "activate-slider",
+ &activate_slider, NULL);
+
+ if (activate_slider && state->active) {
+ shadow_type = GTK_SHADOW_IN;
+ state_type = GTK_STATE_ACTIVE;
+ }
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_paint_slider(style, drawable, state_type, shadow_type, cliprect,
+ GTK_WIDGET(scrollbar), "slider", rect->x, rect->y,
+ rect->width, rect->height,
+ (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL) ?
+ GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_spin_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyle* style;
+
+ ensure_spin_widget();
+ gtk_widget_set_direction(gSpinWidget, direction);
+ style = gSpinWidget->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL,
+ gSpinWidget, "spinbutton",
+ rect->x, rect->y, rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_spin_updown_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ gboolean isDown, GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GdkRectangle arrow_rect;
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type = state_type == GTK_STATE_ACTIVE ?
+ GTK_SHADOW_IN : GTK_SHADOW_OUT;
+ GtkStyle* style;
+
+ ensure_spin_widget();
+ style = gSpinWidget->style;
+ gtk_widget_set_direction(gSpinWidget, direction);
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_box(style, drawable, state_type, shadow_type, NULL, gSpinWidget,
+ isDown ? "spinbutton_down" : "spinbutton_up",
+ rect->x, rect->y, rect->width, rect->height);
+
+ /* hard code these values */
+ arrow_rect.width = 6;
+ arrow_rect.height = 6;
+ arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2;
+ arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2;
+ arrow_rect.y += isDown ? -1 : 1;
+
+ gtk_paint_arrow(style, drawable, state_type, shadow_type, NULL,
+ gSpinWidget, "spinbutton",
+ isDown ? GTK_ARROW_DOWN : GTK_ARROW_UP, TRUE,
+ arrow_rect.x, arrow_rect.y,
+ arrow_rect.width, arrow_rect.height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_scale_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkOrientation flags, GtkTextDirection direction)
+{
+ gint x = 0, y = 0;
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkStyle* style;
+ GtkWidget* widget;
+
+ ensure_scale_widget();
+ widget = ((flags == GTK_ORIENTATION_HORIZONTAL) ? gHScaleWidget : gVScaleWidget);
+ gtk_widget_set_direction(widget, direction);
+
+ style = widget->style;
+
+ if (flags == GTK_ORIENTATION_HORIZONTAL) {
+ x = XTHICKNESS(style);
+ y++;
+ }
+ else {
+ x++;
+ y = YTHICKNESS(style);
+ }
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_paint_box(style, drawable, GTK_STATE_ACTIVE, GTK_SHADOW_IN, cliprect,
+ widget, "trough", rect->x + x, rect->y + y,
+ rect->width - 2*x, rect->height - 2*y);
+
+ if (state->focused)
+ gtk_paint_focus(style, drawable, state_type, cliprect, widget, "trough",
+ rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_scale_thumb_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkOrientation flags, GtkTextDirection direction)
+{
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkStyle* style;
+ GtkWidget* widget;
+ gint thumb_width, thumb_height, x, y;
+
+ ensure_scale_widget();
+ widget = ((flags == GTK_ORIENTATION_HORIZONTAL) ? gHScaleWidget : gVScaleWidget);
+ gtk_widget_set_direction(widget, direction);
+
+ style = widget->style;
+
+ /* determine the thumb size, and position the thumb in the center in the opposite axis */
+ if (flags == GTK_ORIENTATION_HORIZONTAL) {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL, &thumb_width, &thumb_height);
+ x = rect->x;
+ y = rect->y + (rect->height - thumb_height) / 2;
+ }
+ else {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_height, &thumb_width);
+ x = rect->x + (rect->width - thumb_width) / 2;
+ y = rect->y;
+ }
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_slider(style, drawable, state_type, GTK_SHADOW_OUT, cliprect,
+ widget, (flags == GTK_ORIENTATION_HORIZONTAL) ? "hscale" : "vscale",
+ x, y, thumb_width, thumb_height, flags);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_gripper_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type;
+ GtkStyle* style;
+
+ ensure_handlebox_widget();
+ gtk_widget_set_direction(gHandleBoxWidget, direction);
+
+ style = gHandleBoxWidget->style;
+ shadow_type = GTK_HANDLE_BOX(gHandleBoxWidget)->shadow_type;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_box(style, drawable, state_type, shadow_type, cliprect,
+ gHandleBoxWidget, "handlebox_bin", rect->x, rect->y,
+ rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_hpaned_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state)
+{
+ GtkStateType hpaned_state = ConvertGtkState(state);
+
+ ensure_hpaned_widget();
+ gtk_paint_handle(gHPanedWidget->style, drawable, hpaned_state,
+ GTK_SHADOW_NONE, cliprect, gHPanedWidget, "paned",
+ rect->x, rect->y, rect->width, rect->height,
+ GTK_ORIENTATION_VERTICAL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_vpaned_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state)
+{
+ GtkStateType vpaned_state = ConvertGtkState(state);
+
+ ensure_vpaned_widget();
+ gtk_paint_handle(gVPanedWidget->style, drawable, vpaned_state,
+ GTK_SHADOW_NONE, cliprect, gVPanedWidget, "paned",
+ rect->x, rect->y, rect->width, rect->height,
+ GTK_ORIENTATION_HORIZONTAL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_entry_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkWidget* widget, GtkTextDirection direction)
+{
+ GtkStateType bg_state = state->disabled ?
+ GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL;
+ gint x, y, width = rect->width, height = rect->height;
+ GtkStyle* style;
+ gboolean interior_focus;
+ gboolean theme_honors_transparency = FALSE;
+ gint focus_width;
+ int draw_focus_outline_only = state->depressed; // NS_THEME_FOCUS_OUTLINE
+
+ gtk_widget_set_direction(widget, direction);
+
+ style = widget->style;
+
+ gtk_widget_style_get(widget,
+ "interior-focus", &interior_focus,
+ "focus-line-width", &focus_width,
+ "honors-transparent-bg-hint", &theme_honors_transparency,
+ NULL);
+
+ if (draw_focus_outline_only) {
+ // Inflate the given 'rect' with the focus outline size.
+ gint h, v;
+ moz_gtk_get_focus_outline_size(&h, &v);
+ rect->x -= h;
+ rect->width += 2 * h;
+ rect->y -= v;
+ rect->height += 2 * v;
+ width = rect->width;
+ height = rect->height;
+ }
+
+ /* gtkentry.c uses two windows, one for the entire widget and one for the
+ * text area inside it. The background of both windows is set to the "base"
+ * color of the new state in gtk_entry_state_changed, but only the inner
+ * textarea window uses gtk_paint_flat_box when exposed */
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ /* This gets us a lovely greyish disabledish look */
+ gtk_widget_set_sensitive(widget, !state->disabled);
+
+ /* GTK fills the outer widget window with the base color before drawing the widget.
+ * Some older themes rely on this behavior, but many themes nowadays use rounded
+ * corners on their widgets. While most GTK apps are blissfully unaware of this
+ * problem due to their use of the default window background, we render widgets on
+ * many kinds of backgrounds on the web.
+ * If the theme is able to cope with transparency, then we can skip pre-filling
+ * and notify the theme it will paint directly on the canvas. */
+ if (theme_honors_transparency) {
+ g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ } else {
+ GdkRectangle clipped_rect;
+ gdk_rectangle_intersect(rect, cliprect, &clipped_rect);
+ if (clipped_rect.width != 0) {
+ gdk_draw_rectangle(drawable, style->base_gc[bg_state], TRUE,
+ clipped_rect.x, clipped_rect.y,
+ clipped_rect.width, clipped_rect.height);
+ }
+ g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(FALSE));
+ }
+
+ if (!draw_focus_outline_only) {
+ /* Get the position of the inner window, see _gtk_entry_get_borders */
+ x = XTHICKNESS(style);
+ y = YTHICKNESS(style);
+
+ if (!interior_focus) {
+ x += focus_width;
+ y += focus_width;
+ }
+
+ /* Simulate an expose of the inner window */
+ gtk_paint_flat_box(style, drawable, bg_state, GTK_SHADOW_NONE,
+ cliprect, widget, "entry_bg", rect->x + x,
+ rect->y + y, rect->width - 2*x, rect->height - 2*y);
+ }
+
+ /* Now paint the shadow and focus border.
+ * We do like in gtk_entry_draw_frame, we first draw the shadow, a tad
+ * smaller when focused if the focus is not interior, then the focus. */
+ x = rect->x;
+ y = rect->y;
+
+ if (state->focused && !state->disabled) {
+ /* This will get us the lit borders that focused textboxes enjoy on
+ * some themes. */
+ GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
+
+ if (!interior_focus) {
+ /* Indent the border a little bit if we have exterior focus
+ (this is what GTK does to draw native entries) */
+ x += focus_width;
+ y += focus_width;
+ width -= 2 * focus_width;
+ height -= 2 * focus_width;
+ }
+ }
+
+ if (!draw_focus_outline_only || interior_focus) {
+ gtk_paint_shadow(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN,
+ cliprect, widget, "entry", x, y, width, height);
+ }
+
+ if (state->focused && !state->disabled) {
+ if (!interior_focus) {
+ gtk_paint_focus(style, drawable, GTK_STATE_NORMAL, cliprect,
+ widget, "entry",
+ rect->x, rect->y, rect->width, rect->height);
+ }
+
+ /* Now unset the focus flag. We don't want other entries to look
+ * like they're focused too! */
+ GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_treeview_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ gint xthickness, ythickness;
+
+ GtkStyle *style;
+ GtkStateType state_type;
+
+ ensure_tree_view_widget();
+ ensure_scrolled_window_widget();
+
+ gtk_widget_set_direction(gTreeViewWidget, direction);
+ gtk_widget_set_direction(gScrolledWindowWidget, direction);
+
+ /* only handle disabled and normal states, otherwise the whole background
+ * area will be painted differently with other states */
+ state_type = state->disabled ? GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL;
+
+ /* In GTK the treeview sets the background of the window
+ * which contains the cells to the treeview base color.
+ * If we don't set it here the background color will not be correct.*/
+ gtk_widget_modify_bg(gTreeViewWidget, state_type,
+ &gTreeViewWidget->style->base[state_type]);
+
+ style = gScrolledWindowWidget->style;
+ xthickness = XTHICKNESS(style);
+ ythickness = YTHICKNESS(style);
+
+ TSOffsetStyleGCs(gTreeViewWidget->style, rect->x, rect->y);
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_paint_flat_box(gTreeViewWidget->style, drawable, state_type,
+ GTK_SHADOW_NONE, cliprect, gTreeViewWidget, "treeview",
+ rect->x + xthickness, rect->y + ythickness,
+ rect->width - 2 * xthickness,
+ rect->height - 2 * ythickness);
+
+ gtk_paint_shadow(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN,
+ cliprect, gScrolledWindowWidget, "scrolled_window",
+ rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_tree_header_cell_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ gboolean isSorted, GtkTextDirection direction)
+{
+ gtk_tree_view_column_set_sort_indicator(gMiddleTreeViewColumn,
+ isSorted);
+
+ moz_gtk_button_paint(drawable, rect, cliprect, state, GTK_RELIEF_NORMAL,
+ gTreeHeaderCellWidget, direction);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_tree_header_sort_arrow_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect,
+ GtkWidgetState* state, GtkArrowType flags,
+ GtkTextDirection direction)
+{
+ GdkRectangle arrow_rect;
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type = GTK_SHADOW_IN;
+ GtkArrowType arrow_type = flags;
+ GtkStyle* style;
+
+ ensure_tree_header_cell_widget();
+ gtk_widget_set_direction(gTreeHeaderSortArrowWidget, direction);
+
+ /* hard code these values */
+ arrow_rect.width = 11;
+ arrow_rect.height = 11;
+ arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2;
+ arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2;
+
+ style = gTreeHeaderSortArrowWidget->style;
+ TSOffsetStyleGCs(style, arrow_rect.x, arrow_rect.y);
+
+ gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect,
+ gTreeHeaderSortArrowWidget, "arrow", arrow_type, TRUE,
+ arrow_rect.x, arrow_rect.y,
+ arrow_rect.width, arrow_rect.height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_treeview_expander_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkExpanderStyle expander_state,
+ GtkTextDirection direction)
+{
+ GtkStyle *style;
+ GtkStateType state_type;
+
+ ensure_tree_view_widget();
+ gtk_widget_set_direction(gTreeViewWidget, direction);
+
+ style = gTreeViewWidget->style;
+
+ /* Because the frame we get is of the entire treeview, we can't get the precise
+ * event state of one expander, thus rendering hover and active feedback useless. */
+ state_type = state->disabled ? GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_expander(style, drawable, state_type, cliprect, gTreeViewWidget, "treeview",
+ rect->x + rect->width / 2, rect->y + rect->height / 2, expander_state);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_combo_box_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ gboolean ishtml, GtkTextDirection direction)
+{
+ GdkRectangle arrow_rect, real_arrow_rect;
+ gint arrow_size, separator_width;
+ gboolean wide_separators;
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type = state->active ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
+ GtkStyle* style;
+ GtkRequisition arrow_req;
+
+ ensure_combo_box_widgets();
+
+ /* Also sets the direction on gComboBoxButtonWidget, which is then
+ * inherited by the separator and arrow */
+ moz_gtk_button_paint(drawable, rect, cliprect, state, GTK_RELIEF_NORMAL,
+ gComboBoxButtonWidget, direction);
+
+ calculate_button_inner_rect(gComboBoxButtonWidget,
+ rect, &arrow_rect, direction, ishtml);
+ /* Now arrow_rect contains the inner rect ; we want to correct the width
+ * to what the arrow needs (see gtk_combo_box_size_allocate) */
+ gtk_widget_size_request(gComboBoxArrowWidget, &arrow_req);
+ if (direction == GTK_TEXT_DIR_LTR)
+ arrow_rect.x += arrow_rect.width - arrow_req.width;
+ arrow_rect.width = arrow_req.width;
+
+ calculate_arrow_rect(gComboBoxArrowWidget,
+ &arrow_rect, &real_arrow_rect, direction);
+
+ style = gComboBoxArrowWidget->style;
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_widget_size_allocate(gComboBoxWidget, rect);
+
+ gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect,
+ gComboBoxArrowWidget, "arrow", GTK_ARROW_DOWN, TRUE,
+ real_arrow_rect.x, real_arrow_rect.y,
+ real_arrow_rect.width, real_arrow_rect.height);
+
+
+ /* If there is no separator in the theme, there's nothing left to do. */
+ if (!gComboBoxSeparatorWidget)
+ return MOZ_GTK_SUCCESS;
+
+ style = gComboBoxSeparatorWidget->style;
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_widget_style_get(gComboBoxSeparatorWidget,
+ "wide-separators", &wide_separators,
+ "separator-width", &separator_width,
+ NULL);
+
+ if (wide_separators) {
+ if (direction == GTK_TEXT_DIR_LTR)
+ arrow_rect.x -= separator_width;
+ else
+ arrow_rect.x += arrow_rect.width;
+
+ gtk_paint_box(style, drawable,
+ GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_OUT,
+ cliprect, gComboBoxSeparatorWidget, "vseparator",
+ arrow_rect.x, arrow_rect.y,
+ separator_width, arrow_rect.height);
+ } else {
+ if (direction == GTK_TEXT_DIR_LTR)
+ arrow_rect.x -= XTHICKNESS(style);
+ else
+ arrow_rect.x += arrow_rect.width;
+
+ gtk_paint_vline(style, drawable, GTK_STATE_NORMAL, cliprect,
+ gComboBoxSeparatorWidget, "vseparator",
+ arrow_rect.y, arrow_rect.y + arrow_rect.height,
+ arrow_rect.x);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_arrow_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkArrowType arrow_type, GtkTextDirection direction)
+{
+ GtkStyle* style;
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type = state->active ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
+ GdkRectangle arrow_rect;
+
+ ensure_button_arrow_widget();
+ style = gButtonArrowWidget->style;
+ gtk_widget_set_direction(gButtonArrowWidget, direction);
+
+ calculate_arrow_rect(gButtonArrowWidget, rect, &arrow_rect,
+ direction);
+
+ if (direction == GTK_TEXT_DIR_RTL) {
+ if (arrow_type == GTK_ARROW_LEFT)
+ arrow_type = GTK_ARROW_RIGHT;
+ else if (arrow_type == GTK_ARROW_RIGHT)
+ arrow_type = GTK_ARROW_LEFT;
+ }
+
+ TSOffsetStyleGCs(style, arrow_rect.x, arrow_rect.y);
+ gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect,
+ gButtonArrowWidget, "arrow", arrow_type, TRUE,
+ arrow_rect.x, arrow_rect.y, arrow_rect.width, arrow_rect.height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_combo_box_entry_button_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect,
+ GtkWidgetState* state,
+ gboolean input_focus,
+ GtkTextDirection direction)
+{
+ gint x_displacement, y_displacement;
+ GdkRectangle arrow_rect, real_arrow_rect;
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type = state->active ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
+ GtkStyle* style;
+
+ ensure_combo_box_entry_widgets();
+
+ if (input_focus) {
+ /* Some themes draw a complementary focus ring for the dropdown button
+ * when the dropdown entry has focus */
+ GTK_WIDGET_SET_FLAGS(gComboBoxEntryTextareaWidget, GTK_HAS_FOCUS);
+ }
+
+ moz_gtk_button_paint(drawable, rect, cliprect, state, GTK_RELIEF_NORMAL,
+ gComboBoxEntryButtonWidget, direction);
+
+ if (input_focus)
+ GTK_WIDGET_UNSET_FLAGS(gComboBoxEntryTextareaWidget, GTK_HAS_FOCUS);
+
+ calculate_button_inner_rect(gComboBoxEntryButtonWidget,
+ rect, &arrow_rect, direction, FALSE);
+ if (state_type == GTK_STATE_ACTIVE) {
+ gtk_widget_style_get(gComboBoxEntryButtonWidget,
+ "child-displacement-x", &x_displacement,
+ "child-displacement-y", &y_displacement,
+ NULL);
+ arrow_rect.x += x_displacement;
+ arrow_rect.y += y_displacement;
+ }
+
+ calculate_arrow_rect(gComboBoxEntryArrowWidget,
+ &arrow_rect, &real_arrow_rect, direction);
+
+ style = gComboBoxEntryArrowWidget->style;
+ TSOffsetStyleGCs(style, real_arrow_rect.x, real_arrow_rect.y);
+
+ gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect,
+ gComboBoxEntryArrowWidget, "arrow", GTK_ARROW_DOWN, TRUE,
+ real_arrow_rect.x, real_arrow_rect.y,
+ real_arrow_rect.width, real_arrow_rect.height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_container_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ gboolean isradio, GtkTextDirection direction)
+{
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkStyle* style;
+ GtkWidget *widget;
+ gboolean interior_focus;
+ gint focus_width, focus_pad;
+
+ if (isradio) {
+ ensure_radiobutton_widget();
+ widget = gRadiobuttonWidget;
+ } else {
+ ensure_checkbox_widget();
+ widget = gCheckboxWidget;
+ }
+ gtk_widget_set_direction(widget, direction);
+
+ style = widget->style;
+ moz_gtk_widget_get_focus(widget, &interior_focus, &focus_width,
+ &focus_pad);
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ /* The detail argument for the gtk_paint_* calls below are "checkbutton"
+ even for radio buttons, to match what gtk does. */
+
+ /* this is for drawing a prelight box */
+ if (state_type == GTK_STATE_PRELIGHT || state_type == GTK_STATE_ACTIVE) {
+ gtk_paint_flat_box(style, drawable, GTK_STATE_PRELIGHT,
+ GTK_SHADOW_ETCHED_OUT, cliprect, widget,
+ "checkbutton",
+ rect->x, rect->y, rect->width, rect->height);
+ }
+
+ if (state_type != GTK_STATE_NORMAL && state_type != GTK_STATE_PRELIGHT)
+ state_type = GTK_STATE_NORMAL;
+
+ if (state->focused && !interior_focus) {
+ gtk_paint_focus(style, drawable, state_type, cliprect, widget,
+ "checkbutton",
+ rect->x, rect->y, rect->width, rect->height);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_toggle_label_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ gboolean isradio, GtkTextDirection direction)
+{
+ GtkStateType state_type;
+ GtkStyle *style;
+ GtkWidget *widget;
+ gboolean interior_focus;
+
+ if (!state->focused)
+ return MOZ_GTK_SUCCESS;
+
+ if (isradio) {
+ ensure_radiobutton_widget();
+ widget = gRadiobuttonWidget;
+ } else {
+ ensure_checkbox_widget();
+ widget = gCheckboxWidget;
+ }
+ gtk_widget_set_direction(widget, direction);
+
+ gtk_widget_style_get(widget, "interior-focus", &interior_focus, NULL);
+ if (!interior_focus)
+ return MOZ_GTK_SUCCESS;
+
+ state_type = ConvertGtkState(state);
+
+ style = widget->style;
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ /* Always "checkbutton" to match gtkcheckbutton.c */
+ gtk_paint_focus(style, drawable, state_type, cliprect, widget,
+ "checkbutton",
+ rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_toolbar_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ GtkStyle* style;
+ GtkShadowType shadow_type;
+
+ ensure_toolbar_widget();
+ gtk_widget_set_direction(gToolbarWidget, direction);
+
+ style = gToolbarWidget->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_style_apply_default_background(style, drawable, TRUE,
+ GTK_STATE_NORMAL,
+ cliprect, rect->x, rect->y,
+ rect->width, rect->height);
+
+ gtk_widget_style_get(gToolbarWidget, "shadow-type", &shadow_type, NULL);
+
+ gtk_paint_box (style, drawable, GTK_STATE_NORMAL, shadow_type,
+ cliprect, gToolbarWidget, "toolbar",
+ rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_toolbar_separator_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect,
+ GtkTextDirection direction)
+{
+ GtkStyle* style;
+ gint separator_width;
+ gint paint_width;
+ gboolean wide_separators;
+
+ /* Defined as constants in GTK+ 2.10.14 */
+ const double start_fraction = 0.2;
+ const double end_fraction = 0.8;
+
+ ensure_toolbar_separator_widget();
+ gtk_widget_set_direction(gToolbarSeparatorWidget, direction);
+
+ style = gToolbarSeparatorWidget->style;
+
+ gtk_widget_style_get(gToolbarWidget,
+ "wide-separators", &wide_separators,
+ "separator-width", &separator_width,
+ NULL);
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ if (wide_separators) {
+ if (separator_width > rect->width)
+ separator_width = rect->width;
+
+ gtk_paint_box(style, drawable,
+ GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_OUT,
+ cliprect, gToolbarWidget, "vseparator",
+ rect->x + (rect->width - separator_width) / 2,
+ rect->y + rect->height * start_fraction,
+ separator_width,
+ rect->height * (end_fraction - start_fraction));
+
+ } else {
+ paint_width = style->xthickness;
+
+ if (paint_width > rect->width)
+ paint_width = rect->width;
+
+ gtk_paint_vline(style, drawable,
+ GTK_STATE_NORMAL, cliprect, gToolbarSeparatorWidget,
+ "toolbar",
+ rect->y + rect->height * start_fraction,
+ rect->y + rect->height * end_fraction,
+ rect->x + (rect->width - paint_width) / 2);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_tooltip_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ GtkStyle* style;
+
+ ensure_tooltip_widget();
+ gtk_widget_set_direction(gTooltipWidget, direction);
+
+ style = gtk_rc_get_style_by_paths(gtk_settings_get_default(),
+ "gtk-tooltips", "GtkWindow",
+ GTK_TYPE_WINDOW);
+
+ style = gtk_style_attach(style, gTooltipWidget->window);
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_flat_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+ cliprect, gTooltipWidget, "tooltip",
+ rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_resizer_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStyle* style;
+ GtkStateType state_type = ConvertGtkState(state);
+
+ ensure_frame_widget();
+ gtk_widget_set_direction(gStatusbarWidget, direction);
+
+ style = gStatusbarWidget->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_paint_resize_grip(style, drawable, state_type, cliprect, gStatusbarWidget,
+ "statusbar", (direction == GTK_TEXT_DIR_LTR) ?
+ GDK_WINDOW_EDGE_SOUTH_EAST :
+ GDK_WINDOW_EDGE_SOUTH_WEST,
+ rect->x, rect->y, rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_frame_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ GtkStyle* style;
+ GtkShadowType shadow_type;
+
+ ensure_frame_widget();
+ gtk_widget_set_direction(gFrameWidget, direction);
+
+ style = gFrameWidget->style;
+
+ gtk_widget_style_get(gStatusbarWidget, "shadow-type", &shadow_type, NULL);
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_shadow(style, drawable, GTK_STATE_NORMAL, shadow_type,
+ cliprect, gFrameWidget, "frame", rect->x, rect->y,
+ rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_progressbar_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ GtkStyle* style;
+
+ ensure_progress_widget();
+ gtk_widget_set_direction(gProgressWidget, direction);
+
+ style = gProgressWidget->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN,
+ cliprect, gProgressWidget, "trough", rect->x, rect->y,
+ rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_progress_chunk_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction,
+ WidgetNodeType widget)
+{
+ GtkStyle* style;
+
+ ensure_progress_widget();
+ gtk_widget_set_direction(gProgressWidget, direction);
+
+ style = gProgressWidget->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ if (widget == MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE ||
+ widget == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE) {
+ /**
+ * The bar's size and the bar speed are set depending of the progress'
+ * size. These could also be constant for all progress bars easily.
+ */
+ gboolean vertical = (widget == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE);
+
+ /* The size of the dimension we are going to use for the animation. */
+ const gint progressSize = vertical ? rect->height : rect->width;
+
+ /* The bar is using a fifth of the element size, based on GtkProgressBar
+ * activity-blocks property. */
+ const gint barSize = MAX(1, progressSize / 5);
+
+ /* Represents the travel that has to be done for a complete cycle. */
+ const gint travel = 2 * (progressSize - barSize);
+
+ /* period equals to travel / pixelsPerMillisecond
+ * where pixelsPerMillisecond equals progressSize / 1000.0.
+ * This is equivalent to 1600. */
+ static const guint period = 1600;
+ const gint t = PR_IntervalToMilliseconds(PR_IntervalNow()) % period;
+ const gint dx = travel * t / period;
+
+ if (vertical) {
+ rect->y += (dx < travel / 2) ? dx : travel - dx;
+ rect->height = barSize;
+ } else {
+ rect->x += (dx < travel / 2) ? dx : travel - dx;
+ rect->width = barSize;
+ }
+ }
+
+ gtk_paint_box(style, drawable, GTK_STATE_PRELIGHT, GTK_SHADOW_OUT,
+ cliprect, gProgressWidget, "bar", rect->x, rect->y,
+ rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_tab_thickness(WidgetNodeType aNodeType)
+{
+ ensure_tab_widget();
+ if (YTHICKNESS(gTabWidget->style) < 2)
+ return 2; /* some themes don't set ythickness correctly */
+
+ return YTHICKNESS(gTabWidget->style);
+}
+
+static gint
+moz_gtk_tab_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkTabFlags flags, GtkTextDirection direction,
+ WidgetNodeType widget)
+{
+ /* When the tab isn't selected, we just draw a notebook extension.
+ * When it is selected, we overwrite the adjacent border of the tabpanel
+ * touching the tab with a pierced border (called "the gap") to make the
+ * tab appear physically attached to the tabpanel; see details below. */
+
+ GtkStyle* style;
+ GdkRectangle focusRect;
+ gboolean isBottomTab = (widget == MOZ_GTK_TAB_BOTTOM);
+
+ ensure_tab_widget();
+ gtk_widget_set_direction(gTabWidget, direction);
+
+ style = gTabWidget->style;
+ focusRect = *rect;
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ if ((flags & MOZ_GTK_TAB_SELECTED) == 0) {
+ /* Only draw the tab */
+ gtk_paint_extension(style, drawable, GTK_STATE_ACTIVE, GTK_SHADOW_OUT,
+ cliprect, gTabWidget, "tab",
+ rect->x, rect->y, rect->width, rect->height,
+ isBottomTab ? GTK_POS_TOP : GTK_POS_BOTTOM );
+ } else {
+ /* Draw the tab and the gap
+ * We want the gap to be positioned exactly on the tabpanel top
+ * border; since tabbox.css may set a negative margin so that the tab
+ * frame rect already overlaps the tabpanel frame rect, we need to take
+ * that into account when drawing. To that effect, nsNativeThemeGTK
+ * passes us this negative margin (bmargin in the graphic below) in the
+ * lowest bits of |flags|. We use it to set gap_voffset, the distance
+ * between the top of the gap and the bottom of the tab (resp. the
+ * bottom of the gap and the top of the tab when we draw a bottom tab),
+ * while ensuring that the gap always touches the border of the tab,
+ * i.e. 0 <= gap_voffset <= gap_height, to avoid surprinsing results
+ * with big negative or positive margins.
+ * Here is a graphical explanation in the case of top tabs:
+ * ___________________________
+ * / \
+ * | T A B |
+ * ----------|. . . . . . . . . . . . . . .|----- top of tabpanel
+ * : ^ bmargin : ^
+ * : | (-negative margin, : |
+ * bottom : v passed in flags) : | gap_height
+ * of -> :.............................: | (the size of the
+ * the tab . part of the gap . | tabpanel top border)
+ * . outside of the tab . v
+ * ----------------------------------------------
+ *
+ * To draw the gap, we use gtk_paint_box_gap(), see comment in
+ * moz_gtk_tabpanels_paint(). This box_gap is made 3 * gap_height tall,
+ * which should suffice to ensure that the only visible border is the
+ * pierced one. If the tab is in the middle, we make the box_gap begin
+ * a bit to the left of the tab and end a bit to the right, adjusting
+ * the gap position so it still is under the tab, because we want the
+ * rendering of a gap in the middle of a tabpanel. This is the role of
+ * the gints gap_{l,r}_offset. On the contrary, if the tab is the
+ * first, we align the start border of the box_gap with the start
+ * border of the tab (left if LTR, right if RTL), by setting the
+ * appropriate offset to 0.*/
+ gint gap_loffset, gap_roffset, gap_voffset, gap_height;
+
+ /* Get height needed by the gap */
+ gap_height = moz_gtk_get_tab_thickness(widget);
+
+ /* Extract gap_voffset from the first bits of flags */
+ gap_voffset = flags & MOZ_GTK_TAB_MARGIN_MASK;
+ if (gap_voffset > gap_height)
+ gap_voffset = gap_height;
+
+ /* Set gap_{l,r}_offset to appropriate values */
+ gap_loffset = gap_roffset = 20; /* should be enough */
+ if (flags & MOZ_GTK_TAB_FIRST) {
+ if (direction == GTK_TEXT_DIR_RTL)
+ gap_roffset = 0;
+ else
+ gap_loffset = 0;
+ }
+
+ if (isBottomTab) {
+ /* Draw the tab */
+ focusRect.y += gap_voffset;
+ focusRect.height -= gap_voffset;
+ gtk_paint_extension(style, drawable, GTK_STATE_NORMAL,
+ GTK_SHADOW_OUT, cliprect, gTabWidget, "tab",
+ rect->x, rect->y + gap_voffset, rect->width,
+ rect->height - gap_voffset, GTK_POS_TOP);
+
+ /* Draw the gap; erase with background color before painting in
+ * case theme does not */
+ gtk_style_apply_default_background(style, drawable, TRUE,
+ GTK_STATE_NORMAL, cliprect,
+ rect->x,
+ rect->y + gap_voffset
+ - gap_height,
+ rect->width, gap_height);
+ gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+ cliprect, gTabWidget, "notebook",
+ rect->x - gap_loffset,
+ rect->y + gap_voffset - 3 * gap_height,
+ rect->width + gap_loffset + gap_roffset,
+ 3 * gap_height, GTK_POS_BOTTOM,
+ gap_loffset, rect->width);
+ } else {
+ /* Draw the tab */
+ focusRect.height -= gap_voffset;
+ gtk_paint_extension(style, drawable, GTK_STATE_NORMAL,
+ GTK_SHADOW_OUT, cliprect, gTabWidget, "tab",
+ rect->x, rect->y, rect->width,
+ rect->height - gap_voffset, GTK_POS_BOTTOM);
+
+ /* Draw the gap; erase with background color before painting in
+ * case theme does not */
+ gtk_style_apply_default_background(style, drawable, TRUE,
+ GTK_STATE_NORMAL, cliprect,
+ rect->x,
+ rect->y + rect->height
+ - gap_voffset,
+ rect->width, gap_height);
+ gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+ cliprect, gTabWidget, "notebook",
+ rect->x - gap_loffset,
+ rect->y + rect->height - gap_voffset,
+ rect->width + gap_loffset + gap_roffset,
+ 3 * gap_height, GTK_POS_TOP,
+ gap_loffset, rect->width);
+ }
+
+ }
+
+ if (state->focused) {
+ /* Paint the focus ring */
+ focusRect.x += XTHICKNESS(style);
+ focusRect.width -= XTHICKNESS(style) * 2;
+ focusRect.y += YTHICKNESS(style);
+ focusRect.height -= YTHICKNESS(style) * 2;
+
+ gtk_paint_focus(style, drawable,
+ /* Believe it or not, NORMAL means a selected tab and
+ ACTIVE means an unselected tab. */
+ (flags & MOZ_GTK_TAB_SELECTED) ? GTK_STATE_NORMAL
+ : GTK_STATE_ACTIVE,
+ cliprect, gTabWidget, "tab",
+ focusRect.x, focusRect.y, focusRect.width, focusRect.height);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_tabpanels_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ /* We have three problems here:
+ * - Most engines draw gtk_paint_box differently to gtk_paint_box_gap, the
+ * former implies there are no tabs, eg. Clearlooks.
+ * - Wanting a gap of width 0 doesn't actually guarantee a zero-width gap, eg.
+ * Clearlooks.
+ * - Our old approach of a negative X position could cause rendering errors
+ * on the box's corner, eg. themes using the Pixbuf engine.
+ */
+ GtkStyle* style;
+ GdkRectangle halfClipRect;
+
+ ensure_tab_widget();
+ gtk_widget_set_direction(gTabWidget, direction);
+
+ style = gTabWidget->style;
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ /* Our approach is as follows:
+ * - Draw the box in two passes. Pass in a clip rect to draw the left half of the
+ * box, with the gap specified to the right outside the clip rect so that it is
+ * not drawn.
+ * - The right half is drawn with the gap to the left outside the modified clip rect.
+ */
+ if (!gdk_rectangle_intersect(rect, cliprect, &halfClipRect))
+ return MOZ_GTK_SUCCESS;
+
+ halfClipRect.width = (halfClipRect.width / 2) + 1;
+ gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+ &halfClipRect, gTabWidget, "notebook", rect->x, rect->y,
+ rect->width, rect->height,
+ GTK_POS_TOP, halfClipRect.width + 1, 0);
+
+ halfClipRect.x += halfClipRect.width;
+ gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+ &halfClipRect, gTabWidget, "notebook", rect->x, rect->y,
+ rect->width, rect->height,
+ GTK_POS_TOP, -10, 0);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_tab_scroll_arrow_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkArrowType arrow_type,
+ GtkTextDirection direction)
+{
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type = state->active ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
+ GtkStyle* style;
+ gint arrow_size = MIN(rect->width, rect->height);
+ gint x = rect->x + (rect->width - arrow_size) / 2;
+ gint y = rect->y + (rect->height - arrow_size) / 2;
+
+ ensure_tab_widget();
+
+ style = gTabWidget->style;
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ if (direction == GTK_TEXT_DIR_RTL) {
+ arrow_type = (arrow_type == GTK_ARROW_LEFT) ?
+ GTK_ARROW_RIGHT : GTK_ARROW_LEFT;
+ }
+
+ gtk_paint_arrow(style, drawable, state_type, shadow_type, NULL,
+ gTabWidget, "notebook", arrow_type, TRUE,
+ x, y, arrow_size, arrow_size);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_menu_bar_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ GtkStyle* style;
+ GtkShadowType shadow_type;
+ ensure_menu_bar_widget();
+ gtk_widget_set_direction(gMenuBarWidget, direction);
+
+ gtk_widget_style_get(gMenuBarWidget, "shadow-type", &shadow_type, NULL);
+
+ style = gMenuBarWidget->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_style_apply_default_background(style, drawable, TRUE, GTK_STATE_NORMAL,
+ cliprect, rect->x, rect->y,
+ rect->width, rect->height);
+
+ gtk_paint_box(style, drawable, GTK_STATE_NORMAL, shadow_type,
+ cliprect, gMenuBarWidget, "menubar", rect->x, rect->y,
+ rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_menu_popup_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ GtkStyle* style;
+ ensure_menu_popup_widget();
+ gtk_widget_set_direction(gMenuPopupWidget, direction);
+
+ style = gMenuPopupWidget->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_style_apply_default_background(style, drawable, TRUE, GTK_STATE_NORMAL,
+ cliprect, rect->x, rect->y,
+ rect->width, rect->height);
+ gtk_paint_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+ cliprect, gMenuPopupWidget, "menu",
+ rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_menu_separator_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ GtkStyle* style;
+ gboolean wide_separators;
+ gint separator_height;
+ guint horizontal_padding;
+ gint paint_height;
+
+ ensure_menu_separator_widget();
+ gtk_widget_set_direction(gMenuSeparatorWidget, direction);
+
+ style = gMenuSeparatorWidget->style;
+
+ gtk_widget_style_get(gMenuSeparatorWidget,
+ "wide-separators", &wide_separators,
+ "separator-height", &separator_height,
+ "horizontal-padding", &horizontal_padding,
+ NULL);
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ if (wide_separators) {
+ if (separator_height > rect->height)
+ separator_height = rect->height;
+
+ gtk_paint_box(style, drawable,
+ GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_OUT,
+ cliprect, gMenuSeparatorWidget, "hseparator",
+ rect->x + horizontal_padding + style->xthickness,
+ rect->y + (rect->height - separator_height - style->ythickness) / 2,
+ rect->width - 2 * (horizontal_padding + style->xthickness),
+ separator_height);
+ } else {
+ paint_height = style->ythickness;
+ if (paint_height > rect->height)
+ paint_height = rect->height;
+
+ gtk_paint_hline(style, drawable,
+ GTK_STATE_NORMAL, cliprect, gMenuSeparatorWidget,
+ "menuitem",
+ rect->x + horizontal_padding + style->xthickness,
+ rect->x + rect->width - horizontal_padding - style->xthickness - 1,
+ rect->y + (rect->height - style->ythickness) / 2);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_menu_item_paint(WidgetNodeType widget, GdkDrawable* drawable,
+ GdkRectangle* rect, GdkRectangle* cliprect,
+ GtkWidgetState* state, GtkTextDirection direction)
+{
+ GtkStyle* style;
+ GtkShadowType shadow_type;
+ GtkWidget* item_widget;
+
+ if (state->inHover && !state->disabled) {
+ if (widget == MOZ_GTK_MENUBARITEM) {
+ ensure_menu_bar_item_widget();
+ item_widget = gMenuBarItemWidget;
+ } else {
+ ensure_menu_item_widget();
+ item_widget = gMenuItemWidget;
+ }
+ gtk_widget_set_direction(item_widget, direction);
+
+ style = item_widget->style;
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_widget_style_get(item_widget, "selected-shadow-type",
+ &shadow_type, NULL);
+
+ gtk_paint_box(style, drawable, GTK_STATE_PRELIGHT, shadow_type,
+ cliprect, item_widget, "menuitem", rect->x, rect->y,
+ rect->width, rect->height);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_menu_arrow_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStyle* style;
+ GtkStateType state_type = ConvertGtkState(state);
+
+ ensure_menu_item_widget();
+ gtk_widget_set_direction(gMenuItemWidget, direction);
+
+ style = gMenuItemWidget->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_arrow(style, drawable, state_type,
+ (state_type == GTK_STATE_PRELIGHT) ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
+ cliprect, gMenuItemWidget, "menuitem",
+ (direction == GTK_TEXT_DIR_LTR) ? GTK_ARROW_RIGHT : GTK_ARROW_LEFT,
+ TRUE, rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_check_menu_item_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ gboolean checked, gboolean isradio,
+ GtkTextDirection direction)
+{
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkStyle* style;
+ GtkShadowType shadow_type = (checked)?GTK_SHADOW_IN:GTK_SHADOW_OUT;
+ gint offset;
+ gint indicator_size, horizontal_padding;
+ gint x, y;
+
+ moz_gtk_menu_item_paint(MOZ_GTK_MENUITEM, drawable, rect, cliprect, state,
+ direction);
+
+ ensure_check_menu_item_widget();
+ gtk_widget_set_direction(gCheckMenuItemWidget, direction);
+
+ gtk_widget_style_get (gCheckMenuItemWidget,
+ "indicator-size", &indicator_size,
+ "horizontal-padding", &horizontal_padding,
+ NULL);
+
+ if (checked || GTK_CHECK_MENU_ITEM(gCheckMenuItemWidget)->always_show_toggle) {
+ style = gCheckMenuItemWidget->style;
+
+ offset = GTK_CONTAINER(gCheckMenuItemWidget)->border_width +
+ gCheckMenuItemWidget->style->xthickness + 2;
+
+ x = (direction == GTK_TEXT_DIR_RTL) ?
+ rect->width - indicator_size - offset - horizontal_padding: rect->x + offset + horizontal_padding;
+ y = rect->y + (rect->height - indicator_size) / 2;
+
+ TSOffsetStyleGCs(style, x, y);
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gCheckMenuItemWidget),
+ checked);
+
+ if (isradio) {
+ gtk_paint_option(style, drawable, state_type, shadow_type, cliprect,
+ gCheckMenuItemWidget, "option",
+ x, y, indicator_size, indicator_size);
+ } else {
+ gtk_paint_check(style, drawable, state_type, shadow_type, cliprect,
+ gCheckMenuItemWidget, "check",
+ x, y, indicator_size, indicator_size);
+ }
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_window_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ GtkStyle* style;
+
+ ensure_window_widget();
+ gtk_widget_set_direction(gProtoWindow, direction);
+
+ style = gProtoWindow->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_style_apply_default_background(style, drawable, TRUE,
+ GTK_STATE_NORMAL,
+ cliprect, rect->x, rect->y,
+ rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_widget_border(WidgetNodeType widget, gint* left, gint* top,
+ gint* right, gint* bottom, GtkTextDirection direction,
+ gboolean inhtml)
+{
+ GtkWidget* w;
+
+ switch (widget) {
+ case MOZ_GTK_BUTTON:
+ case MOZ_GTK_TOOLBAR_BUTTON:
+ {
+ GtkBorder inner_border;
+ gboolean interior_focus;
+ gint focus_width, focus_pad;
+
+ ensure_button_widget();
+ *left = *top = *right = *bottom = GTK_CONTAINER(gButtonWidget)->border_width;
+
+ /* Don't add this padding in HTML, otherwise the buttons will
+ become too big and stuff the layout. */
+ if (!inhtml) {
+ moz_gtk_widget_get_focus(gButtonWidget, &interior_focus, &focus_width, &focus_pad);
+ moz_gtk_button_get_inner_border(gButtonWidget, &inner_border);
+ *left += focus_width + focus_pad + inner_border.left;
+ *right += focus_width + focus_pad + inner_border.right;
+ *top += focus_width + focus_pad + inner_border.top;
+ *bottom += focus_width + focus_pad + inner_border.bottom;
+ }
+
+ *left += gButtonWidget->style->xthickness;
+ *right += gButtonWidget->style->xthickness;
+ *top += gButtonWidget->style->ythickness;
+ *bottom += gButtonWidget->style->ythickness;
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_ENTRY:
+ ensure_entry_widget();
+ w = gEntryWidget;
+ break;
+ case MOZ_GTK_TREEVIEW:
+ ensure_tree_view_widget();
+ w = gTreeViewWidget;
+ break;
+ case MOZ_GTK_TREE_HEADER_CELL:
+ {
+ /* A Tree Header in GTK is just a different styled button
+ * It must be placed in a TreeView for getting the correct style
+ * assigned.
+ * That is why the following code is the same as for MOZ_GTK_BUTTON.
+ * */
+
+ GtkBorder inner_border;
+ gboolean interior_focus;
+ gint focus_width, focus_pad;
+
+ ensure_tree_header_cell_widget();
+ *left = *top = *right = *bottom = GTK_CONTAINER(gTreeHeaderCellWidget)->border_width;
+
+ moz_gtk_widget_get_focus(gTreeHeaderCellWidget, &interior_focus, &focus_width, &focus_pad);
+ moz_gtk_button_get_inner_border(gTreeHeaderCellWidget, &inner_border);
+ *left += focus_width + focus_pad + inner_border.left;
+ *right += focus_width + focus_pad + inner_border.right;
+ *top += focus_width + focus_pad + inner_border.top;
+ *bottom += focus_width + focus_pad + inner_border.bottom;
+
+ *left += gTreeHeaderCellWidget->style->xthickness;
+ *right += gTreeHeaderCellWidget->style->xthickness;
+ *top += gTreeHeaderCellWidget->style->ythickness;
+ *bottom += gTreeHeaderCellWidget->style->ythickness;
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_TREE_HEADER_SORTARROW:
+ ensure_tree_header_cell_widget();
+ w = gTreeHeaderSortArrowWidget;
+ break;
+ case MOZ_GTK_DROPDOWN_ENTRY:
+ ensure_combo_box_entry_widgets();
+ w = gComboBoxEntryTextareaWidget;
+ break;
+ case MOZ_GTK_DROPDOWN_ARROW:
+ ensure_combo_box_entry_widgets();
+ w = gComboBoxEntryButtonWidget;
+ break;
+ case MOZ_GTK_DROPDOWN:
+ {
+ /* We need to account for the arrow on the dropdown, so text
+ * doesn't come too close to the arrow, or in some cases spill
+ * into the arrow. */
+ gboolean ignored_interior_focus, wide_separators;
+ gint focus_width, focus_pad, separator_width;
+ GtkRequisition arrow_req;
+
+ ensure_combo_box_widgets();
+
+ *left = GTK_CONTAINER(gComboBoxButtonWidget)->border_width;
+
+ if (!inhtml) {
+ moz_gtk_widget_get_focus(gComboBoxButtonWidget,
+ &ignored_interior_focus,
+ &focus_width, &focus_pad);
+ *left += focus_width + focus_pad;
+ }
+
+ *top = *left + gComboBoxButtonWidget->style->ythickness;
+ *left += gComboBoxButtonWidget->style->xthickness;
+
+ *right = *left; *bottom = *top;
+
+ /* If there is no separator, don't try to count its width. */
+ separator_width = 0;
+ if (gComboBoxSeparatorWidget) {
+ gtk_widget_style_get(gComboBoxSeparatorWidget,
+ "wide-separators", &wide_separators,
+ "separator-width", &separator_width,
+ NULL);
+
+ if (!wide_separators)
+ separator_width =
+ XTHICKNESS(gComboBoxSeparatorWidget->style);
+ }
+
+ gtk_widget_size_request(gComboBoxArrowWidget, &arrow_req);
+
+ if (direction == GTK_TEXT_DIR_RTL)
+ *left += separator_width + arrow_req.width;
+ else
+ *right += separator_width + arrow_req.width;
+
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_TABPANELS:
+ ensure_tab_widget();
+ w = gTabWidget;
+ break;
+ case MOZ_GTK_PROGRESSBAR:
+ ensure_progress_widget();
+ w = gProgressWidget;
+ break;
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ case MOZ_GTK_SPINBUTTON_UP:
+ case MOZ_GTK_SPINBUTTON_DOWN:
+ ensure_spin_widget();
+ w = gSpinWidget;
+ break;
+ case MOZ_GTK_SCALE_HORIZONTAL:
+ ensure_scale_widget();
+ w = gHScaleWidget;
+ break;
+ case MOZ_GTK_SCALE_VERTICAL:
+ ensure_scale_widget();
+ w = gVScaleWidget;
+ break;
+ case MOZ_GTK_FRAME:
+ ensure_frame_widget();
+ w = gFrameWidget;
+ break;
+ case MOZ_GTK_CHECKBUTTON_LABEL:
+ case MOZ_GTK_RADIOBUTTON_LABEL:
+ {
+ gboolean interior_focus;
+ gint focus_width, focus_pad;
+
+ /* If the focus is interior, then the label has a border of
+ (focus_width + focus_pad). */
+ if (widget == MOZ_GTK_CHECKBUTTON_LABEL) {
+ ensure_checkbox_widget();
+ moz_gtk_widget_get_focus(gCheckboxWidget, &interior_focus,
+ &focus_width, &focus_pad);
+ }
+ else {
+ ensure_radiobutton_widget();
+ moz_gtk_widget_get_focus(gRadiobuttonWidget, &interior_focus,
+ &focus_width, &focus_pad);
+ }
+
+ if (interior_focus)
+ *left = *top = *right = *bottom = (focus_width + focus_pad);
+ else
+ *left = *top = *right = *bottom = 0;
+
+ return MOZ_GTK_SUCCESS;
+ }
+
+ case MOZ_GTK_CHECKBUTTON_CONTAINER:
+ case MOZ_GTK_RADIOBUTTON_CONTAINER:
+ {
+ gboolean interior_focus;
+ gint focus_width, focus_pad;
+
+ /* If the focus is _not_ interior, then the container has a border
+ of (focus_width + focus_pad). */
+ if (widget == MOZ_GTK_CHECKBUTTON_CONTAINER) {
+ ensure_checkbox_widget();
+ moz_gtk_widget_get_focus(gCheckboxWidget, &interior_focus,
+ &focus_width, &focus_pad);
+ w = gCheckboxWidget;
+ } else {
+ ensure_radiobutton_widget();
+ moz_gtk_widget_get_focus(gRadiobuttonWidget, &interior_focus,
+ &focus_width, &focus_pad);
+ w = gRadiobuttonWidget;
+ }
+
+ *left = *top = *right = *bottom = GTK_CONTAINER(w)->border_width;
+
+ if (!interior_focus) {
+ *left += (focus_width + focus_pad);
+ *right += (focus_width + focus_pad);
+ *top += (focus_width + focus_pad);
+ *bottom += (focus_width + focus_pad);
+ }
+
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_MENUPOPUP:
+ ensure_menu_popup_widget();
+ w = gMenuPopupWidget;
+ break;
+ case MOZ_GTK_MENUBARITEM:
+ // Bug 1274143 for MOZ_GTK_MENUBARITEM.
+ // Fall through to MOZ_GTK_MENUITEM for now.
+ case MOZ_GTK_MENUITEM:
+ ensure_menu_item_widget();
+ ensure_menu_bar_item_widget();
+ w = gMenuItemWidget;
+ break;
+ case MOZ_GTK_CHECKMENUITEM:
+ case MOZ_GTK_RADIOMENUITEM:
+ ensure_check_menu_item_widget();
+ w = gCheckMenuItemWidget;
+ break;
+ case MOZ_GTK_TAB_TOP:
+ case MOZ_GTK_TAB_BOTTOM:
+ ensure_tab_widget();
+ w = gTabWidget;
+ break;
+ case MOZ_GTK_TOOLTIP:
+ // In GTK 2 the spacing between box is set to 4.
+ *left = *top = *right = *bottom = 4;
+ return MOZ_GTK_SUCCESS;
+ /* These widgets have no borders, since they are not containers. */
+ case MOZ_GTK_SPLITTER_HORIZONTAL:
+ case MOZ_GTK_SPLITTER_VERTICAL:
+ case MOZ_GTK_CHECKBUTTON:
+ case MOZ_GTK_RADIOBUTTON:
+ case MOZ_GTK_SCROLLBAR_BUTTON:
+ case MOZ_GTK_SCROLLBAR_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_VERTICAL:
+ case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ case MOZ_GTK_GRIPPER:
+ case MOZ_GTK_PROGRESS_CHUNK:
+ case MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE:
+ case MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE:
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ case MOZ_GTK_TOOLBAR_SEPARATOR:
+ case MOZ_GTK_MENUSEPARATOR:
+ /* These widgets have no borders.*/
+ case MOZ_GTK_SPINBUTTON:
+ case MOZ_GTK_WINDOW:
+ case MOZ_GTK_RESIZER:
+ case MOZ_GTK_MENUARROW:
+ case MOZ_GTK_TOOLBARBUTTON_ARROW:
+ case MOZ_GTK_TOOLBAR:
+ case MOZ_GTK_MENUBAR:
+ case MOZ_GTK_TAB_SCROLLARROW:
+ *left = *top = *right = *bottom = 0;
+ return MOZ_GTK_SUCCESS;
+ default:
+ g_warning("Unsupported widget type: %d", widget);
+ return MOZ_GTK_UNKNOWN_WIDGET;
+ }
+
+ *right = *left = XTHICKNESS(w->style);
+ *bottom = *top = YTHICKNESS(w->style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_tab_border(gint* left, gint* top, gint* right, gint* bottom,
+ GtkTextDirection direction, GtkTabFlags flags,
+ WidgetNodeType widget)
+{
+ moz_gtk_get_widget_border(widget, left, top,
+ right, bottom, direction,
+ FALSE);
+
+ // Top tabs have no bottom border, bottom tabs have no top border
+ if (widget == MOZ_GTK_TAB_BOTTOM) {
+ *top = 0;
+ } else {
+ *bottom = 0;
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_combo_box_entry_button_size(gint* width, gint* height)
+{
+ /*
+ * We get the requisition of the drop down button, which includes
+ * all padding, border and focus line widths the button uses,
+ * as well as the minimum arrow size and its padding
+ * */
+ GtkRequisition requisition;
+ ensure_combo_box_entry_widgets();
+
+ gtk_widget_size_request(gComboBoxEntryButtonWidget, &requisition);
+ *width = requisition.width;
+ *height = requisition.height;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_tab_scroll_arrow_size(gint* width, gint* height)
+{
+ gint arrow_size;
+
+ ensure_tab_widget();
+ gtk_widget_style_get(gTabWidget,
+ "scroll-arrow-hlength", &arrow_size,
+ NULL);
+
+ *height = *width = arrow_size;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+void
+moz_gtk_get_arrow_size(WidgetNodeType widgetType, gint* width, gint* height)
+{
+ GtkWidget* widget;
+ switch (widgetType) {
+ case MOZ_GTK_DROPDOWN:
+ ensure_combo_box_widgets();
+ widget = gComboBoxArrowWidget;
+ break;
+ default:
+ ensure_button_arrow_widget();
+ widget = gButtonArrowWidget;
+ break;
+ }
+
+ GtkRequisition requisition;
+ gtk_widget_size_request(widget, &requisition);
+ *width = requisition.width;
+ *height = requisition.height;
+}
+
+gint
+moz_gtk_get_toolbar_separator_width(gint* size)
+{
+ gboolean wide_separators;
+ gint separator_width;
+ GtkStyle* style;
+
+ ensure_toolbar_widget();
+
+ style = gToolbarWidget->style;
+
+ gtk_widget_style_get(gToolbarWidget,
+ "space-size", size,
+ "wide-separators", &wide_separators,
+ "separator-width", &separator_width,
+ NULL);
+
+ /* Just in case... */
+ *size = MAX(*size, (wide_separators ? separator_width : style->xthickness));
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_expander_size(gint* size)
+{
+ ensure_expander_widget();
+ gtk_widget_style_get(gExpanderWidget,
+ "expander-size", size,
+ NULL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_treeview_expander_size(gint* size)
+{
+ ensure_tree_view_widget();
+ gtk_widget_style_get(gTreeViewWidget,
+ "expander-size", size,
+ NULL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_menu_separator_height(gint *size)
+{
+ gboolean wide_separators;
+ gint separator_height;
+
+ ensure_menu_separator_widget();
+
+ gtk_widget_style_get(gMenuSeparatorWidget,
+ "wide-separators", &wide_separators,
+ "separator-height", &separator_height,
+ NULL);
+
+ if (wide_separators)
+ *size = separator_height + gMenuSeparatorWidget->style->ythickness;
+ else
+ *size = gMenuSeparatorWidget->style->ythickness * 2;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+void
+moz_gtk_get_scale_metrics(GtkOrientation orient, gint* scale_width,
+ gint* scale_height)
+{
+ moz_gtk_get_scalethumb_metrics(orient, scale_width, scale_height);
+}
+
+
+gint
+moz_gtk_get_scalethumb_metrics(GtkOrientation orient, gint* thumb_length, gint* thumb_height)
+{
+ GtkWidget* widget;
+
+ ensure_scale_widget();
+ widget = ((orient == GTK_ORIENTATION_HORIZONTAL) ? gHScaleWidget : gVScaleWidget);
+
+ gtk_widget_style_get (widget,
+ "slider_length", thumb_length,
+ "slider_width", thumb_height,
+ NULL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_scrollbar_metrics(MozGtkScrollbarMetrics *metrics)
+{
+ ensure_scrollbar_widget();
+
+ gtk_widget_style_get (gHorizScrollbarWidget,
+ "slider_width", &metrics->slider_width,
+ "trough_border", &metrics->trough_border,
+ "stepper_size", &metrics->stepper_size,
+ "stepper_spacing", &metrics->stepper_spacing,
+ NULL);
+
+ metrics->min_slider_size =
+ GTK_RANGE(gHorizScrollbarWidget)->min_slider_size;
+
+ return MOZ_GTK_SUCCESS;
+}
+void
+moz_gtk_get_widget_min_size(WidgetNodeType aGtkWidgetType, int* width, int* height) {
+ MOZ_ASSERT_UNREACHABLE("get_widget_min_size not available for GTK2");
+}
+
+gint
+moz_gtk_widget_paint(WidgetNodeType widget, GdkDrawable* drawable,
+ GdkRectangle* rect, GdkRectangle* cliprect,
+ GtkWidgetState* state, gint flags,
+ GtkTextDirection direction)
+{
+ switch (widget) {
+ case MOZ_GTK_BUTTON:
+ case MOZ_GTK_TOOLBAR_BUTTON:
+ if (state->depressed) {
+ ensure_toggle_button_widget();
+ return moz_gtk_button_paint(drawable, rect, cliprect, state,
+ (GtkReliefStyle) flags,
+ gToggleButtonWidget, direction);
+ }
+ ensure_button_widget();
+ return moz_gtk_button_paint(drawable, rect, cliprect, state,
+ (GtkReliefStyle) flags, gButtonWidget,
+ direction);
+ break;
+ case MOZ_GTK_CHECKBUTTON:
+ case MOZ_GTK_RADIOBUTTON:
+ return moz_gtk_toggle_paint(drawable, rect, cliprect, state,
+ !!(flags & MOZ_GTK_WIDGET_CHECKED),
+ !!(flags & MOZ_GTK_WIDGET_INCONSISTENT),
+ (widget == MOZ_GTK_RADIOBUTTON),
+ direction);
+ break;
+ case MOZ_GTK_SCROLLBAR_BUTTON:
+ return moz_gtk_scrollbar_button_paint(drawable, rect, cliprect, state,
+ (GtkScrollbarButtonFlags) flags,
+ direction);
+ break;
+ case MOZ_GTK_SCROLLBAR_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_VERTICAL:
+ return moz_gtk_scrollbar_trough_paint(widget, drawable, rect,
+ cliprect, state, direction);
+ break;
+ case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
+ return MOZ_GTK_SUCCESS;
+ break;
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ return moz_gtk_scrollbar_thumb_paint(widget, drawable, rect,
+ cliprect, state, direction);
+ break;
+ case MOZ_GTK_SCALE_HORIZONTAL:
+ case MOZ_GTK_SCALE_VERTICAL:
+ return moz_gtk_scale_paint(drawable, rect, cliprect, state,
+ (GtkOrientation) flags, direction);
+ break;
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ return moz_gtk_scale_thumb_paint(drawable, rect, cliprect, state,
+ (GtkOrientation) flags, direction);
+ break;
+ case MOZ_GTK_SPINBUTTON:
+ return moz_gtk_spin_paint(drawable, rect, direction);
+ break;
+ case MOZ_GTK_SPINBUTTON_UP:
+ case MOZ_GTK_SPINBUTTON_DOWN:
+ return moz_gtk_spin_updown_paint(drawable, rect,
+ (widget == MOZ_GTK_SPINBUTTON_DOWN),
+ state, direction);
+ break;
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ ensure_spin_widget();
+ return moz_gtk_entry_paint(drawable, rect, cliprect, state,
+ gSpinWidget, direction);
+ break;
+ case MOZ_GTK_GRIPPER:
+ return moz_gtk_gripper_paint(drawable, rect, cliprect, state,
+ direction);
+ break;
+ case MOZ_GTK_TREEVIEW:
+ return moz_gtk_treeview_paint(drawable, rect, cliprect, state,
+ direction);
+ break;
+ case MOZ_GTK_TREE_HEADER_CELL:
+ return moz_gtk_tree_header_cell_paint(drawable, rect, cliprect, state,
+ flags, direction);
+ break;
+ case MOZ_GTK_TREE_HEADER_SORTARROW:
+ return moz_gtk_tree_header_sort_arrow_paint(drawable, rect, cliprect,
+ state,
+ (GtkArrowType) flags,
+ direction);
+ break;
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ return moz_gtk_treeview_expander_paint(drawable, rect, cliprect, state,
+ (GtkExpanderStyle) flags, direction);
+ break;
+ case MOZ_GTK_ENTRY:
+ ensure_entry_widget();
+ return moz_gtk_entry_paint(drawable, rect, cliprect, state,
+ gEntryWidget, direction);
+ break;
+ case MOZ_GTK_DROPDOWN:
+ return moz_gtk_combo_box_paint(drawable, rect, cliprect, state,
+ (gboolean) flags, direction);
+ break;
+ case MOZ_GTK_DROPDOWN_ARROW:
+ return moz_gtk_combo_box_entry_button_paint(drawable, rect, cliprect,
+ state, flags, direction);
+ break;
+ case MOZ_GTK_DROPDOWN_ENTRY:
+ ensure_combo_box_entry_widgets();
+ return moz_gtk_entry_paint(drawable, rect, cliprect, state,
+ gComboBoxEntryTextareaWidget, direction);
+ break;
+ case MOZ_GTK_CHECKBUTTON_CONTAINER:
+ case MOZ_GTK_RADIOBUTTON_CONTAINER:
+ return moz_gtk_container_paint(drawable, rect, cliprect, state,
+ (widget == MOZ_GTK_RADIOBUTTON_CONTAINER),
+ direction);
+ break;
+ case MOZ_GTK_CHECKBUTTON_LABEL:
+ case MOZ_GTK_RADIOBUTTON_LABEL:
+ return moz_gtk_toggle_label_paint(drawable, rect, cliprect, state,
+ (widget == MOZ_GTK_RADIOBUTTON_LABEL),
+ direction);
+ break;
+ case MOZ_GTK_TOOLBAR:
+ return moz_gtk_toolbar_paint(drawable, rect, cliprect, direction);
+ break;
+ case MOZ_GTK_TOOLBAR_SEPARATOR:
+ return moz_gtk_toolbar_separator_paint(drawable, rect, cliprect,
+ direction);
+ break;
+ case MOZ_GTK_TOOLTIP:
+ return moz_gtk_tooltip_paint(drawable, rect, cliprect, direction);
+ break;
+ case MOZ_GTK_FRAME:
+ return moz_gtk_frame_paint(drawable, rect, cliprect, direction);
+ break;
+ case MOZ_GTK_RESIZER:
+ return moz_gtk_resizer_paint(drawable, rect, cliprect, state,
+ direction);
+ break;
+ case MOZ_GTK_PROGRESSBAR:
+ return moz_gtk_progressbar_paint(drawable, rect, cliprect, direction);
+ break;
+ case MOZ_GTK_PROGRESS_CHUNK:
+ case MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE:
+ case MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE:
+ return moz_gtk_progress_chunk_paint(drawable, rect, cliprect,
+ direction, widget);
+ break;
+ case MOZ_GTK_TAB_TOP:
+ case MOZ_GTK_TAB_BOTTOM:
+ return moz_gtk_tab_paint(drawable, rect, cliprect, state,
+ (GtkTabFlags) flags, direction, widget);
+ break;
+ case MOZ_GTK_TABPANELS:
+ return moz_gtk_tabpanels_paint(drawable, rect, cliprect, direction);
+ break;
+ case MOZ_GTK_TAB_SCROLLARROW:
+ return moz_gtk_tab_scroll_arrow_paint(drawable, rect, cliprect, state,
+ (GtkArrowType) flags, direction);
+ break;
+ case MOZ_GTK_MENUBAR:
+ return moz_gtk_menu_bar_paint(drawable, rect, cliprect, direction);
+ break;
+ case MOZ_GTK_MENUPOPUP:
+ return moz_gtk_menu_popup_paint(drawable, rect, cliprect, direction);
+ break;
+ case MOZ_GTK_MENUSEPARATOR:
+ return moz_gtk_menu_separator_paint(drawable, rect, cliprect,
+ direction);
+ break;
+ case MOZ_GTK_MENUBARITEM:
+ case MOZ_GTK_MENUITEM:
+ return moz_gtk_menu_item_paint(widget, drawable, rect, cliprect, state,
+ direction);
+ break;
+ case MOZ_GTK_MENUARROW:
+ return moz_gtk_menu_arrow_paint(drawable, rect, cliprect, state,
+ direction);
+ break;
+ case MOZ_GTK_TOOLBARBUTTON_ARROW:
+ return moz_gtk_arrow_paint(drawable, rect, cliprect, state,
+ (GtkArrowType) flags, direction);
+ break;
+ case MOZ_GTK_CHECKMENUITEM:
+ case MOZ_GTK_RADIOMENUITEM:
+ return moz_gtk_check_menu_item_paint(drawable, rect, cliprect, state,
+ (gboolean) flags,
+ (widget == MOZ_GTK_RADIOMENUITEM),
+ direction);
+ break;
+ case MOZ_GTK_SPLITTER_HORIZONTAL:
+ return moz_gtk_vpaned_paint(drawable, rect, cliprect, state);
+ break;
+ case MOZ_GTK_SPLITTER_VERTICAL:
+ return moz_gtk_hpaned_paint(drawable, rect, cliprect, state);
+ break;
+ case MOZ_GTK_WINDOW:
+ return moz_gtk_window_paint(drawable, rect, cliprect, direction);
+ break;
+ default:
+ g_warning("Unknown widget type: %d", widget);
+ }
+
+ return MOZ_GTK_UNKNOWN_WIDGET;
+}
+
+GtkWidget* moz_gtk_get_scrollbar_widget(void)
+{
+ MOZ_ASSERT(is_initialized, "Forgot to call moz_gtk_init()");
+ ensure_scrollbar_widget();
+ return gHorizScrollbarWidget;
+}
+
+gboolean moz_gtk_has_scrollbar_buttons(void)
+{
+ gboolean backward, forward, secondary_backward, secondary_forward;
+ MOZ_ASSERT(is_initialized, "Forgot to call moz_gtk_init()");
+ ensure_scrollbar_widget();
+ gtk_widget_style_get (gHorizScrollbarWidget,
+ "has-backward-stepper", &backward,
+ "has-forward-stepper", &forward,
+ "has-secondary-backward-stepper", &secondary_backward,
+ "has-secondary-forward-stepper", &secondary_forward,
+ NULL);
+ return backward | forward | secondary_forward | secondary_forward;
+}
+
+gint
+moz_gtk_shutdown()
+{
+ GtkWidgetClass *entry_class;
+
+ if (gTooltipWidget)
+ gtk_widget_destroy(gTooltipWidget);
+ /* This will destroy all of our widgets */
+ if (gProtoWindow)
+ gtk_widget_destroy(gProtoWindow);
+
+ gProtoWindow = NULL;
+ gProtoLayout = NULL;
+ gButtonWidget = NULL;
+ gToggleButtonWidget = NULL;
+ gButtonArrowWidget = NULL;
+ gCheckboxWidget = NULL;
+ gRadiobuttonWidget = NULL;
+ gHorizScrollbarWidget = NULL;
+ gVertScrollbarWidget = NULL;
+ gSpinWidget = NULL;
+ gHScaleWidget = NULL;
+ gVScaleWidget = NULL;
+ gEntryWidget = NULL;
+ gComboBoxWidget = NULL;
+ gComboBoxButtonWidget = NULL;
+ gComboBoxSeparatorWidget = NULL;
+ gComboBoxArrowWidget = NULL;
+ gComboBoxEntryWidget = NULL;
+ gComboBoxEntryButtonWidget = NULL;
+ gComboBoxEntryArrowWidget = NULL;
+ gComboBoxEntryTextareaWidget = NULL;
+ gHandleBoxWidget = NULL;
+ gToolbarWidget = NULL;
+ gStatusbarWidget = NULL;
+ gFrameWidget = NULL;
+ gProgressWidget = NULL;
+ gTabWidget = NULL;
+ gTooltipWidget = NULL;
+ gMenuBarWidget = NULL;
+ gMenuBarItemWidget = NULL;
+ gMenuPopupWidget = NULL;
+ gMenuItemWidget = NULL;
+ gImageMenuItemWidget = NULL;
+ gCheckMenuItemWidget = NULL;
+ gTreeViewWidget = NULL;
+ gMiddleTreeViewColumn = NULL;
+ gTreeHeaderCellWidget = NULL;
+ gTreeHeaderSortArrowWidget = NULL;
+ gExpanderWidget = NULL;
+ gToolbarSeparatorWidget = NULL;
+ gMenuSeparatorWidget = NULL;
+ gHPanedWidget = NULL;
+ gVPanedWidget = NULL;
+ gScrolledWindowWidget = NULL;
+
+ entry_class = g_type_class_peek(GTK_TYPE_ENTRY);
+ g_type_class_unref(entry_class);
+
+ is_initialized = FALSE;
+
+ return MOZ_GTK_SUCCESS;
+}
diff --git a/widget/gtk/gtk3drawing.cpp b/widget/gtk/gtk3drawing.cpp
new file mode 100644
index 000000000..fb95b4cc4
--- /dev/null
+++ b/widget/gtk/gtk3drawing.cpp
@@ -0,0 +1,2843 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+/*
+ * This file contains painting functions for each of the gtk2 widgets.
+ * Adapted from the gtkdrawing.c, and gtk+2.0 source.
+ */
+
+#include <gtk/gtk.h>
+#include <gdk/gdkprivate.h>
+#include <string.h>
+#include "gtkdrawing.h"
+#include "mozilla/Assertions.h"
+#include "prinrval.h"
+#include "WidgetStyleCache.h"
+
+#include <math.h>
+
+static style_prop_t style_prop_func;
+static gboolean have_arrow_scaling;
+static gboolean checkbox_check_state;
+static gboolean notebook_has_tab_gap;
+static gboolean is_initialized;
+
+#define ARROW_UP 0
+#define ARROW_DOWN G_PI
+#define ARROW_RIGHT G_PI_2
+#define ARROW_LEFT (G_PI+G_PI_2)
+
+#if !GTK_CHECK_VERSION(3,14,0)
+#define GTK_STATE_FLAG_CHECKED (1 << 11)
+#endif
+
+static gint
+moz_gtk_get_tab_thickness(GtkStyleContext *style);
+
+// GetStateFlagsFromGtkWidgetState() can be safely used for the specific
+// GtkWidgets that set both prelight and active flags. For other widgets,
+// either the GtkStateFlags or Gecko's GtkWidgetState need to be carefully
+// adjusted to match GTK behavior. Although GTK sets insensitive and focus
+// flags in the generic GtkWidget base class, GTK adds prelight and active
+// flags only to widgets that are expected to demonstrate prelight or active
+// states. This contrasts with HTML where any element may have :active and
+// :hover states, and so Gecko's GtkStateFlags do not necessarily map to GTK
+// flags. Failure to restrict the flags in the same way as GTK can cause
+// generic CSS selectors from some themes to unintentionally match elements
+// that are not expected to change appearance on hover or mouse-down.
+static GtkStateFlags
+GetStateFlagsFromGtkWidgetState(GtkWidgetState* state)
+{
+ GtkStateFlags stateFlags = GTK_STATE_FLAG_NORMAL;
+
+ if (state->disabled)
+ stateFlags = GTK_STATE_FLAG_INSENSITIVE;
+ else {
+ if (state->depressed || state->active)
+ stateFlags = static_cast<GtkStateFlags>(stateFlags|GTK_STATE_FLAG_ACTIVE);
+ if (state->inHover)
+ stateFlags = static_cast<GtkStateFlags>(stateFlags|GTK_STATE_FLAG_PRELIGHT);
+ if (state->focused)
+ stateFlags = static_cast<GtkStateFlags>(stateFlags|GTK_STATE_FLAG_FOCUSED);
+ }
+
+ return stateFlags;
+}
+
+static GtkStateFlags
+GetStateFlagsFromGtkTabFlags(GtkTabFlags flags)
+{
+ return ((flags & MOZ_GTK_TAB_SELECTED) == 0) ?
+ GTK_STATE_FLAG_NORMAL : GTK_STATE_FLAG_ACTIVE;
+}
+
+gint
+moz_gtk_enable_style_props(style_prop_t styleGetProp)
+{
+ style_prop_func = styleGetProp;
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_init()
+{
+ if (is_initialized)
+ return MOZ_GTK_SUCCESS;
+
+ is_initialized = TRUE;
+ have_arrow_scaling = (gtk_major_version > 2 ||
+ (gtk_major_version == 2 && gtk_minor_version >= 12));
+ if (gtk_major_version > 3 ||
+ (gtk_major_version == 3 && gtk_minor_version >= 14))
+ checkbox_check_state = GTK_STATE_FLAG_CHECKED;
+ else
+ checkbox_check_state = GTK_STATE_FLAG_ACTIVE;
+
+ if (gtk_check_version(3, 12, 0) == nullptr &&
+ gtk_check_version(3, 20, 0) != nullptr)
+ {
+ // Deprecated for Gtk >= 3.20+
+ GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_TAB_TOP);
+ gtk_style_context_get_style(style,
+ "has-tab-gap", &notebook_has_tab_gap, NULL);
+ ReleaseStyleContext(style);
+ }
+ else {
+ notebook_has_tab_gap = true;
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_checkbox_get_metrics(gint* indicator_size, gint* indicator_spacing)
+{
+ gtk_widget_style_get(GetWidget(MOZ_GTK_CHECKBUTTON_CONTAINER),
+ "indicator_size", indicator_size,
+ "indicator_spacing", indicator_spacing,
+ NULL);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_radio_get_metrics(gint* indicator_size, gint* indicator_spacing)
+{
+ gtk_widget_style_get(GetWidget(MOZ_GTK_RADIOBUTTON_CONTAINER),
+ "indicator_size", indicator_size,
+ "indicator_spacing", indicator_spacing,
+ NULL);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_get_focus_outline_size(GtkStyleContext* style,
+ gint* focus_h_width, gint* focus_v_width)
+{
+ 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);
+ *focus_h_width = border.left + padding.left;
+ *focus_v_width = border.top + padding.top;
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_focus_outline_size(gint* focus_h_width, gint* focus_v_width)
+{
+ GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_ENTRY);
+ moz_gtk_get_focus_outline_size(style, focus_h_width, focus_v_width);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_menuitem_get_horizontal_padding(gint* horizontal_padding)
+{
+ GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_MENUITEM);
+ gtk_style_context_get_style(style,
+ "horizontal-padding", horizontal_padding,
+ nullptr);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_checkmenuitem_get_horizontal_padding(gint* horizontal_padding)
+{
+ GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_CHECKMENUITEM_CONTAINER);
+ gtk_style_context_get_style(style,
+ "horizontal-padding", horizontal_padding,
+ nullptr);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_button_get_default_overflow(gint* border_top, gint* border_left,
+ gint* border_bottom, gint* border_right)
+{
+ GtkBorder* default_outside_border;
+
+ GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_BUTTON);
+ gtk_style_context_get_style(style,
+ "default-outside-border", &default_outside_border,
+ NULL);
+ ReleaseStyleContext(style);
+
+ if (default_outside_border) {
+ *border_top = default_outside_border->top;
+ *border_left = default_outside_border->left;
+ *border_bottom = default_outside_border->bottom;
+ *border_right = default_outside_border->right;
+ gtk_border_free(default_outside_border);
+ } else {
+ *border_top = *border_left = *border_bottom = *border_right = 0;
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_button_get_default_border(gint* border_top, gint* border_left,
+ gint* border_bottom, gint* border_right)
+{
+ GtkBorder* default_border;
+
+ GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_BUTTON);
+ gtk_style_context_get_style(style,
+ "default-border", &default_border,
+ NULL);
+ ReleaseStyleContext(style);
+
+ if (default_border) {
+ *border_top = default_border->top;
+ *border_left = default_border->left;
+ *border_bottom = default_border->bottom;
+ *border_right = default_border->right;
+ gtk_border_free(default_border);
+ } else {
+ /* see gtkbutton.c */
+ *border_top = *border_left = *border_bottom = *border_right = 1;
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_splitter_get_metrics(gint orientation, gint* size)
+{
+ GtkStyleContext *style;
+ if (orientation == GTK_ORIENTATION_HORIZONTAL) {
+ style = ClaimStyleContext(MOZ_GTK_SPLITTER_HORIZONTAL);
+ } else {
+ style = ClaimStyleContext(MOZ_GTK_SPLITTER_VERTICAL);
+ }
+ gtk_style_context_get_style(style, "handle_size", size, NULL);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_window_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_WINDOW, direction);
+
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND);
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_style_context_restore(style);
+
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_button_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkReliefStyle relief, GtkWidget* widget,
+ GtkTextDirection direction)
+{
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style = gtk_widget_get_style_context(widget);
+ gint x = rect->x, y=rect->y, width=rect->width, height=rect->height;
+
+ gtk_widget_set_direction(widget, direction);
+
+ gtk_style_context_save(style);
+ gtk_style_context_set_state(style, state_flags);
+
+ if (state->isDefault && relief == GTK_RELIEF_NORMAL) {
+ /* handle default borders both outside and inside the button */
+ gint default_top, default_left, default_bottom, default_right;
+ moz_gtk_button_get_default_overflow(&default_top, &default_left,
+ &default_bottom, &default_right);
+ x -= default_left;
+ y -= default_top;
+ width += default_left + default_right;
+ height += default_top + default_bottom;
+ gtk_render_background(style, cr, x, y, width, height);
+ gtk_render_frame(style, cr, x, y, width, height);
+ moz_gtk_button_get_default_border(&default_top, &default_left,
+ &default_bottom, &default_right);
+ x += default_left;
+ y += default_top;
+ width -= (default_left + default_right);
+ height -= (default_top + default_bottom);
+ } else if (relief != GTK_RELIEF_NONE || state->depressed ||
+ (state_flags & GTK_STATE_FLAG_PRELIGHT)) {
+ /* the following line can trigger an assertion (Crux theme)
+ file ../../gdk/gdkwindow.c: line 1846 (gdk_window_clear_area):
+ assertion `GDK_IS_WINDOW (window)' failed */
+ gtk_render_background(style, cr, x, y, width, height);
+ gtk_render_frame(style, cr, x, y, width, height);
+ }
+
+ if (state->focused) {
+ GtkBorder border;
+ gtk_style_context_get_border(style, state_flags, &border);
+ x += border.left;
+ y += border.top;
+ width -= (border.left + border.right);
+ height -= (border.top + border.bottom);
+ gtk_render_focus(style, cr, x, y, width, height);
+ }
+ gtk_style_context_restore(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_toggle_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ gboolean selected, gboolean inconsistent,
+ gboolean isradio, GtkTextDirection direction)
+{
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ gint indicator_size, indicator_spacing;
+ gint x, y, width, height;
+ gint focus_x, focus_y, focus_width, focus_height;
+ GtkStyleContext *style;
+
+ GtkWidget *widget = GetWidget(isradio ? MOZ_GTK_RADIOBUTTON_CONTAINER :
+ MOZ_GTK_CHECKBUTTON_CONTAINER);
+ gtk_widget_style_get(widget,
+ "indicator_size", &indicator_size,
+ "indicator_spacing", &indicator_spacing,
+ nullptr);
+
+ // XXX we should assert rect->height >= indicator_size too
+ // after bug 369581 is fixed.
+ MOZ_ASSERT(rect->width >= indicator_size,
+ "GetMinimumWidgetSize was ignored");
+
+ // Paint it center aligned in the rect.
+ x = rect->x + (rect->width - indicator_size) / 2;
+ y = rect->y + (rect->height - indicator_size) / 2;
+ width = indicator_size;
+ height = indicator_size;
+
+ focus_x = x - indicator_spacing;
+ focus_y = y - indicator_spacing;
+ focus_width = width + 2 * indicator_spacing;
+ focus_height = height + 2 * indicator_spacing;
+
+ if (selected)
+ state_flags = static_cast<GtkStateFlags>(state_flags|checkbox_check_state);
+
+ if (inconsistent)
+ state_flags = static_cast<GtkStateFlags>(state_flags|GTK_STATE_FLAG_INCONSISTENT);
+
+ style = ClaimStyleContext(isradio ? MOZ_GTK_RADIOBUTTON :
+ MOZ_GTK_CHECKBUTTON,
+ direction, state_flags);
+
+ if (gtk_check_version(3, 20, 0) == nullptr) {
+ gtk_render_background(style, cr, x, y, width, height);
+ gtk_render_frame(style, cr, x, y, width, height);
+ }
+
+ if (isradio) {
+ gtk_render_option(style, cr, x, y, width, height);
+ if (state->focused) {
+ gtk_render_focus(style, cr, focus_x, focus_y,
+ focus_width, focus_height);
+ }
+ }
+ else {
+ gtk_render_check(style, cr, x, y, width, height);
+ if (state->focused) {
+ gtk_render_focus(style, cr,
+ focus_x, focus_y, focus_width, focus_height);
+ }
+ }
+
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+calculate_button_inner_rect(GtkWidget* button, GdkRectangle* rect,
+ GdkRectangle* inner_rect,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style;
+ GtkBorder border;
+ GtkBorder padding = {0, 0, 0, 0};
+
+ 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);
+
+ inner_rect->x = rect->x + border.left + padding.left;
+ inner_rect->y = rect->y + padding.top + border.top;
+ inner_rect->width = MAX(1, rect->width - padding.left -
+ padding.right - border.left * 2);
+ inner_rect->height = MAX(1, rect->height - padding.top -
+ padding.bottom - border.top * 2);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+
+static gint
+calculate_arrow_rect(GtkWidget* arrow, GdkRectangle* rect,
+ GdkRectangle* arrow_rect, GtkTextDirection direction)
+{
+ /* defined in gtkarrow.c */
+ gfloat arrow_scaling = 0.7;
+ gfloat xalign, xpad;
+ gint extent;
+ gint mxpad, mypad;
+ gfloat mxalign, myalign;
+ GtkMisc* misc = GTK_MISC(arrow);
+
+ if (have_arrow_scaling)
+ gtk_style_context_get_style(gtk_widget_get_style_context(arrow),
+ "arrow_scaling", &arrow_scaling, NULL);
+
+ gtk_misc_get_padding(misc, &mxpad, &mypad);
+ extent = MIN((rect->width - mxpad * 2),
+ (rect->height - mypad * 2)) * arrow_scaling;
+
+ gtk_misc_get_alignment(misc, &mxalign, &myalign);
+
+ xalign = direction == GTK_TEXT_DIR_LTR ? mxalign : 1.0 - mxalign;
+ xpad = mxpad + (rect->width - extent) * xalign;
+
+ arrow_rect->x = direction == GTK_TEXT_DIR_LTR ?
+ floor(rect->x + xpad) : ceil(rect->x + xpad);
+ arrow_rect->y = floor(rect->y + mypad +
+ ((rect->height - extent) * myalign));
+
+ arrow_rect->width = arrow_rect->height = extent;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+void
+moz_gtk_get_widget_min_size(WidgetNodeType aGtkWidgetType, int* width,
+ int* height)
+{
+ GtkStyleContext* style = ClaimStyleContext(aGtkWidgetType);
+ GtkStateFlags state_flags = gtk_style_context_get_state(style);
+ gtk_style_context_get(style, state_flags,
+ "min-height", height,
+ "min-width", width,
+ nullptr);
+
+ GtkBorder border, padding, margin;
+ gtk_style_context_get_border(style, state_flags, &border);
+ gtk_style_context_get_padding(style, state_flags, &padding);
+ gtk_style_context_get_margin(style, state_flags, &margin);
+ ReleaseStyleContext(style);
+
+ *width += border.left + border.right + margin.left + margin.right +
+ padding.left + padding.right;
+ *height += border.top + border.bottom + margin.top + margin.bottom +
+ padding.top + padding.bottom;
+}
+
+static void
+moz_gtk_rectangle_inset(GdkRectangle* rect, GtkBorder& aBorder)
+{
+ MOZ_ASSERT(rect);
+ rect->x += aBorder.left;
+ rect->y += aBorder.top;
+ rect->width -= aBorder.left + aBorder.right;
+ rect->height -= aBorder.top + aBorder.bottom;
+}
+
+/* Subtracting margin is used to inset drawing of element which can have margins,
+ * like scrollbar, scrollbar's trough, thumb and scrollbar's button */
+static void
+moz_gtk_subtract_margin(GtkStyleContext* style, GdkRectangle* rect)
+{
+ MOZ_ASSERT(rect);
+ GtkBorder margin;
+
+ gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
+ &margin);
+ moz_gtk_rectangle_inset(rect, margin);
+}
+
+static gint
+moz_gtk_scrollbar_button_paint(cairo_t *cr, const GdkRectangle* aRect,
+ GtkWidgetState* state,
+ GtkScrollbarButtonFlags flags,
+ GtkTextDirection direction)
+{
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GdkRectangle arrow_rect;
+ gdouble arrow_angle;
+ GtkStyleContext* style;
+ gint arrow_displacement_x, arrow_displacement_y;
+
+ GtkWidget *scrollbar =
+ GetWidget(flags & MOZ_GTK_STEPPER_VERTICAL ?
+ MOZ_GTK_SCROLLBAR_VERTICAL : MOZ_GTK_SCROLLBAR_HORIZONTAL);
+
+ gtk_widget_set_direction(scrollbar, direction);
+
+ if (flags & MOZ_GTK_STEPPER_VERTICAL) {
+ arrow_angle = (flags & MOZ_GTK_STEPPER_DOWN) ? ARROW_DOWN : ARROW_UP;
+ } else {
+ arrow_angle = (flags & MOZ_GTK_STEPPER_DOWN) ? ARROW_RIGHT : ARROW_LEFT;
+ }
+
+ style = gtk_widget_get_style_context(scrollbar);
+
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BUTTON);
+ gtk_style_context_set_state(style, state_flags);
+ if (arrow_angle == ARROW_RIGHT) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_RIGHT);
+ } else if (arrow_angle == ARROW_DOWN) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BOTTOM);
+ } else if (arrow_angle == ARROW_LEFT) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_LEFT);
+ } else {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_TOP);
+ }
+
+ GdkRectangle rect = *aRect;
+ if (gtk_check_version(3,20,0) == nullptr) {
+ // The "trough-border" is not used since GTK 3.20. The stepper margin
+ // box occupies the full width of the "contents" gadget content box.
+ moz_gtk_subtract_margin(style, &rect);
+ } else {
+ // Scrollbar button has to be inset by trough_border because its DOM
+ // element is filling width of vertical scrollbar's track (or height
+ // in case of horizontal scrollbars).
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+ if (flags & MOZ_GTK_STEPPER_VERTICAL) {
+ rect.x += metrics.trough_border;
+ rect.width = metrics.slider_width;
+ } else {
+ rect.y += metrics.trough_border;
+ rect.height = metrics.slider_width;
+ }
+ }
+
+ gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
+ gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height);
+
+ arrow_rect.width = rect.width / 2;
+ arrow_rect.height = rect.height / 2;
+
+ gfloat arrow_scaling;
+ gtk_style_context_get_style(style, "arrow-scaling", &arrow_scaling, NULL);
+
+ gdouble arrow_size = MIN(rect.width, rect.height) * arrow_scaling;
+ arrow_rect.x = rect.x + (rect.width - arrow_size) / 2;
+ arrow_rect.y = rect.y + (rect.height - arrow_size) / 2;
+
+ if (state_flags & GTK_STATE_FLAG_ACTIVE) {
+ gtk_style_context_get_style(style,
+ "arrow-displacement-x", &arrow_displacement_x,
+ "arrow-displacement-y", &arrow_displacement_y,
+ NULL);
+
+ arrow_rect.x += arrow_displacement_x;
+ arrow_rect.y += arrow_displacement_y;
+ }
+
+ gtk_render_arrow(style, cr, arrow_angle,
+ arrow_rect.x,
+ arrow_rect.y,
+ arrow_size);
+
+ gtk_style_context_restore(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static void
+moz_gtk_update_scrollbar_style(GtkStyleContext* style,
+ WidgetNodeType widget,
+ GtkTextDirection direction)
+{
+ if (widget == MOZ_GTK_SCROLLBAR_HORIZONTAL) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BOTTOM);
+ } else {
+ if (direction == GTK_TEXT_DIR_LTR) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_RIGHT);
+ gtk_style_context_remove_class(style, GTK_STYLE_CLASS_LEFT);
+ } else {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_LEFT);
+ gtk_style_context_remove_class(style, GTK_STYLE_CLASS_RIGHT);
+ }
+ }
+}
+
+static void
+moz_gtk_draw_styled_frame(GtkStyleContext* style, cairo_t *cr,
+ const GdkRectangle* aRect, bool drawFocus)
+{
+ GdkRectangle rect = *aRect;
+ if (gtk_check_version(3, 6, 0) == nullptr) {
+ moz_gtk_subtract_margin(style, &rect);
+ }
+ gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
+ gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height);
+ if (drawFocus) {
+ gtk_render_focus(style, cr,
+ rect.x, rect.y, rect.width, rect.height);
+ }
+}
+
+static gint
+moz_gtk_scrollbar_trough_paint(WidgetNodeType widget,
+ cairo_t *cr, const GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(widget, direction);
+ moz_gtk_draw_styled_frame(style, cr, rect, state->focused);
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_scrollbar_paint(WidgetNodeType widget,
+ cairo_t *cr, const GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(widget, direction);
+ moz_gtk_update_scrollbar_style(style, widget, direction);
+
+ moz_gtk_draw_styled_frame(style, cr, rect, state->focused);
+
+ ReleaseStyleContext(style);
+ style = ClaimStyleContext((widget == MOZ_GTK_SCROLLBAR_HORIZONTAL) ?
+ MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL :
+ MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL,
+ direction);
+ moz_gtk_draw_styled_frame(style, cr, rect, state->focused);
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_scrollbar_thumb_paint(WidgetNodeType widget,
+ cairo_t *cr, const GdkRectangle* aRect,
+ GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+
+ GdkRectangle rect = *aRect;
+ GtkStyleContext* style = ClaimStyleContext(widget, direction, state_flags);
+ moz_gtk_subtract_margin(style, &rect);
+
+ 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);
+
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_spin_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_SPINBUTTON, direction);
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_spin_updown_paint(cairo_t *cr, GdkRectangle* rect,
+ gboolean isDown, GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_SPINBUTTON, direction,
+ GetStateFlagsFromGtkWidgetState(state));
+
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ /* hard code these values */
+ GdkRectangle arrow_rect;
+ arrow_rect.width = 6;
+ arrow_rect.height = 6;
+ arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2;
+ arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2;
+ arrow_rect.y += isDown ? -1 : 1;
+
+ gtk_render_arrow(style, cr,
+ isDown ? ARROW_DOWN : ARROW_UP,
+ arrow_rect.x, arrow_rect.y,
+ arrow_rect.width);
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+/* See gtk_range_draw() for reference.
+*/
+static gint
+moz_gtk_scale_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkOrientation flags, GtkTextDirection direction)
+{
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ gint x, y, width, height, min_width, min_height;
+ GtkStyleContext* style;
+ GtkBorder margin;
+
+ moz_gtk_get_scale_metrics(flags, &min_width, &min_height);
+
+ WidgetNodeType widget = (flags == GTK_ORIENTATION_HORIZONTAL) ?
+ MOZ_GTK_SCALE_TROUGH_HORIZONTAL :
+ MOZ_GTK_SCALE_TROUGH_VERTICAL;
+ style = ClaimStyleContext(widget, direction, state_flags);
+ gtk_style_context_get_margin(style, state_flags, &margin);
+
+ // Clamp the dimension perpendicular to the direction that the slider crosses
+ // to the minimum size.
+ if (flags == GTK_ORIENTATION_HORIZONTAL) {
+ width = rect->width - (margin.left + margin.right);
+ height = min_height - (margin.top + margin.bottom);
+ x = rect->x + margin.left;
+ y = rect->y + (rect->height - height)/2;
+ } else {
+ width = min_width - (margin.left + margin.right);
+ height = rect->height - (margin.top + margin.bottom);
+ x = rect->x + (rect->width - width)/2;
+ y = rect->y + margin.top;
+ }
+
+ gtk_render_background(style, cr, x, y, width, height);
+ gtk_render_frame(style, cr, x, y, width, height);
+
+ if (state->focused)
+ gtk_render_focus(style, cr,
+ rect->x, rect->y, rect->width, rect->height);
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_scale_thumb_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkOrientation flags, GtkTextDirection direction)
+{
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style;
+ gint thumb_width, thumb_height, x, y;
+
+ /* determine the thumb size, and position the thumb in the center in the opposite axis
+ */
+ if (flags == GTK_ORIENTATION_HORIZONTAL) {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL, &thumb_width, &thumb_height);
+ x = rect->x;
+ y = rect->y + (rect->height - thumb_height) / 2;
+ }
+ else {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_height, &thumb_width);
+ x = rect->x + (rect->width - thumb_width) / 2;
+ y = rect->y;
+ }
+
+ WidgetNodeType widget = (flags == GTK_ORIENTATION_HORIZONTAL) ?
+ MOZ_GTK_SCALE_THUMB_HORIZONTAL :
+ MOZ_GTK_SCALE_THUMB_VERTICAL;
+ style = ClaimStyleContext(widget, direction, state_flags);
+ gtk_render_slider(style, cr, x, y, thumb_width, thumb_height, flags);
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_gripper_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style =
+ ClaimStyleContext(MOZ_GTK_GRIPPER, direction,
+ GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_hpaned_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state)
+{
+ GtkStyleContext* style =
+ ClaimStyleContext(MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL,
+ GTK_TEXT_DIR_LTR,
+ GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_handle(style, cr,
+ rect->x, rect->y, rect->width, rect->height);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_vpaned_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state)
+{
+ GtkStyleContext* style =
+ ClaimStyleContext(MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL,
+ GTK_TEXT_DIR_LTR,
+ GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_handle(style, cr,
+ rect->x, rect->y, rect->width, rect->height);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+// See gtk_entry_draw() for reference.
+static gint
+moz_gtk_entry_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ 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
+
+ if (draw_focus_outline_only) {
+ // Inflate the given 'rect' with the focus outline size.
+ gint h, v;
+ moz_gtk_get_focus_outline_size(style, &h, &v);
+ rect->x -= h;
+ rect->width += 2 * h;
+ rect->y -= v;
+ rect->height += 2 * v;
+ width = rect->width;
+ height = rect->height;
+ } else {
+ gtk_render_background(style, cr, x, y, width, height);
+ }
+ gtk_render_frame(style, cr, x, y, width, height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_text_view_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ // GtkTextView and GtkScrolledWindow do not set active and prelight flags.
+ // The use of focus with MOZ_GTK_SCROLLED_WINDOW here is questionable
+ // because a parent widget will not have focus when its child GtkTextView
+ // has focus, but perhaps this may help identify a focused textarea with
+ // some themes as GtkTextView backgrounds do not typically render
+ // differently with focus.
+ GtkStateFlags state_flags =
+ state->disabled ? GTK_STATE_FLAG_INSENSITIVE :
+ state->focused ? GTK_STATE_FLAG_FOCUSED :
+ GTK_STATE_FLAG_NORMAL;
+
+ GtkStyleContext* style_frame =
+ ClaimStyleContext(MOZ_GTK_SCROLLED_WINDOW, direction, state_flags);
+ gtk_render_frame(style_frame, cr, rect->x, rect->y, rect->width, rect->height);
+
+ GtkBorder border, padding;
+ gtk_style_context_get_border(style_frame, state_flags, &border);
+ gtk_style_context_get_padding(style_frame, state_flags, &padding);
+ ReleaseStyleContext(style_frame);
+
+ GtkStyleContext* style =
+ ClaimStyleContext(MOZ_GTK_TEXT_VIEW, direction, state_flags);
+
+ gint xthickness = border.left + padding.left;
+ gint ythickness = border.top + padding.top;
+
+ gtk_render_background(style, cr,
+ rect->x + xthickness, rect->y + ythickness,
+ rect->width - 2 * xthickness,
+ rect->height - 2 * ythickness);
+
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_treeview_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ gint xthickness, ythickness;
+ GtkStyleContext *style;
+ GtkStyleContext *style_tree;
+ GtkStateFlags state_flags;
+ GtkBorder border;
+
+ /* only handle disabled and normal states, otherwise the whole background
+ * area will be painted differently with other states */
+ state_flags = state->disabled ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL;
+
+ style = ClaimStyleContext(MOZ_GTK_SCROLLED_WINDOW, direction);
+ gtk_style_context_get_border(style, state_flags, &border);
+ xthickness = border.left;
+ ythickness = border.top;
+ ReleaseStyleContext(style);
+
+ style_tree = ClaimStyleContext(MOZ_GTK_TREEVIEW_VIEW, direction);
+ gtk_render_background(style_tree, cr,
+ rect->x + xthickness, rect->y + ythickness,
+ rect->width - 2 * xthickness,
+ rect->height - 2 * ythickness);
+ ReleaseStyleContext(style_tree);
+
+ style = ClaimStyleContext(MOZ_GTK_SCROLLED_WINDOW, direction);
+ gtk_render_frame(style, cr,
+ rect->x, rect->y, rect->width, rect->height);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_tree_header_cell_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ gboolean isSorted, GtkTextDirection direction)
+{
+ moz_gtk_button_paint(cr, rect, state, GTK_RELIEF_NORMAL,
+ GetWidget(MOZ_GTK_TREE_HEADER_CELL), direction);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_tree_header_sort_arrow_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state, GtkArrowType arrow_type,
+ GtkTextDirection direction)
+{
+ GdkRectangle arrow_rect;
+ gdouble arrow_angle;
+ GtkStyleContext* style;
+
+ /* hard code these values */
+ arrow_rect.width = 11;
+ arrow_rect.height = 11;
+ arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2;
+ arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2;
+ style = ClaimStyleContext(MOZ_GTK_TREE_HEADER_SORTARROW, direction,
+ GetStateFlagsFromGtkWidgetState(state));
+ switch (arrow_type) {
+ case GTK_ARROW_LEFT:
+ arrow_angle = ARROW_LEFT;
+ break;
+ case GTK_ARROW_RIGHT:
+ arrow_angle = ARROW_RIGHT;
+ break;
+ case GTK_ARROW_DOWN:
+ arrow_angle = ARROW_DOWN;
+ break;
+ default:
+ arrow_angle = ARROW_UP;
+ break;
+ }
+ if (arrow_type != GTK_ARROW_NONE)
+ gtk_render_arrow(style, cr, arrow_angle,
+ arrow_rect.x, arrow_rect.y,
+ arrow_rect.width);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+/* See gtk_expander_paint() for reference.
+ */
+static gint
+moz_gtk_treeview_expander_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkExpanderStyle expander_state,
+ GtkTextDirection direction)
+{
+ /* Because the frame we get is of the entire treeview, we can't get the precise
+ * event state of one expander, thus rendering hover and active feedback useless. */
+ GtkStateFlags state_flags = state->disabled ? GTK_STATE_FLAG_INSENSITIVE :
+ GTK_STATE_FLAG_NORMAL;
+
+ /* GTK_STATE_FLAG_ACTIVE controls expanded/colapsed state rendering
+ * in gtk_render_expander()
+ */
+ if (expander_state == GTK_EXPANDER_EXPANDED)
+ state_flags = static_cast<GtkStateFlags>(state_flags|checkbox_check_state);
+ else
+ state_flags = static_cast<GtkStateFlags>(state_flags&~(checkbox_check_state));
+
+ GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_TREEVIEW_EXPANDER,
+ direction, state_flags);
+ gtk_render_expander(style, cr,
+ rect->x,
+ rect->y,
+ rect->width,
+ rect->height);
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+/* See gtk_separator_draw() for reference.
+*/
+static gint
+moz_gtk_combo_box_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GdkRectangle arrow_rect, real_arrow_rect;
+ gint separator_width;
+ gboolean wide_separators;
+ GtkStyleContext* style;
+ GtkRequisition arrow_req;
+
+ GtkWidget* comboBoxButton = GetWidget(MOZ_GTK_COMBOBOX_BUTTON);
+ GtkWidget* comboBoxArrow = GetWidget(MOZ_GTK_COMBOBOX_ARROW);
+
+ /* Also sets the direction on gComboBoxButtonWidget, which is then
+ * inherited by the separator and arrow */
+ moz_gtk_button_paint(cr, rect, state, GTK_RELIEF_NORMAL,
+ comboBoxButton, direction);
+
+ calculate_button_inner_rect(comboBoxButton, rect, &arrow_rect, direction);
+ /* Now arrow_rect contains the inner rect ; we want to correct the width
+ * to what the arrow needs (see gtk_combo_box_size_allocate) */
+ gtk_widget_get_preferred_size(comboBoxArrow, NULL, &arrow_req);
+
+ if (direction == GTK_TEXT_DIR_LTR)
+ arrow_rect.x += arrow_rect.width - arrow_req.width;
+ arrow_rect.width = arrow_req.width;
+
+ calculate_arrow_rect(comboBoxArrow,
+ &arrow_rect, &real_arrow_rect, direction);
+
+ style = ClaimStyleContext(MOZ_GTK_COMBOBOX_ARROW);
+ gtk_render_arrow(style, cr, ARROW_DOWN,
+ real_arrow_rect.x, real_arrow_rect.y,
+ real_arrow_rect.width);
+ ReleaseStyleContext(style);
+
+ /* If there is no separator in the theme, there's nothing left to do. */
+ GtkWidget* widget = GetWidget(MOZ_GTK_COMBOBOX_SEPARATOR);
+ if (!widget)
+ return MOZ_GTK_SUCCESS;
+ style = gtk_widget_get_style_context(widget);
+ gtk_style_context_get_style(style,
+ "wide-separators", &wide_separators,
+ "separator-width", &separator_width,
+ NULL);
+
+ if (wide_separators) {
+ if (direction == GTK_TEXT_DIR_LTR)
+ arrow_rect.x -= separator_width;
+ else
+ arrow_rect.x += arrow_rect.width;
+
+ gtk_render_frame(style, cr, arrow_rect.x, arrow_rect.y, separator_width, arrow_rect.height);
+ } else {
+ if (direction == GTK_TEXT_DIR_LTR) {
+ GtkBorder padding;
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ gtk_style_context_get_padding(style, state_flags, &padding);
+ arrow_rect.x -= padding.left;
+ }
+ else
+ arrow_rect.x += arrow_rect.width;
+
+ gtk_render_line(style, cr,
+ arrow_rect.x, arrow_rect.y,
+ arrow_rect.x, arrow_rect.y + arrow_rect.height);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_arrow_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkArrowType arrow_type, GtkTextDirection direction)
+{
+ GdkRectangle arrow_rect;
+ gdouble arrow_angle;
+
+ if (direction == GTK_TEXT_DIR_RTL) {
+ if (arrow_type == GTK_ARROW_LEFT) {
+ arrow_type = GTK_ARROW_RIGHT;
+ } else if (arrow_type == GTK_ARROW_RIGHT) {
+ arrow_type = GTK_ARROW_LEFT;
+ }
+ }
+ switch (arrow_type) {
+ case GTK_ARROW_LEFT:
+ arrow_angle = ARROW_LEFT;
+ break;
+ case GTK_ARROW_RIGHT:
+ arrow_angle = ARROW_RIGHT;
+ break;
+ case GTK_ARROW_DOWN:
+ arrow_angle = ARROW_DOWN;
+ break;
+ default:
+ arrow_angle = ARROW_UP;
+ break;
+ }
+ if (arrow_type == GTK_ARROW_NONE)
+ return MOZ_GTK_SUCCESS;
+
+ calculate_arrow_rect(GetWidget(MOZ_GTK_BUTTON_ARROW), rect, &arrow_rect,
+ direction);
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_BUTTON_ARROW,
+ direction, state_flags);
+ gtk_render_arrow(style, cr, arrow_angle,
+ arrow_rect.x, arrow_rect.y, arrow_rect.width);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_combo_box_entry_button_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ gboolean input_focus,
+ GtkTextDirection direction)
+{
+ gint x_displacement, y_displacement;
+ GdkRectangle arrow_rect, real_arrow_rect;
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style;
+
+ GtkWidget* comboBoxEntry = GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON);
+ moz_gtk_button_paint(cr, rect, state, GTK_RELIEF_NORMAL,
+ comboBoxEntry, direction);
+ calculate_button_inner_rect(comboBoxEntry, rect, &arrow_rect, direction);
+
+ if (state_flags & GTK_STATE_FLAG_ACTIVE) {
+ style = gtk_widget_get_style_context(comboBoxEntry);
+ gtk_style_context_get_style(style,
+ "child-displacement-x", &x_displacement,
+ "child-displacement-y", &y_displacement,
+ NULL);
+ arrow_rect.x += x_displacement;
+ arrow_rect.y += y_displacement;
+ }
+
+ calculate_arrow_rect(GetWidget(MOZ_GTK_COMBOBOX_ENTRY_ARROW),
+ &arrow_rect, &real_arrow_rect, direction);
+
+ style = ClaimStyleContext(MOZ_GTK_COMBOBOX_ENTRY_ARROW);
+ gtk_render_arrow(style, cr, ARROW_DOWN,
+ real_arrow_rect.x, real_arrow_rect.y,
+ real_arrow_rect.width);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_container_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ WidgetNodeType widget_type,
+ GtkTextDirection direction)
+{
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style = ClaimStyleContext(widget_type, direction,
+ state_flags);
+ /* this is for drawing a prelight box */
+ if (state_flags & GTK_STATE_FLAG_PRELIGHT) {
+ gtk_render_background(style, cr,
+ rect->x, rect->y, rect->width, rect->height);
+ }
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_toggle_label_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ gboolean isradio, GtkTextDirection direction)
+{
+ if (!state->focused)
+ return MOZ_GTK_SUCCESS;
+
+ GtkStyleContext *style =
+ ClaimStyleContext(isradio ? MOZ_GTK_RADIOBUTTON_CONTAINER :
+ MOZ_GTK_CHECKBUTTON_CONTAINER,
+ direction,
+ GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_focus(style, cr,
+ rect->x, rect->y, rect->width, rect->height);
+
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_toolbar_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_TOOLBAR, direction);
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+/* See _gtk_toolbar_paint_space_line() for reference.
+*/
+static gint
+moz_gtk_toolbar_separator_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ gint separator_width;
+ gint paint_width;
+ gboolean wide_separators;
+
+ /* Defined as constants in GTK+ 2.10.14 */
+ const double start_fraction = 0.2;
+ const double end_fraction = 0.8;
+
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_TOOLBAR);
+ gtk_style_context_get_style(style,
+ "wide-separators", &wide_separators,
+ "separator-width", &separator_width,
+ NULL);
+ ReleaseStyleContext(style);
+
+ style = ClaimStyleContext(MOZ_GTK_TOOLBAR_SEPARATOR, direction);
+ if (wide_separators) {
+ if (separator_width > rect->width)
+ separator_width = rect->width;
+
+ gtk_render_frame(style, cr,
+ rect->x + (rect->width - separator_width) / 2,
+ rect->y + rect->height * start_fraction,
+ separator_width,
+ rect->height * (end_fraction - start_fraction));
+ } else {
+ GtkBorder padding;
+ gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding);
+
+ paint_width = padding.left;
+ if (paint_width > rect->width)
+ paint_width = rect->width;
+
+ gtk_render_line(style, cr,
+ rect->x + (rect->width - paint_width) / 2,
+ rect->y + rect->height * start_fraction,
+ rect->x + (rect->width - paint_width) / 2,
+ rect->y + rect->height * end_fraction);
+ }
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_tooltip_paint(cairo_t *cr, const GdkRectangle* aRect,
+ GtkTextDirection direction)
+{
+ // Tooltip widget is made in GTK3 as following tree:
+ // Tooltip window
+ // Horizontal Box
+ // Icon (not supported by Firefox)
+ // Label
+ // Each element can be fully styled by CSS of GTK theme.
+ // We have to draw all elements with appropriate offset and right dimensions.
+
+ // Tooltip drawing
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_TOOLTIP, direction);
+ GdkRectangle rect = *aRect;
+ gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
+ gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height);
+
+ // Horizontal Box drawing
+ //
+ // The box element has hard-coded 6px margin-* GtkWidget properties, which
+ // are added between the window dimensions and the CSS margin box of the
+ // horizontal box. The frame of the tooltip window is drawn in the
+ // 6px margin.
+ // For drawing Horizontal Box we have to inset drawing area by that 6px
+ // plus its CSS margin.
+ GtkStyleContext* boxStyle =
+ CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0), style);
+
+ rect.x += 6;
+ rect.y += 6;
+ rect.width -= 12;
+ rect.height -= 12;
+
+ moz_gtk_subtract_margin(boxStyle, &rect);
+ gtk_render_background(boxStyle, cr, rect.x, rect.y, rect.width, rect.height);
+ gtk_render_frame(boxStyle, cr, rect.x, rect.y, rect.width, rect.height);
+
+ // Label drawing
+ GtkBorder padding, border;
+ gtk_style_context_get_padding(boxStyle, GTK_STATE_FLAG_NORMAL, &padding);
+ moz_gtk_rectangle_inset(&rect, padding);
+ gtk_style_context_get_border(boxStyle, GTK_STATE_FLAG_NORMAL, &border);
+ moz_gtk_rectangle_inset(&rect, border);
+
+ GtkStyleContext* labelStyle =
+ CreateStyleForWidget(gtk_label_new(nullptr), boxStyle);
+ moz_gtk_draw_styled_frame(labelStyle, cr, &rect, false);
+ g_object_unref(labelStyle);
+
+ g_object_unref(boxStyle);
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_resizer_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style;
+
+ // gtk_render_handle() draws a background, so use GtkTextView and its
+ // GTK_STYLE_CLASS_VIEW to match the background with textarea elements.
+ // The resizer is drawn with shaded variants of the background color, and
+ // so a transparent background would lead to a transparent resizer.
+ style = ClaimStyleContext(MOZ_GTK_TEXT_VIEW, GTK_TEXT_DIR_LTR,
+ GetStateFlagsFromGtkWidgetState(state));
+ // TODO - we need to save/restore style when gtk 3.20 CSS node path
+ // is used
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_GRIP);
+
+ // Workaround unico not respecting the text direction for resizers.
+ // See bug 1174248.
+ cairo_save(cr);
+ if (direction == GTK_TEXT_DIR_RTL) {
+ cairo_matrix_t mat;
+ cairo_matrix_init_translate(&mat, 2 * rect->x + rect->width, 0);
+ cairo_matrix_scale(&mat, -1, 1);
+ cairo_transform(cr, &mat);
+ }
+
+ gtk_render_handle(style, cr, rect->x, rect->y, rect->width, rect->height);
+ cairo_restore(cr);
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_frame_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_FRAME, direction);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_progressbar_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_PROGRESS_TROUGH,
+ direction);
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_progress_chunk_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction,
+ WidgetNodeType widget)
+{
+ GtkStyleContext* style;
+
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ /* Ask for MOZ_GTK_PROGRESS_TROUGH instead of MOZ_GTK_PROGRESSBAR
+ * because ClaimStyleContext() saves/restores that style */
+ style = ClaimStyleContext(MOZ_GTK_PROGRESS_TROUGH, direction);
+ gtk_style_context_remove_class(style, GTK_STYLE_CLASS_TROUGH);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_PROGRESSBAR);
+ } else {
+ style = ClaimStyleContext(MOZ_GTK_PROGRESS_CHUNK, direction);
+ }
+
+ if (widget == MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE ||
+ widget == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE) {
+ /**
+ * The bar's size and the bar speed are set depending of the progress'
+ * size. These could also be constant for all progress bars easily.
+ */
+ gboolean vertical = (widget == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE);
+
+ /* The size of the dimension we are going to use for the animation. */
+ const gint progressSize = vertical ? rect->height : rect->width;
+
+ /* The bar is using a fifth of the element size, based on GtkProgressBar
+ * activity-blocks property. */
+ const gint barSize = MAX(1, progressSize / 5);
+
+ /* Represents the travel that has to be done for a complete cycle. */
+ const gint travel = 2 * (progressSize - barSize);
+
+ /* period equals to travel / pixelsPerMillisecond
+ * where pixelsPerMillisecond equals progressSize / 1000.0.
+ * This is equivalent to 1600. */
+ static const guint period = 1600;
+ const gint t = PR_IntervalToMilliseconds(PR_IntervalNow()) % period;
+ const gint dx = travel * t / period;
+
+ if (vertical) {
+ rect->y += (dx < travel / 2) ? dx : travel - dx;
+ rect->height = barSize;
+ } else {
+ rect->x += (dx < travel / 2) ? dx : travel - dx;
+ rect->width = barSize;
+ }
+ }
+
+ // gtk_render_activity was used to render progress chunks on GTK versions
+ // before 3.13.7, see bug 1173907.
+ if (!gtk_check_version(3, 13, 7)) {
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ } else {
+ gtk_render_activity(style, cr, rect->x, rect->y, rect->width, rect->height);
+ }
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_get_tab_thickness(GtkStyleContext *style)
+{
+ if (!notebook_has_tab_gap)
+ 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);
+ if (border.top < 2)
+ return 2; /* some themes don't set ythickness correctly */
+
+ return border.top;
+}
+
+gint
+moz_gtk_get_tab_thickness(WidgetNodeType aNodeType)
+{
+ GtkStyleContext *style = ClaimStyleContext(aNodeType);
+ int thickness = moz_gtk_get_tab_thickness(style);
+ ReleaseStyleContext(style);
+ return thickness;
+}
+
+/* actual small tabs */
+static gint
+moz_gtk_tab_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTabFlags flags, GtkTextDirection direction,
+ WidgetNodeType widget)
+{
+ /* When the tab isn't selected, we just draw a notebook extension.
+ * When it is selected, we overwrite the adjacent border of the tabpanel
+ * touching the tab with a pierced border (called "the gap") to make the
+ * tab appear physically attached to the tabpanel; see details below. */
+
+ GtkStyleContext* style;
+ GdkRectangle tabRect;
+ GdkRectangle focusRect;
+ GdkRectangle backRect;
+ int initial_gap = 0;
+ bool isBottomTab = (widget == MOZ_GTK_TAB_BOTTOM);
+
+ style = ClaimStyleContext(widget, direction,
+ GetStateFlagsFromGtkTabFlags(flags));
+ tabRect = *rect;
+
+ if (flags & MOZ_GTK_TAB_FIRST) {
+ gtk_style_context_get_style(style, "initial-gap", &initial_gap, NULL);
+ tabRect.width -= initial_gap;
+
+ if (direction != GTK_TEXT_DIR_RTL) {
+ tabRect.x += initial_gap;
+ }
+ }
+
+ focusRect = backRect = tabRect;
+
+ if (notebook_has_tab_gap) {
+ if ((flags & MOZ_GTK_TAB_SELECTED) == 0) {
+ /* Only draw the tab */
+ gtk_render_extension(style, cr,
+ tabRect.x, tabRect.y, tabRect.width, tabRect.height,
+ isBottomTab ? GTK_POS_TOP : GTK_POS_BOTTOM );
+ } else {
+ /* Draw the tab and the gap
+ * We want the gap to be positioned exactly on the tabpanel top
+ * border; since tabbox.css may set a negative margin so that the tab
+ * frame rect already overlaps the tabpanel frame rect, we need to take
+ * that into account when drawing. To that effect, nsNativeThemeGTK
+ * passes us this negative margin (bmargin in the graphic below) in the
+ * lowest bits of |flags|. We use it to set gap_voffset, the distance
+ * between the top of the gap and the bottom of the tab (resp. the
+ * bottom of the gap and the top of the tab when we draw a bottom tab),
+ * while ensuring that the gap always touches the border of the tab,
+ * i.e. 0 <= gap_voffset <= gap_height, to avoid surprinsing results
+ * with big negative or positive margins.
+ * Here is a graphical explanation in the case of top tabs:
+ * ___________________________
+ * / \
+ * | T A B |
+ * ----------|. . . . . . . . . . . . . . .|----- top of tabpanel
+ * : ^ bmargin : ^
+ * : | (-negative margin, : |
+ * bottom : v passed in flags) : | gap_height
+ * of -> :.............................: | (the size of the
+ * the tab . part of the gap . | tabpanel top border)
+ * . outside of the tab . v
+ * ----------------------------------------------
+ *
+ * To draw the gap, we use gtk_paint_box_gap(), see comment in
+ * moz_gtk_tabpanels_paint(). This box_gap is made 3 * gap_height tall,
+ * which should suffice to ensure that the only visible border is the
+ * pierced one. If the tab is in the middle, we make the box_gap begin
+ * a bit to the left of the tab and end a bit to the right, adjusting
+ * the gap position so it still is under the tab, because we want the
+ * rendering of a gap in the middle of a tabpanel. This is the role of
+ * the gints gap_{l,r}_offset. On the contrary, if the tab is the
+ * first, we align the start border of the box_gap with the start
+ * border of the tab (left if LTR, right if RTL), by setting the
+ * appropriate offset to 0.*/
+ gint gap_loffset, gap_roffset, gap_voffset, gap_height;
+
+ /* Get height needed by the gap */
+ gap_height = moz_gtk_get_tab_thickness(style);
+
+ /* Extract gap_voffset from the first bits of flags */
+ gap_voffset = flags & MOZ_GTK_TAB_MARGIN_MASK;
+ if (gap_voffset > gap_height)
+ gap_voffset = gap_height;
+
+ /* Set gap_{l,r}_offset to appropriate values */
+ gap_loffset = gap_roffset = 20; /* should be enough */
+ if (flags & MOZ_GTK_TAB_FIRST) {
+ if (direction == GTK_TEXT_DIR_RTL)
+ gap_roffset = initial_gap;
+ else
+ gap_loffset = initial_gap;
+ }
+
+ if (isBottomTab) {
+ /* Draw the tab on bottom */
+ focusRect.y += gap_voffset;
+ focusRect.height -= gap_voffset;
+
+ gtk_render_extension(style, cr,
+ tabRect.x, tabRect.y + gap_voffset, tabRect.width,
+ tabRect.height - gap_voffset, GTK_POS_TOP);
+
+ gtk_style_context_remove_region(style, GTK_STYLE_REGION_TAB);
+
+ backRect.y += (gap_voffset - gap_height);
+ backRect.height = gap_height;
+
+ /* Draw the gap; erase with background color before painting in
+ * case theme does not */
+ gtk_render_background(style, cr, backRect.x, backRect.y,
+ backRect.width, backRect.height);
+ cairo_save(cr);
+ cairo_rectangle(cr, backRect.x, backRect.y, backRect.width, backRect.height);
+ cairo_clip(cr);
+
+ gtk_render_frame_gap(style, cr,
+ tabRect.x - gap_loffset,
+ tabRect.y + gap_voffset - 3 * gap_height,
+ tabRect.width + gap_loffset + gap_roffset,
+ 3 * gap_height, GTK_POS_BOTTOM,
+ gap_loffset, gap_loffset + tabRect.width);
+ cairo_restore(cr);
+ } else {
+ /* Draw the tab on top */
+ focusRect.height -= gap_voffset;
+ gtk_render_extension(style, cr,
+ tabRect.x, tabRect.y, tabRect.width,
+ tabRect.height - gap_voffset, GTK_POS_BOTTOM);
+
+ gtk_style_context_remove_region(style, GTK_STYLE_REGION_TAB);
+
+ backRect.y += (tabRect.height - gap_voffset);
+ backRect.height = gap_height;
+
+ /* Draw the gap; erase with background color before painting in
+ * case theme does not */
+ gtk_render_background(style, cr, backRect.x, backRect.y,
+ backRect.width, backRect.height);
+
+ cairo_save(cr);
+ cairo_rectangle(cr, backRect.x, backRect.y, backRect.width, backRect.height);
+ cairo_clip(cr);
+
+ gtk_render_frame_gap(style, cr,
+ tabRect.x - gap_loffset,
+ tabRect.y + tabRect.height - gap_voffset,
+ tabRect.width + gap_loffset + gap_roffset,
+ 3 * gap_height, GTK_POS_TOP,
+ gap_loffset, gap_loffset + tabRect.width);
+ cairo_restore(cr);
+ }
+ }
+ } else {
+ gtk_render_background(style, cr, tabRect.x, tabRect.y, tabRect.width, tabRect.height);
+ gtk_render_frame(style, cr, tabRect.x, tabRect.y, tabRect.width, tabRect.height);
+ }
+
+ if (state->focused) {
+ /* Paint the focus ring */
+ GtkBorder padding;
+ gtk_style_context_get_padding(style, GetStateFlagsFromGtkWidgetState(state), &padding);
+
+ focusRect.x += padding.left;
+ focusRect.width -= (padding.left + padding.right);
+ focusRect.y += padding.top;
+ focusRect.height -= (padding.top + padding.bottom);
+
+ gtk_render_focus(style, cr,
+ focusRect.x, focusRect.y, focusRect.width, focusRect.height);
+ }
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+/* tab area*/
+static gint
+moz_gtk_tabpanels_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_TABPANELS, direction);
+ gtk_render_background(style, cr, rect->x, rect->y,
+ rect->width, rect->height);
+ /*
+ * The gap size is not needed in moz_gtk_tabpanels_paint because
+ * the gap will be painted with the foreground tab in moz_gtk_tab_paint.
+ *
+ * However, if moz_gtk_tabpanels_paint just uses gtk_render_frame(),
+ * the theme will think that there are no tabs and may draw something
+ * different.Hence the trick of using two clip regions, and drawing the
+ * gap outside each clip region, to get the correct frame for
+ * a tabpanel with tabs.
+ */
+ /* left side */
+ cairo_save(cr);
+ cairo_rectangle(cr, rect->x, rect->y,
+ rect->x + rect->width / 2,
+ rect->y + rect->height);
+ cairo_clip(cr);
+ gtk_render_frame_gap(style, cr,
+ rect->x, rect->y,
+ rect->width, rect->height,
+ GTK_POS_TOP, rect->width - 1, rect->width);
+ cairo_restore(cr);
+
+ /* right side */
+ cairo_save(cr);
+ cairo_rectangle(cr, rect->x + rect->width / 2, rect->y,
+ rect->x + rect->width,
+ rect->y + rect->height);
+ cairo_clip(cr);
+ gtk_render_frame_gap(style, cr,
+ rect->x, rect->y,
+ rect->width, rect->height,
+ GTK_POS_TOP, 0, 1);
+ cairo_restore(cr);
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_tab_scroll_arrow_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkArrowType arrow_type,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style;
+ gdouble arrow_angle;
+ gint arrow_size = MIN(rect->width, rect->height);
+ gint x = rect->x + (rect->width - arrow_size) / 2;
+ gint y = rect->y + (rect->height - arrow_size) / 2;
+
+ if (direction == GTK_TEXT_DIR_RTL) {
+ arrow_type = (arrow_type == GTK_ARROW_LEFT) ?
+ GTK_ARROW_RIGHT : GTK_ARROW_LEFT;
+ }
+ switch (arrow_type) {
+ case GTK_ARROW_LEFT:
+ arrow_angle = ARROW_LEFT;
+ break;
+ case GTK_ARROW_RIGHT:
+ arrow_angle = ARROW_RIGHT;
+ break;
+ case GTK_ARROW_DOWN:
+ arrow_angle = ARROW_DOWN;
+ break;
+ default:
+ arrow_angle = ARROW_UP;
+ break;
+ }
+ if (arrow_type != GTK_ARROW_NONE) {
+ style = ClaimStyleContext(MOZ_GTK_TAB_SCROLLARROW, direction,
+ GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_arrow(style, cr, arrow_angle,
+ x, y, arrow_size);
+ ReleaseStyleContext(style);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_menu_bar_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style;
+
+ GtkWidget* widget = GetWidget(MOZ_GTK_MENUBAR);
+ gtk_widget_set_direction(widget, direction);
+
+ style = gtk_widget_get_style_context(widget);
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_MENUBAR);
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_style_context_restore(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_menu_popup_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style;
+
+ GtkWidget* widget = GetWidget(MOZ_GTK_MENUPOPUP);
+ gtk_widget_set_direction(widget, direction);
+
+ // Draw a backing toplevel. This fixes themes that don't provide a menu
+ // background, and depend on the GtkMenu's implementation window to provide it.
+ moz_gtk_window_paint(cr, rect, direction);
+
+ style = gtk_widget_get_style_context(widget);
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_MENU);
+
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_style_context_restore(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+// See gtk_menu_item_draw() for reference.
+static gint
+moz_gtk_menu_separator_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style;
+ gboolean wide_separators;
+ gint separator_height;
+ gint x, y, w;
+ GtkBorder padding;
+
+ style = ClaimStyleContext(MOZ_GTK_MENUSEPARATOR, direction);
+ gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding);
+
+ x = rect->x;
+ y = rect->y;
+ w = rect->width;
+
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_SEPARATOR);
+
+ gtk_style_context_get_style(style,
+ "wide-separators", &wide_separators,
+ "separator-height", &separator_height,
+ NULL);
+
+ if (wide_separators) {
+ gtk_render_frame(style, cr,
+ x + padding.left,
+ y + padding.top,
+ w - padding.left - padding.right,
+ separator_height);
+ } else {
+ gtk_render_line(style, cr,
+ x + padding.left,
+ y + padding.top,
+ x + w - padding.right - 1,
+ y + padding.top);
+ }
+
+ gtk_style_context_restore(style);
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+// See gtk_menu_item_draw() for reference.
+static gint
+moz_gtk_menu_item_paint(WidgetNodeType widget, cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state, GtkTextDirection direction)
+{
+ gint x, y, w, h;
+
+ if (state->inHover && !state->disabled) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style =
+ ClaimStyleContext(widget, direction, state_flags);
+
+ bool pre_3_6 = gtk_check_version(3, 6, 0) != nullptr;
+ if (pre_3_6) {
+ // GTK+ 3.4 saves the style context and adds the menubar class to
+ // menubar children, but does each of these only when drawing, not
+ // during layout.
+ gtk_style_context_save(style);
+ if (widget == MOZ_GTK_MENUBARITEM) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_MENUBAR);
+ }
+ }
+
+ x = rect->x;
+ y = rect->y;
+ w = rect->width;
+ h = rect->height;
+
+ gtk_render_background(style, cr, x, y, w, h);
+ gtk_render_frame(style, cr, x, y, w, h);
+
+ if (pre_3_6) {
+ gtk_style_context_restore(style);
+ }
+ ReleaseStyleContext(style);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_menu_arrow_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_MENUITEM,
+ direction, state_flags);
+ gtk_render_arrow(style, cr,
+ (direction == GTK_TEXT_DIR_LTR) ? ARROW_RIGHT : ARROW_LEFT,
+ rect->x, rect->y, rect->width);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+// See gtk_real_check_menu_item_draw_indicator() for reference.
+static gint
+moz_gtk_check_menu_item_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ gboolean checked, gboolean isradio,
+ GtkTextDirection direction)
+{
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style;
+ GtkBorder padding;
+ gint indicator_size, horizontal_padding;
+ gint x, y;
+
+ moz_gtk_menu_item_paint(MOZ_GTK_MENUITEM, cr, rect, state, direction);
+
+ if (checked) {
+ state_flags = static_cast<GtkStateFlags>(state_flags|checkbox_check_state);
+ }
+
+ style = ClaimStyleContext(isradio ? MOZ_GTK_RADIOMENUITEM_CONTAINER :
+ MOZ_GTK_CHECKMENUITEM_CONTAINER,
+ direction);
+ gtk_style_context_get_style(style,
+ "indicator-size", &indicator_size,
+ "horizontal-padding", &horizontal_padding,
+ NULL);
+ ReleaseStyleContext(style);
+
+ style = ClaimStyleContext(isradio ? MOZ_GTK_RADIOMENUITEM :
+ MOZ_GTK_CHECKMENUITEM,
+ direction, state_flags);
+ gtk_style_context_get_padding(style, state_flags, &padding);
+ gint offset = padding.left + 2;
+
+ if (direction == GTK_TEXT_DIR_RTL) {
+ x = rect->width - indicator_size - offset - horizontal_padding;
+ }
+ else {
+ x = rect->x + offset + horizontal_padding;
+ }
+ y = rect->y + (rect->height - indicator_size) / 2;
+
+ if (gtk_check_version(3, 20, 0) == nullptr) {
+ gtk_render_background(style, cr, x, y, indicator_size, indicator_size);
+ gtk_render_frame(style, cr, x, y, indicator_size, indicator_size);
+ }
+
+ if (isradio) {
+ gtk_render_option(style, cr, x, y, indicator_size, indicator_size);
+ } else {
+ gtk_render_check(style, cr, x, y, indicator_size, indicator_size);
+ }
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_info_bar_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state)
+{
+ GtkStyleContext *style =
+ ClaimStyleContext(MOZ_GTK_INFO_BAR, GTK_TEXT_DIR_LTR,
+ GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width,
+ rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static void
+moz_gtk_add_style_margin(GtkStyleContext* style,
+ gint* left, gint* top, gint* right, gint* bottom)
+{
+ GtkBorder margin;
+
+ gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin);
+
+ *left += margin.left;
+ *right += margin.right;
+ *top += margin.top;
+ *bottom += margin.bottom;
+}
+
+static void
+moz_gtk_add_style_border(GtkStyleContext* style,
+ gint* left, gint* top, gint* right, gint* bottom)
+{
+ GtkBorder border;
+
+ gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border);
+
+ *left += border.left;
+ *right += border.right;
+ *top += border.top;
+ *bottom += border.bottom;
+}
+
+static void
+moz_gtk_add_style_padding(GtkStyleContext* style,
+ gint* left, gint* top, gint* right, gint* bottom)
+{
+ GtkBorder padding;
+
+ gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding);
+
+ *left += padding.left;
+ *right += padding.right;
+ *top += padding.top;
+ *bottom += padding.bottom;
+}
+
+static void moz_gtk_add_margin_border_padding(GtkStyleContext *style,
+ gint* left, gint* top,
+ gint* right, gint* bottom)
+{
+ moz_gtk_add_style_margin(style, left, top, right, bottom);
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+}
+
+gint
+moz_gtk_get_widget_border(WidgetNodeType widget, gint* left, gint* top,
+ gint* right, gint* bottom, GtkTextDirection direction,
+ gboolean inhtml)
+{
+ GtkWidget* w;
+ GtkStyleContext* style;
+ *left = *top = *right = *bottom = 0;
+
+ switch (widget) {
+ case MOZ_GTK_BUTTON:
+ case MOZ_GTK_TOOLBAR_BUTTON:
+ {
+ style = ClaimStyleContext(MOZ_GTK_BUTTON);
+
+ *left = *top = *right = *bottom =
+ gtk_container_get_border_width(GTK_CONTAINER(GetWidget(MOZ_GTK_BUTTON)));
+
+ if (widget == MOZ_GTK_TOOLBAR_BUTTON) {
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, "image-button");
+ }
+
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+
+ if (widget == MOZ_GTK_TOOLBAR_BUTTON)
+ gtk_style_context_restore(style);
+
+ // XXX: Subtract 1 pixel from the border to account for the added
+ // -moz-focus-inner border (Bug 1228281).
+ *left -= 1; *top -= 1; *right -= 1; *bottom -= 1;
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_ENTRY:
+ {
+ style = ClaimStyleContext(MOZ_GTK_ENTRY);
+
+ // XXX: Subtract 1 pixel from the padding to account for the default
+ // padding in forms.css. See bug 1187385.
+ *left = *top = *right = *bottom = -1;
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_TEXT_VIEW:
+ case MOZ_GTK_TREEVIEW:
+ {
+ style = ClaimStyleContext(MOZ_GTK_SCROLLED_WINDOW);
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_TREE_HEADER_CELL:
+ {
+ /* A Tree Header in GTK is just a different styled button
+ * It must be placed in a TreeView for getting the correct style
+ * assigned.
+ * That is why the following code is the same as for MOZ_GTK_BUTTON.
+ * */
+ *left = *top = *right = *bottom =
+ gtk_container_get_border_width(GTK_CONTAINER(
+ GetWidget(MOZ_GTK_TREE_HEADER_CELL)));
+
+ style = ClaimStyleContext(MOZ_GTK_TREE_HEADER_CELL);
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_TREE_HEADER_SORTARROW:
+ w = GetWidget(MOZ_GTK_TREE_HEADER_SORTARROW);
+ break;
+ case MOZ_GTK_DROPDOWN_ENTRY:
+ w = GetWidget(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA);
+ break;
+ case MOZ_GTK_DROPDOWN_ARROW:
+ w = GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON);
+ break;
+ case MOZ_GTK_DROPDOWN:
+ {
+ /* We need to account for the arrow on the dropdown, so text
+ * doesn't come too close to the arrow, or in some cases spill
+ * into the arrow. */
+ gboolean wide_separators;
+ gint separator_width;
+ GtkRequisition arrow_req;
+ GtkBorder border;
+
+ *left = *top = *right = *bottom =
+ gtk_container_get_border_width(GTK_CONTAINER(
+ GetWidget(MOZ_GTK_COMBOBOX_BUTTON)));
+ style = ClaimStyleContext(MOZ_GTK_COMBOBOX_BUTTON);
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+ ReleaseStyleContext(style);
+
+ /* If there is no separator, don't try to count its width. */
+ separator_width = 0;
+ GtkWidget* comboBoxSeparator = GetWidget(MOZ_GTK_COMBOBOX_SEPARATOR);
+ if (comboBoxSeparator) {
+ style = gtk_widget_get_style_context(comboBoxSeparator);
+ gtk_style_context_get_style(style,
+ "wide-separators", &wide_separators,
+ "separator-width", &separator_width,
+ NULL);
+
+ if (!wide_separators) {
+ gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL,
+ &border);
+ separator_width = border.left;
+ }
+ }
+
+ gtk_widget_get_preferred_size(GetWidget(MOZ_GTK_COMBOBOX_ARROW),
+ NULL, &arrow_req);
+
+ if (direction == GTK_TEXT_DIR_RTL)
+ *left += separator_width + arrow_req.width;
+ else
+ *right += separator_width + arrow_req.width;
+
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_TABPANELS:
+ w = GetWidget(MOZ_GTK_TABPANELS);
+ break;
+ case MOZ_GTK_PROGRESSBAR:
+ w = GetWidget(MOZ_GTK_PROGRESSBAR);
+ break;
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ case MOZ_GTK_SPINBUTTON_UP:
+ case MOZ_GTK_SPINBUTTON_DOWN:
+ w = GetWidget(MOZ_GTK_SPINBUTTON);
+ break;
+ case MOZ_GTK_SCALE_HORIZONTAL:
+ case MOZ_GTK_SCALE_VERTICAL:
+ w = GetWidget(widget);
+ break;
+ case MOZ_GTK_FRAME:
+ w = GetWidget(MOZ_GTK_FRAME);
+ break;
+ case MOZ_GTK_CHECKBUTTON_CONTAINER:
+ case MOZ_GTK_RADIOBUTTON_CONTAINER:
+ {
+ w = GetWidget(widget);
+ style = gtk_widget_get_style_context(w);
+
+ *left = *top = *right = *bottom = gtk_container_get_border_width(GTK_CONTAINER(w));
+ moz_gtk_add_style_border(style,
+ left, top, right, bottom);
+ moz_gtk_add_style_padding(style,
+ left, top, right, bottom);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_MENUPOPUP:
+ w = GetWidget(MOZ_GTK_MENUPOPUP);
+ break;
+ case MOZ_GTK_MENUBARITEM:
+ case MOZ_GTK_MENUITEM:
+ case MOZ_GTK_CHECKMENUITEM:
+ case MOZ_GTK_RADIOMENUITEM:
+ {
+ // Bug 1274143 for MOZ_GTK_MENUBARITEM
+ WidgetNodeType type =
+ widget == MOZ_GTK_MENUBARITEM || widget == MOZ_GTK_MENUITEM ?
+ MOZ_GTK_MENUITEM : MOZ_GTK_CHECKMENUITEM_CONTAINER;
+ style = ClaimStyleContext(type);
+
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_INFO_BAR:
+ w = GetWidget(MOZ_GTK_INFO_BAR);
+ break;
+ case MOZ_GTK_TOOLTIP:
+ {
+ style = ClaimStyleContext(MOZ_GTK_TOOLTIP);
+ // In GTK 3 there are 6 pixels of additional margin around the box.
+ // See details there:
+ // https://github.com/GNOME/gtk/blob/5ea69a136bd7e4970b3a800390e20314665aaed2/gtk/ui/gtktooltipwindow.ui#L11
+ *left = *right = *top = *bottom = 6;
+
+ // We also need to add margin/padding/borders from Tooltip content.
+ // Tooltip contains horizontal box, where icon and label is put.
+ // We ignore icon as long as we don't have support for it.
+ GtkStyleContext* boxStyle =
+ CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0),
+ style);
+ moz_gtk_add_margin_border_padding(boxStyle,
+ left, top, right, bottom);
+
+ GtkStyleContext* labelStyle =
+ CreateStyleForWidget(gtk_label_new(nullptr), boxStyle);
+ moz_gtk_add_margin_border_padding(labelStyle,
+ left, top, right, bottom);
+
+ g_object_unref(labelStyle);
+ g_object_unref(boxStyle);
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_SCROLLBAR_VERTICAL:
+ case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
+ {
+ if (gtk_check_version(3,20,0) == nullptr) {
+ style = ClaimStyleContext(widget);
+ moz_gtk_add_margin_border_padding(style, left, top, right, bottom);
+ ReleaseStyleContext(style);
+ if (widget == MOZ_GTK_SCROLLBAR_VERTICAL) {
+ style = ClaimStyleContext(MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL);
+ moz_gtk_add_margin_border_padding(style, left, top, right, bottom);
+ ReleaseStyleContext(style);
+ }
+ } else {
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+ /* Top and bottom border for whole vertical scrollbar, top and bottom
+ * border for horizontal track - to correctly position thumb element */
+ *top = *bottom = metrics.trough_border;
+ }
+ return MOZ_GTK_SUCCESS;
+ }
+ break;
+
+ case MOZ_GTK_SCROLLBAR_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
+ {
+ if (gtk_check_version(3,20,0) == nullptr) {
+ style = ClaimStyleContext(widget);
+ moz_gtk_add_margin_border_padding(style, left, top, right, bottom);
+ ReleaseStyleContext(style);
+ if (widget == MOZ_GTK_SCROLLBAR_HORIZONTAL) {
+ style = ClaimStyleContext(MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL);
+ moz_gtk_add_margin_border_padding(style, left, top, right, bottom);
+ ReleaseStyleContext(style);
+ }
+ } else {
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+ *left = *right = metrics.trough_border;
+ }
+ return MOZ_GTK_SUCCESS;
+ }
+ break;
+
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ {
+ if (gtk_check_version(3,20,0) == nullptr) {
+ style = ClaimStyleContext(widget);
+ moz_gtk_add_margin_border_padding(style, left, top, right, bottom);
+ ReleaseStyleContext(style);
+ }
+ return MOZ_GTK_SUCCESS;
+ }
+ break;
+ /* These widgets have no borders, since they are not containers. */
+ case MOZ_GTK_CHECKBUTTON_LABEL:
+ case MOZ_GTK_RADIOBUTTON_LABEL:
+ case MOZ_GTK_SPLITTER_HORIZONTAL:
+ case MOZ_GTK_SPLITTER_VERTICAL:
+ case MOZ_GTK_CHECKBUTTON:
+ case MOZ_GTK_RADIOBUTTON:
+ case MOZ_GTK_SCROLLBAR_BUTTON:
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ case MOZ_GTK_GRIPPER:
+ case MOZ_GTK_PROGRESS_CHUNK:
+ case MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE:
+ case MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE:
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ case MOZ_GTK_TOOLBAR_SEPARATOR:
+ case MOZ_GTK_MENUSEPARATOR:
+ /* These widgets have no borders.*/
+ case MOZ_GTK_SPINBUTTON:
+ case MOZ_GTK_WINDOW:
+ case MOZ_GTK_RESIZER:
+ case MOZ_GTK_MENUARROW:
+ case MOZ_GTK_TOOLBARBUTTON_ARROW:
+ case MOZ_GTK_TOOLBAR:
+ case MOZ_GTK_MENUBAR:
+ case MOZ_GTK_TAB_SCROLLARROW:
+ return MOZ_GTK_SUCCESS;
+ default:
+ g_warning("Unsupported widget type: %d", widget);
+ return MOZ_GTK_UNKNOWN_WIDGET;
+ }
+ /* TODO - we're still missing some widget implementations */
+ if (w) {
+ moz_gtk_add_style_border(gtk_widget_get_style_context(w),
+ left, top, right, bottom);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_tab_border(gint* left, gint* top, gint* right, gint* bottom,
+ GtkTextDirection direction, GtkTabFlags flags,
+ WidgetNodeType widget)
+{
+ GtkStyleContext* style = ClaimStyleContext(widget, direction,
+ GetStateFlagsFromGtkTabFlags(flags));
+
+ *left = *top = *right = *bottom = 0;
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+
+ // Gtk >= 3.20 does not use those styles
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ int tab_curvature;
+
+ gtk_style_context_get_style(style, "tab-curvature", &tab_curvature, NULL);
+ *left += tab_curvature;
+ *right += tab_curvature;
+
+ if (flags & MOZ_GTK_TAB_FIRST) {
+ int initial_gap = 0;
+ gtk_style_context_get_style(style, "initial-gap", &initial_gap, NULL);
+ if (direction == GTK_TEXT_DIR_RTL)
+ *right += initial_gap;
+ else
+ *left += initial_gap;
+ }
+ } else {
+ GtkBorder margin;
+
+ gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin);
+ *left += margin.left;
+ *right += margin.right;
+
+ if (flags & MOZ_GTK_TAB_FIRST) {
+ ReleaseStyleContext(style);
+ style = ClaimStyleContext(MOZ_GTK_NOTEBOOK_HEADER, direction);
+ gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin);
+ *left += margin.left;
+ *right += margin.right;
+ }
+ }
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_combo_box_entry_button_size(gint* width, gint* height)
+{
+ /*
+ * We get the requisition of the drop down button, which includes
+ * all padding, border and focus line widths the button uses,
+ * as well as the minimum arrow size and its padding
+ * */
+ GtkRequisition requisition;
+
+ gtk_widget_get_preferred_size(GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON),
+ NULL, &requisition);
+ *width = requisition.width;
+ *height = requisition.height;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_tab_scroll_arrow_size(gint* width, gint* height)
+{
+ gint arrow_size;
+
+ GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_TABPANELS);
+ gtk_style_context_get_style(style,
+ "scroll-arrow-hlength", &arrow_size,
+ NULL);
+ ReleaseStyleContext(style);
+
+ *height = *width = arrow_size;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+void
+moz_gtk_get_arrow_size(WidgetNodeType widgetType, gint* width, gint* height)
+{
+ GtkWidget* widget;
+ switch (widgetType) {
+ case MOZ_GTK_DROPDOWN:
+ widget = GetWidget(MOZ_GTK_COMBOBOX_ARROW);
+ break;
+ default:
+ widget = GetWidget(MOZ_GTK_BUTTON_ARROW);
+ break;
+ }
+
+ GtkRequisition requisition;
+ gtk_widget_get_preferred_size(widget, NULL, &requisition);
+ *width = requisition.width;
+ *height = requisition.height;
+}
+
+gint
+moz_gtk_get_toolbar_separator_width(gint* size)
+{
+ gboolean wide_separators;
+ gint separator_width;
+ GtkBorder border;
+
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_TOOLBAR);
+ gtk_style_context_get_style(style,
+ "space-size", size,
+ "wide-separators", &wide_separators,
+ "separator-width", &separator_width,
+ NULL);
+ /* Just in case... */
+ gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border);
+ *size = MAX(*size, (wide_separators ? separator_width : border.left));
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_expander_size(gint* size)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_EXPANDER);
+ gtk_style_context_get_style(style,
+ "expander-size", size,
+ NULL);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_treeview_expander_size(gint* size)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_TREEVIEW);
+ gtk_style_context_get_style(style, "expander-size", size, NULL);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+// See gtk_menu_item_draw() for reference.
+gint
+moz_gtk_get_menu_separator_height(gint *size)
+{
+ gboolean wide_separators;
+ gint separator_height;
+ GtkBorder padding;
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_MENUSEPARATOR);
+ gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding);
+
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_SEPARATOR);
+
+ gtk_style_context_get_style(style,
+ "wide-separators", &wide_separators,
+ "separator-height", &separator_height,
+ NULL);
+
+ gtk_style_context_restore(style);
+ ReleaseStyleContext(style);
+
+ *size = padding.top + padding.bottom;
+ *size += (wide_separators) ? separator_height : 1;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+void
+moz_gtk_get_entry_min_height(gint* height)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_ENTRY);
+ if (!gtk_check_version(3, 20, 0)) {
+ gtk_style_context_get(style, gtk_style_context_get_state(style),
+ "min-height", height,
+ nullptr);
+ } else {
+ *height = 0;
+ }
+
+ 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);
+
+ *height += (border.top + border.bottom + padding.top + padding.bottom);
+ ReleaseStyleContext(style);
+}
+
+void
+moz_gtk_get_scale_metrics(GtkOrientation orient, gint* scale_width,
+ gint* scale_height)
+{
+ WidgetNodeType widget = (orient == GTK_ORIENTATION_HORIZONTAL) ?
+ MOZ_GTK_SCALE_HORIZONTAL :
+ MOZ_GTK_SCALE_VERTICAL;
+
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ gint thumb_length, thumb_height, trough_border;
+ moz_gtk_get_scalethumb_metrics(orient, &thumb_length, &thumb_height);
+
+ GtkStyleContext* style = ClaimStyleContext(widget);
+ gtk_style_context_get_style(style, "trough-border", &trough_border, NULL);
+
+ if (orient == GTK_ORIENTATION_HORIZONTAL) {
+ *scale_width = thumb_length + trough_border * 2;
+ *scale_height = thumb_height + trough_border * 2;
+ } else {
+ *scale_width = thumb_height + trough_border * 2;
+ *scale_height = thumb_length + trough_border * 2;
+ }
+ ReleaseStyleContext(style);
+ } else {
+ GtkStyleContext* style = ClaimStyleContext(widget);
+ gtk_style_context_get(style, gtk_style_context_get_state(style),
+ "min-width", scale_width,
+ "min-height", scale_height,
+ nullptr);
+ ReleaseStyleContext(style);
+ }
+}
+
+gint
+moz_gtk_get_scalethumb_metrics(GtkOrientation orient, gint* thumb_length, gint* thumb_height)
+{
+
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ WidgetNodeType widget = (orient == GTK_ORIENTATION_HORIZONTAL) ?
+ MOZ_GTK_SCALE_HORIZONTAL:
+ MOZ_GTK_SCALE_VERTICAL;
+ GtkStyleContext* style = ClaimStyleContext(widget);
+ gtk_style_context_get_style(style,
+ "slider_length", thumb_length,
+ "slider_width", thumb_height,
+ NULL);
+ ReleaseStyleContext(style);
+ } else {
+ WidgetNodeType widget = (orient == GTK_ORIENTATION_HORIZONTAL) ?
+ MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ MOZ_GTK_SCALE_THUMB_VERTICAL;
+ GtkStyleContext* style = ClaimStyleContext(widget);
+ gtk_style_context_get(style, gtk_style_context_get_state(style),
+ "min-width", thumb_length,
+ "min-height", thumb_height,
+ nullptr);
+ ReleaseStyleContext(style);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_scrollbar_metrics(MozGtkScrollbarMetrics *metrics)
+{
+ // For Gtk >= 3.20 scrollbar metrics are ignored
+ MOZ_ASSERT(gtk_check_version(3, 20, 0) != nullptr);
+
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_SCROLLBAR_VERTICAL);
+ gtk_style_context_get_style(style,
+ "slider_width", &metrics->slider_width,
+ "trough_border", &metrics->trough_border,
+ "stepper_size", &metrics->stepper_size,
+ "stepper_spacing", &metrics->stepper_spacing,
+ "min-slider-length", &metrics->min_slider_size,
+ nullptr);
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+/* cairo_t *cr argument has to be a system-cairo. */
+gint
+moz_gtk_widget_paint(WidgetNodeType widget, cairo_t *cr,
+ GdkRectangle* rect,
+ GtkWidgetState* state, gint flags,
+ GtkTextDirection direction)
+{
+ /* A workaround for https://bugzilla.gnome.org/show_bug.cgi?id=694086
+ */
+ cairo_new_path(cr);
+
+ switch (widget) {
+ case MOZ_GTK_BUTTON:
+ case MOZ_GTK_TOOLBAR_BUTTON:
+ if (state->depressed) {
+ return moz_gtk_button_paint(cr, rect, state,
+ (GtkReliefStyle) flags,
+ GetWidget(MOZ_GTK_TOGGLE_BUTTON),
+ direction);
+ }
+ return moz_gtk_button_paint(cr, rect, state,
+ (GtkReliefStyle) flags,
+ GetWidget(MOZ_GTK_BUTTON),
+ direction);
+ break;
+ case MOZ_GTK_CHECKBUTTON:
+ case MOZ_GTK_RADIOBUTTON:
+ return moz_gtk_toggle_paint(cr, rect, state,
+ !!(flags & MOZ_GTK_WIDGET_CHECKED),
+ !!(flags & MOZ_GTK_WIDGET_INCONSISTENT),
+ (widget == MOZ_GTK_RADIOBUTTON),
+ direction);
+ break;
+ case MOZ_GTK_SCROLLBAR_BUTTON:
+ return moz_gtk_scrollbar_button_paint(cr, rect, state,
+ (GtkScrollbarButtonFlags) flags,
+ direction);
+ break;
+ case MOZ_GTK_SCROLLBAR_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_VERTICAL:
+ if (flags & MOZ_GTK_TRACK_OPAQUE) {
+ GtkStyleContext* style =
+ ClaimStyleContext(MOZ_GTK_WINDOW, direction);
+ gtk_render_background(style, cr,
+ rect->x, rect->y, rect->width, rect->height);
+ ReleaseStyleContext(style);
+ }
+ if (gtk_check_version(3,20,0) == nullptr) {
+ return moz_gtk_scrollbar_paint(widget, cr, rect, state, direction);
+ } else {
+ WidgetNodeType trough_widget = (widget == MOZ_GTK_SCROLLBAR_HORIZONTAL) ?
+ MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL : MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL;
+ return moz_gtk_scrollbar_trough_paint(trough_widget, cr, rect,
+ state, direction);
+ }
+ break;
+ case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
+ if (gtk_check_version(3,20,0) == nullptr) {
+ return moz_gtk_scrollbar_trough_paint(widget, cr, rect,
+ state, direction);
+ }
+ break;
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ return moz_gtk_scrollbar_thumb_paint(widget, cr, rect,
+ state, direction);
+ break;
+ case MOZ_GTK_SCALE_HORIZONTAL:
+ case MOZ_GTK_SCALE_VERTICAL:
+ return moz_gtk_scale_paint(cr, rect, state,
+ (GtkOrientation) flags, direction);
+ break;
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ return moz_gtk_scale_thumb_paint(cr, rect, state,
+ (GtkOrientation) flags, direction);
+ break;
+ case MOZ_GTK_SPINBUTTON:
+ return moz_gtk_spin_paint(cr, rect, direction);
+ break;
+ case MOZ_GTK_SPINBUTTON_UP:
+ case MOZ_GTK_SPINBUTTON_DOWN:
+ return moz_gtk_spin_updown_paint(cr, rect,
+ (widget == MOZ_GTK_SPINBUTTON_DOWN),
+ state, direction);
+ break;
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ {
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_SPINBUTTON_ENTRY,
+ direction, GetStateFlagsFromGtkWidgetState(state));
+ gint ret = moz_gtk_entry_paint(cr, rect, state, style);
+ ReleaseStyleContext(style);
+ return ret;
+ }
+ break;
+ case MOZ_GTK_GRIPPER:
+ return moz_gtk_gripper_paint(cr, rect, state,
+ direction);
+ break;
+ case MOZ_GTK_TREEVIEW:
+ return moz_gtk_treeview_paint(cr, rect, state,
+ direction);
+ break;
+ case MOZ_GTK_TREE_HEADER_CELL:
+ return moz_gtk_tree_header_cell_paint(cr, rect, state,
+ flags, direction);
+ break;
+ case MOZ_GTK_TREE_HEADER_SORTARROW:
+ return moz_gtk_tree_header_sort_arrow_paint(cr, rect,
+ state,
+ (GtkArrowType) flags,
+ direction);
+ break;
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ return moz_gtk_treeview_expander_paint(cr, rect, state,
+ (GtkExpanderStyle) flags, direction);
+ break;
+ case MOZ_GTK_ENTRY:
+ {
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_ENTRY,
+ direction, GetStateFlagsFromGtkWidgetState(state));
+ gint ret = moz_gtk_entry_paint(cr, rect, state, style);
+ ReleaseStyleContext(style);
+ return ret;
+ }
+ case MOZ_GTK_TEXT_VIEW:
+ return moz_gtk_text_view_paint(cr, rect, state, direction);
+ break;
+ case MOZ_GTK_DROPDOWN:
+ return moz_gtk_combo_box_paint(cr, rect, state, direction);
+ break;
+ case MOZ_GTK_DROPDOWN_ARROW:
+ return moz_gtk_combo_box_entry_button_paint(cr, rect,
+ state, flags, direction);
+ break;
+ case MOZ_GTK_DROPDOWN_ENTRY:
+ {
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA,
+ direction, GetStateFlagsFromGtkWidgetState(state));
+ gint ret = moz_gtk_entry_paint(cr, rect, state, style);
+ ReleaseStyleContext(style);
+ return ret;
+ }
+ break;
+ case MOZ_GTK_CHECKBUTTON_CONTAINER:
+ case MOZ_GTK_RADIOBUTTON_CONTAINER:
+ return moz_gtk_container_paint(cr, rect, state, widget, direction);
+ break;
+ case MOZ_GTK_CHECKBUTTON_LABEL:
+ case MOZ_GTK_RADIOBUTTON_LABEL:
+ return moz_gtk_toggle_label_paint(cr, rect, state,
+ (widget == MOZ_GTK_RADIOBUTTON_LABEL),
+ direction);
+ break;
+ case MOZ_GTK_TOOLBAR:
+ return moz_gtk_toolbar_paint(cr, rect, direction);
+ break;
+ case MOZ_GTK_TOOLBAR_SEPARATOR:
+ return moz_gtk_toolbar_separator_paint(cr, rect,
+ direction);
+ break;
+ case MOZ_GTK_TOOLTIP:
+ return moz_gtk_tooltip_paint(cr, rect, direction);
+ break;
+ case MOZ_GTK_FRAME:
+ return moz_gtk_frame_paint(cr, rect, direction);
+ break;
+ case MOZ_GTK_RESIZER:
+ return moz_gtk_resizer_paint(cr, rect, state,
+ direction);
+ break;
+ case MOZ_GTK_PROGRESSBAR:
+ return moz_gtk_progressbar_paint(cr, rect, direction);
+ break;
+ case MOZ_GTK_PROGRESS_CHUNK:
+ case MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE:
+ case MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE:
+ return moz_gtk_progress_chunk_paint(cr, rect,
+ direction, widget);
+ break;
+ case MOZ_GTK_TAB_TOP:
+ case MOZ_GTK_TAB_BOTTOM:
+ return moz_gtk_tab_paint(cr, rect, state,
+ (GtkTabFlags) flags, direction, widget);
+ break;
+ case MOZ_GTK_TABPANELS:
+ return moz_gtk_tabpanels_paint(cr, rect, direction);
+ break;
+ case MOZ_GTK_TAB_SCROLLARROW:
+ return moz_gtk_tab_scroll_arrow_paint(cr, rect, state,
+ (GtkArrowType) flags, direction);
+ break;
+ case MOZ_GTK_MENUBAR:
+ return moz_gtk_menu_bar_paint(cr, rect, direction);
+ break;
+ case MOZ_GTK_MENUPOPUP:
+ return moz_gtk_menu_popup_paint(cr, rect, direction);
+ break;
+ case MOZ_GTK_MENUSEPARATOR:
+ return moz_gtk_menu_separator_paint(cr, rect,
+ direction);
+ break;
+ case MOZ_GTK_MENUBARITEM:
+ case MOZ_GTK_MENUITEM:
+ return moz_gtk_menu_item_paint(widget, cr, rect, state, direction);
+ break;
+ case MOZ_GTK_MENUARROW:
+ return moz_gtk_menu_arrow_paint(cr, rect, state,
+ direction);
+ break;
+ case MOZ_GTK_TOOLBARBUTTON_ARROW:
+ return moz_gtk_arrow_paint(cr, rect, state,
+ (GtkArrowType) flags, direction);
+ break;
+ case MOZ_GTK_CHECKMENUITEM:
+ case MOZ_GTK_RADIOMENUITEM:
+ return moz_gtk_check_menu_item_paint(cr, rect, state,
+ (gboolean) flags,
+ (widget == MOZ_GTK_RADIOMENUITEM),
+ direction);
+ break;
+ case MOZ_GTK_SPLITTER_HORIZONTAL:
+ return moz_gtk_vpaned_paint(cr, rect, state);
+ break;
+ case MOZ_GTK_SPLITTER_VERTICAL:
+ return moz_gtk_hpaned_paint(cr, rect, state);
+ break;
+ case MOZ_GTK_WINDOW:
+ return moz_gtk_window_paint(cr, rect, direction);
+ break;
+ case MOZ_GTK_INFO_BAR:
+ return moz_gtk_info_bar_paint(cr, rect, state);
+ break;
+ default:
+ g_warning("Unknown widget type: %d", widget);
+ }
+
+ return MOZ_GTK_UNKNOWN_WIDGET;
+}
+
+GtkWidget* moz_gtk_get_scrollbar_widget(void)
+{
+ return GetWidget(MOZ_GTK_SCROLLBAR_HORIZONTAL);
+}
+
+gboolean moz_gtk_has_scrollbar_buttons(void)
+{
+ gboolean backward, forward, secondary_backward, secondary_forward;
+ MOZ_ASSERT(is_initialized, "Forgot to call moz_gtk_init()");
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_SCROLLBAR_VERTICAL);
+ gtk_style_context_get_style(style,
+ "has-backward-stepper", &backward,
+ "has-forward-stepper", &forward,
+ "has-secondary-backward-stepper", &secondary_backward,
+ "has-secondary-forward-stepper", &secondary_forward,
+ NULL);
+ ReleaseStyleContext(style);
+
+ return backward | forward | secondary_forward | secondary_forward;
+}
+
+gint
+moz_gtk_shutdown()
+{
+ /* This will destroy all of our widgets */
+ ResetWidgetCache();
+
+ is_initialized = FALSE;
+
+ return MOZ_GTK_SUCCESS;
+}
diff --git a/widget/gtk/gtkdrawing.h b/widget/gtk/gtkdrawing.h
new file mode 100644
index 000000000..9bbfdefe9
--- /dev/null
+++ b/widget/gtk/gtkdrawing.h
@@ -0,0 +1,542 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+/**
+ * gtkdrawing.h: GTK widget rendering utilities
+ *
+ * gtkdrawing provides an API for rendering GTK widgets in the
+ * current theme to a pixmap or window, without requiring an actual
+ * widget instantiation, similar to the Macintosh Appearance Manager
+ * or Windows XP's DrawThemeBackground() API.
+ */
+
+#ifndef _GTK_DRAWING_H_
+#define _GTK_DRAWING_H_
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#if (MOZ_WIDGET_GTK == 2)
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+#endif
+
+/*** type definitions ***/
+typedef struct {
+ guint8 active;
+ guint8 focused;
+ guint8 inHover;
+ guint8 disabled;
+ guint8 isDefault;
+ guint8 canDefault;
+ /* The depressed state is for buttons which remain active for a longer period:
+ * activated toggle buttons or buttons showing a popup menu. */
+ guint8 depressed;
+ gint32 curpos; /* curpos and maxpos are used for scrollbars */
+ gint32 maxpos;
+} GtkWidgetState;
+
+typedef struct {
+ gint slider_width;
+ gint trough_border;
+ gint stepper_size;
+ gint stepper_spacing;
+ gint min_slider_size;
+} MozGtkScrollbarMetrics;
+
+typedef enum {
+ MOZ_GTK_STEPPER_DOWN = 1 << 0,
+ MOZ_GTK_STEPPER_BOTTOM = 1 << 1,
+ MOZ_GTK_STEPPER_VERTICAL = 1 << 2
+} GtkScrollbarButtonFlags;
+
+typedef enum {
+ MOZ_GTK_TRACK_OPAQUE = 1 << 0
+} GtkScrollbarTrackFlags;
+
+/** flags for tab state **/
+typedef enum {
+ /* first eight bits are used to pass a margin */
+ MOZ_GTK_TAB_MARGIN_MASK = 0xFF,
+ /* the first tab in the group */
+ MOZ_GTK_TAB_FIRST = 1 << 9,
+ /* the selected tab */
+ MOZ_GTK_TAB_SELECTED = 1 << 10
+} GtkTabFlags;
+
+/* function type for moz_gtk_enable_style_props */
+typedef gint (*style_prop_t)(GtkStyle*, const gchar*, gint);
+
+/*** result/error codes ***/
+#define MOZ_GTK_SUCCESS 0
+#define MOZ_GTK_UNKNOWN_WIDGET -1
+#define MOZ_GTK_UNSAFE_THEME -2
+
+/*** checkbox/radio flags ***/
+#define MOZ_GTK_WIDGET_CHECKED 1
+#define MOZ_GTK_WIDGET_INCONSISTENT (1 << 1)
+
+/*** widget type constants ***/
+typedef enum {
+ /* Paints a GtkButton. flags is a GtkReliefStyle. */
+ MOZ_GTK_BUTTON,
+ /* Paints a button with image and no text */
+ MOZ_GTK_TOOLBAR_BUTTON,
+ /* Paints a toggle button */
+ MOZ_GTK_TOGGLE_BUTTON,
+ /* Paints a button arrow */
+ MOZ_GTK_BUTTON_ARROW,
+
+ /* Paints the container part of a GtkCheckButton. */
+ MOZ_GTK_CHECKBUTTON_CONTAINER,
+ /* Paints a GtkCheckButton. flags is a boolean, 1=checked, 0=not checked. */
+ MOZ_GTK_CHECKBUTTON,
+ /* Paints the label of a GtkCheckButton (focus outline) */
+ MOZ_GTK_CHECKBUTTON_LABEL,
+
+ /* Paints the container part of a GtkRadioButton. */
+ MOZ_GTK_RADIOBUTTON_CONTAINER,
+ /* Paints a GtkRadioButton. flags is a boolean, 1=checked, 0=not checked. */
+ MOZ_GTK_RADIOBUTTON,
+ /* Paints the label of a GtkRadioButton (focus outline) */
+ MOZ_GTK_RADIOBUTTON_LABEL,
+ /**
+ * Paints the button of a GtkScrollbar. flags is a GtkArrowType giving
+ * the arrow direction.
+ */
+ MOZ_GTK_SCROLLBAR_BUTTON,
+
+ /* Horizontal GtkScrollbar counterparts */
+ MOZ_GTK_SCROLLBAR_HORIZONTAL,
+ MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL,
+ /* Paints the trough (track) of a GtkScrollbar. */
+ MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL,
+ /* Paints the slider (thumb) of a GtkScrollbar. */
+ MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL,
+
+ /* Vertical GtkScrollbar counterparts */
+ MOZ_GTK_SCROLLBAR_VERTICAL,
+ MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL,
+ MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL,
+ MOZ_GTK_SCROLLBAR_THUMB_VERTICAL,
+
+ /* Paints a GtkScale. */
+ MOZ_GTK_SCALE_HORIZONTAL,
+ MOZ_GTK_SCALE_VERTICAL,
+ /* Paints a GtkScale trough. */
+ MOZ_GTK_SCALE_CONTENTS_HORIZONTAL,
+ MOZ_GTK_SCALE_CONTENTS_VERTICAL,
+ MOZ_GTK_SCALE_TROUGH_HORIZONTAL,
+ MOZ_GTK_SCALE_TROUGH_VERTICAL,
+ /* Paints a GtkScale thumb. */
+ MOZ_GTK_SCALE_THUMB_HORIZONTAL,
+ MOZ_GTK_SCALE_THUMB_VERTICAL,
+ /* Paints a GtkSpinButton */
+ MOZ_GTK_SPINBUTTON,
+ MOZ_GTK_SPINBUTTON_UP,
+ MOZ_GTK_SPINBUTTON_DOWN,
+ MOZ_GTK_SPINBUTTON_ENTRY,
+ /* Paints the gripper of a GtkHandleBox. */
+ MOZ_GTK_GRIPPER,
+ /* Paints a GtkEntry. */
+ MOZ_GTK_ENTRY,
+ /* Paints a GtkExpander. */
+ MOZ_GTK_EXPANDER,
+ /* Paints a GtkTextView. */
+ MOZ_GTK_TEXT_VIEW,
+ /* Paints a GtkOptionMenu. */
+ MOZ_GTK_DROPDOWN,
+ /* Paints a dropdown arrow (a GtkButton containing a down GtkArrow). */
+ MOZ_GTK_DROPDOWN_ARROW,
+ /* Paints an entry in an editable option menu */
+ MOZ_GTK_DROPDOWN_ENTRY,
+
+ /* Paints the background of a GtkHandleBox. */
+ MOZ_GTK_TOOLBAR,
+ /* Paints a toolbar separator */
+ MOZ_GTK_TOOLBAR_SEPARATOR,
+ /* Paints a GtkToolTip */
+ MOZ_GTK_TOOLTIP,
+ /* Paints a GtkFrame (e.g. a status bar panel). */
+ MOZ_GTK_FRAME,
+ /* Paints the border of a GtkFrame */
+ MOZ_GTK_FRAME_BORDER,
+ /* Paints a resize grip for a GtkWindow */
+ MOZ_GTK_RESIZER,
+ /* Paints a GtkProgressBar. */
+ MOZ_GTK_PROGRESSBAR,
+ /* Paints a trough (track) of a GtkProgressBar */
+ MOZ_GTK_PROGRESS_TROUGH,
+ /* Paints a progress chunk of a GtkProgressBar. */
+ MOZ_GTK_PROGRESS_CHUNK,
+ /* Paints a progress chunk of an indeterminated GtkProgressBar. */
+ MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE,
+ /* Paints a progress chunk of a vertical indeterminated GtkProgressBar. */
+ MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE,
+ /* Used as root style of whole GtkNotebook widget */
+ MOZ_GTK_NOTEBOOK,
+ /* Used as root style of active GtkNotebook area which contains tabs and arrows. */
+ MOZ_GTK_NOTEBOOK_HEADER,
+ /* Paints a tab of a GtkNotebook. flags is a GtkTabFlags, defined above. */
+ MOZ_GTK_TAB_TOP,
+ /* Paints a tab of a GtkNotebook. flags is a GtkTabFlags, defined above. */
+ MOZ_GTK_TAB_BOTTOM,
+ /* Paints the background and border of a GtkNotebook. */
+ MOZ_GTK_TABPANELS,
+ /* Paints a GtkArrow for a GtkNotebook. flags is a GtkArrowType. */
+ MOZ_GTK_TAB_SCROLLARROW,
+ /* Paints the expander and border of a GtkTreeView */
+ MOZ_GTK_TREEVIEW,
+ /* Paints the border of a GtkTreeView */
+ MOZ_GTK_TREEVIEW_VIEW,
+ /* Paints treeheader cells */
+ MOZ_GTK_TREE_HEADER_CELL,
+ /* Paints sort arrows in treeheader cells */
+ MOZ_GTK_TREE_HEADER_SORTARROW,
+ /* Paints an expander for a GtkTreeView */
+ MOZ_GTK_TREEVIEW_EXPANDER,
+ /* Paints the background of the menu bar. */
+ MOZ_GTK_MENUBAR,
+ /* Paints the background of menus, context menus. */
+ MOZ_GTK_MENUPOPUP,
+ /* Paints the arrow of menuitems that contain submenus */
+ MOZ_GTK_MENUARROW,
+ /* Paints an arrow in a toolbar button. flags is a GtkArrowType. */
+ MOZ_GTK_TOOLBARBUTTON_ARROW,
+ /* Paints items of menubar. */
+ MOZ_GTK_MENUBARITEM,
+ /* Paints items of popup menus. */
+ MOZ_GTK_MENUITEM,
+ MOZ_GTK_IMAGEMENUITEM,
+ MOZ_GTK_CHECKMENUITEM_CONTAINER,
+ MOZ_GTK_RADIOMENUITEM_CONTAINER,
+ MOZ_GTK_CHECKMENUITEM,
+ MOZ_GTK_RADIOMENUITEM,
+ MOZ_GTK_MENUSEPARATOR,
+ /* GtkVPaned base class */
+ MOZ_GTK_SPLITTER_HORIZONTAL,
+ /* GtkHPaned base class */
+ MOZ_GTK_SPLITTER_VERTICAL,
+ /* Paints a GtkVPaned separator */
+ MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL,
+ /* Paints a GtkHPaned separator */
+ MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL,
+ /* Paints the background of a window, dialog or page. */
+ MOZ_GTK_WINDOW,
+ /* Window container for all widgets */
+ MOZ_GTK_WINDOW_CONTAINER,
+ /* Paints a GtkInfoBar, for notifications. */
+ MOZ_GTK_INFO_BAR,
+ /* Used for widget tree construction. */
+ MOZ_GTK_COMBOBOX,
+ /* Paints a GtkComboBox button widget. */
+ MOZ_GTK_COMBOBOX_BUTTON,
+ /* Paints a GtkComboBox arrow widget. */
+ MOZ_GTK_COMBOBOX_ARROW,
+ /* Paints a GtkComboBox separator widget. */
+ MOZ_GTK_COMBOBOX_SEPARATOR,
+ /* Used for widget tree construction. */
+ MOZ_GTK_COMBOBOX_ENTRY,
+ /* Paints a GtkComboBox entry widget. */
+ MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA,
+ /* Paints a GtkComboBox entry button widget. */
+ MOZ_GTK_COMBOBOX_ENTRY_BUTTON,
+ /* Paints a GtkComboBox entry arrow widget. */
+ MOZ_GTK_COMBOBOX_ENTRY_ARROW,
+ /* Used for scrolled window shell. */
+ MOZ_GTK_SCROLLED_WINDOW,
+
+ MOZ_GTK_WIDGET_NODE_COUNT
+} WidgetNodeType;
+
+/*** General library functions ***/
+/**
+ * Initializes the drawing library. You must call this function
+ * prior to using any other functionality.
+ * returns: MOZ_GTK_SUCCESS if there were no errors
+ * MOZ_GTK_UNSAFE_THEME if the current theme engine is known
+ * to crash with gtkdrawing.
+ */
+gint moz_gtk_init();
+
+/**
+ * Enable GTK+ 1.2.9+ theme enhancements. You must provide a pointer
+ * to the GTK+ 1.2.9+ function "gtk_style_get_prop_experimental".
+ * styleGetProp: pointer to gtk_style_get_prop_experimental
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_enable_style_props(style_prop_t styleGetProp);
+
+/**
+ * Perform cleanup of the drawing library. You should call this function
+ * when your program exits, or you no longer need the library.
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_shutdown();
+
+#if (MOZ_WIDGET_GTK == 2)
+/**
+ * Retrieves the colormap to use for drawables passed to moz_gtk_widget_paint.
+ */
+GdkColormap* moz_gtk_widget_get_colormap();
+#endif
+
+/*** Widget drawing ***/
+#if (MOZ_WIDGET_GTK == 2)
+/**
+ * Paint a widget in the current theme.
+ * widget: a constant giving the widget to paint
+ * drawable: the drawable to paint to;
+ * it's colormap must be moz_gtk_widget_get_colormap().
+ * rect: the bounding rectangle for the widget
+ * cliprect: a clipprect rectangle for this painting operation
+ * state: the state of the widget. ignored for some widgets.
+ * flags: widget-dependant flags; see the WidgetNodeType definition.
+ * direction: the text direction, to draw the widget correctly LTR and RTL.
+ */
+gint
+moz_gtk_widget_paint(WidgetNodeType widget, GdkDrawable* drawable,
+ GdkRectangle* rect, GdkRectangle* cliprect,
+ GtkWidgetState* state, gint flags,
+ GtkTextDirection direction);
+#else
+gint
+moz_gtk_widget_paint(WidgetNodeType widget, cairo_t *cr,
+ GdkRectangle* rect,
+ GtkWidgetState* state, gint flags,
+ GtkTextDirection direction);
+#endif
+
+
+/*** Widget metrics ***/
+/**
+ * Get the border size of a widget
+ * left/right: [OUT] the widget's left/right border
+ * top/bottom: [OUT] the widget's top/bottom border
+ * direction: the text direction for the widget
+ * inhtml: boolean indicating whether this widget will be drawn as a HTML form control,
+ * in order to workaround a size issue (MOZ_GTK_BUTTON only, ignored otherwise)
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_widget_border(WidgetNodeType widget, gint* left, gint* top,
+ gint* right, gint* bottom, GtkTextDirection direction,
+ gboolean inhtml);
+
+/**
+ * Get the border size of a notebook tab
+ * left/right: [OUT] the tab's left/right border
+ * top/bottom: [OUT] the tab's top/bottom border
+ * direction: the text direction for the widget
+ * flags: tab-dependant flags; see the GtkTabFlags definition.
+ * widget: tab widget
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint
+moz_gtk_get_tab_border(gint* left, gint* top, gint* right, gint* bottom,
+ GtkTextDirection direction, GtkTabFlags flags,
+ WidgetNodeType widget);
+
+/**
+ * Get the desired size of a GtkCheckButton
+ * indicator_size: [OUT] the indicator size
+ * indicator_spacing: [OUT] the spacing between the indicator and its
+ * container
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint
+moz_gtk_checkbox_get_metrics(gint* indicator_size, gint* indicator_spacing);
+
+/**
+ * Get the desired size of a GtkRadioButton
+ * indicator_size: [OUT] the indicator size
+ * indicator_spacing: [OUT] the spacing between the indicator and its
+ * container
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint
+moz_gtk_radio_get_metrics(gint* indicator_size, gint* indicator_spacing);
+
+/** Get the extra size for the focus ring for outline:auto.
+ * widget: [IN] the widget to get the focus metrics for
+ * focus_h_width: [OUT] the horizontal width
+ * focus_v_width: [OUT] the vertical width
+ *
+ * returns: MOZ_GTK_SUCCESS
+ */
+gint
+moz_gtk_get_focus_outline_size(gint* focus_h_width, gint* focus_v_width);
+
+/** Get the horizontal padding for the menuitem widget or checkmenuitem widget.
+ * horizontal_padding: [OUT] The left and right padding of the menuitem or checkmenuitem
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint
+moz_gtk_menuitem_get_horizontal_padding(gint* horizontal_padding);
+
+gint
+moz_gtk_checkmenuitem_get_horizontal_padding(gint* horizontal_padding);
+
+/**
+ * Some GTK themes draw their indication for the default button outside
+ * the button (e.g. the glow in New Wave). This gets the extra space necessary.
+ *
+ * border_top: [OUT] extra space to add above
+ * border_left: [OUT] extra space to add to the left
+ * border_bottom: [OUT] extra space to add underneath
+ * border_right: [OUT] extra space to add to the right
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint
+moz_gtk_button_get_default_overflow(gint* border_top, gint* border_left,
+ gint* border_bottom, gint* border_right);
+
+/**
+ * Gets the minimum size of a GtkScale.
+ * orient: [IN] the scale orientation
+ * scale_width: [OUT] the width of the scale
+ * scale_height: [OUT] the height of the scale
+ */
+void
+moz_gtk_get_scale_metrics(GtkOrientation orient, gint* scale_width,
+ gint* scale_height);
+
+/**
+ * Get the desired size of a GtkScale thumb
+ * orient: [IN] the scale orientation
+ * thumb_length: [OUT] the length of the thumb
+ * thumb_height: [OUT] the height of the thumb
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint
+moz_gtk_get_scalethumb_metrics(GtkOrientation orient, gint* thumb_length, gint* thumb_height);
+
+/**
+ * Get the desired metrics for a GtkScrollbar
+ * metrics: [IN] struct which will contain the metrics
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint
+moz_gtk_get_scrollbar_metrics(MozGtkScrollbarMetrics* metrics);
+
+/**
+ * Get the desired size of a dropdown arrow button
+ * width: [OUT] the desired width
+ * height: [OUT] the desired height
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_combo_box_entry_button_size(gint* width, gint* height);
+
+/**
+ * Get the desired size of a scroll arrow widget
+ * width: [OUT] the desired width
+ * height: [OUT] the desired height
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_tab_scroll_arrow_size(gint* width, gint* height);
+
+/**
+ * Get the desired size of an arrow in a button
+ *
+ * widgetType: [IN] the widget for which to get the arrow size
+ * width: [OUT] the desired width
+ * height: [OUT] the desired height
+ */
+void
+moz_gtk_get_arrow_size(WidgetNodeType widgetType,
+ gint* width, gint* height);
+
+/**
+ * Get the minimum height of a entry widget
+ * size: [OUT] the minimum height
+ *
+ */
+void moz_gtk_get_entry_min_height(gint* height);
+
+/**
+ * Get the desired size of a toolbar separator
+ * size: [OUT] the desired width
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_toolbar_separator_width(gint* size);
+
+/**
+ * Get the size of a regular GTK expander that shows/hides content
+ * size: [OUT] the size of the GTK expander, size = width = height.
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_expander_size(gint* size);
+
+/**
+ * Get the size of a treeview's expander (we call them twisties)
+ * size: [OUT] the size of the GTK expander, size = width = height.
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_treeview_expander_size(gint* size);
+
+/**
+ * Get the desired height of a menu separator
+ * size: [OUT] the desired height
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_menu_separator_height(gint* size);
+
+/**
+ * Get the desired size of a splitter
+ * orientation: [IN] GTK_ORIENTATION_HORIZONTAL or GTK_ORIENTATION_VERTICAL
+ * size: [OUT] width or height of the splitter handle
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_splitter_get_metrics(gint orientation, gint* size);
+
+/**
+ * Retrieve an actual GTK scrollbar widget for style analysis. It will not
+ * be modified.
+ */
+GtkWidget* moz_gtk_get_scrollbar_widget(void);
+
+/**
+ * Get the YTHICKNESS of a tab (notebook extension).
+ */
+gint
+moz_gtk_get_tab_thickness(WidgetNodeType aNodeType);
+
+/**
+ * Get a boolean which indicates whether the theme draws scrollbar buttons.
+ * If TRUE, draw scrollbar buttons.
+ */
+gboolean moz_gtk_has_scrollbar_buttons(void);
+
+/**
+ * Get minimum widget size as sum of margin, padding, border and min-width,
+ * min-height.
+ */
+void moz_gtk_get_widget_min_size(WidgetNodeType aGtkWidgetType, int* width,
+ int* height);
+
+#if (MOZ_WIDGET_GTK == 2)
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
+
+#endif
diff --git a/widget/gtk/maiRedundantObjectFactory.c b/widget/gtk/maiRedundantObjectFactory.c
new file mode 100644
index 000000000..3db26c8bb
--- /dev/null
+++ b/widget/gtk/maiRedundantObjectFactory.c
@@ -0,0 +1,93 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include <atk/atk.h>
+#include "maiRedundantObjectFactory.h"
+
+static void mai_redundant_object_factory_class_init (
+ maiRedundantObjectFactoryClass *klass);
+
+static AtkObject* mai_redundant_object_factory_create_accessible (
+ GObject *obj);
+static GType mai_redundant_object_factory_get_accessible_type (void);
+
+GType
+mai_redundant_object_factory_get_type (void)
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ static const GTypeInfo tinfo =
+ {
+ sizeof (maiRedundantObjectFactoryClass),
+ (GBaseInitFunc) NULL, /* base init */
+ (GBaseFinalizeFunc) NULL, /* base finalize */
+ (GClassInitFunc) mai_redundant_object_factory_class_init, /* class init */
+ (GClassFinalizeFunc) NULL, /* class finalize */
+ NULL, /* class data */
+ sizeof (maiRedundantObjectFactory), /* instance size */
+ 0, /* nb preallocs */
+ (GInstanceInitFunc) NULL, /* instance init */
+ NULL /* value table */
+ };
+ type = g_type_register_static (
+ ATK_TYPE_OBJECT_FACTORY,
+ "MaiRedundantObjectFactory" , &tinfo, 0);
+ }
+
+ return type;
+}
+
+static void
+mai_redundant_object_factory_class_init (maiRedundantObjectFactoryClass *klass)
+{
+ AtkObjectFactoryClass *class = ATK_OBJECT_FACTORY_CLASS (klass);
+
+ class->create_accessible = mai_redundant_object_factory_create_accessible;
+ class->get_accessible_type = mai_redundant_object_factory_get_accessible_type;
+}
+
+/**
+ * mai_redundant_object_factory_new:
+ *
+ * Creates an instance of an #AtkObjectFactory which generates primitive
+ * (non-functioning) #AtkObjects.
+ *
+ * Returns: an instance of an #AtkObjectFactory
+ **/
+AtkObjectFactory*
+mai_redundant_object_factory_new ()
+{
+ GObject *factory;
+
+ factory = g_object_new (mai_redundant_object_factory_get_type(), NULL);
+
+ g_return_val_if_fail (factory != NULL, NULL);
+ return ATK_OBJECT_FACTORY (factory);
+}
+
+static AtkObject*
+mai_redundant_object_factory_create_accessible (GObject *obj)
+{
+ AtkObject *accessible;
+
+ g_return_val_if_fail (obj != NULL, NULL);
+
+ accessible = g_object_new (ATK_TYPE_OBJECT, NULL);
+ g_return_val_if_fail (accessible != NULL, NULL);
+
+ accessible->role = ATK_ROLE_REDUNDANT_OBJECT;
+
+ return accessible;
+}
+
+static GType
+mai_redundant_object_factory_get_accessible_type ()
+{
+ return mai_redundant_object_factory_get_type();
+}
diff --git a/widget/gtk/maiRedundantObjectFactory.h b/widget/gtk/maiRedundantObjectFactory.h
new file mode 100644
index 000000000..809af23ce
--- /dev/null
+++ b/widget/gtk/maiRedundantObjectFactory.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 __MAI_REDUNDANT_OBJECT_FACTORY_H__
+#define __MAI_REDUNDANT_OBJECT_FACTORY_H__
+
+G_BEGIN_DECLS
+
+typedef struct _maiRedundantObjectFactory maiRedundantObjectFactory;
+typedef struct _maiRedundantObjectFactoryClass maiRedundantObjectFactoryClass;
+
+struct _maiRedundantObjectFactory
+{
+ AtkObjectFactory parent;
+};
+
+struct _maiRedundantObjectFactoryClass
+{
+ AtkObjectFactoryClass parent_class;
+};
+
+GType mai_redundant_object_factory_get_type();
+
+AtkObjectFactory *mai_redundant_object_factory_new();
+
+G_END_DECLS
+
+#endif /* __NS_MAI_REDUNDANT_OBJECT_FACTORY_H__ */
diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build
new file mode 100644
index 000000000..baccb6ccd
--- /dev/null
+++ b/widget/gtk/moz.build
@@ -0,0 +1,141 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# 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/.
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk3':
+ DIRS += ['mozgtk']
+
+EXPORTS += [
+ 'mozcontainer.h',
+ 'nsGTKToolkit.h',
+ 'nsIImageToPixbuf.h',
+]
+
+EXPORTS.mozilla += [
+ 'WidgetUtilsGtk.h'
+]
+
+UNIFIED_SOURCES += [
+ 'IMContextWrapper.cpp',
+ 'mozcontainer.c',
+ 'NativeKeyBindings.cpp',
+ 'nsAppShell.cpp',
+ 'nsBidiKeyboard.cpp',
+ 'nsColorPicker.cpp',
+ 'nsFilePicker.cpp',
+ 'nsGtkKeyUtils.cpp',
+ 'nsImageToPixbuf.cpp',
+ 'nsLookAndFeel.cpp',
+ 'nsNativeThemeGTK.cpp',
+ 'nsScreenGtk.cpp',
+ 'nsScreenManagerGtk.cpp',
+ 'nsSound.cpp',
+ 'nsToolkit.cpp',
+ 'nsWidgetFactory.cpp',
+ 'WakeLockListener.cpp',
+ 'WidgetTraceEvent.cpp',
+ 'WidgetUtilsGtk.cpp',
+]
+
+SOURCES += [
+ 'nsWindow.cpp', # conflicts with X11 headers
+]
+
+if CONFIG['MOZ_X11']:
+ UNIFIED_SOURCES += [
+ 'CompositorWidgetChild.cpp',
+ 'CompositorWidgetParent.cpp',
+ 'InProcessX11CompositorWidget.cpp',
+ 'nsIdleServiceGTK.cpp',
+ 'X11CompositorWidget.cpp',
+ ]
+ EXPORTS.mozilla.widget += [
+ 'CompositorWidgetChild.h',
+ 'CompositorWidgetParent.h',
+ 'InProcessX11CompositorWidget.h',
+ 'X11CompositorWidget.h',
+ ]
+
+if CONFIG['NS_PRINTING']:
+ UNIFIED_SOURCES += [
+ 'nsCUPSShim.cpp',
+ 'nsDeviceContextSpecG.cpp',
+ 'nsPaperPS.cpp',
+ 'nsPrintDialogGTK.cpp',
+ 'nsPrintOptionsGTK.cpp',
+ 'nsPrintSettingsGTK.cpp',
+ 'nsPSPrinters.cpp',
+ ]
+
+if CONFIG['MOZ_X11']:
+ UNIFIED_SOURCES += [
+ 'nsClipboard.cpp',
+ 'nsDragService.cpp',
+ 'WindowSurfaceProvider.cpp',
+ 'WindowSurfaceX11.cpp',
+ 'WindowSurfaceX11Image.cpp',
+ 'WindowSurfaceXRender.cpp',
+ ]
+ EXPORTS.mozilla.widget += [
+ 'WindowSurfaceProvider.h',
+ ]
+
+if CONFIG['ACCESSIBILITY']:
+ UNIFIED_SOURCES += [
+ 'maiRedundantObjectFactory.c',
+ ]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk2':
+ UNIFIED_SOURCES += [
+ 'gtk2drawing.c',
+ ]
+else:
+ UNIFIED_SOURCES += [
+ 'gtk3drawing.cpp',
+ 'nsApplicationChooser.cpp',
+ 'WidgetStyleCache.cpp',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/layout/generic',
+ '/layout/xul',
+ '/other-licenses/atk-1.0',
+ '/widget',
+]
+
+if CONFIG['MOZ_X11']:
+ LOCAL_INCLUDES += [
+ '/widget/x11',
+ ]
+
+DEFINES['CAIRO_GFX'] = True
+
+DEFINES['MOZ_APP_NAME'] = '"%s"' % CONFIG['MOZ_APP_NAME']
+
+CFLAGS += CONFIG['MOZ_STARTUP_NOTIFICATION_CFLAGS']
+
+# When building with GTK3, the widget code always needs to use
+# system Cairo headers, regardless of whether we are also linked
+# against and using in-tree Cairo. By not using in-tree Cairo
+# headers, we avoid picking up our renamed symbols, and instead
+# use only system Cairo symbols that GTK3 uses. This allows that
+# any Cairo objects created can be freely passed back and forth
+# between the widget code and GTK3.
+if not (CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk3' and CONFIG['MOZ_TREE_CAIRO']):
+ CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
+
+CXXFLAGS += CONFIG['MOZ_STARTUP_NOTIFICATION_CFLAGS']
+
+CFLAGS += CONFIG['TK_CFLAGS']
+CXXFLAGS += CONFIG['TK_CFLAGS']
+
+if CONFIG['MOZ_ENABLE_DBUS']:
+ CXXFLAGS += CONFIG['MOZ_DBUS_GLIB_CFLAGS']
+
+CXXFLAGS += ['-Wno-error=shadow']
diff --git a/widget/gtk/mozcontainer.c b/widget/gtk/mozcontainer.c
new file mode 100644
index 000000000..9b596e4fb
--- /dev/null
+++ b/widget/gtk/mozcontainer.c
@@ -0,0 +1,418 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "mozcontainer.h"
+#include <gtk/gtk.h>
+#include <stdio.h>
+
+#ifdef ACCESSIBILITY
+#include <atk/atk.h>
+#include "maiRedundantObjectFactory.h"
+#endif
+
+/* init methods */
+static void moz_container_class_init (MozContainerClass *klass);
+static void moz_container_init (MozContainer *container);
+
+/* widget class methods */
+static void moz_container_map (GtkWidget *widget);
+static void moz_container_unmap (GtkWidget *widget);
+static void moz_container_realize (GtkWidget *widget);
+static void moz_container_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+
+/* container class methods */
+static void moz_container_remove (GtkContainer *container,
+ GtkWidget *child_widget);
+static void moz_container_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_data);
+static void moz_container_add (GtkContainer *container,
+ GtkWidget *widget);
+
+typedef struct _MozContainerChild MozContainerChild;
+
+struct _MozContainerChild {
+ GtkWidget *widget;
+ gint x;
+ gint y;
+};
+
+static void moz_container_allocate_child (MozContainer *container,
+ MozContainerChild *child);
+static MozContainerChild *
+moz_container_get_child (MozContainer *container, GtkWidget *child);
+
+/* public methods */
+
+GType
+moz_container_get_type(void)
+{
+ static GType moz_container_type = 0;
+
+ if (!moz_container_type) {
+ static GTypeInfo moz_container_info = {
+ sizeof(MozContainerClass), /* class_size */
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) moz_container_class_init, /* class_init */
+ NULL, /* class_destroy */
+ NULL, /* class_data */
+ sizeof(MozContainer), /* instance_size */
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) moz_container_init, /* instance_init */
+ NULL, /* value_table */
+ };
+
+ moz_container_type = g_type_register_static (GTK_TYPE_CONTAINER,
+ "MozContainer",
+ &moz_container_info, 0);
+#ifdef ACCESSIBILITY
+ /* Set a factory to return accessible object with ROLE_REDUNDANT for
+ * MozContainer, so that gail won't send focus notification for it */
+ atk_registry_set_factory_type(atk_get_default_registry(),
+ moz_container_type,
+ mai_redundant_object_factory_get_type());
+#endif
+ }
+
+ return moz_container_type;
+}
+
+GtkWidget *
+moz_container_new (void)
+{
+ MozContainer *container;
+
+ container = g_object_new (MOZ_CONTAINER_TYPE, NULL);
+
+ return GTK_WIDGET(container);
+}
+
+void
+moz_container_put (MozContainer *container, GtkWidget *child_widget,
+ gint x, gint y)
+{
+ MozContainerChild *child;
+
+ child = g_new (MozContainerChild, 1);
+
+ child->widget = child_widget;
+ child->x = x;
+ child->y = y;
+
+ /* printf("moz_container_put %p %p %d %d\n", (void *)container,
+ (void *)child_widget, x, y); */
+
+ container->children = g_list_append (container->children, child);
+
+ /* we assume that the caller of this function will have already set
+ the parent GdkWindow because we can have many anonymous children. */
+ gtk_widget_set_parent(child_widget, GTK_WIDGET(container));
+}
+
+void
+moz_container_move (MozContainer *container, GtkWidget *child_widget,
+ gint x, gint y, gint width, gint height)
+{
+ MozContainerChild *child;
+ GtkAllocation new_allocation;
+
+ child = moz_container_get_child (container, child_widget);
+
+ child->x = x;
+ child->y = y;
+
+ new_allocation.x = x;
+ new_allocation.y = y;
+ new_allocation.width = width;
+ new_allocation.height = height;
+
+ /* printf("moz_container_move %p %p will allocate to %d %d %d %d\n",
+ (void *)container, (void *)child_widget,
+ new_allocation.x, new_allocation.y,
+ new_allocation.width, new_allocation.height); */
+
+ gtk_widget_size_allocate(child_widget, &new_allocation);
+}
+
+/* static methods */
+
+void
+moz_container_class_init (MozContainerClass *klass)
+{
+ /*GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass); */
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ widget_class->map = moz_container_map;
+ widget_class->unmap = moz_container_unmap;
+ widget_class->realize = moz_container_realize;
+ widget_class->size_allocate = moz_container_size_allocate;
+
+ container_class->remove = moz_container_remove;
+ container_class->forall = moz_container_forall;
+ container_class->add = moz_container_add;
+}
+
+void
+moz_container_init (MozContainer *container)
+{
+ gtk_widget_set_can_focus(GTK_WIDGET(container), TRUE);
+ gtk_container_set_resize_mode(GTK_CONTAINER(container), GTK_RESIZE_IMMEDIATE);
+ gtk_widget_set_redraw_on_allocate(GTK_WIDGET(container), FALSE);
+}
+
+void
+moz_container_map (GtkWidget *widget)
+{
+ MozContainer *container;
+ GList *tmp_list;
+ GtkWidget *tmp_child;
+
+ g_return_if_fail (IS_MOZ_CONTAINER(widget));
+ container = MOZ_CONTAINER (widget);
+
+ gtk_widget_set_mapped(widget, TRUE);
+
+ tmp_list = container->children;
+ while (tmp_list) {
+ tmp_child = ((MozContainerChild *)tmp_list->data)->widget;
+
+ if (gtk_widget_get_visible(tmp_child)) {
+ if (!gtk_widget_get_mapped(tmp_child))
+ gtk_widget_map(tmp_child);
+ }
+ tmp_list = tmp_list->next;
+ }
+
+ if (gtk_widget_get_has_window (widget)) {
+ gdk_window_show (gtk_widget_get_window(widget));
+ }
+}
+
+void
+moz_container_unmap (GtkWidget *widget)
+{
+ g_return_if_fail (IS_MOZ_CONTAINER (widget));
+
+ gtk_widget_set_mapped(widget, FALSE);
+
+ if (gtk_widget_get_has_window (widget)) {
+ gdk_window_hide (gtk_widget_get_window(widget));
+ }
+}
+
+void
+moz_container_realize (GtkWidget *widget)
+{
+ GdkWindow *parent = gtk_widget_get_parent_window (widget);
+ GdkWindow *window;
+
+ gtk_widget_set_realized(widget, TRUE);
+
+ if (gtk_widget_get_has_window (widget)) {
+ GdkWindowAttr attributes;
+ gint attributes_mask = GDK_WA_VISUAL | GDK_WA_X | GDK_WA_Y;
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (widget, &allocation);
+ attributes.event_mask = gtk_widget_get_events (widget);
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.window_type = GDK_WINDOW_CHILD;
+
+#if (MOZ_WIDGET_GTK == 2)
+ attributes.colormap = gtk_widget_get_colormap (widget);
+ attributes_mask |= GDK_WA_COLORMAP;
+#endif
+
+ window = gdk_window_new (parent, &attributes, attributes_mask);
+ gdk_window_set_user_data (window, widget);
+#if (MOZ_WIDGET_GTK == 2)
+ /* TODO GTK3? */
+ /* set the back pixmap to None so that you don't end up with the gtk
+ default which is BlackPixel */
+ gdk_window_set_back_pixmap (window, NULL, FALSE);
+#endif
+ } else {
+ window = parent;
+ g_object_ref (window);
+ }
+
+ gtk_widget_set_window (widget, window);
+
+#if (MOZ_WIDGET_GTK == 2)
+ widget->style = gtk_style_attach (widget->style, widget->window);
+#endif
+}
+
+void
+moz_container_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ MozContainer *container;
+ GList *tmp_list;
+ GtkAllocation tmp_allocation;
+
+ g_return_if_fail (IS_MOZ_CONTAINER (widget));
+
+ /* printf("moz_container_size_allocate %p %d %d %d %d\n",
+ (void *)widget,
+ allocation->x,
+ allocation->y,
+ allocation->width,
+ allocation->height); */
+
+ /* short circuit if you can */
+ container = MOZ_CONTAINER (widget);
+ gtk_widget_get_allocation(widget, &tmp_allocation);
+ if (!container->children &&
+ tmp_allocation.x == allocation->x &&
+ tmp_allocation.y == allocation->y &&
+ tmp_allocation.width == allocation->width &&
+ tmp_allocation.height == allocation->height) {
+ return;
+ }
+
+ gtk_widget_set_allocation(widget, allocation);
+
+ tmp_list = container->children;
+
+ while (tmp_list) {
+ MozContainerChild *child = tmp_list->data;
+
+ moz_container_allocate_child (container, child);
+
+ tmp_list = tmp_list->next;
+ }
+
+ if (gtk_widget_get_has_window (widget) &&
+ gtk_widget_get_realized (widget)) {
+
+ gdk_window_move_resize(gtk_widget_get_window(widget),
+ allocation->x,
+ allocation->y,
+ allocation->width,
+ allocation->height);
+ }
+}
+
+void
+moz_container_remove (GtkContainer *container, GtkWidget *child_widget)
+{
+ MozContainerChild *child;
+ MozContainer *moz_container;
+ GdkWindow* parent_window;
+
+ g_return_if_fail (IS_MOZ_CONTAINER(container));
+ g_return_if_fail (GTK_IS_WIDGET(child_widget));
+
+ moz_container = MOZ_CONTAINER(container);
+
+ child = moz_container_get_child (moz_container, child_widget);
+ g_return_if_fail (child);
+
+ /* gtk_widget_unparent will remove the parent window (as well as the
+ * parent widget), but, in Mozilla's window hierarchy, the parent window
+ * may need to be kept because it may be part of a GdkWindow sub-hierarchy
+ * that is being moved to another MozContainer.
+ *
+ * (In a conventional GtkWidget hierarchy, GdkWindows being reparented
+ * would have their own GtkWidget and that widget would be the one being
+ * reparented. In Mozilla's hierarchy, the parent_window needs to be
+ * retained so that the GdkWindow sub-hierarchy is maintained.)
+ */
+ parent_window = gtk_widget_get_parent_window(child_widget);
+ if (parent_window)
+ g_object_ref(parent_window);
+
+ gtk_widget_unparent(child_widget);
+
+ if (parent_window) {
+ /* The child_widget will always still exist because g_signal_emit,
+ * which invokes this function, holds a reference.
+ *
+ * If parent_window is the container's root window then it will not be
+ * the parent_window if the child_widget is placed in another
+ * container.
+ */
+ if (parent_window != gtk_widget_get_window(GTK_WIDGET(container)))
+ gtk_widget_set_parent_window(child_widget, parent_window);
+
+ g_object_unref(parent_window);
+ }
+
+ moz_container->children = g_list_remove(moz_container->children, child);
+ g_free(child);
+}
+
+void
+moz_container_forall (GtkContainer *container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data)
+{
+ MozContainer *moz_container;
+ GList *tmp_list;
+
+ g_return_if_fail (IS_MOZ_CONTAINER(container));
+ g_return_if_fail (callback != NULL);
+
+ moz_container = MOZ_CONTAINER(container);
+
+ tmp_list = moz_container->children;
+ while (tmp_list) {
+ MozContainerChild *child;
+ child = tmp_list->data;
+ tmp_list = tmp_list->next;
+ (* callback) (child->widget, callback_data);
+ }
+}
+
+static void
+moz_container_allocate_child (MozContainer *container,
+ MozContainerChild *child)
+{
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (child->widget, &allocation);
+ allocation.x = child->x;
+ allocation.y = child->y;
+
+ gtk_widget_size_allocate (child->widget, &allocation);
+}
+
+MozContainerChild *
+moz_container_get_child (MozContainer *container, GtkWidget *child_widget)
+{
+ GList *tmp_list;
+
+ tmp_list = container->children;
+ while (tmp_list) {
+ MozContainerChild *child;
+
+ child = tmp_list->data;
+ tmp_list = tmp_list->next;
+
+ if (child->widget == child_widget)
+ return child;
+ }
+
+ return NULL;
+}
+
+static void
+moz_container_add(GtkContainer *container, GtkWidget *widget)
+{
+ moz_container_put(MOZ_CONTAINER(container), widget, 0, 0);
+}
+
diff --git a/widget/gtk/mozcontainer.h b/widget/gtk/mozcontainer.h
new file mode 100644
index 000000000..23e17f7b3
--- /dev/null
+++ b/widget/gtk/mozcontainer.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 __MOZ_CONTAINER_H__
+#define __MOZ_CONTAINER_H__
+
+#include <gtk/gtk.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * MozContainer
+ *
+ * This class serves two purposes in the nsIWidget implementation.
+ *
+ * - It provides objects to receive signals from GTK for events on native
+ * windows.
+ *
+ * - It provides a container parent for GtkWidgets. The only GtkWidgets
+ * that need this in Mozilla are the GtkSockets for windowed plugins (Xt
+ * and XEmbed).
+ *
+ * Note that the window hierarchy in Mozilla differs from conventional
+ * GtkWidget hierarchies.
+ *
+ * Mozilla's hierarchy exists through the GdkWindow hierarchy, and all child
+ * GdkWindows (within a child nsIWidget hierarchy) belong to one MozContainer
+ * GtkWidget. If the MozContainer is unrealized or its GdkWindows are
+ * destroyed for some other reason, then the hierarchy no longer exists. (In
+ * conventional GTK clients, the hierarchy is recorded by the GtkWidgets, and
+ * so can be re-established after destruction of the GdkWindows.)
+ *
+ * One consequence of this is that the MozContainer does not know which of its
+ * GdkWindows should parent child GtkWidgets. (Conventional GtkContainers
+ * determine which GdkWindow to assign child GtkWidgets.)
+ *
+ * Therefore, when adding a child GtkWidget to a MozContainer,
+ * gtk_widget_set_parent_window should be called on the child GtkWidget before
+ * it is realized.
+ */
+
+#define MOZ_CONTAINER_TYPE (moz_container_get_type())
+#define MOZ_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MOZ_CONTAINER_TYPE, MozContainer))
+#define MOZ_CONTAINER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MOZ_CONTAINER_TYPE, MozContainerClass))
+#define IS_MOZ_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MOZ_CONTAINER_TYPE))
+#define IS_MOZ_CONTAINER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MOZ_CONTAINER_TYPE))
+#define MOZ_CONAINTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MOZ_CONTAINER_TYPE, MozContainerClass))
+
+typedef struct _MozContainer MozContainer;
+typedef struct _MozContainerClass MozContainerClass;
+
+struct _MozContainer
+{
+ GtkContainer container;
+ GList *children;
+};
+
+struct _MozContainerClass
+{
+ GtkContainerClass parent_class;
+};
+
+GType moz_container_get_type (void);
+GtkWidget *moz_container_new (void);
+void moz_container_put (MozContainer *container,
+ GtkWidget *child_widget,
+ gint x,
+ gint y);
+void moz_container_move (MozContainer *container,
+ GtkWidget *child_widget,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __MOZ_CONTAINER_H__ */
diff --git a/widget/gtk/mozgtk/gtk2/moz.build b/widget/gtk/mozgtk/gtk2/moz.build
new file mode 100644
index 000000000..d07fd06df
--- /dev/null
+++ b/widget/gtk/mozgtk/gtk2/moz.build
@@ -0,0 +1,40 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# 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/.
+
+SOURCES += [
+ '../mozgtk.c',
+]
+
+DEFINES['GTK3_SYMBOLS'] = True
+
+SharedLibrary('mozgtk2')
+
+SHARED_LIBRARY_NAME = 'mozgtk'
+
+FINAL_TARGET = 'dist/bin/gtk2'
+
+# If LDFLAGS contains -Wl,--as-needed or if it's the default for the toolchain,
+# we need to add -Wl,--no-as-needed before the gtk libraries, otherwise the
+# linker will drop those dependencies because no symbols are used from them.
+# But those dependencies need to be kept for things to work properly.
+# Ideally, we'd only add -Wl,--no-as-needed if necessary, but it's just simpler
+# to add it unconditionally. This library is also simple enough that forcing
+# -Wl,--as-needed after the gtk libraries is not going to make a significant
+# difference.
+if CONFIG['GCC_USE_GNU_LD']:
+ no_as_needed = ['-Wl,--no-as-needed']
+ as_needed = ['-Wl,--as-needed']
+else:
+ no_as_needed = []
+ as_needed = []
+
+OS_LIBS += [f for f in CONFIG['MOZ_GTK2_LIBS'] if f.startswith('-L')]
+OS_LIBS += no_as_needed
+OS_LIBS += [
+ 'gtk-x11-2.0',
+ 'gdk-x11-2.0',
+]
+OS_LIBS += as_needed
diff --git a/widget/gtk/mozgtk/gtk3/moz.build b/widget/gtk/mozgtk/gtk3/moz.build
new file mode 100644
index 000000000..4e9379565
--- /dev/null
+++ b/widget/gtk/mozgtk/gtk3/moz.build
@@ -0,0 +1,38 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# 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/.
+
+SOURCES += [
+ '../mozgtk.c',
+]
+
+DEFINES['GTK2_SYMBOLS'] = True
+
+SharedLibrary('mozgtk')
+
+SONAME = 'mozgtk'
+
+# If LDFLAGS contains -Wl,--as-needed or if it's the default for the toolchain,
+# we need to add -Wl,--no-as-needed before the gtk libraries, otherwise the
+# linker will drop those dependencies because no symbols are used from them.
+# But those dependencies need to be kept for things to work properly.
+# Ideally, we'd only add -Wl,--no-as-needed if necessary, but it's just simpler
+# to add it unconditionally. This library is also simple enough that forcing
+# -Wl,--as-needed after the gtk libraries is not going to make a significant
+# difference.
+if CONFIG['GCC_USE_GNU_LD']:
+ no_as_needed = ['-Wl,--no-as-needed']
+ as_needed = ['-Wl,--as-needed']
+else:
+ no_as_needed = []
+ as_needed = []
+
+OS_LIBS += [f for f in CONFIG['MOZ_GTK3_LIBS'] if f.startswith('-L')]
+OS_LIBS += no_as_needed
+OS_LIBS += [
+ 'gtk-3',
+ 'gdk-3',
+]
+OS_LIBS += as_needed
diff --git a/widget/gtk/mozgtk/moz.build b/widget/gtk/mozgtk/moz.build
new file mode 100644
index 000000000..528e2e9d0
--- /dev/null
+++ b/widget/gtk/mozgtk/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# 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/.
+
+DIRS += ['stub', 'gtk2', 'gtk3']
diff --git a/widget/gtk/mozgtk/mozgtk.c b/widget/gtk/mozgtk/mozgtk.c
new file mode 100644
index 000000000..d9fb9385d
--- /dev/null
+++ b/widget/gtk/mozgtk/mozgtk.c
@@ -0,0 +1,634 @@
+#include "mozilla/Types.h"
+#include "mozilla/Assertions.h"
+
+#define STUB(symbol) MOZ_EXPORT void symbol (void) { MOZ_CRASH(); }
+
+#ifdef COMMON_SYMBOLS
+STUB(gdk_atom_intern)
+STUB(gdk_atom_name)
+STUB(gdk_beep)
+STUB(gdk_cairo_create)
+STUB(gdk_color_free)
+STUB(gdk_color_parse)
+STUB(gdk_cursor_new_for_display)
+STUB(gdk_cursor_new_from_name)
+STUB(gdk_cursor_new_from_pixbuf)
+STUB(gdk_display_close)
+STUB(gdk_display_get_default)
+STUB(gdk_display_get_default_screen)
+STUB(gdk_display_get_pointer)
+STUB(gdk_display_get_window_at_pointer)
+STUB(gdk_display_manager_get)
+STUB(gdk_display_manager_set_default_display)
+STUB(gdk_display_open)
+STUB(gdk_display_sync)
+STUB(gdk_display_warp_pointer)
+STUB(gdk_drag_context_get_actions)
+STUB(gdk_drag_context_get_dest_window)
+STUB(gdk_drag_context_list_targets)
+STUB(gdk_drag_status)
+STUB(gdk_error_trap_pop)
+STUB(gdk_error_trap_push)
+STUB(gdk_event_copy)
+STUB(gdk_event_free)
+STUB(gdk_event_get_axis)
+STUB(gdk_event_get_time)
+STUB(gdk_event_handler_set)
+STUB(gdk_event_peek)
+STUB(gdk_event_put)
+STUB(gdk_flush)
+STUB(gdk_get_default_root_window)
+STUB(gdk_get_display)
+STUB(gdk_get_display_arg_name)
+STUB(gdk_get_program_class)
+STUB(gdk_keymap_get_default)
+STUB(gdk_keymap_get_direction)
+STUB(gdk_keymap_get_entries_for_keyval)
+STUB(gdk_keymap_get_for_display)
+STUB(gdk_keymap_have_bidi_layouts)
+STUB(gdk_keymap_translate_keyboard_state)
+STUB(gdk_keyval_name)
+STUB(gdk_keyval_to_unicode)
+STUB(gdk_pango_context_get)
+STUB(gdk_pointer_grab)
+STUB(gdk_pointer_ungrab)
+STUB(gdk_property_get)
+STUB(gdk_screen_get_default)
+STUB(gdk_screen_get_display)
+STUB(gdk_screen_get_font_options)
+STUB(gdk_screen_get_height)
+STUB(gdk_screen_get_height_mm)
+STUB(gdk_screen_get_monitor_at_window)
+STUB(gdk_screen_get_monitor_geometry)
+STUB(gdk_screen_get_number)
+STUB(gdk_screen_get_resolution)
+STUB(gdk_screen_get_rgba_visual)
+STUB(gdk_screen_get_root_window)
+STUB(gdk_screen_get_system_visual)
+STUB(gdk_screen_get_width)
+STUB(gdk_screen_height)
+STUB(gdk_screen_is_composited)
+STUB(gdk_screen_width)
+STUB(gdk_unicode_to_keyval)
+STUB(gdk_visual_get_depth)
+STUB(gdk_visual_get_system)
+STUB(gdk_window_add_filter)
+STUB(gdk_window_begin_move_drag)
+STUB(gdk_window_begin_resize_drag)
+STUB(gdk_window_destroy)
+STUB(gdk_window_focus)
+STUB(gdk_window_get_children)
+STUB(gdk_window_get_display)
+STUB(gdk_window_get_events)
+STUB(gdk_window_get_geometry)
+STUB(gdk_window_get_height)
+STUB(gdk_window_get_origin)
+STUB(gdk_window_get_parent)
+STUB(gdk_window_get_position)
+STUB(gdk_window_get_root_origin)
+STUB(gdk_window_get_screen)
+STUB(gdk_window_get_state)
+STUB(gdk_window_get_toplevel)
+STUB(gdk_window_get_update_area)
+STUB(gdk_window_get_user_data)
+STUB(gdk_window_get_visual)
+STUB(gdk_window_get_width)
+STUB(gdk_window_hide)
+STUB(gdk_window_input_shape_combine_region)
+STUB(gdk_window_invalidate_rect)
+STUB(gdk_window_invalidate_region)
+STUB(gdk_window_is_destroyed)
+STUB(gdk_window_is_visible)
+STUB(gdk_window_lower)
+STUB(gdk_window_move)
+STUB(gdk_window_move_resize)
+STUB(gdk_window_new)
+STUB(gdk_window_peek_children)
+STUB(gdk_window_process_updates)
+STUB(gdk_window_raise)
+STUB(gdk_window_remove_filter)
+STUB(gdk_window_reparent)
+STUB(gdk_window_resize)
+STUB(gdk_window_set_cursor)
+STUB(gdk_window_set_debug_updates)
+STUB(gdk_window_set_decorations)
+STUB(gdk_window_set_events)
+STUB(gdk_window_set_role)
+STUB(gdk_window_set_urgency_hint)
+STUB(gdk_window_set_user_data)
+STUB(gdk_window_shape_combine_region)
+STUB(gdk_window_show)
+STUB(gdk_window_show_unraised)
+STUB(gdk_x11_atom_to_xatom)
+STUB(gdk_x11_display_get_user_time)
+STUB(gdk_x11_display_get_xdisplay)
+STUB(gdk_x11_get_default_root_xwindow)
+STUB(gdk_x11_get_default_xdisplay)
+STUB(gdk_x11_get_server_time)
+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_supports_net_wm_hint)
+STUB(gdk_x11_visual_get_xvisual)
+STUB(gdk_x11_window_foreign_new_for_display)
+STUB(gdk_x11_window_lookup_for_display)
+STUB(gdk_x11_window_set_user_time)
+STUB(gdk_x11_xatom_to_atom)
+STUB(gdk_x11_set_sm_client_id)
+STUB(gtk_accel_label_new)
+STUB(gtk_alignment_get_type)
+STUB(gtk_alignment_new)
+STUB(gtk_alignment_set_padding)
+STUB(gtk_arrow_get_type)
+STUB(gtk_arrow_new)
+STUB(gtk_bindings_activate)
+STUB(gtk_bin_get_child)
+STUB(gtk_bin_get_type)
+STUB(gtk_border_free)
+STUB(gtk_box_get_type)
+STUB(gtk_box_pack_start)
+STUB(gtk_button_new)
+STUB(gtk_button_new_with_label)
+STUB(gtk_check_button_new_with_label)
+STUB(gtk_check_button_new_with_mnemonic)
+STUB(gtk_check_menu_item_new)
+STUB(gtk_check_version)
+STUB(gtk_clipboard_clear)
+STUB(gtk_clipboard_get)
+STUB(gtk_clipboard_request_contents)
+STUB(gtk_clipboard_request_text)
+STUB(gtk_clipboard_set_can_store)
+STUB(gtk_clipboard_set_with_data)
+STUB(gtk_clipboard_store)
+STUB(gtk_color_selection_dialog_get_color_selection)
+STUB(gtk_color_selection_dialog_get_type)
+STUB(gtk_color_selection_dialog_new)
+STUB(gtk_color_selection_get_current_color)
+STUB(gtk_color_selection_get_type)
+STUB(gtk_color_selection_set_current_color)
+STUB(gtk_combo_box_get_active)
+STUB(gtk_combo_box_get_type)
+STUB(gtk_combo_box_new)
+STUB(gtk_combo_box_new_with_entry)
+STUB(gtk_combo_box_set_active)
+STUB(gtk_combo_box_text_get_type)
+STUB(gtk_combo_box_text_new)
+STUB(gtk_container_add)
+STUB(gtk_container_forall)
+STUB(gtk_container_get_border_width)
+STUB(gtk_container_get_type)
+STUB(gtk_container_set_border_width)
+STUB(gtk_container_set_resize_mode)
+STUB(gtk_dialog_get_content_area)
+STUB(gtk_dialog_get_type)
+STUB(gtk_dialog_new_with_buttons)
+STUB(gtk_dialog_run)
+STUB(gtk_dialog_set_alternative_button_order)
+STUB(gtk_dialog_set_default_response)
+STUB(gtk_drag_begin)
+STUB(gtk_drag_dest_set)
+STUB(gtk_drag_finish)
+STUB(gtk_drag_get_data)
+STUB(gtk_drag_get_source_widget)
+STUB(gtk_drag_set_icon_pixbuf)
+STUB(gtk_drag_set_icon_widget)
+STUB(gtk_editable_get_type)
+STUB(gtk_editable_select_region)
+STUB(gtk_entry_get_text)
+STUB(gtk_entry_get_type)
+STUB(gtk_entry_new)
+STUB(gtk_entry_set_activates_default)
+STUB(gtk_entry_set_text)
+STUB(gtk_enumerate_printers)
+STUB(gtk_expander_new)
+STUB(gtk_file_chooser_add_filter)
+STUB(gtk_file_chooser_dialog_new)
+STUB(gtk_file_chooser_get_filenames)
+STUB(gtk_file_chooser_get_filter)
+STUB(gtk_file_chooser_get_preview_filename)
+STUB(gtk_file_chooser_get_type)
+STUB(gtk_file_chooser_get_uri)
+STUB(gtk_file_chooser_list_filters)
+STUB(gtk_file_chooser_set_current_folder)
+STUB(gtk_file_chooser_set_current_name)
+STUB(gtk_file_chooser_set_do_overwrite_confirmation)
+STUB(gtk_file_chooser_set_filename)
+STUB(gtk_file_chooser_set_filter)
+STUB(gtk_file_chooser_set_local_only)
+STUB(gtk_file_chooser_set_preview_widget)
+STUB(gtk_file_chooser_set_preview_widget_active)
+STUB(gtk_file_chooser_set_select_multiple)
+STUB(gtk_file_chooser_widget_get_type)
+STUB(gtk_file_filter_add_pattern)
+STUB(gtk_file_filter_new)
+STUB(gtk_file_filter_set_name)
+STUB(gtk_fixed_new)
+STUB(gtk_frame_new)
+STUB(gtk_grab_add)
+STUB(gtk_grab_remove)
+STUB(gtk_handle_box_new)
+STUB(gtk_hbox_new)
+STUB(gtk_icon_info_free)
+STUB(gtk_icon_info_load_icon)
+STUB(gtk_icon_set_add_source)
+STUB(gtk_icon_set_new)
+STUB(gtk_icon_set_render_icon)
+STUB(gtk_icon_set_unref)
+STUB(gtk_icon_size_lookup)
+STUB(gtk_icon_source_free)
+STUB(gtk_icon_source_new)
+STUB(gtk_icon_source_set_icon_name)
+STUB(gtk_icon_theme_add_builtin_icon)
+STUB(gtk_icon_theme_get_default)
+STUB(gtk_icon_theme_get_icon_sizes)
+STUB(gtk_icon_theme_lookup_by_gicon)
+STUB(gtk_icon_theme_lookup_icon)
+STUB(gtk_image_get_type)
+STUB(gtk_image_menu_item_new)
+STUB(gtk_image_new)
+STUB(gtk_image_new_from_stock)
+STUB(gtk_image_set_from_pixbuf)
+STUB(gtk_im_context_filter_keypress)
+STUB(gtk_im_context_focus_in)
+STUB(gtk_im_context_focus_out)
+STUB(gtk_im_context_get_preedit_string)
+STUB(gtk_im_context_reset)
+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_type)
+STUB(gtk_im_multicontext_new)
+STUB(gtk_info_bar_get_type)
+STUB(gtk_info_bar_get_content_area)
+STUB(gtk_info_bar_new)
+STUB(gtk_init)
+STUB(gtk_invisible_new)
+STUB(gtk_key_snooper_install)
+STUB(gtk_key_snooper_remove)
+STUB(gtk_label_get_type)
+STUB(gtk_label_new)
+STUB(gtk_label_set_markup)
+STUB(gtk_link_button_new)
+STUB(gtk_main_do_event)
+STUB(gtk_main_iteration)
+STUB(gtk_menu_attach_to_widget)
+STUB(gtk_menu_bar_new)
+STUB(gtk_menu_get_type)
+STUB(gtk_menu_item_get_type)
+STUB(gtk_menu_item_new)
+STUB(gtk_menu_item_set_submenu)
+STUB(gtk_menu_new)
+STUB(gtk_menu_shell_append)
+STUB(gtk_menu_shell_get_type)
+STUB(gtk_misc_get_alignment)
+STUB(gtk_misc_get_padding)
+STUB(gtk_misc_get_type)
+STUB(gtk_misc_set_alignment)
+STUB(gtk_misc_set_padding)
+STUB(gtk_notebook_new)
+STUB(gtk_page_setup_copy)
+STUB(gtk_page_setup_get_bottom_margin)
+STUB(gtk_page_setup_get_left_margin)
+STUB(gtk_page_setup_get_orientation)
+STUB(gtk_page_setup_get_paper_size)
+STUB(gtk_page_setup_get_right_margin)
+STUB(gtk_page_setup_get_top_margin)
+STUB(gtk_page_setup_new)
+STUB(gtk_page_setup_set_bottom_margin)
+STUB(gtk_page_setup_set_left_margin)
+STUB(gtk_page_setup_set_orientation)
+STUB(gtk_page_setup_set_paper_size)
+STUB(gtk_page_setup_set_paper_size_and_default_margins)
+STUB(gtk_page_setup_set_right_margin)
+STUB(gtk_page_setup_set_top_margin)
+STUB(gtk_paper_size_free)
+STUB(gtk_paper_size_get_display_name)
+STUB(gtk_paper_size_get_height)
+STUB(gtk_paper_size_get_name)
+STUB(gtk_paper_size_get_width)
+STUB(gtk_paper_size_is_custom)
+STUB(gtk_paper_size_is_equal)
+STUB(gtk_paper_size_new)
+STUB(gtk_paper_size_new_custom)
+STUB(gtk_paper_size_set_size)
+STUB(gtk_parse_args)
+STUB(gtk_plug_get_socket_window)
+STUB(gtk_plug_get_type)
+STUB(gtk_printer_accepts_pdf)
+STUB(gtk_printer_get_name)
+STUB(gtk_printer_get_type)
+STUB(gtk_printer_is_default)
+STUB(gtk_print_job_new)
+STUB(gtk_print_job_send)
+STUB(gtk_print_job_set_source_file)
+STUB(gtk_print_run_page_setup_dialog)
+STUB(gtk_print_settings_copy)
+STUB(gtk_print_settings_foreach)
+STUB(gtk_print_settings_get)
+STUB(gtk_print_settings_get_duplex)
+STUB(gtk_print_settings_get_n_copies)
+STUB(gtk_print_settings_get_page_ranges)
+STUB(gtk_print_settings_get_paper_size)
+STUB(gtk_print_settings_get_printer)
+STUB(gtk_print_settings_get_print_pages)
+STUB(gtk_print_settings_get_resolution)
+STUB(gtk_print_settings_get_reverse)
+STUB(gtk_print_settings_get_scale)
+STUB(gtk_print_settings_get_use_color)
+STUB(gtk_print_settings_has_key)
+STUB(gtk_print_settings_new)
+STUB(gtk_print_settings_set)
+STUB(gtk_print_settings_set_duplex)
+STUB(gtk_print_settings_set_n_copies)
+STUB(gtk_print_settings_set_orientation)
+STUB(gtk_print_settings_set_page_ranges)
+STUB(gtk_print_settings_set_paper_size)
+STUB(gtk_print_settings_set_printer)
+STUB(gtk_print_settings_set_print_pages)
+STUB(gtk_print_settings_set_resolution)
+STUB(gtk_print_settings_set_reverse)
+STUB(gtk_print_settings_set_scale)
+STUB(gtk_print_settings_set_use_color)
+STUB(gtk_print_unix_dialog_add_custom_tab)
+STUB(gtk_print_unix_dialog_get_page_setup)
+STUB(gtk_print_unix_dialog_get_selected_printer)
+STUB(gtk_print_unix_dialog_get_settings)
+STUB(gtk_print_unix_dialog_get_type)
+STUB(gtk_print_unix_dialog_new)
+STUB(gtk_print_unix_dialog_set_manual_capabilities)
+STUB(gtk_print_unix_dialog_set_page_setup)
+STUB(gtk_print_unix_dialog_set_settings)
+STUB(gtk_progress_bar_new)
+STUB(gtk_propagate_event)
+STUB(gtk_radio_button_get_type)
+STUB(gtk_radio_button_new_with_label)
+STUB(gtk_radio_button_new_with_mnemonic)
+STUB(gtk_radio_button_new_with_mnemonic_from_widget)
+STUB(gtk_range_get_min_slider_size)
+STUB(gtk_range_get_type)
+STUB(gtk_recent_manager_add_item)
+STUB(gtk_recent_manager_get_default)
+STUB(gtk_scrollbar_get_type)
+STUB(gtk_scrolled_window_new)
+STUB(gtk_selection_data_copy)
+STUB(gtk_selection_data_free)
+STUB(gtk_selection_data_get_data)
+STUB(gtk_selection_data_get_length)
+STUB(gtk_selection_data_get_selection)
+STUB(gtk_selection_data_get_target)
+STUB(gtk_selection_data_get_targets)
+STUB(gtk_selection_data_set)
+STUB(gtk_selection_data_set_pixbuf)
+STUB(gtk_selection_data_set_text)
+STUB(gtk_selection_data_targets_include_text)
+STUB(gtk_separator_get_type)
+STUB(gtk_separator_menu_item_new)
+STUB(gtk_separator_tool_item_new)
+STUB(gtk_settings_get_default)
+STUB(gtk_settings_get_for_screen)
+STUB(gtk_socket_add_id)
+STUB(gtk_socket_get_id)
+STUB(gtk_socket_get_type)
+STUB(gtk_socket_get_plug_window)
+STUB(gtk_socket_new)
+STUB(gtk_spin_button_new)
+STUB(gtk_statusbar_new)
+STUB(gtk_style_lookup_icon_set)
+STUB(gtk_table_attach)
+STUB(gtk_table_get_type)
+STUB(gtk_table_new)
+STUB(gtk_target_list_add)
+STUB(gtk_target_list_add_image_targets)
+STUB(gtk_target_list_new)
+STUB(gtk_target_list_unref)
+STUB(gtk_targets_include_image)
+STUB(gtk_target_table_free)
+STUB(gtk_target_table_new_from_list)
+STUB(gtk_text_view_new)
+STUB(gtk_toggle_button_get_active)
+STUB(gtk_toggle_button_get_type)
+STUB(gtk_toggle_button_new)
+STUB(gtk_toggle_button_set_active)
+STUB(gtk_toggle_button_set_inconsistent)
+STUB(gtk_toolbar_new)
+STUB(gtk_tooltip_get_type)
+STUB(gtk_tree_view_append_column)
+STUB(gtk_tree_view_column_new)
+STUB(gtk_tree_view_column_set_title)
+STUB(gtk_tree_view_get_type)
+STUB(gtk_tree_view_new)
+STUB(gtk_vbox_new)
+STUB(gtk_widget_add_events)
+STUB(gtk_widget_class_find_style_property)
+STUB(gtk_widget_destroy)
+STUB(gtk_widget_destroyed)
+STUB(gtk_widget_ensure_style)
+STUB(gtk_widget_event)
+STUB(gtk_widget_get_accessible)
+STUB(gtk_widget_get_allocation)
+STUB(gtk_widget_get_default_direction)
+STUB(gtk_widget_get_display)
+STUB(gtk_widget_get_events)
+STUB(gtk_widget_get_has_window)
+STUB(gtk_widget_get_mapped)
+STUB(gtk_widget_get_parent)
+STUB(gtk_widget_get_parent_window)
+STUB(gtk_widget_get_realized)
+STUB(gtk_widget_get_screen)
+STUB(gtk_widget_get_settings)
+STUB(gtk_widget_get_style)
+STUB(gtk_widget_get_toplevel)
+STUB(gtk_widget_get_type)
+STUB(gtk_widget_get_visible)
+STUB(gtk_widget_get_visual)
+STUB(gtk_widget_get_window)
+STUB(gtk_widget_grab_focus)
+STUB(gtk_widget_has_focus)
+STUB(gtk_widget_has_grab)
+STUB(gtk_widget_hide)
+STUB(gtk_widget_is_focus)
+STUB(gtk_widget_is_toplevel)
+STUB(gtk_widget_map)
+STUB(gtk_widget_modify_bg)
+STUB(gtk_widget_realize)
+STUB(gtk_widget_reparent)
+STUB(gtk_widget_set_allocation)
+STUB(gtk_widget_set_app_paintable)
+STUB(gtk_window_set_auto_startup_notification)
+STUB(gtk_window_set_opacity)
+STUB(gtk_window_set_screen)
+STUB(gtk_widget_set_can_focus)
+STUB(gtk_widget_set_direction)
+STUB(gtk_widget_set_double_buffered)
+STUB(gtk_widget_set_has_window)
+STUB(gtk_widget_set_mapped)
+STUB(gtk_widget_set_name)
+STUB(gtk_widget_set_parent)
+STUB(gtk_widget_set_parent_window)
+STUB(gtk_widget_set_realized)
+STUB(gtk_widget_set_redraw_on_allocate)
+STUB(gtk_widget_set_sensitive)
+STUB(gtk_widget_set_window)
+STUB(gtk_widget_show)
+STUB(gtk_widget_show_all)
+STUB(gtk_widget_size_allocate)
+STUB(gtk_widget_style_get)
+STUB(gtk_widget_unparent)
+STUB(gtk_window_deiconify)
+STUB(gtk_window_fullscreen)
+STUB(gtk_window_get_group)
+STUB(gtk_window_get_transient_for)
+STUB(gtk_window_get_type)
+STUB(gtk_window_get_type_hint)
+STUB(gtk_window_get_window_type)
+STUB(gtk_window_group_add_window)
+STUB(gtk_window_group_get_current_grab)
+STUB(gtk_window_group_new)
+STUB(gtk_window_iconify)
+STUB(gtk_window_is_active)
+STUB(gtk_window_maximize)
+STUB(gtk_window_move)
+STUB(gtk_window_new)
+STUB(gtk_window_present_with_time)
+STUB(gtk_window_resize)
+STUB(gtk_window_set_accept_focus)
+STUB(gtk_window_set_decorated)
+STUB(gtk_window_set_deletable)
+STUB(gtk_window_set_destroy_with_parent)
+STUB(gtk_window_set_geometry_hints)
+STUB(gtk_window_set_icon_name)
+STUB(gtk_window_set_modal)
+STUB(gtk_window_set_skip_taskbar_hint)
+STUB(gtk_window_set_title)
+STUB(gtk_window_set_transient_for)
+STUB(gtk_window_set_type_hint)
+STUB(gtk_window_set_wmclass)
+STUB(gtk_window_unfullscreen)
+STUB(gtk_window_unmaximize)
+#endif
+
+#ifdef GTK3_SYMBOLS
+STUB(gdk_device_get_source)
+STUB(gdk_device_manager_get_client_pointer)
+STUB(gdk_disable_multidevice)
+STUB(gdk_device_manager_list_devices)
+STUB(gdk_display_get_device_manager)
+STUB(gdk_error_trap_pop_ignored)
+STUB(gdk_event_get_source_device)
+STUB(gdk_window_get_type)
+STUB(gdk_x11_window_get_xid)
+STUB(gdk_x11_display_get_type)
+STUB(gtk_box_new)
+STUB(gtk_cairo_should_draw_window)
+STUB(gtk_cairo_transform_to_window)
+STUB(gtk_combo_box_text_append)
+STUB(gtk_drag_set_icon_surface)
+STUB(gtk_get_major_version)
+STUB(gtk_get_micro_version)
+STUB(gtk_get_minor_version)
+STUB(gtk_menu_button_new)
+STUB(gtk_offscreen_window_new)
+STUB(gtk_paned_new)
+STUB(gtk_radio_menu_item_new)
+STUB(gtk_render_activity)
+STUB(gtk_render_arrow)
+STUB(gtk_render_background)
+STUB(gtk_render_check)
+STUB(gtk_render_expander)
+STUB(gtk_render_extension)
+STUB(gtk_render_focus)
+STUB(gtk_render_frame)
+STUB(gtk_render_frame_gap)
+STUB(gtk_render_handle)
+STUB(gtk_render_line)
+STUB(gtk_render_option)
+STUB(gtk_render_slider)
+STUB(gtk_scale_new)
+STUB(gtk_scrollbar_new)
+STUB(gtk_style_context_add_class)
+STUB(gtk_style_context_add_region)
+STUB(gtk_style_context_get)
+STUB(gtk_style_context_get_background_color)
+STUB(gtk_style_context_get_border)
+STUB(gtk_style_context_get_border_color)
+STUB(gtk_style_context_get_color)
+STUB(gtk_style_context_get_direction)
+STUB(gtk_style_context_get_margin)
+STUB(gtk_style_context_get_padding)
+STUB(gtk_style_context_get_path)
+STUB(gtk_style_context_get_property)
+STUB(gtk_style_context_get_state)
+STUB(gtk_style_context_get_style)
+STUB(gtk_style_context_has_class)
+STUB(gtk_style_context_invalidate)
+STUB(gtk_style_context_list_classes)
+STUB(gtk_style_context_new)
+STUB(gtk_style_context_remove_class)
+STUB(gtk_style_context_remove_region)
+STUB(gtk_style_context_restore)
+STUB(gtk_style_context_save)
+STUB(gtk_style_context_set_direction)
+STUB(gtk_style_context_set_path)
+STUB(gtk_style_context_set_parent)
+STUB(gtk_style_context_set_state)
+STUB(gtk_style_properties_lookup_property)
+STUB(gtk_tree_view_column_get_button)
+STUB(gtk_widget_get_preferred_size)
+STUB(gtk_widget_get_state_flags)
+STUB(gtk_widget_get_style_context)
+STUB(gtk_widget_path_append_for_widget)
+STUB(gtk_widget_path_append_type)
+STUB(gtk_widget_path_copy)
+STUB(gtk_widget_path_free)
+STUB(gtk_widget_path_iter_add_class)
+STUB(gtk_widget_path_new)
+STUB(gtk_widget_path_unref)
+STUB(gtk_widget_set_visual)
+STUB(gtk_app_chooser_dialog_new_for_content_type)
+STUB(gtk_app_chooser_get_type)
+STUB(gtk_app_chooser_get_app_info)
+STUB(gtk_app_chooser_dialog_get_type)
+STUB(gtk_app_chooser_dialog_set_heading)
+STUB(gtk_color_chooser_dialog_new)
+STUB(gtk_color_chooser_dialog_get_type)
+STUB(gtk_color_chooser_get_type)
+STUB(gtk_color_chooser_set_rgba)
+STUB(gtk_color_chooser_get_rgba)
+STUB(gtk_color_chooser_set_use_alpha)
+#endif
+
+#ifdef GTK2_SYMBOLS
+STUB(gdk_drawable_get_screen)
+STUB(gdk_rgb_get_colormap)
+STUB(gdk_rgb_get_visual)
+STUB(gdk_window_lookup)
+STUB(gdk_window_set_back_pixmap)
+STUB(gdk_x11_colormap_foreign_new)
+STUB(gdk_x11_colormap_get_xcolormap)
+STUB(gdk_x11_drawable_get_xdisplay)
+STUB(gdk_x11_drawable_get_xid)
+STUB(gdk_x11_window_get_drawable_impl)
+STUB(gdkx_visual_get)
+STUB(gtk_object_get_type)
+#endif
+
+#ifndef GTK3_SYMBOLS
+// Only define the following workaround when using GTK3, which we detect
+// by checking if GTK3 stubs are not provided.
+#include <X11/Xlib.h>
+// Bug 1271100
+// We need to trick system Cairo into not using the XShm extension due to
+// a race condition in it that results in frequent BadAccess errors. Cairo
+// relies upon XShmQueryExtension to initially detect if XShm is available.
+// So we define our own stub that always indicates XShm not being present.
+// mozgtk loads before libXext/libcairo and so this stub will take priority.
+// Our tree usage goes through xcb and remains unaffected by this.
+MOZ_EXPORT Bool
+XShmQueryExtension(Display* aDisplay)
+{
+ return False;
+}
+#endif
+
diff --git a/widget/gtk/mozgtk/stub/moz.build b/widget/gtk/mozgtk/stub/moz.build
new file mode 100644
index 000000000..1a8e21001
--- /dev/null
+++ b/widget/gtk/mozgtk/stub/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# 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/.
+
+SOURCES += [
+ '../mozgtk.c',
+]
+
+for var in ('COMMON_SYMBOLS', 'GTK2_SYMBOLS', 'GTK3_SYMBOLS'):
+ DEFINES[var] = True
+
+SharedLibrary('mozgtk_stub')
+
+SONAME = 'mozgtk'
diff --git a/widget/gtk/nsAppShell.cpp b/widget/gtk/nsAppShell.cpp
new file mode 100644
index 000000000..5473dd883
--- /dev/null
+++ b/widget/gtk/nsAppShell.cpp
@@ -0,0 +1,271 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <gdk/gdk.h>
+#include "nsAppShell.h"
+#include "nsWindow.h"
+#include "mozilla/Logging.h"
+#include "prenv.h"
+#include "mozilla/HangMonitor.h"
+#include "mozilla/Unused.h"
+#include "GeckoProfiler.h"
+#include "nsIPowerManagerService.h"
+#ifdef MOZ_ENABLE_DBUS
+#include "WakeLockListener.h"
+#endif
+
+using mozilla::Unused;
+
+#define NOTIFY_TOKEN 0xFA
+
+PRLogModuleInfo *gWidgetLog = nullptr;
+PRLogModuleInfo *gWidgetFocusLog = nullptr;
+PRLogModuleInfo *gWidgetDragLog = nullptr;
+PRLogModuleInfo *gWidgetDrawLog = nullptr;
+
+static GPollFunc sPollFunc;
+
+// Wrapper function to disable hang monitoring while waiting in poll().
+static gint
+PollWrapper(GPollFD *ufds, guint nfsd, gint timeout_)
+{
+ mozilla::HangMonitor::Suspend();
+ profiler_sleep_start();
+ gint result = (*sPollFunc)(ufds, nfsd, timeout_);
+ profiler_sleep_end();
+ mozilla::HangMonitor::NotifyActivity();
+ return result;
+}
+
+#if MOZ_WIDGET_GTK == 3
+// For bug 726483.
+static decltype(GtkContainerClass::check_resize) sReal_gtk_window_check_resize;
+
+static void
+wrap_gtk_window_check_resize(GtkContainer *container)
+{
+ GdkWindow* gdk_window = gtk_widget_get_window(&container->widget);
+ if (gdk_window) {
+ g_object_ref(gdk_window);
+ }
+
+ sReal_gtk_window_check_resize(container);
+
+ if (gdk_window) {
+ g_object_unref(gdk_window);
+ }
+}
+
+// Emit resume-events on GdkFrameClock if flush-events has not been
+// balanced by resume-events at dispose.
+// For https://bugzilla.gnome.org/show_bug.cgi?id=742636
+static decltype(GObjectClass::constructed) sRealGdkFrameClockConstructed;
+static decltype(GObjectClass::dispose) sRealGdkFrameClockDispose;
+static GQuark sPendingResumeQuark;
+
+static void
+OnFlushEvents(GObject* clock, gpointer)
+{
+ g_object_set_qdata(clock, sPendingResumeQuark, GUINT_TO_POINTER(1));
+}
+
+static void
+OnResumeEvents(GObject* clock, gpointer)
+{
+ g_object_set_qdata(clock, sPendingResumeQuark, nullptr);
+}
+
+static void
+WrapGdkFrameClockConstructed(GObject* object)
+{
+ sRealGdkFrameClockConstructed(object);
+
+ g_signal_connect(object, "flush-events",
+ G_CALLBACK(OnFlushEvents), nullptr);
+ g_signal_connect(object, "resume-events",
+ G_CALLBACK(OnResumeEvents), nullptr);
+}
+
+static void
+WrapGdkFrameClockDispose(GObject* object)
+{
+ if (g_object_get_qdata(object, sPendingResumeQuark)) {
+ g_signal_emit_by_name(object, "resume-events");
+ }
+
+ sRealGdkFrameClockDispose(object);
+}
+#endif
+
+/*static*/ gboolean
+nsAppShell::EventProcessorCallback(GIOChannel *source,
+ GIOCondition condition,
+ gpointer data)
+{
+ nsAppShell *self = static_cast<nsAppShell *>(data);
+
+ unsigned char c;
+ Unused << read(self->mPipeFDs[0], &c, 1);
+ NS_ASSERTION(c == (unsigned char) NOTIFY_TOKEN, "wrong token");
+
+ self->NativeEventCallback();
+ return TRUE;
+}
+
+nsAppShell::~nsAppShell()
+{
+ if (mTag)
+ g_source_remove(mTag);
+ if (mPipeFDs[0])
+ close(mPipeFDs[0]);
+ if (mPipeFDs[1])
+ close(mPipeFDs[1]);
+}
+
+nsresult
+nsAppShell::Init()
+{
+ // For any versions of Glib before 2.36, g_type_init must be explicitly called
+ // to safely use the library. Failure to do so may cause various failures/crashes
+ // in any code that uses Glib, Gdk, or Gtk. In later versions of Glib, this call
+ // is a no-op.
+ g_type_init();
+
+ if (!gWidgetLog)
+ gWidgetLog = PR_NewLogModule("Widget");
+ if (!gWidgetFocusLog)
+ gWidgetFocusLog = PR_NewLogModule("WidgetFocus");
+ if (!gWidgetDragLog)
+ gWidgetDragLog = PR_NewLogModule("WidgetDrag");
+ if (!gWidgetDrawLog)
+ gWidgetDrawLog = PR_NewLogModule("WidgetDraw");
+
+#ifdef MOZ_ENABLE_DBUS
+ nsCOMPtr<nsIPowerManagerService> powerManagerService =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+
+ if (powerManagerService) {
+ powerManagerService->AddWakeLockListener(WakeLockListener::GetSingleton());
+ } else {
+ NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!");
+ }
+#endif
+
+ if (!sPollFunc) {
+ sPollFunc = g_main_context_get_poll_func(nullptr);
+ g_main_context_set_poll_func(nullptr, &PollWrapper);
+ }
+
+#if MOZ_WIDGET_GTK == 3
+ if (!sReal_gtk_window_check_resize &&
+ gtk_check_version(3,8,0) != nullptr) { // GTK 3.0 to GTK 3.6.
+ // GtkWindow is a static class and so will leak anyway but this ref
+ // makes sure it isn't recreated.
+ gpointer gtk_plug_class = g_type_class_ref(GTK_TYPE_WINDOW);
+ auto check_resize = &GTK_CONTAINER_CLASS(gtk_plug_class)->check_resize;
+ sReal_gtk_window_check_resize = *check_resize;
+ *check_resize = wrap_gtk_window_check_resize;
+ }
+
+ if (!sPendingResumeQuark &&
+ gtk_check_version(3,14,7) != nullptr) { // GTK 3.0 to GTK 3.14.7.
+ // GTK 3.8 - 3.14 registered this type when creating the frame clock
+ // for the root window of the display when the display was opened.
+ GType gdkFrameClockIdleType = g_type_from_name("GdkFrameClockIdle");
+ if (gdkFrameClockIdleType) { // not in versions prior to 3.8
+ sPendingResumeQuark = g_quark_from_string("moz-resume-is-pending");
+ auto gdk_frame_clock_idle_class =
+ G_OBJECT_CLASS(g_type_class_peek_static(gdkFrameClockIdleType));
+ auto constructed = &gdk_frame_clock_idle_class->constructed;
+ sRealGdkFrameClockConstructed = *constructed;
+ *constructed = WrapGdkFrameClockConstructed;
+ auto dispose = &gdk_frame_clock_idle_class->dispose;
+ sRealGdkFrameClockDispose = *dispose;
+ *dispose = WrapGdkFrameClockDispose;
+ }
+ }
+
+ // Workaround for bug 1209659 which is fixed by Gtk3.20
+ if (gtk_check_version(3, 20, 0) != nullptr)
+ unsetenv("GTK_CSD");
+#endif
+
+ if (PR_GetEnv("MOZ_DEBUG_PAINTS"))
+ gdk_window_set_debug_updates(TRUE);
+
+ // Whitelist of only common, stable formats - see bugs 1197059 and 1203078
+ GSList* pixbufFormats = gdk_pixbuf_get_formats();
+ for (GSList* iter = pixbufFormats; iter; iter = iter->next) {
+ GdkPixbufFormat* format = static_cast<GdkPixbufFormat*>(iter->data);
+ gchar* name = gdk_pixbuf_format_get_name(format);
+ if (strcmp(name, "jpeg") &&
+ strcmp(name, "png") &&
+ strcmp(name, "gif") &&
+ strcmp(name, "bmp") &&
+ strcmp(name, "ico") &&
+ strcmp(name, "xpm") &&
+ strcmp(name, "svg")) {
+ gdk_pixbuf_format_set_disabled(format, TRUE);
+ }
+ g_free(name);
+ }
+ g_slist_free(pixbufFormats);
+
+ int err = pipe(mPipeFDs);
+ if (err)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ GIOChannel *ioc;
+ GSource *source;
+
+ // make the pipe nonblocking
+
+ int flags = fcntl(mPipeFDs[0], F_GETFL, 0);
+ if (flags == -1)
+ goto failed;
+ err = fcntl(mPipeFDs[0], F_SETFL, flags | O_NONBLOCK);
+ if (err == -1)
+ goto failed;
+ flags = fcntl(mPipeFDs[1], F_GETFL, 0);
+ if (flags == -1)
+ goto failed;
+ err = fcntl(mPipeFDs[1], F_SETFL, flags | O_NONBLOCK);
+ if (err == -1)
+ goto failed;
+
+ ioc = g_io_channel_unix_new(mPipeFDs[0]);
+ source = g_io_create_watch(ioc, G_IO_IN);
+ g_io_channel_unref(ioc);
+ g_source_set_callback(source, (GSourceFunc)EventProcessorCallback, this, nullptr);
+ g_source_set_can_recurse(source, TRUE);
+ mTag = g_source_attach(source, nullptr);
+ g_source_unref(source);
+
+ return nsBaseAppShell::Init();
+failed:
+ close(mPipeFDs[0]);
+ close(mPipeFDs[1]);
+ mPipeFDs[0] = mPipeFDs[1] = 0;
+ return NS_ERROR_FAILURE;
+}
+
+void
+nsAppShell::ScheduleNativeEventCallback()
+{
+ unsigned char buf[] = { NOTIFY_TOKEN };
+ Unused << write(mPipeFDs[1], buf, 1);
+}
+
+bool
+nsAppShell::ProcessNextNativeEvent(bool mayWait)
+{
+ return g_main_context_iteration(nullptr, mayWait);
+}
diff --git a/widget/gtk/nsAppShell.h b/widget/gtk/nsAppShell.h
new file mode 100644
index 000000000..afdea5074
--- /dev/null
+++ b/widget/gtk/nsAppShell.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 nsAppShell_h__
+#define nsAppShell_h__
+
+#include <glib.h>
+#include "nsBaseAppShell.h"
+#include "nsCOMPtr.h"
+
+class nsAppShell : public nsBaseAppShell {
+public:
+ nsAppShell() : mTag(0) {
+ mPipeFDs[0] = mPipeFDs[1] = 0;
+ }
+
+ // nsBaseAppShell overrides:
+ nsresult Init();
+ virtual void ScheduleNativeEventCallback();
+ virtual bool ProcessNextNativeEvent(bool mayWait);
+
+private:
+ virtual ~nsAppShell();
+
+ static gboolean EventProcessorCallback(GIOChannel *source,
+ GIOCondition condition,
+ gpointer data);
+
+ int mPipeFDs[2];
+ unsigned mTag;
+};
+
+#endif /* nsAppShell_h__ */
diff --git a/widget/gtk/nsApplicationChooser.cpp b/widget/gtk/nsApplicationChooser.cpp
new file mode 100644
index 000000000..76c231cf6
--- /dev/null
+++ b/widget/gtk/nsApplicationChooser.cpp
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "mozilla/Types.h"
+
+#include <gtk/gtk.h>
+
+#include "nsApplicationChooser.h"
+#include "WidgetUtils.h"
+#include "nsIMIMEInfo.h"
+#include "nsIWidget.h"
+#include "nsCExternalHandlerService.h"
+#include "nsComponentManagerUtils.h"
+#include "nsGtkUtils.h"
+#include "nsPIDOMWindow.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsApplicationChooser, nsIApplicationChooser)
+
+nsApplicationChooser::nsApplicationChooser()
+{
+}
+
+nsApplicationChooser::~nsApplicationChooser()
+{
+}
+
+NS_IMETHODIMP
+nsApplicationChooser::Init(mozIDOMWindowProxy* aParent,
+ const nsACString& aTitle)
+{
+ NS_ENSURE_TRUE(aParent, NS_ERROR_FAILURE);
+ auto* parent = nsPIDOMWindowOuter::From(aParent);
+ mParentWidget = widget::WidgetUtils::DOMWindowToWidget(parent);
+ mWindowTitle.Assign(aTitle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationChooser::Open(const nsACString& aContentType, nsIApplicationChooserFinishedCallback *aCallback)
+{
+ MOZ_ASSERT(aCallback);
+ if (mCallback) {
+ NS_WARNING("Chooser is already in progress.");
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+ mCallback = aCallback;
+ NS_ENSURE_TRUE(mParentWidget, NS_ERROR_FAILURE);
+ GtkWindow *parent_widget =
+ GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET));
+
+ GtkWidget* chooser =
+ gtk_app_chooser_dialog_new_for_content_type(parent_widget,
+ (GtkDialogFlags) (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
+ PromiseFlatCString(aContentType).get());
+ gtk_app_chooser_dialog_set_heading(GTK_APP_CHOOSER_DIALOG(chooser), mWindowTitle.BeginReading());
+ NS_ADDREF_THIS();
+ g_signal_connect(chooser, "response", G_CALLBACK(OnResponse), this);
+ g_signal_connect(chooser, "destroy", G_CALLBACK(OnDestroy), this);
+ gtk_widget_show(chooser);
+ return NS_OK;
+}
+
+/* static */ void
+nsApplicationChooser::OnResponse(GtkWidget* chooser, gint response_id, gpointer user_data)
+{
+ static_cast<nsApplicationChooser*>(user_data)->Done(chooser, response_id);
+}
+
+/* static */ void
+nsApplicationChooser::OnDestroy(GtkWidget *chooser, gpointer user_data)
+{
+ static_cast<nsApplicationChooser*>(user_data)->Done(chooser, GTK_RESPONSE_CANCEL);
+}
+
+void nsApplicationChooser::Done(GtkWidget* chooser, gint response)
+{
+ nsCOMPtr<nsILocalHandlerApp> localHandler;
+ nsresult rv;
+ switch (response) {
+ case GTK_RESPONSE_OK:
+ case GTK_RESPONSE_ACCEPT:
+ {
+ localHandler = do_CreateInstance(NS_LOCALHANDLERAPP_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Out of memory.");
+ break;
+ }
+ GAppInfo *app_info = gtk_app_chooser_get_app_info(GTK_APP_CHOOSER(chooser));
+
+ nsCOMPtr<nsIFile> localExecutable;
+ gchar *fileWithFullPath = g_find_program_in_path(g_app_info_get_executable(app_info));
+ rv = NS_NewNativeLocalFile(nsDependentCString(fileWithFullPath), false, getter_AddRefs(localExecutable));
+ g_free(fileWithFullPath);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Cannot create local filename.");
+ localHandler = nullptr;
+ } else {
+ localHandler->SetExecutable(localExecutable);
+ localHandler->SetName(NS_ConvertUTF8toUTF16(g_app_info_get_display_name(app_info)));
+ }
+ g_object_unref(app_info);
+ }
+
+ break;
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_CLOSE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ break;
+ default:
+ NS_WARNING("Unexpected response");
+ break;
+ }
+
+ // A "response" signal won't be sent again but "destroy" will be.
+ g_signal_handlers_disconnect_by_func(chooser, FuncToGpointer(OnDestroy), this);
+ gtk_widget_destroy(chooser);
+
+ if (mCallback) {
+ mCallback->Done(localHandler);
+ mCallback = nullptr;
+ }
+ NS_RELEASE_THIS();
+}
+
diff --git a/widget/gtk/nsApplicationChooser.h b/widget/gtk/nsApplicationChooser.h
new file mode 100644
index 000000000..da16dac71
--- /dev/null
+++ b/widget/gtk/nsApplicationChooser.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 nsApplicationChooser_h__
+#define nsApplicationChooser_h__
+
+#include <gtk/gtk.h>
+#include "nsCOMPtr.h"
+#include "nsIApplicationChooser.h"
+#include "nsString.h"
+
+class nsIWidget;
+
+class nsApplicationChooser final : public nsIApplicationChooser
+{
+public:
+ nsApplicationChooser();
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAPPLICATIONCHOOSER
+ void Done(GtkWidget* chooser, gint response);
+
+private:
+ ~nsApplicationChooser();
+ nsCOMPtr<nsIWidget> mParentWidget;
+ nsCString mWindowTitle;
+ nsCOMPtr<nsIApplicationChooserFinishedCallback> mCallback;
+ static void OnResponse(GtkWidget* chooser, gint response_id, gpointer user_data);
+ static void OnDestroy(GtkWidget* chooser, gpointer user_data);
+};
+#endif
diff --git a/widget/gtk/nsBidiKeyboard.cpp b/widget/gtk/nsBidiKeyboard.cpp
new file mode 100644
index 000000000..51102d945
--- /dev/null
+++ b/widget/gtk/nsBidiKeyboard.cpp
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "prlink.h"
+
+#include "nsBidiKeyboard.h"
+#include <gtk/gtk.h>
+
+NS_IMPL_ISUPPORTS(nsBidiKeyboard, nsIBidiKeyboard)
+
+nsBidiKeyboard::nsBidiKeyboard()
+{
+ Reset();
+}
+
+NS_IMETHODIMP
+nsBidiKeyboard::Reset()
+{
+ // NB: The default keymap can be null (e.g. in xpcshell). In that case,
+ // simply assume that we don't have bidi keyboards.
+ mHaveBidiKeyboards = false;
+
+ GdkDisplay *display = gdk_display_get_default();
+ if (!display)
+ return NS_OK;
+
+ GdkKeymap *keymap = gdk_keymap_get_for_display(display);
+ mHaveBidiKeyboards = keymap && gdk_keymap_have_bidi_layouts(keymap);
+ return NS_OK;
+}
+
+nsBidiKeyboard::~nsBidiKeyboard()
+{
+}
+
+NS_IMETHODIMP
+nsBidiKeyboard::IsLangRTL(bool *aIsRTL)
+{
+ if (!mHaveBidiKeyboards)
+ return NS_ERROR_FAILURE;
+
+ *aIsRTL = (gdk_keymap_get_direction(gdk_keymap_get_default()) == PANGO_DIRECTION_RTL);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBidiKeyboard::GetHaveBidiKeyboards(bool* aResult)
+{
+ // not implemented yet
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/widget/gtk/nsBidiKeyboard.h b/widget/gtk/nsBidiKeyboard.h
new file mode 100644
index 000000000..08c02f354
--- /dev/null
+++ b/widget/gtk/nsBidiKeyboard.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 __nsBidiKeyboard
+#define __nsBidiKeyboard
+#include "nsIBidiKeyboard.h"
+
+class nsBidiKeyboard : public nsIBidiKeyboard
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIBIDIKEYBOARD
+
+ nsBidiKeyboard();
+
+protected:
+ virtual ~nsBidiKeyboard();
+
+ bool mHaveBidiKeyboards;
+};
+
+#endif // __nsBidiKeyboard
diff --git a/widget/gtk/nsCUPSShim.cpp b/widget/gtk/nsCUPSShim.cpp
new file mode 100644
index 000000000..d05da307f
--- /dev/null
+++ b/widget/gtk/nsCUPSShim.cpp
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ex: set tabstop=8 softtabstop=4 shiftwidth=4 expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "nsDebug.h"
+#include "nsString.h"
+#include "nsCUPSShim.h"
+#include "mozilla/ArrayUtils.h"
+#include "prlink.h"
+
+
+// List of symbols to find in libcups. Must match symAddr[] defined in Init().
+// Making this an array of arrays instead of pointers allows storing the
+// whole thing in read-only memory.
+static const char gSymName[][sizeof("cupsPrintFile")] = {
+ { "cupsAddOption" },
+ { "cupsFreeDests" },
+ { "cupsGetDest" },
+ { "cupsGetDests" },
+ { "cupsPrintFile" },
+ { "cupsTempFd" },
+};
+static const int gSymNameCt = mozilla::ArrayLength(gSymName);
+
+
+bool
+nsCUPSShim::Init()
+{
+ mCupsLib = PR_LoadLibrary("libcups.so.2");
+ if (!mCupsLib)
+ return false;
+
+ // List of symbol pointers. Must match gSymName[] defined above.
+ void **symAddr[] = {
+ (void **)&mCupsAddOption,
+ (void **)&mCupsFreeDests,
+ (void **)&mCupsGetDest,
+ (void **)&mCupsGetDests,
+ (void **)&mCupsPrintFile,
+ (void **)&mCupsTempFd,
+ };
+
+ for (int i = gSymNameCt; i--; ) {
+ *(symAddr[i]) = PR_FindSymbol(mCupsLib, gSymName[i]);
+ if (! *(symAddr[i])) {
+#ifdef DEBUG
+ nsAutoCString msg(gSymName[i]);
+ msg.AppendLiteral(" not found in CUPS library");
+ NS_WARNING(msg.get());
+#endif
+ PR_UnloadLibrary(mCupsLib);
+ mCupsLib = nullptr;
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/widget/gtk/nsCUPSShim.h b/widget/gtk/nsCUPSShim.h
new file mode 100644
index 000000000..3e7d96f3a
--- /dev/null
+++ b/widget/gtk/nsCUPSShim.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ex: set tabstop=8 softtabstop=4 shiftwidth=4 expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 nsCUPSShim_h___
+#define nsCUPSShim_h___
+
+
+/* Various CUPS data types. We don't #include cups headers to avoid
+ * requiring CUPS to be installed on the build host (and to avoid having
+ * to test for CUPS in configure).
+ */
+typedef struct /**** Printer Options ****/
+{
+ char *name; /* Name of option */
+ char *value; /* Value of option */
+} cups_option_t;
+
+typedef struct /**** Destination ****/
+{
+ char *name, /* Printer or class name */
+ *instance; /* Local instance name or nullptr */
+ int is_default; /* Is this printer the default? */
+ int num_options; /* Number of options */
+ cups_option_t *options; /* Options */
+} cups_dest_t;
+
+typedef cups_dest_t* (*CupsGetDestType)(const char *printer,
+ const char *instance,
+ int num_dests,
+ cups_dest_t *dests);
+typedef int (*CupsGetDestsType)(cups_dest_t **dests);
+typedef int (*CupsFreeDestsType)(int num_dests,
+ cups_dest_t *dests);
+typedef int (*CupsPrintFileType)(const char *printer,
+ const char *filename,
+ const char *title,
+ int num_options,
+ cups_option_t *options);
+typedef int (*CupsTempFdType)(char *filename,
+ int length);
+typedef int (*CupsAddOptionType)(const char *name,
+ const char *value,
+ int num_options,
+ cups_option_t **options);
+
+struct PRLibrary;
+
+/* Note: this class relies on static initialization. */
+class nsCUPSShim {
+ public:
+ /**
+ * Initialize this object. Attempt to load the CUPS shared
+ * library and find function pointers for the supported
+ * functions (see below).
+ * @return false if the shared library could not be loaded, or if
+ * any of the functions could not be found.
+ * true for successful initialization.
+ */
+ bool Init();
+
+ /**
+ * @return true if the object was initialized successfully.
+ * false otherwise.
+ */
+ bool IsInitialized() { return nullptr != mCupsLib; }
+
+ /* Function pointers for supported functions. These are only
+ * valid after successful initialization.
+ */
+ CupsAddOptionType mCupsAddOption;
+ CupsFreeDestsType mCupsFreeDests;
+ CupsGetDestType mCupsGetDest;
+ CupsGetDestsType mCupsGetDests;
+ CupsPrintFileType mCupsPrintFile;
+ CupsTempFdType mCupsTempFd;
+
+ private:
+ PRLibrary *mCupsLib;
+};
+
+
+
+#endif /* nsCUPSShim_h___ */
diff --git a/widget/gtk/nsClipboard.cpp b/widget/gtk/nsClipboard.cpp
new file mode 100644
index 000000000..053ae970e
--- /dev/null
+++ b/widget/gtk/nsClipboard.cpp
@@ -0,0 +1,1046 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsArrayUtils.h"
+#include "nsClipboard.h"
+#include "nsSupportsPrimitives.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsXPIDLString.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsIServiceManager.h"
+#include "nsImageToPixbuf.h"
+#include "nsStringStream.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+
+#include "imgIContainer.h"
+
+#include <gtk/gtk.h>
+
+// For manipulation of the X event queue
+#include <X11/Xlib.h>
+#include <gdk/gdkx.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <unistd.h>
+#include "X11UndefineNone.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+#include "nsIUnicodeDecoder.h"
+
+using mozilla::dom::EncodingUtils;
+using namespace mozilla;
+
+// Callback when someone asks us for the data
+void
+clipboard_get_cb(GtkClipboard *aGtkClipboard,
+ GtkSelectionData *aSelectionData,
+ guint info,
+ gpointer user_data);
+
+// Callback when someone asks us to clear a clipboard
+void
+clipboard_clear_cb(GtkClipboard *aGtkClipboard,
+ gpointer user_data);
+
+static void
+ConvertHTMLtoUCS2 (guchar *data,
+ int32_t dataLength,
+ char16_t **unicodeData,
+ int32_t &outUnicodeLen);
+
+static void
+GetHTMLCharset (guchar * data, int32_t dataLength, nsCString& str);
+
+
+// Our own versions of gtk_clipboard_wait_for_contents and
+// gtk_clipboard_wait_for_text, which don't run the event loop while
+// waiting for the data. This prevents a lot of problems related to
+// dispatching events at unexpected times.
+
+static GtkSelectionData *
+wait_for_contents (GtkClipboard *clipboard, GdkAtom target);
+
+static gchar *
+wait_for_text (GtkClipboard *clipboard);
+
+static GdkFilterReturn
+selection_request_filter (GdkXEvent *gdk_xevent,
+ GdkEvent *event,
+ gpointer data);
+
+nsClipboard::nsClipboard()
+{
+}
+
+nsClipboard::~nsClipboard()
+{
+ // We have to clear clipboard before gdk_display_close() call.
+ // See bug 531580 for details.
+ if (mGlobalTransferable) {
+ gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
+ }
+ if (mSelectionTransferable) {
+ gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
+
+nsresult
+nsClipboard::Init(void)
+{
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (!os)
+ return NS_ERROR_FAILURE;
+
+ os->AddObserver(this, "quit-application", false);
+
+ // A custom event filter to workaround attempting to dereference a null
+ // selection requestor in GTK3 versions before 3.11.3. See bug 1178799.
+#if (MOZ_WIDGET_GTK == 3) && defined(MOZ_X11)
+ if (gtk_check_version(3, 11, 3))
+ gdk_window_add_filter(nullptr, selection_request_filter, nullptr);
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
+{
+ if (strcmp(aTopic, "quit-application") == 0) {
+ // application is going to quit, save clipboard content
+ Store();
+ gdk_window_remove_filter(nullptr, selection_request_filter, nullptr);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsClipboard::Store(void)
+{
+ // Ask the clipboard manager to store the current clipboard content
+ if (mGlobalTransferable) {
+ GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+ gtk_clipboard_store(clipboard);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SetData(nsITransferable *aTransferable,
+ nsIClipboardOwner *aOwner, int32_t aWhichClipboard)
+{
+ // See if we can short cut
+ if ((aWhichClipboard == kGlobalClipboard &&
+ aTransferable == mGlobalTransferable.get() &&
+ aOwner == mGlobalOwner.get()) ||
+ (aWhichClipboard == kSelectionClipboard &&
+ aTransferable == mSelectionTransferable.get() &&
+ aOwner == mSelectionOwner.get())) {
+ return NS_OK;
+ }
+
+ // Clear out the clipboard in order to set the new data
+ EmptyClipboard(aWhichClipboard);
+
+ // List of suported targets
+ GtkTargetList *list = gtk_target_list_new(nullptr, 0);
+
+ // Get the types of supported flavors
+ nsCOMPtr<nsIArray> flavors;
+
+ nsresult rv =
+ aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavors));
+ if (!flavors || NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ // Add all the flavors to this widget's supported type.
+ bool imagesAdded = false;
+ uint32_t count;
+ flavors->GetLength(&count);
+ for (uint32_t i=0; i < count; i++) {
+ nsCOMPtr<nsISupportsCString> flavor = do_QueryElementAt(flavors, i);
+
+ if (flavor) {
+ nsXPIDLCString flavorStr;
+ flavor->ToString(getter_Copies(flavorStr));
+
+ // special case text/unicode since we can handle all of
+ // the string types
+ if (!strcmp(flavorStr, kUnicodeMime)) {
+ gtk_target_list_add(list, gdk_atom_intern("UTF8_STRING", FALSE), 0, 0);
+ gtk_target_list_add(list, gdk_atom_intern("COMPOUND_TEXT", FALSE), 0, 0);
+ gtk_target_list_add(list, gdk_atom_intern("TEXT", FALSE), 0, 0);
+ gtk_target_list_add(list, GDK_SELECTION_TYPE_STRING, 0, 0);
+ continue;
+ }
+
+ if (flavorStr.EqualsLiteral(kNativeImageMime) ||
+ flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) ||
+ flavorStr.EqualsLiteral(kGIFImageMime)) {
+ // don't bother adding image targets twice
+ if (!imagesAdded) {
+ // accept any writable image type
+ gtk_target_list_add_image_targets(list, 0, TRUE);
+ imagesAdded = true;
+ }
+ continue;
+ }
+
+ // Add this to our list of valid targets
+ GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
+ gtk_target_list_add(list, atom, 0, 0);
+ }
+ }
+
+ // Get GTK clipboard (CLIPBOARD or PRIMARY)
+ GtkClipboard *gtkClipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+
+ gint numTargets;
+ GtkTargetEntry *gtkTargets = gtk_target_table_new_from_list(list, &numTargets);
+
+ // Set getcallback and request to store data after an application exit
+ if (gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets,
+ clipboard_get_cb, clipboard_clear_cb, this))
+ {
+ // We managed to set-up the clipboard so update internal state
+ // We have to set it now because gtk_clipboard_set_with_data() calls clipboard_clear_cb()
+ // which reset our internal state
+ if (aWhichClipboard == kSelectionClipboard) {
+ mSelectionOwner = aOwner;
+ mSelectionTransferable = aTransferable;
+ }
+ else {
+ mGlobalOwner = aOwner;
+ mGlobalTransferable = aTransferable;
+ gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
+ }
+
+ rv = NS_OK;
+ }
+ else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ gtk_target_table_free(gtkTargets, numTargets);
+ gtk_target_list_unref(list);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsClipboard::GetData(nsITransferable *aTransferable, int32_t aWhichClipboard)
+{
+ if (!aTransferable)
+ return NS_ERROR_FAILURE;
+
+ GtkClipboard *clipboard;
+ clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+
+ guchar *data = nullptr;
+ gint length = 0;
+ bool foundData = false;
+ nsAutoCString foundFlavor;
+
+ // Get a list of flavors this transferable can import
+ nsCOMPtr<nsIArray> flavors;
+ nsresult rv;
+ rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavors));
+ if (!flavors || NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ uint32_t count;
+ flavors->GetLength(&count);
+ for (uint32_t i=0; i < count; i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavors, i);
+
+ if (currentFlavor) {
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ // Special case text/unicode since we can convert any
+ // string into text/unicode
+ if (!strcmp(flavorStr, kUnicodeMime)) {
+ gchar* new_text = wait_for_text(clipboard);
+ if (new_text) {
+ // Convert utf-8 into our unicode format.
+ NS_ConvertUTF8toUTF16 ucs2string(new_text);
+ data = (guchar *)ToNewUnicode(ucs2string);
+ length = ucs2string.Length() * 2;
+ g_free(new_text);
+ foundData = true;
+ foundFlavor = kUnicodeMime;
+ break;
+ }
+ // If the type was text/unicode and we couldn't get
+ // text off the clipboard, run the next loop
+ // iteration.
+ continue;
+ }
+
+ // For images, we must wrap the data in an nsIInputStream then return instead of break,
+ // because that code below won't help us.
+ if (!strcmp(flavorStr, kJPEGImageMime) ||
+ !strcmp(flavorStr, kJPGImageMime) ||
+ !strcmp(flavorStr, kPNGImageMime) ||
+ !strcmp(flavorStr, kGIFImageMime)) {
+ // Emulate support for image/jpg
+ if (!strcmp(flavorStr, kJPGImageMime)) {
+ flavorStr.Assign(kJPEGImageMime);
+ }
+
+ GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
+
+ GtkSelectionData *selectionData = wait_for_contents(clipboard, atom);
+ if (!selectionData)
+ continue;
+
+ nsCOMPtr<nsIInputStream> byteStream;
+ NS_NewByteInputStream(getter_AddRefs(byteStream),
+ (const char*)gtk_selection_data_get_data(selectionData),
+ gtk_selection_data_get_length(selectionData),
+ NS_ASSIGNMENT_COPY);
+ aTransferable->SetTransferData(flavorStr, byteStream, sizeof(nsIInputStream*));
+ gtk_selection_data_free(selectionData);
+ return NS_OK;
+ }
+
+ // Get the atom for this type and try to request it off
+ // the clipboard.
+ GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
+ GtkSelectionData *selectionData;
+ selectionData = wait_for_contents(clipboard, atom);
+ if (selectionData) {
+ const guchar *clipboardData = gtk_selection_data_get_data(selectionData);
+ length = gtk_selection_data_get_length(selectionData);
+ // Special case text/html since we can convert into UCS2
+ if (!strcmp(flavorStr, kHTMLMime)) {
+ char16_t* htmlBody= nullptr;
+ int32_t htmlBodyLen = 0;
+ // Convert text/html into our unicode format
+ ConvertHTMLtoUCS2(const_cast<guchar*>(clipboardData), length,
+ &htmlBody, htmlBodyLen);
+ // Try next data format?
+ if (!htmlBodyLen)
+ continue;
+ data = (guchar *)htmlBody;
+ length = htmlBodyLen * 2;
+ } else {
+ data = (guchar *)moz_xmalloc(length);
+ if (!data)
+ break;
+ memcpy(data, clipboardData, length);
+ }
+ gtk_selection_data_free(selectionData);
+ foundData = true;
+ foundFlavor = flavorStr;
+ break;
+ }
+ }
+ }
+
+ if (foundData) {
+ nsCOMPtr<nsISupports> wrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(foundFlavor.get(),
+ data, length,
+ getter_AddRefs(wrapper));
+ aTransferable->SetTransferData(foundFlavor.get(),
+ wrapper, length);
+ }
+
+ if (data)
+ free(data);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::EmptyClipboard(int32_t aWhichClipboard)
+{
+ if (aWhichClipboard == kSelectionClipboard) {
+ if (mSelectionOwner) {
+ mSelectionOwner->LosingOwnership(mSelectionTransferable);
+ mSelectionOwner = nullptr;
+ }
+ mSelectionTransferable = nullptr;
+ }
+ else {
+ if (mGlobalOwner) {
+ mGlobalOwner->LosingOwnership(mGlobalTransferable);
+ mGlobalOwner = nullptr;
+ }
+ mGlobalTransferable = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
+ int32_t aWhichClipboard, bool *_retval)
+{
+ if (!aFlavorList || !_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *_retval = false;
+
+ GtkSelectionData *selection_data =
+ GetTargets(GetSelectionAtom(aWhichClipboard));
+ if (!selection_data)
+ return NS_OK;
+
+ gint n_targets = 0;
+ GdkAtom *targets = nullptr;
+
+ if (!gtk_selection_data_get_targets(selection_data,
+ &targets, &n_targets) ||
+ !n_targets)
+ return NS_OK;
+
+ // Walk through the provided types and try to match it to a
+ // provided type.
+ for (uint32_t i = 0; i < aLength && !*_retval; i++) {
+ // We special case text/unicode here.
+ if (!strcmp(aFlavorList[i], kUnicodeMime) &&
+ gtk_selection_data_targets_include_text(selection_data)) {
+ *_retval = true;
+ break;
+ }
+
+ for (int32_t j = 0; j < n_targets; j++) {
+ gchar *atom_name = gdk_atom_name(targets[j]);
+ if (!atom_name)
+ continue;
+
+ if (!strcmp(atom_name, aFlavorList[i]))
+ *_retval = true;
+
+ // X clipboard supports image/jpeg, but we want to emulate support
+ // for image/jpg as well
+ if (!strcmp(aFlavorList[i], kJPGImageMime) && !strcmp(atom_name, kJPEGImageMime))
+ *_retval = true;
+
+ g_free(atom_name);
+
+ if (*_retval)
+ break;
+ }
+ }
+ gtk_selection_data_free(selection_data);
+ g_free(targets);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsSelectionClipboard(bool *_retval)
+{
+ *_retval = true; // yeah, unix supports the selection clipboard
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsFindClipboard(bool* _retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+/* static */
+GdkAtom
+nsClipboard::GetSelectionAtom(int32_t aWhichClipboard)
+{
+ if (aWhichClipboard == kGlobalClipboard)
+ return GDK_SELECTION_CLIPBOARD;
+
+ return GDK_SELECTION_PRIMARY;
+}
+
+/* static */
+GtkSelectionData *
+nsClipboard::GetTargets(GdkAtom aWhichClipboard)
+{
+ GtkClipboard *clipboard = gtk_clipboard_get(aWhichClipboard);
+ return wait_for_contents(clipboard, gdk_atom_intern("TARGETS", FALSE));
+}
+
+nsITransferable *
+nsClipboard::GetTransferable(int32_t aWhichClipboard)
+{
+ nsITransferable *retval;
+
+ if (aWhichClipboard == kSelectionClipboard)
+ retval = mSelectionTransferable.get();
+ else
+ retval = mGlobalTransferable.get();
+
+ return retval;
+}
+
+void
+nsClipboard::SelectionGetEvent(GtkClipboard *aClipboard,
+ GtkSelectionData *aSelectionData)
+{
+ // Someone has asked us to hand them something. The first thing
+ // that we want to do is see if that something includes text. If
+ // it does, try to give it text/unicode after converting it to
+ // utf-8.
+
+ int32_t whichClipboard;
+
+ // which clipboard?
+ GdkAtom selection = gtk_selection_data_get_selection(aSelectionData);
+ if (selection == GDK_SELECTION_PRIMARY)
+ whichClipboard = kSelectionClipboard;
+ else if (selection == GDK_SELECTION_CLIPBOARD)
+ whichClipboard = kGlobalClipboard;
+ else
+ return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
+
+ nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
+ if (!trans) {
+ // We have nothing to serve
+#ifdef DEBUG_CLIPBOARD
+ printf("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
+ whichClipboard == kSelectionClipboard ? "Selection" : "Global");
+#endif
+ return;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> item;
+ uint32_t len;
+
+ GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
+
+ // Check to see if the selection data includes any of the string
+ // types that we support.
+ if (selectionTarget == gdk_atom_intern ("STRING", FALSE) ||
+ selectionTarget == gdk_atom_intern ("TEXT", FALSE) ||
+ selectionTarget == gdk_atom_intern ("COMPOUND_TEXT", FALSE) ||
+ selectionTarget == gdk_atom_intern ("UTF8_STRING", FALSE)) {
+ // Try to convert our internal type into a text string. Get
+ // the transferable for this clipboard and try to get the
+ // text/unicode type for it.
+ rv = trans->GetTransferData("text/unicode", getter_AddRefs(item),
+ &len);
+ if (!item || NS_FAILED(rv))
+ return;
+
+ nsCOMPtr<nsISupportsString> wideString;
+ wideString = do_QueryInterface(item);
+ if (!wideString)
+ return;
+
+ nsAutoString ucs2string;
+ wideString->GetData(ucs2string);
+ char *utf8string = ToNewUTF8String(ucs2string);
+ if (!utf8string)
+ return;
+
+ gtk_selection_data_set_text (aSelectionData, utf8string,
+ strlen(utf8string));
+
+ free(utf8string);
+ return;
+ }
+
+ // Check to see if the selection data is an image type
+ if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
+ // Look through our transfer data for the image
+ static const char* const imageMimeTypes[] = {
+ kNativeImageMime, kPNGImageMime, kJPEGImageMime, kJPGImageMime, kGIFImageMime };
+ nsCOMPtr<nsISupports> imageItem;
+ nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive;
+ for (uint32_t i = 0; !ptrPrimitive && i < ArrayLength(imageMimeTypes); i++) {
+ rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(imageItem), &len);
+ ptrPrimitive = do_QueryInterface(imageItem);
+ }
+ if (!ptrPrimitive)
+ return;
+
+ nsCOMPtr<nsISupports> primitiveData;
+ ptrPrimitive->GetData(getter_AddRefs(primitiveData));
+ nsCOMPtr<imgIContainer> image(do_QueryInterface(primitiveData));
+ if (!image) // Not getting an image for an image mime type!?
+ return;
+
+ GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
+ if (!pixbuf)
+ return;
+
+ gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
+ g_object_unref(pixbuf);
+ return;
+ }
+
+ // Try to match up the selection data target to something our
+ // transferable provides.
+ gchar *target_name = gdk_atom_name(selectionTarget);
+ if (!target_name)
+ return;
+
+ rv = trans->GetTransferData(target_name, getter_AddRefs(item), &len);
+ // nothing found?
+ if (!item || NS_FAILED(rv)) {
+ g_free(target_name);
+ return;
+ }
+
+ void *primitive_data = nullptr;
+ nsPrimitiveHelpers::CreateDataFromPrimitive(target_name, item,
+ &primitive_data, len);
+
+ if (primitive_data) {
+ // Check to see if the selection data is text/html
+ if (selectionTarget == gdk_atom_intern (kHTMLMime, FALSE)) {
+ /*
+ * "text/html" can be encoded UCS2. It is recommended that
+ * documents transmitted as UCS2 always begin with a ZERO-WIDTH
+ * NON-BREAKING SPACE character (hexadecimal FEFF, also called
+ * Byte Order Mark (BOM)). Adding BOM can help other app to
+ * detect mozilla use UCS2 encoding when copy-paste.
+ */
+ guchar *buffer = (guchar *)
+ moz_xmalloc((len * sizeof(guchar)) + sizeof(char16_t));
+ if (!buffer)
+ return;
+ char16_t prefix = 0xFEFF;
+ memcpy(buffer, &prefix, sizeof(prefix));
+ memcpy(buffer + sizeof(prefix), primitive_data, len);
+ free((guchar *)primitive_data);
+ primitive_data = (guchar *)buffer;
+ len += sizeof(prefix);
+ }
+
+ gtk_selection_data_set(aSelectionData, selectionTarget,
+ 8, /* 8 bits in a unit */
+ (const guchar *)primitive_data, len);
+ free(primitive_data);
+ }
+
+ g_free(target_name);
+
+}
+
+void
+nsClipboard::SelectionClearEvent(GtkClipboard *aGtkClipboard)
+{
+ int32_t whichClipboard;
+
+ // which clipboard?
+ if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY))
+ whichClipboard = kSelectionClipboard;
+ else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
+ whichClipboard = kGlobalClipboard;
+ else
+ return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
+
+ EmptyClipboard(whichClipboard);
+}
+
+void
+clipboard_get_cb(GtkClipboard *aGtkClipboard,
+ GtkSelectionData *aSelectionData,
+ guint info,
+ gpointer user_data)
+{
+ nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data);
+ aClipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
+}
+
+void
+clipboard_clear_cb(GtkClipboard *aGtkClipboard,
+ gpointer user_data)
+{
+ nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data);
+ aClipboard->SelectionClearEvent(aGtkClipboard);
+}
+
+/*
+ * when copy-paste, mozilla wants data encoded using UCS2,
+ * other app such as StarOffice use "text/html"(RFC2854).
+ * This function convert data(got from GTK clipboard)
+ * to data mozilla wanted.
+ *
+ * data from GTK clipboard can be 3 forms:
+ * 1. From current mozilla
+ * "text/html", charset = utf-16
+ * 2. From old version mozilla or mozilla-based app
+ * content("body" only), charset = utf-16
+ * 3. From other app who use "text/html" when copy-paste
+ * "text/html", has "charset" info
+ *
+ * data : got from GTK clipboard
+ * dataLength: got from GTK clipboard
+ * body : pass to Mozilla
+ * bodyLength: pass to Mozilla
+ */
+void ConvertHTMLtoUCS2(guchar * data, int32_t dataLength,
+ char16_t** unicodeData, int32_t& outUnicodeLen)
+{
+ nsAutoCString charset;
+ GetHTMLCharset(data, dataLength, charset);// get charset of HTML
+ if (charset.EqualsLiteral("UTF-16")) {//current mozilla
+ outUnicodeLen = (dataLength / 2) - 1;
+ *unicodeData = reinterpret_cast<char16_t*>
+ (moz_xmalloc((outUnicodeLen + sizeof('\0')) *
+ sizeof(char16_t)));
+ if (*unicodeData) {
+ memcpy(*unicodeData, data + sizeof(char16_t),
+ outUnicodeLen * sizeof(char16_t));
+ (*unicodeData)[outUnicodeLen] = '\0';
+ }
+ } else if (charset.EqualsLiteral("UNKNOWN")) {
+ outUnicodeLen = 0;
+ return;
+ } else {
+ // app which use "text/html" to copy&paste
+ nsCOMPtr<nsIUnicodeDecoder> decoder;
+ // get the decoder
+ nsAutoCString encoding;
+ if (!EncodingUtils::FindEncodingForLabelNoReplacement(charset,
+ encoding)) {
+#ifdef DEBUG_CLIPBOARD
+ g_print(" get unicode decoder error\n");
+#endif
+ outUnicodeLen = 0;
+ return;
+ }
+ decoder = EncodingUtils::DecoderForEncoding(encoding);
+ // converting
+ nsresult rv = decoder->GetMaxLength((const char *)data, dataLength,
+ &outUnicodeLen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ outUnicodeLen = 0;
+ return;
+ }
+
+ // |outUnicodeLen| is number of chars
+ if (outUnicodeLen) {
+ *unicodeData = reinterpret_cast<char16_t*>
+ (moz_xmalloc((outUnicodeLen + sizeof('\0')) *
+ sizeof(char16_t)));
+ if (*unicodeData) {
+ int32_t numberTmp = dataLength;
+ decoder->Convert((const char *)data, &numberTmp,
+ *unicodeData, &outUnicodeLen);
+#ifdef DEBUG_CLIPBOARD
+ if (numberTmp != dataLength)
+ printf("didn't consume all the bytes\n");
+#endif
+ // null terminate. Convert() doesn't do it for us
+ (*unicodeData)[outUnicodeLen] = '\0';
+ }
+ } // if valid length
+ }
+}
+
+/*
+ * get "charset" information from clipboard data
+ * return value can be:
+ * 1. "UTF-16": mozilla or "text/html" with "charset=utf-16"
+ * 2. "UNKNOWN": mozilla can't detect what encode it use
+ * 3. other: "text/html" with other charset than utf-16
+ */
+void GetHTMLCharset(guchar * data, int32_t dataLength, nsCString& str)
+{
+ // if detect "FFFE" or "FEFF", assume UTF-16
+ char16_t* beginChar = (char16_t*)data;
+ if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) {
+ str.AssignLiteral("UTF-16");
+ return;
+ }
+ // no "FFFE" and "FEFF", assume ASCII first to find "charset" info
+ const nsDependentCString htmlStr((const char *)data, dataLength);
+ nsACString::const_iterator start, end;
+ htmlStr.BeginReading(start);
+ htmlStr.EndReading(end);
+ nsACString::const_iterator valueStart(start), valueEnd(start);
+
+ if (CaseInsensitiveFindInReadable(
+ NS_LITERAL_CSTRING("CONTENT=\"text/html;"),
+ start, end)) {
+ start = end;
+ htmlStr.EndReading(end);
+
+ if (CaseInsensitiveFindInReadable(
+ NS_LITERAL_CSTRING("charset="),
+ start, end)) {
+ valueStart = end;
+ start = end;
+ htmlStr.EndReading(end);
+
+ if (FindCharInReadable('"', start, end))
+ valueEnd = start;
+ }
+ }
+ // find "charset" in HTML
+ if (valueStart != valueEnd) {
+ str = Substring(valueStart, valueEnd);
+ ToUpperCase(str);
+#ifdef DEBUG_CLIPBOARD
+ printf("Charset of HTML = %s\n", charsetUpperStr.get());
+#endif
+ return;
+ }
+ str.AssignLiteral("UNKNOWN");
+}
+
+static void
+DispatchSelectionNotifyEvent(GtkWidget *widget, XEvent *xevent)
+{
+ GdkEvent event;
+ event.selection.type = GDK_SELECTION_NOTIFY;
+ event.selection.window = gtk_widget_get_window(widget);
+ event.selection.selection = gdk_x11_xatom_to_atom(xevent->xselection.selection);
+ event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target);
+ event.selection.property = gdk_x11_xatom_to_atom(xevent->xselection.property);
+ event.selection.time = xevent->xselection.time;
+
+ gtk_widget_event(widget, &event);
+}
+
+static void
+DispatchPropertyNotifyEvent(GtkWidget *widget, XEvent *xevent)
+{
+ GdkWindow *window = gtk_widget_get_window(widget);
+ if ((gdk_window_get_events(window)) & GDK_PROPERTY_CHANGE_MASK) {
+ GdkEvent event;
+ event.property.type = GDK_PROPERTY_NOTIFY;
+ event.property.window = window;
+ event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom);
+ event.property.time = xevent->xproperty.time;
+ event.property.state = xevent->xproperty.state;
+
+ gtk_widget_event(widget, &event);
+ }
+}
+
+struct checkEventContext
+{
+ GtkWidget *cbWidget;
+ Atom selAtom;
+};
+
+static Bool
+checkEventProc(Display *display, XEvent *event, XPointer arg)
+{
+ checkEventContext *context = (checkEventContext *) arg;
+
+ if (event->xany.type == SelectionNotify ||
+ (event->xany.type == PropertyNotify &&
+ event->xproperty.atom == context->selAtom)) {
+
+ GdkWindow *cbWindow =
+ gdk_x11_window_lookup_for_display(gdk_x11_lookup_xdisplay(display),
+ event->xany.window);
+ if (cbWindow) {
+ GtkWidget *cbWidget = nullptr;
+ gdk_window_get_user_data(cbWindow, (gpointer *)&cbWidget);
+ if (cbWidget && GTK_IS_WIDGET(cbWidget)) {
+ context->cbWidget = cbWidget;
+ return True;
+ }
+ }
+ }
+
+ return False;
+}
+
+// Idle timeout for receiving selection and property notify events (microsec)
+static const int kClipboardTimeout = 500000;
+
+static gchar* CopyRetrievedData(const gchar *aData)
+{
+ return g_strdup(aData);
+}
+
+static GtkSelectionData* CopyRetrievedData(GtkSelectionData *aData)
+{
+ // A negative length indicates that retrieving the data failed.
+ return gtk_selection_data_get_length(aData) >= 0 ?
+ gtk_selection_data_copy(aData) : nullptr;
+}
+
+class RetrievalContext {
+ ~RetrievalContext()
+ {
+ MOZ_ASSERT(!mData, "Wait() wasn't called");
+ }
+
+public:
+ NS_INLINE_DECL_REFCOUNTING(RetrievalContext)
+ enum State { INITIAL, COMPLETED, TIMED_OUT };
+
+ RetrievalContext() : mState(INITIAL), mData(nullptr) {}
+
+ /**
+ * Call this when data has been retrieved.
+ */
+ template <class T> void Complete(T *aData)
+ {
+ if (mState == INITIAL) {
+ mState = COMPLETED;
+ mData = CopyRetrievedData(aData);
+ } else {
+ // Already timed out
+ MOZ_ASSERT(mState == TIMED_OUT);
+ }
+ }
+
+ /**
+ * Spins X event loop until timing out or being completed. Returns
+ * null if we time out, otherwise returns the completed data (passing
+ * ownership to caller).
+ */
+ void *Wait();
+
+protected:
+ State mState;
+ void* mData;
+};
+
+void *
+RetrievalContext::Wait()
+{
+ if (mState == COMPLETED) { // the request completed synchronously
+ void *data = mData;
+ mData = nullptr;
+ return data;
+ }
+
+ GdkDisplay *gdkDisplay = gdk_display_get_default();
+ if (GDK_IS_X11_DISPLAY(gdkDisplay)) {
+ Display *xDisplay = GDK_DISPLAY_XDISPLAY(gdkDisplay);
+ checkEventContext context;
+ context.cbWidget = nullptr;
+ context.selAtom = gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION",
+ FALSE));
+
+ // Send X events which are relevant to the ongoing selection retrieval
+ // to the clipboard widget. Wait until either the operation completes, or
+ // we hit our timeout. All other X events remain queued.
+
+ int select_result;
+
+ int cnumber = ConnectionNumber(xDisplay);
+ fd_set select_set;
+ FD_ZERO(&select_set);
+ FD_SET(cnumber, &select_set);
+ ++cnumber;
+ TimeStamp start = TimeStamp::Now();
+
+ do {
+ XEvent xevent;
+
+ while (XCheckIfEvent(xDisplay, &xevent, checkEventProc,
+ (XPointer) &context)) {
+
+ if (xevent.xany.type == SelectionNotify)
+ DispatchSelectionNotifyEvent(context.cbWidget, &xevent);
+ else
+ DispatchPropertyNotifyEvent(context.cbWidget, &xevent);
+
+ if (mState == COMPLETED) {
+ void *data = mData;
+ mData = nullptr;
+ return data;
+ }
+ }
+
+ TimeStamp now = TimeStamp::Now();
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = std::max<int32_t>(0,
+ kClipboardTimeout - (now - start).ToMicroseconds());
+ select_result = select(cnumber, &select_set, nullptr, nullptr, &tv);
+ } while (select_result == 1 ||
+ (select_result == -1 && errno == EINTR));
+ }
+#ifdef DEBUG_CLIPBOARD
+ printf("exceeded clipboard timeout\n");
+#endif
+ mState = TIMED_OUT;
+ return nullptr;
+}
+
+static void
+clipboard_contents_received(GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ gpointer data)
+{
+ RetrievalContext *context = static_cast<RetrievalContext*>(data);
+ context->Complete(selection_data);
+ context->Release();
+}
+
+static GtkSelectionData *
+wait_for_contents(GtkClipboard *clipboard, GdkAtom target)
+{
+ RefPtr<RetrievalContext> context = new RetrievalContext();
+ // Balanced by Release in clipboard_contents_received
+ context.get()->AddRef();
+ gtk_clipboard_request_contents(clipboard, target,
+ clipboard_contents_received,
+ context.get());
+ return static_cast<GtkSelectionData*>(context->Wait());
+}
+
+static void
+clipboard_text_received(GtkClipboard *clipboard,
+ const gchar *text,
+ gpointer data)
+{
+ RetrievalContext *context = static_cast<RetrievalContext*>(data);
+ context->Complete(text);
+ context->Release();
+}
+
+static gchar *
+wait_for_text(GtkClipboard *clipboard)
+{
+ RefPtr<RetrievalContext> context = new RetrievalContext();
+ // Balanced by Release in clipboard_text_received
+ context.get()->AddRef();
+ gtk_clipboard_request_text(clipboard, clipboard_text_received, context.get());
+ return static_cast<gchar*>(context->Wait());
+}
+
+static GdkFilterReturn
+selection_request_filter(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data)
+{
+ XEvent *xevent = static_cast<XEvent*>(gdk_xevent);
+ if (xevent->xany.type == SelectionRequest) {
+ if (xevent->xselectionrequest.requestor == X11None)
+ return GDK_FILTER_REMOVE;
+
+ GdkDisplay *display = gdk_x11_lookup_xdisplay(
+ xevent->xselectionrequest.display);
+ if (!display)
+ return GDK_FILTER_REMOVE;
+
+ GdkWindow *window = gdk_x11_window_foreign_new_for_display(display,
+ xevent->xselectionrequest.requestor);
+ if (!window)
+ return GDK_FILTER_REMOVE;
+
+ g_object_unref(window);
+ }
+ return GDK_FILTER_CONTINUE;
+}
diff --git a/widget/gtk/nsClipboard.h b/widget/gtk/nsClipboard.h
new file mode 100644
index 000000000..70c866a01
--- /dev/null
+++ b/widget/gtk/nsClipboard.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 __nsClipboard_h_
+#define __nsClipboard_h_
+
+#include "nsIClipboard.h"
+#include "nsIObserver.h"
+#include <gtk/gtk.h>
+
+class nsClipboard : public nsIClipboard,
+ public nsIObserver
+{
+public:
+ nsClipboard();
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSICLIPBOARD
+ NS_DECL_NSIOBSERVER
+
+ // Make sure we are initialized, called from the factory
+ // constructor
+ nsresult Init (void);
+
+ // Someone requested the selection
+ void SelectionGetEvent (GtkClipboard *aGtkClipboard,
+ GtkSelectionData *aSelectionData);
+ void SelectionClearEvent (GtkClipboard *aGtkClipboard);
+
+private:
+ virtual ~nsClipboard();
+
+ // Utility methods
+ static GdkAtom GetSelectionAtom (int32_t aWhichClipboard);
+ static GtkSelectionData *GetTargets (GdkAtom aWhichClipboard);
+
+ // Save global clipboard content to gtk
+ nsresult Store (void);
+
+ // Get our hands on the correct transferable, given a specific
+ // clipboard
+ nsITransferable *GetTransferable (int32_t aWhichClipboard);
+
+ // Hang on to our owners and transferables so we can transfer data
+ // when asked.
+ nsCOMPtr<nsIClipboardOwner> mSelectionOwner;
+ nsCOMPtr<nsIClipboardOwner> mGlobalOwner;
+ nsCOMPtr<nsITransferable> mSelectionTransferable;
+ nsCOMPtr<nsITransferable> mGlobalTransferable;
+
+};
+
+#endif /* __nsClipboard_h_ */
diff --git a/widget/gtk/nsColorPicker.cpp b/widget/gtk/nsColorPicker.cpp
new file mode 100644
index 000000000..93ab8bb9a
--- /dev/null
+++ b/widget/gtk/nsColorPicker.cpp
@@ -0,0 +1,252 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include <gtk/gtk.h>
+
+#include "nsColor.h"
+#include "nsColorPicker.h"
+#include "nsGtkUtils.h"
+#include "nsIWidget.h"
+#include "WidgetUtils.h"
+#include "nsPIDOMWindow.h"
+
+NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker)
+
+#if defined(ACTIVATE_GTK3_COLOR_PICKER) && GTK_CHECK_VERSION(3,4,0)
+int nsColorPicker::convertGdkRgbaComponent(gdouble color_component) {
+ // GdkRGBA value is in range [0.0..1.0]. We need something in range [0..255]
+ return color_component * 255 + 0.5;
+}
+
+gdouble nsColorPicker::convertToGdkRgbaComponent(int color_component) {
+ return color_component / 255.0;
+}
+
+GdkRGBA nsColorPicker::convertToRgbaColor(nscolor color) {
+ GdkRGBA result = { convertToGdkRgbaComponent(NS_GET_R(color)),
+ convertToGdkRgbaComponent(NS_GET_G(color)),
+ convertToGdkRgbaComponent(NS_GET_B(color)),
+ convertToGdkRgbaComponent(NS_GET_A(color)) };
+
+ return result;
+}
+#else
+int nsColorPicker::convertGdkColorComponent(guint16 color_component) {
+ // GdkColor value is in range [0..65535]. We need something in range [0..255]
+ return (color_component * 255 + 127) / 65535;
+}
+
+guint16 nsColorPicker::convertToGdkColorComponent(int color_component) {
+ return color_component * 65535 / 255;
+}
+
+GdkColor nsColorPicker::convertToGdkColor(nscolor color) {
+ GdkColor result = { 0 /* obsolete, unused 'pixel' value */,
+ convertToGdkColorComponent(NS_GET_R(color)),
+ convertToGdkColorComponent(NS_GET_G(color)),
+ convertToGdkColorComponent(NS_GET_B(color)) };
+
+ return result;
+}
+
+GtkColorSelection* nsColorPicker::WidgetGetColorSelection(GtkWidget* widget)
+{
+ return GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(
+ GTK_COLOR_SELECTION_DIALOG(widget)));
+}
+#endif
+
+NS_IMETHODIMP nsColorPicker::Init(mozIDOMWindowProxy *aParent,
+ const nsAString& title,
+ const nsAString& initialColor)
+{
+ auto* parent = nsPIDOMWindowOuter::From(aParent);
+ mParentWidget = mozilla::widget::WidgetUtils::DOMWindowToWidget(parent);
+ mTitle = title;
+ mInitialColor = initialColor;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsColorPicker::Open(nsIColorPickerShownCallback *aColorPickerShownCallback)
+{
+
+ // Input color string should be 7 length (i.e. a string representing a valid
+ // simple color)
+ if (mInitialColor.Length() != 7) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const nsAString& withoutHash = StringTail(mInitialColor, 6);
+ nscolor color;
+ if (!NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mCallback) {
+ // It means Open has already been called: this is not allowed
+ NS_WARNING("mCallback is already set. Open called twice?");
+ return NS_ERROR_FAILURE;
+ }
+ mCallback = aColorPickerShownCallback;
+
+ nsXPIDLCString title;
+ title.Adopt(ToNewUTF8String(mTitle));
+ GtkWindow *parent_window = GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET));
+
+#if defined(ACTIVATE_GTK3_COLOR_PICKER) && GTK_CHECK_VERSION(3,4,0)
+ GtkWidget* color_chooser = gtk_color_chooser_dialog_new(title, parent_window);
+
+ if (parent_window) {
+ gtk_window_set_destroy_with_parent(GTK_WINDOW(color_chooser), TRUE);
+ }
+
+ gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(color_chooser), FALSE);
+ GdkRGBA color_rgba = convertToRgbaColor(color);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(color_chooser),
+ &color_rgba);
+
+ g_signal_connect(GTK_COLOR_CHOOSER(color_chooser), "color-activated",
+ G_CALLBACK(OnColorChanged), this);
+#else
+ GtkWidget *color_chooser = gtk_color_selection_dialog_new(title);
+
+ if (parent_window) {
+ GtkWindow *window = GTK_WINDOW(color_chooser);
+ gtk_window_set_transient_for(window, parent_window);
+ gtk_window_set_destroy_with_parent(window, TRUE);
+ }
+
+ GdkColor color_gdk = convertToGdkColor(color);
+ gtk_color_selection_set_current_color(WidgetGetColorSelection(color_chooser),
+ &color_gdk);
+
+ g_signal_connect(WidgetGetColorSelection(color_chooser), "color-changed",
+ G_CALLBACK(OnColorChanged), this);
+#endif
+
+ NS_ADDREF_THIS();
+
+ g_signal_connect(color_chooser, "response", G_CALLBACK(OnResponse), this);
+ g_signal_connect(color_chooser, "destroy", G_CALLBACK(OnDestroy), this);
+ gtk_widget_show(color_chooser);
+
+ return NS_OK;
+}
+
+#if defined(ACTIVATE_GTK3_COLOR_PICKER) && GTK_CHECK_VERSION(3,4,0)
+/* static */ void
+nsColorPicker::OnColorChanged(GtkColorChooser* color_chooser, GdkRGBA* color,
+ gpointer user_data)
+{
+ static_cast<nsColorPicker*>(user_data)->Update(color);
+}
+
+void
+nsColorPicker::Update(GdkRGBA* color)
+{
+ SetColor(color);
+ if (mCallback) {
+ mCallback->Update(mColor);
+ }
+}
+
+void nsColorPicker::SetColor(const GdkRGBA* color)
+{
+ mColor.Assign('#');
+ mColor += ToHexString(convertGdkRgbaComponent(color->red));
+ mColor += ToHexString(convertGdkRgbaComponent(color->green));
+ mColor += ToHexString(convertGdkRgbaComponent(color->blue));
+}
+#else
+/* static */ void
+nsColorPicker::OnColorChanged(GtkColorSelection* colorselection,
+ gpointer user_data)
+{
+ static_cast<nsColorPicker*>(user_data)->Update(colorselection);
+}
+
+void
+nsColorPicker::Update(GtkColorSelection* colorselection)
+{
+ ReadValueFromColorSelection(colorselection);
+ if (mCallback) {
+ mCallback->Update(mColor);
+ }
+}
+
+void nsColorPicker::ReadValueFromColorSelection(GtkColorSelection* colorselection)
+{
+ GdkColor rgba;
+ gtk_color_selection_get_current_color(colorselection, &rgba);
+
+ mColor.Assign('#');
+ mColor += ToHexString(convertGdkColorComponent(rgba.red));
+ mColor += ToHexString(convertGdkColorComponent(rgba.green));
+ mColor += ToHexString(convertGdkColorComponent(rgba.blue));
+}
+#endif
+
+/* static */ void
+nsColorPicker::OnResponse(GtkWidget* color_chooser, gint response_id,
+ gpointer user_data)
+{
+ static_cast<nsColorPicker*>(user_data)->
+ Done(color_chooser, response_id);
+}
+
+/* static */ void
+nsColorPicker::OnDestroy(GtkWidget* color_chooser, gpointer user_data)
+{
+ static_cast<nsColorPicker*>(user_data)->
+ Done(color_chooser, GTK_RESPONSE_CANCEL);
+}
+
+void
+nsColorPicker::Done(GtkWidget* color_chooser, gint response)
+{
+ switch (response) {
+ case GTK_RESPONSE_OK:
+ case GTK_RESPONSE_ACCEPT:
+#if defined(ACTIVATE_GTK3_COLOR_PICKER) && GTK_CHECK_VERSION(3,4,0)
+ GdkRGBA color;
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(color_chooser), &color);
+ SetColor(&color);
+#else
+ ReadValueFromColorSelection(WidgetGetColorSelection(color_chooser));
+#endif
+ break;
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_CLOSE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ mColor = mInitialColor;
+ break;
+ default:
+ NS_WARNING("Unexpected response");
+ break;
+ }
+
+ // A "response" signal won't be sent again but "destroy" will be.
+ g_signal_handlers_disconnect_by_func(color_chooser,
+ FuncToGpointer(OnDestroy), this);
+
+ gtk_widget_destroy(color_chooser);
+ if (mCallback) {
+ mCallback->Done(mColor);
+ mCallback = nullptr;
+ }
+
+ NS_RELEASE_THIS();
+}
+
+nsString nsColorPicker::ToHexString(int n)
+{
+ nsString result;
+ if (n <= 0x0F) {
+ result.Append('0');
+ }
+ result.AppendInt(n, 16);
+ return result;
+}
diff --git a/widget/gtk/nsColorPicker.h b/widget/gtk/nsColorPicker.h
new file mode 100644
index 000000000..107e6f058
--- /dev/null
+++ b/widget/gtk/nsColorPicker.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 nsColorPicker_h__
+#define nsColorPicker_h__
+
+#include <gtk/gtk.h>
+
+#include "nsCOMPtr.h"
+#include "nsIColorPicker.h"
+#include "nsString.h"
+
+// Don't activate the GTK3 color picker for now, because it is missing a few
+// things, mainly the ability to let the user select a color on the screen.
+// See bug 1198256.
+#undef ACTIVATE_GTK3_COLOR_PICKER
+
+class nsIWidget;
+
+class nsColorPicker final : public nsIColorPicker
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICOLORPICKER
+
+ nsColorPicker() {};
+
+private:
+ ~nsColorPicker() {};
+
+ static nsString ToHexString(int n);
+
+ static void OnResponse(GtkWidget* dialog, gint response_id,
+ gpointer user_data);
+ static void OnDestroy(GtkWidget* dialog, gpointer user_data);
+
+#if defined(ACTIVATE_GTK3_COLOR_PICKER) && GTK_CHECK_VERSION(3,4,0)
+ static void OnColorChanged(GtkColorChooser* color_chooser, GdkRGBA* color,
+ gpointer user_data);
+
+ static int convertGdkRgbaComponent(gdouble color_component);
+ static gdouble convertToGdkRgbaComponent(int color_component);
+ static GdkRGBA convertToRgbaColor(nscolor color);
+
+ void Update(GdkRGBA* color);
+ void SetColor(const GdkRGBA* color);
+#else
+ static void OnColorChanged(GtkColorSelection* colorselection,
+ gpointer user_data);
+
+ // Conversion functions for color
+ static int convertGdkColorComponent(guint16 color_component);
+ static guint16 convertToGdkColorComponent(int color_component);
+ static GdkColor convertToGdkColor(nscolor color);
+
+ static GtkColorSelection* WidgetGetColorSelection(GtkWidget* widget);
+
+ void Update(GtkColorSelection* colorselection);
+ void ReadValueFromColorSelection(GtkColorSelection* colorselection);
+#endif
+
+ void Done(GtkWidget* dialog, gint response_id);
+
+ nsCOMPtr<nsIWidget> mParentWidget;
+ nsCOMPtr<nsIColorPickerShownCallback> mCallback;
+ nsString mTitle;
+ nsString mColor;
+ nsString mInitialColor;
+};
+
+#endif // nsColorPicker_h__
diff --git a/widget/gtk/nsDeviceContextSpecG.cpp b/widget/gtk/nsDeviceContextSpecG.cpp
new file mode 100644
index 000000000..0bd87bd85
--- /dev/null
+++ b/widget/gtk/nsDeviceContextSpecG.cpp
@@ -0,0 +1,498 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "nsDeviceContextSpecG.h"
+
+#include "mozilla/gfx/PrintTargetPDF.h"
+#include "mozilla/gfx/PrintTargetPS.h"
+#include "mozilla/Logging.h"
+
+#include "plstr.h"
+#include "prenv.h" /* for PR_GetEnv */
+
+#include "nsPrintfCString.h"
+#include "nsReadableUtils.h"
+#include "nsStringEnumerator.h"
+#include "nsIServiceManager.h"
+#include "nsThreadUtils.h"
+
+#include "nsPSPrinters.h"
+#include "nsPaperPS.h" /* Paper size list */
+
+#include "nsPrintSettingsGTK.h"
+
+#include "nsIFileStreams.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/Preferences.h"
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+using namespace mozilla;
+
+using mozilla::gfx::IntSize;
+using mozilla::gfx::PrintTarget;
+using mozilla::gfx::PrintTargetPDF;
+using mozilla::gfx::PrintTargetPS;
+
+static PRLogModuleInfo *
+GetDeviceContextSpecGTKLog()
+{
+ static PRLogModuleInfo *sLog;
+ if (!sLog)
+ sLog = PR_NewLogModule("DeviceContextSpecGTK");
+ return sLog;
+}
+/* Macro to make lines shorter */
+#define DO_PR_DEBUG_LOG(x) MOZ_LOG(GetDeviceContextSpecGTKLog(), mozilla::LogLevel::Debug, x)
+
+//----------------------------------------------------------------------------------
+// The printer data is shared between the PrinterEnumerator and the nsDeviceContextSpecGTK
+// The PrinterEnumerator creates the printer info
+// but the nsDeviceContextSpecGTK cleans it up
+// If it gets created (via the Page Setup Dialog) but the user never prints anything
+// then it will never be delete, so this class takes care of that.
+class GlobalPrinters {
+public:
+ static GlobalPrinters* GetInstance() { return &mGlobalPrinters; }
+ ~GlobalPrinters() { FreeGlobalPrinters(); }
+
+ void FreeGlobalPrinters();
+ nsresult InitializeGlobalPrinters();
+
+ bool PrintersAreAllocated() { return mGlobalPrinterList != nullptr; }
+ uint32_t GetNumPrinters()
+ { return mGlobalPrinterList ? mGlobalPrinterList->Length() : 0; }
+ nsString* GetStringAt(int32_t aInx) { return &mGlobalPrinterList->ElementAt(aInx); }
+ void GetDefaultPrinterName(char16_t **aDefaultPrinterName);
+
+protected:
+ GlobalPrinters() {}
+
+ static GlobalPrinters mGlobalPrinters;
+ static nsTArray<nsString>* mGlobalPrinterList;
+};
+
+//---------------
+// static members
+GlobalPrinters GlobalPrinters::mGlobalPrinters;
+nsTArray<nsString>* GlobalPrinters::mGlobalPrinterList = nullptr;
+//---------------
+
+nsDeviceContextSpecGTK::nsDeviceContextSpecGTK()
+ : mGtkPrintSettings(nullptr)
+ , mGtkPageSetup(nullptr)
+{
+ DO_PR_DEBUG_LOG(("nsDeviceContextSpecGTK::nsDeviceContextSpecGTK()\n"));
+}
+
+nsDeviceContextSpecGTK::~nsDeviceContextSpecGTK()
+{
+ DO_PR_DEBUG_LOG(("nsDeviceContextSpecGTK::~nsDeviceContextSpecGTK()\n"));
+
+ if (mGtkPageSetup) {
+ g_object_unref(mGtkPageSetup);
+ }
+
+ if (mGtkPrintSettings) {
+ g_object_unref(mGtkPrintSettings);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsDeviceContextSpecGTK,
+ nsIDeviceContextSpec)
+
+already_AddRefed<PrintTarget> nsDeviceContextSpecGTK::MakePrintTarget()
+{
+ double width, height;
+ mPrintSettings->GetEffectivePageSize(&width, &height);
+
+ // convert twips to points
+ width /= TWIPS_PER_POINT_FLOAT;
+ height /= TWIPS_PER_POINT_FLOAT;
+
+ DO_PR_DEBUG_LOG(("\"%s\", %f, %f\n", mPath, width, height));
+ nsresult rv;
+
+ // We shouldn't be attempting to get a surface if we've already got a spool
+ // file.
+ MOZ_ASSERT(!mSpoolFile);
+
+ // Spool file. Use Glib's temporary file function since we're
+ // already dependent on the gtk software stack.
+ gchar *buf;
+ gint fd = g_file_open_tmp("XXXXXX.tmp", &buf, nullptr);
+ if (-1 == fd)
+ return nullptr;
+ close(fd);
+
+ rv = NS_NewNativeLocalFile(nsDependentCString(buf), false,
+ getter_AddRefs(mSpoolFile));
+ if (NS_FAILED(rv)) {
+ unlink(buf);
+ return nullptr;
+ }
+
+ mSpoolName = buf;
+ g_free(buf);
+
+ mSpoolFile->SetPermissions(0600);
+
+ nsCOMPtr<nsIFileOutputStream> stream = do_CreateInstance("@mozilla.org/network/file-output-stream;1");
+ rv = stream->Init(mSpoolFile, -1, -1, 0);
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ int16_t format;
+ mPrintSettings->GetOutputFormat(&format);
+
+ // Determine the real format with some GTK magic
+ if (format == nsIPrintSettings::kOutputFormatNative) {
+ if (mIsPPreview) {
+ // There is nothing to detect on Print Preview, use PS.
+ format = nsIPrintSettings::kOutputFormatPS;
+ } else {
+ return nullptr;
+ }
+ }
+
+ IntSize size = IntSize::Truncate(width, height);
+
+ if (format == nsIPrintSettings::kOutputFormatPDF) {
+ return PrintTargetPDF::CreateOrNull(stream, size);
+ }
+
+ int32_t orientation;
+ mPrintSettings->GetOrientation(&orientation);
+ return PrintTargetPS::CreateOrNull(stream,
+ size,
+ orientation == nsIPrintSettings::kPortraitOrientation
+ ? PrintTargetPS::PORTRAIT
+ : PrintTargetPS::LANDSCAPE);
+}
+
+/** -------------------------------------------------------
+ * Initialize the nsDeviceContextSpecGTK
+ * @update dc 2/15/98
+ * @update syd 3/2/99
+ */
+NS_IMETHODIMP nsDeviceContextSpecGTK::Init(nsIWidget *aWidget,
+ nsIPrintSettings* aPS,
+ bool aIsPrintPreview)
+{
+ DO_PR_DEBUG_LOG(("nsDeviceContextSpecGTK::Init(aPS=%p)\n", aPS));
+
+ if (gtk_major_version < 2 ||
+ (gtk_major_version == 2 && gtk_minor_version < 10))
+ return NS_ERROR_NOT_AVAILABLE; // I'm so sorry bz
+
+ mPrintSettings = do_QueryInterface(aPS);
+ if (!mPrintSettings)
+ return NS_ERROR_NO_INTERFACE;
+
+ mIsPPreview = aIsPrintPreview;
+
+ // This is only set by embedders
+ bool toFile;
+ aPS->GetPrintToFile(&toFile);
+
+ mToPrinter = !toFile && !aIsPrintPreview;
+
+ mGtkPrintSettings = mPrintSettings->GetGtkPrintSettings();
+ mGtkPageSetup = mPrintSettings->GetGtkPageSetup();
+
+ // This is a horrible workaround for some printer driver bugs that treat custom page sizes different
+ // to standard ones. If our paper object matches one of a standard one, use a standard paper size
+ // object instead. See bug 414314 for more info.
+ GtkPaperSize* geckosHackishPaperSize = gtk_page_setup_get_paper_size(mGtkPageSetup);
+ GtkPaperSize* standardGtkPaperSize = gtk_paper_size_new(gtk_paper_size_get_name(geckosHackishPaperSize));
+
+ mGtkPageSetup = gtk_page_setup_copy(mGtkPageSetup);
+ mGtkPrintSettings = gtk_print_settings_copy(mGtkPrintSettings);
+
+ GtkPaperSize* properPaperSize;
+ if (gtk_paper_size_is_equal(geckosHackishPaperSize, standardGtkPaperSize)) {
+ properPaperSize = standardGtkPaperSize;
+ } else {
+ properPaperSize = geckosHackishPaperSize;
+ }
+ gtk_print_settings_set_paper_size(mGtkPrintSettings, properPaperSize);
+ gtk_page_setup_set_paper_size_and_default_margins(mGtkPageSetup, properPaperSize);
+ gtk_paper_size_free(standardGtkPaperSize);
+
+ return NS_OK;
+}
+
+static void
+#if (MOZ_WIDGET_GTK == 3)
+print_callback(GtkPrintJob *aJob, gpointer aData, const GError *aError) {
+#else
+print_callback(GtkPrintJob *aJob, gpointer aData, GError *aError) {
+#endif
+ g_object_unref(aJob);
+ ((nsIFile*) aData)->Remove(false);
+}
+
+static void
+ns_release_macro(gpointer aData) {
+ nsIFile* spoolFile = (nsIFile*) aData;
+ NS_RELEASE(spoolFile);
+}
+
+/* static */
+gboolean nsDeviceContextSpecGTK::PrinterEnumerator(GtkPrinter *aPrinter,
+ gpointer aData) {
+ nsDeviceContextSpecGTK *spec = (nsDeviceContextSpecGTK*)aData;
+
+ // Find the printer whose name matches the one inside the settings.
+ nsXPIDLString printerName;
+ nsresult rv =
+ spec->mPrintSettings->GetPrinterName(getter_Copies(printerName));
+ if (NS_SUCCEEDED(rv) && printerName) {
+ NS_ConvertUTF16toUTF8 requestedName(printerName);
+ const char* currentName = gtk_printer_get_name(aPrinter);
+ if (requestedName.Equals(currentName)) {
+ spec->mPrintSettings->SetGtkPrinter(aPrinter);
+
+ // Bug 1145916 - attempting to kick off a print job for this printer
+ // during this tick of the event loop will result in the printer backend
+ // misunderstanding what the capabilities of the printer are due to a
+ // GTK bug (https://bugzilla.gnome.org/show_bug.cgi?id=753041). We
+ // sidestep this by deferring the print to the next tick.
+ NS_DispatchToCurrentThread(NewRunnableMethod(spec, &nsDeviceContextSpecGTK::StartPrintJob));
+ return TRUE;
+ }
+ }
+
+ // We haven't found it yet - keep searching...
+ return FALSE;
+}
+
+void nsDeviceContextSpecGTK::StartPrintJob() {
+ GtkPrintJob* job = gtk_print_job_new(mTitle.get(),
+ mPrintSettings->GetGtkPrinter(),
+ mGtkPrintSettings,
+ mGtkPageSetup);
+
+ if (!gtk_print_job_set_source_file(job, mSpoolName.get(), nullptr))
+ return;
+
+ NS_ADDREF(mSpoolFile.get());
+ gtk_print_job_send(job, print_callback, mSpoolFile, ns_release_macro);
+}
+
+void
+nsDeviceContextSpecGTK::EnumeratePrinters()
+{
+ gtk_enumerate_printers(&nsDeviceContextSpecGTK::PrinterEnumerator, this,
+ nullptr, TRUE);
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecGTK::BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage)
+{
+ mTitle.Truncate();
+ AppendUTF16toUTF8(aTitle, mTitle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDeviceContextSpecGTK::EndDocument()
+{
+ if (mToPrinter) {
+ // At this point, we might have a GtkPrinter set up in nsPrintSettingsGTK,
+ // or we might not. In the single-process case, we probably will, as this
+ // is populated by the print settings dialog, or set to the default
+ // printer.
+ // In the multi-process case, we proxy the print settings dialog over to
+ // the parent process, and only get the name of the printer back on the
+ // content process side. In that case, we need to enumerate the printers
+ // on the content side, and find a printer with a matching name.
+
+ GtkPrinter* printer = mPrintSettings->GetGtkPrinter();
+ if (printer) {
+ // We have a printer, so we can print right away.
+ StartPrintJob();
+ } else {
+ // We don't have a printer. We have to enumerate the printers and find
+ // one with a matching name.
+ NS_DispatchToCurrentThread(NewRunnableMethod(this, &nsDeviceContextSpecGTK::EnumeratePrinters));
+ }
+ } else {
+ // Handle print-to-file ourselves for the benefit of embedders
+ nsXPIDLString targetPath;
+ nsCOMPtr<nsIFile> destFile;
+ mPrintSettings->GetToFileName(getter_Copies(targetPath));
+
+ nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(targetPath),
+ false, getter_AddRefs(destFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString destLeafName;
+ rv = destFile->GetLeafName(destLeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> destDir;
+ rv = destFile->GetParent(getter_AddRefs(destDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mSpoolFile->MoveTo(destDir, destLeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This is the standard way to get the UNIX umask. Ugh.
+ mode_t mask = umask(0);
+ umask(mask);
+ // 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));
+ }
+ return NS_OK;
+}
+
+// Printer Enumerator
+nsPrinterEnumeratorGTK::nsPrinterEnumeratorGTK()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsPrinterEnumeratorGTK, nsIPrinterEnumerator)
+
+NS_IMETHODIMP nsPrinterEnumeratorGTK::GetPrinterNameList(nsIStringEnumerator **aPrinterNameList)
+{
+ NS_ENSURE_ARG_POINTER(aPrinterNameList);
+ *aPrinterNameList = nullptr;
+
+ nsresult rv = GlobalPrinters::GetInstance()->InitializeGlobalPrinters();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint32_t numPrinters = GlobalPrinters::GetInstance()->GetNumPrinters();
+ nsTArray<nsString> *printers = new nsTArray<nsString>(numPrinters);
+ if (!printers) {
+ GlobalPrinters::GetInstance()->FreeGlobalPrinters();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t count = 0;
+ while( count < numPrinters )
+ {
+ printers->AppendElement(*GlobalPrinters::GetInstance()->GetStringAt(count++));
+ }
+ GlobalPrinters::GetInstance()->FreeGlobalPrinters();
+
+ return NS_NewAdoptingStringEnumerator(aPrinterNameList, printers);
+}
+
+NS_IMETHODIMP nsPrinterEnumeratorGTK::GetDefaultPrinterName(char16_t **aDefaultPrinterName)
+{
+ DO_PR_DEBUG_LOG(("nsPrinterEnumeratorGTK::GetDefaultPrinterName()\n"));
+ NS_ENSURE_ARG_POINTER(aDefaultPrinterName);
+
+ GlobalPrinters::GetInstance()->GetDefaultPrinterName(aDefaultPrinterName);
+
+ DO_PR_DEBUG_LOG(("GetDefaultPrinterName(): default printer='%s'.\n", NS_ConvertUTF16toUTF8(*aDefaultPrinterName).get()));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrinterEnumeratorGTK::InitPrintSettingsFromPrinter(const char16_t *aPrinterName, nsIPrintSettings *aPrintSettings)
+{
+ DO_PR_DEBUG_LOG(("nsPrinterEnumeratorGTK::InitPrintSettingsFromPrinter()"));
+
+ NS_ENSURE_ARG_POINTER(aPrintSettings);
+
+ /* Set filename */
+ nsAutoCString filename;
+ const char *path;
+
+ if (!(path = PR_GetEnv("PWD")))
+ path = PR_GetEnv("HOME");
+
+ if (path)
+ filename = nsPrintfCString("%s/mozilla.pdf", path);
+ else
+ filename.AssignLiteral("mozilla.pdf");
+
+ DO_PR_DEBUG_LOG(("Setting default filename to '%s'\n", filename.get()));
+ aPrintSettings->SetToFileName(NS_ConvertUTF8toUTF16(filename).get());
+
+ aPrintSettings->SetIsInitializedFromPrinter(true);
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+nsresult GlobalPrinters::InitializeGlobalPrinters ()
+{
+ if (PrintersAreAllocated()) {
+ return NS_OK;
+ }
+
+ mGlobalPrinterList = new nsTArray<nsString>();
+
+ nsPSPrinterList psMgr;
+ if (psMgr.Enabled()) {
+ /* Get the list of PostScript-module printers */
+ // XXX: this function is the only user of GetPrinterList
+ // So it may be interesting to convert the nsCStrings
+ // in this function, we would save one loop here
+ nsTArray<nsCString> printerList;
+ psMgr.GetPrinterList(printerList);
+ for (uint32_t i = 0; i < printerList.Length(); i++)
+ {
+ mGlobalPrinterList->AppendElement(NS_ConvertUTF8toUTF16(printerList[i]));
+ }
+ }
+
+ /* If there are no printers available after all checks, return an error */
+ if (!mGlobalPrinterList->Length())
+ {
+ /* Make sure we do not cache an empty printer list */
+ FreeGlobalPrinters();
+
+ return NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+void GlobalPrinters::FreeGlobalPrinters()
+{
+ if (mGlobalPrinterList) {
+ delete mGlobalPrinterList;
+ mGlobalPrinterList = nullptr;
+ }
+}
+
+void
+GlobalPrinters::GetDefaultPrinterName(char16_t **aDefaultPrinterName)
+{
+ *aDefaultPrinterName = nullptr;
+
+ bool allocate = !GlobalPrinters::GetInstance()->PrintersAreAllocated();
+
+ if (allocate) {
+ nsresult rv = GlobalPrinters::GetInstance()->InitializeGlobalPrinters();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+ NS_ASSERTION(GlobalPrinters::GetInstance()->PrintersAreAllocated(), "no GlobalPrinters");
+
+ if (GlobalPrinters::GetInstance()->GetNumPrinters() == 0)
+ return;
+
+ *aDefaultPrinterName = ToNewUnicode(*GlobalPrinters::GetInstance()->GetStringAt(0));
+
+ if (allocate) {
+ GlobalPrinters::GetInstance()->FreeGlobalPrinters();
+ }
+}
+
diff --git a/widget/gtk/nsDeviceContextSpecG.h b/widget/gtk/nsDeviceContextSpecG.h
new file mode 100644
index 000000000..921c6275c
--- /dev/null
+++ b/widget/gtk/nsDeviceContextSpecG.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 nsDeviceContextSpecGTK_h___
+#define nsDeviceContextSpecGTK_h___
+
+#include "nsIDeviceContextSpec.h"
+#include "nsIPrintSettings.h"
+#include "nsIPrinterEnumerator.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+#include "nsCRT.h" /* should be <limits.h>? */
+
+#include <gtk/gtk.h>
+#include <gtk/gtkunixprint.h>
+
+#define NS_PORTRAIT 0
+#define NS_LANDSCAPE 1
+
+class nsPrintSettingsGTK;
+
+class nsDeviceContextSpecGTK : public nsIDeviceContextSpec
+{
+public:
+ nsDeviceContextSpecGTK();
+
+ NS_DECL_ISUPPORTS
+
+ virtual already_AddRefed<PrintTarget> MakePrintTarget() final;
+
+ NS_IMETHOD Init(nsIWidget *aWidget, nsIPrintSettings* aPS,
+ bool aIsPrintPreview) override;
+ NS_IMETHOD BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) override;
+ NS_IMETHOD EndDocument() override;
+ NS_IMETHOD BeginPage() override { return NS_OK; }
+ NS_IMETHOD EndPage() override { return NS_OK; }
+
+protected:
+ virtual ~nsDeviceContextSpecGTK();
+ nsCOMPtr<nsPrintSettingsGTK> mPrintSettings;
+ bool mToPrinter : 1; /* If true, print to printer */
+ bool mIsPPreview : 1; /* If true, is print preview */
+ char mPath[PATH_MAX]; /* If toPrinter = false, dest file */
+ char mPrinter[256]; /* Printer name */
+ GtkPrintSettings* mGtkPrintSettings;
+ GtkPageSetup* mGtkPageSetup;
+
+ nsCString mSpoolName;
+ nsCOMPtr<nsIFile> mSpoolFile;
+ nsCString mTitle;
+
+private:
+ void EnumeratePrinters();
+ void StartPrintJob();
+ static gboolean PrinterEnumerator(GtkPrinter *aPrinter, gpointer aData);
+};
+
+//-------------------------------------------------------------------------
+// Printer Enumerator
+//-------------------------------------------------------------------------
+class nsPrinterEnumeratorGTK final : public nsIPrinterEnumerator
+{
+ ~nsPrinterEnumeratorGTK() {}
+public:
+ nsPrinterEnumeratorGTK();
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPRINTERENUMERATOR
+};
+
+#endif /* !nsDeviceContextSpecGTK_h___ */
diff --git a/widget/gtk/nsDragService.cpp b/widget/gtk/nsDragService.cpp
new file mode 100644
index 000000000..15b4eeffa
--- /dev/null
+++ b/widget/gtk/nsDragService.cpp
@@ -0,0 +1,2109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "nsDragService.h"
+#include "nsArrayUtils.h"
+#include "nsIObserverService.h"
+#include "nsWidgetsCID.h"
+#include "nsWindow.h"
+#include "nsIServiceManager.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIIOService.h"
+#include "nsIFileURL.h"
+#include "nsNetUtil.h"
+#include "mozilla/Logging.h"
+#include "nsTArray.h"
+#include "nsPrimitiveHelpers.h"
+#include "prtime.h"
+#include "prthread.h"
+#include <dlfcn.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include "nsCRT.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/Services.h"
+
+#include "gfxXlibSurface.h"
+#include "gfxContext.h"
+#include "nsImageToPixbuf.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsISelection.h"
+#include "nsViewManager.h"
+#include "nsIFrame.h"
+#include "nsGtkUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxPlatform.h"
+#include "nsScreenGtk.h"
+#include "nsArrayUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+// This sets how opaque the drag image is
+#define DRAG_IMAGE_ALPHA_LEVEL 0.5
+
+// These values are copied from GtkDragResult (rather than using GtkDragResult
+// directly) so that this code can be compiled against versions of GTK+ that
+// do not have GtkDragResult.
+// GtkDragResult is available from GTK+ version 2.12.
+enum {
+ MOZ_GTK_DRAG_RESULT_SUCCESS,
+ MOZ_GTK_DRAG_RESULT_NO_TARGET
+};
+
+static PRLogModuleInfo *sDragLm = nullptr;
+
+// data used for synthetic periodic motion events sent to the source widget
+// grabbing real events for the drag.
+static guint sMotionEventTimerID;
+static GdkEvent *sMotionEvent;
+static GtkWidget *sGrabWidget;
+
+static const char gMimeListType[] = "application/x-moz-internal-item-list";
+static const char gMozUrlType[] = "_NETSCAPE_URL";
+static const char gTextUriListType[] = "text/uri-list";
+static const char gTextPlainUTF8Type[] = "text/plain;charset=utf-8";
+
+static void
+invisibleSourceDragBegin(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ gpointer aData);
+
+static void
+invisibleSourceDragEnd(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ gpointer aData);
+
+static gboolean
+invisibleSourceDragFailed(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ gint aResult,
+ gpointer aData);
+
+static void
+invisibleSourceDragDataGet(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ GtkSelectionData *aSelectionData,
+ guint aInfo,
+ guint32 aTime,
+ gpointer aData);
+
+nsDragService::nsDragService()
+ : mScheduledTask(eDragTaskNone)
+ , mTaskSource(0)
+{
+ // We have to destroy the hidden widget before the event loop stops
+ // running.
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ obsServ->AddObserver(this, "quit-application", false);
+
+ // our hidden source widget
+#if (MOZ_WIDGET_GTK == 2)
+ mHiddenWidget = gtk_window_new(GTK_WINDOW_POPUP);
+#else
+ // Using an offscreen window works around bug 983843.
+ mHiddenWidget = gtk_offscreen_window_new();
+#endif
+ // make sure that the widget is realized so that
+ // we can use it as a drag source.
+ gtk_widget_realize(mHiddenWidget);
+ // hook up our internal signals so that we can get some feedback
+ // from our drag source
+ g_signal_connect(mHiddenWidget, "drag_begin",
+ G_CALLBACK(invisibleSourceDragBegin), this);
+ g_signal_connect(mHiddenWidget, "drag_data_get",
+ G_CALLBACK(invisibleSourceDragDataGet), this);
+ g_signal_connect(mHiddenWidget, "drag_end",
+ G_CALLBACK(invisibleSourceDragEnd), this);
+ // drag-failed is available from GTK+ version 2.12
+ guint dragFailedID = g_signal_lookup("drag-failed",
+ G_TYPE_FROM_INSTANCE(mHiddenWidget));
+ if (dragFailedID) {
+ g_signal_connect_closure_by_id(mHiddenWidget, dragFailedID, 0,
+ g_cclosure_new(G_CALLBACK(invisibleSourceDragFailed),
+ this, nullptr),
+ FALSE);
+ }
+
+ // set up our logging module
+ if (!sDragLm)
+ sDragLm = PR_NewLogModule("nsDragService");
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::nsDragService"));
+ mCanDrop = false;
+ mTargetDragDataReceived = false;
+ mTargetDragData = 0;
+ mTargetDragDataLen = 0;
+}
+
+nsDragService::~nsDragService()
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::~nsDragService"));
+ if (mTaskSource)
+ g_source_remove(mTaskSource);
+
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsDragService, nsBaseDragService, nsIObserver)
+
+/* static */ nsDragService*
+nsDragService::GetInstance()
+{
+ static const nsIID iid = NS_DRAGSERVICE_CID;
+ nsCOMPtr<nsIDragService> dragService = do_GetService(iid);
+ return static_cast<nsDragService*>(dragService.get());
+ // We rely on XPCOM keeping a reference to the service.
+}
+
+// nsIObserver
+
+NS_IMETHODIMP
+nsDragService::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *aData)
+{
+ if (!nsCRT::strcmp(aTopic, "quit-application")) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("nsDragService::Observe(\"quit-application\")"));
+ if (mHiddenWidget) {
+ gtk_widget_destroy(mHiddenWidget);
+ mHiddenWidget = 0;
+ }
+ TargetResetData();
+ } else {
+ NS_NOTREACHED("unexpected topic");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+// Support for periodic drag events
+
+// http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
+// and the Xdnd protocol both recommend that drag events are sent periodically,
+// but GTK does not normally provide this.
+//
+// Here GTK is periodically stimulated by copies of the most recent mouse
+// motion events so as to send drag position messages to the destination when
+// appropriate (after it has received a status event from the previous
+// message).
+//
+// (If events were sent only on the destination side then the destination
+// would have no message to which it could reply with a drag status. Without
+// sending a drag status to the source, the destination would not be able to
+// change its feedback re whether it could accept the drop, and so the
+// source's behavior on drop will not be consistent.)
+
+static gboolean
+DispatchMotionEventCopy(gpointer aData)
+{
+ // Clear the timer id before OnSourceGrabEventAfter is called during event
+ // dispatch.
+ sMotionEventTimerID = 0;
+
+ GdkEvent *event = sMotionEvent;
+ sMotionEvent = nullptr;
+ // If there is no longer a grab on the widget, then the drag is over and
+ // there is no need to continue drag motion.
+ if (gtk_widget_has_grab(sGrabWidget)) {
+ gtk_propagate_event(sGrabWidget, event);
+ }
+ gdk_event_free(event);
+
+ // Cancel this timer;
+ // We've already started another if the motion event was dispatched.
+ return FALSE;
+}
+
+static void
+OnSourceGrabEventAfter(GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+ // If there is no longer a grab on the widget, then the drag motion is
+ // over (though the data may not be fetched yet).
+ if (!gtk_widget_has_grab(sGrabWidget))
+ return;
+
+ if (event->type == GDK_MOTION_NOTIFY) {
+ if (sMotionEvent) {
+ gdk_event_free(sMotionEvent);
+ }
+ sMotionEvent = gdk_event_copy(event);
+
+ // Update the cursor position. The last of these recorded gets used for
+ // the eDragEnd event.
+ nsDragService *dragService = static_cast<nsDragService*>(user_data);
+ gint scale = nsScreenGtk::GetGtkMonitorScaleFactor();
+ auto p = LayoutDeviceIntPoint::Round(event->motion.x_root * scale,
+ event->motion.y_root * scale);
+ dragService->SetDragEndPoint(p);
+ } else if (sMotionEvent && (event->type == GDK_KEY_PRESS ||
+ event->type == GDK_KEY_RELEASE)) {
+ // Update modifier state from key events.
+ sMotionEvent->motion.state = event->key.state;
+ } else {
+ return;
+ }
+
+ if (sMotionEventTimerID) {
+ g_source_remove(sMotionEventTimerID);
+ }
+
+ // G_PRIORITY_DEFAULT_IDLE is lower priority than GDK's redraw idle source
+ // and lower than GTK's idle source that sends drag position messages after
+ // motion-notify signals.
+ //
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
+ // recommends an interval of 350ms +/- 200ms.
+ sMotionEventTimerID =
+ g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, 350,
+ DispatchMotionEventCopy, nullptr, nullptr);
+}
+
+static GtkWindow*
+GetGtkWindow(nsIDOMDocument *aDocument)
+{
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDocument);
+ if (!doc)
+ return nullptr;
+
+ nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
+ if (!presShell)
+ return nullptr;
+
+ RefPtr<nsViewManager> vm = presShell->GetViewManager();
+ if (!vm)
+ return nullptr;
+
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ if (!widget)
+ return nullptr;
+
+ GtkWidget *gtkWidget =
+ static_cast<nsWindow*>(widget.get())->GetMozContainerWidget();
+ if (!gtkWidget)
+ return nullptr;
+
+ GtkWidget *toplevel = nullptr;
+ toplevel = gtk_widget_get_toplevel(gtkWidget);
+ if (!GTK_IS_WINDOW(toplevel))
+ return nullptr;
+
+ return GTK_WINDOW(toplevel);
+}
+
+// nsIDragService
+
+NS_IMETHODIMP
+nsDragService::InvokeDragSession(nsIDOMNode *aDOMNode,
+ nsIArray * aArrayTransferables,
+ nsIScriptableRegion * aRegion,
+ uint32_t aActionType,
+ nsContentPolicyType aContentPolicyType =
+ nsIContentPolicy::TYPE_OTHER)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::InvokeDragSession"));
+
+ // If the previous source drag has not yet completed, signal handlers need
+ // to be removed from sGrabWidget and dragend needs to be dispatched to
+ // the source node, but we can't call EndDragSession yet because we don't
+ // know whether or not the drag succeeded.
+ if (mSourceNode)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return nsBaseDragService::InvokeDragSession(aDOMNode, aArrayTransferables,
+ aRegion, aActionType,
+ aContentPolicyType);
+}
+
+// nsBaseDragService
+nsresult
+nsDragService::InvokeDragSessionImpl(nsIArray* aArrayTransferables,
+ nsIScriptableRegion* aRegion,
+ uint32_t aActionType)
+{
+ // make sure that we have an array of transferables to use
+ if (!aArrayTransferables)
+ return NS_ERROR_INVALID_ARG;
+ // set our reference to the transferables. this will also addref
+ // the transferables since we're going to hang onto this beyond the
+ // length of this call
+ mSourceDataItems = aArrayTransferables;
+ // get the list of items we offer for drags
+ GtkTargetList *sourceList = GetSourceList();
+
+ 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;
+
+ if (aActionType & DRAGDROP_ACTION_COPY)
+ action = (GdkDragAction)(action | GDK_ACTION_COPY);
+ if (aActionType & DRAGDROP_ACTION_MOVE)
+ action = (GdkDragAction)(action | GDK_ACTION_MOVE);
+ if (aActionType & DRAGDROP_ACTION_LINK)
+ action = (GdkDragAction)(action | GDK_ACTION_LINK);
+
+ // Create a fake event for the drag so we can pass the time (so to speak).
+ // If we don't do this, then, when the timestamp for the pending button
+ // release event is used for the ungrab, the ungrab can fail due to the
+ // timestamp being _earlier_ than CurrentTime.
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+ event.type = GDK_BUTTON_PRESS;
+ event.button.window = gtk_widget_get_window(mHiddenWidget);
+ event.button.time = nsWindow::GetLastUserInputTime();
+
+ // Put the drag widget in the window group of the source node so that the
+ // gtk_grab_add during gtk_drag_begin is effective.
+ // gtk_window_get_group(nullptr) returns the default window group.
+ GtkWindowGroup *window_group =
+ gtk_window_get_group(GetGtkWindow(mSourceDocument));
+ gtk_window_group_add_window(window_group,
+ GTK_WINDOW(mHiddenWidget));
+
+#if (MOZ_WIDGET_GTK == 3)
+ // Get device for event source
+ GdkDisplay *display = gdk_display_get_default();
+ GdkDeviceManager *device_manager = gdk_display_get_device_manager(display);
+ event.button.device = gdk_device_manager_get_client_pointer(device_manager);
+#endif
+
+ // start our drag.
+ GdkDragContext *context = gtk_drag_begin(mHiddenWidget,
+ sourceList,
+ action,
+ 1,
+ &event);
+
+ mSourceRegion = nullptr;
+
+ nsresult rv;
+ if (context) {
+ StartDragSession();
+
+ // GTK uses another hidden window for receiving mouse events.
+ sGrabWidget = gtk_window_group_get_current_grab(window_group);
+ if (sGrabWidget) {
+ g_object_ref(sGrabWidget);
+ // Only motion and key events are required but connect to
+ // "event-after" as this is never blocked by other handlers.
+ g_signal_connect(sGrabWidget, "event-after",
+ G_CALLBACK(OnSourceGrabEventAfter), this);
+ }
+ // We don't have a drag end point yet.
+ mEndDragPoint = LayoutDeviceIntPoint(-1, -1);
+ rv = NS_OK;
+ }
+ else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ gtk_target_list_unref(sourceList);
+
+ return rv;
+}
+
+bool
+nsDragService::SetAlphaPixmap(SourceSurface *aSurface,
+ GdkDragContext *aContext,
+ int32_t aXOffset,
+ int32_t aYOffset,
+ const LayoutDeviceIntRect& dragRect)
+{
+ GdkScreen* screen = gtk_widget_get_screen(mHiddenWidget);
+
+ // Transparent drag icons need, like a lot of transparency-related things,
+ // a compositing X window manager
+ if (!gdk_screen_is_composited(screen))
+ return false;
+
+#if (MOZ_WIDGET_GTK == 2)
+ GdkColormap* alphaColormap = gdk_screen_get_rgba_colormap(screen);
+ if (!alphaColormap)
+ return false;
+
+ GdkPixmap* pixmap = gdk_pixmap_new(nullptr, dragRect.width, dragRect.height,
+ gdk_colormap_get_visual(alphaColormap)->depth);
+ if (!pixmap)
+ return false;
+
+ gdk_drawable_set_colormap(GDK_DRAWABLE(pixmap), alphaColormap);
+
+ // Make a DrawTarget wrapped around the pixmap to render on
+ RefPtr<DrawTarget> dt =
+ nsWindow::GetDrawTargetForGdkDrawable(GDK_DRAWABLE(pixmap),
+ IntSize(dragRect.width,
+ dragRect.height));
+ if (!dt)
+ return false;
+
+ // Clear it...
+ dt->ClearRect(Rect(0, 0, dragRect.width, dragRect.height));
+
+ // ...and paint the drag image with translucency
+ dt->DrawSurface(aSurface,
+ Rect(0, 0, dragRect.width, dragRect.height),
+ Rect(0, 0, dragRect.width, dragRect.height),
+ DrawSurfaceOptions(),
+ DrawOptions(DRAG_IMAGE_ALPHA_LEVEL, CompositionOp::OP_SOURCE));
+
+ // The drag transaction addrefs the pixmap, so we can just unref it from us here
+ gtk_drag_set_icon_pixmap(aContext, alphaColormap, pixmap, nullptr,
+ aXOffset, aYOffset);
+ g_object_unref(pixmap);
+ return true;
+#else
+#ifdef cairo_image_surface_create
+#error "Looks like we're including Mozilla's cairo instead of system cairo"
+#endif
+ // Prior to GTK 3.9.12, cairo surfaces passed into gtk_drag_set_icon_surface
+ // had their shape information derived from the alpha channel and used with
+ // the X SHAPE extension instead of being displayed as an ARGB window.
+ // See bug 1249604.
+ if (gtk_check_version(3, 9, 12))
+ return false;
+
+ // TODO: grab X11 pixmap or image data instead of expensive readback.
+ cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ dragRect.width,
+ dragRect.height);
+ if (!surf)
+ return false;
+
+ RefPtr<DrawTarget> dt = gfxPlatform::CreateDrawTargetForData(
+ cairo_image_surface_get_data(surf),
+ nsIntSize(dragRect.width, dragRect.height),
+ cairo_image_surface_get_stride(surf),
+ SurfaceFormat::B8G8R8A8);
+ if (!dt)
+ return false;
+
+ dt->ClearRect(Rect(0, 0, dragRect.width, dragRect.height));
+ dt->DrawSurface(aSurface,
+ Rect(0, 0, dragRect.width, dragRect.height),
+ Rect(0, 0, dragRect.width, dragRect.height),
+ DrawSurfaceOptions(),
+ DrawOptions(DRAG_IMAGE_ALPHA_LEVEL, CompositionOp::OP_SOURCE));
+
+ cairo_surface_mark_dirty(surf);
+ cairo_surface_set_device_offset(surf, -aXOffset, -aYOffset);
+
+ // Ensure that the surface is drawn at the correct scale on HiDPI displays.
+ static auto sCairoSurfaceSetDeviceScalePtr =
+ (void (*)(cairo_surface_t*,double,double))
+ dlsym(RTLD_DEFAULT, "cairo_surface_set_device_scale");
+ if (sCairoSurfaceSetDeviceScalePtr) {
+ gint scale = nsScreenGtk::GetGtkMonitorScaleFactor();
+ sCairoSurfaceSetDeviceScalePtr(surf, scale, scale);
+ }
+
+ gtk_drag_set_icon_surface(aContext, surf);
+ cairo_surface_destroy(surf);
+ return true;
+#endif
+}
+
+NS_IMETHODIMP
+nsDragService::StartDragSession()
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::StartDragSession"));
+ return nsBaseDragService::StartDragSession();
+}
+
+NS_IMETHODIMP
+nsDragService::EndDragSession(bool aDoneDrag)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::EndDragSession %d",
+ aDoneDrag));
+
+ if (sGrabWidget) {
+ g_signal_handlers_disconnect_by_func(sGrabWidget,
+ FuncToGpointer(OnSourceGrabEventAfter), this);
+ g_object_unref(sGrabWidget);
+ sGrabWidget = nullptr;
+
+ if (sMotionEventTimerID) {
+ g_source_remove(sMotionEventTimerID);
+ sMotionEventTimerID = 0;
+ }
+ if (sMotionEvent) {
+ gdk_event_free(sMotionEvent);
+ sMotionEvent = nullptr;
+ }
+ }
+
+ // unset our drag action
+ SetDragAction(DRAGDROP_ACTION_NONE);
+
+ // We're done with the drag context.
+ mTargetDragContextForRemote = nullptr;
+
+ return nsBaseDragService::EndDragSession(aDoneDrag);
+}
+
+// nsIDragSession
+NS_IMETHODIMP
+nsDragService::SetCanDrop(bool aCanDrop)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::SetCanDrop %d",
+ aCanDrop));
+ mCanDrop = aCanDrop;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDragService::GetCanDrop(bool *aCanDrop)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetCanDrop"));
+ *aCanDrop = mCanDrop;
+ return NS_OK;
+}
+
+static void
+UTF16ToNewUTF8(const char16_t* aUTF16,
+ uint32_t aUTF16Len,
+ char** aUTF8,
+ uint32_t* aUTF8Len)
+{
+ nsDependentSubstring utf16(aUTF16, aUTF16Len);
+ *aUTF8 = ToNewUTF8String(utf16, aUTF8Len);
+}
+
+static void
+UTF8ToNewUTF16(const char* aUTF8,
+ uint32_t aUTF8Len,
+ char16_t** aUTF16,
+ uint32_t* aUTF16Len)
+{
+ nsDependentCSubstring utf8(aUTF8, aUTF8Len);
+ *aUTF16 = UTF8ToNewUnicode(utf8, aUTF16Len);
+}
+
+// count the number of URIs in some text/uri-list format data.
+static uint32_t
+CountTextUriListItems(const char *data,
+ uint32_t datalen)
+{
+ const char *p = data;
+ const char *endPtr = p + datalen;
+ uint32_t count = 0;
+
+ while (p < endPtr) {
+ // skip whitespace (if any)
+ while (p < endPtr && *p != '\0' && isspace(*p))
+ p++;
+ // if we aren't at the end of the line ...
+ if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r')
+ count++;
+ // skip to the end of the line
+ while (p < endPtr && *p != '\0' && *p != '\n')
+ p++;
+ p++; // skip the actual newline as well.
+ }
+ return count;
+}
+
+// extract an item from text/uri-list formatted data and convert it to
+// unicode.
+static void
+GetTextUriListItem(const char *data,
+ uint32_t datalen,
+ uint32_t aItemIndex,
+ char16_t **convertedText,
+ uint32_t *convertedTextLen)
+{
+ const char *p = data;
+ const char *endPtr = p + datalen;
+ unsigned int count = 0;
+
+ *convertedText = nullptr;
+ while (p < endPtr) {
+ // skip whitespace (if any)
+ while (p < endPtr && *p != '\0' && isspace(*p))
+ p++;
+ // if we aren't at the end of the line, we have a url
+ if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r')
+ count++;
+ // this is the item we are after ...
+ if (aItemIndex + 1 == count) {
+ const char *q = p;
+ while (q < endPtr && *q != '\0' && *q != '\n' && *q != '\r')
+ q++;
+ UTF8ToNewUTF16(p, q - p, convertedText, convertedTextLen);
+ break;
+ }
+ // skip to the end of the line
+ while (p < endPtr && *p != '\0' && *p != '\n')
+ p++;
+ p++; // skip the actual newline as well.
+ }
+
+ // didn't find the desired item, so just pass the whole lot
+ if (!*convertedText) {
+ UTF8ToNewUTF16(data, datalen, convertedText, convertedTextLen);
+ }
+}
+
+NS_IMETHODIMP
+nsDragService::GetNumDropItems(uint32_t * aNumItems)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetNumDropItems"));
+
+ if (!mTargetWidget) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("*** warning: GetNumDropItems \
+ called without a valid target widget!\n"));
+ *aNumItems = 0;
+ return NS_OK;
+ }
+
+ bool isList = IsTargetContextList();
+ if (isList)
+ mSourceDataItems->GetLength(aNumItems);
+ else {
+ GdkAtom gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ const char *data = reinterpret_cast<char*>(mTargetDragData);
+ *aNumItems = CountTextUriListItems(data, mTargetDragDataLen);
+ } else
+ *aNumItems = 1;
+ }
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("%d items", *aNumItems));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDragService::GetData(nsITransferable * aTransferable,
+ uint32_t aItemIndex)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetData %d", aItemIndex));
+
+ // make sure that we have a transferable
+ if (!aTransferable)
+ return NS_ERROR_INVALID_ARG;
+
+ if (!mTargetWidget) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("*** warning: GetData \
+ called without a valid target widget!\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // get flavor list that includes all acceptable flavors (including
+ // ones obtained through conversion). Flavors are nsISupportsStrings
+ // so that they can be seen from JS.
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(
+ getter_AddRefs(flavorList));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // count the number of flavors
+ uint32_t cnt;
+ flavorList->GetLength(&cnt);
+ unsigned int i;
+
+ // check to see if this is an internal list
+ bool isList = IsTargetContextList();
+
+ if (isList) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("it's a list..."));
+ // find a matching flavor
+ for (i = 0; i < cnt; ++i) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavorList, i);
+ if (!currentFlavor)
+ continue;
+
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ MOZ_LOG(sDragLm,
+ LogLevel::Debug,
+ ("flavor is %s\n", (const char *)flavorStr));
+ // get the item with the right index
+ nsCOMPtr<nsITransferable> item =
+ do_QueryElementAt(mSourceDataItems, aItemIndex);
+ if (!item)
+ continue;
+
+ nsCOMPtr<nsISupports> data;
+ uint32_t tmpDataLen = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("trying to get transfer data for %s\n",
+ (const char *)flavorStr));
+ rv = item->GetTransferData(flavorStr,
+ getter_AddRefs(data),
+ &tmpDataLen);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("failed.\n"));
+ continue;
+ }
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("succeeded.\n"));
+ rv = aTransferable->SetTransferData(flavorStr,data,tmpDataLen);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(sDragLm,
+ LogLevel::Debug,
+ ("fail to set transfer data into transferable!\n"));
+ continue;
+ }
+ // ok, we got the data
+ return NS_OK;
+ }
+ // if we got this far, we failed
+ return NS_ERROR_FAILURE;
+ }
+
+ // Now walk down the list of flavors. When we find one that is
+ // actually present, copy out the data into the transferable in that
+ // format. SetTransferData() implicitly handles conversions.
+ for ( i = 0; i < cnt; ++i ) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavorList, i);
+ if (currentFlavor) {
+ // find our gtk flavor
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ GdkAtom gdkFlavor = gdk_atom_intern(flavorStr, FALSE);
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("looking for data in type %s, gdk flavor %ld\n",
+ static_cast<const char*>(flavorStr), gdkFlavor));
+ bool dataFound = false;
+ if (gdkFlavor) {
+ GetTargetDragData(gdkFlavor);
+ }
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound = true\n"));
+ dataFound = true;
+ }
+ else {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound = false\n"));
+
+ // Dragging and dropping from the file manager would cause us
+ // to parse the source text as a nsIFile URL.
+ if ( strcmp(flavorStr, kFileMime) == 0 ) {
+ gdkFlavor = gdk_atom_intern(kTextMime, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (!mTargetDragData) {
+ gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
+ GetTargetDragData(gdkFlavor);
+ }
+ if (mTargetDragData) {
+ const char* text = static_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+
+ GetTextUriListItem(text, mTargetDragDataLen, aItemIndex,
+ &convertedText, &convertedTextLen);
+
+ if (convertedText) {
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ nsCOMPtr<nsIURI> fileURI;
+ rv = ioService->NewURI(NS_ConvertUTF16toUTF8(convertedText),
+ nullptr, nullptr, getter_AddRefs(fileURI));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ if (NS_SUCCEEDED(rv)) {
+ // The common wrapping code at the end of
+ // this function assumes the data is text
+ // and calls text-specific operations.
+ // Make a secret hideout here for nsIFile
+ // objects and return early.
+ aTransferable->SetTransferData(flavorStr, file,
+ convertedTextLen);
+ g_free(convertedText);
+ return NS_OK;
+ }
+ }
+ }
+ g_free(convertedText);
+ }
+ continue;
+ }
+ }
+
+ // if we are looking for text/unicode and we fail to find it
+ // on the clipboard first, try again with text/plain. If that
+ // is present, convert it to unicode.
+ if ( strcmp(flavorStr, kUnicodeMime) == 0 ) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("we were looking for text/unicode... \
+ trying with text/plain;charset=utf-8\n"));
+ gdkFlavor = gdk_atom_intern(gTextPlainUTF8Type, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Got textplain data\n"));
+ const char* castedText =
+ reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ NS_ConvertUTF8toUTF16 ucs2string(castedText,
+ mTargetDragDataLen);
+ convertedText = ToNewUnicode(ucs2string);
+ if ( convertedText ) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("successfully converted plain text \
+ to unicode.\n"));
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = ucs2string.Length() * 2;
+ dataFound = true;
+ } // if plain text data on clipboard
+ } else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("we were looking for text/unicode... \
+ trying again with text/plain\n"));
+ gdkFlavor = gdk_atom_intern(kTextMime, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Got textplain data\n"));
+ const char* castedText =
+ reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+ UTF8ToNewUTF16(castedText, mTargetDragDataLen,
+ &convertedText, &convertedTextLen);
+ if ( convertedText ) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("successfully converted plain text \
+ to unicode.\n"));
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = convertedTextLen * 2;
+ dataFound = true;
+ } // if plain text data on clipboard
+ } // if plain text flavor present
+ } // if plain text charset=utf-8 flavor present
+ } // if looking for text/unicode
+
+ // if we are looking for text/x-moz-url and we failed to find
+ // it on the clipboard, try again with text/uri-list, and then
+ // _NETSCAPE_URL
+ if (strcmp(flavorStr, kURLMime) == 0) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("we were looking for text/x-moz-url...\
+ trying again with text/uri-list\n"));
+ gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("Got text/uri-list data\n"));
+ const char *data =
+ reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+
+ GetTextUriListItem(data, mTargetDragDataLen, aItemIndex,
+ &convertedText, &convertedTextLen);
+
+ if ( convertedText ) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("successfully converted \
+ _NETSCAPE_URL to unicode.\n"));
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = convertedTextLen * 2;
+ dataFound = true;
+ }
+ }
+ else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("failed to get text/uri-list data\n"));
+ }
+ if (!dataFound) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("we were looking for text/x-moz-url...\
+ trying again with _NETSCAP_URL\n"));
+ gdkFlavor = gdk_atom_intern(gMozUrlType, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("Got _NETSCAPE_URL data\n"));
+ const char* castedText =
+ reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+ UTF8ToNewUTF16(castedText, mTargetDragDataLen, &convertedText, &convertedTextLen);
+ if ( convertedText ) {
+ MOZ_LOG(sDragLm,
+ LogLevel::Debug,
+ ("successfully converted _NETSCAPE_URL \
+ to unicode.\n"));
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = convertedTextLen * 2;
+ dataFound = true;
+ }
+ }
+ else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("failed to get _NETSCAPE_URL data\n"));
+ }
+ }
+ }
+
+ } // else we try one last ditch effort to find our data
+
+ if (dataFound) {
+ if (strcmp(flavorStr, kCustomTypesMime) != 0) {
+ // the DOM only wants LF, so convert from MacOS line endings
+ // to DOM line endings.
+ nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(
+ flavorStr,
+ &mTargetDragData,
+ reinterpret_cast<int*>(&mTargetDragDataLen));
+ }
+
+ // put it into the transferable.
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr,
+ mTargetDragData, mTargetDragDataLen,
+ getter_AddRefs(genericDataWrapper));
+ aTransferable->SetTransferData(flavorStr,
+ genericDataWrapper,
+ mTargetDragDataLen);
+ // we found one, get out of this loop!
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound and converted!\n"));
+ break;
+ }
+ } // if (currentFlavor)
+ } // foreach flavor
+
+ return NS_OK;
+
+}
+
+NS_IMETHODIMP
+nsDragService::IsDataFlavorSupported(const char *aDataFlavor,
+ bool *_retval)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::IsDataFlavorSupported %s",
+ aDataFlavor));
+ if (!_retval)
+ return NS_ERROR_INVALID_ARG;
+
+ // set this to no by default
+ *_retval = false;
+
+ // check to make sure that we have a drag object set, here
+ if (!mTargetWidget) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("*** warning: IsDataFlavorSupported \
+ called without a valid target widget!\n"));
+ return NS_OK;
+ }
+
+ // check to see if the target context is a list.
+ bool isList = IsTargetContextList();
+ // if it is, just look in the internal data since we are the source
+ // for it.
+ if (isList) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("It's a list.."));
+ uint32_t numDragItems = 0;
+ // if we don't have mDataItems we didn't start this drag so it's
+ // an external client trying to fool us.
+ if (!mSourceDataItems)
+ return NS_OK;
+ mSourceDataItems->GetLength(&numDragItems);
+ for (uint32_t itemIndex = 0; itemIndex < numDragItems; ++itemIndex) {
+ nsCOMPtr<nsITransferable> currItem =
+ do_QueryElementAt(mSourceDataItems, itemIndex);
+ if (currItem) {
+ nsCOMPtr <nsIArray> flavorList;
+ currItem->FlavorsTransferableCanExport(
+ getter_AddRefs(flavorList));
+ if (flavorList) {
+ uint32_t numFlavors;
+ flavorList->GetLength( &numFlavors );
+ for ( uint32_t flavorIndex = 0;
+ flavorIndex < numFlavors ;
+ ++flavorIndex ) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavorList, flavorIndex);
+ if (currentFlavor) {
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("checking %s against %s\n",
+ (const char *)flavorStr, aDataFlavor));
+ if (strcmp(flavorStr, aDataFlavor) == 0) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("boioioioiooioioioing!\n"));
+ *_retval = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return NS_OK;
+ }
+
+ // 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) {
+ /* Bug 331198 */
+ GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data);
+ gchar *name = nullptr;
+ name = gdk_atom_name(atom);
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("checking %s against %s\n", name, aDataFlavor));
+ if (name && (strcmp(name, aDataFlavor) == 0)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("good!\n"));
+ *_retval = true;
+ }
+ // check for automatic text/uri-list -> text/x-moz-url mapping
+ if (!*_retval &&
+ name &&
+ (strcmp(name, gTextUriListType) == 0) &&
+ (strcmp(aDataFlavor, kURLMime) == 0 ||
+ strcmp(aDataFlavor, kFileMime) == 0)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("good! ( it's text/uri-list and \
+ we're checking against text/x-moz-url )\n"));
+ *_retval = true;
+ }
+ // check for automatic _NETSCAPE_URL -> text/x-moz-url mapping
+ if (!*_retval &&
+ name &&
+ (strcmp(name, gMozUrlType) == 0) &&
+ (strcmp(aDataFlavor, kURLMime) == 0)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("good! ( it's _NETSCAPE_URL and \
+ we're checking against text/x-moz-url )\n"));
+ *_retval = true;
+ }
+ // check for auto text/plain -> text/unicode mapping
+ if (!*_retval &&
+ name &&
+ (strcmp(name, kTextMime) == 0) &&
+ ((strcmp(aDataFlavor, kUnicodeMime) == 0) ||
+ (strcmp(aDataFlavor, kFileMime) == 0))) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("good! ( it's text plain and we're checking \
+ against text/unicode or application/x-moz-file)\n"));
+ *_retval = true;
+ }
+ g_free(name);
+ }
+ return NS_OK;
+}
+
+void
+nsDragService::ReplyToDragMotion(GdkDragContext* 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;
+ }
+ }
+
+ gdk_drag_status(aDragContext, action, mTargetTime);
+}
+
+void
+nsDragService::TargetDataReceived(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ gint aX,
+ gint aY,
+ GtkSelectionData *aSelectionData,
+ guint aInfo,
+ guint32 aTime)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::TargetDataReceived"));
+ TargetResetData();
+ mTargetDragDataReceived = true;
+ gint len = gtk_selection_data_get_length(aSelectionData);
+ const guchar* data = gtk_selection_data_get_data(aSelectionData);
+ if (len > 0 && data) {
+ mTargetDragDataLen = len;
+ mTargetDragData = g_malloc(mTargetDragDataLen);
+ memcpy(mTargetDragData, data, mTargetDragDataLen);
+ }
+ else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("Failed to get data. selection data len was %d\n",
+ mTargetDragDataLen));
+ }
+}
+
+bool
+nsDragService::IsTargetContextList(void)
+{
+ bool retval = false;
+
+ // 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
+ // drop will work or not.
+ if (gtk_drag_get_source_widget(mTargetDragContext) == nullptr)
+ return retval;
+
+ GList *tmp;
+
+ // walk the list of context targets and see if one of them is a list
+ // of items.
+ for (tmp = gdk_drag_context_list_targets(mTargetDragContext);
+ tmp; tmp = tmp->next) {
+ /* Bug 331198 */
+ GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data);
+ gchar *name = nullptr;
+ name = gdk_atom_name(atom);
+ if (name && strcmp(name, gMimeListType) == 0)
+ retval = true;
+ g_free(name);
+ if (retval)
+ break;
+ }
+ return retval;
+}
+
+// Maximum time to wait for a "drag_received" arrived, in microseconds
+#define NS_DND_TIMEOUT 500000
+
+void
+nsDragService::GetTargetDragData(GdkAtom aFlavor)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("getting data flavor %d\n", aFlavor));
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("mLastWidget is %p and mLastContext is %p\n",
+ mTargetWidget.get(),
+ 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();
+ }
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("finished inner iteration\n"));
+}
+
+void
+nsDragService::TargetResetData(void)
+{
+ mTargetDragDataReceived = false;
+ // make sure to free old data if we have to
+ g_free(mTargetDragData);
+ mTargetDragData = 0;
+ mTargetDragDataLen = 0;
+}
+
+GtkTargetList *
+nsDragService::GetSourceList(void)
+{
+ if (!mSourceDataItems)
+ return nullptr;
+ nsTArray<GtkTargetEntry*> targetArray;
+ GtkTargetEntry *targets;
+ GtkTargetList *targetList = 0;
+ uint32_t targetCount = 0;
+ unsigned int numDragItems = 0;
+
+ mSourceDataItems->GetLength(&numDragItems);
+
+ // Check to see if we're dragging > 1 item.
+ if (numDragItems > 1) {
+ // as the Xdnd protocol only supports a single item (or is it just
+ // gtk's implementation?), we don't advertise all flavours listed
+ // in the nsITransferable.
+
+ // the application/x-moz-internal-item-list format, which preserves
+ // all information for drags within the same mozilla instance.
+ GtkTargetEntry *listTarget =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
+ listTarget->target = g_strdup(gMimeListType);
+ listTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n", listTarget->target));
+ targetArray.AppendElement(listTarget);
+
+ // check what flavours are supported so we can decide what other
+ // targets to advertise.
+ nsCOMPtr<nsITransferable> currItem =
+ do_QueryElementAt(mSourceDataItems, 0);
+
+ if (currItem) {
+ nsCOMPtr <nsIArray> flavorList;
+ currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
+ if (flavorList) {
+ uint32_t numFlavors;
+ flavorList->GetLength( &numFlavors );
+ for (uint32_t flavorIndex = 0;
+ flavorIndex < numFlavors ;
+ ++flavorIndex ) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavorList, flavorIndex);
+ if (currentFlavor) {
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ // check if text/x-moz-url is supported.
+ // If so, advertise
+ // text/uri-list.
+ if (strcmp(flavorStr, kURLMime) == 0) {
+ listTarget =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
+ listTarget->target = g_strdup(gTextUriListType);
+ listTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n",
+ listTarget->target));
+ targetArray.AppendElement(listTarget);
+ }
+ }
+ } // foreach flavor in item
+ } // if valid flavor list
+ } // if item is a transferable
+ } else if (numDragItems == 1) {
+ nsCOMPtr<nsITransferable> currItem =
+ do_QueryElementAt(mSourceDataItems, 0);
+ if (currItem) {
+ nsCOMPtr <nsIArray> flavorList;
+ currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
+ if (flavorList) {
+ uint32_t numFlavors;
+ flavorList->GetLength( &numFlavors );
+ for (uint32_t flavorIndex = 0;
+ flavorIndex < numFlavors ;
+ ++flavorIndex ) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavorList, flavorIndex);
+ if (currentFlavor) {
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ GtkTargetEntry *target =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
+ target->target = g_strdup(flavorStr);
+ target->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("adding target %s\n", target->target));
+ targetArray.AppendElement(target);
+
+ // If there is a file, add the text/uri-list type.
+ if (strcmp(flavorStr, kFileMime) == 0) {
+ GtkTargetEntry *urilistTarget =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
+ urilistTarget->target = g_strdup(gTextUriListType);
+ urilistTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n",
+ urilistTarget->target));
+ targetArray.AppendElement(urilistTarget);
+ }
+ // Check to see if this is text/unicode.
+ // If it is, add text/plain
+ // since we automatically support text/plain
+ // if we support text/unicode.
+ else if (strcmp(flavorStr, kUnicodeMime) == 0) {
+ GtkTargetEntry *plainUTF8Target =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
+ plainUTF8Target->target = g_strdup(gTextPlainUTF8Type);
+ plainUTF8Target->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n",
+ plainUTF8Target->target));
+ targetArray.AppendElement(plainUTF8Target);
+
+ GtkTargetEntry *plainTarget =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
+ plainTarget->target = g_strdup(kTextMime);
+ plainTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n",
+ plainTarget->target));
+ targetArray.AppendElement(plainTarget);
+ }
+ // Check to see if this is the x-moz-url type.
+ // If it is, add _NETSCAPE_URL
+ // this is a type used by everybody.
+ else if (strcmp(flavorStr, kURLMime) == 0) {
+ GtkTargetEntry *urlTarget =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
+ urlTarget->target = g_strdup(gMozUrlType);
+ urlTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n",
+ urlTarget->target));
+ targetArray.AppendElement(urlTarget);
+ }
+ }
+ } // foreach flavor in item
+ } // if valid flavor list
+ } // if item is a transferable
+ } // if it is a single item drag
+
+ // get all the elements that we created.
+ targetCount = targetArray.Length();
+ if (targetCount) {
+ // allocate space to create the list of valid targets
+ targets =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry) * targetCount);
+ uint32_t targetIndex;
+ for ( targetIndex = 0; targetIndex < targetCount; ++targetIndex) {
+ GtkTargetEntry *disEntry = targetArray.ElementAt(targetIndex);
+ // this is a string reference but it will be freed later.
+ targets[targetIndex].target = disEntry->target;
+ targets[targetIndex].flags = disEntry->flags;
+ targets[targetIndex].info = 0;
+ }
+ targetList = gtk_target_list_new(targets, targetCount);
+ // clean up the target list
+ for (uint32_t cleanIndex = 0; cleanIndex < targetCount; ++cleanIndex) {
+ GtkTargetEntry *thisTarget = targetArray.ElementAt(cleanIndex);
+ g_free(thisTarget->target);
+ g_free(thisTarget);
+ }
+ g_free(targets);
+ }
+ return targetList;
+}
+
+void
+nsDragService::SourceEndDragSession(GdkDragContext *aContext,
+ gint aResult)
+{
+ // this just releases the list of data items that we provide
+ mSourceDataItems = nullptr;
+
+ if (!mDoingDrag || mScheduledTask == eDragTaskSourceEnd)
+ // EndDragSession() was already called on drop
+ // or SourceEndDragSession on drag-failed
+ return;
+
+ if (mEndDragPoint.x < 0) {
+ // We don't have a drag end point, so guess
+ gint x, y;
+ GdkDisplay* display = gdk_display_get_default();
+ if (display) {
+ gint scale = nsScreenGtk::GetGtkMonitorScaleFactor();
+ gdk_display_get_pointer(display, nullptr, &x, &y, nullptr);
+ SetDragEndPoint(LayoutDeviceIntPoint(x * scale, y * scale));
+ }
+ }
+
+ // Either the drag was aborted or the drop occurred outside the app.
+ // The dropEffect of mDataTransfer is not updated for motion outside the
+ // app, but is needed for the dragend event, so set it now.
+
+ uint32_t dropEffect;
+
+ if (aResult == MOZ_GTK_DRAG_RESULT_SUCCESS) {
+
+ // With GTK+ versions 2.10.x and prior the drag may have been
+ // cancelled (but no drag-failed signal would have been sent).
+ // aContext->dest_window will be non-nullptr only if the drop was
+ // sent.
+ GdkDragAction action =
+ gdk_drag_context_get_dest_window(aContext) ?
+ gdk_drag_context_get_actions(aContext) : (GdkDragAction)0;
+
+ // Only one bit of action should be set, but, just in case someone
+ // does something funny, erring away from MOVE, and not recording
+ // unusual action combinations as NONE.
+ if (!action)
+ dropEffect = DRAGDROP_ACTION_NONE;
+ else if (action & GDK_ACTION_COPY)
+ dropEffect = DRAGDROP_ACTION_COPY;
+ else if (action & GDK_ACTION_LINK)
+ dropEffect = DRAGDROP_ACTION_LINK;
+ else if (action & GDK_ACTION_MOVE)
+ dropEffect = DRAGDROP_ACTION_MOVE;
+ else
+ dropEffect = DRAGDROP_ACTION_COPY;
+
+ } else {
+
+ dropEffect = DRAGDROP_ACTION_NONE;
+
+ if (aResult != MOZ_GTK_DRAG_RESULT_NO_TARGET) {
+ mUserCancelled = true;
+ }
+ }
+
+ if (mDataTransfer) {
+ mDataTransfer->SetDropEffectInt(dropEffect);
+ }
+
+ // Schedule the appropriate drag end dom events.
+ Schedule(eDragTaskSourceEnd, nullptr, nullptr, LayoutDeviceIntPoint(), 0);
+}
+
+static void
+CreateUriList(nsIArray *items, gchar **text, gint *length)
+{
+ uint32_t i, count;
+ GString *uriList = g_string_new(nullptr);
+
+ items->GetLength(&count);
+ for (i = 0; i < count; i++) {
+ nsCOMPtr<nsITransferable> item;
+ item = do_QueryElementAt(items, i);
+
+ if (item) {
+ uint32_t tmpDataLen = 0;
+ void *tmpData = nullptr;
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsISupports> data;
+ rv = item->GetTransferData(kURLMime,
+ getter_AddRefs(data),
+ &tmpDataLen);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsPrimitiveHelpers::CreateDataFromPrimitive(kURLMime,
+ data,
+ &tmpData,
+ tmpDataLen);
+ char* plainTextData = nullptr;
+ char16_t* castedUnicode = reinterpret_cast<char16_t*>
+ (tmpData);
+ uint32_t plainTextLen = 0;
+ UTF16ToNewUTF8(castedUnicode,
+ tmpDataLen / 2,
+ &plainTextData,
+ &plainTextLen);
+ if (plainTextData) {
+ uint32_t j;
+
+ // text/x-moz-url is of form url + "\n" + title.
+ // We just want the url.
+ for (j = 0; j < plainTextLen; j++)
+ if (plainTextData[j] == '\n' ||
+ plainTextData[j] == '\r') {
+ plainTextData[j] = '\0';
+ break;
+ }
+ g_string_append(uriList, plainTextData);
+ g_string_append(uriList, "\r\n");
+ // this wasn't allocated with glib
+ free(plainTextData);
+ }
+ if (tmpData) {
+ // this wasn't allocated with glib
+ free(tmpData);
+ }
+ } else {
+ // There is no uri available. If there is a file available,
+ // create a uri from the file.
+ nsCOMPtr<nsISupports> data;
+ rv = item->GetTransferData(kFileMime,
+ getter_AddRefs(data),
+ &tmpDataLen);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> file = do_QueryInterface(data);
+ if (!file) {
+ // Sometimes the file is wrapped in a
+ // nsISupportsInterfacePointer. See bug 1310193 for
+ // removing this distinction.
+ nsCOMPtr<nsISupportsInterfacePointer> ptr =
+ do_QueryInterface(data);
+ if (ptr) {
+ ptr->GetData(getter_AddRefs(data));
+ file = do_QueryInterface(data);
+ }
+ }
+
+ if (file) {
+ nsCOMPtr<nsIURI> fileURI;
+ NS_NewFileURI(getter_AddRefs(fileURI), file);
+ if (fileURI) {
+ nsAutoCString uristring;
+ fileURI->GetSpec(uristring);
+ g_string_append(uriList, uristring.get());
+ g_string_append(uriList, "\r\n");
+ }
+ }
+ }
+ }
+ }
+ }
+ *text = uriList->str;
+ *length = uriList->len + 1;
+ g_string_free(uriList, FALSE); // don't free the data
+}
+
+
+void
+nsDragService::SourceDataGet(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ GtkSelectionData *aSelectionData,
+ guint32 aTime)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::SourceDataGet"));
+ GdkAtom target = gtk_selection_data_get_target(aSelectionData);
+ nsXPIDLCString mimeFlavor;
+ gchar *typeName = 0;
+ typeName = gdk_atom_name(target);
+ if (!typeName) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("failed to get atom name.\n"));
+ return;
+ }
+
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Type is %s\n", typeName));
+ // make a copy since |nsXPIDLCString| won't use |g_free|...
+ mimeFlavor.Adopt(strdup(typeName));
+ g_free(typeName);
+ // check to make sure that we have data items to return.
+ if (!mSourceDataItems) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Failed to get our data items\n"));
+ return;
+ }
+
+ nsCOMPtr<nsITransferable> item;
+ item = do_QueryElementAt(mSourceDataItems, 0);
+ if (item) {
+ // if someone was asking for text/plain, lookup unicode instead so
+ // we can convert it.
+ bool needToDoConversionToPlainText = false;
+ const char* actualFlavor = mimeFlavor;
+ if (strcmp(mimeFlavor, kTextMime) == 0 ||
+ strcmp(mimeFlavor, gTextPlainUTF8Type) == 0) {
+ actualFlavor = kUnicodeMime;
+ needToDoConversionToPlainText = true;
+ }
+ // if someone was asking for _NETSCAPE_URL we need to convert to
+ // plain text but we also need to look for x-moz-url
+ else if (strcmp(mimeFlavor, gMozUrlType) == 0) {
+ actualFlavor = kURLMime;
+ needToDoConversionToPlainText = true;
+ }
+ // if someone was asking for text/uri-list we need to convert to
+ // plain text.
+ else if (strcmp(mimeFlavor, gTextUriListType) == 0) {
+ actualFlavor = gTextUriListType;
+ needToDoConversionToPlainText = true;
+ }
+ else
+ actualFlavor = mimeFlavor;
+
+ uint32_t tmpDataLen = 0;
+ void *tmpData = nullptr;
+ nsresult rv;
+ nsCOMPtr<nsISupports> data;
+ rv = item->GetTransferData(actualFlavor,
+ getter_AddRefs(data),
+ &tmpDataLen);
+ if (NS_SUCCEEDED(rv)) {
+ nsPrimitiveHelpers::CreateDataFromPrimitive (actualFlavor, data,
+ &tmpData, tmpDataLen);
+ // if required, do the extra work to convert unicode to plain
+ // text and replace the output values with the plain text.
+ if (needToDoConversionToPlainText) {
+ char* plainTextData = nullptr;
+ char16_t* castedUnicode = reinterpret_cast<char16_t*>
+ (tmpData);
+ uint32_t plainTextLen = 0;
+ UTF16ToNewUTF8(castedUnicode,
+ tmpDataLen / 2,
+ &plainTextData,
+ &plainTextLen);
+ if (tmpData) {
+ // this was not allocated using glib
+ free(tmpData);
+ tmpData = plainTextData;
+ tmpDataLen = plainTextLen;
+ }
+ }
+ if (tmpData) {
+ // this copies the data
+ gtk_selection_data_set(aSelectionData, target,
+ 8,
+ (guchar *)tmpData, tmpDataLen);
+ // this wasn't allocated with glib
+ free(tmpData);
+ }
+ } else {
+ if (strcmp(mimeFlavor, gTextUriListType) == 0) {
+ // fall back for text/uri-list
+ gchar *uriList;
+ gint length;
+ CreateUriList(mSourceDataItems, &uriList, &length);
+ gtk_selection_data_set(aSelectionData, target,
+ 8, (guchar *)uriList, length);
+ g_free(uriList);
+ return;
+ }
+ }
+ }
+}
+
+void nsDragService::SetDragIcon(GdkDragContext* aContext)
+{
+ if (!mHasImage && !mSelection)
+ return;
+
+ LayoutDeviceIntRect dragRect;
+ nsPresContext* pc;
+ RefPtr<SourceSurface> surface;
+ DrawDrag(mSourceNode, mSourceRegion, mScreenPosition,
+ &dragRect, &surface, &pc);
+ if (!pc)
+ return;
+
+ LayoutDeviceIntPoint screenPoint =
+ ConvertToUnscaledDevPixels(pc, mScreenPosition);
+ int32_t offsetX = screenPoint.x - dragRect.x;
+ int32_t offsetY = screenPoint.y - dragRect.y;
+
+ // If a popup is set as the drag image, use its widget. Otherwise, use
+ // the surface that DrawDrag created.
+ //
+ // XXX: Disable drag popups on GTK 3.19.4 and above: see bug 1264454.
+ // Fix this once a new GTK version ships that does not destroy our
+ // widget in gtk_drag_set_icon_widget.
+ if (mDragPopup && gtk_check_version(3, 19, 4)) {
+ GtkWidget* gtkWidget = nullptr;
+ nsIFrame* frame = mDragPopup->GetPrimaryFrame();
+ if (frame) {
+ // DrawDrag ensured that this is a popup frame.
+ nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget();
+ if (widget) {
+ gtkWidget = (GtkWidget *)widget->GetNativeData(NS_NATIVE_SHELLWIDGET);
+ if (gtkWidget) {
+ OpenDragPopup();
+ gtk_drag_set_icon_widget(aContext, gtkWidget, offsetX, offsetY);
+ }
+ }
+ }
+ }
+ else if (surface) {
+ if (!SetAlphaPixmap(surface, aContext, offsetX, offsetY, dragRect)) {
+ GdkPixbuf* dragPixbuf =
+ nsImageToPixbuf::SourceSurfaceToPixbuf(surface, dragRect.width, dragRect.height);
+ if (dragPixbuf) {
+ gtk_drag_set_icon_pixbuf(aContext, dragPixbuf, offsetX, offsetY);
+ g_object_unref(dragPixbuf);
+ }
+ }
+ }
+}
+
+static void
+invisibleSourceDragBegin(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ gpointer aData)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragBegin"));
+ nsDragService *dragService = (nsDragService *)aData;
+
+ dragService->SetDragIcon(aContext);
+}
+
+static void
+invisibleSourceDragDataGet(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ GtkSelectionData *aSelectionData,
+ guint aInfo,
+ guint32 aTime,
+ gpointer aData)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragDataGet"));
+ nsDragService *dragService = (nsDragService *)aData;
+ dragService->SourceDataGet(aWidget, aContext,
+ aSelectionData, aTime);
+}
+
+static gboolean
+invisibleSourceDragFailed(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ gint aResult,
+ gpointer aData)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragFailed %i", aResult));
+ nsDragService *dragService = (nsDragService *)aData;
+ // End the drag session now (rather than waiting for the drag-end signal)
+ // so that operations performed on dropEffect == none can start immediately
+ // rather than waiting for the drag-failed animation to finish.
+ dragService->SourceEndDragSession(aContext, aResult);
+
+ // We should return TRUE to disable the drag-failed animation iff the
+ // source performed an operation when dropEffect was none, but the handler
+ // of the dragend DOM event doesn't provide this information.
+ return FALSE;
+}
+
+static void
+invisibleSourceDragEnd(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ gpointer aData)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragEnd"));
+ nsDragService *dragService = (nsDragService *)aData;
+
+ // The drag has ended. Release the hostages!
+ dragService->SourceEndDragSession(aContext, MOZ_GTK_DRAG_RESULT_SUCCESS);
+}
+
+// The following methods handle responding to GTK drag signals and
+// tracking state between these signals.
+//
+// In general, GTK does not expect us to run the event loop while handling its
+// drag signals, however our drag event handlers may run the
+// event loop, most often to fetch information about the drag data.
+//
+// GTK, for example, uses the return value from drag-motion signals to
+// determine whether drag-leave signals should be sent. If an event loop is
+// run during drag-motion the XdndLeave message can get processed but when GTK
+// receives the message it does not yet know that it needs to send the
+// drag-leave signal to our widget.
+//
+// After a drag-drop signal, we need to reply with gtk_drag_finish().
+// However, gtk_drag_finish should happen after the drag-drop signal handler
+// returns so that when the Motif drag protocol is used, the
+// XmTRANSFER_SUCCESS during gtk_drag_finish is sent after the XmDROP_START
+// reply sent on return from the drag-drop signal handler.
+//
+// Similarly drag-end for a successful drag and drag-failed are not good
+// times to run a nested event loop as gtk_drag_drop_finished() and
+// gtk_drag_source_info_destroy() don't gtk_drag_clear_source_info() or remove
+// drop_timeout until after at least the first of these signals is sent.
+// Processing other events (e.g. a slow GDK_DROP_FINISHED reply, or the drop
+// timeout) could cause gtk_drag_drop_finished to be called again with the
+// same GtkDragSourceInfo, which won't like being destroyed twice.
+//
+// Therefore we reply to the signals immediately and schedule a task to
+// dispatch the Gecko events, which may run the event loop.
+//
+// Action in response to drag-leave signals is also delayed until the event
+// loop runs again so that we find out whether a drag-drop signal follows.
+//
+// A single task is scheduled to manage responses to all three GTK signals.
+// If further signals are received while the task is scheduled, the scheduled
+// response is updated, sometimes effectively compressing successive signals.
+//
+// No Gecko drag events are dispatched (during nested event loops) while other
+// Gecko drag events are in flight. This helps event handlers that may not
+// expect nested events, while accessing an event's dataTransfer for example.
+
+gboolean
+nsDragService::ScheduleMotionEvent(nsWindow *aWindow,
+ GdkDragContext *aDragContext,
+ LayoutDeviceIntPoint aWindowPoint, guint aTime)
+{
+ if (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
+ // scheduled task with the new position reply only to the most
+ // recent message.
+ NS_WARNING("Drag Motion message received before previous reply was sent");
+ }
+
+ // Returning TRUE means we'll reply with a status message, unless we first
+ // get a leave.
+ return Schedule(eDragTaskMotion, aWindow, aDragContext,
+ aWindowPoint, aTime);
+}
+
+void
+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)) {
+ NS_WARNING("Drag leave after drop");
+ }
+}
+
+gboolean
+nsDragService::ScheduleDropEvent(nsWindow *aWindow,
+ GdkDragContext *aDragContext,
+ LayoutDeviceIntPoint aWindowPoint, guint aTime)
+{
+ if (!Schedule(eDragTaskDrop, aWindow,
+ aDragContext, aWindowPoint, aTime)) {
+ NS_WARNING("Additional drag drop ignored");
+ return FALSE;
+ }
+
+ SetDragEndPoint(aWindowPoint + aWindow->WidgetToScreenOffset());
+
+ // We'll reply with gtk_drag_finish().
+ return TRUE;
+}
+
+gboolean
+nsDragService::Schedule(DragTask aTask, nsWindow *aWindow,
+ GdkDragContext *aDragContext,
+ LayoutDeviceIntPoint aWindowPoint, guint aTime)
+{
+ // If there is an existing leave or motion task scheduled, then that
+ // will be replaced. When the new task is run, it will dispatch
+ // any necessary leave or motion events.
+
+ // If aTask is eDragTaskSourceEnd, then it will replace even a scheduled
+ // drop event (which could happen if the drop event has not been processed
+ // within the allowed time). Otherwise, if we haven't yet run a scheduled
+ // drop or end task, just say that we are not ready to receive another
+ // drop.
+ if (mScheduledTask == eDragTaskSourceEnd ||
+ (mScheduledTask == eDragTaskDrop && aTask != eDragTaskSourceEnd))
+ return FALSE;
+
+ mScheduledTask = aTask;
+ mPendingWindow = aWindow;
+ mPendingDragContext = aDragContext;
+ mPendingWindowPoint = aWindowPoint;
+ mPendingTime = aTime;
+
+ if (!mTaskSource) {
+ // High priority is used here because the native events involved have
+ // already waited at default priority. Perhaps a lower than default
+ // priority could be used for motion tasks because there is a chance
+ // that a leave or drop is waiting, but managing different priorities
+ // may not be worth the effort. Motion tasks shouldn't queue up as
+ // they should be throttled based on replies.
+ mTaskSource = g_idle_add_full(G_PRIORITY_HIGH, TaskDispatchCallback,
+ this, nullptr);
+ }
+ return TRUE;
+}
+
+gboolean
+nsDragService::TaskDispatchCallback(gpointer data)
+{
+ RefPtr<nsDragService> dragService = static_cast<nsDragService*>(data);
+ return dragService->RunScheduledTask();
+}
+
+gboolean
+nsDragService::RunScheduledTask()
+{
+ if (mTargetWindow && mTargetWindow != mPendingWindow) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("nsDragService: dispatch drag leave (%p)\n",
+ mTargetWindow.get()));
+ mTargetWindow->DispatchDragEvent(eDragExit, mTargetWindowPoint, 0);
+
+ if (!mSourceNode) {
+ // The drag that was initiated in a different app. End the drag
+ // session, since we're done with it for now (until the user drags
+ // back into this app).
+ EndDragSession(false);
+ }
+ }
+
+ // It is possible that the pending state has been updated during dispatch
+ // of the leave event. That's fine.
+
+ // Now we collect the pending state because, from this point on, we want
+ // to use the same state for all events dispatched. All state is updated
+ // so that when other tasks are scheduled during dispatch here, this
+ // task is considered to have already been run.
+ bool positionHasChanged =
+ mPendingWindow != mTargetWindow ||
+ mPendingWindowPoint != mTargetWindowPoint;
+ DragTask task = mScheduledTask;
+ mScheduledTask = eDragTaskNone;
+ mTargetWindow = mPendingWindow.forget();
+ mTargetWindowPoint = mPendingWindowPoint;
+
+ if (task == eDragTaskLeave || task == eDragTaskSourceEnd) {
+ if (task == eDragTaskSourceEnd) {
+ // Dispatch drag end events.
+ EndDragSession(true);
+ }
+
+ // Nothing more to do
+ // Returning false removes the task source from the event loop.
+ mTaskSource = 0;
+ return FALSE;
+ }
+
+ // This may be the start of a destination drag session.
+ StartDragSession();
+
+ // mTargetWidget may be nullptr if the window has been destroyed.
+ // (The leave event is not scheduled if a drop task is still scheduled.)
+ // We still reply appropriately to indicate that the drop will or didn't
+ // succeeed.
+ mTargetWidget = mTargetWindow->GetMozContainerWidget();
+ mTargetDragContext.steal(mPendingDragContext);
+ mTargetTime = mPendingTime;
+
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
+ // (as at 27 December 2010) indicates that a "drop" event should only be
+ // fired (at the current target element) if the current drag operation is
+ // not none. The current drag operation will only be set to a non-none
+ // value during a "dragover" event.
+ //
+ // If the user has ended the drag before any dragover events have been
+ // sent, then the spec recommends skipping the drop (because the current
+ // drag operation is none). However, here we assume that, by releasing
+ // the mouse button, the user has indicated that they want to drop, so we
+ // proceed with the drop where possible.
+ //
+ // In order to make the events appear to content in the same way as if the
+ // spec is being followed we make sure to dispatch a "dragover" event with
+ // appropriate coordinates and check canDrop before the "drop" event.
+ //
+ // When the Xdnd protocol is used for source/destination communication (as
+ // should be the case with GTK source applications) a dragover event
+ // should have already been sent during the drag-motion signal, which
+ // would have already been received because XdndDrop messages do not
+ // contain a position. However, we can't assume the same when the Motif
+ // protocol is used.
+ if (task == eDragTaskMotion || positionHasChanged) {
+ UpdateDragAction();
+ TakeDragEventDispatchedToChildProcess(); // Clear the old value.
+ DispatchMotionEvents();
+ if (task == eDragTaskMotion) {
+ if (TakeDragEventDispatchedToChildProcess()) {
+ mTargetDragContextForRemote = mTargetDragContext;
+ } else {
+ // Reply to tell the source whether we can drop and what
+ // action would be taken.
+ ReplyToDragMotion(mTargetDragContext);
+ }
+ }
+ }
+
+ if (task == eDragTaskDrop) {
+ gboolean success = DispatchDropEvent();
+
+ // 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);
+
+ // This drag is over, so clear out our reference to the previous
+ // window.
+ mTargetWindow = nullptr;
+ // Make sure to end the drag session. If this drag started in a
+ // different app, we won't get a drag_end signal to end it from.
+ EndDragSession(true);
+ }
+
+ // We're done with the drag context.
+ mTargetWidget = nullptr;
+ mTargetDragContext = nullptr;
+
+ // If we got another drag signal while running the sheduled task, that
+ // must have happened while running a nested event loop. Leave the task
+ // source on the event loop.
+ if (mScheduledTask != eDragTaskNone)
+ return TRUE;
+
+ // We have no task scheduled.
+ // Returning false removes the task source from the event loop.
+ mTaskSource = 0;
+ return FALSE;
+}
+
+// This will update the drag action based on the information in the
+// drag context. Gtk gets this from a combination of the key settings
+// and what the source is offering.
+
+void
+nsDragService::UpdateDragAction()
+{
+ // This doesn't look right. dragSession.dragAction is used by
+ // nsContentUtils::SetDataTransferInEvent() to set the initial
+ // dataTransfer.dropEffect, so GdkDragContext::suggested_action would be
+ // more appropriate. GdkDragContext::actions should be used to set
+ // dataTransfer.effectAllowed, which doesn't currently happen with
+ // external sources.
+
+ // default is to do nothing
+ int action = nsIDragService::DRAGDROP_ACTION_NONE;
+ GdkDragAction gdkAction = gdk_drag_context_get_actions(mTargetDragContext);
+
+ // set the default just in case nothing matches below
+ if (gdkAction & GDK_ACTION_DEFAULT)
+ action = nsIDragService::DRAGDROP_ACTION_MOVE;
+
+ // first check to see if move is set
+ if (gdkAction & GDK_ACTION_MOVE)
+ action = nsIDragService::DRAGDROP_ACTION_MOVE;
+
+ // then fall to the others
+ else if (gdkAction & GDK_ACTION_LINK)
+ action = nsIDragService::DRAGDROP_ACTION_LINK;
+
+ // copy is ctrl
+ else if (gdkAction & GDK_ACTION_COPY)
+ action = nsIDragService::DRAGDROP_ACTION_COPY;
+
+ // update the drag information
+ SetDragAction(action);
+}
+
+NS_IMETHODIMP
+nsDragService::UpdateDragEffect()
+{
+ if (mTargetDragContextForRemote) {
+ ReplyToDragMotion(mTargetDragContextForRemote);
+ mTargetDragContextForRemote = nullptr;
+ }
+ return NS_OK;
+}
+
+void
+nsDragService::DispatchMotionEvents()
+{
+ mCanDrop = false;
+
+ FireDragEventAtSource(eDrag);
+
+ mTargetWindow->DispatchDragEvent(eDragOver, mTargetWindowPoint,
+ mTargetTime);
+}
+
+// Returns true if the drop was successful
+gboolean
+nsDragService::DispatchDropEvent()
+{
+ // We need to check IsDestroyed here because the nsRefPtr
+ // only protects this from being deleted, it does NOT protect
+ // against nsView::~nsView() calling Destroy() on it, bug 378273.
+ if (mTargetWindow->IsDestroyed())
+ return FALSE;
+
+ EventMessage msg = mCanDrop ? eDrop : eDragExit;
+
+ mTargetWindow->DispatchDragEvent(msg, mTargetWindowPoint, mTargetTime);
+
+ return mCanDrop;
+}
diff --git a/widget/gtk/nsDragService.h b/widget/gtk/nsDragService.h
new file mode 100644
index 000000000..90c113106
--- /dev/null
+++ b/widget/gtk/nsDragService.h
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 nsDragService_h__
+#define nsDragService_h__
+
+#include "mozilla/RefPtr.h"
+#include "nsBaseDragService.h"
+#include "nsIObserver.h"
+#include "nsAutoRef.h"
+#include <gtk/gtk.h>
+
+class nsWindow;
+
+namespace mozilla {
+namespace gfx {
+class SourceSurface;
+}
+}
+
+#ifndef HAVE_NSGOBJECTREFTRAITS
+#define HAVE_NSGOBJECTREFTRAITS
+template <class T>
+class nsGObjectRefTraits : public nsPointerRefTraits<T> {
+public:
+ static void Release(T *aPtr) { g_object_unref(aPtr); }
+ static void AddRef(T *aPtr) { g_object_ref(aPtr); }
+};
+#endif
+
+#ifndef HAVE_NSAUTOREFTRAITS_GTKWIDGET
+#define HAVE_NSAUTOREFTRAITS_GTKWIDGET
+template <>
+class nsAutoRefTraits<GtkWidget> : public nsGObjectRefTraits<GtkWidget> { };
+#endif
+
+#ifndef HAVE_NSAUTOREFTRAITS_GDKDRAGCONTEXT
+#define HAVE_NSAUTOREFTRAITS_GDKDRAGCONTEXT
+template <>
+class nsAutoRefTraits<GdkDragContext> :
+ public nsGObjectRefTraits<GdkDragContext> { };
+#endif
+
+/**
+ * Native GTK DragService wrapper
+ */
+
+class nsDragService final : public nsBaseDragService,
+ public nsIObserver
+{
+public:
+ nsDragService();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSIOBSERVER
+
+ // nsBaseDragService
+ virtual nsresult InvokeDragSessionImpl(nsIArray* anArrayTransferables,
+ nsIScriptableRegion* aRegion,
+ uint32_t aActionType) override;
+ // nsIDragService
+ NS_IMETHOD InvokeDragSession (nsIDOMNode *aDOMNode,
+ nsIArray * anArrayTransferables,
+ nsIScriptableRegion * aRegion,
+ uint32_t aActionType,
+ nsContentPolicyType aContentPolicyType) override;
+ NS_IMETHOD StartDragSession() override;
+ NS_IMETHOD EndDragSession(bool aDoneDrag) override;
+
+ // nsIDragSession
+ NS_IMETHOD SetCanDrop (bool aCanDrop) override;
+ NS_IMETHOD GetCanDrop (bool *aCanDrop) override;
+ NS_IMETHOD GetNumDropItems (uint32_t * aNumItems) override;
+ NS_IMETHOD GetData (nsITransferable * aTransferable,
+ uint32_t aItemIndex) override;
+ NS_IMETHOD IsDataFlavorSupported (const char *aDataFlavor,
+ bool *_retval) override;
+
+ NS_IMETHOD UpdateDragEffect() override;
+
+ // Methods called from nsWindow to handle responding to GTK drag
+ // destination signals
+
+ static nsDragService* GetInstance();
+
+ void TargetDataReceived (GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ gint aX,
+ gint aY,
+ GtkSelectionData *aSelection_data,
+ guint aInfo,
+ guint32 aTime);
+
+ gboolean ScheduleMotionEvent(nsWindow *aWindow,
+ GdkDragContext *aDragContext,
+ mozilla::LayoutDeviceIntPoint aWindowPoint,
+ guint aTime);
+ void ScheduleLeaveEvent();
+ gboolean ScheduleDropEvent(nsWindow *aWindow,
+ GdkDragContext *aDragContext,
+ mozilla::LayoutDeviceIntPoint aWindowPoint,
+ guint aTime);
+
+ nsWindow* GetMostRecentDestWindow()
+ {
+ return mScheduledTask == eDragTaskNone ? mTargetWindow
+ : mPendingWindow;
+ }
+
+ // END PUBLIC API
+
+ // These methods are public only so that they can be called from functions
+ // with C calling conventions. They are called for drags started with the
+ // invisible widget.
+ void SourceEndDragSession(GdkDragContext *aContext,
+ gint aResult);
+ void SourceDataGet(GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint32 aTime);
+
+ // set the drag icon during drag-begin
+ void SetDragIcon(GdkDragContext* aContext);
+
+protected:
+ virtual ~nsDragService();
+
+private:
+
+ // mScheduledTask indicates what signal has been received from GTK and
+ // so what needs to be dispatched when the scheduled task is run. It is
+ // eDragTaskNone when there is no task scheduled (but the
+ // previous task may still not have finished running).
+ enum DragTask {
+ eDragTaskNone,
+ eDragTaskMotion,
+ eDragTaskLeave,
+ eDragTaskDrop,
+ eDragTaskSourceEnd
+ };
+ DragTask mScheduledTask;
+ // mTaskSource is the GSource id for the task that is either scheduled
+ // or currently running. It is 0 if no task is scheduled or running.
+ guint mTaskSource;
+
+ // target/destination side vars
+ // These variables keep track of the state of the current drag.
+
+ // mPendingWindow, mPendingWindowPoint, mPendingDragContext, and
+ // mPendingTime, carry information from the GTK signal that will be used
+ // when the scheduled task is run. mPendingWindow and mPendingDragContext
+ // will be nullptr if the scheduled task is eDragTaskLeave.
+ RefPtr<nsWindow> mPendingWindow;
+ mozilla::LayoutDeviceIntPoint mPendingWindowPoint;
+ nsCountedRef<GdkDragContext> mPendingDragContext;
+ guint mPendingTime;
+
+ // mTargetWindow and mTargetWindowPoint record the position of the last
+ // eDragTaskMotion or eDragTaskDrop task that was run or is still running.
+ // mTargetWindow is cleared once the drag has completed or left.
+ RefPtr<nsWindow> mTargetWindow;
+ mozilla::LayoutDeviceIntPoint mTargetWindowPoint;
+ // mTargetWidget and mTargetDragContext are set only while dispatching
+ // motion or drop events. mTime records the corresponding timestamp.
+ nsCountedRef<GtkWidget> mTargetWidget;
+ nsCountedRef<GdkDragContext> mTargetDragContext;
+ // mTargetDragContextForRemote is set while waiting for a reply from
+ // a child process.
+ nsCountedRef<GdkDragContext> mTargetDragContextForRemote;
+ guint mTargetTime;
+
+ // is it OK to drop on us?
+ bool mCanDrop;
+
+ // have we received our drag data?
+ bool mTargetDragDataReceived;
+ // last data received and its length
+ void *mTargetDragData;
+ uint32_t mTargetDragDataLen;
+ // is the current target drag context contain a list?
+ bool IsTargetContextList(void);
+ // this will get the native data from the last target given a
+ // specific flavor
+ void GetTargetDragData(GdkAtom aFlavor);
+ // this will reset all of the target vars
+ void TargetResetData(void);
+
+ // source side vars
+
+ // the source of our drags
+ GtkWidget *mHiddenWidget;
+ // our source data items
+ nsCOMPtr<nsIArray> mSourceDataItems;
+
+ nsCOMPtr<nsIScriptableRegion> mSourceRegion;
+
+ // get a list of the sources in gtk's format
+ GtkTargetList *GetSourceList(void);
+
+ // attempts to create a semi-transparent drag image. Returns TRUE if
+ // successful, FALSE if not
+ bool SetAlphaPixmap(SourceSurface *aPixbuf,
+ GdkDragContext *aContext,
+ int32_t aXOffset,
+ int32_t aYOffset,
+ const mozilla::LayoutDeviceIntRect &dragRect);
+
+ gboolean Schedule(DragTask aTask, nsWindow *aWindow,
+ GdkDragContext *aDragContext,
+ mozilla::LayoutDeviceIntPoint aWindowPoint, guint aTime);
+
+ // Callback for g_idle_add_full() to run mScheduledTask.
+ static gboolean TaskDispatchCallback(gpointer data);
+ gboolean RunScheduledTask();
+ void UpdateDragAction();
+ void DispatchMotionEvents();
+ void ReplyToDragMotion(GdkDragContext* aDragContext);
+ gboolean DispatchDropEvent();
+};
+
+#endif // nsDragService_h__
+
diff --git a/widget/gtk/nsFilePicker.cpp b/widget/gtk/nsFilePicker.cpp
new file mode 100644
index 000000000..172cb4444
--- /dev/null
+++ b/widget/gtk/nsFilePicker.cpp
@@ -0,0 +1,610 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "mozilla/Types.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <gtk/gtk.h>
+
+#include "nsGtkUtils.h"
+#include "nsIFileURL.h"
+#include "nsIURI.h"
+#include "nsIWidget.h"
+#include "nsIFile.h"
+#include "nsIStringBundle.h"
+
+#include "nsArrayEnumerator.h"
+#include "nsMemory.h"
+#include "nsEnumeratorUtils.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "mozcontainer.h"
+
+#include "nsFilePicker.h"
+
+using namespace mozilla;
+
+#define MAX_PREVIEW_SIZE 180
+// bug 1184009
+#define MAX_PREVIEW_SOURCE_SIZE 4096
+
+nsIFile *nsFilePicker::mPrevDisplayDirectory = nullptr;
+
+void
+nsFilePicker::Shutdown()
+{
+ NS_IF_RELEASE(mPrevDisplayDirectory);
+}
+
+static GtkFileChooserAction
+GetGtkFileChooserAction(int16_t aMode)
+{
+ GtkFileChooserAction action;
+
+ switch (aMode) {
+ case nsIFilePicker::modeSave:
+ action = GTK_FILE_CHOOSER_ACTION_SAVE;
+ break;
+
+ case nsIFilePicker::modeGetFolder:
+ action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
+ break;
+
+ case nsIFilePicker::modeOpen:
+ case nsIFilePicker::modeOpenMultiple:
+ action = GTK_FILE_CHOOSER_ACTION_OPEN;
+ break;
+
+ default:
+ NS_WARNING("Unknown nsIFilePicker mode");
+ action = GTK_FILE_CHOOSER_ACTION_OPEN;
+ break;
+ }
+
+ return action;
+}
+
+
+static void
+UpdateFilePreviewWidget(GtkFileChooser *file_chooser,
+ gpointer preview_widget_voidptr)
+{
+ GtkImage *preview_widget = GTK_IMAGE(preview_widget_voidptr);
+ char *image_filename = gtk_file_chooser_get_preview_filename(file_chooser);
+ struct stat st_buf;
+
+ if (!image_filename) {
+ gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
+ return;
+ }
+
+ gint preview_width = 0;
+ gint preview_height = 0;
+ /* check type of file
+ * if file is named pipe, Open is blocking which may lead to UI
+ * nonresponsiveness; if file is directory/socket, it also isn't
+ * likely to get preview */
+ if (stat(image_filename, &st_buf) || (!S_ISREG(st_buf.st_mode))) {
+ g_free(image_filename);
+ gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
+ return; /* stat failed or file is not regular */
+ }
+
+ GdkPixbufFormat *preview_format = gdk_pixbuf_get_file_info(image_filename,
+ &preview_width,
+ &preview_height);
+ if (!preview_format ||
+ preview_width <= 0 || preview_height <= 0 ||
+ preview_width > MAX_PREVIEW_SOURCE_SIZE ||
+ preview_height > MAX_PREVIEW_SOURCE_SIZE) {
+ g_free(image_filename);
+ gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
+ return;
+ }
+
+ GdkPixbuf *preview_pixbuf = nullptr;
+ // Only scale down images that are too big
+ if (preview_width > MAX_PREVIEW_SIZE || preview_height > MAX_PREVIEW_SIZE) {
+ preview_pixbuf = gdk_pixbuf_new_from_file_at_size(image_filename,
+ MAX_PREVIEW_SIZE,
+ MAX_PREVIEW_SIZE,
+ nullptr);
+ }
+ else {
+ preview_pixbuf = gdk_pixbuf_new_from_file(image_filename, nullptr);
+ }
+
+ g_free(image_filename);
+
+ if (!preview_pixbuf) {
+ gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
+ return;
+ }
+
+ GdkPixbuf *preview_pixbuf_temp = preview_pixbuf;
+ preview_pixbuf = gdk_pixbuf_apply_embedded_orientation(preview_pixbuf_temp);
+ g_object_unref(preview_pixbuf_temp);
+
+ // This is the easiest way to do center alignment without worrying about containers
+ // Minimum 3px padding each side (hence the 6) just to make things nice
+ gint x_padding = (MAX_PREVIEW_SIZE + 6 - gdk_pixbuf_get_width(preview_pixbuf)) / 2;
+ gtk_misc_set_padding(GTK_MISC(preview_widget), x_padding, 0);
+
+ gtk_image_set_from_pixbuf(preview_widget, preview_pixbuf);
+ g_object_unref(preview_pixbuf);
+ gtk_file_chooser_set_preview_widget_active(file_chooser, TRUE);
+}
+
+static nsAutoCString
+MakeCaseInsensitiveShellGlob(const char* aPattern) {
+ // aPattern is UTF8
+ nsAutoCString result;
+ unsigned int len = strlen(aPattern);
+
+ for (unsigned int i = 0; i < len; i++) {
+ if (!g_ascii_isalpha(aPattern[i])) {
+ // non-ASCII characters will also trigger this path, so unicode
+ // is safely handled albeit case-sensitively
+ result.Append(aPattern[i]);
+ continue;
+ }
+
+ // add the lowercase and uppercase version of a character to a bracket
+ // match, so it matches either the lowercase or uppercase char.
+ result.Append('[');
+ result.Append(g_ascii_tolower(aPattern[i]));
+ result.Append(g_ascii_toupper(aPattern[i]));
+ result.Append(']');
+
+ }
+
+ return result;
+}
+
+NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
+
+nsFilePicker::nsFilePicker()
+ : mSelectedType(0)
+ , mRunning(false)
+ , mAllowURLs(false)
+#if (MOZ_WIDGET_GTK == 3)
+ , mFileChooserDelegate(nullptr)
+#endif
+{
+}
+
+nsFilePicker::~nsFilePicker()
+{
+}
+
+void
+ReadMultipleFiles(gpointer filename, gpointer array)
+{
+ nsCOMPtr<nsIFile> localfile;
+ nsresult rv = NS_NewNativeLocalFile(nsDependentCString(static_cast<char*>(filename)),
+ false,
+ getter_AddRefs(localfile));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMArray<nsIFile>& files = *static_cast<nsCOMArray<nsIFile>*>(array);
+ files.AppendObject(localfile);
+ }
+
+ g_free(filename);
+}
+
+void
+nsFilePicker::ReadValuesFromFileChooser(GtkWidget *file_chooser)
+{
+ mFiles.Clear();
+
+ if (mMode == nsIFilePicker::modeOpenMultiple) {
+ mFileURL.Truncate();
+
+ GSList *list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(file_chooser));
+ g_slist_foreach(list, ReadMultipleFiles, static_cast<gpointer>(&mFiles));
+ g_slist_free(list);
+ } else {
+ gchar *filename = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(file_chooser));
+ mFileURL.Assign(filename);
+ g_free(filename);
+ }
+
+ GtkFileFilter *filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(file_chooser));
+ GSList *filter_list = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(file_chooser));
+
+ mSelectedType = static_cast<int16_t>(g_slist_index(filter_list, filter));
+ g_slist_free(filter_list);
+
+ // Remember last used directory.
+ nsCOMPtr<nsIFile> file;
+ GetFile(getter_AddRefs(file));
+ if (file) {
+ nsCOMPtr<nsIFile> dir;
+ file->GetParent(getter_AddRefs(dir));
+ if (dir) {
+ dir.swap(mPrevDisplayDirectory);
+ }
+ }
+}
+
+void
+nsFilePicker::InitNative(nsIWidget *aParent,
+ const nsAString& aTitle)
+{
+ mParentWidget = aParent;
+ mTitle.Assign(aTitle);
+}
+
+NS_IMETHODIMP
+nsFilePicker::AppendFilters(int32_t aFilterMask)
+{
+ mAllowURLs = !!(aFilterMask & filterAllowURLs);
+ return nsBaseFilePicker::AppendFilters(aFilterMask);
+}
+
+NS_IMETHODIMP
+nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter)
+{
+ if (aFilter.EqualsLiteral("..apps")) {
+ // No platform specific thing we can do here, really....
+ return NS_OK;
+ }
+
+ nsAutoCString filter, name;
+ CopyUTF16toUTF8(aFilter, filter);
+ CopyUTF16toUTF8(aTitle, name);
+
+ mFilters.AppendElement(filter);
+ mFilterNames.AppendElement(name);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::SetDefaultString(const nsAString& aString)
+{
+ mDefault = aString;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetDefaultString(nsAString& aString)
+{
+ // Per API...
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsFilePicker::SetDefaultExtension(const nsAString& aExtension)
+{
+ mDefaultExtension = aExtension;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetDefaultExtension(nsAString& aExtension)
+{
+ aExtension = mDefaultExtension;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFilterIndex(int32_t *aFilterIndex)
+{
+ *aFilterIndex = mSelectedType;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::SetFilterIndex(int32_t aFilterIndex)
+{
+ mSelectedType = aFilterIndex;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFile(nsIFile **aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ *aFile = nullptr;
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetFileURL(getter_AddRefs(uri));
+ if (!uri)
+ return rv;
+
+ nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(uri, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ file.forget(aFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFileURL(nsIURI **aFileURL)
+{
+ *aFileURL = nullptr;
+ return NS_NewURI(aFileURL, mFileURL);
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles)
+{
+ NS_ENSURE_ARG_POINTER(aFiles);
+
+ if (mMode == nsIFilePicker::modeOpenMultiple) {
+ return NS_NewArrayEnumerator(aFiles, mFiles);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsFilePicker::Show(int16_t *aReturn)
+{
+ NS_ENSURE_ARG_POINTER(aReturn);
+
+ nsresult rv = Open(nullptr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ while (mRunning) {
+ g_main_context_iteration(nullptr, TRUE);
+ }
+
+ *aReturn = mResult;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::Open(nsIFilePickerShownCallback *aCallback)
+{
+ // Can't show two dialogs concurrently with the same filepicker
+ if (mRunning)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsXPIDLCString title;
+ title.Adopt(ToNewUTF8String(mTitle));
+
+ GtkWindow *parent_widget =
+ GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET));
+
+ GtkFileChooserAction action = GetGtkFileChooserAction(mMode);
+
+ const gchar* accept_button;
+ NS_ConvertUTF16toUTF8 buttonLabel(mOkButtonLabel);
+ if (!mOkButtonLabel.IsEmpty()) {
+ accept_button = buttonLabel.get();
+ } else {
+ accept_button = (action == GTK_FILE_CHOOSER_ACTION_SAVE) ?
+ GTK_STOCK_SAVE : GTK_STOCK_OPEN;
+ }
+
+ GtkWidget *file_chooser =
+ gtk_file_chooser_dialog_new(title, parent_widget, action,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ accept_button, GTK_RESPONSE_ACCEPT,
+ nullptr);
+ gtk_dialog_set_alternative_button_order(GTK_DIALOG(file_chooser),
+ GTK_RESPONSE_ACCEPT,
+ GTK_RESPONSE_CANCEL,
+ -1);
+ if (mAllowURLs) {
+ gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(file_chooser), FALSE);
+ }
+
+ if (action == GTK_FILE_CHOOSER_ACTION_OPEN || action == GTK_FILE_CHOOSER_ACTION_SAVE) {
+ GtkWidget *img_preview = gtk_image_new();
+ gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(file_chooser), img_preview);
+ g_signal_connect(file_chooser, "update-preview", G_CALLBACK(UpdateFilePreviewWidget), img_preview);
+ }
+
+ GtkWindow *window = GTK_WINDOW(file_chooser);
+ gtk_window_set_modal(window, TRUE);
+ if (parent_widget) {
+ gtk_window_set_destroy_with_parent(window, TRUE);
+ }
+
+ NS_ConvertUTF16toUTF8 defaultName(mDefault);
+ switch (mMode) {
+ case nsIFilePicker::modeOpenMultiple:
+ gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(file_chooser), TRUE);
+ break;
+ case nsIFilePicker::modeSave:
+ gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(file_chooser),
+ defaultName.get());
+ break;
+ }
+
+ nsCOMPtr<nsIFile> defaultPath;
+ if (mDisplayDirectory) {
+ mDisplayDirectory->Clone(getter_AddRefs(defaultPath));
+ } else if (mPrevDisplayDirectory) {
+ mPrevDisplayDirectory->Clone(getter_AddRefs(defaultPath));
+ }
+
+ if (defaultPath) {
+ if (!defaultName.IsEmpty() && mMode != nsIFilePicker::modeSave) {
+ // Try to select the intended file. Even if it doesn't exist, GTK still switches
+ // directories.
+ defaultPath->AppendNative(defaultName);
+ nsAutoCString path;
+ defaultPath->GetNativePath(path);
+ gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(file_chooser), path.get());
+ } else {
+ nsAutoCString directory;
+ defaultPath->GetNativePath(directory);
+
+#if (MOZ_WIDGET_GTK == 3)
+ // Workaround for problematic refcounting in GTK3 before 3.16.
+ // We need to keep a reference to the dialog's internal delegate.
+ // Otherwise, if our dialog gets destroyed, we'll lose the dialog's
+ // delegate by the time this gets processed in the event loop.
+ // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1166741
+ GtkDialog *dialog = GTK_DIALOG(file_chooser);
+ GtkContainer *area = GTK_CONTAINER(gtk_dialog_get_content_area(dialog));
+ gtk_container_forall(area, [](GtkWidget *widget,
+ gpointer data) {
+ if (GTK_IS_FILE_CHOOSER_WIDGET(widget)) {
+ auto result = static_cast<GtkFileChooserWidget**>(data);
+ *result = GTK_FILE_CHOOSER_WIDGET(widget);
+ }
+ }, &mFileChooserDelegate);
+
+ if (mFileChooserDelegate)
+ g_object_ref(mFileChooserDelegate);
+#endif
+
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(file_chooser),
+ directory.get());
+ }
+ }
+
+ gtk_dialog_set_default_response(GTK_DIALOG(file_chooser), GTK_RESPONSE_ACCEPT);
+
+ int32_t count = mFilters.Length();
+ for (int32_t i = 0; i < count; ++i) {
+ // This is fun... the GTK file picker does not accept a list of filters
+ // so we need to split out each string, and add it manually.
+
+ char **patterns = g_strsplit(mFilters[i].get(), ";", -1);
+ if (!patterns) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ GtkFileFilter *filter = gtk_file_filter_new();
+ for (int j = 0; patterns[j] != nullptr; ++j) {
+ nsAutoCString caseInsensitiveFilter = MakeCaseInsensitiveShellGlob(g_strstrip(patterns[j]));
+ gtk_file_filter_add_pattern(filter, caseInsensitiveFilter.get());
+ }
+
+ g_strfreev(patterns);
+
+ if (!mFilterNames[i].IsEmpty()) {
+ // If we have a name for our filter, let's use that.
+ const char *filter_name = mFilterNames[i].get();
+ gtk_file_filter_set_name(filter, filter_name);
+ } else {
+ // If we don't have a name, let's just use the filter pattern.
+ const char *filter_pattern = mFilters[i].get();
+ gtk_file_filter_set_name(filter, filter_pattern);
+ }
+
+ gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser), filter);
+
+ // Set the initially selected filter
+ if (mSelectedType == i) {
+ gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(file_chooser), filter);
+ }
+ }
+
+ gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(file_chooser), TRUE);
+
+ mRunning = true;
+ mCallback = aCallback;
+ NS_ADDREF_THIS();
+ g_signal_connect(file_chooser, "response", G_CALLBACK(OnResponse), this);
+ g_signal_connect(file_chooser, "destroy", G_CALLBACK(OnDestroy), this);
+ gtk_widget_show(file_chooser);
+
+ return NS_OK;
+}
+
+/* static */ void
+nsFilePicker::OnResponse(GtkWidget* file_chooser, gint response_id,
+ gpointer user_data)
+{
+ static_cast<nsFilePicker*>(user_data)->
+ Done(file_chooser, response_id);
+}
+
+/* static */ void
+nsFilePicker::OnDestroy(GtkWidget* file_chooser, gpointer user_data)
+{
+ static_cast<nsFilePicker*>(user_data)->
+ Done(file_chooser, GTK_RESPONSE_CANCEL);
+}
+
+void
+nsFilePicker::Done(GtkWidget* file_chooser, gint response)
+{
+ mRunning = false;
+
+ int16_t result;
+ switch (response) {
+ case GTK_RESPONSE_OK:
+ case GTK_RESPONSE_ACCEPT:
+ ReadValuesFromFileChooser(file_chooser);
+ result = nsIFilePicker::returnOK;
+ if (mMode == nsIFilePicker::modeSave) {
+ nsCOMPtr<nsIFile> file;
+ GetFile(getter_AddRefs(file));
+ if (file) {
+ bool exists = false;
+ file->Exists(&exists);
+ if (exists)
+ result = nsIFilePicker::returnReplace;
+ }
+ }
+ break;
+
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_CLOSE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ result = nsIFilePicker::returnCancel;
+ break;
+
+ default:
+ NS_WARNING("Unexpected response");
+ result = nsIFilePicker::returnCancel;
+ break;
+ }
+
+ // A "response" signal won't be sent again but "destroy" will be.
+ g_signal_handlers_disconnect_by_func(file_chooser,
+ FuncToGpointer(OnDestroy), this);
+
+ // When response_id is GTK_RESPONSE_DELETE_EVENT or when called from
+ // OnDestroy, the widget would be destroyed anyway but it is fine if
+ // gtk_widget_destroy is called more than once. gtk_widget_destroy has
+ // requests that any remaining references be released, but the reference
+ // count will not be decremented again if GtkWindow's reference has already
+ // been released.
+ gtk_widget_destroy(file_chooser);
+
+#if (MOZ_WIDGET_GTK == 3)
+ if (mFileChooserDelegate) {
+ // Properly deref our acquired reference. We call this after
+ // gtk_widget_destroy() to try and ensure that pending file info
+ // queries caused by updating the current folder have been cancelled.
+ // However, we do not know for certain when the callback will run after
+ // cancelled.
+ g_idle_add([](gpointer data) -> gboolean {
+ g_object_unref(data);
+ return G_SOURCE_REMOVE;
+ }, mFileChooserDelegate);
+ mFileChooserDelegate = nullptr;
+ }
+#endif
+
+ if (mCallback) {
+ mCallback->Done(result);
+ mCallback = nullptr;
+ } else {
+ mResult = result;
+ }
+ NS_RELEASE_THIS();
+}
diff --git a/widget/gtk/nsFilePicker.h b/widget/gtk/nsFilePicker.h
new file mode 100644
index 000000000..2b5042098
--- /dev/null
+++ b/widget/gtk/nsFilePicker.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 nsFilePicker_h__
+#define nsFilePicker_h__
+
+#include <gtk/gtk.h>
+
+#include "nsBaseFilePicker.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsCOMArray.h"
+
+class nsIWidget;
+class nsIFile;
+
+class nsFilePicker : public nsBaseFilePicker
+{
+public:
+ nsFilePicker();
+
+ NS_DECL_ISUPPORTS
+
+ // nsIFilePicker (less what's in nsBaseFilePicker)
+ NS_IMETHOD Open(nsIFilePickerShownCallback *aCallback) override;
+ NS_IMETHOD AppendFilters(int32_t aFilterMask) override;
+ NS_IMETHOD AppendFilter(const nsAString& aTitle,
+ const nsAString& aFilter) override;
+ NS_IMETHOD SetDefaultString(const nsAString& aString) override;
+ NS_IMETHOD GetDefaultString(nsAString& aString) override;
+ NS_IMETHOD SetDefaultExtension(const nsAString& aExtension) override;
+ NS_IMETHOD GetDefaultExtension(nsAString& aExtension) override;
+ NS_IMETHOD GetFilterIndex(int32_t *aFilterIndex) override;
+ NS_IMETHOD SetFilterIndex(int32_t aFilterIndex) override;
+ NS_IMETHOD GetFile(nsIFile **aFile) override;
+ NS_IMETHOD GetFileURL(nsIURI **aFileURL) override;
+ NS_IMETHOD GetFiles(nsISimpleEnumerator **aFiles) override;
+ NS_IMETHOD Show(int16_t *aReturn) override;
+
+ // nsBaseFilePicker
+ virtual void InitNative(nsIWidget *aParent,
+ const nsAString& aTitle) override;
+
+ static void Shutdown();
+
+protected:
+ virtual ~nsFilePicker();
+
+ void ReadValuesFromFileChooser(GtkWidget *file_chooser);
+
+ static void OnResponse(GtkWidget* dialog, gint response_id,
+ gpointer user_data);
+ static void OnDestroy(GtkWidget* dialog, gpointer user_data);
+ void Done(GtkWidget* dialog, gint response_id);
+
+ nsCOMPtr<nsIWidget> mParentWidget;
+ nsCOMPtr<nsIFilePickerShownCallback> mCallback;
+ nsCOMArray<nsIFile> mFiles;
+
+ int16_t mSelectedType;
+ int16_t mResult;
+ bool mRunning;
+ bool mAllowURLs;
+ nsCString mFileURL;
+ nsString mTitle;
+ nsString mDefault;
+ nsString mDefaultExtension;
+
+ nsTArray<nsCString> mFilters;
+ nsTArray<nsCString> mFilterNames;
+
+private:
+ static nsIFile *mPrevDisplayDirectory;
+
+#if (MOZ_WIDGET_GTK == 3)
+ GtkFileChooserWidget *mFileChooserDelegate;
+#endif
+};
+
+#endif
diff --git a/widget/gtk/nsGTKToolkit.h b/widget/gtk/nsGTKToolkit.h
new file mode 100644
index 000000000..ae0d55b63
--- /dev/null
+++ b/widget/gtk/nsGTKToolkit.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 GTKTOOLKIT_H
+#define GTKTOOLKIT_H
+
+#include "nsString.h"
+#include <gtk/gtk.h>
+
+/**
+ * Wrapper around the thread running the message pump.
+ * The toolkit abstraction is necessary because the message pump must
+ * execute within the same thread that created the widget under Win32.
+ */
+
+class nsGTKToolkit
+{
+public:
+ nsGTKToolkit();
+
+ static nsGTKToolkit* GetToolkit();
+
+ static void Shutdown() {
+ delete gToolkit;
+ gToolkit = nullptr;
+ }
+
+ /**
+ * Get/set our value of DESKTOP_STARTUP_ID. When non-empty, this is applied
+ * to the next toplevel window to be shown or focused (and then immediately
+ * cleared).
+ */
+ void SetDesktopStartupID(const nsACString& aID) { mDesktopStartupID = aID; }
+ void GetDesktopStartupID(nsACString* aID) { *aID = mDesktopStartupID; }
+
+ /**
+ * Get/set the timestamp value to be used, if non-zero, to focus the
+ * next top-level window to be shown or focused (upon which it is cleared).
+ */
+ void SetFocusTimestamp(uint32_t aTimestamp) { mFocusTimestamp = aTimestamp; }
+ uint32_t GetFocusTimestamp() { return mFocusTimestamp; }
+
+private:
+ static nsGTKToolkit* gToolkit;
+
+ nsCString mDesktopStartupID;
+ uint32_t mFocusTimestamp;
+};
+
+#endif // GTKTOOLKIT_H
diff --git a/widget/gtk/nsGtkCursors.h b/widget/gtk/nsGtkCursors.h
new file mode 100644
index 000000000..b7df7f65e
--- /dev/null
+++ b/widget/gtk/nsGtkCursors.h
@@ -0,0 +1,405 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 nsGtkCursors_h__
+#define nsGtkCursors_h__
+
+typedef struct {
+ const unsigned char *bits;
+ const unsigned char *mask_bits;
+ int hot_x;
+ int hot_y;
+ const char *hash;
+} nsGtkCursor;
+
+/* MOZ_CURSOR_HAND_GRAB */
+static const unsigned char moz_hand_grab_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00,
+ 0x60, 0x39, 0x00, 0x00, 0x90, 0x49, 0x00, 0x00, 0x90, 0x49, 0x01, 0x00,
+ 0x20, 0xc9, 0x02, 0x00, 0x20, 0x49, 0x02, 0x00, 0x58, 0x40, 0x02, 0x00,
+ 0x64, 0x00, 0x02, 0x00, 0x44, 0x00, 0x01, 0x00, 0x08, 0x00, 0x01, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x10, 0x80, 0x00, 0x00, 0x20, 0x80, 0x00, 0x00,
+ 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static const unsigned char moz_hand_grab_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x60, 0x3f, 0x00, 0x00,
+ 0xf0, 0x7f, 0x00, 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf8, 0xff, 0x03, 0x00,
+ 0xf0, 0xff, 0x07, 0x00, 0xf8, 0xff, 0x07, 0x00, 0xfc, 0xff, 0x07, 0x00,
+ 0xfe, 0xff, 0x07, 0x00, 0xfe, 0xff, 0x03, 0x00, 0xfc, 0xff, 0x03, 0x00,
+ 0xf8, 0xff, 0x03, 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf0, 0xff, 0x01, 0x00,
+ 0xe0, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* MOZ_CURSOR_HAND_GRABBING */
+static const unsigned char moz_hand_grabbing_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xc0, 0x36, 0x00, 0x00, 0x20, 0xc9, 0x00, 0x00, 0x20, 0x40, 0x01, 0x00,
+ 0x40, 0x00, 0x01, 0x00, 0x60, 0x00, 0x01, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x10, 0x80, 0x00, 0x00, 0x20, 0x80, 0x00, 0x00,
+ 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static const unsigned char moz_hand_grabbing_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x36, 0x00, 0x00,
+ 0xe0, 0xff, 0x00, 0x00, 0xf0, 0xff, 0x01, 0x00, 0xf0, 0xff, 0x03, 0x00,
+ 0xe0, 0xff, 0x03, 0x00, 0xf0, 0xff, 0x03, 0x00, 0xf8, 0xff, 0x03, 0x00,
+ 0xf8, 0xff, 0x03, 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf0, 0xff, 0x01, 0x00,
+ 0xe0, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* MOZ_CURSOR_COPY */
+static const unsigned char moz_copy_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
+ 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00,
+ 0xfc, 0x03, 0x00, 0x00, 0x7c, 0x30, 0x00, 0x00, 0x6c, 0x30, 0x00, 0x00,
+ 0xc4, 0xfc, 0x00, 0x00, 0xc0, 0xfc, 0x00, 0x00, 0x80, 0x31, 0x00, 0x00,
+ 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static const unsigned char moz_copy_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x00,
+ 0xfe, 0x37, 0x00, 0x00, 0xfe, 0x7b, 0x00, 0x00, 0xfe, 0xfc, 0x00, 0x00,
+ 0xee, 0xff, 0x01, 0x00, 0xe4, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x00, 0x00,
+ 0xc0, 0x7b, 0x00, 0x00, 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* MOZ_CURSOR_ALIAS */
+static const unsigned char moz_alias_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
+ 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00,
+ 0xfc, 0x03, 0x00, 0x00, 0x7c, 0xf0, 0x00, 0x00, 0x6c, 0xe0, 0x00, 0x00,
+ 0xc4, 0xf0, 0x00, 0x00, 0xc0, 0xb0, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00,
+ 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static const unsigned char moz_alias_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x00,
+ 0xfe, 0xf7, 0x00, 0x00, 0xfe, 0xfb, 0x01, 0x00, 0xfe, 0xf0, 0x01, 0x00,
+ 0xee, 0xf9, 0x01, 0x00, 0xe4, 0xf9, 0x01, 0x00, 0xc0, 0xbf, 0x00, 0x00,
+ 0xc0, 0x3f, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* MOZ_CURSOR_CONTEXT_MENU */
+static const unsigned char moz_menu_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
+ 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0xfd, 0x00, 0x00,
+ 0xfc, 0xff, 0x00, 0x00, 0x7c, 0x84, 0x00, 0x00, 0x6c, 0xfc, 0x00, 0x00,
+ 0xc4, 0x84, 0x00, 0x00, 0xc0, 0xfc, 0x00, 0x00, 0x80, 0x85, 0x00, 0x00,
+ 0x80, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static const unsigned char moz_menu_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0xfd, 0x00, 0x00, 0xfe, 0xff, 0x01, 0x00,
+ 0xfe, 0xff, 0x01, 0x00, 0xfe, 0xff, 0x01, 0x00, 0xfe, 0xfe, 0x01, 0x00,
+ 0xee, 0xff, 0x01, 0x00, 0xe4, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00,
+ 0xc0, 0xff, 0x01, 0x00, 0x80, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* MOZ_CURSOR_SPINNING */
+static const unsigned char moz_spinning_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
+ 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00,
+ 0xfc, 0x3b, 0x00, 0x00, 0x7c, 0x38, 0x00, 0x00, 0x6c, 0x54, 0x00, 0x00,
+ 0xc4, 0xdc, 0x00, 0x00, 0xc0, 0x44, 0x00, 0x00, 0x80, 0x39, 0x00, 0x00,
+ 0x80, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static const unsigned char moz_spinning_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x3b, 0x00, 0x00,
+ 0xfe, 0x7f, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, 0xfe, 0xfe, 0x00, 0x00,
+ 0xee, 0xff, 0x01, 0x00, 0xe4, 0xff, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00,
+ 0xc0, 0x7f, 0x00, 0x00, 0x80, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* MOZ_CURSOR_ZOOM_IN */
+static const unsigned char moz_zoom_in_bits[] = {
+ 0xf0, 0x00, 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00,
+ 0x62, 0x04, 0x00, 0x00, 0x61, 0x08, 0x00, 0x00, 0xf9, 0x09, 0x00, 0x00,
+ 0xf9, 0x09, 0x00, 0x00, 0x61, 0x08, 0x00, 0x00, 0x62, 0x04, 0x00, 0x00,
+ 0x02, 0x04, 0x00, 0x00, 0x0c, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static const unsigned char moz_zoom_in_mask_bits[] = {
+ 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xfc, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* MOZ_CURSOR_ZOOM_OUT */
+static const unsigned char moz_zoom_out_bits[] = {
+ 0xf0, 0x00, 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00,
+ 0x02, 0x04, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0xf9, 0x09, 0x00, 0x00,
+ 0xf9, 0x09, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00,
+ 0x02, 0x04, 0x00, 0x00, 0x0c, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static const unsigned char moz_zoom_out_mask_bits[] = {
+ 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xfc, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* MOZ_CURSOR_NOT_ALLOWED */
+static const unsigned char moz_not_allowed_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x1f, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00,
+ 0xf0, 0xf0, 0x00, 0x00, 0x38, 0xc0, 0x01, 0x00, 0x7c, 0x80, 0x03, 0x00,
+ 0xec, 0x00, 0x03, 0x00, 0xce, 0x01, 0x07, 0x00, 0x86, 0x03, 0x06, 0x00,
+ 0x06, 0x07, 0x06, 0x00, 0x06, 0x0e, 0x06, 0x00, 0x06, 0x1c, 0x06, 0x00,
+ 0x0e, 0x38, 0x07, 0x00, 0x0c, 0x70, 0x03, 0x00, 0x1c, 0xe0, 0x03, 0x00,
+ 0x38, 0xc0, 0x01, 0x00, 0xf0, 0xf0, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00,
+ 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static const unsigned char moz_not_allowed_mask_bits[] = {
+ 0x80, 0x1f, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00, 0xf0, 0xff, 0x00, 0x00,
+ 0xf8, 0xff, 0x01, 0x00, 0xfc, 0xf0, 0x03, 0x00, 0xfe, 0xc0, 0x07, 0x00,
+ 0xfe, 0x81, 0x07, 0x00, 0xff, 0x83, 0x0f, 0x00, 0xcf, 0x07, 0x0f, 0x00,
+ 0x8f, 0x0f, 0x0f, 0x00, 0x0f, 0x1f, 0x0f, 0x00, 0x0f, 0x3e, 0x0f, 0x00,
+ 0x1f, 0xfc, 0x0f, 0x00, 0x1e, 0xf8, 0x07, 0x00, 0x3e, 0xf0, 0x07, 0x00,
+ 0xfc, 0xf0, 0x03, 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf0, 0xff, 0x00, 0x00,
+ 0xe0, 0x7f, 0x00, 0x00, 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* MOZ_CURSOR_VERTICAL_TEXT */
+static const unsigned char moz_vertical_text_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00,
+ 0x06, 0x60, 0x00, 0x00, 0xfc, 0x3f, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00,
+ 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static const unsigned char moz_vertical_text_mask_bits[] = {
+ 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
+ 0x0f, 0xf0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* MOZ_CURSOR_NESW_RESIZE */
+static const unsigned char moz_nesw_resize_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0xbe, 0x00, 0x00, 0x00, 0xbc, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00,
+ 0x02, 0xb4, 0x00, 0x00, 0x02, 0xa2, 0x00, 0x00, 0x02, 0x81, 0x00, 0x00,
+ 0x8a, 0x80, 0x00, 0x00, 0x5a, 0x80, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00,
+ 0x7a, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static const unsigned char moz_nesw_resize_mask_bits[] = {
+ 0xc0, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00,
+ 0x00, 0xff, 0x01, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfc, 0x01, 0x00,
+ 0x07, 0xfe, 0x01, 0x00, 0x07, 0xf7, 0x01, 0x00, 0x8f, 0xe3, 0x01, 0x00,
+ 0xdf, 0xc1, 0x01, 0x00, 0xff, 0xc0, 0x01, 0x00, 0x7f, 0x00, 0x00, 0x00,
+ 0xff, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* MOZ_CURSOR_NWSE_RESIZE */
+static const unsigned char moz_nwse_resize_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0xfa, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00,
+ 0x5a, 0x80, 0x00, 0x00, 0x8a, 0x80, 0x00, 0x00, 0x02, 0x81, 0x00, 0x00,
+ 0x02, 0xa2, 0x00, 0x00, 0x02, 0xb4, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00,
+ 0x00, 0xbc, 0x00, 0x00, 0x00, 0xbe, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0xc0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static const unsigned char moz_nwse_resize_mask_bits[] = {
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00,
+ 0xff, 0xc0, 0x01, 0x00, 0xdf, 0xc1, 0x01, 0x00, 0x8f, 0xe3, 0x01, 0x00,
+ 0x07, 0xf7, 0x01, 0x00, 0x07, 0xfe, 0x01, 0x00, 0x00, 0xfc, 0x01, 0x00,
+ 0x00, 0xfe, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00,
+ 0xc0, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/* MOZ_CURSOR_NONE */
+static const unsigned char moz_none_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static const unsigned char moz_none_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+enum {
+ MOZ_CURSOR_HAND_GRAB,
+ MOZ_CURSOR_HAND_GRABBING,
+ MOZ_CURSOR_COPY,
+ MOZ_CURSOR_ALIAS,
+ MOZ_CURSOR_CONTEXT_MENU,
+ MOZ_CURSOR_SPINNING,
+ MOZ_CURSOR_ZOOM_IN,
+ MOZ_CURSOR_ZOOM_OUT,
+ MOZ_CURSOR_NOT_ALLOWED,
+ MOZ_CURSOR_VERTICAL_TEXT,
+ MOZ_CURSOR_NESW_RESIZE,
+ MOZ_CURSOR_NWSE_RESIZE,
+ MOZ_CURSOR_NONE
+};
+
+// create custom pixmap cursor. The hash values must stay in sync with the
+// bitmap data above. To see the hash function, have a look at XcursorImageHash
+// in libXcursor
+static const nsGtkCursor GtkCursors[] = {
+ { moz_hand_grab_bits, moz_hand_grab_mask_bits, 10, 10, "5aca4d189052212118709018842178c0" },
+ { moz_hand_grabbing_bits, moz_hand_grabbing_mask_bits, 10, 10, "208530c400c041818281048008011002" },
+ { moz_copy_bits, moz_copy_mask_bits, 2, 2, "08ffe1cb5fe6fc01f906f1c063814ccf" },
+ { moz_alias_bits, moz_alias_mask_bits, 2, 2, "0876e1c15ff2fc01f906f1c363074c0f" },
+ { moz_menu_bits, moz_menu_mask_bits, 2, 2, "08ffe1e65f80fcfdf9fff11263e74c48" },
+ { moz_spinning_bits, moz_spinning_mask_bits, 2, 2, "08e8e1c95fe2fc01f976f1e063a24ccd" },
+ { moz_zoom_in_bits, moz_zoom_in_mask_bits, 6, 6, "f41c0e382c94c0958e07017e42b00462" },
+ { moz_zoom_out_bits, moz_zoom_out_mask_bits, 6, 6, "f41c0e382c97c0938e07017e42800402" },
+ { moz_not_allowed_bits, moz_not_allowed_mask_bits, 9, 9, "03b6e0fcb3499374a867d041f52298f0" },
+ { moz_vertical_text_bits, moz_vertical_text_mask_bits, 8, 4, "048008013003cff3c00c801001200000" },
+ { moz_nesw_resize_bits, moz_nesw_resize_mask_bits, 8, 8, "50585d75b494802d0151028115016902" },
+ { moz_nwse_resize_bits, moz_nwse_resize_mask_bits, 8, 8, "38c5dff7c7b8962045400281044508d2" },
+ { moz_none_bits, moz_none_mask_bits, 0, 0, nullptr }
+};
+
+#endif /* nsGtkCursors_h__ */
diff --git a/widget/gtk/nsGtkKeyUtils.cpp b/widget/gtk/nsGtkKeyUtils.cpp
new file mode 100644
index 000000000..ce55cf18e
--- /dev/null
+++ b/widget/gtk/nsGtkKeyUtils.cpp
@@ -0,0 +1,1483 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "mozilla/Logging.h"
+
+#include "nsGtkKeyUtils.h"
+
+#include <gdk/gdkkeysyms.h>
+#include <algorithm>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#if (MOZ_WIDGET_GTK == 3)
+#include <gdk/gdkkeysyms-compat.h>
+#endif
+#include <X11/XKBlib.h>
+#include "WidgetUtils.h"
+#include "keysym2ucs.h"
+#include "nsContentUtils.h"
+#include "nsGtkUtils.h"
+#include "nsIBidiKeyboard.h"
+#include "nsServiceManagerUtils.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEvents.h"
+
+namespace mozilla {
+namespace widget {
+
+LazyLogModule gKeymapWrapperLog("KeymapWrapperWidgets");
+
+#define IS_ASCII_ALPHABETICAL(key) \
+ ((('a' <= key) && (key <= 'z')) || (('A' <= key) && (key <= 'Z')))
+
+#define MOZ_MODIFIER_KEYS "MozKeymapWrapper"
+
+KeymapWrapper* KeymapWrapper::sInstance = nullptr;
+guint KeymapWrapper::sLastRepeatableHardwareKeyCode = 0;
+KeymapWrapper::RepeatState KeymapWrapper::sRepeatState =
+ KeymapWrapper::NOT_PRESSED;
+
+static const char* GetBoolName(bool aBool)
+{
+ return aBool ? "TRUE" : "FALSE";
+}
+
+/* static */ const char*
+KeymapWrapper::GetModifierName(Modifier aModifier)
+{
+ switch (aModifier) {
+ case CAPS_LOCK: return "CapsLock";
+ case NUM_LOCK: return "NumLock";
+ case SCROLL_LOCK: return "ScrollLock";
+ case SHIFT: return "Shift";
+ case CTRL: return "Ctrl";
+ case ALT: return "Alt";
+ case SUPER: return "Super";
+ case HYPER: return "Hyper";
+ case META: return "Meta";
+ case LEVEL3: return "Level3";
+ case LEVEL5: return "Level5";
+ case NOT_MODIFIER: return "NotModifier";
+ default: return "InvalidValue";
+ }
+}
+
+/* static */ KeymapWrapper::Modifier
+KeymapWrapper::GetModifierForGDKKeyval(guint aGdkKeyval)
+{
+ switch (aGdkKeyval) {
+ case GDK_Caps_Lock: return CAPS_LOCK;
+ case GDK_Num_Lock: return NUM_LOCK;
+ case GDK_Scroll_Lock: return SCROLL_LOCK;
+ case GDK_Shift_Lock:
+ case GDK_Shift_L:
+ case GDK_Shift_R: return SHIFT;
+ case GDK_Control_L:
+ case GDK_Control_R: return CTRL;
+ case GDK_Alt_L:
+ case GDK_Alt_R: return ALT;
+ case GDK_Super_L:
+ case GDK_Super_R: return SUPER;
+ case GDK_Hyper_L:
+ case GDK_Hyper_R: return HYPER;
+ case GDK_Meta_L:
+ case GDK_Meta_R: return META;
+ case GDK_ISO_Level3_Shift:
+ case GDK_Mode_switch: return LEVEL3;
+ case GDK_ISO_Level5_Shift: return LEVEL5;
+ default: return NOT_MODIFIER;
+ }
+}
+
+guint
+KeymapWrapper::GetModifierMask(Modifier aModifier) const
+{
+ switch (aModifier) {
+ case CAPS_LOCK:
+ return GDK_LOCK_MASK;
+ case NUM_LOCK:
+ return mModifierMasks[INDEX_NUM_LOCK];
+ case SCROLL_LOCK:
+ return mModifierMasks[INDEX_SCROLL_LOCK];
+ case SHIFT:
+ return GDK_SHIFT_MASK;
+ case CTRL:
+ return GDK_CONTROL_MASK;
+ case ALT:
+ return mModifierMasks[INDEX_ALT];
+ case SUPER:
+ return mModifierMasks[INDEX_SUPER];
+ case HYPER:
+ return mModifierMasks[INDEX_HYPER];
+ case META:
+ return mModifierMasks[INDEX_META];
+ case LEVEL3:
+ return mModifierMasks[INDEX_LEVEL3];
+ case LEVEL5:
+ return mModifierMasks[INDEX_LEVEL5];
+ default:
+ return 0;
+ }
+}
+
+KeymapWrapper::ModifierKey*
+KeymapWrapper::GetModifierKey(guint aHardwareKeycode)
+{
+ for (uint32_t i = 0; i < mModifierKeys.Length(); i++) {
+ ModifierKey& key = mModifierKeys[i];
+ if (key.mHardwareKeycode == aHardwareKeycode) {
+ return &key;
+ }
+ }
+ return nullptr;
+}
+
+/* static */ KeymapWrapper*
+KeymapWrapper::GetInstance()
+{
+ if (sInstance) {
+ sInstance->Init();
+ return sInstance;
+ }
+
+ sInstance = new KeymapWrapper();
+ return sInstance;
+}
+
+/* static */ void
+KeymapWrapper::Shutdown()
+{
+ if (sInstance) {
+ delete sInstance;
+ sInstance = nullptr;
+ }
+}
+
+KeymapWrapper::KeymapWrapper() :
+ mInitialized(false), mGdkKeymap(gdk_keymap_get_default()),
+ mXKBBaseEventCode(0)
+{
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p Constructor, mGdkKeymap=%p",
+ this, mGdkKeymap));
+
+ g_object_ref(mGdkKeymap);
+ g_signal_connect(mGdkKeymap, "keys-changed",
+ (GCallback)OnKeysChanged, this);
+ g_signal_connect(mGdkKeymap, "direction-changed",
+ (GCallback)OnDirectionChanged, this);
+
+ if (GDK_IS_X11_DISPLAY(gdk_display_get_default()))
+ InitXKBExtension();
+
+ Init();
+}
+
+void
+KeymapWrapper::Init()
+{
+ if (mInitialized) {
+ return;
+ }
+ mInitialized = true;
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p Init, mGdkKeymap=%p",
+ this, mGdkKeymap));
+
+ mModifierKeys.Clear();
+ memset(mModifierMasks, 0, sizeof(mModifierMasks));
+
+ if (GDK_IS_X11_DISPLAY(gdk_display_get_default()))
+ InitBySystemSettings();
+
+ gdk_window_add_filter(nullptr, FilterEvents, this);
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p Init, 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",
+ this,
+ GetModifierMask(CAPS_LOCK), GetModifierMask(NUM_LOCK),
+ GetModifierMask(SCROLL_LOCK), GetModifierMask(LEVEL3),
+ GetModifierMask(LEVEL5),
+ GetModifierMask(SHIFT), GetModifierMask(CTRL),
+ GetModifierMask(ALT), GetModifierMask(META),
+ GetModifierMask(SUPER), GetModifierMask(HYPER)));
+}
+
+void
+KeymapWrapper::InitXKBExtension()
+{
+ PodZero(&mKeyboardState);
+
+ int xkbMajorVer = XkbMajorVersion;
+ int xkbMinorVer = XkbMinorVersion;
+ if (!XkbLibraryVersion(&xkbMajorVer, &xkbMinorVer)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XkbLibraryVersion()", this));
+ return;
+ }
+
+ Display* display =
+ gdk_x11_display_get_xdisplay(gdk_display_get_default());
+
+ // XkbLibraryVersion() set xkbMajorVer and xkbMinorVer to that of the
+ // library, which may be newer than what is required of the server in
+ // XkbQueryExtension(), so these variables should be reset to
+ // XkbMajorVersion and XkbMinorVersion before the XkbQueryExtension call.
+ xkbMajorVer = XkbMajorVersion;
+ xkbMinorVer = XkbMinorVersion;
+ int opcode, baseErrorCode;
+ if (!XkbQueryExtension(display, &opcode, &mXKBBaseEventCode, &baseErrorCode,
+ &xkbMajorVer, &xkbMinorVer)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XkbQueryExtension(), display=0x%p", this, display));
+ return;
+ }
+
+ if (!XkbSelectEventDetails(display, XkbUseCoreKbd, XkbStateNotify,
+ XkbModifierStateMask, XkbModifierStateMask)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XkbSelectEventDetails() for XModifierStateMask, display=0x%p",
+ this, display));
+ return;
+ }
+
+ if (!XkbSelectEventDetails(display, XkbUseCoreKbd, XkbControlsNotify,
+ XkbPerKeyRepeatMask, XkbPerKeyRepeatMask)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XkbSelectEventDetails() for XkbControlsNotify, display=0x%p",
+ this, display));
+ return;
+ }
+
+ if (!XGetKeyboardControl(display, &mKeyboardState)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XGetKeyboardControl(), display=0x%p",
+ this, display));
+ return;
+ }
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitXKBExtension, Succeeded", this));
+}
+
+void
+KeymapWrapper::InitBySystemSettings()
+{
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitBySystemSettings, mGdkKeymap=%p",
+ this, mGdkKeymap));
+
+ Display* display =
+ gdk_x11_display_get_xdisplay(gdk_display_get_default());
+
+ int min_keycode = 0;
+ int max_keycode = 0;
+ XDisplayKeycodes(display, &min_keycode, &max_keycode);
+
+ int keysyms_per_keycode = 0;
+ KeySym* xkeymap = XGetKeyboardMapping(display, min_keycode,
+ max_keycode - min_keycode + 1,
+ &keysyms_per_keycode);
+ if (!xkeymap) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitBySystemSettings, "
+ "Failed due to null xkeymap", this));
+ return;
+ }
+
+ XModifierKeymap* xmodmap = XGetModifierMapping(display);
+ if (!xmodmap) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitBySystemSettings, "
+ "Failed due to null xmodmap", this));
+ XFree(xkeymap);
+ return;
+ }
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitBySystemSettings, min_keycode=%d, "
+ "max_keycode=%d, keysyms_per_keycode=%d, max_keypermod=%d",
+ this, min_keycode, max_keycode, keysyms_per_keycode,
+ xmodmap->max_keypermod));
+
+ // The modifiermap member of the XModifierKeymap structure contains 8 sets
+ // of max_keypermod KeyCodes, one for each modifier in the order Shift,
+ // Lock, Control, Mod1, Mod2, Mod3, Mod4, and Mod5.
+ // Only nonzero KeyCodes have meaning in each set, and zero KeyCodes are
+ // ignored.
+
+ // Note that two or more modifiers may use one modifier flag. E.g.,
+ // on Ubuntu 10.10, Alt and Meta share the Mod1 in default settings.
+ // And also Super and Hyper share the Mod4. In such cases, we need to
+ // decide which modifier flag means one of DOM modifiers.
+
+ // mod[0] is Modifier introduced by Mod1.
+ Modifier mod[5];
+ int32_t foundLevel[5];
+ for (uint32_t i = 0; i < ArrayLength(mod); i++) {
+ mod[i] = NOT_MODIFIER;
+ foundLevel[i] = INT32_MAX;
+ }
+ const uint32_t map_size = 8 * xmodmap->max_keypermod;
+ for (uint32_t i = 0; i < map_size; i++) {
+ KeyCode keycode = xmodmap->modifiermap[i];
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitBySystemSettings, "
+ " i=%d, keycode=0x%08X",
+ this, i, keycode));
+ if (!keycode || keycode < min_keycode || keycode > max_keycode) {
+ continue;
+ }
+
+ ModifierKey* modifierKey = GetModifierKey(keycode);
+ if (!modifierKey) {
+ modifierKey = mModifierKeys.AppendElement(ModifierKey(keycode));
+ }
+
+ const KeySym* syms =
+ xkeymap + (keycode - min_keycode) * keysyms_per_keycode;
+ const uint32_t bit = i / xmodmap->max_keypermod;
+ modifierKey->mMask |= 1 << bit;
+
+ // We need to know the meaning of Mod1, Mod2, Mod3, Mod4 and Mod5.
+ // Let's skip if current map is for others.
+ if (bit < 3) {
+ continue;
+ }
+
+ const int32_t modIndex = bit - 3;
+ for (int32_t j = 0; j < keysyms_per_keycode; j++) {
+ Modifier modifier = GetModifierForGDKKeyval(syms[j]);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitBySystemSettings, "
+ " Mod%d, j=%d, syms[j]=%s(0x%X), modifier=%s",
+ this, modIndex + 1, j, gdk_keyval_name(syms[j]), syms[j],
+ GetModifierName(modifier)));
+
+ switch (modifier) {
+ case NOT_MODIFIER:
+ // Don't overwrite the stored information with
+ // NOT_MODIFIER.
+ break;
+ case CAPS_LOCK:
+ case SHIFT:
+ case CTRL:
+ // Ignore the modifiers defined in GDK spec. They shouldn't
+ // be mapped to Mod1-5 because they must not work on native
+ // GTK applications.
+ break;
+ default:
+ // If new modifier is found in higher level than stored
+ // value, we don't need to overwrite it.
+ if (j > foundLevel[modIndex]) {
+ break;
+ }
+ // If new modifier is more important than stored value,
+ // we should overwrite it with new modifier.
+ if (j == foundLevel[modIndex]) {
+ mod[modIndex] = std::min(modifier, mod[modIndex]);
+ break;
+ }
+ foundLevel[modIndex] = j;
+ mod[modIndex] = modifier;
+ break;
+ }
+ }
+ }
+
+ for (uint32_t i = 0; i < COUNT_OF_MODIFIER_INDEX; i++) {
+ Modifier modifier;
+ switch (i) {
+ case INDEX_NUM_LOCK:
+ modifier = NUM_LOCK;
+ break;
+ case INDEX_SCROLL_LOCK:
+ modifier = SCROLL_LOCK;
+ break;
+ case INDEX_ALT:
+ modifier = ALT;
+ break;
+ case INDEX_META:
+ modifier = META;
+ break;
+ case INDEX_SUPER:
+ modifier = SUPER;
+ break;
+ case INDEX_HYPER:
+ modifier = HYPER;
+ break;
+ case INDEX_LEVEL3:
+ modifier = LEVEL3;
+ break;
+ case INDEX_LEVEL5:
+ modifier = LEVEL5;
+ break;
+ default:
+ MOZ_CRASH("All indexes must be handled here");
+ }
+ for (uint32_t j = 0; j < ArrayLength(mod); j++) {
+ if (modifier == mod[j]) {
+ mModifierMasks[i] |= 1 << (j + 3);
+ }
+ }
+ }
+
+ XFreeModifiermap(xmodmap);
+ XFree(xkeymap);
+}
+
+KeymapWrapper::~KeymapWrapper()
+{
+ gdk_window_remove_filter(nullptr, FilterEvents, this);
+ g_signal_handlers_disconnect_by_func(mGdkKeymap,
+ FuncToGpointer(OnKeysChanged), this);
+ g_signal_handlers_disconnect_by_func(mGdkKeymap,
+ FuncToGpointer(OnDirectionChanged), this);
+ g_object_unref(mGdkKeymap);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p Destructor", this));
+}
+
+/* static */ GdkFilterReturn
+KeymapWrapper::FilterEvents(GdkXEvent* aXEvent,
+ GdkEvent* aGdkEvent,
+ gpointer aData)
+{
+ XEvent* xEvent = static_cast<XEvent*>(aXEvent);
+ switch (xEvent->type) {
+ case KeyPress: {
+ // If the key doesn't support auto repeat, ignore the event because
+ // even if such key (e.g., Shift) is pressed during auto repeat of
+ // anoter key, it doesn't stop the auto repeat.
+ KeymapWrapper* self = static_cast<KeymapWrapper*>(aData);
+ if (!self->IsAutoRepeatableKey(xEvent->xkey.keycode)) {
+ break;
+ }
+ if (sRepeatState == NOT_PRESSED) {
+ sRepeatState = FIRST_PRESS;
+ } else if (sLastRepeatableHardwareKeyCode == xEvent->xkey.keycode) {
+ sRepeatState = REPEATING;
+ } else {
+ // If a different key is pressed while another key is pressed,
+ // auto repeat system repeats only the last pressed key.
+ // So, setting new keycode and setting repeat state as first key
+ // press should work fine.
+ sRepeatState = FIRST_PRESS;
+ }
+ sLastRepeatableHardwareKeyCode = xEvent->xkey.keycode;
+ break;
+ }
+ case KeyRelease: {
+ if (sLastRepeatableHardwareKeyCode != xEvent->xkey.keycode) {
+ // This case means the key release event is caused by
+ // a non-repeatable key such as Shift or a repeatable key that
+ // was pressed before sLastRepeatableHardwareKeyCode was
+ // pressed.
+ break;
+ }
+ sRepeatState = NOT_PRESSED;
+ break;
+ }
+ case FocusOut: {
+ // At moving focus, we should reset keyboard repeat state.
+ // Strictly, this causes incorrect behavior. However, this
+ // correctness must be enough for web applications.
+ sRepeatState = NOT_PRESSED;
+ break;
+ }
+ default: {
+ KeymapWrapper* self = static_cast<KeymapWrapper*>(aData);
+ if (xEvent->type != self->mXKBBaseEventCode) {
+ break;
+ }
+ XkbEvent* xkbEvent = (XkbEvent*)xEvent;
+ if (xkbEvent->any.xkb_type != XkbControlsNotify ||
+ !(xkbEvent->ctrls.changed_ctrls & XkbPerKeyRepeatMask)) {
+ break;
+ }
+ if (!XGetKeyboardControl(xkbEvent->any.display,
+ &self->mKeyboardState)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p FilterEvents failed due to failure "
+ "of XGetKeyboardControl(), display=0x%p",
+ self, xkbEvent->any.display));
+ }
+ break;
+ }
+ }
+
+ return GDK_FILTER_CONTINUE;
+}
+
+static void
+ResetBidiKeyboard()
+{
+ // Reset the bidi keyboard settings for the new GdkKeymap
+ nsCOMPtr<nsIBidiKeyboard> bidiKeyboard = nsContentUtils::GetBidiKeyboard();
+ if (bidiKeyboard) {
+ bidiKeyboard->Reset();
+ }
+ WidgetUtils::SendBidiKeyboardInfoToContent();
+}
+
+/* static */ void
+KeymapWrapper::OnKeysChanged(GdkKeymap *aGdkKeymap,
+ KeymapWrapper* aKeymapWrapper)
+{
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("OnKeysChanged, aGdkKeymap=%p, aKeymapWrapper=%p",
+ aGdkKeymap, aKeymapWrapper));
+
+ MOZ_ASSERT(sInstance == aKeymapWrapper,
+ "This instance must be the singleton instance");
+
+ // We cannot reintialize here becasue we don't have GdkWindow which is using
+ // the GdkKeymap. We'll reinitialize it when next GetInstance() is called.
+ sInstance->mInitialized = false;
+ ResetBidiKeyboard();
+}
+
+// static
+void
+KeymapWrapper::OnDirectionChanged(GdkKeymap *aGdkKeymap,
+ KeymapWrapper* aKeymapWrapper)
+{
+ // XXX
+ // A lot of diretion-changed signal might be fired on switching bidi
+ // keyboard when using both ibus (with arabic layout) and fcitx (with IME).
+ // See https://github.com/fcitx/fcitx/issues/257
+ //
+ // Also, when using ibus, switching to IM might not cause this signal.
+ // See https://github.com/ibus/ibus/issues/1848
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("OnDirectionChanged, aGdkKeymap=%p, aKeymapWrapper=%p",
+ aGdkKeymap, aKeymapWrapper));
+
+ ResetBidiKeyboard();
+}
+
+/* static */ guint
+KeymapWrapper::GetCurrentModifierState()
+{
+ GdkModifierType modifiers;
+ gdk_display_get_pointer(gdk_display_get_default(),
+ nullptr, nullptr, nullptr, &modifiers);
+ return static_cast<guint>(modifiers);
+}
+
+/* static */ bool
+KeymapWrapper::AreModifiersCurrentlyActive(Modifiers aModifiers)
+{
+ guint modifierState = GetCurrentModifierState();
+ return AreModifiersActive(aModifiers, modifierState);
+}
+
+/* static */ bool
+KeymapWrapper::AreModifiersActive(Modifiers aModifiers,
+ guint aModifierState)
+{
+ NS_ENSURE_TRUE(aModifiers, false);
+
+ KeymapWrapper* keymapWrapper = GetInstance();
+ for (uint32_t i = 0; i < sizeof(Modifier) * 8 && aModifiers; i++) {
+ Modifier modifier = static_cast<Modifier>(1 << i);
+ if (!(aModifiers & modifier)) {
+ continue;
+ }
+ if (!(aModifierState & keymapWrapper->GetModifierMask(modifier))) {
+ return false;
+ }
+ aModifiers &= ~modifier;
+ }
+ return true;
+}
+
+/* static */ void
+KeymapWrapper::InitInputEvent(WidgetInputEvent& aInputEvent,
+ guint aModifierState)
+{
+ KeymapWrapper* keymapWrapper = GetInstance();
+
+ aInputEvent.mModifiers = 0;
+ // DOM Meta key should be TRUE only on Mac. We need to discuss this
+ // issue later.
+ if (keymapWrapper->AreModifiersActive(SHIFT, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_SHIFT;
+ }
+ if (keymapWrapper->AreModifiersActive(CTRL, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_CONTROL;
+ }
+ if (keymapWrapper->AreModifiersActive(ALT, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_ALT;
+ }
+ if (keymapWrapper->AreModifiersActive(META, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_META;
+ }
+ if (keymapWrapper->AreModifiersActive(SUPER, aModifierState) ||
+ keymapWrapper->AreModifiersActive(HYPER, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_OS;
+ }
+ if (keymapWrapper->AreModifiersActive(LEVEL3, aModifierState) ||
+ keymapWrapper->AreModifiersActive(LEVEL5, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_ALTGRAPH;
+ }
+ if (keymapWrapper->AreModifiersActive(CAPS_LOCK, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_CAPSLOCK;
+ }
+ if (keymapWrapper->AreModifiersActive(NUM_LOCK, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_NUMLOCK;
+ }
+ if (keymapWrapper->AreModifiersActive(SCROLL_LOCK, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_SCROLLLOCK;
+ }
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Debug,
+ ("%p InitInputEvent, aModifierState=0x%08X, "
+ "aInputEvent.mModifiers=0x%04X (Shift: %s, Control: %s, Alt: %s, "
+ "Meta: %s, OS: %s, AltGr: %s, "
+ "CapsLock: %s, NumLock: %s, ScrollLock: %s)",
+ keymapWrapper, aModifierState, aInputEvent.mModifiers,
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_SHIFT),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_CONTROL),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_ALT),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_META),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_OS),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_ALTGRAPH),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_CAPSLOCK),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_NUMLOCK),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_SCROLLLOCK)));
+
+ switch(aInputEvent.mClass) {
+ case eMouseEventClass:
+ case eMouseScrollEventClass:
+ case eWheelEventClass:
+ case eDragEventClass:
+ case eSimpleGestureEventClass:
+ break;
+ default:
+ return;
+ }
+
+ WidgetMouseEventBase& mouseEvent = *aInputEvent.AsMouseEventBase();
+ mouseEvent.buttons = 0;
+ if (aModifierState & GDK_BUTTON1_MASK) {
+ mouseEvent.buttons |= WidgetMouseEvent::eLeftButtonFlag;
+ }
+ if (aModifierState & GDK_BUTTON3_MASK) {
+ mouseEvent.buttons |= WidgetMouseEvent::eRightButtonFlag;
+ }
+ if (aModifierState & GDK_BUTTON2_MASK) {
+ mouseEvent.buttons |= WidgetMouseEvent::eMiddleButtonFlag;
+ }
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Debug,
+ ("%p InitInputEvent, aInputEvent has buttons, "
+ "aInputEvent.buttons=0x%04X (Left: %s, Right: %s, Middle: %s, "
+ "4th (BACK): %s, 5th (FORWARD): %s)",
+ keymapWrapper, mouseEvent.buttons,
+ GetBoolName(mouseEvent.buttons & WidgetMouseEvent::eLeftButtonFlag),
+ GetBoolName(mouseEvent.buttons & WidgetMouseEvent::eRightButtonFlag),
+ GetBoolName(mouseEvent.buttons & WidgetMouseEvent::eMiddleButtonFlag),
+ GetBoolName(mouseEvent.buttons & WidgetMouseEvent::e4thButtonFlag),
+ GetBoolName(mouseEvent.buttons & WidgetMouseEvent::e5thButtonFlag)));
+}
+
+/* static */ uint32_t
+KeymapWrapper::ComputeDOMKeyCode(const GdkEventKey* aGdkKeyEvent)
+{
+ // If the keyval indicates it's a modifier key, we should use unshifted
+ // key's modifier keyval.
+ guint keyval = aGdkKeyEvent->keyval;
+ if (GetModifierForGDKKeyval(keyval)) {
+ // But if the keyval without modifiers isn't a modifier key, we
+ // shouldn't use it. E.g., Japanese keyboard layout's
+ // Shift + Eisu-Toggle key is CapsLock. This is an actual rare case,
+ // Windows uses different keycode for a physical key for different
+ // shift key state.
+ guint keyvalWithoutModifier = GetGDKKeyvalWithoutModifier(aGdkKeyEvent);
+ if (GetModifierForGDKKeyval(keyvalWithoutModifier)) {
+ keyval = keyvalWithoutModifier;
+ }
+ // Note that the modifier keycode and activating or deactivating
+ // modifier flag may be mismatched, but it's okay. If a DOM key
+ // event handler is testing a keydown event, it's more likely being
+ // used to test which key is being pressed than to test which
+ // modifier will become active. So, if we computed DOM keycode
+ // from modifier flag which were changing by the physical key, then
+ // there would be no other way for the user to generate the original
+ // keycode.
+ uint32_t DOMKeyCode = GetDOMKeyCodeFromKeyPairs(keyval);
+ NS_ASSERTION(DOMKeyCode, "All modifier keys must have a DOM keycode");
+ return DOMKeyCode;
+ }
+
+ // If the key isn't printable, let's look at the key pairs.
+ uint32_t charCode = GetCharCodeFor(aGdkKeyEvent);
+ if (!charCode) {
+ // Always use unshifted keycode for the non-printable key.
+ // XXX It might be better to decide DOM keycode from all keyvals of
+ // the hardware keycode. However, I think that it's too excessive.
+ guint keyvalWithoutModifier = GetGDKKeyvalWithoutModifier(aGdkKeyEvent);
+ uint32_t DOMKeyCode = GetDOMKeyCodeFromKeyPairs(keyvalWithoutModifier);
+ if (!DOMKeyCode) {
+ // If the unshifted keyval couldn't be mapped to a DOM keycode,
+ // we should fallback to legacy logic, so, we should recompute with
+ // the keyval with aGdkKeyEvent.
+ DOMKeyCode = GetDOMKeyCodeFromKeyPairs(keyval);
+ }
+ return DOMKeyCode;
+ }
+
+ // printable numpad keys should be resolved here.
+ switch (keyval) {
+ case GDK_KP_Multiply: return NS_VK_MULTIPLY;
+ case GDK_KP_Add: return NS_VK_ADD;
+ case GDK_KP_Separator: return NS_VK_SEPARATOR;
+ case GDK_KP_Subtract: return NS_VK_SUBTRACT;
+ case GDK_KP_Decimal: return NS_VK_DECIMAL;
+ case GDK_KP_Divide: return NS_VK_DIVIDE;
+ case GDK_KP_0: return NS_VK_NUMPAD0;
+ case GDK_KP_1: return NS_VK_NUMPAD1;
+ case GDK_KP_2: return NS_VK_NUMPAD2;
+ case GDK_KP_3: return NS_VK_NUMPAD3;
+ case GDK_KP_4: return NS_VK_NUMPAD4;
+ case GDK_KP_5: return NS_VK_NUMPAD5;
+ case GDK_KP_6: return NS_VK_NUMPAD6;
+ case GDK_KP_7: return NS_VK_NUMPAD7;
+ case GDK_KP_8: return NS_VK_NUMPAD8;
+ case GDK_KP_9: return NS_VK_NUMPAD9;
+ }
+
+ KeymapWrapper* keymapWrapper = GetInstance();
+
+ // Ignore all modifier state except NumLock.
+ guint baseState =
+ (aGdkKeyEvent->state & keymapWrapper->GetModifierMask(NUM_LOCK));
+
+ // Basically, we should use unmodified character for deciding our keyCode.
+ uint32_t unmodifiedChar =
+ keymapWrapper->GetCharCodeFor(aGdkKeyEvent, baseState,
+ aGdkKeyEvent->group);
+ if (IsBasicLatinLetterOrNumeral(unmodifiedChar)) {
+ // If the unmodified character is an ASCII alphabet or an ASCII
+ // numeric, it's the best hint for deciding our keyCode.
+ return WidgetUtils::ComputeKeyCodeFromChar(unmodifiedChar);
+ }
+
+ // If the unmodified character is not an ASCII character, that means we
+ // couldn't find the hint. We should reset it.
+ if (unmodifiedChar > 0x7F) {
+ unmodifiedChar = 0;
+ }
+
+ // Retry with shifted keycode.
+ guint shiftState = (baseState | keymapWrapper->GetModifierMask(SHIFT));
+ uint32_t shiftedChar =
+ keymapWrapper->GetCharCodeFor(aGdkKeyEvent, shiftState,
+ aGdkKeyEvent->group);
+ if (IsBasicLatinLetterOrNumeral(shiftedChar)) {
+ // A shifted character can be an ASCII alphabet on Hebrew keyboard
+ // layout. And also shifted character can be an ASCII numeric on
+ // AZERTY keyboad layout. Then, it's a good hint for deciding our
+ // keyCode.
+ return WidgetUtils::ComputeKeyCodeFromChar(shiftedChar);
+ }
+
+ // If the shifted unmodified character isn't an ASCII character, we should
+ // discard it too.
+ if (shiftedChar > 0x7F) {
+ shiftedChar = 0;
+ }
+
+ // If current keyboard layout isn't ASCII alphabet inputtable layout,
+ // look for ASCII alphabet inputtable keyboard layout. If the key
+ // inputs an ASCII alphabet or an ASCII numeric, we should use it
+ // for deciding our keyCode.
+ // Note that it's important not to use alternative keyboard layout for ASCII
+ // alphabet inputabble keyboard layout because the keycode for the key with
+ // alternative keyboard layout may conflict with another key on current
+ // keyboard layout.
+ if (!keymapWrapper->IsLatinGroup(aGdkKeyEvent->group)) {
+ gint minGroup = keymapWrapper->GetFirstLatinGroup();
+ if (minGroup >= 0) {
+ uint32_t unmodCharLatin =
+ keymapWrapper->GetCharCodeFor(aGdkKeyEvent, baseState,
+ minGroup);
+ if (IsBasicLatinLetterOrNumeral(unmodCharLatin)) {
+ // If the unmodified character is an ASCII alphabet or
+ // an ASCII numeric, we should use it for the keyCode.
+ return WidgetUtils::ComputeKeyCodeFromChar(unmodCharLatin);
+ }
+ uint32_t shiftedCharLatin =
+ keymapWrapper->GetCharCodeFor(aGdkKeyEvent, shiftState,
+ minGroup);
+ if (IsBasicLatinLetterOrNumeral(shiftedCharLatin)) {
+ // If the shifted character is an ASCII alphabet or an ASCII
+ // numeric, we should use it for the keyCode.
+ return WidgetUtils::ComputeKeyCodeFromChar(shiftedCharLatin);
+ }
+ }
+ }
+
+ // If unmodified character is in ASCII range, use it. Otherwise, use
+ // shifted character.
+ if (!unmodifiedChar && !shiftedChar) {
+ return 0;
+ }
+ return WidgetUtils::ComputeKeyCodeFromChar(
+ unmodifiedChar ? unmodifiedChar : shiftedChar);
+}
+
+KeyNameIndex
+KeymapWrapper::ComputeDOMKeyNameIndex(const GdkEventKey* aGdkKeyEvent)
+{
+ switch (aGdkKeyEvent->keyval) {
+
+#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
+ case aNativeKey: return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ break;
+ }
+
+ return KEY_NAME_INDEX_Unidentified;
+}
+
+/* static */ CodeNameIndex
+KeymapWrapper::ComputeDOMCodeNameIndex(const GdkEventKey* aGdkKeyEvent)
+{
+ switch (aGdkKeyEvent->hardware_keycode) {
+
+#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
+ case aNativeKey: return aCodeNameIndex;
+
+#include "NativeKeyToDOMCodeName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
+
+ default:
+ break;
+ }
+
+ return CODE_NAME_INDEX_UNKNOWN;
+}
+
+/* static */ void
+KeymapWrapper::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent)
+{
+ KeymapWrapper* keymapWrapper = GetInstance();
+
+ aKeyEvent.mCodeNameIndex = ComputeDOMCodeNameIndex(aGdkKeyEvent);
+ MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
+ aKeyEvent.mKeyNameIndex =
+ keymapWrapper->ComputeDOMKeyNameIndex(aGdkKeyEvent);
+ if (aKeyEvent.mKeyNameIndex == KEY_NAME_INDEX_Unidentified) {
+ uint32_t charCode = GetCharCodeFor(aGdkKeyEvent);
+ if (!charCode) {
+ charCode = keymapWrapper->GetUnmodifiedCharCodeFor(aGdkKeyEvent);
+ }
+ if (charCode) {
+ aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+ MOZ_ASSERT(aKeyEvent.mKeyValue.IsEmpty(),
+ "Uninitialized mKeyValue must be empty");
+ AppendUCS4ToUTF16(charCode, aKeyEvent.mKeyValue);
+ }
+ }
+ aKeyEvent.mKeyCode = ComputeDOMKeyCode(aGdkKeyEvent);
+
+ if (aKeyEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING ||
+ aKeyEvent.mMessage != eKeyPress) {
+ aKeyEvent.mKeyCode = ComputeDOMKeyCode(aGdkKeyEvent);
+ } else {
+ aKeyEvent.mKeyCode = 0;
+ }
+
+ // NOTE: The state of given key event indicates adjacent state of
+ // modifier keys. E.g., even if the event is Shift key press event,
+ // the bit for Shift is still false. By the same token, even if the
+ // event is Shift key release event, the bit for Shift is still true.
+ // Unfortunately, gdk_keyboard_get_modifiers() returns current modifier
+ // state. It means if there're some pending modifier key press or
+ // key release events, the result isn't what we want.
+ guint modifierState = aGdkKeyEvent->state;
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ if (aGdkKeyEvent->is_modifier && GDK_IS_X11_DISPLAY(gdkDisplay)) {
+ Display* display =
+ gdk_x11_display_get_xdisplay(gdkDisplay);
+ if (XEventsQueued(display, QueuedAfterReading)) {
+ XEvent nextEvent;
+ XPeekEvent(display, &nextEvent);
+ if (nextEvent.type == keymapWrapper->mXKBBaseEventCode) {
+ XkbEvent* XKBEvent = (XkbEvent*)&nextEvent;
+ if (XKBEvent->any.xkb_type == XkbStateNotify) {
+ XkbStateNotifyEvent* stateNotifyEvent =
+ (XkbStateNotifyEvent*)XKBEvent;
+ modifierState &= ~0xFF;
+ modifierState |= stateNotifyEvent->lookup_mods;
+ }
+ }
+ }
+ }
+ InitInputEvent(aKeyEvent, modifierState);
+
+ switch (aGdkKeyEvent->keyval) {
+ case GDK_Shift_L:
+ case GDK_Control_L:
+ case GDK_Alt_L:
+ case GDK_Super_L:
+ case GDK_Hyper_L:
+ case GDK_Meta_L:
+ aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
+ break;
+
+ case GDK_Shift_R:
+ case GDK_Control_R:
+ case GDK_Alt_R:
+ case GDK_Super_R:
+ case GDK_Hyper_R:
+ case GDK_Meta_R:
+ aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
+ break;
+
+ case GDK_KP_0:
+ case GDK_KP_1:
+ case GDK_KP_2:
+ case GDK_KP_3:
+ case GDK_KP_4:
+ case GDK_KP_5:
+ case GDK_KP_6:
+ case GDK_KP_7:
+ case GDK_KP_8:
+ case GDK_KP_9:
+ case GDK_KP_Space:
+ case GDK_KP_Tab:
+ case GDK_KP_Enter:
+ case GDK_KP_F1:
+ case GDK_KP_F2:
+ case GDK_KP_F3:
+ case GDK_KP_F4:
+ case GDK_KP_Home:
+ case GDK_KP_Left:
+ case GDK_KP_Up:
+ case GDK_KP_Right:
+ case GDK_KP_Down:
+ case GDK_KP_Prior: // same as GDK_KP_Page_Up
+ case GDK_KP_Next: // same as GDK_KP_Page_Down
+ case GDK_KP_End:
+ case GDK_KP_Begin:
+ case GDK_KP_Insert:
+ case GDK_KP_Delete:
+ case GDK_KP_Equal:
+ case GDK_KP_Multiply:
+ case GDK_KP_Add:
+ case GDK_KP_Separator:
+ case GDK_KP_Subtract:
+ case GDK_KP_Decimal:
+ case GDK_KP_Divide:
+ aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+ break;
+
+ default:
+ aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
+ break;
+ }
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitKeyEvent, modifierState=0x%08X "
+ "aGdkKeyEvent={ type=%s, keyval=%s(0x%X), state=0x%08X, "
+ "hardware_keycode=0x%08X, is_modifier=%s } "
+ "aKeyEvent={ message=%s, isShift=%s, isControl=%s, "
+ "isAlt=%s, isMeta=%s }",
+ keymapWrapper, modifierState,
+ ((aGdkKeyEvent->type == GDK_KEY_PRESS) ?
+ "GDK_KEY_PRESS" : "GDK_KEY_RELEASE"),
+ gdk_keyval_name(aGdkKeyEvent->keyval),
+ aGdkKeyEvent->keyval, aGdkKeyEvent->state,
+ aGdkKeyEvent->hardware_keycode,
+ GetBoolName(aGdkKeyEvent->is_modifier),
+ ((aKeyEvent.mMessage == eKeyDown) ? "eKeyDown" :
+ (aKeyEvent.mMessage == eKeyPress) ? "eKeyPress" : "eKeyUp"),
+ GetBoolName(aKeyEvent.IsShift()), GetBoolName(aKeyEvent.IsControl()),
+ GetBoolName(aKeyEvent.IsAlt()), GetBoolName(aKeyEvent.IsMeta())));
+
+ // The transformations above and in gdk for the keyval are not invertible
+ // so link to the GdkEvent (which will vanish soon after return from the
+ // event callback) to give plugins access to hardware_keycode and state.
+ // (An XEvent would be nice but the GdkEvent is good enough.)
+ aKeyEvent.mPluginEvent.Copy(*aGdkKeyEvent);
+ aKeyEvent.mTime = aGdkKeyEvent->time;
+ aKeyEvent.mNativeKeyEvent = static_cast<void*>(aGdkKeyEvent);
+ aKeyEvent.mIsRepeat = sRepeatState == REPEATING &&
+ aGdkKeyEvent->hardware_keycode == sLastRepeatableHardwareKeyCode;
+}
+
+/* static */ uint32_t
+KeymapWrapper::GetCharCodeFor(const GdkEventKey *aGdkKeyEvent)
+{
+ // Anything above 0xf000 is considered a non-printable
+ // Exception: directly encoded UCS characters
+ if (aGdkKeyEvent->keyval > 0xf000 &&
+ (aGdkKeyEvent->keyval & 0xff000000) != 0x01000000) {
+ // Keypad keys are an exception: they return a value different
+ // from their non-keypad equivalents, but mozilla doesn't distinguish.
+ switch (aGdkKeyEvent->keyval) {
+ case GDK_KP_Space: return ' ';
+ case GDK_KP_Equal: return '=';
+ case GDK_KP_Multiply: return '*';
+ case GDK_KP_Add: return '+';
+ case GDK_KP_Separator: return ',';
+ case GDK_KP_Subtract: return '-';
+ case GDK_KP_Decimal: return '.';
+ case GDK_KP_Divide: return '/';
+ case GDK_KP_0: return '0';
+ case GDK_KP_1: return '1';
+ case GDK_KP_2: return '2';
+ case GDK_KP_3: return '3';
+ case GDK_KP_4: return '4';
+ case GDK_KP_5: return '5';
+ case GDK_KP_6: return '6';
+ case GDK_KP_7: return '7';
+ case GDK_KP_8: return '8';
+ case GDK_KP_9: return '9';
+ default: return 0; // non-printables
+ }
+ }
+
+ static const long MAX_UNICODE = 0x10FFFF;
+
+ // we're supposedly printable, let's try to convert
+ long ucs = keysym2ucs(aGdkKeyEvent->keyval);
+ if ((ucs != -1) && (ucs < MAX_UNICODE)) {
+ return ucs;
+ }
+
+ // I guess we couldn't convert
+ return 0;
+}
+
+uint32_t
+KeymapWrapper::GetCharCodeFor(const GdkEventKey *aGdkKeyEvent,
+ guint aModifierState,
+ gint aGroup)
+{
+ guint keyval;
+ if (!gdk_keymap_translate_keyboard_state(mGdkKeymap,
+ aGdkKeyEvent->hardware_keycode,
+ GdkModifierType(aModifierState),
+ aGroup, &keyval, nullptr, nullptr, nullptr)) {
+ return 0;
+ }
+ GdkEventKey tmpEvent = *aGdkKeyEvent;
+ tmpEvent.state = aModifierState;
+ tmpEvent.keyval = keyval;
+ tmpEvent.group = aGroup;
+ return GetCharCodeFor(&tmpEvent);
+}
+
+uint32_t
+KeymapWrapper::GetUnmodifiedCharCodeFor(const GdkEventKey* aGdkKeyEvent)
+{
+ guint state = aGdkKeyEvent->state &
+ (GetModifierMask(SHIFT) | GetModifierMask(CAPS_LOCK) |
+ GetModifierMask(NUM_LOCK) | GetModifierMask(SCROLL_LOCK) |
+ GetModifierMask(LEVEL3) | GetModifierMask(LEVEL5));
+ uint32_t charCode = GetCharCodeFor(aGdkKeyEvent, GdkModifierType(state),
+ aGdkKeyEvent->group);
+ if (charCode) {
+ return charCode;
+ }
+ // If no character is mapped to the key when Level3 Shift or Level5 Shift
+ // is active, let's return a character which is inputted by the key without
+ // Level3 nor Level5 Shift.
+ guint stateWithoutAltGraph =
+ state & ~(GetModifierMask(LEVEL3) | GetModifierMask(LEVEL5));
+ if (state == stateWithoutAltGraph) {
+ return 0;
+ }
+ return GetCharCodeFor(aGdkKeyEvent, GdkModifierType(stateWithoutAltGraph),
+ aGdkKeyEvent->group);
+}
+
+gint
+KeymapWrapper::GetKeyLevel(GdkEventKey *aGdkKeyEvent)
+{
+ gint level;
+ if (!gdk_keymap_translate_keyboard_state(mGdkKeymap,
+ aGdkKeyEvent->hardware_keycode,
+ GdkModifierType(aGdkKeyEvent->state),
+ aGdkKeyEvent->group, nullptr, nullptr, &level, nullptr)) {
+ return -1;
+ }
+ return level;
+}
+
+gint
+KeymapWrapper::GetFirstLatinGroup()
+{
+ GdkKeymapKey *keys;
+ gint count;
+ gint minGroup = -1;
+ if (gdk_keymap_get_entries_for_keyval(mGdkKeymap, GDK_a, &keys, &count)) {
+ // find the minimum number group for latin inputtable layout
+ for (gint i = 0; i < count && minGroup != 0; ++i) {
+ if (keys[i].level != 0 && keys[i].level != 1) {
+ continue;
+ }
+ if (minGroup >= 0 && keys[i].group > minGroup) {
+ continue;
+ }
+ minGroup = keys[i].group;
+ }
+ g_free(keys);
+ }
+ return minGroup;
+}
+
+bool
+KeymapWrapper::IsLatinGroup(guint8 aGroup)
+{
+ GdkKeymapKey *keys;
+ gint count;
+ bool result = false;
+ if (gdk_keymap_get_entries_for_keyval(mGdkKeymap, GDK_a, &keys, &count)) {
+ for (gint i = 0; i < count; ++i) {
+ if (keys[i].level != 0 && keys[i].level != 1) {
+ continue;
+ }
+ if (keys[i].group == aGroup) {
+ result = true;
+ break;
+ }
+ }
+ g_free(keys);
+ }
+ return result;
+}
+
+bool
+KeymapWrapper::IsAutoRepeatableKey(guint aHardwareKeyCode)
+{
+ uint8_t indexOfArray = aHardwareKeyCode / 8;
+ MOZ_ASSERT(indexOfArray < ArrayLength(mKeyboardState.auto_repeats),
+ "invalid index");
+ char bitMask = 1 << (aHardwareKeyCode % 8);
+ return (mKeyboardState.auto_repeats[indexOfArray] & bitMask) != 0;
+}
+
+/* static */ bool
+KeymapWrapper::IsBasicLatinLetterOrNumeral(uint32_t aCharCode)
+{
+ return (aCharCode >= 'a' && aCharCode <= 'z') ||
+ (aCharCode >= 'A' && aCharCode <= 'Z') ||
+ (aCharCode >= '0' && aCharCode <= '9');
+}
+
+/* static */ guint
+KeymapWrapper::GetGDKKeyvalWithoutModifier(const GdkEventKey *aGdkKeyEvent)
+{
+ KeymapWrapper* keymapWrapper = GetInstance();
+ guint state =
+ (aGdkKeyEvent->state & keymapWrapper->GetModifierMask(NUM_LOCK));
+ guint keyval;
+ if (!gdk_keymap_translate_keyboard_state(keymapWrapper->mGdkKeymap,
+ aGdkKeyEvent->hardware_keycode, GdkModifierType(state),
+ aGdkKeyEvent->group, &keyval, nullptr, nullptr, nullptr)) {
+ return 0;
+ }
+ return keyval;
+}
+
+/* static */ uint32_t
+KeymapWrapper::GetDOMKeyCodeFromKeyPairs(guint aGdkKeyval)
+{
+ switch (aGdkKeyval) {
+ case GDK_Cancel: return NS_VK_CANCEL;
+ case GDK_BackSpace: return NS_VK_BACK;
+ case GDK_Tab:
+ case GDK_ISO_Left_Tab: return NS_VK_TAB;
+ case GDK_Clear: return NS_VK_CLEAR;
+ case GDK_Return: return NS_VK_RETURN;
+ case GDK_Shift_L:
+ case GDK_Shift_R:
+ case GDK_Shift_Lock: return NS_VK_SHIFT;
+ case GDK_Control_L:
+ case GDK_Control_R: return NS_VK_CONTROL;
+ case GDK_Alt_L:
+ case GDK_Alt_R: return NS_VK_ALT;
+ case GDK_Meta_L:
+ case GDK_Meta_R: return NS_VK_META;
+
+ // Assume that Super or Hyper is always mapped to physical Win key.
+ case GDK_Super_L:
+ case GDK_Super_R:
+ case GDK_Hyper_L:
+ case GDK_Hyper_R: return NS_VK_WIN;
+
+ // GTK's AltGraph key is similar to Mac's Option (Alt) key. However,
+ // unfortunately, browsers on Mac are using NS_VK_ALT for it even though
+ // it's really different from Alt key on Windows.
+ // On the other hand, GTK's AltGrapsh keys are really different from
+ // Alt key. However, there is no AltGrapsh key on Windows. On Windows,
+ // both Ctrl and Alt keys are pressed internally when AltGr key is
+ // pressed. For some languages' users, AltGraph key is important, so,
+ // web applications on such locale may want to know AltGraph key press.
+ // Therefore, we should map AltGr keycode for them only on GTK.
+ case GDK_ISO_Level3_Shift:
+ case GDK_ISO_Level5_Shift:
+ // We assume that Mode_switch is always used for level3 shift.
+ case GDK_Mode_switch: return NS_VK_ALTGR;
+
+ case GDK_Pause: return NS_VK_PAUSE;
+ case GDK_Caps_Lock: return NS_VK_CAPS_LOCK;
+ case GDK_Kana_Lock:
+ case GDK_Kana_Shift: return NS_VK_KANA;
+ case GDK_Hangul: return NS_VK_HANGUL;
+ // case GDK_XXX: return NS_VK_JUNJA;
+ // case GDK_XXX: return NS_VK_FINAL;
+ case GDK_Hangul_Hanja: return NS_VK_HANJA;
+ case GDK_Kanji: return NS_VK_KANJI;
+ case GDK_Escape: return NS_VK_ESCAPE;
+ case GDK_Henkan: return NS_VK_CONVERT;
+ case GDK_Muhenkan: return NS_VK_NONCONVERT;
+ // case GDK_XXX: return NS_VK_ACCEPT;
+ // case GDK_XXX: return NS_VK_MODECHANGE;
+ case GDK_Page_Up: return NS_VK_PAGE_UP;
+ case GDK_Page_Down: return NS_VK_PAGE_DOWN;
+ case GDK_End: return NS_VK_END;
+ case GDK_Home: return NS_VK_HOME;
+ case GDK_Left: return NS_VK_LEFT;
+ case GDK_Up: return NS_VK_UP;
+ case GDK_Right: return NS_VK_RIGHT;
+ case GDK_Down: return NS_VK_DOWN;
+ case GDK_Select: return NS_VK_SELECT;
+ case GDK_Print: return NS_VK_PRINT;
+ case GDK_Execute: return NS_VK_EXECUTE;
+ case GDK_Insert: return NS_VK_INSERT;
+ case GDK_Delete: return NS_VK_DELETE;
+ case GDK_Help: return NS_VK_HELP;
+
+ // keypad keys
+ case GDK_KP_Left: return NS_VK_LEFT;
+ case GDK_KP_Right: return NS_VK_RIGHT;
+ case GDK_KP_Up: return NS_VK_UP;
+ case GDK_KP_Down: return NS_VK_DOWN;
+ case GDK_KP_Page_Up: return NS_VK_PAGE_UP;
+ // Not sure what these are
+ // case GDK_KP_Prior: return NS_VK_;
+ // case GDK_KP_Next: return NS_VK_;
+ case GDK_KP_Begin: return NS_VK_CLEAR; // Num-unlocked 5
+ case GDK_KP_Page_Down: return NS_VK_PAGE_DOWN;
+ case GDK_KP_Home: return NS_VK_HOME;
+ case GDK_KP_End: return NS_VK_END;
+ case GDK_KP_Insert: return NS_VK_INSERT;
+ case GDK_KP_Delete: return NS_VK_DELETE;
+ case GDK_KP_Enter: return NS_VK_RETURN;
+
+ case GDK_Num_Lock: return NS_VK_NUM_LOCK;
+ case GDK_Scroll_Lock: return NS_VK_SCROLL_LOCK;
+
+ // Function keys
+ case GDK_F1: return NS_VK_F1;
+ case GDK_F2: return NS_VK_F2;
+ case GDK_F3: return NS_VK_F3;
+ case GDK_F4: return NS_VK_F4;
+ case GDK_F5: return NS_VK_F5;
+ case GDK_F6: return NS_VK_F6;
+ case GDK_F7: return NS_VK_F7;
+ case GDK_F8: return NS_VK_F8;
+ case GDK_F9: return NS_VK_F9;
+ case GDK_F10: return NS_VK_F10;
+ case GDK_F11: return NS_VK_F11;
+ case GDK_F12: return NS_VK_F12;
+ case GDK_F13: return NS_VK_F13;
+ case GDK_F14: return NS_VK_F14;
+ case GDK_F15: return NS_VK_F15;
+ case GDK_F16: return NS_VK_F16;
+ case GDK_F17: return NS_VK_F17;
+ case GDK_F18: return NS_VK_F18;
+ case GDK_F19: return NS_VK_F19;
+ case GDK_F20: return NS_VK_F20;
+ case GDK_F21: return NS_VK_F21;
+ case GDK_F22: return NS_VK_F22;
+ case GDK_F23: return NS_VK_F23;
+ case GDK_F24: return NS_VK_F24;
+
+ // context menu key, keysym 0xff67, typically keycode 117 on 105-key
+ // (Microsoft) x86 keyboards, located between right 'Windows' key and
+ // right Ctrl key
+ case GDK_Menu: return NS_VK_CONTEXT_MENU;
+ case GDK_Sleep: return NS_VK_SLEEP;
+
+ case GDK_3270_Attn: return NS_VK_ATTN;
+ case GDK_3270_CursorSelect: return NS_VK_CRSEL;
+ case GDK_3270_ExSelect: return NS_VK_EXSEL;
+ case GDK_3270_EraseEOF: return NS_VK_EREOF;
+ case GDK_3270_Play: return NS_VK_PLAY;
+ // case GDK_XXX: return NS_VK_ZOOM;
+ case GDK_3270_PA1: return NS_VK_PA1;
+
+ // map Sun Keyboard special keysyms on to NS_VK keys
+
+ // Sun F11 key generates SunF36(0x1005ff10) keysym
+ case 0x1005ff10: return NS_VK_F11;
+ // Sun F12 key generates SunF37(0x1005ff11) keysym
+ case 0x1005ff11: return NS_VK_F12;
+ default: return 0;
+ }
+}
+
+void
+KeymapWrapper::WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent)
+{
+ GetInstance()->WillDispatchKeyboardEventInternal(aKeyEvent, aGdkKeyEvent);
+}
+
+void
+KeymapWrapper::WillDispatchKeyboardEventInternal(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent)
+{
+ uint32_t charCode = GetCharCodeFor(aGdkKeyEvent);
+ if (!charCode) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p WillDispatchKeyboardEventInternal, "
+ "mKeyCode=0x%02X, charCode=0x%08X",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode));
+ return;
+ }
+
+ // The mCharCode was set from mKeyValue. However, for example, when Ctrl key
+ // is pressed, its value should indicate an ASCII character for backward
+ // compatibility rather than inputting character without the modifiers.
+ // Therefore, we need to modify mCharCode value here.
+ aKeyEvent.SetCharCode(charCode);
+
+ gint level = GetKeyLevel(aGdkKeyEvent);
+ if (level != 0 && level != 1) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p WillDispatchKeyboardEventInternal, "
+ "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level));
+ return;
+ }
+
+ guint baseState = aGdkKeyEvent->state &
+ ~(GetModifierMask(SHIFT) | GetModifierMask(CTRL) |
+ GetModifierMask(ALT) | GetModifierMask(META) |
+ GetModifierMask(SUPER) | GetModifierMask(HYPER));
+
+ // We shold send both shifted char and unshifted char, all keyboard layout
+ // users can use all keys. Don't change event.mCharCode. On some keyboard
+ // layouts, Ctrl/Alt/Meta keys are used for inputting some characters.
+ AlternativeCharCode altCharCodes(0, 0);
+ // unshifted charcode of current keyboard layout.
+ altCharCodes.mUnshiftedCharCode =
+ GetCharCodeFor(aGdkKeyEvent, baseState, aGdkKeyEvent->group);
+ bool isLatin = (altCharCodes.mUnshiftedCharCode <= 0xFF);
+ // shifted charcode of current keyboard layout.
+ altCharCodes.mShiftedCharCode =
+ GetCharCodeFor(aGdkKeyEvent,
+ baseState | GetModifierMask(SHIFT),
+ aGdkKeyEvent->group);
+ isLatin = isLatin && (altCharCodes.mShiftedCharCode <= 0xFF);
+ if (altCharCodes.mUnshiftedCharCode || altCharCodes.mShiftedCharCode) {
+ aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
+ }
+
+ bool needLatinKeyCodes = !isLatin;
+ if (!needLatinKeyCodes) {
+ needLatinKeyCodes =
+ (IS_ASCII_ALPHABETICAL(altCharCodes.mUnshiftedCharCode) !=
+ IS_ASCII_ALPHABETICAL(altCharCodes.mShiftedCharCode));
+ }
+
+ // If current keyboard layout can input Latin characters, we don't need
+ // more information.
+ if (!needLatinKeyCodes) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p WillDispatchKeyboardEventInternal, "
+ "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d, altCharCodes={ "
+ "mUnshiftedCharCode=0x%08X, mShiftedCharCode=0x%08X }",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level,
+ altCharCodes.mUnshiftedCharCode, altCharCodes.mShiftedCharCode));
+ return;
+ }
+
+ // Next, find Latin inputtable keyboard layout.
+ gint minGroup = GetFirstLatinGroup();
+ if (minGroup < 0) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p WillDispatchKeyboardEventInternal, "
+ "Latin keyboard layout isn't found: "
+ "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d, "
+ "altCharCodes={ mUnshiftedCharCode=0x%08X, "
+ "mShiftedCharCode=0x%08X }",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level,
+ altCharCodes.mUnshiftedCharCode, altCharCodes.mShiftedCharCode));
+ return;
+ }
+
+ AlternativeCharCode altLatinCharCodes(0, 0);
+ uint32_t unmodifiedCh =
+ aKeyEvent.IsShift() ? altCharCodes.mShiftedCharCode :
+ altCharCodes.mUnshiftedCharCode;
+
+ // unshifted charcode of found keyboard layout.
+ uint32_t ch = GetCharCodeFor(aGdkKeyEvent, baseState, minGroup);
+ altLatinCharCodes.mUnshiftedCharCode =
+ IsBasicLatinLetterOrNumeral(ch) ? ch : 0;
+ // shifted charcode of found keyboard layout.
+ ch = GetCharCodeFor(aGdkKeyEvent,
+ baseState | GetModifierMask(SHIFT),
+ minGroup);
+ altLatinCharCodes.mShiftedCharCode =
+ IsBasicLatinLetterOrNumeral(ch) ? ch : 0;
+ if (altLatinCharCodes.mUnshiftedCharCode ||
+ altLatinCharCodes.mShiftedCharCode) {
+ aKeyEvent.mAlternativeCharCodes.AppendElement(altLatinCharCodes);
+ }
+ // If the mCharCode is not Latin, and the level is 0 or 1, we should
+ // replace the mCharCode to Latin char if Alt and Meta keys are not
+ // pressed. (Alt should be sent the localized char for accesskey
+ // like handling of Web Applications.)
+ ch = aKeyEvent.IsShift() ? altLatinCharCodes.mShiftedCharCode :
+ altLatinCharCodes.mUnshiftedCharCode;
+ if (ch && !(aKeyEvent.IsAlt() || aKeyEvent.IsMeta()) &&
+ charCode == unmodifiedCh) {
+ aKeyEvent.SetCharCode(ch);
+ }
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p WillDispatchKeyboardEventInternal, "
+ "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d, minGroup=%d, "
+ "altCharCodes={ mUnshiftedCharCode=0x%08X, "
+ "mShiftedCharCode=0x%08X } "
+ "altLatinCharCodes={ mUnshiftedCharCode=0x%08X, "
+ "mShiftedCharCode=0x%08X }",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level, minGroup,
+ altCharCodes.mUnshiftedCharCode, altCharCodes.mShiftedCharCode,
+ altLatinCharCodes.mUnshiftedCharCode,
+ altLatinCharCodes.mShiftedCharCode));
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/nsGtkKeyUtils.h b/widget/gtk/nsGtkKeyUtils.h
new file mode 100644
index 000000000..67528d817
--- /dev/null
+++ b/widget/gtk/nsGtkKeyUtils.h
@@ -0,0 +1,360 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 __nsGdkKeyUtils_h__
+#define __nsGdkKeyUtils_h__
+
+#include "nsTArray.h"
+#include "mozilla/EventForwards.h"
+
+#include <gdk/gdk.h>
+#include <X11/XKBlib.h>
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * KeymapWrapper is a wrapper class of GdkKeymap. GdkKeymap doesn't support
+ * all our needs, therefore, we need to access lower level APIs.
+ * But such code is usually complex and might be slow. Against such issues,
+ * we should cache some information.
+ *
+ * This class provides only static methods. The methods is using internal
+ * singleton instance which is initialized by default GdkKeymap. When the
+ * GdkKeymap is destroyed, the singleton instance will be destroyed.
+ */
+
+class KeymapWrapper
+{
+public:
+ /**
+ * Compute an our DOM keycode from a GDK keyval.
+ */
+ static uint32_t ComputeDOMKeyCode(const GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * Compute a DOM key name index from aGdkKeyEvent.
+ */
+ KeyNameIndex ComputeDOMKeyNameIndex(const GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * Compute a DOM code name index from aGdkKeyEvent.
+ */
+ static CodeNameIndex ComputeDOMCodeNameIndex(
+ const GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * Modifier is list of modifiers which we support in widget level.
+ */
+ enum Modifier {
+ NOT_MODIFIER = 0x0000,
+ CAPS_LOCK = 0x0001,
+ NUM_LOCK = 0x0002,
+ SCROLL_LOCK = 0x0004,
+ SHIFT = 0x0008,
+ CTRL = 0x0010,
+ ALT = 0x0020,
+ META = 0x0040,
+ SUPER = 0x0080,
+ HYPER = 0x0100,
+ LEVEL3 = 0x0200,
+ LEVEL5 = 0x0400
+ };
+
+ /**
+ * Modifiers is used for combination of Modifier.
+ * E.g., |Modifiers modifiers = (SHIFT | CTRL);| means Shift and Ctrl.
+ */
+ typedef uint32_t Modifiers;
+
+ /**
+ * GetCurrentModifierState() returns current modifier key state.
+ * The "current" means actual state of hardware keyboard when this is
+ * called. I.e., if some key events are not still dispatched by GDK,
+ * the state may mismatch with GdkEventKey::state.
+ *
+ * @return Current modifier key state.
+ */
+ static guint GetCurrentModifierState();
+
+ /**
+ * AreModifiersCurrentlyActive() checks the "current" modifier state
+ * on aGdkWindow with the keymap of the singleton instance.
+ *
+ * @param aModifiers One or more of Modifier values except
+ * NOT_MODIFIER.
+ * @return TRUE if all of modifieres in aModifiers are
+ * active. Otherwise, FALSE.
+ */
+ static bool AreModifiersCurrentlyActive(Modifiers aModifiers);
+
+ /**
+ * AreModifiersActive() just checks whether aModifierState indicates
+ * all modifiers in aModifiers are active or not.
+ *
+ * @param aModifiers One or more of Modifier values except
+ * NOT_MODIFIER.
+ * @param aModifierState GDK's modifier states.
+ * @return TRUE if aGdkModifierType indecates all of
+ * modifiers in aModifier are active.
+ * Otherwise, FALSE.
+ */
+ static bool AreModifiersActive(Modifiers aModifiers,
+ guint aModifierState);
+
+ /**
+ * InitInputEvent() initializes the aInputEvent with aModifierState.
+ */
+ static void InitInputEvent(WidgetInputEvent& aInputEvent,
+ guint aModifierState);
+
+ /**
+ * InitKeyEvent() intializes aKeyEvent's modifier key related members
+ * and keycode related values.
+ *
+ * @param aKeyEvent It's an WidgetKeyboardEvent which needs to be
+ * initialized.
+ * @param aGdkKeyEvent A native GDK key event.
+ */
+ static void InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * WillDispatchKeyboardEvent() is called via
+ * TextEventDispatcherListener::WillDispatchKeyboardEvent().
+ *
+ * @param aKeyEvent An instance of KeyboardEvent which will be
+ * dispatched. This method should set charCode
+ * and alternative char codes if it's necessary.
+ * @param aGdkKeyEvent A GdkEventKey instance which caused the
+ * aKeyEvent.
+ */
+ static void WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * Destroys the singleton KeymapWrapper instance, if it exists.
+ */
+ static void Shutdown();
+
+protected:
+
+ /**
+ * GetInstance() returns a KeymapWrapper instance.
+ *
+ * @return A singleton instance of KeymapWrapper.
+ */
+ static KeymapWrapper* GetInstance();
+
+ KeymapWrapper();
+ ~KeymapWrapper();
+
+ bool mInitialized;
+
+ /**
+ * Initializing methods.
+ */
+ void Init();
+ void InitXKBExtension();
+ void InitBySystemSettings();
+
+ /**
+ * mModifierKeys stores each hardware key information.
+ */
+ struct ModifierKey {
+ guint mHardwareKeycode;
+ guint mMask;
+
+ explicit ModifierKey(guint aHardwareKeycode) :
+ mHardwareKeycode(aHardwareKeycode), mMask(0)
+ {
+ }
+ };
+ nsTArray<ModifierKey> mModifierKeys;
+
+ /**
+ * GetModifierKey() returns modifier key information of the hardware
+ * keycode. If the key isn't a modifier key, returns nullptr.
+ */
+ ModifierKey* GetModifierKey(guint aHardwareKeycode);
+
+ /**
+ * mModifierMasks is bit masks for each modifier. The index should be one
+ * of ModifierIndex values.
+ */
+ enum ModifierIndex {
+ INDEX_NUM_LOCK,
+ INDEX_SCROLL_LOCK,
+ INDEX_ALT,
+ INDEX_META,
+ INDEX_SUPER,
+ INDEX_HYPER,
+ INDEX_LEVEL3,
+ INDEX_LEVEL5,
+ COUNT_OF_MODIFIER_INDEX
+ };
+ guint mModifierMasks[COUNT_OF_MODIFIER_INDEX];
+
+ guint GetModifierMask(Modifier aModifier) const;
+
+ /**
+ * @param aGdkKeyval A GDK defined modifier key value such as
+ * GDK_Shift_L.
+ * @return Returns Modifier values for aGdkKeyval.
+ * If the given key code isn't a modifier key,
+ * returns NOT_MODIFIER.
+ */
+ static Modifier GetModifierForGDKKeyval(guint aGdkKeyval);
+
+ static const char* GetModifierName(Modifier aModifier);
+
+ /**
+ * mGdkKeymap is a wrapped instance by this class.
+ */
+ GdkKeymap* mGdkKeymap;
+
+ /**
+ * The base event code of XKB extension.
+ */
+ int mXKBBaseEventCode;
+
+ /**
+ * Only auto_repeats[] stores valid value. If you need to use other
+ * members, you need to listen notification events for them.
+ * See a call of XkbSelectEventDetails() with XkbControlsNotify in
+ * InitXKBExtension().
+ */
+ XKeyboardState mKeyboardState;
+
+ /**
+ * Pointer of the singleton instance.
+ */
+ static KeymapWrapper* sInstance;
+
+ /**
+ * Auto key repeat management.
+ */
+ static guint sLastRepeatableHardwareKeyCode;
+ enum RepeatState
+ {
+ NOT_PRESSED,
+ FIRST_PRESS,
+ REPEATING
+ };
+ static RepeatState sRepeatState;
+
+ /**
+ * IsAutoRepeatableKey() returns true if the key supports auto repeat.
+ * Otherwise, false.
+ */
+ bool IsAutoRepeatableKey(guint aHardwareKeyCode);
+
+ /**
+ * Signal handlers.
+ */
+ static void OnKeysChanged(GdkKeymap* aKeymap, KeymapWrapper* aKeymapWrapper);
+ static void OnDirectionChanged(GdkKeymap *aGdkKeymap,
+ KeymapWrapper* aKeymapWrapper);
+
+ /**
+ * GetCharCodeFor() Computes what character is inputted by the key event
+ * with aModifierState and aGroup.
+ *
+ * @param aGdkKeyEvent Native key event, must not be nullptr.
+ * @param aModifierState Combination of GdkModifierType which you
+ * want to test with aGdkKeyEvent.
+ * @param aGroup Set group in the mGdkKeymap.
+ * @return charCode which is inputted by aGdkKeyEvent.
+ * If failed, this returns 0.
+ */
+ static uint32_t GetCharCodeFor(const GdkEventKey *aGdkKeyEvent);
+ uint32_t GetCharCodeFor(const GdkEventKey *aGdkKeyEvent,
+ guint aModifierState,
+ gint aGroup);
+
+ /**
+ * GetUnmodifiedCharCodeFor() computes what character is inputted by the
+ * key event without Ctrl/Alt/Meta/Super/Hyper modifiers.
+ * If Level3 or Level5 Shift causes no character input, this also ignores
+ * them.
+ *
+ * @param aGdkKeyEvent Native key event, must not be nullptr.
+ * @return charCode which is computed without modifiers
+ * which prevent text input.
+ */
+ uint32_t GetUnmodifiedCharCodeFor(const GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * GetKeyLevel() returns level of the aGdkKeyEvent in mGdkKeymap.
+ *
+ * @param aGdkKeyEvent Native key event, must not be nullptr.
+ * @return Using level. Typically, this is 0 or 1.
+ * If failed, this returns -1.
+ */
+ gint GetKeyLevel(GdkEventKey *aGdkKeyEvent);
+
+ /**
+ * GetFirstLatinGroup() returns group of mGdkKeymap which can input an
+ * ASCII character by GDK_A.
+ *
+ * @return group value of GdkEventKey.
+ */
+ gint GetFirstLatinGroup();
+
+ /**
+ * IsLatinGroup() checkes whether the keyboard layout of aGroup is
+ * ASCII alphabet inputtable or not.
+ *
+ * @param aGroup The group value of GdkEventKey.
+ * @return TRUE if the keyboard layout can input
+ * ASCII alphabet. Otherwise, FALSE.
+ */
+ bool IsLatinGroup(guint8 aGroup);
+
+ /**
+ * IsBasicLatinLetterOrNumeral() Checks whether the aCharCode is an
+ * alphabet or a numeric character in ASCII.
+ *
+ * @param aCharCode Charcode which you want to test.
+ * @return TRUE if aCharCode is an alphabet or a numeric
+ * in ASCII range. Otherwise, FALSE.
+ */
+ static bool IsBasicLatinLetterOrNumeral(uint32_t aCharCode);
+
+ /**
+ * GetGDKKeyvalWithoutModifier() returns the keyval for aGdkKeyEvent when
+ * ignoring the modifier state except NumLock. (NumLock is a key to change
+ * some key's meaning.)
+ */
+ static guint GetGDKKeyvalWithoutModifier(const GdkEventKey *aGdkKeyEvent);
+
+ /**
+ * GetDOMKeyCodeFromKeyPairs() returns DOM keycode for aGdkKeyval if
+ * it's in KeyPair table.
+ */
+ static uint32_t GetDOMKeyCodeFromKeyPairs(guint aGdkKeyval);
+
+ /**
+ * FilterEvents() listens all events on all our windows.
+ * Be careful, this may make damage to performance if you add expensive
+ * code in this method.
+ */
+ static GdkFilterReturn FilterEvents(GdkXEvent* aXEvent,
+ GdkEvent* aGdkEvent,
+ gpointer aData);
+
+ /**
+ * See the document of WillDispatchKeyboardEvent().
+ */
+ void WillDispatchKeyboardEventInternal(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __nsGdkKeyUtils_h__ */
diff --git a/widget/gtk/nsGtkUtils.h b/widget/gtk/nsGtkUtils.h
new file mode 100644
index 000000000..cb41ddaf7
--- /dev/null
+++ b/widget/gtk/nsGtkUtils.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 nsGtkUtils_h__
+#define nsGtkUtils_h__
+
+#include <glib.h>
+
+// Some gobject functions expect functions for gpointer arguments.
+// gpointer is void* but C++ doesn't like casting functions to void*.
+template<class T> static inline gpointer
+FuncToGpointer(T aFunction)
+{
+ return reinterpret_cast<gpointer>
+ (reinterpret_cast<uintptr_t>
+ // This cast just provides a warning if T is not a function.
+ (reinterpret_cast<void (*)()>(aFunction)));
+}
+
+#endif // nsGtkUtils_h__
diff --git a/widget/gtk/nsIImageToPixbuf.h b/widget/gtk/nsIImageToPixbuf.h
new file mode 100644
index 000000000..0faa1c6e8
--- /dev/null
+++ b/widget/gtk/nsIImageToPixbuf.h
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 NSIIMAGETOPIXBUF_H_
+#define NSIIMAGETOPIXBUF_H_
+
+#include "nsISupports.h"
+
+// dfa4ac93-83f2-4ab8-9b2a-0ff7022aebe2
+#define NSIIMAGETOPIXBUF_IID \
+{ 0xdfa4ac93, 0x83f2, 0x4ab8, \
+ { 0x9b, 0x2a, 0x0f, 0xf7, 0x02, 0x2a, 0xeb, 0xe2 } }
+
+class imgIContainer;
+typedef struct _GdkPixbuf GdkPixbuf;
+
+/**
+ * An interface that allows converting the current frame of an imgIContainer to a GdkPixbuf*.
+ */
+class nsIImageToPixbuf : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NSIIMAGETOPIXBUF_IID)
+
+ /**
+ * The return value, if not null, should be released as needed
+ * by the caller using g_object_unref.
+ */
+ NS_IMETHOD_(GdkPixbuf*) ConvertImageToPixbuf(imgIContainer* aImage) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIImageToPixbuf, NSIIMAGETOPIXBUF_IID)
+
+#endif
diff --git a/widget/gtk/nsIdleServiceGTK.cpp b/widget/gtk/nsIdleServiceGTK.cpp
new file mode 100644
index 000000000..6b6832c82
--- /dev/null
+++ b/widget/gtk/nsIdleServiceGTK.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include <gtk/gtk.h>
+
+#include "nsIdleServiceGTK.h"
+#include "nsIServiceManager.h"
+#include "nsDebug.h"
+#include "prlink.h"
+#include "mozilla/Logging.h"
+
+using mozilla::LogLevel;
+
+static PRLogModuleInfo* sIdleLog = nullptr;
+
+typedef bool (*_XScreenSaverQueryExtension_fn)(Display* dpy, int* event_base,
+ int* error_base);
+
+typedef XScreenSaverInfo* (*_XScreenSaverAllocInfo_fn)(void);
+
+typedef void (*_XScreenSaverQueryInfo_fn)(Display* dpy, Drawable drw,
+ XScreenSaverInfo *info);
+
+static bool sInitialized = false;
+static _XScreenSaverQueryExtension_fn _XSSQueryExtension = nullptr;
+static _XScreenSaverAllocInfo_fn _XSSAllocInfo = nullptr;
+static _XScreenSaverQueryInfo_fn _XSSQueryInfo = nullptr;
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsIdleServiceGTK, nsIdleService)
+
+static void Initialize()
+{
+ if (!GDK_IS_X11_DISPLAY(gdk_display_get_default()))
+ return;
+
+ // This will leak - See comments in ~nsIdleServiceGTK().
+ PRLibrary* xsslib = PR_LoadLibrary("libXss.so.1");
+ if (!xsslib) // ouch.
+ {
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to find libXss.so!\n"));
+ return;
+ }
+
+ _XSSQueryExtension = (_XScreenSaverQueryExtension_fn)
+ PR_FindFunctionSymbol(xsslib, "XScreenSaverQueryExtension");
+ _XSSAllocInfo = (_XScreenSaverAllocInfo_fn)
+ PR_FindFunctionSymbol(xsslib, "XScreenSaverAllocInfo");
+ _XSSQueryInfo = (_XScreenSaverQueryInfo_fn)
+ PR_FindFunctionSymbol(xsslib, "XScreenSaverQueryInfo");
+
+ if (!_XSSQueryExtension)
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to get XSSQueryExtension!\n"));
+ if (!_XSSAllocInfo)
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to get XSSAllocInfo!\n"));
+ if (!_XSSQueryInfo)
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to get XSSQueryInfo!\n"));
+
+ sInitialized = true;
+}
+
+nsIdleServiceGTK::nsIdleServiceGTK()
+ : mXssInfo(nullptr)
+{
+ if (!sIdleLog)
+ sIdleLog = PR_NewLogModule("nsIIdleService");
+
+ Initialize();
+}
+
+nsIdleServiceGTK::~nsIdleServiceGTK()
+{
+ if (mXssInfo)
+ XFree(mXssInfo);
+
+// It is not safe to unload libXScrnSaver until each display is closed because
+// the library registers callbacks through XESetCloseDisplay (Bug 397607).
+// (Also the library and its functions are scoped for the file not the object.)
+#if 0
+ if (xsslib) {
+ PR_UnloadLibrary(xsslib);
+ xsslib = nullptr;
+ }
+#endif
+}
+
+bool
+nsIdleServiceGTK::PollIdleTime(uint32_t *aIdleTime)
+{
+ if (!sInitialized) {
+ // For some reason, we could not find xscreensaver.
+ return false;
+ }
+
+ // Ask xscreensaver about idle time:
+ *aIdleTime = 0;
+
+ // We might not have a display (cf. in xpcshell)
+ Display *dplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+ if (!dplay) {
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("No display found!\n"));
+ return false;
+ }
+
+ if (!_XSSQueryExtension || !_XSSAllocInfo || !_XSSQueryInfo) {
+ return false;
+ }
+
+ int event_base, error_base;
+ if (_XSSQueryExtension(dplay, &event_base, &error_base))
+ {
+ if (!mXssInfo)
+ mXssInfo = _XSSAllocInfo();
+ if (!mXssInfo)
+ return false;
+ _XSSQueryInfo(dplay, GDK_ROOT_WINDOW(), mXssInfo);
+ *aIdleTime = mXssInfo->idle;
+ return true;
+ }
+ // If we get here, we couldn't get to XScreenSaver:
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("XSSQueryExtension returned false!\n"));
+ return false;
+}
+
+bool
+nsIdleServiceGTK::UsePollMode()
+{
+ return sInitialized;
+}
diff --git a/widget/gtk/nsIdleServiceGTK.h b/widget/gtk/nsIdleServiceGTK.h
new file mode 100644
index 000000000..01ae9268e
--- /dev/null
+++ b/widget/gtk/nsIdleServiceGTK.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 nsIdleServiceGTK_h__
+#define nsIdleServiceGTK_h__
+
+#include "nsIdleService.h"
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <gdk/gdkx.h>
+
+typedef struct {
+ Window window; // Screen saver window
+ int state; // ScreenSaver(Off,On,Disabled)
+ int kind; // ScreenSaver(Blanked,Internal,External)
+ unsigned long til_or_since; // milliseconds since/til screensaver kicks in
+ unsigned long idle; // milliseconds idle
+ unsigned long event_mask; // event stuff
+} XScreenSaverInfo;
+
+class nsIdleServiceGTK : public nsIdleService
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual bool PollIdleTime(uint32_t* aIdleTime) override;
+
+ static already_AddRefed<nsIdleServiceGTK> GetInstance()
+ {
+ RefPtr<nsIdleServiceGTK> idleService =
+ nsIdleService::GetInstance().downcast<nsIdleServiceGTK>();
+ if (!idleService) {
+ idleService = new nsIdleServiceGTK();
+ }
+
+ return idleService.forget();
+ }
+
+private:
+ ~nsIdleServiceGTK();
+ XScreenSaverInfo* mXssInfo;
+
+protected:
+ nsIdleServiceGTK();
+ virtual bool UsePollMode() override;
+};
+
+#endif // nsIdleServiceGTK_h__
diff --git a/widget/gtk/nsImageToPixbuf.cpp b/widget/gtk/nsImageToPixbuf.cpp
new file mode 100644
index 000000000..e06605b2b
--- /dev/null
+++ b/widget/gtk/nsImageToPixbuf.cpp
@@ -0,0 +1,120 @@
+/* vim:set sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "nsImageToPixbuf.h"
+
+#include "imgIContainer.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
+
+using mozilla::gfx::DataSourceSurface;
+using mozilla::gfx::SurfaceFormat;
+
+NS_IMPL_ISUPPORTS(nsImageToPixbuf, nsIImageToPixbuf)
+
+inline unsigned char
+unpremultiply (unsigned char color,
+ unsigned char alpha)
+{
+ if (alpha == 0)
+ return 0;
+ // plus alpha/2 to round instead of truncate
+ return (color * 255 + alpha / 2) / alpha;
+}
+
+NS_IMETHODIMP_(GdkPixbuf*)
+nsImageToPixbuf::ConvertImageToPixbuf(imgIContainer* aImage)
+{
+ return ImageToPixbuf(aImage);
+}
+
+GdkPixbuf*
+nsImageToPixbuf::ImageToPixbuf(imgIContainer* aImage)
+{
+ RefPtr<SourceSurface> surface =
+ aImage->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE);
+
+ // If the last call failed, it was probably because our call stack originates
+ // in an imgINotificationObserver event, meaning that we're not allowed request
+ // a sync decode. Presumably the originating event is something sensible like
+ // OnStopFrame(), so we can just retry the call without a sync decode.
+ if (!surface)
+ surface = aImage->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_NONE);
+
+ NS_ENSURE_TRUE(surface, nullptr);
+
+ return SourceSurfaceToPixbuf(surface,
+ surface->GetSize().width,
+ surface->GetSize().height);
+}
+
+GdkPixbuf*
+nsImageToPixbuf::SourceSurfaceToPixbuf(SourceSurface* aSurface,
+ int32_t aWidth,
+ int32_t aHeight)
+{
+ MOZ_ASSERT(aWidth <= aSurface->GetSize().width &&
+ aHeight <= aSurface->GetSize().height,
+ "Requested rect is bigger than the supplied surface");
+
+ GdkPixbuf* pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
+ aWidth, aHeight);
+ if (!pixbuf)
+ return nullptr;
+
+ uint32_t destStride = gdk_pixbuf_get_rowstride (pixbuf);
+ guchar* destPixels = gdk_pixbuf_get_pixels (pixbuf);
+
+ RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map))
+ return nullptr;
+
+ uint8_t* srcData = map.mData;
+ int32_t srcStride = map.mStride;
+
+ SurfaceFormat format = dataSurface->GetFormat();
+
+ for (int32_t row = 0; row < aHeight; ++row) {
+ for (int32_t col = 0; col < aWidth; ++col) {
+ guchar* destPixel = destPixels + row * destStride + 4 * col;
+
+ uint32_t* srcPixel =
+ reinterpret_cast<uint32_t*>((srcData + row * srcStride + 4 * col));
+
+ if (format == SurfaceFormat::B8G8R8A8) {
+ const uint8_t a = (*srcPixel >> 24) & 0xFF;
+ const uint8_t r = unpremultiply((*srcPixel >> 16) & 0xFF, a);
+ const uint8_t g = unpremultiply((*srcPixel >> 8) & 0xFF, a);
+ const uint8_t b = unpremultiply((*srcPixel >> 0) & 0xFF, a);
+
+ *destPixel++ = r;
+ *destPixel++ = g;
+ *destPixel++ = b;
+ *destPixel++ = a;
+ } else {
+ MOZ_ASSERT(format == SurfaceFormat::B8G8R8X8);
+
+ const uint8_t r = (*srcPixel >> 16) & 0xFF;
+ const uint8_t g = (*srcPixel >> 8) & 0xFF;
+ const uint8_t b = (*srcPixel >> 0) & 0xFF;
+
+ *destPixel++ = r;
+ *destPixel++ = g;
+ *destPixel++ = b;
+ *destPixel++ = 0xFF; // A
+ }
+ }
+ }
+
+ dataSurface->Unmap();
+
+ return pixbuf;
+}
+
diff --git a/widget/gtk/nsImageToPixbuf.h b/widget/gtk/nsImageToPixbuf.h
new file mode 100644
index 000000000..9c026048a
--- /dev/null
+++ b/widget/gtk/nsImageToPixbuf.h
@@ -0,0 +1,46 @@
+/* vim:set sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 NSIMAGETOPIXBUF_H_
+#define NSIMAGETOPIXBUF_H_
+
+#include "nsIImageToPixbuf.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace gfx {
+class SourceSurface;
+}
+}
+
+class nsImageToPixbuf final : public nsIImageToPixbuf {
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD_(GdkPixbuf*) ConvertImageToPixbuf(imgIContainer* aImage) override;
+
+ // Friendlier version of ConvertImageToPixbuf for callers inside of
+ // widget
+ /**
+ * The return value of all these, if not null, should be
+ * released as needed by the caller using g_object_unref.
+ */
+ static GdkPixbuf* ImageToPixbuf(imgIContainer * aImage);
+ static GdkPixbuf* SourceSurfaceToPixbuf(SourceSurface* aSurface,
+ int32_t aWidth,
+ int32_t aHeight);
+
+ private:
+ ~nsImageToPixbuf() {}
+};
+
+
+// fc2389b8-c650-4093-9e42-b05e5f0685b7
+#define NS_IMAGE_TO_PIXBUF_CID \
+{ 0xfc2389b8, 0xc650, 0x4093, \
+ { 0x9e, 0x42, 0xb0, 0x5e, 0x5f, 0x06, 0x85, 0xb7 } }
+
+#endif
diff --git a/widget/gtk/nsLookAndFeel.cpp b/widget/gtk/nsLookAndFeel.cpp
new file mode 100644
index 000000000..53430dfbb
--- /dev/null
+++ b/widget/gtk/nsLookAndFeel.cpp
@@ -0,0 +1,1465 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+// for strtod()
+#include <stdlib.h>
+
+#include "nsLookAndFeel.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+
+#include <pango/pango.h>
+#include <pango/pango-fontmap.h>
+
+#include <fontconfig/fontconfig.h>
+#include "gfxPlatformGtk.h"
+#include "nsScreenGtk.h"
+
+#include "gtkdrawing.h"
+#include "nsStyleConsts.h"
+#include "gfxFontConstants.h"
+#include "WidgetUtils.h"
+
+#include <dlfcn.h>
+
+#include "mozilla/gfx/2D.h"
+
+#if MOZ_WIDGET_GTK != 2
+#include <cairo-gobject.h>
+#include "WidgetStyleCache.h"
+#include "prenv.h"
+#endif
+
+using mozilla::LookAndFeel;
+
+#define GDK_COLOR_TO_NS_RGB(c) \
+ ((nscolor) NS_RGB(c.red>>8, c.green>>8, c.blue>>8))
+#define GDK_RGBA_TO_NS_RGBA(c) \
+ ((nscolor) NS_RGBA((int)((c).red*255), (int)((c).green*255), \
+ (int)((c).blue*255), (int)((c).alpha*255)))
+
+nsLookAndFeel::nsLookAndFeel()
+ : nsXPLookAndFeel(),
+#if (MOZ_WIDGET_GTK == 2)
+ mStyle(nullptr),
+#else
+ mBackgroundStyle(nullptr),
+ mButtonStyle(nullptr),
+#endif
+ mDefaultFontCached(false), mButtonFontCached(false),
+ mFieldFontCached(false), mMenuFontCached(false)
+{
+ Init();
+}
+
+nsLookAndFeel::~nsLookAndFeel()
+{
+#if (MOZ_WIDGET_GTK == 2)
+ g_object_unref(mStyle);
+#else
+ g_object_unref(mBackgroundStyle);
+ g_object_unref(mButtonStyle);
+#endif
+}
+
+#if MOZ_WIDGET_GTK != 2
+static void
+GetLightAndDarkness(const GdkRGBA& aColor,
+ double* aLightness, double* aDarkness)
+{
+ double sum = aColor.red + aColor.green + aColor.blue;
+ *aLightness = sum * aColor.alpha;
+ *aDarkness = (3.0 - sum) * aColor.alpha;
+}
+
+static bool
+GetGradientColors(const GValue* aValue,
+ GdkRGBA* aLightColor, GdkRGBA* aDarkColor)
+{
+ if (!G_TYPE_CHECK_VALUE_TYPE(aValue, CAIRO_GOBJECT_TYPE_PATTERN))
+ return false;
+
+ auto pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue));
+ if (!pattern)
+ return false;
+
+ // Just picking the lightest and darkest colors as simple samples rather
+ // than trying to blend, which could get messy if there are many stops.
+ if (CAIRO_STATUS_SUCCESS !=
+ cairo_pattern_get_color_stop_rgba(pattern, 0, nullptr, &aDarkColor->red,
+ &aDarkColor->green, &aDarkColor->blue,
+ &aDarkColor->alpha))
+ return false;
+
+ double maxLightness, maxDarkness;
+ GetLightAndDarkness(*aDarkColor, &maxLightness, &maxDarkness);
+ *aLightColor = *aDarkColor;
+
+ GdkRGBA stop;
+ for (int index = 1;
+ CAIRO_STATUS_SUCCESS ==
+ cairo_pattern_get_color_stop_rgba(pattern, index, nullptr,
+ &stop.red, &stop.green,
+ &stop.blue, &stop.alpha);
+ ++index) {
+ double lightness, darkness;
+ GetLightAndDarkness(stop, &lightness, &darkness);
+ if (lightness > maxLightness) {
+ maxLightness = lightness;
+ *aLightColor = stop;
+ }
+ if (darkness > maxDarkness) {
+ maxDarkness = darkness;
+ *aDarkColor = stop;
+ }
+ }
+
+ return true;
+}
+
+static bool
+GetUnicoBorderGradientColors(GtkStyleContext* aContext,
+ GdkRGBA* aLightColor, GdkRGBA* aDarkColor)
+{
+ // Ubuntu 12.04 has GTK engine Unico-1.0.2, which overrides render_frame,
+ // providing its own border code. Ubuntu 14.04 has
+ // Unico-1.0.3+14.04.20140109, which does not override render_frame, and
+ // so does not need special attention. The earlier Unico can be detected
+ // by the -unico-border-gradient style property it registers.
+ // gtk_style_properties_lookup_property() is checked first to avoid the
+ // warning from gtk_style_context_get_property() when the property does
+ // not exist. (gtk_render_frame() of GTK+ 3.16 no longer uses the
+ // engine.)
+ const char* propertyName = "-unico-border-gradient";
+ if (!gtk_style_properties_lookup_property(propertyName, nullptr, nullptr))
+ return false;
+
+ // -unico-border-gradient is used only when the CSS node's engine is Unico.
+ GtkThemingEngine* engine;
+ GtkStateFlags state = gtk_style_context_get_state(aContext);
+ gtk_style_context_get(aContext, state, "engine", &engine, nullptr);
+ if (strcmp(g_type_name(G_TYPE_FROM_INSTANCE(engine)), "UnicoEngine") != 0)
+ return false;
+
+ // draw_border() of Unico engine uses -unico-border-gradient
+ // in preference to border-color.
+ GValue value = G_VALUE_INIT;
+ gtk_style_context_get_property(aContext, propertyName, state, &value);
+
+ bool result = GetGradientColors(&value, aLightColor, aDarkColor);
+
+ g_value_unset(&value);
+ return result;
+}
+
+// Sets |aLightColor| and |aDarkColor| to colors from |aContext|. Returns
+// true if |aContext| uses these colors to render a visible border.
+// If returning false, then the colors returned are a fallback from the
+// border-color value even though |aContext| does not use these colors to
+// render a border.
+static bool
+GetBorderColors(GtkStyleContext* aContext,
+ GdkRGBA* aLightColor, GdkRGBA* aDarkColor)
+{
+ // Determine whether the border on this style context is visible.
+ GtkStateFlags state = gtk_style_context_get_state(aContext);
+ GtkBorderStyle borderStyle;
+ gtk_style_context_get(aContext, state, GTK_STYLE_PROPERTY_BORDER_STYLE,
+ &borderStyle, nullptr);
+ bool visible = borderStyle != GTK_BORDER_STYLE_NONE &&
+ borderStyle != GTK_BORDER_STYLE_HIDDEN;
+ if (visible) {
+ // 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);
+ visible = border.top != 0 || border.right != 0 ||
+ border.bottom != 0 || border.left != 0;
+ }
+
+ if (visible &&
+ GetUnicoBorderGradientColors(aContext, aLightColor, aDarkColor))
+ return true;
+
+ // The initial value for the border-color is the foreground color, and so
+ // this will usually return a color distinct from the background even if
+ // there is no visible border detected.
+ gtk_style_context_get_border_color(aContext, state, aDarkColor);
+ // TODO GTK3 - update aLightColor
+ // for GTK_BORDER_STYLE_INSET/OUTSET/GROVE/RIDGE border styles.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=978172#c25
+ *aLightColor = *aDarkColor;
+ return visible;
+}
+
+static bool
+GetBorderColors(GtkStyleContext* aContext,
+ nscolor* aLightColor, nscolor* aDarkColor)
+{
+ GdkRGBA lightColor, darkColor;
+ bool ret = GetBorderColors(aContext, &lightColor, &darkColor);
+ *aLightColor = GDK_RGBA_TO_NS_RGBA(lightColor);
+ *aDarkColor = GDK_RGBA_TO_NS_RGBA(darkColor);
+ return ret;
+}
+#endif
+
+nsresult
+nsLookAndFeel::NativeGetColor(ColorID aID, nscolor& aColor)
+{
+#if (MOZ_WIDGET_GTK == 3)
+ GdkRGBA gdk_color;
+#endif
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ // These colors don't seem to be used for anything anymore in Mozilla
+ // (except here at least TextSelectBackground and TextSelectForeground)
+ // The CSS2 colors below are used.
+#if (MOZ_WIDGET_GTK == 2)
+ case eColorID_WindowBackground:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_WindowForeground:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_WidgetBackground:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_WidgetForeground:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_WidgetSelectBackground:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_SELECTED]);
+ break;
+ case eColorID_WidgetSelectForeground:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_SELECTED]);
+ break;
+#else
+ case eColorID_WindowBackground:
+ case eColorID_WidgetBackground:
+ case eColorID_TextBackground:
+ case eColorID_activecaption: // active window caption background
+ case eColorID_appworkspace: // MDI background color
+ case eColorID_background: // desktop background
+ case eColorID_window:
+ case eColorID_windowframe:
+ case eColorID__moz_dialog:
+ case eColorID__moz_combobox:
+ aColor = sMozWindowBackground;
+ break;
+ case eColorID_WindowForeground:
+ case eColorID_WidgetForeground:
+ case eColorID_TextForeground:
+ case eColorID_captiontext: // text in active window caption, size box, and scrollbar arrow box (!)
+ case eColorID_windowtext:
+ case eColorID__moz_dialogtext:
+ aColor = sMozWindowText;
+ break;
+ case eColorID_WidgetSelectBackground:
+ case eColorID_TextSelectBackground:
+ 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 = sTextSelectedBackground;
+ break;
+ case eColorID_WidgetSelectForeground:
+ case eColorID_TextSelectForeground:
+ case eColorID_IMESelectedRawTextForeground:
+ case eColorID_IMESelectedConvertedTextForeground:
+ case eColorID_highlighttext:
+ case eColorID__moz_cellhighlighttext:
+ case eColorID__moz_html_cellhighlighttext:
+ aColor = sTextSelectedText;
+ break;
+#endif
+ case eColorID_Widget3DHighlight:
+ aColor = NS_RGB(0xa0,0xa0,0xa0);
+ break;
+ case eColorID_Widget3DShadow:
+ aColor = NS_RGB(0x40,0x40,0x40);
+ break;
+#if (MOZ_WIDGET_GTK == 2)
+ case eColorID_TextBackground:
+ // not used?
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_TextForeground:
+ // not used?
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_TextSelectBackground:
+ case eColorID_IMESelectedRawTextBackground:
+ case eColorID_IMESelectedConvertedTextBackground:
+ // still used
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_SELECTED]);
+ break;
+ case eColorID_TextSelectForeground:
+ case eColorID_IMESelectedRawTextForeground:
+ case eColorID_IMESelectedConvertedTextForeground:
+ // still used
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_SELECTED]);
+ break;
+#endif
+ case eColorID_IMERawInputBackground:
+ case eColorID_IMEConvertedTextBackground:
+ aColor = NS_TRANSPARENT;
+ break;
+ case eColorID_IMERawInputForeground:
+ case eColorID_IMEConvertedTextForeground:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMERawInputUnderline:
+ case eColorID_IMEConvertedTextUnderline:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMESelectedRawTextUnderline:
+ case eColorID_IMESelectedConvertedTextUnderline:
+ aColor = NS_TRANSPARENT;
+ break;
+ case eColorID_SpellCheckerUnderline:
+ aColor = NS_RGB(0xff, 0, 0);
+ break;
+
+#if (MOZ_WIDGET_GTK == 2)
+ // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
+ case eColorID_activeborder:
+ // active window border
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_activecaption:
+ // active window caption background
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_appworkspace:
+ // MDI background color
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_background:
+ // desktop background
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_captiontext:
+ // text in active window caption, size box, and scrollbar arrow box (!)
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_graytext:
+ // disabled text in windows, menus, etc.
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_INSENSITIVE]);
+ break;
+ case eColorID_highlight:
+ // background of selected item
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_SELECTED]);
+ break;
+ case eColorID_highlighttext:
+ // text of selected item
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_SELECTED]);
+ break;
+ case eColorID_inactiveborder:
+ // inactive window border
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_inactivecaption:
+ // inactive window caption
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_INSENSITIVE]);
+ break;
+ case eColorID_inactivecaptiontext:
+ // text in inactive window caption
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_INSENSITIVE]);
+ break;
+#else
+ // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
+ case eColorID_activeborder:
+ // active window border
+ gtk_style_context_get_border_color(mBackgroundStyle,
+ GTK_STATE_FLAG_NORMAL, &gdk_color);
+ aColor = GDK_RGBA_TO_NS_RGBA(gdk_color);
+ break;
+ case eColorID_inactiveborder:
+ // inactive window border
+ gtk_style_context_get_border_color(mBackgroundStyle,
+ GTK_STATE_FLAG_INSENSITIVE,
+ &gdk_color);
+ aColor = GDK_RGBA_TO_NS_RGBA(gdk_color);
+ break;
+ case eColorID_graytext: // disabled text in windows, menus, etc.
+ case eColorID_inactivecaptiontext: // text in inactive window caption
+ aColor = sMenuTextInactive;
+ break;
+ case eColorID_inactivecaption:
+ // inactive window caption
+ gtk_style_context_get_background_color(mBackgroundStyle,
+ GTK_STATE_FLAG_INSENSITIVE,
+ &gdk_color);
+ aColor = GDK_RGBA_TO_NS_RGBA(gdk_color);
+ break;
+#endif
+ case eColorID_infobackground:
+ // tooltip background color
+ aColor = sInfoBackground;
+ break;
+ case eColorID_infotext:
+ // tooltip text color
+ aColor = sInfoText;
+ break;
+ case eColorID_menu:
+ // menu background
+ aColor = sMenuBackground;
+ break;
+ case eColorID_menutext:
+ // menu text
+ aColor = sMenuText;
+ break;
+ case eColorID_scrollbar:
+ // scrollbar gray area
+#if (MOZ_WIDGET_GTK == 2)
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_ACTIVE]);
+#else
+ aColor = sMozScrollbar;
+#endif
+ break;
+
+ case eColorID_threedlightshadow:
+ // 3-D highlighted inner edge color
+ // always same as background in GTK code
+ case eColorID_threedface:
+ case eColorID_buttonface:
+ // 3-D face color
+#if (MOZ_WIDGET_GTK == 3)
+ aColor = sMozWindowBackground;
+#else
+ aColor = sButtonBackground;
+#endif
+ break;
+
+ case eColorID_buttontext:
+ // text on push buttons
+ aColor = sButtonText;
+ break;
+
+ case eColorID_buttonhighlight:
+ // 3-D highlighted edge color
+ case eColorID_threedhighlight:
+ // 3-D highlighted outer edge color
+ aColor = sFrameOuterLightBorder;
+ break;
+
+ case eColorID_buttonshadow:
+ // 3-D shadow edge color
+ case eColorID_threedshadow:
+ // 3-D shadow inner edge color
+ aColor = sFrameInnerDarkBorder;
+ break;
+
+#if (MOZ_WIDGET_GTK == 2)
+ case eColorID_threeddarkshadow:
+ // 3-D shadow outer edge color
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->black);
+ break;
+
+ case eColorID_window:
+ case eColorID_windowframe:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
+ break;
+
+ case eColorID_windowtext:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_NORMAL]);
+ break;
+
+ case eColorID__moz_eventreerow:
+ case eColorID__moz_field:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_NORMAL]);
+ break;
+ case eColorID__moz_fieldtext:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_NORMAL]);
+ break;
+ case eColorID__moz_dialog:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID__moz_dialogtext:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID__moz_dragtargetzone:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_SELECTED]);
+ break;
+ case eColorID__moz_buttondefault:
+ // default button border color
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->black);
+ break;
+ case eColorID__moz_buttonhoverface:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_PRELIGHT]);
+ break;
+ case eColorID__moz_buttonhovertext:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_PRELIGHT]);
+ break;
+ case eColorID__moz_cellhighlight:
+ case eColorID__moz_html_cellhighlight:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_ACTIVE]);
+ break;
+ case eColorID__moz_cellhighlighttext:
+ case eColorID__moz_html_cellhighlighttext:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_ACTIVE]);
+ break;
+#else
+ case eColorID_threeddarkshadow:
+ // Hardcode to black
+ aColor = NS_RGB(0x00,0x00,0x00);
+ break;
+
+ case eColorID__moz_eventreerow:
+ case eColorID__moz_field:
+ aColor = sMozFieldBackground;
+ break;
+ case eColorID__moz_fieldtext:
+ aColor = sMozFieldText;
+ break;
+ case eColorID__moz_buttondefault:
+ // default button border color
+ gtk_style_context_get_border_color(mButtonStyle,
+ GTK_STATE_FLAG_NORMAL, &gdk_color);
+ aColor = GDK_RGBA_TO_NS_RGBA(gdk_color);
+ break;
+ case eColorID__moz_buttonhoverface:
+ gtk_style_context_get_background_color(mButtonStyle,
+ GTK_STATE_FLAG_PRELIGHT,
+ &gdk_color);
+ aColor = GDK_RGBA_TO_NS_RGBA(gdk_color);
+ break;
+ case eColorID__moz_buttonhovertext:
+ aColor = sButtonHoverText;
+ break;
+#endif
+ case eColorID__moz_menuhover:
+ aColor = sMenuHover;
+ break;
+ case eColorID__moz_menuhovertext:
+ aColor = sMenuHoverText;
+ break;
+ case eColorID__moz_oddtreerow:
+ aColor = sOddCellBackground;
+ break;
+ case eColorID__moz_nativehyperlinktext:
+ aColor = sNativeHyperLinkText;
+ break;
+ case eColorID__moz_comboboxtext:
+ aColor = sComboBoxText;
+ break;
+#if (MOZ_WIDGET_GTK == 2)
+ case eColorID__moz_combobox:
+ aColor = sComboBoxBackground;
+ break;
+#endif
+ case eColorID__moz_menubartext:
+ aColor = sMenuBarText;
+ break;
+ case eColorID__moz_menubarhovertext:
+ aColor = sMenuBarHoverText;
+ break;
+ case eColorID__moz_gtk_info_bar_text:
+#if (MOZ_WIDGET_GTK == 3)
+ aColor = sInfoBarText;
+#else
+ aColor = sInfoText;
+#endif
+ break;
+ default:
+ /* default color is BLACK */
+ aColor = 0;
+ res = NS_ERROR_FAILURE;
+ break;
+ }
+
+ return res;
+}
+
+#if (MOZ_WIDGET_GTK == 2)
+static void darken_gdk_color(GdkColor *src, GdkColor *dest)
+{
+ gdouble red;
+ gdouble green;
+ gdouble blue;
+
+ red = (gdouble) src->red / 65535.0;
+ green = (gdouble) src->green / 65535.0;
+ blue = (gdouble) src->blue / 65535.0;
+
+ red *= 0.93;
+ green *= 0.93;
+ blue *= 0.93;
+
+ dest->red = red * 65535.0;
+ dest->green = green * 65535.0;
+ dest->blue = blue * 65535.0;
+}
+#endif
+
+static int32_t CheckWidgetStyle(GtkWidget* aWidget, const char* aStyle, int32_t aResult) {
+ gboolean value = FALSE;
+ gtk_widget_style_get(aWidget, aStyle, &value, nullptr);
+ return value ? aResult : 0;
+}
+
+static int32_t ConvertGTKStepperStyleToMozillaScrollArrowStyle(GtkWidget* aWidget)
+{
+ if (!aWidget)
+ return mozilla::LookAndFeel::eScrollArrowStyle_Single;
+
+ return
+ CheckWidgetStyle(aWidget, "has-backward-stepper",
+ mozilla::LookAndFeel::eScrollArrow_StartBackward) |
+ CheckWidgetStyle(aWidget, "has-forward-stepper",
+ mozilla::LookAndFeel::eScrollArrow_EndForward) |
+ CheckWidgetStyle(aWidget, "has-secondary-backward-stepper",
+ mozilla::LookAndFeel::eScrollArrow_EndBackward) |
+ CheckWidgetStyle(aWidget, "has-secondary-forward-stepper",
+ mozilla::LookAndFeel::eScrollArrow_StartForward);
+}
+
+nsresult
+nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult)
+{
+ nsresult res = NS_OK;
+
+ // Set these before they can get overrided in the nsXPLookAndFeel.
+ switch (aID) {
+ case eIntID_ScrollButtonLeftMouseButtonAction:
+ aResult = 0;
+ return NS_OK;
+ case eIntID_ScrollButtonMiddleMouseButtonAction:
+ aResult = 1;
+ return NS_OK;
+ case eIntID_ScrollButtonRightMouseButtonAction:
+ aResult = 2;
+ return NS_OK;
+ default:
+ break;
+ }
+
+ res = nsXPLookAndFeel::GetIntImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eIntID_CaretBlinkTime:
+ {
+ GtkSettings *settings;
+ gint blink_time;
+ gboolean blink;
+
+ settings = gtk_settings_get_default ();
+ g_object_get (settings,
+ "gtk-cursor-blink-time", &blink_time,
+ "gtk-cursor-blink", &blink,
+ nullptr);
+
+ if (blink)
+ aResult = (int32_t) blink_time;
+ else
+ aResult = 0;
+ break;
+ }
+ case eIntID_CaretWidth:
+ aResult = 1;
+ break;
+ case eIntID_ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+ case eIntID_SelectTextfieldsOnKeyFocus:
+ {
+ GtkWidget *entry;
+ GtkSettings *settings;
+ gboolean select_on_focus;
+
+ entry = gtk_entry_new();
+ g_object_ref_sink(entry);
+ settings = gtk_widget_get_settings(entry);
+ g_object_get(settings,
+ "gtk-entry-select-on-focus",
+ &select_on_focus,
+ nullptr);
+
+ if(select_on_focus)
+ aResult = 1;
+ else
+ aResult = 0;
+
+ gtk_widget_destroy(entry);
+ g_object_unref(entry);
+ }
+ break;
+ case eIntID_ScrollToClick:
+ {
+ GtkSettings *settings;
+ gboolean warps_slider = FALSE;
+
+ settings = gtk_settings_get_default ();
+ if (g_object_class_find_property (G_OBJECT_GET_CLASS(settings),
+ "gtk-primary-button-warps-slider")) {
+ g_object_get (settings,
+ "gtk-primary-button-warps-slider",
+ &warps_slider,
+ nullptr);
+ }
+
+ if (warps_slider)
+ aResult = 1;
+ else
+ aResult = 0;
+ }
+ break;
+ case eIntID_SubmenuDelay:
+ {
+ GtkSettings *settings;
+ gint delay;
+
+ settings = gtk_settings_get_default ();
+ g_object_get (settings, "gtk-menu-popup-delay", &delay, nullptr);
+ aResult = (int32_t) delay;
+ break;
+ }
+ case eIntID_TooltipDelay:
+ {
+ aResult = 500;
+ break;
+ }
+ case eIntID_MenusCanOverlapOSBar:
+ // we want XUL popups to be able to overlap the task bar.
+ aResult = 1;
+ break;
+ case eIntID_SkipNavigatingDisabledMenuItem:
+ aResult = 1;
+ break;
+ case eIntID_DragThresholdX:
+ case eIntID_DragThresholdY:
+ {
+ GtkWidget* box = gtk_hbox_new(FALSE, 5);
+ gint threshold = 0;
+ g_object_get(gtk_widget_get_settings(box),
+ "gtk-dnd-drag-threshold", &threshold,
+ nullptr);
+ g_object_ref_sink(box);
+
+ aResult = threshold;
+ }
+ break;
+ case eIntID_ScrollArrowStyle:
+ moz_gtk_init();
+ aResult =
+ ConvertGTKStepperStyleToMozillaScrollArrowStyle(moz_gtk_get_scrollbar_widget());
+ break;
+ case eIntID_ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+ case eIntID_TreeOpenDelay:
+ aResult = 1000;
+ break;
+ case eIntID_TreeCloseDelay:
+ aResult = 1000;
+ break;
+ case eIntID_TreeLazyScrollDelay:
+ aResult = 150;
+ break;
+ case eIntID_TreeScrollDelay:
+ aResult = 100;
+ break;
+ case eIntID_TreeScrollLinesMax:
+ aResult = 3;
+ break;
+ case eIntID_DWMCompositor:
+ case eIntID_WindowsClassic:
+ case eIntID_WindowsDefaultTheme:
+ case eIntID_WindowsThemeIdentifier:
+ case eIntID_OperatingSystemVersionIdentifier:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case eIntID_TouchEnabled:
+#if MOZ_WIDGET_GTK == 3
+ aResult = mozilla::widget::WidgetUtils::IsTouchDeviceSupportPresent();
+ break;
+#else
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+#endif
+ break;
+ case eIntID_MacGraphiteTheme:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case eIntID_AlertNotificationOrigin:
+ aResult = NS_ALERT_TOP;
+ break;
+ case eIntID_IMERawInputUnderlineStyle:
+ case eIntID_IMEConvertedTextUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
+ break;
+ case eIntID_IMESelectedRawTextUnderlineStyle:
+ case eIntID_IMESelectedConvertedTextUnderline:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE;
+ break;
+ case eIntID_SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY;
+ break;
+ case eIntID_MenuBarDrag:
+ aResult = sMenuSupportsDrag;
+ break;
+ case eIntID_ScrollbarButtonAutoRepeatBehavior:
+ aResult = 1;
+ break;
+ case eIntID_SwipeAnimationEnabled:
+ aResult = 0;
+ break;
+ case eIntID_ColorPickerAvailable:
+ aResult = 1;
+ break;
+ case eIntID_ContextMenuOffsetVertical:
+ case eIntID_ContextMenuOffsetHorizontal:
+ aResult = 2;
+ break;
+ default:
+ aResult = 0;
+ res = NS_ERROR_FAILURE;
+ }
+
+ return res;
+}
+
+nsresult
+nsLookAndFeel::GetFloatImpl(FloatID aID, float &aResult)
+{
+ nsresult res = NS_OK;
+ res = nsXPLookAndFeel::GetFloatImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eFloatID_IMEUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case eFloatID_SpellCheckerUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case eFloatID_CaretAspectRatio:
+ aResult = sCaretRatio;
+ break;
+ default:
+ aResult = -1.0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+}
+
+static void
+GetSystemFontInfo(GtkWidget *aWidget,
+ nsString *aFontName,
+ gfxFontStyle *aFontStyle)
+{
+ GtkSettings *settings = gtk_widget_get_settings(aWidget);
+
+ aFontStyle->style = NS_FONT_STYLE_NORMAL;
+
+ gchar *fontname;
+ g_object_get(settings, "gtk-font-name", &fontname, nullptr);
+
+ PangoFontDescription *desc;
+ desc = pango_font_description_from_string(fontname);
+
+ aFontStyle->systemFont = true;
+
+ g_free(fontname);
+
+ NS_NAMED_LITERAL_STRING(quote, "\"");
+ NS_ConvertUTF8toUTF16 family(pango_font_description_get_family(desc));
+ *aFontName = quote + family + quote;
+
+ aFontStyle->weight = pango_font_description_get_weight(desc);
+
+ // FIXME: Set aFontStyle->stretch correctly!
+ aFontStyle->stretch = NS_FONT_STRETCH_NORMAL;
+
+ float size = float(pango_font_description_get_size(desc)) / PANGO_SCALE;
+
+ // |size| is now either pixels or pango-points (not Mozilla-points!)
+
+ if (!pango_font_description_get_size_is_absolute(desc)) {
+ // |size| is in pango-points, so convert to pixels.
+ size *= float(gfxPlatformGtk::GetDPI()) / POINTS_PER_INCH_FLOAT;
+ }
+
+ // Scale fonts up on HiDPI displays.
+ // This would be done automatically with cairo, but we manually manage
+ // the display scale for platform consistency.
+ size *= nsScreenGtk::GetGtkMonitorScaleFactor();
+
+ // |size| is now pixels
+
+ aFontStyle->size = size;
+
+ pango_font_description_free(desc);
+}
+
+static void
+GetSystemFontInfo(LookAndFeel::FontID aID,
+ nsString *aFontName,
+ gfxFontStyle *aFontStyle)
+{
+ if (aID == LookAndFeel::eFont_Widget) {
+ GtkWidget *label = gtk_label_new("M");
+ GtkWidget *parent = gtk_fixed_new();
+ GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP);
+
+ gtk_container_add(GTK_CONTAINER(parent), label);
+ gtk_container_add(GTK_CONTAINER(window), parent);
+
+ gtk_widget_ensure_style(label);
+ GetSystemFontInfo(label, aFontName, aFontStyle);
+ gtk_widget_destroy(window); // no unref, windows are different
+
+ } else if (aID == LookAndFeel::eFont_Button) {
+ GtkWidget *label = gtk_label_new("M");
+ GtkWidget *parent = gtk_fixed_new();
+ GtkWidget *button = gtk_button_new();
+ GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP);
+
+ gtk_container_add(GTK_CONTAINER(button), label);
+ gtk_container_add(GTK_CONTAINER(parent), button);
+ gtk_container_add(GTK_CONTAINER(window), parent);
+
+ gtk_widget_ensure_style(label);
+ GetSystemFontInfo(label, aFontName, aFontStyle);
+ gtk_widget_destroy(window); // no unref, windows are different
+
+ } else if (aID == LookAndFeel::eFont_Field) {
+ GtkWidget *entry = gtk_entry_new();
+ GtkWidget *parent = gtk_fixed_new();
+ GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP);
+
+ gtk_container_add(GTK_CONTAINER(parent), entry);
+ gtk_container_add(GTK_CONTAINER(window), parent);
+
+ gtk_widget_ensure_style(entry);
+ GetSystemFontInfo(entry, aFontName, aFontStyle);
+ gtk_widget_destroy(window); // no unref, windows are different
+
+ } else {
+ MOZ_ASSERT(aID == LookAndFeel::eFont_Menu, "unexpected font ID");
+ GtkWidget *accel_label = gtk_accel_label_new("M");
+ GtkWidget *menuitem = gtk_menu_item_new();
+ GtkWidget *menu = gtk_menu_new();
+ g_object_ref_sink(menu);
+
+ gtk_container_add(GTK_CONTAINER(menuitem), accel_label);
+ gtk_menu_shell_append((GtkMenuShell *)GTK_MENU(menu), menuitem);
+
+ gtk_widget_ensure_style(accel_label);
+ GetSystemFontInfo(accel_label, aFontName, aFontStyle);
+ g_object_unref(menu);
+ }
+}
+
+bool
+nsLookAndFeel::GetFontImpl(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle,
+ float aDevPixPerCSSPixel)
+{
+ nsString *cachedFontName = nullptr;
+ gfxFontStyle *cachedFontStyle = nullptr;
+ bool *isCached = nullptr;
+
+ switch (aID) {
+ case eFont_Menu: // css2
+ case eFont_PullDownMenu: // css3
+ cachedFontName = &mMenuFontName;
+ cachedFontStyle = &mMenuFontStyle;
+ isCached = &mMenuFontCached;
+ aID = eFont_Menu;
+ break;
+
+ case eFont_Field: // css3
+ case eFont_List: // css3
+ cachedFontName = &mFieldFontName;
+ cachedFontStyle = &mFieldFontStyle;
+ isCached = &mFieldFontCached;
+ aID = eFont_Field;
+ break;
+
+ case eFont_Button: // css3
+ cachedFontName = &mButtonFontName;
+ cachedFontStyle = &mButtonFontStyle;
+ isCached = &mButtonFontCached;
+ break;
+
+ case eFont_Caption: // css2
+ case eFont_Icon: // css2
+ case eFont_MessageBox: // css2
+ case eFont_SmallCaption: // css2
+ case eFont_StatusBar: // css2
+ case eFont_Window: // css3
+ case eFont_Document: // css3
+ case eFont_Workspace: // css3
+ case eFont_Desktop: // css3
+ case eFont_Info: // css3
+ case eFont_Dialog: // css3
+ case eFont_Tooltips: // moz
+ case eFont_Widget: // moz
+ cachedFontName = &mDefaultFontName;
+ cachedFontStyle = &mDefaultFontStyle;
+ isCached = &mDefaultFontCached;
+ aID = eFont_Widget;
+ break;
+ }
+
+ if (!*isCached) {
+ GetSystemFontInfo(aID, cachedFontName, cachedFontStyle);
+ *isCached = true;
+ }
+
+ aFontName = *cachedFontName;
+ aFontStyle = *cachedFontStyle;
+ return true;
+}
+
+#if (MOZ_WIDGET_GTK == 3)
+static GtkStyleContext*
+create_context(GtkWidgetPath *path)
+{
+ GtkStyleContext *style = gtk_style_context_new();
+ gtk_style_context_set_path(style, path);
+ return(style);
+}
+#endif
+
+void
+nsLookAndFeel::Init()
+{
+ GdkColor colorValue;
+ GdkColor *colorValuePtr;
+
+#if (MOZ_WIDGET_GTK == 2)
+ NS_ASSERTION(!mStyle, "already initialized");
+ // GtkInvisibles come with a refcount that is not floating
+ // (since their initialization code calls g_object_ref_sink) and
+ // their destroy code releases that reference (which means they
+ // have to be explicitly destroyed, since calling unref enough
+ // to cause destruction would lead to *another* unref).
+ // However, this combination means that it's actually still ok
+ // to use the normal pattern, which is to g_object_ref_sink
+ // after construction, and then destroy *and* unref when we're
+ // done. (Though we could skip the g_object_ref_sink and the
+ // corresponding g_object_unref, but that's particular to
+ // GtkInvisibles and GtkWindows.)
+ GtkWidget *widget = gtk_invisible_new();
+ g_object_ref_sink(widget); // effectively g_object_ref (see above)
+
+ gtk_widget_ensure_style(widget);
+ mStyle = gtk_style_copy(gtk_widget_get_style(widget));
+
+ gtk_widget_destroy(widget);
+ g_object_unref(widget);
+
+ // tooltip foreground and background
+ GtkStyle *style = gtk_rc_get_style_by_paths(gtk_settings_get_default(),
+ "gtk-tooltips", "GtkWindow",
+ GTK_TYPE_WINDOW);
+ if (style) {
+ sInfoBackground = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_NORMAL]);
+ sInfoText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]);
+ }
+
+ // menu foreground & menu background
+ GtkWidget *accel_label = gtk_accel_label_new("M");
+ GtkWidget *menuitem = gtk_menu_item_new();
+ GtkWidget *menu = gtk_menu_new();
+
+ g_object_ref_sink(menu);
+
+ gtk_container_add(GTK_CONTAINER(menuitem), accel_label);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ gtk_widget_set_style(accel_label, nullptr);
+ gtk_widget_set_style(menu, nullptr);
+ gtk_widget_realize(menu);
+ gtk_widget_realize(accel_label);
+
+ style = gtk_widget_get_style(accel_label);
+ if (style) {
+ sMenuText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]);
+ }
+
+ style = gtk_widget_get_style(menu);
+ if (style) {
+ sMenuBackground = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_NORMAL]);
+ }
+
+ style = gtk_widget_get_style(menuitem);
+ if (style) {
+ sMenuHover = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_PRELIGHT]);
+ sMenuHoverText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_PRELIGHT]);
+ }
+
+ g_object_unref(menu);
+#else
+ GdkRGBA color;
+ GtkStyleContext *style;
+
+ // Gtk manages a screen's CSS in the settings object so we
+ // ask Gtk to create it explicitly. Otherwise we may end up
+ // with wrong color theme, see Bug 972382
+ GtkSettings *settings = gtk_settings_get_for_screen(gdk_screen_get_default());
+
+ // Disable dark theme because it interacts poorly with widget styling in
+ // web content (see bug 1216658).
+ // To avoid triggering reload of theme settings unnecessarily, only set the
+ // setting when necessary.
+ const gchar* dark_setting = "gtk-application-prefer-dark-theme";
+ gboolean dark;
+ g_object_get(settings, dark_setting, &dark, nullptr);
+
+ if (dark && !PR_GetEnv("MOZ_ALLOW_GTK_DARK_THEME")) {
+ g_object_set(settings, dark_setting, FALSE, nullptr);
+ }
+
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_WINDOW);
+
+ mBackgroundStyle = create_context(path);
+ gtk_style_context_add_class(mBackgroundStyle, GTK_STYLE_CLASS_BACKGROUND);
+
+ mButtonStyle = create_context(path);
+ gtk_style_context_add_class(mButtonStyle, GTK_STYLE_CLASS_BUTTON);
+
+ // Scrollbar colors
+ style = create_context(path);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_SCROLLBAR);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_TROUGH);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sMozScrollbar = GDK_RGBA_TO_NS_RGBA(color);
+ g_object_unref(style);
+
+ // Window colors
+ style = create_context(path);
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sMozWindowBackground = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sMozWindowText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_restore(style);
+ g_object_unref(style);
+
+ // tooltip foreground and background
+ style = ClaimStyleContext(MOZ_GTK_TOOLTIP);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sInfoBackground = GDK_RGBA_TO_NS_RGBA(color);
+ {
+ GtkStyleContext* boxStyle =
+ CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0),
+ style);
+ GtkStyleContext* labelStyle =
+ CreateStyleForWidget(gtk_label_new(nullptr), boxStyle);
+ gtk_style_context_get_color(labelStyle, GTK_STATE_FLAG_NORMAL, &color);
+ g_object_unref(labelStyle);
+ g_object_unref(boxStyle);
+ }
+ sInfoText = GDK_RGBA_TO_NS_RGBA(color);
+ ReleaseStyleContext(style);
+
+ // menu foreground & menu background
+ GtkWidget *accel_label = gtk_accel_label_new("M");
+ GtkWidget *menuitem = gtk_menu_item_new();
+ GtkWidget *menu = gtk_menu_new();
+
+ g_object_ref_sink(menu);
+
+ gtk_container_add(GTK_CONTAINER(menuitem), accel_label);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ style = gtk_widget_get_style_context(accel_label);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sMenuText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_INSENSITIVE, &color);
+ sMenuTextInactive = GDK_RGBA_TO_NS_RGBA(color);
+
+ style = gtk_widget_get_style_context(menu);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sMenuBackground = GDK_RGBA_TO_NS_RGBA(color);
+
+ style = gtk_widget_get_style_context(menuitem);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ sMenuHover = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ sMenuHoverText = GDK_RGBA_TO_NS_RGBA(color);
+
+ g_object_unref(menu);
+#endif
+
+ // button styles
+ GtkWidget *parent = gtk_fixed_new();
+ GtkWidget *button = gtk_button_new();
+ GtkWidget *label = gtk_label_new("M");
+#if (MOZ_WIDGET_GTK == 2)
+ GtkWidget *combobox = gtk_combo_box_new();
+ GtkWidget *comboboxLabel = gtk_label_new("M");
+ gtk_container_add(GTK_CONTAINER(combobox), comboboxLabel);
+#else
+ GtkWidget *combobox = gtk_combo_box_new_with_entry();
+ GtkWidget *comboboxLabel = gtk_bin_get_child(GTK_BIN(combobox));
+#endif
+ GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP);
+ GtkWidget *treeView = gtk_tree_view_new();
+ GtkWidget *linkButton = gtk_link_button_new("http://example.com/");
+ GtkWidget *menuBar = gtk_menu_bar_new();
+ GtkWidget *menuBarItem = gtk_menu_item_new();
+ GtkWidget *entry = gtk_entry_new();
+ GtkWidget *textView = gtk_text_view_new();
+
+ gtk_container_add(GTK_CONTAINER(button), label);
+ gtk_container_add(GTK_CONTAINER(parent), button);
+ gtk_container_add(GTK_CONTAINER(parent), treeView);
+ gtk_container_add(GTK_CONTAINER(parent), linkButton);
+ gtk_container_add(GTK_CONTAINER(parent), combobox);
+ gtk_container_add(GTK_CONTAINER(parent), menuBar);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menuBar), menuBarItem);
+ gtk_container_add(GTK_CONTAINER(window), parent);
+ gtk_container_add(GTK_CONTAINER(parent), entry);
+ gtk_container_add(GTK_CONTAINER(parent), textView);
+
+#if (MOZ_WIDGET_GTK == 2)
+ gtk_widget_set_style(button, nullptr);
+ gtk_widget_set_style(label, nullptr);
+ gtk_widget_set_style(treeView, nullptr);
+ gtk_widget_set_style(linkButton, nullptr);
+ gtk_widget_set_style(combobox, nullptr);
+ gtk_widget_set_style(comboboxLabel, nullptr);
+ gtk_widget_set_style(menuBar, nullptr);
+ gtk_widget_set_style(entry, nullptr);
+
+ gtk_widget_realize(button);
+ gtk_widget_realize(label);
+ gtk_widget_realize(treeView);
+ gtk_widget_realize(linkButton);
+ gtk_widget_realize(combobox);
+ gtk_widget_realize(comboboxLabel);
+ gtk_widget_realize(menuBar);
+ gtk_widget_realize(entry);
+
+ style = gtk_widget_get_style(label);
+ if (style) {
+ sButtonText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]);
+ }
+
+ style = gtk_widget_get_style(comboboxLabel);
+ if (style) {
+ sComboBoxText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]);
+ }
+ style = gtk_widget_get_style(combobox);
+ if (style) {
+ sComboBoxBackground = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_NORMAL]);
+ }
+
+ style = gtk_widget_get_style(menuBar);
+ if (style) {
+ sMenuBarText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]);
+ sMenuBarHoverText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_SELECTED]);
+ }
+
+ // GTK's guide to fancy odd row background colors:
+ // 1) Check if a theme explicitly defines an odd row color
+ // 2) If not, check if it defines an even row color, and darken it
+ // slightly by a hardcoded value (gtkstyle.c)
+ // 3) If neither are defined, take the base background color and
+ // darken that by a hardcoded value
+ colorValuePtr = nullptr;
+ gtk_widget_style_get(treeView,
+ "odd-row-color", &colorValuePtr,
+ nullptr);
+
+ if (colorValuePtr) {
+ colorValue = *colorValuePtr;
+ } else {
+ gtk_widget_style_get(treeView,
+ "even-row-color", &colorValuePtr,
+ nullptr);
+ if (colorValuePtr)
+ darken_gdk_color(colorValuePtr, &colorValue);
+ else
+ darken_gdk_color(&treeView->style->base[GTK_STATE_NORMAL], &colorValue);
+ }
+
+ sOddCellBackground = GDK_COLOR_TO_NS_RGB(colorValue);
+ if (colorValuePtr)
+ gdk_color_free(colorValuePtr);
+
+ style = gtk_widget_get_style(button);
+ if (style) {
+ sButtonBackground = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_NORMAL]);
+ sFrameOuterLightBorder =
+ GDK_COLOR_TO_NS_RGB(style->light[GTK_STATE_NORMAL]);
+ sFrameInnerDarkBorder =
+ GDK_COLOR_TO_NS_RGB(style->dark[GTK_STATE_NORMAL]);
+ }
+#else
+ // Text colors
+ style = gtk_widget_get_style_context(textView);
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_VIEW);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sMozFieldBackground = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sMozFieldText = GDK_RGBA_TO_NS_RGBA(color);
+
+ // Selected text and background
+ gtk_style_context_get_background_color(style,
+ static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED|GTK_STATE_FLAG_SELECTED),
+ &color);
+ sTextSelectedBackground = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style,
+ static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED|GTK_STATE_FLAG_SELECTED),
+ &color);
+ sTextSelectedText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_restore(style);
+
+ // Button text, background, border
+ style = gtk_widget_get_style_context(label);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sButtonText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ sButtonHoverText = GDK_RGBA_TO_NS_RGBA(color);
+
+ // Combobox text color
+ style = gtk_widget_get_style_context(comboboxLabel);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sComboBoxText = GDK_RGBA_TO_NS_RGBA(color);
+
+ // Menubar text and hover text colors
+ style = gtk_widget_get_style_context(menuBarItem);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sMenuBarText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ sMenuBarHoverText = GDK_RGBA_TO_NS_RGBA(color);
+
+ // GTK's guide to fancy odd row background colors:
+ // 1) Check if a theme explicitly defines an odd row color
+ // 2) If not, check if it defines an even row color, and darken it
+ // slightly by a hardcoded value (gtkstyle.c)
+ // 3) If neither are defined, take the base background color and
+ // darken that by a hardcoded value
+ style = gtk_widget_get_style_context(treeView);
+
+ // Get odd row background color
+ gtk_style_context_save(style);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_ROW, GTK_REGION_ODD);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sOddCellBackground = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_restore(style);
+
+ gtk_widget_path_free(path);
+
+ // 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
+ // node.
+ style = ClaimStyleContext(MOZ_GTK_FRAME_BORDER);
+ bool themeUsesColors =
+ GetBorderColors(style, &sFrameOuterLightBorder, &sFrameInnerDarkBorder);
+ ReleaseStyleContext(style);
+ if (!themeUsesColors) {
+ style = ClaimStyleContext(MOZ_GTK_FRAME);
+ GetBorderColors(style, &sFrameOuterLightBorder, &sFrameInnerDarkBorder);
+ ReleaseStyleContext(style);
+ }
+
+ // GtkInfoBar
+ // TODO - Use WidgetCache for it?
+ GtkWidget* infoBar = gtk_info_bar_new();
+ GtkWidget* infoBarContent = gtk_info_bar_get_content_area(GTK_INFO_BAR(infoBar));
+ GtkWidget* infoBarLabel = gtk_label_new(nullptr);
+ gtk_container_add(GTK_CONTAINER(parent), infoBar);
+ gtk_container_add(GTK_CONTAINER(infoBarContent), infoBarLabel);
+ style = gtk_widget_get_style_context(infoBarLabel);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_INFO);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sInfoBarText = GDK_RGBA_TO_NS_RGBA(color);
+#endif
+ // Some themes have a unified menu bar, and support window dragging on it
+ gboolean supports_menubar_drag = FALSE;
+ GParamSpec *param_spec =
+ gtk_widget_class_find_style_property(GTK_WIDGET_GET_CLASS(menuBar),
+ "window-dragging");
+ if (param_spec) {
+ if (g_type_is_a(G_PARAM_SPEC_VALUE_TYPE(param_spec), G_TYPE_BOOLEAN)) {
+ gtk_widget_style_get(menuBar,
+ "window-dragging", &supports_menubar_drag,
+ nullptr);
+ }
+ }
+ sMenuSupportsDrag = supports_menubar_drag;
+
+ colorValuePtr = nullptr;
+ gtk_widget_style_get(linkButton, "link-color", &colorValuePtr, nullptr);
+ if (colorValuePtr) {
+ colorValue = *colorValuePtr; // we can't pass deref pointers to GDK_COLOR_TO_NS_RGB
+ sNativeHyperLinkText = GDK_COLOR_TO_NS_RGB(colorValue);
+ gdk_color_free(colorValuePtr);
+ } else {
+ sNativeHyperLinkText = NS_RGB(0x00,0x00,0xEE);
+ }
+
+ // invisible character styles
+ guint value;
+ g_object_get (entry, "invisible-char", &value, nullptr);
+ sInvisibleCharacter = char16_t(value);
+
+ // caret styles
+ gtk_widget_style_get(entry,
+ "cursor-aspect-ratio", &sCaretRatio,
+ nullptr);
+
+ gtk_widget_destroy(window);
+}
+
+// virtual
+char16_t
+nsLookAndFeel::GetPasswordCharacterImpl()
+{
+ return sInvisibleCharacter;
+}
+
+void
+nsLookAndFeel::RefreshImpl()
+{
+ nsXPLookAndFeel::RefreshImpl();
+
+ mDefaultFontCached = false;
+ mButtonFontCached = false;
+ mFieldFontCached = false;
+ mMenuFontCached = false;
+
+#if (MOZ_WIDGET_GTK == 2)
+ g_object_unref(mStyle);
+ mStyle = nullptr;
+#else
+ g_object_unref(mBackgroundStyle);
+ g_object_unref(mButtonStyle);
+
+ mBackgroundStyle = nullptr;
+ mButtonStyle = nullptr;
+#endif
+
+ Init();
+}
+
+bool
+nsLookAndFeel::GetEchoPasswordImpl() {
+ return false;
+}
diff --git a/widget/gtk/nsLookAndFeel.h b/widget/gtk/nsLookAndFeel.h
new file mode 100644
index 000000000..9058250b9
--- /dev/null
+++ b/widget/gtk/nsLookAndFeel.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 __nsLookAndFeel
+#define __nsLookAndFeel
+
+#include "nsXPLookAndFeel.h"
+#include "nsCOMPtr.h"
+#include "gfxFont.h"
+
+struct _GtkStyle;
+
+class nsLookAndFeel: public nsXPLookAndFeel {
+public:
+ nsLookAndFeel();
+ virtual ~nsLookAndFeel();
+
+ virtual nsresult NativeGetColor(ColorID aID, nscolor &aResult);
+ virtual nsresult GetIntImpl(IntID aID, int32_t &aResult);
+ virtual nsresult GetFloatImpl(FloatID aID, float &aResult);
+ virtual bool GetFontImpl(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle,
+ float aDevPixPerCSSPixel);
+
+ virtual void RefreshImpl();
+ virtual char16_t GetPasswordCharacterImpl();
+ virtual bool GetEchoPasswordImpl();
+
+protected:
+#if (MOZ_WIDGET_GTK == 2)
+ struct _GtkStyle *mStyle;
+#else
+ struct _GtkStyleContext *mBackgroundStyle;
+ struct _GtkStyleContext *mButtonStyle;
+#endif
+
+ // Cached fonts
+ bool mDefaultFontCached;
+ bool mButtonFontCached;
+ bool mFieldFontCached;
+ bool mMenuFontCached;
+ nsString mDefaultFontName;
+ nsString mButtonFontName;
+ nsString mFieldFontName;
+ nsString mMenuFontName;
+ gfxFontStyle mDefaultFontStyle;
+ gfxFontStyle mButtonFontStyle;
+ gfxFontStyle mFieldFontStyle;
+ gfxFontStyle mMenuFontStyle;
+
+ // Cached colors
+ nscolor sInfoBackground;
+ nscolor sInfoText;
+ nscolor sMenuBackground;
+ nscolor sMenuBarText;
+ nscolor sMenuBarHoverText;
+ nscolor sMenuText;
+ nscolor sMenuTextInactive;
+ nscolor sMenuHover;
+ nscolor sMenuHoverText;
+ nscolor sButtonText;
+ nscolor sButtonHoverText;
+ nscolor sButtonBackground;
+ nscolor sFrameOuterLightBorder;
+ nscolor sFrameInnerDarkBorder;
+ nscolor sOddCellBackground;
+ nscolor sNativeHyperLinkText;
+ nscolor sComboBoxText;
+ nscolor sComboBoxBackground;
+ nscolor sMozFieldText;
+ nscolor sMozFieldBackground;
+ nscolor sMozWindowText;
+ nscolor sMozWindowBackground;
+ nscolor sTextSelectedText;
+ nscolor sTextSelectedBackground;
+ nscolor sMozScrollbar;
+#if (MOZ_WIDGET_GTK == 3)
+ nscolor sInfoBarText;
+#endif
+ char16_t sInvisibleCharacter;
+ float sCaretRatio;
+ bool sMenuSupportsDrag;
+
+ void Init();
+};
+
+#endif
diff --git a/widget/gtk/nsNativeThemeGTK.cpp b/widget/gtk/nsNativeThemeGTK.cpp
new file mode 100644
index 000000000..89b8ab7dc
--- /dev/null
+++ b/widget/gtk/nsNativeThemeGTK.cpp
@@ -0,0 +1,1994 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "nsNativeThemeGTK.h"
+#include "nsThemeConstants.h"
+#include "gtkdrawing.h"
+#include "nsScreenGtk.h"
+
+#include "gfx2DGlue.h"
+#include "nsIObserverService.h"
+#include "nsIServiceManager.h"
+#include "nsIFrame.h"
+#include "nsIPresShell.h"
+#include "nsIContent.h"
+#include "nsViewManager.h"
+#include "nsNameSpaceManager.h"
+#include "nsGfxCIID.h"
+#include "nsTransform2D.h"
+#include "nsMenuFrame.h"
+#include "prlink.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsRenderingContext.h"
+#include "nsGkAtoms.h"
+#include "nsAttrValueInlines.h"
+
+#include "mozilla/EventStates.h"
+#include "mozilla/Services.h"
+
+#include <gdk/gdkprivate.h>
+#include <gtk/gtk.h>
+
+#include "gfxContext.h"
+#include "gfxPlatformGtk.h"
+#include "gfxGdkNativeRenderer.h"
+#include "mozilla/gfx/BorrowedContext.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "mozilla/gfx/PathHelpers.h"
+
+#ifdef MOZ_X11
+# ifdef CAIRO_HAS_XLIB_SURFACE
+# include "cairo-xlib.h"
+# endif
+# ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE
+# include "cairo-xlib-xrender.h"
+# endif
+#endif
+
+#include <algorithm>
+#include <dlfcn.h>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeGTK, nsNativeTheme, nsITheme,
+ nsIObserver)
+
+static int gLastGdkError;
+
+nsNativeThemeGTK::nsNativeThemeGTK()
+{
+ if (moz_gtk_init() != MOZ_GTK_SUCCESS) {
+ memset(mDisabledWidgetTypes, 0xff, sizeof(mDisabledWidgetTypes));
+ return;
+ }
+
+ // We have to call moz_gtk_shutdown before the event loop stops running.
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ obsServ->AddObserver(this, "xpcom-shutdown", false);
+
+ memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes));
+ memset(mSafeWidgetStates, 0, sizeof(mSafeWidgetStates));
+}
+
+nsNativeThemeGTK::~nsNativeThemeGTK() {
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *aData)
+{
+ if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
+ moz_gtk_shutdown();
+ } else {
+ NS_NOTREACHED("unexpected topic");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+void
+nsNativeThemeGTK::RefreshWidgetWindow(nsIFrame* aFrame)
+{
+ nsIPresShell *shell = GetPresShell(aFrame);
+ if (!shell)
+ return;
+
+ nsViewManager* vm = shell->GetViewManager();
+ if (!vm)
+ return;
+
+ vm->InvalidateAllViews();
+}
+
+
+static bool IsFrameContentNodeInNamespace(nsIFrame *aFrame, uint32_t aNamespace)
+{
+ nsIContent *content = aFrame ? aFrame->GetContent() : nullptr;
+ if (!content)
+ return false;
+ return content->IsInNamespace(aNamespace);
+}
+
+static bool IsWidgetTypeDisabled(uint8_t* aDisabledVector, uint8_t aWidgetType) {
+ return (aDisabledVector[aWidgetType >> 3] & (1 << (aWidgetType & 7))) != 0;
+}
+
+static void SetWidgetTypeDisabled(uint8_t* aDisabledVector, uint8_t aWidgetType) {
+ aDisabledVector[aWidgetType >> 3] |= (1 << (aWidgetType & 7));
+}
+
+static inline uint16_t
+GetWidgetStateKey(uint8_t aWidgetType, GtkWidgetState *aWidgetState)
+{
+ return (aWidgetState->active |
+ aWidgetState->focused << 1 |
+ aWidgetState->inHover << 2 |
+ aWidgetState->disabled << 3 |
+ aWidgetState->isDefault << 4 |
+ aWidgetType << 5);
+}
+
+static bool IsWidgetStateSafe(uint8_t* aSafeVector,
+ uint8_t aWidgetType,
+ GtkWidgetState *aWidgetState)
+{
+ uint8_t key = GetWidgetStateKey(aWidgetType, aWidgetState);
+ return (aSafeVector[key >> 3] & (1 << (key & 7))) != 0;
+}
+
+static void SetWidgetStateSafe(uint8_t *aSafeVector,
+ uint8_t aWidgetType,
+ GtkWidgetState *aWidgetState)
+{
+ uint8_t key = GetWidgetStateKey(aWidgetType, aWidgetState);
+ aSafeVector[key >> 3] |= (1 << (key & 7));
+}
+
+/* static */ GtkTextDirection
+nsNativeThemeGTK::GetTextDirection(nsIFrame* aFrame)
+{
+ // IsFrameRTL() treats vertical-rl modes as right-to-left (in addition to
+ // horizontal text with direction=RTL), rather than just considering the
+ // text direction. GtkTextDirection does not have distinct values for
+ // vertical writing modes, but considering the block flow direction is
+ // important for resizers and scrollbar elements, at least.
+ return IsFrameRTL(aFrame) ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR;
+}
+
+// Returns positive for negative margins (otherwise 0).
+gint
+nsNativeThemeGTK::GetTabMarginPixels(nsIFrame* aFrame)
+{
+ nscoord margin =
+ IsBottomTab(aFrame) ? aFrame->GetUsedMargin().top
+ : aFrame->GetUsedMargin().bottom;
+
+ return std::min<gint>(MOZ_GTK_TAB_MARGIN_MASK,
+ std::max(0,
+ aFrame->PresContext()->AppUnitsToDevPixels(-margin)));
+}
+
+static bool ShouldScrollbarButtonBeDisabled(int32_t aCurpos, int32_t aMaxpos,
+ uint8_t 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)));
+}
+
+bool
+nsNativeThemeGTK::GetGtkWidgetAndState(uint8_t aWidgetType, nsIFrame* aFrame,
+ WidgetNodeType& aGtkWidgetType,
+ GtkWidgetState* aState,
+ gint* aWidgetFlags)
+{
+ 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)) {
+
+ nsIAtom* atom = nullptr;
+ if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) {
+ if (aWidgetType == NS_THEME_CHECKBOX_LABEL ||
+ aWidgetType == NS_THEME_RADIO_LABEL) {
+ // Adjust stateFrame so GetContentState finds the correct state.
+ stateFrame = aFrame = aFrame->GetParent()->GetParent();
+ } else {
+ // GetContentState knows to look one frame up for radio/checkbox
+ // widgets, so don't adjust stateFrame here.
+ aFrame = aFrame->GetParent();
+ }
+ if (aWidgetFlags) {
+ if (!atom) {
+ atom = (aWidgetType == NS_THEME_CHECKBOX ||
+ aWidgetType == NS_THEME_CHECKBOX_LABEL) ? nsGkAtoms::checked
+ : nsGkAtoms::selected;
+ }
+ *aWidgetFlags = CheckBooleanAttr(aFrame, atom);
+ }
+ } else {
+ if (aWidgetFlags) {
+ nsCOMPtr<nsIDOMHTMLInputElement> inputElt(do_QueryInterface(aFrame->GetContent()));
+ *aWidgetFlags = 0;
+ if (inputElt) {
+ bool isHTMLChecked;
+ inputElt->GetChecked(&isHTMLChecked);
+ if (isHTMLChecked)
+ *aWidgetFlags |= MOZ_GTK_WIDGET_CHECKED;
+ }
+
+ if (GetIndeterminate(aFrame))
+ *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) {
+ // The state of an arrow comes from its parent.
+ stateFrame = aFrame = aFrame->GetParent();
+ }
+
+ EventStates eventState = GetContentState(stateFrame, aWidgetType);
+
+ aState->disabled = IsDisabled(aFrame, eventState) || IsReadOnly(aFrame);
+ aState->active = eventState.HasState(NS_EVENT_STATE_ACTIVE);
+ aState->focused = eventState.HasState(NS_EVENT_STATE_FOCUS);
+ aState->inHover = eventState.HasState(NS_EVENT_STATE_HOVER);
+ aState->isDefault = IsDefaultButton(aFrame);
+ aState->canDefault = FALSE; // XXX fix me
+ aState->depressed = FALSE;
+
+ if (aWidgetType == NS_THEME_FOCUS_OUTLINE) {
+ aState->disabled = FALSE;
+ aState->active = FALSE;
+ aState->inHover = FALSE;
+ aState->isDefault = FALSE;
+ aState->canDefault = FALSE;
+
+ 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) {
+ aState->active &= aState->inHover;
+ }
+
+ if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) {
+ // 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) {
+ aState->focused = IsFocused(aFrame);
+ } else if (aWidgetType == NS_THEME_RADIO ||
+ aWidgetType == NS_THEME_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) {
+ // 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();
+
+ aState->curpos = CheckIntAttr(tmpFrame, nsGkAtoms::curpos, 0);
+ aState->maxpos = CheckIntAttr(tmpFrame, nsGkAtoms::maxpos, 100);
+
+ if (CheckBooleanAttr(aFrame, nsGkAtoms::active)) {
+ aState->active = TRUE;
+ // Set hover state to emulate Gtk style of active scrollbar thumb
+ aState->inHover = TRUE;
+ }
+ }
+
+ if (aWidgetType == NS_THEME_SCROLLBARBUTTON_UP ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT) {
+ // 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);
+ int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
+ if (ShouldScrollbarButtonBeDisabled(curpos, maxpos, aWidgetType)) {
+ aState->disabled = true;
+ }
+
+ // In order to simulate native GTK scrollbar click behavior,
+ // we set the active attribute on the element to true if it's
+ // pressed with any mouse button.
+ // This allows us to show that it's active without setting :active
+ else if (CheckBooleanAttr(aFrame, nsGkAtoms::active))
+ aState->active = true;
+
+ if (aWidgetFlags) {
+ *aWidgetFlags = GetScrollbarButtonType(aFrame);
+ if (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP < 2)
+ *aWidgetFlags |= MOZ_GTK_STEPPER_VERTICAL;
+ }
+ }
+
+ // menu item state is determined by the attribute "_moz-menuactive",
+ // and not by the mouse hovering (accessibility). as a special case,
+ // 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) {
+ bool isTopLevel = false;
+ nsMenuFrame *menuFrame = do_QueryFrame(aFrame);
+ if (menuFrame) {
+ isTopLevel = menuFrame->IsOnMenuBar();
+ }
+
+ if (isTopLevel) {
+ aState->inHover = menuFrame->IsOpen();
+ } else {
+ aState->inHover = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
+ }
+
+ aState->active = FALSE;
+
+ if (aWidgetType == NS_THEME_CHECKMENUITEM ||
+ aWidgetType == NS_THEME_RADIOMENUITEM) {
+ *aWidgetFlags = 0;
+ if (aFrame && aFrame->GetContent()) {
+ *aWidgetFlags = aFrame->GetContent()->
+ AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
+ nsGkAtoms::_true, eIgnoreCase);
+ }
+ }
+ }
+
+ // 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) {
+ bool menuOpen = IsOpenButton(aFrame);
+ aState->depressed = IsCheckedButton(aFrame) || menuOpen;
+ // we must not highlight buttons with open drop down menus on hover.
+ aState->inHover = aState->inHover && !menuOpen;
+ }
+
+ // 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) {
+ *aWidgetFlags = CheckBooleanAttr(aFrame, nsGkAtoms::parentfocused);
+ }
+ }
+ }
+
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_RELIEF_NORMAL;
+ aGtkWidgetType = MOZ_GTK_BUTTON;
+ break;
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_DUALBUTTON:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_RELIEF_NONE;
+ aGtkWidgetType = MOZ_GTK_TOOLBAR_BUTTON;
+ break;
+ case NS_THEME_FOCUS_OUTLINE:
+ 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:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_BUTTON;
+ break;
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_VERTICAL;
+ if (GetWidgetTransparency(aFrame, aWidgetType) == eOpaque)
+ *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE;
+ else
+ *aWidgetFlags = 0;
+ break;
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_HORIZONTAL;
+ if (GetWidgetTransparency(aFrame, aWidgetType) == eOpaque)
+ *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE;
+ else
+ *aWidgetFlags = 0;
+ break;
+ case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL;
+ break;
+ case NS_THEME_SCROLLBARTRACK_VERTICAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL;
+ break;
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_VERTICAL;
+ break;
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL;
+ break;
+ case NS_THEME_SPINNER:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON;
+ break;
+ case NS_THEME_SPINNER_UPBUTTON:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON_UP;
+ break;
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON_DOWN;
+ break;
+ case NS_THEME_SPINNER_TEXTFIELD:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON_ENTRY;
+ break;
+ case NS_THEME_RANGE:
+ {
+ if (IsRangeHorizontal(aFrame)) {
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL;
+ } else {
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL;
+ }
+ break;
+ }
+ case NS_THEME_RANGE_THUMB:
+ {
+ if (IsRangeHorizontal(aFrame)) {
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL;
+ } else {
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL;
+ }
+ break;
+ }
+ case NS_THEME_SCALE_HORIZONTAL:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL;
+ break;
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL;
+ break;
+ case NS_THEME_SCALE_VERTICAL:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL;
+ break;
+ case NS_THEME_SEPARATOR:
+ aGtkWidgetType = MOZ_GTK_TOOLBAR_SEPARATOR;
+ break;
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL;
+ break;
+ case NS_THEME_TOOLBARGRIPPER:
+ aGtkWidgetType = MOZ_GTK_GRIPPER;
+ break;
+ case NS_THEME_RESIZER:
+ aGtkWidgetType = MOZ_GTK_RESIZER;
+ break;
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ aGtkWidgetType = MOZ_GTK_ENTRY;
+ break;
+ case NS_THEME_TEXTFIELD_MULTILINE:
+#if (MOZ_WIDGET_GTK == 3)
+ aGtkWidgetType = MOZ_GTK_TEXT_VIEW;
+#else
+ aGtkWidgetType = MOZ_GTK_ENTRY;
+#endif
+ break;
+ case NS_THEME_LISTBOX:
+ case NS_THEME_TREEVIEW:
+ aGtkWidgetType = MOZ_GTK_TREEVIEW;
+ break;
+ case NS_THEME_TREEHEADERCELL:
+ if (aWidgetFlags) {
+ // In this case, the flag denotes whether the header is the sorted one or not
+ if (GetTreeSortDirection(aFrame) == eTreeSortDirection_Natural)
+ *aWidgetFlags = false;
+ else
+ *aWidgetFlags = true;
+ }
+ aGtkWidgetType = MOZ_GTK_TREE_HEADER_CELL;
+ break;
+ case NS_THEME_TREEHEADERSORTARROW:
+ if (aWidgetFlags) {
+ switch (GetTreeSortDirection(aFrame)) {
+ case eTreeSortDirection_Ascending:
+ *aWidgetFlags = GTK_ARROW_DOWN;
+ break;
+ case eTreeSortDirection_Descending:
+ *aWidgetFlags = GTK_ARROW_UP;
+ break;
+ case eTreeSortDirection_Natural:
+ default:
+ /* This prevents the treecolums from getting smaller
+ * and wider when switching sort direction off and on
+ * */
+ *aWidgetFlags = GTK_ARROW_NONE;
+ break;
+ }
+ }
+ aGtkWidgetType = MOZ_GTK_TREE_HEADER_SORTARROW;
+ break;
+ case NS_THEME_TREETWISTY:
+ aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_EXPANDER_COLLAPSED;
+ break;
+ case NS_THEME_TREETWISTYOPEN:
+ aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_EXPANDER_EXPANDED;
+ break;
+ case NS_THEME_MENULIST:
+ aGtkWidgetType = MOZ_GTK_DROPDOWN;
+ if (aWidgetFlags)
+ *aWidgetFlags = IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML);
+ break;
+ case NS_THEME_MENULIST_TEXT:
+ return false; // nothing to do, but prevents the bg from being drawn
+ case NS_THEME_MENULIST_TEXTFIELD:
+ aGtkWidgetType = MOZ_GTK_DROPDOWN_ENTRY;
+ break;
+ case NS_THEME_MENULIST_BUTTON:
+ 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:
+ aGtkWidgetType = MOZ_GTK_TOOLBARBUTTON_ARROW;
+ if (aWidgetFlags) {
+ *aWidgetFlags = GTK_ARROW_DOWN;
+
+ if (aWidgetType == NS_THEME_BUTTON_ARROW_UP)
+ *aWidgetFlags = GTK_ARROW_UP;
+ else if (aWidgetType == NS_THEME_BUTTON_ARROW_NEXT)
+ *aWidgetFlags = GTK_ARROW_RIGHT;
+ else if (aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS)
+ *aWidgetFlags = GTK_ARROW_LEFT;
+ }
+ break;
+ case NS_THEME_CHECKBOX_CONTAINER:
+ aGtkWidgetType = MOZ_GTK_CHECKBUTTON_CONTAINER;
+ break;
+ case NS_THEME_RADIO_CONTAINER:
+ aGtkWidgetType = MOZ_GTK_RADIOBUTTON_CONTAINER;
+ break;
+ case NS_THEME_CHECKBOX_LABEL:
+ aGtkWidgetType = MOZ_GTK_CHECKBUTTON_LABEL;
+ break;
+ case NS_THEME_RADIO_LABEL:
+ aGtkWidgetType = MOZ_GTK_RADIOBUTTON_LABEL;
+ break;
+ case NS_THEME_TOOLBAR:
+ aGtkWidgetType = MOZ_GTK_TOOLBAR;
+ break;
+ case NS_THEME_TOOLTIP:
+ aGtkWidgetType = MOZ_GTK_TOOLTIP;
+ break;
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ aGtkWidgetType = MOZ_GTK_FRAME;
+ break;
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ aGtkWidgetType = MOZ_GTK_PROGRESSBAR;
+ break;
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ {
+ nsIFrame* stateFrame = aFrame->GetParent();
+ EventStates eventStates = GetContentState(stateFrame, aWidgetType);
+
+ aGtkWidgetType = IsIndeterminateProgress(stateFrame, eventStates)
+ ? IsVerticalProgress(stateFrame)
+ ? MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE
+ : MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE
+ : MOZ_GTK_PROGRESS_CHUNK;
+ }
+ break;
+ case NS_THEME_TAB_SCROLL_ARROW_BACK:
+ case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
+ if (aWidgetFlags)
+ *aWidgetFlags = aWidgetType == NS_THEME_TAB_SCROLL_ARROW_BACK ?
+ GTK_ARROW_LEFT : GTK_ARROW_RIGHT;
+ aGtkWidgetType = MOZ_GTK_TAB_SCROLLARROW;
+ break;
+ case NS_THEME_TABPANELS:
+ aGtkWidgetType = MOZ_GTK_TABPANELS;
+ break;
+ case NS_THEME_TAB:
+ {
+ if (IsBottomTab(aFrame)) {
+ aGtkWidgetType = MOZ_GTK_TAB_BOTTOM;
+ } else {
+ aGtkWidgetType = MOZ_GTK_TAB_TOP;
+ }
+
+ if (aWidgetFlags) {
+ /* First bits will be used to store max(0,-bmargin) where bmargin
+ * is the bottom margin of the tab in pixels (resp. top margin,
+ * for bottom tabs). */
+ *aWidgetFlags = GetTabMarginPixels(aFrame);
+
+ if (IsSelectedTab(aFrame))
+ *aWidgetFlags |= MOZ_GTK_TAB_SELECTED;
+
+ if (IsFirstTab(aFrame))
+ *aWidgetFlags |= MOZ_GTK_TAB_FIRST;
+ }
+ }
+ break;
+ case NS_THEME_SPLITTER:
+ if (IsHorizontal(aFrame))
+ aGtkWidgetType = MOZ_GTK_SPLITTER_VERTICAL;
+ else
+ aGtkWidgetType = MOZ_GTK_SPLITTER_HORIZONTAL;
+ break;
+ case NS_THEME_MENUBAR:
+ aGtkWidgetType = MOZ_GTK_MENUBAR;
+ break;
+ case NS_THEME_MENUPOPUP:
+ aGtkWidgetType = MOZ_GTK_MENUPOPUP;
+ break;
+ case NS_THEME_MENUITEM:
+ {
+ nsMenuFrame *menuFrame = do_QueryFrame(aFrame);
+ if (menuFrame && menuFrame->IsOnMenuBar()) {
+ aGtkWidgetType = MOZ_GTK_MENUBARITEM;
+ break;
+ }
+ }
+ aGtkWidgetType = MOZ_GTK_MENUITEM;
+ break;
+ case NS_THEME_MENUSEPARATOR:
+ aGtkWidgetType = MOZ_GTK_MENUSEPARATOR;
+ break;
+ case NS_THEME_MENUARROW:
+ aGtkWidgetType = MOZ_GTK_MENUARROW;
+ break;
+ case NS_THEME_CHECKMENUITEM:
+ aGtkWidgetType = MOZ_GTK_CHECKMENUITEM;
+ break;
+ case NS_THEME_RADIOMENUITEM:
+ aGtkWidgetType = MOZ_GTK_RADIOMENUITEM;
+ break;
+ case NS_THEME_WINDOW:
+ case NS_THEME_DIALOG:
+ aGtkWidgetType = MOZ_GTK_WINDOW;
+ break;
+ case NS_THEME_GTK_INFO_BAR:
+ aGtkWidgetType = MOZ_GTK_INFO_BAR;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+#if (MOZ_WIDGET_GTK == 2)
+class ThemeRenderer : public gfxGdkNativeRenderer {
+public:
+ ThemeRenderer(GtkWidgetState aState, WidgetNodeType aGTKWidgetType,
+ gint aFlags, GtkTextDirection aDirection,
+ const GdkRectangle& aGDKRect, const GdkRectangle& aGDKClip)
+ : mState(aState), mGTKWidgetType(aGTKWidgetType), mFlags(aFlags),
+ mDirection(aDirection), mGDKRect(aGDKRect), mGDKClip(aGDKClip) {}
+ nsresult DrawWithGDK(GdkDrawable * drawable, gint offsetX, gint offsetY,
+ GdkRectangle * clipRects, uint32_t numClipRects);
+private:
+ GtkWidgetState mState;
+ WidgetNodeType mGTKWidgetType;
+ gint mFlags;
+ GtkTextDirection mDirection;
+ const GdkRectangle& mGDKRect;
+ const GdkRectangle& mGDKClip;
+};
+
+nsresult
+ThemeRenderer::DrawWithGDK(GdkDrawable * drawable, gint offsetX,
+ gint offsetY, GdkRectangle * clipRects, uint32_t numClipRects)
+{
+ GdkRectangle gdk_rect = mGDKRect;
+ gdk_rect.x += offsetX;
+ gdk_rect.y += offsetY;
+
+ GdkRectangle gdk_clip = mGDKClip;
+ gdk_clip.x += offsetX;
+ gdk_clip.y += offsetY;
+
+ GdkRectangle surfaceRect;
+ surfaceRect.x = 0;
+ surfaceRect.y = 0;
+ gdk_drawable_get_size(drawable, &surfaceRect.width, &surfaceRect.height);
+ gdk_rectangle_intersect(&gdk_clip, &surfaceRect, &gdk_clip);
+
+ NS_ASSERTION(numClipRects == 0, "We don't support clipping!!!");
+ moz_gtk_widget_paint(mGTKWidgetType, drawable, &gdk_rect, &gdk_clip,
+ &mState, mFlags, mDirection);
+
+ return NS_OK;
+}
+#else
+class SystemCairoClipper : public ClipExporter {
+public:
+ explicit SystemCairoClipper(cairo_t* aContext) : mContext(aContext)
+ {
+ }
+
+ void
+ BeginClip(const Matrix& aTransform) override
+ {
+ cairo_matrix_t mat;
+ GfxMatrixToCairoMatrix(aTransform, mat);
+ cairo_set_matrix(mContext, &mat);
+
+ cairo_new_path(mContext);
+ }
+
+ void
+ MoveTo(const Point &aPoint) override
+ {
+ cairo_move_to(mContext, aPoint.x, aPoint.y);
+ mCurrentPoint = aPoint;
+ }
+
+ void
+ LineTo(const Point &aPoint) override
+ {
+ cairo_line_to(mContext, aPoint.x, aPoint.y);
+ mCurrentPoint = aPoint;
+ }
+
+ void
+ BezierTo(const Point &aCP1, const Point &aCP2, const Point &aCP3) override
+ {
+ cairo_curve_to(mContext, aCP1.x, aCP1.y, aCP2.x, aCP2.y, aCP3.x, aCP3.y);
+ mCurrentPoint = aCP3;
+ }
+
+ void
+ QuadraticBezierTo(const Point &aCP1, const Point &aCP2) override
+ {
+ Point CP0 = CurrentPoint();
+ Point CP1 = (CP0 + aCP1 * 2.0) / 3.0;
+ Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0;
+ Point CP3 = aCP2;
+ cairo_curve_to(mContext, CP1.x, CP1.y, CP2.x, CP2.y, CP3.x, CP3.y);
+ mCurrentPoint = aCP2;
+ }
+
+ void
+ Arc(const Point &aOrigin, float aRadius, float aStartAngle, float aEndAngle,
+ bool aAntiClockwise) override
+ {
+ ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle,
+ aAntiClockwise);
+ }
+
+ void
+ Close() override
+ {
+ cairo_close_path(mContext);
+ }
+
+ void
+ EndClip() override
+ {
+ cairo_clip(mContext);
+ }
+
+ Point
+ CurrentPoint() const override
+ {
+ return mCurrentPoint;
+ }
+
+private:
+ cairo_t* mContext;
+ Point mCurrentPoint;
+};
+
+static void
+DrawThemeWithCairo(gfxContext* aContext, DrawTarget* aDrawTarget,
+ GtkWidgetState aState, WidgetNodeType aGTKWidgetType,
+ gint aFlags, GtkTextDirection aDirection, gint aScaleFactor,
+ bool aSnapped, const Point& aDrawOrigin, const nsIntSize& aDrawSize,
+ GdkRectangle& aGDKRect, nsITheme::Transparency aTransparency)
+{
+ Point drawOffset;
+ Matrix transform;
+ if (!aSnapped) {
+ // If we are not snapped, we depend on the DT for translation.
+ drawOffset = aDrawOrigin;
+ transform = aDrawTarget->GetTransform().PreTranslate(aDrawOrigin);
+ } else {
+ // Otherwise, we only need to take the device offset into account.
+ drawOffset = aDrawOrigin - aContext->GetDeviceOffset();
+ transform = Matrix::Translation(drawOffset);
+ }
+
+ if (aScaleFactor != 1)
+ transform.PreScale(aScaleFactor, aScaleFactor);
+
+ cairo_matrix_t mat;
+ GfxMatrixToCairoMatrix(transform, mat);
+
+ nsIntSize clipSize((aDrawSize.width + aScaleFactor - 1) / aScaleFactor,
+ (aDrawSize.height + aScaleFactor - 1) / aScaleFactor);
+
+#ifndef MOZ_TREE_CAIRO
+ // Directly use the Cairo draw target to render the widget if using system Cairo everywhere.
+ BorrowedCairoContext borrowCairo(aDrawTarget);
+ if (borrowCairo.mCairo) {
+ cairo_set_matrix(borrowCairo.mCairo, &mat);
+
+ cairo_new_path(borrowCairo.mCairo);
+ cairo_rectangle(borrowCairo.mCairo, 0, 0, clipSize.width, clipSize.height);
+ cairo_clip(borrowCairo.mCairo);
+
+ moz_gtk_widget_paint(aGTKWidgetType, borrowCairo.mCairo, &aGDKRect, &aState, aFlags, aDirection);
+
+ borrowCairo.Finish();
+ return;
+ }
+#endif
+
+ // A direct Cairo draw target is not available, so we need to create a temporary one.
+#if defined(MOZ_X11) && defined(CAIRO_HAS_XLIB_SURFACE)
+ // If using a Cairo xlib surface, then try to reuse it.
+ BorrowedXlibDrawable borrow(aDrawTarget);
+ if (borrow.GetDrawable()) {
+ nsIntSize size = borrow.GetSize();
+ cairo_surface_t* surf = nullptr;
+ // Check if the surface is using XRender.
+#ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE
+ if (borrow.GetXRenderFormat()) {
+ surf = cairo_xlib_surface_create_with_xrender_format(
+ borrow.GetDisplay(), borrow.GetDrawable(), borrow.GetScreen(),
+ borrow.GetXRenderFormat(), size.width, size.height);
+ } else {
+#else
+ if (! borrow.GetXRenderFormat()) {
+#endif
+ surf = cairo_xlib_surface_create(
+ borrow.GetDisplay(), borrow.GetDrawable(), borrow.GetVisual(),
+ size.width, size.height);
+ }
+ if (!NS_WARN_IF(!surf)) {
+ Point offset = borrow.GetOffset();
+ if (offset != Point()) {
+ cairo_surface_set_device_offset(surf, offset.x, offset.y);
+ }
+ cairo_t* cr = cairo_create(surf);
+ if (!NS_WARN_IF(!cr)) {
+ RefPtr<SystemCairoClipper> clipper = new SystemCairoClipper(cr);
+ aContext->ExportClip(*clipper);
+
+ cairo_set_matrix(cr, &mat);
+
+ cairo_new_path(cr);
+ cairo_rectangle(cr, 0, 0, clipSize.width, clipSize.height);
+ cairo_clip(cr);
+
+ moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags, aDirection);
+
+ cairo_destroy(cr);
+ }
+ cairo_surface_destroy(surf);
+ }
+ borrow.Finish();
+ return;
+ }
+#endif
+
+ // Check if the widget requires complex masking that must be composited.
+ // Try to directly write to the draw target's pixels if possible.
+ uint8_t* data;
+ nsIntSize size;
+ int32_t stride;
+ SurfaceFormat format;
+ IntPoint origin;
+ if (aDrawTarget->LockBits(&data, &size, &stride, &format, &origin)) {
+ // Create a Cairo image surface context the device rectangle.
+ cairo_surface_t* surf =
+ cairo_image_surface_create_for_data(
+ data, GfxFormatToCairoFormat(format), size.width, size.height, stride);
+ if (!NS_WARN_IF(!surf)) {
+ if (origin != IntPoint()) {
+ cairo_surface_set_device_offset(surf, -origin.x, -origin.y);
+ }
+ cairo_t* cr = cairo_create(surf);
+ if (!NS_WARN_IF(!cr)) {
+ RefPtr<SystemCairoClipper> clipper = new SystemCairoClipper(cr);
+ aContext->ExportClip(*clipper);
+
+ cairo_set_matrix(cr, &mat);
+
+ cairo_new_path(cr);
+ cairo_rectangle(cr, 0, 0, clipSize.width, clipSize.height);
+ cairo_clip(cr);
+
+ moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags, aDirection);
+
+ cairo_destroy(cr);
+ }
+ cairo_surface_destroy(surf);
+ }
+ aDrawTarget->ReleaseBits(data);
+ } else {
+ // If the widget has any transparency, make sure to choose an alpha format.
+ format = aTransparency != nsITheme::eOpaque ? SurfaceFormat::B8G8R8A8 : aDrawTarget->GetFormat();
+ // Create a temporary data surface to render the widget into.
+ RefPtr<DataSourceSurface> dataSurface =
+ Factory::CreateDataSourceSurface(aDrawSize, format, aTransparency != nsITheme::eOpaque);
+ DataSourceSurface::MappedSurface map;
+ if (!NS_WARN_IF(!(dataSurface && dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)))) {
+ // Create a Cairo image surface wrapping the data surface.
+ cairo_surface_t* surf =
+ cairo_image_surface_create_for_data(map.mData, GfxFormatToCairoFormat(format),
+ aDrawSize.width, aDrawSize.height, map.mStride);
+ cairo_t* cr = nullptr;
+ if (!NS_WARN_IF(!surf)) {
+ cr = cairo_create(surf);
+ if (!NS_WARN_IF(!cr)) {
+ if (aScaleFactor != 1) {
+ cairo_scale(cr, aScaleFactor, aScaleFactor);
+ }
+
+ moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags, aDirection);
+ }
+ }
+
+ // Unmap the surface before using it as a source
+ dataSurface->Unmap();
+
+ if (cr) {
+ if (!aSnapped || aTransparency != nsITheme::eOpaque) {
+ // The widget either needs to be masked or has transparency, so use the slower drawing path.
+ aDrawTarget->DrawSurface(dataSurface,
+ Rect(aSnapped ? drawOffset - aDrawTarget->GetTransform().GetTranslation() : drawOffset,
+ Size(aDrawSize)),
+ Rect(0, 0, aDrawSize.width, aDrawSize.height));
+ } else {
+ // The widget is a simple opaque rectangle, so just copy it out.
+ aDrawTarget->CopySurface(dataSurface,
+ IntRect(0, 0, aDrawSize.width, aDrawSize.height),
+ TruncatedToInt(drawOffset));
+ }
+
+ cairo_destroy(cr);
+ }
+
+ if (surf) {
+ cairo_surface_destroy(surf);
+ }
+ }
+ }
+}
+#endif
+
+bool
+nsNativeThemeGTK::GetExtraSizeForWidget(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIntMargin* aExtra)
+{
+ *aExtra = nsIntMargin(0,0,0,0);
+ // Allow an extra one pixel above and below the thumb for certain
+ // 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:
+ aExtra->top = aExtra->bottom = 1;
+ break;
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ aExtra->left = aExtra->right = 1;
+ break;
+
+ // Include the indicator spacing (the padding around the control).
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ {
+ gint indicator_size, indicator_spacing;
+
+ if (aWidgetType == NS_THEME_CHECKBOX) {
+ moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing);
+ } else {
+ moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing);
+ }
+
+ aExtra->top = indicator_spacing;
+ aExtra->right = indicator_spacing;
+ aExtra->bottom = indicator_spacing;
+ aExtra->left = indicator_spacing;
+ break;
+ }
+ case NS_THEME_BUTTON :
+ {
+ if (IsDefaultButton(aFrame)) {
+ // Some themes draw a default indicator outside the widget,
+ // include that in overflow
+ gint top, left, bottom, right;
+ moz_gtk_button_get_default_overflow(&top, &left, &bottom, &right);
+ aExtra->top = top;
+ aExtra->right = right;
+ aExtra->bottom = bottom;
+ aExtra->left = left;
+ break;
+ }
+ return false;
+ }
+ case NS_THEME_FOCUS_OUTLINE:
+ {
+ moz_gtk_get_focus_outline_size(&aExtra->left, &aExtra->top);
+ aExtra->right = aExtra->left;
+ aExtra->bottom = aExtra->top;
+ break;
+ }
+ case NS_THEME_TAB :
+ {
+ if (!IsSelectedTab(aFrame))
+ return false;
+
+ gint gap_height = moz_gtk_get_tab_thickness(IsBottomTab(aFrame) ?
+ MOZ_GTK_TAB_BOTTOM : MOZ_GTK_TAB_TOP);
+ if (!gap_height)
+ return false;
+
+ int32_t extra = gap_height - GetTabMarginPixels(aFrame);
+ if (extra <= 0)
+ return false;
+
+ if (IsBottomTab(aFrame)) {
+ aExtra->top = extra;
+ } else {
+ aExtra->bottom = extra;
+ }
+ return false;
+ }
+ default:
+ return false;
+ }
+ gint scale = nsScreenGtk::GetGtkMonitorScaleFactor();
+ aExtra->top *= scale;
+ aExtra->right *= scale;
+ aExtra->bottom *= scale;
+ aExtra->left *= scale;
+ return true;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::DrawWidgetBackground(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect)
+{
+ GtkWidgetState state;
+ WidgetNodeType gtkWidgetType;
+ GtkTextDirection direction = GetTextDirection(aFrame);
+ gint flags;
+ if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, &state,
+ &flags))
+ return NS_OK;
+
+ gfxContext* ctx = aContext->ThebesContext();
+ nsPresContext *presContext = aFrame->PresContext();
+
+ gfxRect rect = presContext->AppUnitsToGfxUnits(aRect);
+ gfxRect dirtyRect = presContext->AppUnitsToGfxUnits(aDirtyRect);
+ gint scaleFactor = nsScreenGtk::GetGtkMonitorScaleFactor();
+
+ // Align to device pixels where sensible
+ // to provide crisper and faster drawing.
+ // Don't snap if it's a non-unit scale factor. We're going to have to take
+ // slow paths then in any case.
+ bool snapped = ctx->UserToDevicePixelSnapped(rect);
+ if (snapped) {
+ // Leave rect in device coords but make dirtyRect consistent.
+ dirtyRect = ctx->UserToDevice(dirtyRect);
+ }
+
+ // Translate the dirty rect so that it is wrt the widget top-left.
+ dirtyRect.MoveBy(-rect.TopLeft());
+ // Round out the dirty rect to gdk pixels to ensure that gtk draws
+ // enough pixels for interpolation to device pixels.
+ dirtyRect.RoundOut();
+
+ // GTK themes can only draw an integer number of pixels
+ // (even when not snapped).
+ nsIntRect widgetRect(0, 0, NS_lround(rect.Width()), NS_lround(rect.Height()));
+ nsIntRect overflowRect(widgetRect);
+ nsIntMargin extraSize;
+ if (GetExtraSizeForWidget(aFrame, aWidgetType, &extraSize)) {
+ overflowRect.Inflate(extraSize);
+ }
+
+ // This is the rectangle that will actually be drawn, in gdk pixels
+ nsIntRect drawingRect(int32_t(dirtyRect.X()),
+ int32_t(dirtyRect.Y()),
+ int32_t(dirtyRect.Width()),
+ int32_t(dirtyRect.Height()));
+ if (widgetRect.IsEmpty()
+ || !drawingRect.IntersectRect(overflowRect, drawingRect))
+ return NS_OK;
+
+ NS_ASSERTION(!IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType),
+ "Trying to render an unsafe widget!");
+
+ bool safeState = IsWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state);
+ if (!safeState) {
+ gLastGdkError = 0;
+ gdk_error_trap_push ();
+ }
+
+ Transparency transparency = GetWidgetTransparency(aFrame, aWidgetType);
+
+ // gdk rectangles are wrt the drawing rect.
+ GdkRectangle gdk_rect = {-drawingRect.x/scaleFactor,
+ -drawingRect.y/scaleFactor,
+ widgetRect.width/scaleFactor,
+ widgetRect.height/scaleFactor};
+
+ // translate everything so (0,0) is the top left of the drawingRect
+ gfxPoint origin = rect.TopLeft() + drawingRect.TopLeft();
+
+#if (MOZ_WIDGET_GTK == 2)
+ gfxContextAutoSaveRestore autoSR(ctx);
+ gfxMatrix matrix;
+ if (!snapped) { // else rects are in device coords
+ matrix = ctx->CurrentMatrix();
+ }
+ matrix.Translate(origin);
+ matrix.Scale(scaleFactor, scaleFactor); // Draw in GDK coords
+ ctx->SetMatrix(matrix);
+
+ // The gdk_clip is just advisory here, meaning "you don't
+ // need to draw outside this rect if you don't feel like it!"
+ GdkRectangle gdk_clip = {0, 0, drawingRect.width, drawingRect.height};
+
+ ThemeRenderer renderer(state, gtkWidgetType, flags, direction,
+ gdk_rect, gdk_clip);
+
+ // Some themes (e.g. Clearlooks) just don't clip properly to any
+ // clip rect we provide, so we cannot advertise support for clipping within
+ // the widget bounds.
+ uint32_t rendererFlags = 0;
+ if (transparency == eOpaque) {
+ rendererFlags |= gfxGdkNativeRenderer::DRAW_IS_OPAQUE;
+ }
+
+ // GtkStyles (used by the widget drawing backend) are created for a
+ // particular colormap/visual.
+ GdkColormap* colormap = moz_gtk_widget_get_colormap();
+
+ renderer.Draw(ctx, drawingRect.Size(), rendererFlags, colormap);
+#else
+ DrawThemeWithCairo(ctx, aContext->GetDrawTarget(),
+ state, gtkWidgetType, flags, direction, scaleFactor,
+ snapped, ToPoint(origin), drawingRect.Size(),
+ gdk_rect, transparency);
+#endif
+
+ if (!safeState) {
+ gdk_flush();
+ gLastGdkError = gdk_error_trap_pop ();
+
+ if (gLastGdkError) {
+#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);
+#endif
+ NS_WARNING("GTK theme failed; disabling unsafe widget");
+ SetWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType);
+ // force refresh of the window, because the widget was not
+ // successfully drawn it must be redrawn using the default look
+ RefreshWidgetWindow(aFrame);
+ } else {
+ SetWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state);
+ }
+ }
+
+ // Indeterminate progress bar are animated.
+ if (gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE ||
+ gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE) {
+ if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
+ NS_WARNING("unable to animate widget!");
+ }
+ }
+
+ return NS_OK;
+}
+
+WidgetNodeType
+nsNativeThemeGTK::NativeThemeToGtkTheme(uint8_t aWidgetType, nsIFrame* aFrame)
+{
+ WidgetNodeType gtkWidgetType;
+ gint unusedFlags;
+
+ if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
+ &unusedFlags))
+ {
+ MOZ_ASSERT_UNREACHABLE("Unknown native widget to gtk widget mapping");
+ return MOZ_GTK_WINDOW;
+ }
+ return gtkWidgetType;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame,
+ uint8_t aWidgetType, nsIntMargin* aResult)
+{
+ GtkTextDirection direction = GetTextDirection(aFrame);
+ aResult->top = aResult->left = aResult->right = aResult->bottom = 0;
+ switch (aWidgetType) {
+ case NS_THEME_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:
+ // 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
+ // against the edge of the outer button so that the borders overlap.
+ // 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:
+ {
+ 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,
+ (GtkTabFlags)flags, gtkWidgetType);
+ }
+ break;
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_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.
+ if (IsRegularMenuItem(aFrame))
+ break;
+ MOZ_FALLTHROUGH;
+ default:
+ {
+ WidgetNodeType gtkWidgetType;
+ gint unusedFlags;
+ if (GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
+ &unusedFlags)) {
+ moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top,
+ &aResult->right, &aResult->bottom, direction,
+ IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML));
+ }
+ }
+ }
+
+ gint scale = nsScreenGtk::GetGtkMonitorScaleFactor();
+ aResult->top *= scale;
+ aResult->right *= scale;
+ aResult->bottom *= scale;
+ aResult->left *= scale;
+ return NS_OK;
+}
+
+bool
+nsNativeThemeGTK::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON_FOCUS:
+ case NS_THEME_TOOLBARBUTTON:
+ 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:
+ // 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:
+ aResult->SizeTo(0, 0, 0, 0);
+ return true;
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM:
+ {
+ // Menubar and menulist have their padding specified in CSS.
+ if (!IsRegularMenuItem(aFrame))
+ return false;
+
+ aResult->SizeTo(0, 0, 0, 0);
+ WidgetNodeType gtkWidgetType;
+ if (GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
+ nullptr)) {
+ moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top,
+ &aResult->right, &aResult->bottom, GetTextDirection(aFrame),
+ IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML));
+ }
+
+ gint horizontal_padding;
+
+ if (aWidgetType == NS_THEME_MENUITEM)
+ moz_gtk_menuitem_get_horizontal_padding(&horizontal_padding);
+ else
+ moz_gtk_checkmenuitem_get_horizontal_padding(&horizontal_padding);
+
+ aResult->left += horizontal_padding;
+ aResult->right += horizontal_padding;
+
+ gint scale = nsScreenGtk::GetGtkMonitorScaleFactor();
+ aResult->top *= scale;
+ aResult->right *= scale;
+ aResult->bottom *= scale;
+ aResult->left *= scale;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+nsNativeThemeGTK::GetWidgetOverflow(nsDeviceContext* aContext,
+ nsIFrame* aFrame, uint8_t aWidgetType,
+ nsRect* aOverflowRect)
+{
+ nsIntMargin extraSize;
+ if (!GetExtraSizeForWidget(aFrame, aWidgetType, &extraSize))
+ return false;
+
+ int32_t p2a = aContext->AppUnitsPerDevPixel();
+ nsMargin m(NSIntPixelsToAppUnits(extraSize.top, p2a),
+ NSIntPixelsToAppUnits(extraSize.right, p2a),
+ NSIntPixelsToAppUnits(extraSize.bottom, p2a),
+ NSIntPixelsToAppUnits(extraSize.left, p2a));
+
+ aOverflowRect->Inflate(m);
+ return true;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::GetMinimumWidgetSize(nsPresContext* aPresContext,
+ nsIFrame* aFrame, uint8_t aWidgetType,
+ LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable)
+{
+ aResult->width = aResult->height = 0;
+ *aIsOverridable = true;
+
+ switch (aWidgetType) {
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ {
+ if (gtk_check_version(3,20,0) == nullptr) {
+ moz_gtk_get_widget_min_size(MOZ_GTK_SCROLLBAR_BUTTON,
+ &(aResult->width), &(aResult->height));
+ } else {
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+
+ aResult->width = metrics.slider_width;
+ aResult->height = metrics.stepper_size;
+ }
+
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ {
+ if (gtk_check_version(3,20,0) == nullptr) {
+ moz_gtk_get_widget_min_size(MOZ_GTK_SCROLLBAR_BUTTON,
+ &(aResult->width), &(aResult->height));
+ } else {
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+
+ aResult->width = metrics.stepper_size;
+ aResult->height = metrics.slider_width;
+ }
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_SPLITTER:
+ {
+ gint metrics;
+ if (IsHorizontal(aFrame)) {
+ moz_gtk_splitter_get_metrics(GTK_ORIENTATION_HORIZONTAL, &metrics);
+ aResult->width = metrics;
+ aResult->height = 0;
+ } else {
+ moz_gtk_splitter_get_metrics(GTK_ORIENTATION_VERTICAL, &metrics);
+ aResult->width = 0;
+ aResult->height = metrics;
+ }
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ {
+ /* While we enforce a minimum size for the thumb, this is ignored
+ * for the some scrollbars if buttons are hidden (bug 513006) because
+ * the thumb isn't a direct child of the scrollbar, unlike the buttons
+ * or track. So add a minimum size to the track as well to prevent a
+ * 0-width scrollbar. */
+ if (gtk_check_version(3,20,0) == nullptr) {
+ // Thumb min dimensions to start with
+ WidgetNodeType thumbType = aWidgetType == NS_THEME_SCROLLBAR_VERTICAL ?
+ MOZ_GTK_SCROLLBAR_THUMB_VERTICAL : MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL;
+ moz_gtk_get_widget_min_size(thumbType, &(aResult->width), &(aResult->height));
+
+ // Add scrollbar's borders
+ nsIntMargin border;
+ nsNativeThemeGTK::GetWidgetBorder(aFrame->PresContext()->DeviceContext(),
+ aFrame, aWidgetType, &border);
+ aResult->width += border.left + border.right;
+ aResult->height += border.top + border.bottom;
+
+ // Add track's borders
+ uint8_t trackType = aWidgetType == NS_THEME_SCROLLBAR_VERTICAL ?
+ NS_THEME_SCROLLBARTRACK_VERTICAL : NS_THEME_SCROLLBARTRACK_HORIZONTAL;
+ nsNativeThemeGTK::GetWidgetBorder(aFrame->PresContext()->DeviceContext(),
+ aFrame, trackType, &border);
+ aResult->width += border.left + border.right;
+ aResult->height += border.top + border.bottom;
+ } else {
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+
+ // Require room for the slider in the track if we don't have buttons.
+ bool hasScrollbarButtons = moz_gtk_has_scrollbar_buttons();
+
+ if (aWidgetType == NS_THEME_SCROLLBAR_VERTICAL) {
+ aResult->width = metrics.slider_width + 2 * metrics.trough_border;
+ if (!hasScrollbarButtons)
+ aResult->height = metrics.min_slider_size + 2 * metrics.trough_border;
+ } else {
+ aResult->height = metrics.slider_width + 2 * metrics.trough_border;
+ if (!hasScrollbarButtons)
+ aResult->width = metrics.min_slider_size + 2 * metrics.trough_border;
+ }
+ *aIsOverridable = false;
+ }
+
+ }
+ break;
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ {
+ if (gtk_check_version(3,20,0) == nullptr) {
+ moz_gtk_get_widget_min_size(NativeThemeToGtkTheme(aWidgetType, aFrame),
+ &(aResult->width), &(aResult->height));
+ } else {
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+
+ if (aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL) {
+ aResult->width = metrics.slider_width;
+ aResult->height = metrics.min_slider_size;
+ } else {
+ aResult->height = metrics.slider_width;
+ aResult->width = metrics.min_slider_size;
+ }
+ }
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_RANGE_THUMB:
+ {
+ gint thumb_length, thumb_height;
+
+ if (IsRangeHorizontal(aFrame)) {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL, &thumb_length, &thumb_height);
+ } else {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_height, &thumb_length);
+ }
+ aResult->width = thumb_length;
+ aResult->height = thumb_height;
+
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_RANGE:
+ {
+ gint scale_width, scale_height;
+
+ moz_gtk_get_scale_metrics(IsRangeHorizontal(aFrame) ?
+ GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL,
+ &scale_width, &scale_height);
+ aResult->width = scale_width;
+ aResult->height = scale_height;
+
+ *aIsOverridable = true;
+ }
+ break;
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ {
+ gint thumb_length, thumb_height;
+
+ if (aWidgetType == NS_THEME_SCALETHUMB_VERTICAL) {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_length, &thumb_height);
+ aResult->width = thumb_height;
+ aResult->height = thumb_length;
+ } else {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL, &thumb_length, &thumb_height);
+ aResult->width = thumb_length;
+ aResult->height = thumb_height;
+ }
+
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_TAB_SCROLL_ARROW_BACK:
+ case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
+ {
+ moz_gtk_get_tab_scroll_arrow_size(&aResult->width, &aResult->height);
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_MENULIST_BUTTON:
+ {
+ moz_gtk_get_combo_box_entry_button_size(&aResult->width,
+ &aResult->height);
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_MENUSEPARATOR:
+ {
+ gint separator_height;
+
+ moz_gtk_get_menu_separator_height(&separator_height);
+ aResult->height = separator_height;
+
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ {
+ gint indicator_size, indicator_spacing;
+
+ if (aWidgetType == NS_THEME_CHECKBOX) {
+ moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing);
+ } else {
+ moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing);
+ }
+
+ // Include space for the indicator and the padding around it.
+ aResult->width = indicator_size;
+ aResult->height = indicator_size;
+ }
+ 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:
+ {
+ moz_gtk_get_arrow_size(MOZ_GTK_TOOLBARBUTTON_ARROW,
+ &aResult->width, &aResult->height);
+ *aIsOverridable = false;
+ }
+ 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:
+ {
+ if (aWidgetType == NS_THEME_MENULIST) {
+ // Include the arrow size.
+ moz_gtk_get_arrow_size(MOZ_GTK_DROPDOWN,
+ &aResult->width, &aResult->height);
+ }
+ // else the minimum size is missing consideration of container
+ // descendants; the value returned here will not be helpful, but the
+ // box model may consider border and padding with child minimum sizes.
+
+ nsIntMargin border;
+ nsNativeThemeGTK::GetWidgetBorder(aFrame->PresContext()->DeviceContext(),
+ aFrame, aWidgetType, &border);
+ aResult->width += border.left + border.right;
+ aResult->height += border.top + border.bottom;
+ }
+ break;
+#if (MOZ_WIDGET_GTK == 3)
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ {
+ moz_gtk_get_entry_min_height(&aResult->height);
+ }
+ break;
+#endif
+ case NS_THEME_SEPARATOR:
+ {
+ gint separator_width;
+
+ moz_gtk_get_toolbar_separator_width(&separator_width);
+
+ aResult->width = separator_width;
+ }
+ break;
+ case NS_THEME_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:
+ // hard code these sizes
+ aResult->width = 14;
+ aResult->height = 13;
+ break;
+ case NS_THEME_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:
+ {
+ gint expander_size;
+
+ moz_gtk_get_treeview_expander_size(&expander_size);
+ aResult->width = aResult->height = expander_size;
+ *aIsOverridable = false;
+ }
+ break;
+ }
+
+ *aResult = *aResult * nsScreenGtk::GetGtkMonitorScaleFactor();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIAtom* 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) {
+ *aShouldRepaint = false;
+ return NS_OK;
+ }
+
+ if ((aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL ||
+ aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) &&
+ 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) &&
+ (aAttribute == nsGkAtoms::curpos ||
+ aAttribute == nsGkAtoms::maxpos)) {
+ // If 'curpos' has changed and we are passed its old value, we can
+ // determine whether the button's enablement actually needs to change.
+ if (aAttribute == nsGkAtoms::curpos && aOldValue) {
+ int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
+ int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 0);
+ nsAutoString str;
+ aOldValue->ToString(str);
+ nsresult err;
+ int32_t oldCurpos = str.ToInteger(&err);
+ if (str.IsEmpty() || NS_FAILED(err)) {
+ *aShouldRepaint = true;
+ } else {
+ bool disabledBefore = ShouldScrollbarButtonBeDisabled(oldCurpos, maxpos, aWidgetType);
+ bool disabledNow = ShouldScrollbarButtonBeDisabled(curpos, maxpos, aWidgetType);
+ *aShouldRepaint = (disabledBefore != disabledNow);
+ }
+ } else {
+ *aShouldRepaint = true;
+ }
+ return NS_OK;
+ }
+
+ // XXXdwh Not sure what can really be done here. Can at least guess for
+ // specific widgets that they're highly unlikely to have certain states.
+ // For example, a toolbar doesn't care about any states.
+ if (!aAttribute) {
+ // Hover/focus/active changed. Always repaint.
+ *aShouldRepaint = true;
+ }
+ else {
+ // Check the attribute to see if it's relevant.
+ // disabled, checked, dlgtype, default, etc.
+ *aShouldRepaint = false;
+ if (aAttribute == nsGkAtoms::disabled ||
+ aAttribute == nsGkAtoms::checked ||
+ aAttribute == nsGkAtoms::selected ||
+ aAttribute == nsGkAtoms::visuallyselected ||
+ aAttribute == nsGkAtoms::focused ||
+ aAttribute == nsGkAtoms::readonly ||
+ aAttribute == nsGkAtoms::_default ||
+ aAttribute == nsGkAtoms::menuactive ||
+ aAttribute == nsGkAtoms::open ||
+ aAttribute == nsGkAtoms::parentfocused)
+ *aShouldRepaint = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::ThemeChanged()
+{
+ memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes));
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ if (IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType))
+ return false;
+
+ 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:
+ 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_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:
+#if (MOZ_WIDGET_GTK == 3)
+ case NS_THEME_GTK_INFO_BAR:
+#endif
+ return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
+
+ case NS_THEME_MENULIST_BUTTON:
+ if (aFrame && aFrame->GetWritingMode().IsVertical()) {
+ return false;
+ }
+ // "Native" dropdown buttons cause padding and margin problems, but only
+ // in HTML so allow them in XUL.
+ return (!aFrame || IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) &&
+ !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
+
+ case NS_THEME_FOCUS_OUTLINE:
+ return true;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP_(bool)
+nsNativeThemeGTK::WidgetIsContainer(uint8_t aWidgetType)
+{
+ // 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)
+ return false;
+ return true;
+}
+
+bool
+nsNativeThemeGTK::ThemeDrawsFocusForWidget(uint8_t aWidgetType)
+{
+ if (aWidgetType == NS_THEME_MENULIST ||
+ aWidgetType == NS_THEME_BUTTON ||
+ aWidgetType == NS_THEME_TREEHEADERCELL)
+ return true;
+
+ return false;
+}
+
+bool
+nsNativeThemeGTK::ThemeNeedsComboboxDropmarker()
+{
+ return false;
+}
+
+nsITheme::Transparency
+nsNativeThemeGTK::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ // These widgets always draw a default background.
+#if (MOZ_WIDGET_GTK == 2)
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_MENUBAR:
+#endif
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_WINDOW:
+ case NS_THEME_DIALOG:
+ return eOpaque;
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+#if (MOZ_WIDGET_GTK == 3)
+ // Make scrollbar tracks opaque on the window's scroll frame to prevent
+ // leaf layers from overlapping. See bug 1179780.
+ if (!(CheckBooleanAttr(aFrame, nsGkAtoms::root_) &&
+ aFrame->PresContext()->IsRootContentDocument() &&
+ IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)))
+ return eTransparent;
+#endif
+ return eOpaque;
+ // Tooltips use gtk_paint_flat_box() on Gtk2
+ // but are shaped on Gtk3
+ case NS_THEME_TOOLTIP:
+#if (MOZ_WIDGET_GTK == 2)
+ return eOpaque;
+#else
+ return eTransparent;
+#endif
+ }
+
+ return eUnknownTransparency;
+}
diff --git a/widget/gtk/nsNativeThemeGTK.h b/widget/gtk/nsNativeThemeGTK.h
new file mode 100644
index 000000000..56ae0317f
--- /dev/null
+++ b/widget/gtk/nsNativeThemeGTK.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 _GTK_NSNATIVETHEMEGTK_H_
+#define _GTK_NSNATIVETHEMEGTK_H_
+
+#include "nsITheme.h"
+#include "nsCOMPtr.h"
+#include "nsIAtom.h"
+#include "nsIObserver.h"
+#include "nsNativeTheme.h"
+
+#include <gtk/gtk.h>
+#include "gtkdrawing.h"
+
+class nsNativeThemeGTK: private nsNativeTheme,
+ public nsITheme,
+ public nsIObserver {
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSIOBSERVER
+
+ // The nsITheme interface.
+ NS_IMETHOD DrawWidgetBackground(nsRenderingContext* aContext,
+ nsIFrame* aFrame, uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) 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;
+
+ virtual bool GetWidgetOverflow(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsRect* aOverflowRect) override;
+
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext,
+ nsIFrame* aFrame, uint8_t aWidgetType,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) override;
+
+ NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIAtom* aAttribute,
+ bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) override;
+
+ NS_IMETHOD ThemeChanged() override;
+
+ NS_IMETHOD_(bool) ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType) override;
+
+ NS_IMETHOD_(bool) WidgetIsContainer(uint8_t aWidgetType) override;
+
+ NS_IMETHOD_(bool) ThemeDrawsFocusForWidget(uint8_t aWidgetType) override;
+
+ virtual bool ThemeNeedsComboboxDropmarker() override;
+
+ virtual Transparency GetWidgetTransparency(nsIFrame* aFrame,
+ uint8_t aWidgetType) override;
+
+ nsNativeThemeGTK();
+
+protected:
+ virtual ~nsNativeThemeGTK();
+
+private:
+ GtkTextDirection GetTextDirection(nsIFrame* aFrame);
+ gint GetTabMarginPixels(nsIFrame* aFrame);
+ bool GetGtkWidgetAndState(uint8_t aWidgetType, nsIFrame* aFrame,
+ WidgetNodeType& aGtkWidgetType,
+ GtkWidgetState* aState, gint* aWidgetFlags);
+ bool GetExtraSizeForWidget(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIntMargin* aExtra);
+
+ void RefreshWidgetWindow(nsIFrame* aFrame);
+ WidgetNodeType NativeThemeToGtkTheme(uint8_t aWidgetType, nsIFrame* aFrame);
+
+ uint8_t mDisabledWidgetTypes[32];
+ uint8_t mSafeWidgetStates[1024]; // 256 widgets * 32 bits per widget
+ static const char* sDisabledEngines[];
+};
+
+#endif
diff --git a/widget/gtk/nsPSPrinters.cpp b/widget/gtk/nsPSPrinters.cpp
new file mode 100644
index 000000000..2d3183e9a
--- /dev/null
+++ b/widget/gtk/nsPSPrinters.cpp
@@ -0,0 +1,123 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ex: set tabstop=8 softtabstop=4 shiftwidth=4 expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "nscore.h"
+#include "nsCUPSShim.h"
+#include "nsIServiceManager.h"
+#include "nsPSPrinters.h"
+#include "nsReadableUtils.h" // StringBeginsWith()
+#include "nsCUPSShim.h"
+#include "mozilla/Preferences.h"
+
+#include "prlink.h"
+#include "prenv.h"
+#include "plstr.h"
+
+using namespace mozilla;
+
+#define NS_CUPS_PRINTER "CUPS/"
+#define NS_CUPS_PRINTER_LEN (sizeof(NS_CUPS_PRINTER) - 1)
+
+/* dummy printer name for the gfx/src/ps driver */
+#define NS_POSTSCRIPT_DRIVER_NAME "PostScript/"
+
+nsCUPSShim gCupsShim;
+
+nsPSPrinterList::nsPSPrinterList()
+{
+ // Should we try cups?
+ if (Preferences::GetBool("print.postscript.cups.enabled", true) &&
+ !gCupsShim.IsInitialized()) {
+ gCupsShim.Init();
+ }
+}
+
+
+/* Check whether the PostScript module has been disabled at runtime */
+bool
+nsPSPrinterList::Enabled()
+{
+ const char *val = PR_GetEnv("MOZILLA_POSTSCRIPT_ENABLED");
+ if (val && (val[0] == '0' || !PL_strcasecmp(val, "false")))
+ return false;
+
+ // is the PS module enabled?
+ return Preferences::GetBool("print.postscript.enabled", true);
+}
+
+
+/* Fetch a list of printers handled by the PostsScript module */
+void
+nsPSPrinterList::GetPrinterList(nsTArray<nsCString>& aList)
+{
+ aList.Clear();
+
+ // Query CUPS for a printer list. The default printer goes to the
+ // head of the output list; others are appended.
+ if (gCupsShim.IsInitialized()) {
+ cups_dest_t *dests;
+
+ int num_dests = (gCupsShim.mCupsGetDests)(&dests);
+ if (num_dests) {
+ for (int i = 0; i < num_dests; i++) {
+ nsAutoCString fullName(NS_CUPS_PRINTER);
+ fullName.Append(dests[i].name);
+ if (dests[i].instance != nullptr) {
+ fullName.Append('/');
+ fullName.Append(dests[i].instance);
+ }
+ if (dests[i].is_default)
+ aList.InsertElementAt(0, fullName);
+ else
+ aList.AppendElement(fullName);
+ }
+ }
+ (gCupsShim.mCupsFreeDests)(num_dests, dests);
+ }
+
+ // Build the "classic" list of printers -- those accessed by running
+ // an opaque command. This list always contains a printer named "default".
+ // In addition, we look for either an environment variable
+ // MOZILLA_POSTSCRIPT_PRINTER_LIST or a preference setting
+ // print.printer_list, which contains a space-separated list of printer
+ // names.
+ aList.AppendElement(
+ NS_LITERAL_CSTRING(NS_POSTSCRIPT_DRIVER_NAME "default"));
+
+ nsAutoCString list(PR_GetEnv("MOZILLA_POSTSCRIPT_PRINTER_LIST"));
+ if (list.IsEmpty()) {
+ list = Preferences::GetCString("print.printer_list");
+ }
+ if (!list.IsEmpty()) {
+ // For each printer (except "default" which was already added),
+ // construct a string "PostScript/<name>" and append it to the list.
+ char *state;
+
+ for (char *name = PL_strtok_r(list.BeginWriting(), " ", &state);
+ nullptr != name;
+ name = PL_strtok_r(nullptr, " ", &state)
+ ) {
+ if (0 != strcmp(name, "default")) {
+ nsAutoCString fullName(NS_POSTSCRIPT_DRIVER_NAME);
+ fullName.Append(name);
+ aList.AppendElement(fullName);
+ }
+ }
+ }
+}
+
+
+/* Identify the printer type */
+nsPSPrinterList::PrinterType
+nsPSPrinterList::GetPrinterType(const nsACString& aName)
+{
+ if (StringBeginsWith(aName, NS_LITERAL_CSTRING(NS_POSTSCRIPT_DRIVER_NAME)))
+ return kTypePS;
+ else if (StringBeginsWith(aName, NS_LITERAL_CSTRING(NS_CUPS_PRINTER)))
+ return kTypeCUPS;
+ else
+ return kTypeUnknown;
+}
diff --git a/widget/gtk/nsPSPrinters.h b/widget/gtk/nsPSPrinters.h
new file mode 100644
index 000000000..b584ef715
--- /dev/null
+++ b/widget/gtk/nsPSPrinters.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ex: set tabstop=8 softtabstop=4 shiftwidth=4 expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 nsPSPrinters_h___
+#define nsPSPrinters_h___
+
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsPSPrinterList {
+ public:
+ nsPSPrinterList();
+
+ /**
+ * Is the PostScript module enabled or disabled?
+ * @return true if enabled,
+ * false if not.
+ */
+ bool Enabled();
+
+ /**
+ * Obtain a list of printers (print destinations) supported by the
+ * PostScript module, Each entry will be in the form <type>/<name>,
+ * where <type> is a printer type string, and <name> is the actual
+ * printer name.
+ *
+ * @param aList Upon return, this is populated with the list of
+ * printer names as described above, replacing any
+ * previous contents. Each entry is a UTF8 string.
+ * There should always be at least one entry. The
+ * first entry is the default print destination.
+ */
+ void GetPrinterList(nsTArray<nsCString>& aList);
+
+ enum PrinterType {
+ kTypeUnknown, // Not actually handled by the PS module
+ kTypePS, // Generic postscript module printer
+ kTypeCUPS // CUPS printer
+ };
+
+ /**
+ * Identify a printer's type from its name.
+ * @param aName The printer's full name as a UTF8 string, including
+ * the <type> portion as described for GetPrinterList().
+ * @return The PrinterType value for this name.
+ */
+ static PrinterType GetPrinterType(const nsACString& aName);
+};
+
+#endif /* nsPSPrinters_h___ */
diff --git a/widget/gtk/nsPaperPS.cpp b/widget/gtk/nsPaperPS.cpp
new file mode 100644
index 000000000..7d583efac
--- /dev/null
+++ b/widget/gtk/nsPaperPS.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ex: set tabstop=8 softtabstop=4 shiftwidth=4 expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsPaperPS.h"
+#include "plstr.h"
+#include "nsCoord.h"
+#include "nsMemory.h"
+
+using namespace mozilla;
+
+const nsPaperSizePS_ nsPaperSizePS::mList[] =
+{
+#define SIZE_MM(x) (x)
+#define SIZE_INCH(x) ((x) * MM_PER_INCH_FLOAT)
+ { "A5", SIZE_MM(148), SIZE_MM(210), true },
+ { "A4", SIZE_MM(210), SIZE_MM(297), true },
+ { "A3", SIZE_MM(297), SIZE_MM(420), true },
+ { "Letter", SIZE_INCH(8.5), SIZE_INCH(11), false },
+ { "Legal", SIZE_INCH(8.5), SIZE_INCH(14), false },
+ { "Tabloid", SIZE_INCH(11), SIZE_INCH(17), false },
+ { "Executive", SIZE_INCH(7.5), SIZE_INCH(10), false },
+#undef SIZE_INCH
+#undef SIZE_MM
+};
+
+const unsigned int nsPaperSizePS::mCount = ArrayLength(mList);
+
+bool
+nsPaperSizePS::Find(const char *aName)
+{
+ for (int i = mCount; i--; ) {
+ if (!PL_strcasecmp(aName, mList[i].name)) {
+ mCurrent = i;
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/widget/gtk/nsPaperPS.h b/widget/gtk/nsPaperPS.h
new file mode 100644
index 000000000..1acc1780c
--- /dev/null
+++ b/widget/gtk/nsPaperPS.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ex: set tabstop=8 softtabstop=4 shiftwidth=4 expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 _PAPERPS_H_
+#define _PAPERPS_H_
+
+#include "nsDebug.h"
+
+struct nsPaperSizePS_ {
+ const char *name;
+ float width_mm;
+ float height_mm;
+ bool isMetric; // Present to the user in metric, if possible
+};
+
+class nsPaperSizePS {
+ public:
+ /** ---------------------------------------------------
+ * Constructor
+ */
+ nsPaperSizePS() { mCurrent = 0; }
+
+ /** ---------------------------------------------------
+ * @return true if the cursor points past the last item.
+ */
+ bool AtEnd() { return mCurrent >= mCount; }
+
+ /** ---------------------------------------------------
+ * Position the cursor at the beginning of the paper size list.
+ * @return VOID
+ */
+ void First() { mCurrent = 0; }
+
+ /** ---------------------------------------------------
+ * Advance the cursor to the next item.
+ * @return VOID
+ */
+ void Next() {
+ NS_ASSERTION(!AtEnd(), "Invalid current item");
+ mCurrent++;
+ }
+
+ /** ---------------------------------------------------
+ * Point the cursor to the entry with the given paper name.
+ * @return true if pointing to a valid entry.
+ */
+ bool Find(const char *aName);
+
+ /** ---------------------------------------------------
+ * @return a pointer to the name of the current paper size
+ */
+ const char *Name() {
+ NS_PRECONDITION(!AtEnd(), "Invalid current item");
+ return mList[mCurrent].name;
+ }
+
+ /** ---------------------------------------------------
+ * @return the width of the page in millimeters
+ */
+ float Width_mm() {
+ NS_PRECONDITION(!AtEnd(), "Invalid current item");
+ return mList[mCurrent].width_mm;
+ }
+
+ /** ---------------------------------------------------
+ * @return the height of the page in millimeters
+ */
+ float Height_mm() {
+ NS_PRECONDITION(!AtEnd(), "Invalid current item");
+ return mList[mCurrent].height_mm;
+ }
+
+ /** ---------------------------------------------------
+ * @return true if the paper should be presented to
+ * the user in metric units.
+ */
+ bool IsMetric() {
+ NS_PRECONDITION(!AtEnd(), "Invalid current item");
+ return mList[mCurrent].isMetric;
+ }
+
+ private:
+ unsigned int mCurrent;
+ static const nsPaperSizePS_ mList[];
+ static const unsigned int mCount;
+};
+
+#endif
+
diff --git a/widget/gtk/nsPrintDialogGTK.cpp b/widget/gtk/nsPrintDialogGTK.cpp
new file mode 100644
index 000000000..77fa2bb69
--- /dev/null
+++ b/widget/gtk/nsPrintDialogGTK.cpp
@@ -0,0 +1,609 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include <gtk/gtk.h>
+#include <gtk/gtkunixprint.h>
+#include <stdlib.h>
+
+#include "mozilla/ArrayUtils.h"
+
+#include "mozcontainer.h"
+#include "nsIPrintSettings.h"
+#include "nsIWidget.h"
+#include "nsPrintDialogGTK.h"
+#include "nsPrintSettingsGTK.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIFile.h"
+#include "nsIStringBundle.h"
+#include "nsIPrintSettingsService.h"
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShell.h"
+#include "WidgetUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+static const char header_footer_tags[][4] = {"", "&T", "&U", "&D", "&P", "&PT"};
+
+#define CUSTOM_VALUE_INDEX gint(ArrayLength(header_footer_tags))
+
+static GtkWindow *
+get_gtk_window_for_nsiwidget(nsIWidget *widget)
+{
+ return GTK_WINDOW(widget->GetNativeData(NS_NATIVE_SHELLWIDGET));
+}
+
+static void
+ShowCustomDialog(GtkComboBox *changed_box, gpointer user_data)
+{
+ if (gtk_combo_box_get_active(changed_box) != CUSTOM_VALUE_INDEX) {
+ g_object_set_data(G_OBJECT(changed_box), "previous-active", GINT_TO_POINTER(gtk_combo_box_get_active(changed_box)));
+ return;
+ }
+
+ GtkWindow* printDialog = GTK_WINDOW(user_data);
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+
+ nsCOMPtr<nsIStringBundle> printBundle;
+ bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties", getter_AddRefs(printBundle));
+ nsXPIDLString intlString;
+
+ printBundle->GetStringFromName(u"headerFooterCustom", getter_Copies(intlString));
+ GtkWidget* prompt_dialog = gtk_dialog_new_with_buttons(NS_ConvertUTF16toUTF8(intlString).get(), printDialog,
+#if (MOZ_WIDGET_GTK == 2)
+ (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR),
+#else
+ (GtkDialogFlags)(GTK_DIALOG_MODAL),
+#endif
+ GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+ nullptr);
+ gtk_dialog_set_default_response(GTK_DIALOG(prompt_dialog), GTK_RESPONSE_ACCEPT);
+ gtk_dialog_set_alternative_button_order(GTK_DIALOG(prompt_dialog),
+ GTK_RESPONSE_ACCEPT,
+ GTK_RESPONSE_REJECT,
+ -1);
+
+ printBundle->GetStringFromName(u"customHeaderFooterPrompt", getter_Copies(intlString));
+ GtkWidget* custom_label = gtk_label_new(NS_ConvertUTF16toUTF8(intlString).get());
+ GtkWidget* custom_entry = gtk_entry_new();
+ GtkWidget* question_icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
+
+ // To be convenient, prefill the textbox with the existing value, if any, and select it all so they can easily
+ // both edit it and type in a new one.
+ const char* current_text = (const char*) g_object_get_data(G_OBJECT(changed_box), "custom-text");
+ if (current_text) {
+ gtk_entry_set_text(GTK_ENTRY(custom_entry), current_text);
+ gtk_editable_select_region(GTK_EDITABLE(custom_entry), 0, -1);
+ }
+ gtk_entry_set_activates_default(GTK_ENTRY(custom_entry), TRUE);
+
+ GtkWidget* custom_vbox = gtk_vbox_new(TRUE, 2);
+ gtk_box_pack_start(GTK_BOX(custom_vbox), custom_label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(custom_vbox), custom_entry, FALSE, FALSE, 5); // Make entry 5px underneath label
+ GtkWidget* custom_hbox = gtk_hbox_new(FALSE, 2);
+ gtk_box_pack_start(GTK_BOX(custom_hbox), question_icon, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(custom_hbox), custom_vbox, FALSE, FALSE, 10); // Make question icon 10px away from content
+ gtk_container_set_border_width(GTK_CONTAINER(custom_hbox), 2);
+ gtk_widget_show_all(custom_hbox);
+
+ gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(prompt_dialog))),
+ custom_hbox, FALSE, FALSE, 0);
+ gint diag_response = gtk_dialog_run(GTK_DIALOG(prompt_dialog));
+
+ if (diag_response == GTK_RESPONSE_ACCEPT) {
+ const gchar* response_text = gtk_entry_get_text(GTK_ENTRY(custom_entry));
+ g_object_set_data_full(G_OBJECT(changed_box), "custom-text", strdup(response_text), (GDestroyNotify) free);
+ g_object_set_data(G_OBJECT(changed_box), "previous-active", GINT_TO_POINTER(CUSTOM_VALUE_INDEX));
+ } else {
+ // Go back to the previous index
+ gint previous_active = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(changed_box), "previous-active"));
+ gtk_combo_box_set_active(changed_box, previous_active);
+ }
+
+ gtk_widget_destroy(prompt_dialog);
+}
+
+class nsPrintDialogWidgetGTK {
+ public:
+ nsPrintDialogWidgetGTK(nsPIDOMWindowOuter *aParent,
+ nsIPrintSettings *aPrintSettings);
+ ~nsPrintDialogWidgetGTK() { gtk_widget_destroy(dialog); }
+ NS_ConvertUTF16toUTF8 GetUTF8FromBundle(const char* aKey);
+ gint Run();
+
+ nsresult ImportSettings(nsIPrintSettings *aNSSettings);
+ nsresult ExportSettings(nsIPrintSettings *aNSSettings);
+
+ private:
+ GtkWidget* dialog;
+ GtkWidget* radio_as_laid_out;
+ GtkWidget* radio_selected_frame;
+ GtkWidget* radio_separate_frames;
+ GtkWidget* shrink_to_fit_toggle;
+ GtkWidget* print_bg_colors_toggle;
+ GtkWidget* print_bg_images_toggle;
+ GtkWidget* selection_only_toggle;
+ GtkWidget* header_dropdown[3]; // {left, center, right}
+ GtkWidget* footer_dropdown[3];
+
+ nsCOMPtr<nsIStringBundle> printBundle;
+
+ bool useNativeSelection;
+
+ GtkWidget* ConstructHeaderFooterDropdown(const char16_t *currentString);
+ const char* OptionWidgetToString(GtkWidget *dropdown);
+
+ /* Code to copy between GTK and NS print settings structures.
+ * In the following,
+ * "Import" means to copy from NS to GTK
+ * "Export" means to copy from GTK to NS
+ */
+ void ExportFramePrinting(nsIPrintSettings *aNS, GtkPrintSettings *aSettings);
+ void ExportHeaderFooter(nsIPrintSettings *aNS);
+};
+
+nsPrintDialogWidgetGTK::nsPrintDialogWidgetGTK(nsPIDOMWindowOuter *aParent,
+ nsIPrintSettings *aSettings)
+{
+ nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
+ NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
+ GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
+ NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");
+
+ nsCOMPtr<nsIStringBundleService> bundleSvc = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties", getter_AddRefs(printBundle));
+
+ dialog = gtk_print_unix_dialog_new(GetUTF8FromBundle("printTitleGTK").get(), gtkParent);
+
+ gtk_print_unix_dialog_set_manual_capabilities(GTK_PRINT_UNIX_DIALOG(dialog),
+ GtkPrintCapabilities(
+ GTK_PRINT_CAPABILITY_PAGE_SET
+ | GTK_PRINT_CAPABILITY_COPIES
+ | GTK_PRINT_CAPABILITY_COLLATE
+ | GTK_PRINT_CAPABILITY_REVERSE
+ | GTK_PRINT_CAPABILITY_SCALE
+ | GTK_PRINT_CAPABILITY_GENERATE_PDF
+ | GTK_PRINT_CAPABILITY_GENERATE_PS
+ )
+ );
+
+ // The vast majority of magic numbers in this widget construction are padding. e.g. for
+ // the set_border_width below, 12px matches that of just about every other window.
+ GtkWidget* custom_options_tab = gtk_vbox_new(FALSE, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(custom_options_tab), 12);
+ GtkWidget* tab_label = gtk_label_new(GetUTF8FromBundle("optionsTabLabelGTK").get());
+
+ int16_t frameUIFlag;
+ aSettings->GetHowToEnableFrameUI(&frameUIFlag);
+ radio_as_laid_out = gtk_radio_button_new_with_mnemonic(nullptr, GetUTF8FromBundle("asLaidOut").get());
+ if (frameUIFlag == nsIPrintSettings::kFrameEnableNone)
+ gtk_widget_set_sensitive(radio_as_laid_out, FALSE);
+
+ radio_selected_frame = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(radio_as_laid_out),
+ GetUTF8FromBundle("selectedFrame").get());
+ if (frameUIFlag == nsIPrintSettings::kFrameEnableNone ||
+ frameUIFlag == nsIPrintSettings::kFrameEnableAsIsAndEach)
+ gtk_widget_set_sensitive(radio_selected_frame, FALSE);
+
+ radio_separate_frames = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(radio_as_laid_out),
+ GetUTF8FromBundle("separateFrames").get());
+ if (frameUIFlag == nsIPrintSettings::kFrameEnableNone)
+ gtk_widget_set_sensitive(radio_separate_frames, FALSE);
+
+ // "Print Frames" options label, bold and center-aligned
+ GtkWidget* print_frames_label = gtk_label_new(nullptr);
+ char* pangoMarkup = g_markup_printf_escaped("<b>%s</b>", GetUTF8FromBundle("printFramesTitleGTK").get());
+ gtk_label_set_markup(GTK_LABEL(print_frames_label), pangoMarkup);
+ g_free(pangoMarkup);
+ gtk_misc_set_alignment(GTK_MISC(print_frames_label), 0, 0);
+
+ // Align the radio buttons slightly so they appear to fall under the aforementioned label as per the GNOME HIG
+ GtkWidget* frames_radio_container = gtk_alignment_new(0, 0, 0, 0);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(frames_radio_container), 8, 0, 12, 0);
+
+ // Radio buttons for the print frames options
+ GtkWidget* frames_radio_list = gtk_vbox_new(TRUE, 2);
+ gtk_box_pack_start(GTK_BOX(frames_radio_list), radio_as_laid_out, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(frames_radio_list), radio_selected_frame, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(frames_radio_list), radio_separate_frames, FALSE, FALSE, 0);
+ gtk_container_add(GTK_CONTAINER(frames_radio_container), frames_radio_list);
+
+ // Check buttons for shrink-to-fit and print selection
+ GtkWidget* check_buttons_container = gtk_vbox_new(TRUE, 2);
+ shrink_to_fit_toggle = gtk_check_button_new_with_mnemonic(GetUTF8FromBundle("shrinkToFit").get());
+ gtk_box_pack_start(GTK_BOX(check_buttons_container), shrink_to_fit_toggle, FALSE, FALSE, 0);
+
+ // GTK+2.18 and above allow us to add a "Selection" option to the main settings screen,
+ // rather than adding an option on a custom tab like we must do on older versions.
+ bool canSelectText;
+ aSettings->GetPrintOptions(nsIPrintSettings::kEnableSelectionRB, &canSelectText);
+ if (gtk_major_version > 2 ||
+ (gtk_major_version == 2 && gtk_minor_version >= 18)) {
+ useNativeSelection = true;
+ g_object_set(dialog,
+ "support-selection", TRUE,
+ "has-selection", canSelectText,
+ "embed-page-setup", TRUE,
+ nullptr);
+ } else {
+ useNativeSelection = false;
+ selection_only_toggle = gtk_check_button_new_with_mnemonic(GetUTF8FromBundle("selectionOnly").get());
+ gtk_widget_set_sensitive(selection_only_toggle, canSelectText);
+ gtk_box_pack_start(GTK_BOX(check_buttons_container), selection_only_toggle, FALSE, FALSE, 0);
+ }
+
+ // Check buttons for printing background
+ GtkWidget* appearance_buttons_container = gtk_vbox_new(TRUE, 2);
+ print_bg_colors_toggle = gtk_check_button_new_with_mnemonic(GetUTF8FromBundle("printBGColors").get());
+ print_bg_images_toggle = gtk_check_button_new_with_mnemonic(GetUTF8FromBundle("printBGImages").get());
+ gtk_box_pack_start(GTK_BOX(appearance_buttons_container), print_bg_colors_toggle, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(appearance_buttons_container), print_bg_images_toggle, FALSE, FALSE, 0);
+
+ // "Appearance" options label, bold and center-aligned
+ GtkWidget* appearance_label = gtk_label_new(nullptr);
+ pangoMarkup = g_markup_printf_escaped("<b>%s</b>", GetUTF8FromBundle("printBGOptions").get());
+ gtk_label_set_markup(GTK_LABEL(appearance_label), pangoMarkup);
+ g_free(pangoMarkup);
+ gtk_misc_set_alignment(GTK_MISC(appearance_label), 0, 0);
+
+ GtkWidget* appearance_container = gtk_alignment_new(0, 0, 0, 0);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(appearance_container), 8, 0, 12, 0);
+ gtk_container_add(GTK_CONTAINER(appearance_container), appearance_buttons_container);
+
+ GtkWidget* appearance_vertical_squasher = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(appearance_vertical_squasher), appearance_label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(appearance_vertical_squasher), appearance_container, FALSE, FALSE, 0);
+
+ // "Header & Footer" options label, bold and center-aligned
+ GtkWidget* header_footer_label = gtk_label_new(nullptr);
+ pangoMarkup = g_markup_printf_escaped("<b>%s</b>", GetUTF8FromBundle("headerFooter").get());
+ gtk_label_set_markup(GTK_LABEL(header_footer_label), pangoMarkup);
+ g_free(pangoMarkup);
+ gtk_misc_set_alignment(GTK_MISC(header_footer_label), 0, 0);
+
+ GtkWidget* header_footer_container = gtk_alignment_new(0, 0, 0, 0);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(header_footer_container), 8, 0, 12, 0);
+
+
+ // --- Table for making the header and footer options ---
+ GtkWidget* header_footer_table = gtk_table_new(3, 3, FALSE); // 3x3 table
+ nsXPIDLString header_footer_str[3];
+
+ aSettings->GetHeaderStrLeft(getter_Copies(header_footer_str[0]));
+ aSettings->GetHeaderStrCenter(getter_Copies(header_footer_str[1]));
+ aSettings->GetHeaderStrRight(getter_Copies(header_footer_str[2]));
+
+ for (unsigned int i = 0; i < ArrayLength(header_dropdown); i++) {
+ header_dropdown[i] = ConstructHeaderFooterDropdown(header_footer_str[i].get());
+ // Those 4 magic numbers in the middle provide the position in the table.
+ // The last two numbers mean 2 px padding on every side.
+ gtk_table_attach(GTK_TABLE(header_footer_table), header_dropdown[i], i, (i + 1),
+ 0, 1, (GtkAttachOptions) 0, (GtkAttachOptions) 0, 2, 2);
+ }
+
+ const char labelKeys[][7] = {"left", "center", "right"};
+ for (unsigned int i = 0; i < ArrayLength(labelKeys); i++) {
+ gtk_table_attach(GTK_TABLE(header_footer_table),
+ gtk_label_new(GetUTF8FromBundle(labelKeys[i]).get()),
+ i, (i + 1), 1, 2, (GtkAttachOptions) 0, (GtkAttachOptions) 0, 2, 2);
+ }
+
+ aSettings->GetFooterStrLeft(getter_Copies(header_footer_str[0]));
+ aSettings->GetFooterStrCenter(getter_Copies(header_footer_str[1]));
+ aSettings->GetFooterStrRight(getter_Copies(header_footer_str[2]));
+
+ for (unsigned int i = 0; i < ArrayLength(footer_dropdown); i++) {
+ footer_dropdown[i] = ConstructHeaderFooterDropdown(header_footer_str[i].get());
+ gtk_table_attach(GTK_TABLE(header_footer_table), footer_dropdown[i], i, (i + 1),
+ 2, 3, (GtkAttachOptions) 0, (GtkAttachOptions) 0, 2, 2);
+ }
+ // ---
+
+ gtk_container_add(GTK_CONTAINER(header_footer_container), header_footer_table);
+
+ GtkWidget* header_footer_vertical_squasher = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(header_footer_vertical_squasher), header_footer_label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(header_footer_vertical_squasher), header_footer_container, FALSE, FALSE, 0);
+
+ // Construction of everything
+ gtk_box_pack_start(GTK_BOX(custom_options_tab), print_frames_label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(custom_options_tab), frames_radio_container, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(custom_options_tab), check_buttons_container, FALSE, FALSE, 10); // 10px padding
+ gtk_box_pack_start(GTK_BOX(custom_options_tab), appearance_vertical_squasher, FALSE, FALSE, 10);
+ gtk_box_pack_start(GTK_BOX(custom_options_tab), header_footer_vertical_squasher, FALSE, FALSE, 0);
+
+ gtk_print_unix_dialog_add_custom_tab(GTK_PRINT_UNIX_DIALOG(dialog), custom_options_tab, tab_label);
+ gtk_widget_show_all(custom_options_tab);
+}
+
+NS_ConvertUTF16toUTF8
+nsPrintDialogWidgetGTK::GetUTF8FromBundle(const char *aKey)
+{
+ nsXPIDLString intlString;
+ printBundle->GetStringFromName(NS_ConvertUTF8toUTF16(aKey).get(), getter_Copies(intlString));
+ return NS_ConvertUTF16toUTF8(intlString); // Return the actual object so we don't lose reference
+}
+
+const char*
+nsPrintDialogWidgetGTK::OptionWidgetToString(GtkWidget *dropdown)
+{
+ gint index = gtk_combo_box_get_active(GTK_COMBO_BOX(dropdown));
+
+ NS_ASSERTION(index <= CUSTOM_VALUE_INDEX, "Index of dropdown is higher than expected!");
+
+ if (index == CUSTOM_VALUE_INDEX)
+ return (const char*) g_object_get_data(G_OBJECT(dropdown), "custom-text");
+ else
+ return header_footer_tags[index];
+}
+
+gint
+nsPrintDialogWidgetGTK::Run()
+{
+ const gint response = gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_hide(dialog);
+ return response;
+}
+
+void
+nsPrintDialogWidgetGTK::ExportFramePrinting(nsIPrintSettings *aNS, GtkPrintSettings *aSettings)
+{
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio_as_laid_out)))
+ aNS->SetPrintFrameType(nsIPrintSettings::kFramesAsIs);
+ else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio_selected_frame)))
+ aNS->SetPrintFrameType(nsIPrintSettings::kSelectedFrame);
+ else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio_separate_frames)))
+ aNS->SetPrintFrameType(nsIPrintSettings::kEachFrameSep);
+ else
+ aNS->SetPrintFrameType(nsIPrintSettings::kNoFrames);
+}
+
+void
+nsPrintDialogWidgetGTK::ExportHeaderFooter(nsIPrintSettings *aNS)
+{
+ const char* header_footer_str;
+ header_footer_str = OptionWidgetToString(header_dropdown[0]);
+ aNS->SetHeaderStrLeft(NS_ConvertUTF8toUTF16(header_footer_str).get());
+
+ header_footer_str = OptionWidgetToString(header_dropdown[1]);
+ aNS->SetHeaderStrCenter(NS_ConvertUTF8toUTF16(header_footer_str).get());
+
+ header_footer_str = OptionWidgetToString(header_dropdown[2]);
+ aNS->SetHeaderStrRight(NS_ConvertUTF8toUTF16(header_footer_str).get());
+
+ header_footer_str = OptionWidgetToString(footer_dropdown[0]);
+ aNS->SetFooterStrLeft(NS_ConvertUTF8toUTF16(header_footer_str).get());
+
+ header_footer_str = OptionWidgetToString(footer_dropdown[1]);
+ aNS->SetFooterStrCenter(NS_ConvertUTF8toUTF16(header_footer_str).get());
+
+ header_footer_str = OptionWidgetToString(footer_dropdown[2]);
+ aNS->SetFooterStrRight(NS_ConvertUTF8toUTF16(header_footer_str).get());
+}
+
+nsresult
+nsPrintDialogWidgetGTK::ImportSettings(nsIPrintSettings *aNSSettings)
+{
+ NS_PRECONDITION(aNSSettings, "aSettings must not be null");
+ NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
+ if (!aNSSettingsGTK)
+ return NS_ERROR_FAILURE;
+
+ GtkPrintSettings* settings = aNSSettingsGTK->GetGtkPrintSettings();
+ GtkPageSetup* setup = aNSSettingsGTK->GetGtkPageSetup();
+
+ bool geckoBool;
+ aNSSettings->GetShrinkToFit(&geckoBool);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(shrink_to_fit_toggle), geckoBool);
+
+ aNSSettings->GetPrintBGColors(&geckoBool);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_colors_toggle), geckoBool);
+
+ aNSSettings->GetPrintBGImages(&geckoBool);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_images_toggle), geckoBool);
+
+ gtk_print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(dialog), settings);
+ gtk_print_unix_dialog_set_page_setup(GTK_PRINT_UNIX_DIALOG(dialog), setup);
+
+ return NS_OK;
+}
+
+nsresult
+nsPrintDialogWidgetGTK::ExportSettings(nsIPrintSettings *aNSSettings)
+{
+ NS_PRECONDITION(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));
+ GtkPageSetup* setup = gtk_print_unix_dialog_get_page_setup(GTK_PRINT_UNIX_DIALOG(dialog));
+ GtkPrinter* printer = gtk_print_unix_dialog_get_selected_printer(GTK_PRINT_UNIX_DIALOG(dialog));
+ if (settings && setup && printer) {
+ ExportFramePrinting(aNSSettings, settings);
+ ExportHeaderFooter(aNSSettings);
+
+ aNSSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatNative);
+
+ // Print-to-file is true by default. This must be turned off or else printing won't occur!
+ // (We manually copy the spool file when this flag is set, because we love our embedders)
+ // Even if it is print-to-file in GTK's case, GTK does The Right Thing when we send the job.
+ aNSSettings->SetPrintToFile(false);
+
+ aNSSettings->SetShrinkToFit(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(shrink_to_fit_toggle)));
+
+ aNSSettings->SetPrintBGColors(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(print_bg_colors_toggle)));
+ aNSSettings->SetPrintBGImages(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(print_bg_images_toggle)));
+
+ // Try to save native settings in the session object
+ nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
+ if (aNSSettingsGTK) {
+ aNSSettingsGTK->SetGtkPrintSettings(settings);
+ aNSSettingsGTK->SetGtkPageSetup(setup);
+ aNSSettingsGTK->SetGtkPrinter(printer);
+ bool printSelectionOnly;
+ if (useNativeSelection) {
+ _GtkPrintPages pageSetting = (_GtkPrintPages)gtk_print_settings_get_print_pages(settings);
+ printSelectionOnly = (pageSetting == _GTK_PRINT_PAGES_SELECTION);
+ } else {
+ printSelectionOnly = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(selection_only_toggle));
+ }
+ aNSSettingsGTK->SetForcePrintSelectionOnly(printSelectionOnly);
+ }
+ }
+
+ if (settings)
+ g_object_unref(settings);
+ return NS_OK;
+}
+
+GtkWidget*
+nsPrintDialogWidgetGTK::ConstructHeaderFooterDropdown(const char16_t *currentString)
+{
+#if (MOZ_WIDGET_GTK == 2)
+ GtkWidget* dropdown = gtk_combo_box_new_text();
+#else
+ GtkWidget* dropdown = gtk_combo_box_text_new();
+#endif
+ const char hf_options[][22] = {"headerFooterBlank", "headerFooterTitle",
+ "headerFooterURL", "headerFooterDate",
+ "headerFooterPage", "headerFooterPageTotal",
+ "headerFooterCustom"};
+
+ for (unsigned int i = 0; i < ArrayLength(hf_options); i++) {
+#if (MOZ_WIDGET_GTK == 2)
+ gtk_combo_box_append_text(GTK_COMBO_BOX(dropdown), GetUTF8FromBundle(hf_options[i]).get());
+#else
+ gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(dropdown), nullptr,
+ GetUTF8FromBundle(hf_options[i]).get());
+#endif
+ }
+
+ bool shouldBeCustom = true;
+ NS_ConvertUTF16toUTF8 currentStringUTF8(currentString);
+
+ for (unsigned int i = 0; i < ArrayLength(header_footer_tags); i++) {
+ if (!strcmp(currentStringUTF8.get(), header_footer_tags[i])) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), i);
+ g_object_set_data(G_OBJECT(dropdown), "previous-active", GINT_TO_POINTER(i));
+ shouldBeCustom = false;
+ break;
+ }
+ }
+
+ if (shouldBeCustom) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), CUSTOM_VALUE_INDEX);
+ g_object_set_data(G_OBJECT(dropdown), "previous-active", GINT_TO_POINTER(CUSTOM_VALUE_INDEX));
+ char* custom_string = strdup(currentStringUTF8.get());
+ g_object_set_data_full(G_OBJECT(dropdown), "custom-text", custom_string, (GDestroyNotify) free);
+ }
+
+ g_signal_connect(dropdown, "changed", (GCallback) ShowCustomDialog, dialog);
+ return dropdown;
+}
+
+NS_IMPL_ISUPPORTS(nsPrintDialogServiceGTK, nsIPrintDialogService)
+
+nsPrintDialogServiceGTK::nsPrintDialogServiceGTK()
+{
+}
+
+nsPrintDialogServiceGTK::~nsPrintDialogServiceGTK()
+{
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceGTK::Init()
+{
+ return NS_OK;
+}
+
+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");
+
+ nsPrintDialogWidgetGTK printDialog(aParent, aSettings);
+ nsresult rv = printDialog.ImportSettings(aSettings);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const gint response = printDialog.Run();
+
+ // Handle the result
+ switch (response) {
+ case GTK_RESPONSE_OK: // Proceed
+ rv = printDialog.ExportSettings(aSettings);
+ break;
+
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_CLOSE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ case GTK_RESPONSE_NONE:
+ rv = NS_ERROR_ABORT;
+ break;
+
+ case GTK_RESPONSE_APPLY: // Print preview
+ default:
+ NS_WARNING("Unexpected response");
+ rv = NS_ERROR_ABORT;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceGTK::ShowPageSetup(nsPIDOMWindowOuter *aParent,
+ nsIPrintSettings *aNSSettings)
+{
+ NS_PRECONDITION(aParent, "aParent must not be null");
+ NS_PRECONDITION(aNSSettings, "aSettings must not be null");
+ NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
+ NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
+ GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
+ NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");
+
+ nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
+ if (!aNSSettingsGTK)
+ return NS_ERROR_FAILURE;
+
+ // We need to init the prefs here because aNSSettings in its current form is a dummy in both uses of the word
+ nsCOMPtr<nsIPrintSettingsService> psService = do_GetService("@mozilla.org/gfx/printsettings-service;1");
+ if (psService) {
+ nsXPIDLString printName;
+ aNSSettings->GetPrinterName(getter_Copies(printName));
+ if (!printName) {
+ psService->GetDefaultPrinterName(getter_Copies(printName));
+ aNSSettings->SetPrinterName(printName.get());
+ }
+ psService->InitPrintSettingsFromPrefs(aNSSettings, true, nsIPrintSettings::kInitSaveAll);
+ }
+
+ GtkPrintSettings* gtkSettings = aNSSettingsGTK->GetGtkPrintSettings();
+ GtkPageSetup* oldPageSetup = aNSSettingsGTK->GetGtkPageSetup();
+
+ GtkPageSetup* newPageSetup = gtk_print_run_page_setup_dialog(gtkParent, oldPageSetup, gtkSettings);
+
+ aNSSettingsGTK->SetGtkPageSetup(newPageSetup);
+
+ // Now newPageSetup has a refcount of 2 (SetGtkPageSetup will addref), put it to 1 so if
+ // this gets replaced we don't leak.
+ g_object_unref(newPageSetup);
+
+ if (psService)
+ psService->SavePrintSettingsToPrefs(aNSSettings, true, nsIPrintSettings::kInitSaveAll);
+
+ return NS_OK;
+}
diff --git a/widget/gtk/nsPrintDialogGTK.h b/widget/gtk/nsPrintDialogGTK.h
new file mode 100644
index 000000000..c60fa6a2b
--- /dev/null
+++ b/widget/gtk/nsPrintDialogGTK.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 nsPrintDialog_h__
+#define nsPrintDialog_h__
+
+#include "nsIPrintDialogService.h"
+
+class nsIPrintSettings;
+
+// Copy the print pages enum here because not all versions
+// have SELECTION, which we will use
+typedef enum
+{
+ _GTK_PRINT_PAGES_ALL,
+ _GTK_PRINT_PAGES_CURRENT,
+ _GTK_PRINT_PAGES_RANGES,
+ _GTK_PRINT_PAGES_SELECTION
+} _GtkPrintPages;
+
+class nsPrintDialogServiceGTK : public nsIPrintDialogService
+{
+ virtual ~nsPrintDialogServiceGTK();
+
+public:
+ nsPrintDialogServiceGTK();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init() override;
+ NS_IMETHOD Show(nsPIDOMWindowOuter *aParent, nsIPrintSettings *aSettings,
+ nsIWebBrowserPrint *aWebBrowserPrint) override;
+ NS_IMETHOD ShowPageSetup(nsPIDOMWindowOuter *aParent,
+ nsIPrintSettings *aSettings) override;
+};
+
+#endif
diff --git a/widget/gtk/nsPrintOptionsGTK.cpp b/widget/gtk/nsPrintOptionsGTK.cpp
new file mode 100644
index 000000000..ca86a1f89
--- /dev/null
+++ b/widget/gtk/nsPrintOptionsGTK.cpp
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "nsPrintOptionsGTK.h"
+#include "nsPrintSettingsGTK.h"
+
+using namespace mozilla::embedding;
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintOptionsWin.h
+ * @update 6/21/00 dwc
+ */
+nsPrintOptionsGTK::nsPrintOptionsGTK()
+{
+
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintOptionsImpl.h
+ * @update 6/21/00 dwc
+ */
+nsPrintOptionsGTK::~nsPrintOptionsGTK()
+{
+}
+
+static void
+serialize_gtk_printsettings_to_printdata(const gchar *key,
+ const gchar *value,
+ gpointer aData)
+{
+ PrintData* data = (PrintData*)aData;
+ CStringKeyValue pair;
+ pair.key() = key;
+ pair.value() = value;
+ data->GTKPrintSettings().AppendElement(pair);
+}
+
+NS_IMETHODIMP
+nsPrintOptionsGTK::SerializeToPrintData(nsIPrintSettings* aSettings,
+ nsIWebBrowserPrint* aWBP,
+ PrintData* data)
+{
+ nsresult rv = nsPrintOptions::SerializeToPrintData(aSettings, aWBP, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsPrintSettingsGTK> settingsGTK(do_QueryInterface(aSettings));
+ NS_ENSURE_STATE(settingsGTK);
+
+ GtkPrintSettings* gtkPrintSettings = settingsGTK->GetGtkPrintSettings();
+ NS_ENSURE_STATE(gtkPrintSettings);
+
+ gtk_print_settings_foreach(
+ gtkPrintSettings,
+ serialize_gtk_printsettings_to_printdata,
+ data);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintOptionsGTK::DeserializeToPrintSettings(const PrintData& data,
+ nsIPrintSettings* settings)
+{
+ nsCOMPtr<nsPrintSettingsGTK> settingsGTK(do_QueryInterface(settings));
+ NS_ENSURE_STATE(settingsGTK);
+
+ nsresult rv = nsPrintOptions::DeserializeToPrintSettings(data, settings);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Instead of re-using the GtkPrintSettings that nsIPrintSettings is
+ // wrapping, we'll create a new one to deserialize to and replace it
+ // within nsIPrintSettings.
+ GtkPrintSettings* newGtkPrintSettings = gtk_print_settings_new();
+
+ for (uint32_t i = 0; i < data.GTKPrintSettings().Length(); ++i) {
+ CStringKeyValue pair = data.GTKPrintSettings()[i];
+ gtk_print_settings_set(newGtkPrintSettings,
+ pair.key().get(),
+ pair.value().get());
+ }
+
+ settingsGTK->SetGtkPrintSettings(newGtkPrintSettings);
+
+ // nsPrintSettingsGTK is holding a reference to newGtkPrintSettings
+ g_object_unref(newGtkPrintSettings);
+ newGtkPrintSettings = nullptr;
+ return NS_OK;
+}
+
+nsresult nsPrintOptionsGTK::_CreatePrintSettings(nsIPrintSettings **_retval)
+{
+ *_retval = nullptr;
+ nsPrintSettingsGTK* printSettings = new nsPrintSettingsGTK(); // does not initially ref count
+ NS_ENSURE_TRUE(printSettings, NS_ERROR_OUT_OF_MEMORY);
+
+ NS_ADDREF(*_retval = printSettings); // ref count
+
+ return NS_OK;
+}
+
diff --git a/widget/gtk/nsPrintOptionsGTK.h b/widget/gtk/nsPrintOptionsGTK.h
new file mode 100644
index 000000000..d558bb800
--- /dev/null
+++ b/widget/gtk/nsPrintOptionsGTK.h
@@ -0,0 +1,40 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 nsPrintOptionsGTK_h__
+#define nsPrintOptionsGTK_h__
+
+#include "nsPrintOptionsImpl.h"
+
+namespace mozilla
+{
+namespace embedding
+{
+ class PrintData;
+} // namespace embedding
+} // namespace mozilla
+
+//*****************************************************************************
+//*** nsPrintOptions
+//*****************************************************************************
+class nsPrintOptionsGTK : public nsPrintOptions
+{
+public:
+ nsPrintOptionsGTK();
+ virtual ~nsPrintOptionsGTK();
+
+ NS_IMETHODIMP SerializeToPrintData(nsIPrintSettings* aSettings,
+ nsIWebBrowserPrint* aWBP,
+ mozilla::embedding::PrintData* data);
+ NS_IMETHODIMP DeserializeToPrintSettings(const mozilla::embedding::PrintData& data,
+ nsIPrintSettings* settings);
+
+ virtual nsresult _CreatePrintSettings(nsIPrintSettings **_retval);
+};
+
+
+
+#endif /* nsPrintOptions_h__ */
diff --git a/widget/gtk/nsPrintSettingsGTK.cpp b/widget/gtk/nsPrintSettingsGTK.cpp
new file mode 100644
index 000000000..a8fd60bd1
--- /dev/null
+++ b/widget/gtk/nsPrintSettingsGTK.cpp
@@ -0,0 +1,813 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "nsPrintSettingsGTK.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include <stdlib.h>
+#include <algorithm>
+
+static
+gboolean ref_printer(GtkPrinter *aPrinter, gpointer aData)
+{
+ ((nsPrintSettingsGTK*) aData)->SetGtkPrinter(aPrinter);
+ return TRUE;
+}
+
+static
+gboolean printer_enumerator(GtkPrinter *aPrinter, gpointer aData)
+{
+ if (gtk_printer_is_default(aPrinter))
+ return ref_printer(aPrinter, aData);
+
+ return FALSE; // Keep 'em coming...
+}
+
+static
+GtkPaperSize* moz_gtk_paper_size_copy_to_new_custom(GtkPaperSize* oldPaperSize)
+{
+ // We make a "custom-ified" copy of the paper size so it can be changed later.
+ return gtk_paper_size_new_custom(gtk_paper_size_get_name(oldPaperSize),
+ gtk_paper_size_get_display_name(oldPaperSize),
+ gtk_paper_size_get_width(oldPaperSize, GTK_UNIT_INCH),
+ gtk_paper_size_get_height(oldPaperSize, GTK_UNIT_INCH),
+ GTK_UNIT_INCH);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsPrintSettingsGTK,
+ nsPrintSettings,
+ nsPrintSettingsGTK)
+
+/** ---------------------------------------------------
+ */
+nsPrintSettingsGTK::nsPrintSettingsGTK() :
+ mPageSetup(nullptr),
+ mPrintSettings(nullptr),
+ mGTKPrinter(nullptr),
+ mPrintSelectionOnly(false)
+{
+ // The aim here is to set up the objects enough that silent printing works well.
+ // These will be replaced anyway if the print dialog is used.
+ mPrintSettings = gtk_print_settings_new();
+ GtkPageSetup* pageSetup = gtk_page_setup_new();
+ SetGtkPageSetup(pageSetup);
+ g_object_unref(pageSetup);
+
+ SetOutputFormat(nsIPrintSettings::kOutputFormatNative);
+}
+
+/** ---------------------------------------------------
+ */
+nsPrintSettingsGTK::~nsPrintSettingsGTK()
+{
+ if (mPageSetup) {
+ g_object_unref(mPageSetup);
+ mPageSetup = nullptr;
+ }
+ if (mPrintSettings) {
+ g_object_unref(mPrintSettings);
+ mPrintSettings = nullptr;
+ }
+ if (mGTKPrinter) {
+ g_object_unref(mGTKPrinter);
+ mGTKPrinter = nullptr;
+ }
+}
+
+/** ---------------------------------------------------
+ */
+nsPrintSettingsGTK::nsPrintSettingsGTK(const nsPrintSettingsGTK& aPS) :
+ mPageSetup(nullptr),
+ mPrintSettings(nullptr),
+ mGTKPrinter(nullptr),
+ mPrintSelectionOnly(false)
+{
+ *this = aPS;
+}
+
+/** ---------------------------------------------------
+ */
+nsPrintSettingsGTK& nsPrintSettingsGTK::operator=(const nsPrintSettingsGTK& rhs)
+{
+ if (this == &rhs) {
+ return *this;
+ }
+
+ nsPrintSettings::operator=(rhs);
+
+ if (mPageSetup)
+ g_object_unref(mPageSetup);
+ mPageSetup = gtk_page_setup_copy(rhs.mPageSetup);
+ // NOTE: No need to re-initialize mUnwriteableMargin here (even
+ // though mPageSetup is changing). It'll be copied correctly by
+ // nsPrintSettings::operator=.
+
+ if (mPrintSettings)
+ g_object_unref(mPrintSettings);
+ mPrintSettings = gtk_print_settings_copy(rhs.mPrintSettings);
+
+ if (mGTKPrinter)
+ g_object_unref(mGTKPrinter);
+ mGTKPrinter = (GtkPrinter*) g_object_ref(rhs.mGTKPrinter);
+
+ mPrintSelectionOnly = rhs.mPrintSelectionOnly;
+
+ return *this;
+}
+
+/** -------------------------------------------
+ */
+nsresult nsPrintSettingsGTK::_Clone(nsIPrintSettings **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ nsPrintSettingsGTK *newSettings = new nsPrintSettingsGTK(*this);
+ if (!newSettings)
+ return NS_ERROR_FAILURE;
+ *_retval = newSettings;
+ NS_ADDREF(*_retval);
+ return NS_OK;
+}
+
+
+/** -------------------------------------------
+ */
+NS_IMETHODIMP
+nsPrintSettingsGTK::_Assign(nsIPrintSettings *aPS)
+{
+ nsPrintSettingsGTK *printSettingsGTK = static_cast<nsPrintSettingsGTK*>(aPS);
+ if (!printSettingsGTK)
+ return NS_ERROR_UNEXPECTED;
+ *this = *printSettingsGTK;
+ return NS_OK;
+}
+
+/** ---------------------------------------------------
+ */
+void
+nsPrintSettingsGTK::SetGtkPageSetup(GtkPageSetup *aPageSetup)
+{
+ if (mPageSetup)
+ g_object_unref(mPageSetup);
+
+ mPageSetup = (GtkPageSetup*) g_object_ref(aPageSetup);
+ InitUnwriteableMargin();
+
+ // If the paper size is not custom, then we make a custom copy of the
+ // GtkPaperSize, so it can be mutable. If a GtkPaperSize wasn't made as
+ // custom, its properties are immutable.
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(aPageSetup);
+ if (!gtk_paper_size_is_custom(paperSize)) {
+ GtkPaperSize* customPaperSize =
+ moz_gtk_paper_size_copy_to_new_custom(paperSize);
+ gtk_page_setup_set_paper_size(mPageSetup, customPaperSize);
+ gtk_paper_size_free(customPaperSize);
+ }
+ SaveNewPageSize();
+}
+
+/** ---------------------------------------------------
+ */
+void
+nsPrintSettingsGTK::SetGtkPrintSettings(GtkPrintSettings *aPrintSettings)
+{
+ if (mPrintSettings)
+ g_object_unref(mPrintSettings);
+
+ mPrintSettings = (GtkPrintSettings*) g_object_ref(aPrintSettings);
+
+ GtkPaperSize* paperSize = gtk_print_settings_get_paper_size(aPrintSettings);
+ if (paperSize) {
+ GtkPaperSize* customPaperSize =
+ moz_gtk_paper_size_copy_to_new_custom(paperSize);
+ gtk_paper_size_free(paperSize);
+ gtk_page_setup_set_paper_size(mPageSetup, customPaperSize);
+ gtk_paper_size_free(customPaperSize);
+ } else {
+ // paperSize was null, and so we add the paper size in the GtkPageSetup to
+ // the settings.
+ SaveNewPageSize();
+ }
+}
+
+/** ---------------------------------------------------
+ */
+void
+nsPrintSettingsGTK::SetGtkPrinter(GtkPrinter *aPrinter)
+{
+ if (mGTKPrinter)
+ g_object_unref(mGTKPrinter);
+
+ mGTKPrinter = (GtkPrinter*) g_object_ref(aPrinter);
+}
+
+NS_IMETHODIMP nsPrintSettingsGTK::GetOutputFormat(int16_t *aOutputFormat)
+{
+ NS_ENSURE_ARG_POINTER(aOutputFormat);
+
+ int16_t format;
+ nsresult rv = nsPrintSettings::GetOutputFormat(&format);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (format == nsIPrintSettings::kOutputFormatNative) {
+ const gchar* fmtGTK =
+ gtk_print_settings_get(mPrintSettings,
+ GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT);
+ if (fmtGTK) {
+ if (nsDependentCString(fmtGTK).EqualsIgnoreCase("pdf")) {
+ format = nsIPrintSettings::kOutputFormatPDF;
+ } else {
+ format = nsIPrintSettings::kOutputFormatPS;
+ }
+ } else if (GTK_IS_PRINTER(mGTKPrinter)) {
+ // Prior to gtk 2.24, gtk_printer_accepts_pdf() and
+ // gtk_printer_accepts_ps() always returned true regardless of the
+ // printer's capability.
+ bool shouldTrustGTK =
+ (gtk_major_version > 2 ||
+ (gtk_major_version == 2 && gtk_minor_version >= 24));
+ bool acceptsPDF = shouldTrustGTK && gtk_printer_accepts_pdf(mGTKPrinter);
+
+ format = acceptsPDF ? nsIPrintSettings::kOutputFormatPDF
+ : nsIPrintSettings::kOutputFormatPS;
+ }
+ }
+
+ *aOutputFormat = format;
+ return NS_OK;
+}
+
+/**
+ * Reimplementation of nsPrintSettings functions so that we get the values
+ * from the GTK objects rather than our own variables.
+ */
+
+NS_IMETHODIMP nsPrintSettingsGTK::GetPrintRange(int16_t *aPrintRange)
+{
+ NS_ENSURE_ARG_POINTER(aPrintRange);
+ if (mPrintSelectionOnly) {
+ *aPrintRange = kRangeSelection;
+ return NS_OK;
+ }
+
+ GtkPrintPages gtkRange = gtk_print_settings_get_print_pages(mPrintSettings);
+ if (gtkRange == GTK_PRINT_PAGES_RANGES)
+ *aPrintRange = kRangeSpecifiedPageRange;
+ else
+ *aPrintRange = kRangeAllPages;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettingsGTK::SetPrintRange(int16_t aPrintRange)
+{
+ if (aPrintRange == kRangeSelection) {
+ mPrintSelectionOnly = true;
+ return NS_OK;
+ }
+
+ mPrintSelectionOnly = false;
+ if (aPrintRange == kRangeSpecifiedPageRange)
+ gtk_print_settings_set_print_pages(mPrintSettings, GTK_PRINT_PAGES_RANGES);
+ else
+ gtk_print_settings_set_print_pages(mPrintSettings, GTK_PRINT_PAGES_ALL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetStartPageRange(int32_t *aStartPageRange)
+{
+ gint ctRanges;
+ GtkPageRange* lstRanges = gtk_print_settings_get_page_ranges(mPrintSettings, &ctRanges);
+
+ // Make sure we got a range.
+ if (ctRanges < 1) {
+ *aStartPageRange = 1;
+ } else {
+ // GTK supports multiple page ranges; gecko only supports 1. So find
+ // the lowest start page.
+ int32_t start(lstRanges[0].start);
+ for (gint ii = 1; ii < ctRanges; ii++) {
+ start = std::min(lstRanges[ii].start, start);
+ }
+ *aStartPageRange = start + 1;
+ }
+
+ g_free(lstRanges);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetStartPageRange(int32_t aStartPageRange)
+{
+ int32_t endRange;
+ GetEndPageRange(&endRange);
+
+ GtkPageRange gtkRange;
+ gtkRange.start = aStartPageRange - 1;
+ gtkRange.end = endRange - 1;
+
+ gtk_print_settings_set_page_ranges(mPrintSettings, &gtkRange, 1);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetEndPageRange(int32_t *aEndPageRange)
+{
+ gint ctRanges;
+ GtkPageRange* lstRanges = gtk_print_settings_get_page_ranges(mPrintSettings, &ctRanges);
+
+ if (ctRanges < 1) {
+ *aEndPageRange = 1;
+ } else {
+ int32_t end(lstRanges[0].end);
+ for (gint ii = 1; ii < ctRanges; ii++) {
+ end = std::max(lstRanges[ii].end, end);
+ }
+ *aEndPageRange = end + 1;
+ }
+
+ g_free(lstRanges);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetEndPageRange(int32_t aEndPageRange)
+{
+ int32_t startRange;
+ GetStartPageRange(&startRange);
+
+ GtkPageRange gtkRange;
+ gtkRange.start = startRange - 1;
+ gtkRange.end = aEndPageRange - 1;
+
+ gtk_print_settings_set_page_ranges(mPrintSettings, &gtkRange, 1);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPrintReversed(bool *aPrintReversed)
+{
+ *aPrintReversed = gtk_print_settings_get_reverse(mPrintSettings);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPrintReversed(bool aPrintReversed)
+{
+ gtk_print_settings_set_reverse(mPrintSettings, aPrintReversed);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPrintInColor(bool *aPrintInColor)
+{
+ *aPrintInColor = gtk_print_settings_get_use_color(mPrintSettings);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPrintInColor(bool aPrintInColor)
+{
+ gtk_print_settings_set_use_color(mPrintSettings, aPrintInColor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetOrientation(int32_t *aOrientation)
+{
+ NS_ENSURE_ARG_POINTER(aOrientation);
+
+ GtkPageOrientation gtkOrient = gtk_page_setup_get_orientation(mPageSetup);
+ switch (gtkOrient) {
+ case GTK_PAGE_ORIENTATION_LANDSCAPE:
+ case GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE:
+ *aOrientation = kLandscapeOrientation;
+ break;
+
+ case GTK_PAGE_ORIENTATION_PORTRAIT:
+ case GTK_PAGE_ORIENTATION_REVERSE_PORTRAIT:
+ default:
+ *aOrientation = kPortraitOrientation;
+ }
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetOrientation(int32_t aOrientation)
+{
+ GtkPageOrientation gtkOrient;
+ if (aOrientation == kLandscapeOrientation)
+ gtkOrient = GTK_PAGE_ORIENTATION_LANDSCAPE;
+ else
+ gtkOrient = GTK_PAGE_ORIENTATION_PORTRAIT;
+
+ gtk_print_settings_set_orientation(mPrintSettings, gtkOrient);
+ gtk_page_setup_set_orientation(mPageSetup, gtkOrient);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetToFileName(char16_t * *aToFileName)
+{
+ // Get the gtk output filename
+ const char* gtk_output_uri = gtk_print_settings_get(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_URI);
+ if (!gtk_output_uri) {
+ *aToFileName = ToNewUnicode(mToFileName);
+ return NS_OK;
+ }
+
+ // Convert to an nsIFile
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_GetFileFromURLSpec(nsDependentCString(gtk_output_uri),
+ getter_AddRefs(file));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Extract the path
+ nsAutoString path;
+ rv = file->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aToFileName = ToNewUnicode(path);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetToFileName(const char16_t * aToFileName)
+{
+ if (aToFileName[0] == 0) {
+ mToFileName.SetLength(0);
+ gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_URI,
+ nullptr);
+ return NS_OK;
+ }
+
+ if (StringEndsWith(nsDependentString(aToFileName), NS_LITERAL_STRING(".ps"))) {
+ gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT, "ps");
+ } else {
+ gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT, "pdf");
+ }
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_NewLocalFile(nsDependentString(aToFileName), true,
+ getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Convert the nsIFile to a URL
+ nsAutoCString url;
+ rv = NS_GetURLSpecFromFile(file, url);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_URI, url.get());
+ mToFileName = aToFileName;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPrinterName(char16_t * *aPrinter)
+{
+ const char* gtkPrintName = gtk_print_settings_get_printer(mPrintSettings);
+ if (!gtkPrintName) {
+ if (GTK_IS_PRINTER(mGTKPrinter)) {
+ gtkPrintName = gtk_printer_get_name(mGTKPrinter);
+ } else {
+ // This mimics what nsPrintSettingsImpl does when we try to Get before we Set
+ nsXPIDLString nullPrintName;
+ *aPrinter = ToNewUnicode(nullPrintName);
+ return NS_OK;
+ }
+ }
+ *aPrinter = UTF8ToNewUnicode(nsDependentCString(gtkPrintName));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPrinterName(const char16_t * aPrinter)
+{
+ NS_ConvertUTF16toUTF8 gtkPrinter(aPrinter);
+
+ if (StringBeginsWith(gtkPrinter, NS_LITERAL_CSTRING("CUPS/"))) {
+ // Strip off "CUPS/"; GTK might recognize the rest
+ gtkPrinter.Cut(0, strlen("CUPS/"));
+ }
+
+ // Give mPrintSettings the passed-in printer name if either...
+ // - it has no printer name stored yet
+ // - it has an existing printer name that's different from
+ // the name passed to this function.
+ const char* oldPrinterName = gtk_print_settings_get_printer(mPrintSettings);
+ if (!oldPrinterName || !gtkPrinter.Equals(oldPrinterName)) {
+ mIsInitedFromPrinter = false;
+ mIsInitedFromPrefs = false;
+ gtk_print_settings_set_printer(mPrintSettings, gtkPrinter.get());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetNumCopies(int32_t *aNumCopies)
+{
+ NS_ENSURE_ARG_POINTER(aNumCopies);
+ *aNumCopies = gtk_print_settings_get_n_copies(mPrintSettings);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetNumCopies(int32_t aNumCopies)
+{
+ gtk_print_settings_set_n_copies(mPrintSettings, aNumCopies);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetScaling(double *aScaling)
+{
+ *aScaling = gtk_print_settings_get_scale(mPrintSettings) / 100.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetScaling(double aScaling)
+{
+ gtk_print_settings_set_scale(mPrintSettings, aScaling * 100.0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPaperName(char16_t * *aPaperName)
+{
+ NS_ENSURE_ARG_POINTER(aPaperName);
+ const gchar* name =
+ gtk_paper_size_get_name(gtk_page_setup_get_paper_size(mPageSetup));
+ *aPaperName = ToNewUnicode(NS_ConvertUTF8toUTF16(name));
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPaperName(const char16_t * aPaperName)
+{
+ NS_ConvertUTF16toUTF8 gtkPaperName(aPaperName);
+
+ // Convert these Gecko names to GTK names
+ if (gtkPaperName.EqualsIgnoreCase("letter"))
+ gtkPaperName.AssignLiteral(GTK_PAPER_NAME_LETTER);
+ else if (gtkPaperName.EqualsIgnoreCase("legal"))
+ gtkPaperName.AssignLiteral(GTK_PAPER_NAME_LEGAL);
+
+ GtkPaperSize* oldPaperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ gdouble width = gtk_paper_size_get_width(oldPaperSize, GTK_UNIT_INCH);
+ gdouble height = gtk_paper_size_get_height(oldPaperSize, GTK_UNIT_INCH);
+
+ // Try to get the display name from the name so our paper size fits in the Page Setup dialog.
+ GtkPaperSize* paperSize = gtk_paper_size_new(gtkPaperName.get());
+ GtkPaperSize* customPaperSize =
+ gtk_paper_size_new_custom(gtkPaperName.get(),
+ gtk_paper_size_get_display_name(paperSize),
+ width, height, GTK_UNIT_INCH);
+ gtk_paper_size_free(paperSize);
+
+ gtk_page_setup_set_paper_size(mPageSetup, customPaperSize);
+ gtk_paper_size_free(customPaperSize);
+ SaveNewPageSize();
+ return NS_OK;
+}
+
+GtkUnit
+nsPrintSettingsGTK::GetGTKUnit(int16_t aGeckoUnit)
+{
+ if (aGeckoUnit == kPaperSizeMillimeters)
+ return GTK_UNIT_MM;
+ else
+ return GTK_UNIT_INCH;
+}
+
+void
+nsPrintSettingsGTK::SaveNewPageSize()
+{
+ gtk_print_settings_set_paper_size(mPrintSettings,
+ gtk_page_setup_get_paper_size(mPageSetup));
+}
+
+void
+nsPrintSettingsGTK::InitUnwriteableMargin()
+{
+ mUnwriteableMargin.SizeTo(
+ NS_INCHES_TO_INT_TWIPS(gtk_page_setup_get_top_margin(mPageSetup, GTK_UNIT_INCH)),
+ NS_INCHES_TO_INT_TWIPS(gtk_page_setup_get_right_margin(mPageSetup, GTK_UNIT_INCH)),
+ NS_INCHES_TO_INT_TWIPS(gtk_page_setup_get_bottom_margin(mPageSetup, GTK_UNIT_INCH)),
+ NS_INCHES_TO_INT_TWIPS(gtk_page_setup_get_left_margin(mPageSetup, GTK_UNIT_INCH))
+ );
+}
+
+/**
+ * NOTE: Need a custom set of SetUnwriteableMargin functions, because
+ * whenever we change mUnwriteableMargin, we must pass the change
+ * down to our GTKPageSetup object. (This is needed in order for us
+ * to give the correct default values in nsPrintDialogGTK.)
+ *
+ * It's important that the following functions pass
+ * mUnwriteableMargin values rather than aUnwriteableMargin values
+ * to gtk_page_setup_set_[blank]_margin, because the two may not be
+ * the same. (Specifically, negative values of aUnwriteableMargin
+ * are ignored by the nsPrintSettings::SetUnwriteableMargin functions.)
+ */
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetUnwriteableMarginInTwips(nsIntMargin& aUnwriteableMargin)
+{
+ nsPrintSettings::SetUnwriteableMarginInTwips(aUnwriteableMargin);
+ gtk_page_setup_set_top_margin(mPageSetup,
+ NS_TWIPS_TO_INCHES(mUnwriteableMargin.top), GTK_UNIT_INCH);
+ gtk_page_setup_set_left_margin(mPageSetup,
+ NS_TWIPS_TO_INCHES(mUnwriteableMargin.left), GTK_UNIT_INCH);
+ gtk_page_setup_set_bottom_margin(mPageSetup,
+ NS_TWIPS_TO_INCHES(mUnwriteableMargin.bottom), GTK_UNIT_INCH);
+ gtk_page_setup_set_right_margin(mPageSetup,
+ NS_TWIPS_TO_INCHES(mUnwriteableMargin.right), GTK_UNIT_INCH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetUnwriteableMarginTop(double aUnwriteableMarginTop)
+{
+ nsPrintSettings::SetUnwriteableMarginTop(aUnwriteableMarginTop);
+ gtk_page_setup_set_top_margin(mPageSetup,
+ NS_TWIPS_TO_INCHES(mUnwriteableMargin.top), GTK_UNIT_INCH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetUnwriteableMarginLeft(double aUnwriteableMarginLeft)
+{
+ nsPrintSettings::SetUnwriteableMarginLeft(aUnwriteableMarginLeft);
+ gtk_page_setup_set_left_margin(mPageSetup,
+ NS_TWIPS_TO_INCHES(mUnwriteableMargin.left), GTK_UNIT_INCH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetUnwriteableMarginBottom(double aUnwriteableMarginBottom)
+{
+ nsPrintSettings::SetUnwriteableMarginBottom(aUnwriteableMarginBottom);
+ gtk_page_setup_set_bottom_margin(mPageSetup,
+ NS_TWIPS_TO_INCHES(mUnwriteableMargin.bottom), GTK_UNIT_INCH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetUnwriteableMarginRight(double aUnwriteableMarginRight)
+{
+ nsPrintSettings::SetUnwriteableMarginRight(aUnwriteableMarginRight);
+ gtk_page_setup_set_right_margin(mPageSetup,
+ NS_TWIPS_TO_INCHES(mUnwriteableMargin.right), GTK_UNIT_INCH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPaperWidth(double *aPaperWidth)
+{
+ NS_ENSURE_ARG_POINTER(aPaperWidth);
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ *aPaperWidth =
+ gtk_paper_size_get_width(paperSize, GetGTKUnit(mPaperSizeUnit));
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPaperWidth(double aPaperWidth)
+{
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ gtk_paper_size_set_size(paperSize,
+ aPaperWidth,
+ gtk_paper_size_get_height(paperSize, GetGTKUnit(mPaperSizeUnit)),
+ GetGTKUnit(mPaperSizeUnit));
+ SaveNewPageSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPaperHeight(double *aPaperHeight)
+{
+ NS_ENSURE_ARG_POINTER(aPaperHeight);
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ *aPaperHeight =
+ gtk_paper_size_get_height(paperSize, GetGTKUnit(mPaperSizeUnit));
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPaperHeight(double aPaperHeight)
+{
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ gtk_paper_size_set_size(paperSize,
+ gtk_paper_size_get_width(paperSize, GetGTKUnit(mPaperSizeUnit)),
+ aPaperHeight,
+ GetGTKUnit(mPaperSizeUnit));
+ SaveNewPageSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPaperSizeUnit(int16_t aPaperSizeUnit)
+{
+ // Convert units internally. e.g. they might have set the values while we're still in mm but
+ // they change to inch just afterwards, expecting that their sizes are in inches.
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ gtk_paper_size_set_size(paperSize,
+ gtk_paper_size_get_width(paperSize, GetGTKUnit(mPaperSizeUnit)),
+ gtk_paper_size_get_height(paperSize, GetGTKUnit(mPaperSizeUnit)),
+ GetGTKUnit(aPaperSizeUnit));
+ SaveNewPageSize();
+
+ mPaperSizeUnit = aPaperSizeUnit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetEffectivePageSize(double *aWidth, double *aHeight)
+{
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ *aWidth = NS_INCHES_TO_INT_TWIPS(gtk_paper_size_get_width(paperSize, GTK_UNIT_INCH));
+ *aHeight = NS_INCHES_TO_INT_TWIPS(gtk_paper_size_get_height(paperSize, GTK_UNIT_INCH));
+
+ GtkPageOrientation gtkOrient = gtk_page_setup_get_orientation(mPageSetup);
+
+ if (gtkOrient == GTK_PAGE_ORIENTATION_LANDSCAPE ||
+ gtkOrient == GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE) {
+ double temp = *aWidth;
+ *aWidth = *aHeight;
+ *aHeight = temp;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetupSilentPrinting()
+{
+ // We have to get a printer here, rather than when the print settings are constructed.
+ // This is because when we request sync, GTK makes us wait in the *event loop* while waiting
+ // for the enumeration to finish. We must do this when event loop runs are expected.
+ gtk_enumerate_printers(printer_enumerator, this, nullptr, TRUE);
+
+ // XXX If no default printer set, get the first one.
+ if (!GTK_IS_PRINTER(mGTKPrinter))
+ gtk_enumerate_printers(ref_printer, this, nullptr, TRUE);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPageRanges(nsTArray<int32_t> &aPages)
+{
+ gint ctRanges;
+ GtkPageRange* lstRanges = gtk_print_settings_get_page_ranges(mPrintSettings, &ctRanges);
+
+ aPages.Clear();
+
+ if (ctRanges > 1) {
+ for (gint i = 0; i < ctRanges; i++) {
+ aPages.AppendElement(lstRanges[i].start+1);
+ aPages.AppendElement(lstRanges[i].end+1);
+ }
+ }
+
+ g_free(lstRanges);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetResolution(int32_t *aResolution)
+{
+ if (!gtk_print_settings_has_key(mPrintSettings, GTK_PRINT_SETTINGS_RESOLUTION))
+ return NS_ERROR_FAILURE;
+ *aResolution = gtk_print_settings_get_resolution(mPrintSettings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetResolution(int32_t aResolution)
+{
+ gtk_print_settings_set_resolution(mPrintSettings, aResolution);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetDuplex(int32_t *aDuplex)
+{
+ if (!gtk_print_settings_has_key(mPrintSettings, GTK_PRINT_SETTINGS_DUPLEX)) {
+ *aDuplex = GTK_PRINT_DUPLEX_SIMPLEX;
+ } else {
+ *aDuplex = gtk_print_settings_get_duplex(mPrintSettings);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetDuplex(int32_t aDuplex)
+{
+ MOZ_ASSERT(aDuplex >= GTK_PRINT_DUPLEX_SIMPLEX &&
+ aDuplex <= GTK_PRINT_DUPLEX_VERTICAL,
+ "value is out of bounds for GtkPrintDuplex enum");
+ gtk_print_settings_set_duplex(mPrintSettings, static_cast<GtkPrintDuplex>(aDuplex));
+ return NS_OK;
+}
+
diff --git a/widget/gtk/nsPrintSettingsGTK.h b/widget/gtk/nsPrintSettingsGTK.h
new file mode 100644
index 000000000..21f389449
--- /dev/null
+++ b/widget/gtk/nsPrintSettingsGTK.h
@@ -0,0 +1,151 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 nsPrintSettingsGTK_h_
+#define nsPrintSettingsGTK_h_
+
+#include "nsPrintSettingsImpl.h"
+
+extern "C" {
+#include <gtk/gtk.h>
+#include <gtk/gtkunixprint.h>
+}
+
+#define NS_PRINTSETTINGSGTK_IID \
+{ 0x758df520, 0xc7c3, 0x11dc, { 0x95, 0xff, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
+
+
+//*****************************************************************************
+//*** nsPrintSettingsGTK
+//*****************************************************************************
+
+class nsPrintSettingsGTK : public nsPrintSettings
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PRINTSETTINGSGTK_IID)
+
+ nsPrintSettingsGTK();
+
+ // We're overriding these methods because we want to read/write with GTK objects,
+ // not local variables. This allows a simpler settings implementation between
+ // Gecko and GTK.
+
+ GtkPageSetup* GetGtkPageSetup() { return mPageSetup; };
+ void SetGtkPageSetup(GtkPageSetup *aPageSetup);
+
+ GtkPrintSettings* GetGtkPrintSettings() { return mPrintSettings; };
+ void SetGtkPrintSettings(GtkPrintSettings *aPrintSettings);
+
+ GtkPrinter* GetGtkPrinter() { return mGTKPrinter; };
+ void SetGtkPrinter(GtkPrinter *aPrinter);
+
+ bool GetForcePrintSelectionOnly() { return mPrintSelectionOnly; };
+ void SetForcePrintSelectionOnly(bool aPrintSelectionOnly) { mPrintSelectionOnly = aPrintSelectionOnly; };
+
+ // If not printing the selection, this is stored in the GtkPrintSettings. Printing the
+ // selection is stored as a protected boolean (mPrintSelectionOnly).
+ NS_IMETHOD GetPrintRange(int16_t *aPrintRange) override;
+ NS_IMETHOD SetPrintRange(int16_t aPrintRange) override;
+
+ // The page range is stored as as single range in the GtkPrintSettings object.
+ NS_IMETHOD GetStartPageRange(int32_t *aStartPageRange) override;
+ NS_IMETHOD SetStartPageRange(int32_t aStartPageRange) override;
+ NS_IMETHOD GetEndPageRange(int32_t *aEndPageRange) override;
+ NS_IMETHOD SetEndPageRange(int32_t aEndPageRange) override;
+
+ // Reversed, color, orientation and file name are all stored in the GtkPrintSettings.
+ // Orientation is also stored in the GtkPageSetup and its setting takes priority when getting the orientation.
+ NS_IMETHOD GetPrintReversed(bool *aPrintReversed) override;
+ NS_IMETHOD SetPrintReversed(bool aPrintReversed) override;
+
+ NS_IMETHOD GetPrintInColor(bool *aPrintInColor) override;
+ NS_IMETHOD SetPrintInColor(bool aPrintInColor) override;
+
+ NS_IMETHOD GetOrientation(int32_t *aOrientation) override;
+ NS_IMETHOD SetOrientation(int32_t aOrientation) override;
+
+ NS_IMETHOD GetToFileName(char16_t * *aToFileName) override;
+ NS_IMETHOD SetToFileName(const char16_t * aToFileName) override;
+
+ // Gets/Sets the printer name in the GtkPrintSettings. If no printer name is specified there,
+ // you will get back the name of the current internal GtkPrinter.
+ NS_IMETHOD GetPrinterName(char16_t * *aPrinter) override;
+ NS_IMETHOD SetPrinterName(const char16_t * aPrinter) override;
+
+ // Number of copies is stored/gotten from the GtkPrintSettings.
+ NS_IMETHOD GetNumCopies(int32_t *aNumCopies) override;
+ NS_IMETHOD SetNumCopies(int32_t aNumCopies) override;
+
+ NS_IMETHOD GetScaling(double *aScaling) override;
+ NS_IMETHOD SetScaling(double aScaling) override;
+
+ // A name recognised by GTK is strongly advised here, as this is used to create a GtkPaperSize.
+ NS_IMETHOD GetPaperName(char16_t * *aPaperName) override;
+ NS_IMETHOD SetPaperName(const char16_t * aPaperName) override;
+
+ NS_IMETHOD SetUnwriteableMarginInTwips(nsIntMargin& aUnwriteableMargin) override;
+ NS_IMETHOD SetUnwriteableMarginTop(double aUnwriteableMarginTop) override;
+ NS_IMETHOD SetUnwriteableMarginLeft(double aUnwriteableMarginLeft) override;
+ NS_IMETHOD SetUnwriteableMarginBottom(double aUnwriteableMarginBottom) override;
+ NS_IMETHOD SetUnwriteableMarginRight(double aUnwriteableMarginRight) override;
+
+ NS_IMETHOD GetPaperWidth(double *aPaperWidth) override;
+ NS_IMETHOD SetPaperWidth(double aPaperWidth) override;
+
+ NS_IMETHOD GetPaperHeight(double *aPaperHeight) override;
+ NS_IMETHOD SetPaperHeight(double aPaperHeight) override;
+
+ NS_IMETHOD SetPaperSizeUnit(int16_t aPaperSizeUnit) override;
+
+ NS_IMETHOD GetEffectivePageSize(double *aWidth, double *aHeight) override;
+
+ NS_IMETHOD SetupSilentPrinting() override;
+
+ NS_IMETHOD GetPageRanges(nsTArray<int32_t> &aPages) override;
+
+ NS_IMETHOD GetResolution(int32_t *aResolution) override;
+ NS_IMETHOD SetResolution(int32_t aResolution) override;
+
+ NS_IMETHOD GetDuplex(int32_t *aDuplex) override;
+ NS_IMETHOD SetDuplex(int32_t aDuplex) override;
+
+ NS_IMETHOD GetOutputFormat(int16_t *aOutputFormat) override;
+
+protected:
+ virtual ~nsPrintSettingsGTK();
+
+ nsPrintSettingsGTK(const nsPrintSettingsGTK& src);
+ nsPrintSettingsGTK& operator=(const nsPrintSettingsGTK& rhs);
+
+ virtual nsresult _Clone(nsIPrintSettings **_retval) override;
+ virtual nsresult _Assign(nsIPrintSettings *aPS) override;
+
+ GtkUnit GetGTKUnit(int16_t aGeckoUnit);
+ void SaveNewPageSize();
+
+ /**
+ * Re-initialize mUnwriteableMargin with values from mPageSetup.
+ * Should be called whenever mPageSetup is initialized or overwritten.
+ */
+ void InitUnwriteableMargin();
+
+ /**
+ * On construction:
+ * - mPrintSettings and mPageSetup are just new objects with defaults determined by GTK.
+ * - mGTKPrinter is nullptr!!! Remember to be careful when accessing this property.
+ */
+ GtkPageSetup* mPageSetup;
+ GtkPrintSettings* mPrintSettings;
+ GtkPrinter* mGTKPrinter;
+
+ bool mPrintSelectionOnly;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPrintSettingsGTK, NS_PRINTSETTINGSGTK_IID)
+
+
+#endif // nsPrintSettingsGTK_h_
diff --git a/widget/gtk/nsScreenGtk.cpp b/widget/gtk/nsScreenGtk.cpp
new file mode 100644
index 000000000..61e6605b7
--- /dev/null
+++ b/widget/gtk/nsScreenGtk.cpp
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "nsScreenGtk.h"
+
+#include "nsIWidget.h"
+
+#include <gdk/gdk.h>
+#ifdef MOZ_X11
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+#endif
+#include <gtk/gtk.h>
+#include <dlfcn.h>
+#include "gfxPlatformGtk.h"
+
+static uint32_t sScreenId = 0;
+
+
+nsScreenGtk :: nsScreenGtk ( )
+ : mScreenNum(0),
+ mRect(0, 0, 0, 0),
+ mAvailRect(0, 0, 0, 0),
+ mId(++sScreenId)
+{
+}
+
+
+nsScreenGtk :: ~nsScreenGtk()
+{
+}
+
+
+NS_IMETHODIMP
+nsScreenGtk :: GetId(uint32_t *aId)
+{
+ *aId = mId;
+ return NS_OK;
+} // GetId
+
+
+NS_IMETHODIMP
+nsScreenGtk :: GetRect(int32_t *outLeft, int32_t *outTop, int32_t *outWidth, int32_t *outHeight)
+{
+ *outLeft = mRect.x;
+ *outTop = mRect.y;
+ *outWidth = mRect.width;
+ *outHeight = mRect.height;
+
+ return NS_OK;
+
+} // GetRect
+
+
+NS_IMETHODIMP
+nsScreenGtk :: GetAvailRect(int32_t *outLeft, int32_t *outTop, int32_t *outWidth, int32_t *outHeight)
+{
+ *outLeft = mAvailRect.x;
+ *outTop = mAvailRect.y;
+ *outWidth = mAvailRect.width;
+ *outHeight = mAvailRect.height;
+
+ return NS_OK;
+
+} // GetAvailRect
+
+gint
+nsScreenGtk :: GetGtkMonitorScaleFactor()
+{
+#if (MOZ_WIDGET_GTK >= 3)
+ // Since GDK 3.10
+ static auto sGdkScreenGetMonitorScaleFactorPtr = (gint (*)(GdkScreen*, gint))
+ dlsym(RTLD_DEFAULT, "gdk_screen_get_monitor_scale_factor");
+ if (sGdkScreenGetMonitorScaleFactorPtr) {
+ // FIXME: In the future, we'll want to fix this for GTK on Wayland which
+ // supports a variable scale factor per display.
+ GdkScreen *screen = gdk_screen_get_default();
+ return sGdkScreenGetMonitorScaleFactorPtr(screen, 0);
+ }
+#endif
+ return 1;
+}
+
+double
+nsScreenGtk :: GetDPIScale()
+{
+ double dpiScale = nsIWidget::DefaultScaleOverride();
+ if (dpiScale <= 0.0) {
+ dpiScale = GetGtkMonitorScaleFactor() * gfxPlatformGtk::GetDPIScale();
+ }
+ return dpiScale;
+}
+
+NS_IMETHODIMP
+nsScreenGtk :: GetPixelDepth(int32_t *aPixelDepth)
+{
+ GdkVisual * visual = gdk_screen_get_system_visual(gdk_screen_get_default());
+ *aPixelDepth = gdk_visual_get_depth(visual);
+
+ return NS_OK;
+
+} // GetPixelDepth
+
+NS_IMETHODIMP
+nsScreenGtk :: GetColorDepth(int32_t *aColorDepth)
+{
+ return GetPixelDepth ( aColorDepth );
+
+} // GetColorDepth
+
+NS_IMETHODIMP
+nsScreenGtk::GetDefaultCSSScaleFactor(double* aScaleFactor)
+{
+ *aScaleFactor = GetDPIScale();
+ return NS_OK;
+}
+
+void
+nsScreenGtk :: Init (GdkWindow *aRootWindow)
+{
+ gint scale = nsScreenGtk::GetGtkMonitorScaleFactor();
+ gint width = gdk_screen_width()*scale;
+ gint height = gdk_screen_height()*scale;
+
+ // We listen for configure events on the root window to pick up
+ // changes to this rect. We could listen for "size_changed" signals
+ // on the default screen to do this, except that doesn't work with
+ // versions of GDK predating the GdkScreen object. See bug 256646.
+ mAvailRect = mRect = nsIntRect(0, 0, width, height);
+
+#ifdef MOZ_X11
+ // We need to account for the taskbar, etc in the available rect.
+ // See http://freedesktop.org/Standards/wm-spec/index.html#id2767771
+
+ // XXX do we care about _NET_WM_STRUT_PARTIAL? That will
+ // add much more complexity to the code here (our screen
+ // could have a non-rectangular shape), but should
+ // lead to greater accuracy.
+
+ long *workareas;
+ GdkAtom type_returned;
+ int format_returned;
+ int length_returned;
+
+ GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
+
+ gdk_error_trap_push();
+
+ // gdk_property_get uses (length + 3) / 4, hence G_MAXLONG - 3 here.
+ if (!gdk_property_get(aRootWindow,
+ gdk_atom_intern ("_NET_WORKAREA", FALSE),
+ cardinal_atom,
+ 0, G_MAXLONG - 3, FALSE,
+ &type_returned,
+ &format_returned,
+ &length_returned,
+ (guchar **) &workareas)) {
+ // This window manager doesn't support the freedesktop standard.
+ // Nothing we can do about it, so assume full screen size.
+ return;
+ }
+
+ // Flush the X queue to catch errors now.
+ gdk_flush();
+
+ if (!gdk_error_trap_pop() &&
+ type_returned == cardinal_atom &&
+ length_returned && (length_returned % 4) == 0 &&
+ format_returned == 32) {
+ int num_items = length_returned / sizeof(long);
+
+ for (int i = 0; i < num_items; i += 4) {
+ nsIntRect workarea(workareas[i], workareas[i + 1],
+ workareas[i + 2], workareas[i + 3]);
+ if (!mRect.Contains(workarea)) {
+ // Note that we hit this when processing screen size changes,
+ // since we'll get the configure event before the toolbars have
+ // been moved. We'll end up cleaning this up when we get the
+ // change notification to the _NET_WORKAREA property. However,
+ // we still want to listen to both, so we'll handle changes
+ // properly for desktop environments that don't set the
+ // _NET_WORKAREA property.
+ NS_WARNING("Invalid bounds");
+ continue;
+ }
+
+ mAvailRect.IntersectRect(mAvailRect, workarea);
+ }
+ }
+ g_free (workareas);
+#endif
+}
+
+#ifdef MOZ_X11
+void
+nsScreenGtk :: Init (XineramaScreenInfo *aScreenInfo)
+{
+ nsIntRect xineRect(aScreenInfo->x_org, aScreenInfo->y_org,
+ aScreenInfo->width, aScreenInfo->height);
+
+ mScreenNum = aScreenInfo->screen_number;
+
+ mAvailRect = mRect = xineRect;
+}
+#endif
diff --git a/widget/gtk/nsScreenGtk.h b/widget/gtk/nsScreenGtk.h
new file mode 100644
index 000000000..d58ea4b1e
--- /dev/null
+++ b/widget/gtk/nsScreenGtk.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 nsScreenGtk_h___
+#define nsScreenGtk_h___
+
+#include "nsBaseScreen.h"
+#include "nsRect.h"
+#include "gdk/gdk.h"
+#ifdef MOZ_X11
+#include <X11/Xlib.h>
+
+// from Xinerama.h
+typedef struct {
+ int screen_number;
+ short x_org;
+ short y_org;
+ short width;
+ short height;
+} XineramaScreenInfo;
+#endif /* MOZ_X11 */
+
+//------------------------------------------------------------------------
+
+class nsScreenGtk : public nsBaseScreen
+{
+public:
+ nsScreenGtk();
+ ~nsScreenGtk();
+
+ NS_IMETHOD GetId(uint32_t* aId) override;
+ NS_IMETHOD GetRect(int32_t* aLeft, int32_t* aTop,
+ int32_t* aWidth, int32_t* aHeight) override;
+ NS_IMETHOD GetAvailRect(int32_t* aLeft, int32_t* aTop,
+ int32_t* aWidth, int32_t* aHeight) override;
+ NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth) override;
+ NS_IMETHOD GetColorDepth(int32_t* aColorDepth) override;
+ NS_IMETHOD GetDefaultCSSScaleFactor(double* aScaleFactor) override;
+
+ void Init(GdkWindow *aRootWindow);
+#ifdef MOZ_X11
+ void Init(XineramaScreenInfo *aScreenInfo);
+#endif /* MOZ_X11 */
+
+ static gint GetGtkMonitorScaleFactor();
+ static double GetDPIScale();
+
+private:
+ uint32_t mScreenNum;
+ nsIntRect mRect;
+ nsIntRect mAvailRect;
+ uint32_t mId;
+};
+
+#endif // nsScreenGtk_h___
diff --git a/widget/gtk/nsScreenManagerGtk.cpp b/widget/gtk/nsScreenManagerGtk.cpp
new file mode 100644
index 000000000..98166cc92
--- /dev/null
+++ b/widget/gtk/nsScreenManagerGtk.cpp
@@ -0,0 +1,366 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "nsScreenManagerGtk.h"
+
+#include "mozilla/RefPtr.h"
+#include "nsScreenGtk.h"
+#include "nsIComponentManager.h"
+#include "nsRect.h"
+#include "nsGtkUtils.h"
+
+#define SCREEN_MANAGER_LIBRARY_LOAD_FAILED ((PRLibrary*)1)
+
+#ifdef MOZ_X11
+#include <gdk/gdkx.h>
+// prototypes from Xinerama.h
+typedef Bool (*_XnrmIsActive_fn)(Display *dpy);
+typedef XineramaScreenInfo* (*_XnrmQueryScreens_fn)(Display *dpy, int *number);
+#endif
+
+#include <gtk/gtk.h>
+
+void
+monitors_changed(GdkScreen* aScreen, gpointer aClosure)
+{
+ nsScreenManagerGtk *manager = static_cast<nsScreenManagerGtk*>(aClosure);
+ manager->Init();
+}
+
+static GdkFilterReturn
+root_window_event_filter(GdkXEvent *aGdkXEvent, GdkEvent *aGdkEvent,
+ gpointer aClosure)
+{
+ nsScreenManagerGtk *manager = static_cast<nsScreenManagerGtk*>(aClosure);
+#ifdef MOZ_X11
+ XEvent *xevent = static_cast<XEvent*>(aGdkXEvent);
+
+ // See comments in nsScreenGtk::Init below.
+ switch (xevent->type) {
+ case PropertyNotify:
+ {
+ XPropertyEvent *propertyEvent = &xevent->xproperty;
+ if (propertyEvent->atom == manager->NetWorkareaAtom()) {
+ manager->Init();
+ }
+ }
+ break;
+ default:
+ break;
+ }
+#endif
+
+ return GDK_FILTER_CONTINUE;
+}
+
+nsScreenManagerGtk :: nsScreenManagerGtk ( )
+ : mXineramalib(nullptr)
+ , mRootWindow(nullptr)
+ , mNetWorkareaAtom(0)
+{
+ // nothing else to do. I guess we could cache a bunch of information
+ // here, but we want to ask the device at runtime in case anything
+ // has changed.
+}
+
+
+nsScreenManagerGtk :: ~nsScreenManagerGtk()
+{
+ g_signal_handlers_disconnect_by_func(gdk_screen_get_default(),
+ FuncToGpointer(monitors_changed),
+ this);
+
+ if (mRootWindow) {
+ gdk_window_remove_filter(mRootWindow, root_window_event_filter, this);
+ g_object_unref(mRootWindow);
+ mRootWindow = nullptr;
+ }
+
+ /* XineramaIsActive() registers a callback function close_display()
+ * in X, which is to be called in XCloseDisplay(). This is the case
+ * if Xinerama is active, even if only with one screen.
+ *
+ * We can't unload libXinerama.so.1 here because this will make
+ * the address of close_display() registered in X to be invalid and
+ * it will crash when XCloseDisplay() is called later. */
+}
+
+
+// addref, release, QI
+NS_IMPL_ISUPPORTS(nsScreenManagerGtk, nsIScreenManager)
+
+
+// this function will make sure that everything has been initialized.
+nsresult
+nsScreenManagerGtk :: EnsureInit()
+{
+ if (mCachedScreenArray.Count() > 0)
+ return NS_OK;
+
+ mRootWindow = gdk_get_default_root_window();
+ if (!mRootWindow) {
+ // Sometimes we don't initial X (e.g., xpcshell)
+ return NS_OK;
+ }
+
+ g_object_ref(mRootWindow);
+
+ // GDK_PROPERTY_CHANGE_MASK ==> PropertyChangeMask, for PropertyNotify
+ gdk_window_set_events(mRootWindow,
+ GdkEventMask(gdk_window_get_events(mRootWindow) |
+ GDK_PROPERTY_CHANGE_MASK));
+
+ g_signal_connect(gdk_screen_get_default(), "monitors-changed",
+ G_CALLBACK(monitors_changed), this);
+#ifdef MOZ_X11
+ gdk_window_add_filter(mRootWindow, root_window_event_filter, this);
+ if (GDK_IS_X11_DISPLAY(gdk_display_get_default()))
+ mNetWorkareaAtom =
+ XInternAtom(GDK_WINDOW_XDISPLAY(mRootWindow), "_NET_WORKAREA", False);
+#endif
+
+ return Init();
+}
+
+nsresult
+nsScreenManagerGtk :: Init()
+{
+#ifdef MOZ_X11
+ XineramaScreenInfo *screenInfo = nullptr;
+ int numScreens;
+
+ bool useXinerama = GDK_IS_X11_DISPLAY(gdk_display_get_default());
+
+ if (useXinerama && !mXineramalib) {
+ mXineramalib = PR_LoadLibrary("libXinerama.so.1");
+ if (!mXineramalib) {
+ mXineramalib = SCREEN_MANAGER_LIBRARY_LOAD_FAILED;
+ }
+ }
+ if (mXineramalib && mXineramalib != SCREEN_MANAGER_LIBRARY_LOAD_FAILED) {
+ _XnrmIsActive_fn _XnrmIsActive = (_XnrmIsActive_fn)
+ PR_FindFunctionSymbol(mXineramalib, "XineramaIsActive");
+
+ _XnrmQueryScreens_fn _XnrmQueryScreens = (_XnrmQueryScreens_fn)
+ PR_FindFunctionSymbol(mXineramalib, "XineramaQueryScreens");
+
+ // get the number of screens via xinerama
+ Display *display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+ if (_XnrmIsActive && _XnrmQueryScreens && _XnrmIsActive(display)) {
+ screenInfo = _XnrmQueryScreens(display, &numScreens);
+ }
+ }
+
+ // screenInfo == nullptr if either Xinerama couldn't be loaded or
+ // isn't running on the current display
+ if (!screenInfo || numScreens == 1) {
+ numScreens = 1;
+#endif
+ RefPtr<nsScreenGtk> screen;
+
+ if (mCachedScreenArray.Count() > 0) {
+ screen = static_cast<nsScreenGtk*>(mCachedScreenArray[0]);
+ } else {
+ screen = new nsScreenGtk();
+ if (!screen || !mCachedScreenArray.AppendObject(screen)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ screen->Init(mRootWindow);
+#ifdef MOZ_X11
+ }
+ // If Xinerama is enabled and there's more than one screen, fill
+ // in the info for all of the screens. If that's not the case
+ // then nsScreenGTK() defaults to the screen width + height
+ else {
+#ifdef DEBUG
+ printf("Xinerama superpowers activated for %d screens!\n", numScreens);
+#endif
+ for (int i = 0; i < numScreens; ++i) {
+ RefPtr<nsScreenGtk> screen;
+ if (mCachedScreenArray.Count() > i) {
+ screen = static_cast<nsScreenGtk*>(mCachedScreenArray[i]);
+ } else {
+ screen = new nsScreenGtk();
+ if (!screen || !mCachedScreenArray.AppendObject(screen)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ // initialize this screen object
+ screen->Init(&screenInfo[i]);
+ }
+ }
+ // Remove any screens that are no longer present.
+ while (mCachedScreenArray.Count() > numScreens) {
+ mCachedScreenArray.RemoveObjectAt(mCachedScreenArray.Count() - 1);
+ }
+
+ if (screenInfo) {
+ XFree(screenInfo);
+ }
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerGtk :: ScreenForId ( uint32_t aId, nsIScreen **outScreen )
+{
+ *outScreen = nullptr;
+
+ nsresult rv;
+ rv = EnsureInit();
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from ScreenForId");
+ return rv;
+ }
+
+ for (int32_t i = 0, i_end = mCachedScreenArray.Count(); i < i_end; ++i) {
+ uint32_t id;
+ rv = mCachedScreenArray[i]->GetId(&id);
+ if (NS_SUCCEEDED(rv) && id == aId) {
+ NS_IF_ADDREF(*outScreen = mCachedScreenArray[i]);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+
+//
+// ScreenForRect
+//
+// Returns the screen that contains the rectangle. If the rect overlaps
+// multiple screens, it picks the screen with the greatest area of intersection.
+//
+// The coordinates are in desktop pixels.
+//
+NS_IMETHODIMP
+nsScreenManagerGtk::ScreenForRect(int32_t aX, int32_t aY,
+ int32_t aWidth, int32_t aHeight,
+ nsIScreen **aOutScreen)
+{
+ nsresult rv;
+ rv = EnsureInit();
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from ScreenForRect");
+ return rv;
+ }
+
+ // which screen ( index from zero ) should we return?
+ uint32_t which = 0;
+ // Optimize for the common case. If the number of screens is only
+ // one then this will fall through with which == 0 and will get the
+ // primary screen.
+ if (mCachedScreenArray.Count() > 1) {
+ // walk the list of screens and find the one that has the most
+ // surface area.
+ uint32_t area = 0;
+ nsIntRect windowRect(aX, aY, aWidth, aHeight);
+ for (int32_t i = 0, i_end = mCachedScreenArray.Count(); i < i_end; ++i) {
+ int32_t x, y, width, height;
+ x = y = width = height = 0;
+ mCachedScreenArray[i]->GetRect(&x, &y, &width, &height);
+ // calculate the surface area
+ nsIntRect screenRect(x, y, width, height);
+ screenRect.IntersectRect(screenRect, windowRect);
+ uint32_t tempArea = screenRect.width * screenRect.height;
+ if (tempArea >= area) {
+ which = i;
+ area = tempArea;
+ }
+ }
+ }
+ *aOutScreen = mCachedScreenArray.SafeObjectAt(which);
+ NS_IF_ADDREF(*aOutScreen);
+ return NS_OK;
+
+} // ScreenForRect
+
+
+//
+// GetPrimaryScreen
+//
+// The screen with the menubar/taskbar. This shouldn't be needed very
+// often.
+//
+NS_IMETHODIMP
+nsScreenManagerGtk :: GetPrimaryScreen(nsIScreen * *aPrimaryScreen)
+{
+ nsresult rv;
+ rv = EnsureInit();
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from GetPrimaryScreen");
+ return rv;
+ }
+ *aPrimaryScreen = mCachedScreenArray.SafeObjectAt(0);
+ NS_IF_ADDREF(*aPrimaryScreen);
+ return NS_OK;
+
+} // GetPrimaryScreen
+
+
+//
+// GetNumberOfScreens
+//
+// Returns how many physical screens are available.
+//
+NS_IMETHODIMP
+nsScreenManagerGtk :: GetNumberOfScreens(uint32_t *aNumberOfScreens)
+{
+ nsresult rv;
+ rv = EnsureInit();
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from GetNumberOfScreens");
+ return rv;
+ }
+ *aNumberOfScreens = mCachedScreenArray.Count();
+ return NS_OK;
+
+} // GetNumberOfScreens
+
+NS_IMETHODIMP
+nsScreenManagerGtk::GetSystemDefaultScale(float *aDefaultScale)
+{
+ *aDefaultScale = nsScreenGtk::GetDPIScale();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerGtk :: ScreenForNativeWidget (void *aWidget, nsIScreen **outScreen)
+{
+ nsresult rv;
+ rv = EnsureInit();
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from ScreenForNativeWidget");
+ return rv;
+ }
+
+ if (mCachedScreenArray.Count() > 1) {
+ // I don't know how to go from GtkWindow to nsIScreen, especially
+ // given xinerama and stuff, so let's just do this
+ gint x, y, width, height;
+#if (MOZ_WIDGET_GTK == 2)
+ gint depth;
+#endif
+ x = y = width = height = 0;
+
+#if (MOZ_WIDGET_GTK == 2)
+ gdk_window_get_geometry(GDK_WINDOW(aWidget), &x, &y, &width, &height,
+ &depth);
+#else
+ gdk_window_get_geometry(GDK_WINDOW(aWidget), &x, &y, &width, &height);
+#endif
+ gdk_window_get_origin(GDK_WINDOW(aWidget), &x, &y);
+ rv = ScreenForRect(x, y, width, height, outScreen);
+ } else {
+ rv = GetPrimaryScreen(outScreen);
+ }
+
+ return rv;
+}
diff --git a/widget/gtk/nsScreenManagerGtk.h b/widget/gtk/nsScreenManagerGtk.h
new file mode 100644
index 000000000..9afb3bf22
--- /dev/null
+++ b/widget/gtk/nsScreenManagerGtk.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 nsScreenManagerGtk_h___
+#define nsScreenManagerGtk_h___
+
+#include "nsIScreenManager.h"
+#include "nsIScreen.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "prlink.h"
+#include "gdk/gdk.h"
+#ifdef MOZ_X11
+#include <X11/Xlib.h>
+#endif
+
+//------------------------------------------------------------------------
+
+class nsScreenManagerGtk : public nsIScreenManager
+{
+public:
+ nsScreenManagerGtk ( );
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCREENMANAGER
+
+#ifdef MOZ_X11
+ Atom NetWorkareaAtom() { return mNetWorkareaAtom; }
+#endif
+
+ // For internal use, or reinitialization from change notification.
+ nsresult Init();
+
+private:
+ virtual ~nsScreenManagerGtk();
+
+ nsresult EnsureInit();
+
+ // Cached screen array. Its length is the number of screens we have.
+ nsCOMArray<nsIScreen> mCachedScreenArray;
+
+ PRLibrary *mXineramalib;
+
+ GdkWindow *mRootWindow;
+#ifdef MOZ_X11
+ Atom mNetWorkareaAtom;
+#endif
+};
+
+#endif // nsScreenManagerGtk_h___
diff --git a/widget/gtk/nsSound.cpp b/widget/gtk/nsSound.cpp
new file mode 100644
index 000000000..4e81fe43f
--- /dev/null
+++ b/widget/gtk/nsSound.cpp
@@ -0,0 +1,444 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include <string.h>
+
+#include "nscore.h"
+#include "plstr.h"
+#include "prlink.h"
+
+#include "nsSound.h"
+
+#include "nsIURL.h"
+#include "nsIFileURL.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsDirectoryService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include "nsIStringBundle.h"
+#include "nsIXULAppInfo.h"
+#include "nsContentUtils.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <gtk/gtk.h>
+static PRLibrary *libcanberra = nullptr;
+
+/* used to play sounds with libcanberra. */
+typedef struct _ca_context ca_context;
+typedef struct _ca_proplist ca_proplist;
+
+typedef void (*ca_finish_callback_t) (ca_context *c,
+ uint32_t id,
+ int error_code,
+ void *userdata);
+
+typedef int (*ca_context_create_fn) (ca_context **);
+typedef int (*ca_context_destroy_fn) (ca_context *);
+typedef int (*ca_context_play_fn) (ca_context *c,
+ uint32_t id,
+ ...);
+typedef int (*ca_context_change_props_fn) (ca_context *c,
+ ...);
+typedef int (*ca_proplist_create_fn) (ca_proplist **);
+typedef int (*ca_proplist_destroy_fn) (ca_proplist *);
+typedef int (*ca_proplist_sets_fn) (ca_proplist *c,
+ const char *key,
+ const char *value);
+typedef int (*ca_context_play_full_fn) (ca_context *c,
+ uint32_t id,
+ ca_proplist *p,
+ ca_finish_callback_t cb,
+ void *userdata);
+
+static ca_context_create_fn ca_context_create;
+static ca_context_destroy_fn ca_context_destroy;
+static ca_context_play_fn ca_context_play;
+static ca_context_change_props_fn ca_context_change_props;
+static ca_proplist_create_fn ca_proplist_create;
+static ca_proplist_destroy_fn ca_proplist_destroy;
+static ca_proplist_sets_fn ca_proplist_sets;
+static ca_context_play_full_fn ca_context_play_full;
+
+struct ScopedCanberraFile {
+ explicit ScopedCanberraFile(nsIFile *file): mFile(file) {};
+
+ ~ScopedCanberraFile() {
+ if (mFile) {
+ mFile->Remove(false);
+ }
+ }
+
+ void forget() {
+ mozilla::Unused << mFile.forget();
+ }
+ nsIFile* operator->() { return mFile; }
+ operator nsIFile*() { return mFile; }
+
+ nsCOMPtr<nsIFile> mFile;
+};
+
+static ca_context*
+ca_context_get_default()
+{
+ // This allows us to avoid race conditions with freeing the context by handing that
+ // responsibility to Glib, and still use one context at a time
+ static GStaticPrivate ctx_static_private = G_STATIC_PRIVATE_INIT;
+
+ ca_context* ctx = (ca_context*) g_static_private_get(&ctx_static_private);
+
+ if (ctx) {
+ return ctx;
+ }
+
+ ca_context_create(&ctx);
+ if (!ctx) {
+ return nullptr;
+ }
+
+ g_static_private_set(&ctx_static_private, ctx, (GDestroyNotify) ca_context_destroy);
+
+ GtkSettings* settings = gtk_settings_get_default();
+ if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
+ "gtk-sound-theme-name")) {
+ gchar* sound_theme_name = nullptr;
+ g_object_get(settings, "gtk-sound-theme-name", &sound_theme_name,
+ nullptr);
+
+ if (sound_theme_name) {
+ ca_context_change_props(ctx, "canberra.xdg-theme.name",
+ sound_theme_name, nullptr);
+ g_free(sound_theme_name);
+ }
+ }
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (bundleService) {
+ nsCOMPtr<nsIStringBundle> brandingBundle;
+ bundleService->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(brandingBundle));
+ if (brandingBundle) {
+ nsAutoString wbrand;
+ brandingBundle->GetStringFromName(u"brandShortName",
+ getter_Copies(wbrand));
+ NS_ConvertUTF16toUTF8 brand(wbrand);
+
+ ca_context_change_props(ctx, "application.name", brand.get(),
+ nullptr);
+ }
+ }
+
+ nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1");
+ if (appInfo) {
+ nsAutoCString version;
+ appInfo->GetVersion(version);
+
+ ca_context_change_props(ctx, "application.version", version.get(),
+ nullptr);
+ }
+
+ ca_context_change_props(ctx, "application.icon_name", MOZ_APP_NAME,
+ nullptr);
+
+ return ctx;
+}
+
+static void
+ca_finish_cb(ca_context *c,
+ uint32_t id,
+ int error_code,
+ void *userdata)
+{
+ nsIFile *file = reinterpret_cast<nsIFile *>(userdata);
+ if (file) {
+ file->Remove(false);
+ NS_RELEASE(file);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver)
+
+////////////////////////////////////////////////////////////////////////
+nsSound::nsSound()
+{
+ mInited = false;
+}
+
+nsSound::~nsSound()
+{
+}
+
+NS_IMETHODIMP
+nsSound::Init()
+{
+ // This function is designed so that no library is compulsory, and
+ // one library missing doesn't cause the other(s) to not be used.
+ if (mInited)
+ return NS_OK;
+
+ mInited = true;
+
+ if (!libcanberra) {
+ libcanberra = PR_LoadLibrary("libcanberra.so.0");
+ if (libcanberra) {
+ ca_context_create = (ca_context_create_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_create");
+ if (!ca_context_create) {
+ PR_UnloadLibrary(libcanberra);
+ libcanberra = nullptr;
+ } else {
+ // at this point we know we have a good libcanberra library
+ ca_context_destroy = (ca_context_destroy_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_destroy");
+ ca_context_play = (ca_context_play_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_play");
+ ca_context_change_props = (ca_context_change_props_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_change_props");
+ ca_proplist_create = (ca_proplist_create_fn) PR_FindFunctionSymbol(libcanberra, "ca_proplist_create");
+ ca_proplist_destroy = (ca_proplist_destroy_fn) PR_FindFunctionSymbol(libcanberra, "ca_proplist_destroy");
+ ca_proplist_sets = (ca_proplist_sets_fn) PR_FindFunctionSymbol(libcanberra, "ca_proplist_sets");
+ ca_context_play_full = (ca_context_play_full_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_play_full");
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+/* static */ void
+nsSound::Shutdown()
+{
+ if (libcanberra) {
+ PR_UnloadLibrary(libcanberra);
+ libcanberra = nullptr;
+ }
+}
+
+NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader,
+ nsISupports *context,
+ nsresult aStatus,
+ uint32_t dataLen,
+ const uint8_t *data)
+{
+ // print a load error on bad status, and return
+ if (NS_FAILED(aStatus)) {
+#ifdef DEBUG
+ if (aLoader) {
+ nsCOMPtr<nsIRequest> request;
+ aLoader->GetRequest(getter_AddRefs(request));
+ if (request) {
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (channel) {
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ printf("Failed to load %s\n",
+ uri->GetSpecOrDefault().get());
+ }
+ }
+ }
+ }
+#endif
+ return aStatus;
+ }
+
+ nsCOMPtr<nsIFile> tmpFile;
+ nsDirectoryService::gService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(tmpFile));
+
+ nsresult rv = tmpFile->AppendNative(nsDependentCString("mozilla_audio_sample"));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, PR_IRUSR | PR_IWUSR);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ ScopedCanberraFile canberraFile(tmpFile);
+
+ mozilla::AutoFDClose fd;
+ rv = canberraFile->OpenNSPRFileDesc(PR_WRONLY, PR_IRUSR | PR_IWUSR,
+ &fd.rwget());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // XXX: Should we do this on another thread?
+ uint32_t length = dataLen;
+ while (length > 0) {
+ int32_t amount = PR_Write(fd, data, length);
+ if (amount < 0) {
+ return NS_ERROR_FAILURE;
+ }
+ length -= amount;
+ data += amount;
+ }
+
+ ca_context* ctx = ca_context_get_default();
+ if (!ctx) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ ca_proplist *p;
+ ca_proplist_create(&p);
+ if (!p) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsAutoCString path;
+ rv = canberraFile->GetNativePath(path);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ ca_proplist_sets(p, "media.filename", path.get());
+ if (ca_context_play_full(ctx, 0, p, ca_finish_cb, canberraFile) >= 0) {
+ // Don't delete the temporary file here if ca_context_play_full succeeds
+ canberraFile.forget();
+ }
+ ca_proplist_destroy(p);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSound::Beep()
+{
+ ::gdk_beep();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSound::Play(nsIURL *aURL)
+{
+ if (!mInited)
+ Init();
+
+ if (!libcanberra)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ bool isFile;
+ nsresult rv = aURL->SchemeIs("file", &isFile);
+ if (NS_SUCCEEDED(rv) && isFile) {
+ ca_context* ctx = ca_context_get_default();
+ if (!ctx) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsAutoCString spec;
+ rv = aURL->GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ gchar *path = g_filename_from_uri(spec.get(), nullptr, nullptr);
+ if (!path) {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+
+ ca_context_play(ctx, 0, "media.filename", path, nullptr);
+ g_free(path);
+ } else {
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(getter_AddRefs(loader),
+ aURL,
+ this, // aObserver
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId)
+{
+ if (!mInited)
+ Init();
+
+ if (!libcanberra)
+ return NS_OK;
+
+ // Do we even want alert sounds?
+ GtkSettings* settings = gtk_settings_get_default();
+
+ if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
+ "gtk-enable-event-sounds")) {
+ gboolean enable_sounds = TRUE;
+ g_object_get(settings, "gtk-enable-event-sounds", &enable_sounds, nullptr);
+
+ if (!enable_sounds) {
+ return NS_OK;
+ }
+ }
+
+ ca_context* ctx = ca_context_get_default();
+ if (!ctx) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ switch (aEventId) {
+ case EVENT_ALERT_DIALOG_OPEN:
+ ca_context_play(ctx, 0, "event.id", "dialog-warning", nullptr);
+ break;
+ case EVENT_CONFIRM_DIALOG_OPEN:
+ ca_context_play(ctx, 0, "event.id", "dialog-question", nullptr);
+ break;
+ case EVENT_NEW_MAIL_RECEIVED:
+ ca_context_play(ctx, 0, "event.id", "message-new-email", nullptr);
+ break;
+ case EVENT_MENU_EXECUTE:
+ ca_context_play(ctx, 0, "event.id", "menu-click", nullptr);
+ break;
+ case EVENT_MENU_POPUP:
+ ca_context_play(ctx, 0, "event.id", "menu-popup", nullptr);
+ break;
+ }
+ 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 <nsIURI> fileURI;
+
+ // create a nsIFile and then a nsIFileURL from that
+ nsCOMPtr <nsIFile> 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<nsIFileURL> fileURL = do_QueryInterface(fileURI,&rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = Play(fileURL);
+
+ return rv;
+}
diff --git a/widget/gtk/nsSound.h b/widget/gtk/nsSound.h
new file mode 100644
index 000000000..0039b8556
--- /dev/null
+++ b/widget/gtk/nsSound.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 __nsSound_h__
+#define __nsSound_h__
+
+#include "nsISound.h"
+#include "nsIStreamLoader.h"
+
+#include <gtk/gtk.h>
+
+class nsSound : public nsISound,
+ public nsIStreamLoaderObserver
+{
+public:
+ nsSound();
+
+ static void Shutdown();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISOUND
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+private:
+ virtual ~nsSound();
+
+ bool mInited;
+
+};
+
+#endif /* __nsSound_h__ */
diff --git a/widget/gtk/nsToolkit.cpp b/widget/gtk/nsToolkit.cpp
new file mode 100644
index 000000000..41d47ff96
--- /dev/null
+++ b/widget/gtk/nsToolkit.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "nscore.h" // needed for 'nullptr'
+#include "nsGTKToolkit.h"
+
+nsGTKToolkit* nsGTKToolkit::gToolkit = nullptr;
+
+//-------------------------------------------------------------------------
+//
+// constructor
+//
+//-------------------------------------------------------------------------
+nsGTKToolkit::nsGTKToolkit()
+ : mFocusTimestamp(0)
+{
+}
+
+//-------------------------------------------------------------------------------
+// Return the toolkit. If a toolkit does not yet exist, then one will be created.
+//-------------------------------------------------------------------------------
+// static
+nsGTKToolkit* nsGTKToolkit::GetToolkit()
+{
+ if (!gToolkit) {
+ gToolkit = new nsGTKToolkit();
+ }
+
+ return gToolkit;
+}
diff --git a/widget/gtk/nsWidgetFactory.cpp b/widget/gtk/nsWidgetFactory.cpp
new file mode 100644
index 000000000..7e4274377
--- /dev/null
+++ b/widget/gtk/nsWidgetFactory.cpp
@@ -0,0 +1,331 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/WidgetUtils.h"
+#include "NativeKeyBindings.h"
+#include "nsWidgetsCID.h"
+#include "nsAppShell.h"
+#include "nsAppShellSingleton.h"
+#include "nsBaseWidget.h"
+#include "nsGtkKeyUtils.h"
+#include "nsLookAndFeel.h"
+#include "nsWindow.h"
+#include "nsTransferable.h"
+#include "nsHTMLFormatConverter.h"
+#ifdef MOZ_X11
+#include "nsClipboardHelper.h"
+#include "nsClipboard.h"
+#include "nsDragService.h"
+#endif
+#if (MOZ_WIDGET_GTK == 3)
+#include "nsApplicationChooser.h"
+#endif
+#include "nsColorPicker.h"
+#include "nsFilePicker.h"
+#include "nsSound.h"
+#include "nsBidiKeyboard.h"
+#include "nsScreenManagerGtk.h"
+#include "nsGTKToolkit.h"
+#include "WakeLockListener.h"
+
+#ifdef NS_PRINTING
+#include "nsPrintOptionsGTK.h"
+#include "nsPrintSession.h"
+#include "nsDeviceContextSpecG.h"
+#endif
+
+#include "mozilla/Preferences.h"
+
+#include "nsImageToPixbuf.h"
+#include "nsPrintDialogGTK.h"
+
+#if defined(MOZ_X11)
+#include "nsIdleServiceGTK.h"
+#include "GfxInfoX11.h"
+#endif
+
+#include "nsNativeThemeGTK.h"
+
+#include "nsIComponentRegistrar.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/gfx/2D.h"
+#include <gtk/gtk.h>
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+/* from nsFilePicker.js */
+#define XULFILEPICKER_CID \
+ { 0x54ae32f8, 0x1dd2, 0x11b2, \
+ { 0xa2, 0x09, 0xdf, 0x7c, 0x50, 0x53, 0x70, 0xf8} }
+static NS_DEFINE_CID(kXULFilePickerCID, XULFILEPICKER_CID);
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindow)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsChildWindow)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsBidiKeyboard)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter)
+#ifdef MOZ_X11
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceGTK, nsIdleServiceGTK::GetInstance)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardHelper)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsClipboard, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDragService)
+#endif
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSound)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsScreenManagerGtk)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsImageToPixbuf)
+
+
+// from nsWindow.cpp
+extern bool gDisableNativeTheme;
+
+static nsresult
+nsNativeThemeGTKConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ nsresult rv;
+ nsNativeThemeGTK * inst;
+
+ if (gDisableNativeTheme)
+ return NS_ERROR_NO_INTERFACE;
+
+ *aResult = nullptr;
+ if (nullptr != aOuter) {
+ rv = NS_ERROR_NO_AGGREGATION;
+ return rv;
+ }
+
+ inst = new nsNativeThemeGTK();
+ if (nullptr == inst) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ return rv;
+ }
+ NS_ADDREF(inst);
+ rv = inst->QueryInterface(aIID, aResult);
+ NS_RELEASE(inst);
+
+ return rv;
+}
+
+#if defined(MOZ_X11)
+namespace mozilla {
+namespace widget {
+// This constructor should really be shared with all platforms.
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init)
+}
+}
+#endif
+
+#ifdef NS_PRINTING
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecGTK)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsGTK, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsPrinterEnumeratorGTK)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSession, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintDialogServiceGTK, Init)
+#endif
+
+static nsresult
+nsFilePickerConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ *aResult = nullptr;
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ bool allowPlatformPicker =
+ Preferences::GetBool("ui.allow_platform_file_picker", true);
+
+ nsCOMPtr<nsIFilePicker> picker;
+ if (allowPlatformPicker) {
+ picker = new nsFilePicker;
+ } else {
+ picker = do_CreateInstance(kXULFilePickerCID);
+ }
+
+ if (!picker) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return picker->QueryInterface(aIID, aResult);
+}
+
+#if (MOZ_WIDGET_GTK == 3)
+static nsresult
+nsApplicationChooserConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ *aResult = nullptr;
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ nsCOMPtr<nsIApplicationChooser> chooser = new nsApplicationChooser;
+
+ if (!chooser) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return chooser->QueryInterface(aIID, aResult);
+}
+#endif
+
+static nsresult
+nsColorPickerConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ *aResult = nullptr;
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ nsCOMPtr<nsIColorPicker> picker = new nsColorPicker;
+
+ if (!picker) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return picker->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);
+#if (MOZ_WIDGET_GTK == 3)
+NS_DEFINE_NAMED_CID(NS_APPLICATIONCHOOSER_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_SOUND_CID);
+NS_DEFINE_NAMED_CID(NS_TRANSFERABLE_CID);
+#ifdef MOZ_X11
+NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARDHELPER_CID);
+NS_DEFINE_NAMED_CID(NS_DRAGSERVICE_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERTER_CID);
+NS_DEFINE_NAMED_CID(NS_BIDIKEYBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_THEMERENDERER_CID);
+#ifdef NS_PRINTING
+NS_DEFINE_NAMED_CID(NS_PRINTSETTINGSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTER_ENUMERATOR_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTSESSION_CID);
+NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SPEC_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTDIALOGSERVICE_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_IMAGE_TO_PIXBUF_CID);
+#if defined(MOZ_X11)
+NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
+#endif
+
+
+static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
+ { &kNS_WINDOW_CID, false, nullptr, nsWindowConstructor },
+ { &kNS_CHILD_CID, false, nullptr, nsChildWindowConstructor },
+ { &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 },
+#if (MOZ_WIDGET_GTK == 3)
+ { &kNS_APPLICATIONCHOOSER_CID, false, nullptr, nsApplicationChooserConstructor, Module::MAIN_PROCESS_ONLY },
+#endif
+ { &kNS_SOUND_CID, false, nullptr, nsSoundConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_TRANSFERABLE_CID, false, nullptr, nsTransferableConstructor },
+#ifdef MOZ_X11
+ { &kNS_CLIPBOARD_CID, false, nullptr, nsClipboardConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_CLIPBOARDHELPER_CID, false, nullptr, nsClipboardHelperConstructor },
+ { &kNS_DRAGSERVICE_CID, false, nullptr, nsDragServiceConstructor, Module::MAIN_PROCESS_ONLY },
+#endif
+ { &kNS_HTMLFORMATCONVERTER_CID, false, nullptr, nsHTMLFormatConverterConstructor },
+ { &kNS_BIDIKEYBOARD_CID, false, nullptr, nsBidiKeyboardConstructor },
+ { &kNS_SCREENMANAGER_CID, false, nullptr, nsScreenManagerGtkConstructor,
+ Module::MAIN_PROCESS_ONLY },
+ { &kNS_THEMERENDERER_CID, false, nullptr, nsNativeThemeGTKConstructor },
+#ifdef NS_PRINTING
+ { &kNS_PRINTSETTINGSSERVICE_CID, false, nullptr, nsPrintOptionsGTKConstructor },
+ { &kNS_PRINTER_ENUMERATOR_CID, false, nullptr, nsPrinterEnumeratorGTKConstructor },
+ { &kNS_PRINTSESSION_CID, false, nullptr, nsPrintSessionConstructor },
+ { &kNS_DEVICE_CONTEXT_SPEC_CID, false, nullptr, nsDeviceContextSpecGTKConstructor },
+ { &kNS_PRINTDIALOGSERVICE_CID, false, nullptr, nsPrintDialogServiceGTKConstructor },
+#endif
+ { &kNS_IMAGE_TO_PIXBUF_CID, false, nullptr, nsImageToPixbufConstructor },
+#if defined(MOZ_X11)
+ { &kNS_IDLE_SERVICE_CID, false, nullptr, nsIdleServiceGTKConstructor },
+ { &kNS_GFXINFO_CID, false, nullptr, mozilla::widget::GfxInfoConstructor },
+#endif
+ { nullptr }
+};
+
+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 },
+#if (MOZ_WIDGET_GTK == 3)
+ { "@mozilla.org/applicationchooser;1", &kNS_APPLICATIONCHOOSER_CID, Module::MAIN_PROCESS_ONLY },
+#endif
+ { "@mozilla.org/sound;1", &kNS_SOUND_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/transferable;1", &kNS_TRANSFERABLE_CID },
+#ifdef MOZ_X11
+ { "@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID },
+ { "@mozilla.org/widget/dragservice;1", &kNS_DRAGSERVICE_CID, Module::MAIN_PROCESS_ONLY },
+#endif
+ { "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID },
+ { "@mozilla.org/widget/bidikeyboard;1", &kNS_BIDIKEYBOARD_CID,
+ Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID,
+ Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/chrome/chrome-native-theme;1", &kNS_THEMERENDERER_CID },
+#ifdef NS_PRINTING
+ { "@mozilla.org/gfx/printsettings-service;1", &kNS_PRINTSETTINGSSERVICE_CID },
+ { "@mozilla.org/gfx/printerenumerator;1", &kNS_PRINTER_ENUMERATOR_CID },
+ { "@mozilla.org/gfx/printsession;1", &kNS_PRINTSESSION_CID },
+ { "@mozilla.org/gfx/devicecontextspec;1", &kNS_DEVICE_CONTEXT_SPEC_CID },
+ { NS_PRINTDIALOGSERVICE_CONTRACTID, &kNS_PRINTDIALOGSERVICE_CID },
+#endif
+ { "@mozilla.org/widget/image-to-gdk-pixbuf;1", &kNS_IMAGE_TO_PIXBUF_CID },
+#if defined(MOZ_X11)
+ { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID },
+ { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
+#endif
+ { nullptr }
+};
+
+static void
+nsWidgetGtk2ModuleDtor()
+{
+ // Shutdown all XP level widget classes.
+ WidgetUtils::Shutdown();
+
+ NativeKeyBindings::Shutdown();
+ nsLookAndFeel::Shutdown();
+ nsFilePicker::Shutdown();
+ nsSound::Shutdown();
+ nsWindow::ReleaseGlobals();
+ KeymapWrapper::Shutdown();
+ nsGTKToolkit::Shutdown();
+ nsAppShellShutdown();
+#ifdef MOZ_ENABLE_DBUS
+ WakeLockListener::Shutdown();
+#endif
+}
+
+static const mozilla::Module kWidgetModule = {
+ mozilla::Module::kVersion,
+ kWidgetCIDs,
+ kWidgetContracts,
+ nullptr,
+ nullptr,
+ nsAppShellInit,
+ nsWidgetGtk2ModuleDtor,
+ Module::ALLOW_IN_GPU_PROCESS
+};
+
+NSMODULE_DEFN(nsWidgetGtk2Module) = &kWidgetModule;
diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp
new file mode 100644
index 000000000..d97b35002
--- /dev/null
+++ b/widget/gtk/nsWindow.cpp
@@ -0,0 +1,7036 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "nsWindow.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include <algorithm>
+
+#include "GeckoProfiler.h"
+
+#include "prlink.h"
+#include "nsGTKToolkit.h"
+#include "nsIRollupListener.h"
+#include "nsIDOMNode.h"
+
+#include "nsWidgetsCID.h"
+#include "nsDragService.h"
+#include "nsIWidgetListener.h"
+#include "nsIScreenManager.h"
+#include "SystemTimeConverter.h"
+
+#include "nsGtkKeyUtils.h"
+#include "nsGtkCursors.h"
+#include "nsScreenGtk.h"
+
+#include <gtk/gtk.h>
+#if (MOZ_WIDGET_GTK == 3)
+#include <gtk/gtkx.h>
+#endif
+#ifdef MOZ_X11
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/XShm.h>
+#include <X11/extensions/shape.h>
+#if (MOZ_WIDGET_GTK == 3)
+#include <gdk/gdkkeysyms-compat.h>
+#endif
+
+#if (MOZ_WIDGET_GTK == 2)
+#include "gtk2xtbin.h"
+#endif
+#endif /* MOZ_X11 */
+#include <gdk/gdkkeysyms.h>
+#if (MOZ_WIDGET_GTK == 2)
+#include <gtk/gtkprivate.h>
+#endif
+
+#include "nsGkAtoms.h"
+
+#ifdef MOZ_ENABLE_STARTUP_NOTIFICATION
+#define SN_API_NOT_YET_FROZEN
+#include <startup-notification-1.0/libsn/sn.h>
+#endif
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Preferences.h"
+#include "nsIPrefService.h"
+#include "nsIGConfService.h"
+#include "nsIServiceManager.h"
+#include "nsIStringBundle.h"
+#include "nsGfxCIID.h"
+#include "nsGtkUtils.h"
+#include "nsIObserverService.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "nsIIdleServiceInternal.h"
+#include "nsIPropertyBag2.h"
+#include "GLContext.h"
+#include "gfx2DGlue.h"
+#include "nsPluginNativeWindowGtk.h"
+
+#ifdef ACCESSIBILITY
+#include "mozilla/a11y/Accessible.h"
+#include "mozilla/a11y/Platform.h"
+#include "nsAccessibilityService.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+#endif
+
+/* For SetIcon */
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsXPIDLString.h"
+#include "nsIFile.h"
+
+/* SetCursor(imgIContainer*) */
+#include <gdk/gdk.h>
+#include <wchar.h>
+#include "imgIContainer.h"
+#include "nsGfxCIID.h"
+#include "nsImageToPixbuf.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "ClientLayerManager.h"
+
+#include "gfxPlatformGtk.h"
+#include "gfxContext.h"
+#include "gfxImageSurface.h"
+#include "gfxUtils.h"
+#include "Layers.h"
+#include "GLContextProvider.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
+
+#ifdef MOZ_X11
+#include "X11CompositorWidget.h"
+#include "gfxXlibSurface.h"
+#include "WindowSurfaceX11Image.h"
+#include "WindowSurfaceX11SHM.h"
+#include "WindowSurfaceXRender.h"
+#endif // MOZ_X11
+
+#include "nsShmImage.h"
+
+#include "nsIDOMWheelEvent.h"
+
+#include "NativeKeyBindings.h"
+
+#include <dlfcn.h>
+
+#include "mozilla/layers/APZCTreeManager.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+using namespace mozilla::layers;
+using mozilla::gl::GLContext;
+
+// Don't put more than this many rects in the dirty region, just fluff
+// out to the bounding-box if there are more
+#define MAX_RECTS_IN_REGION 100
+
+const gint kEvents = GDK_EXPOSURE_MASK | GDK_STRUCTURE_MASK |
+ GDK_VISIBILITY_NOTIFY_MASK |
+ GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+#if GTK_CHECK_VERSION(3,4,0)
+ GDK_SMOOTH_SCROLL_MASK |
+ GDK_TOUCH_MASK |
+#endif
+ GDK_SCROLL_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_PROPERTY_CHANGE_MASK;
+
+/* utility functions */
+static bool is_mouse_in_window(GdkWindow* aWindow,
+ gdouble aMouseX, gdouble aMouseY);
+static nsWindow *get_window_for_gtk_widget(GtkWidget *widget);
+static nsWindow *get_window_for_gdk_window(GdkWindow *window);
+static GtkWidget *get_gtk_widget_for_gdk_window(GdkWindow *window);
+static GdkCursor *get_gtk_cursor(nsCursor aCursor);
+
+static GdkWindow *get_inner_gdk_window (GdkWindow *aWindow,
+ gint x, gint y,
+ gint *retx, gint *rety);
+
+static inline bool is_context_menu_key(const WidgetKeyboardEvent& inKeyEvent);
+
+static int is_parent_ungrab_enter(GdkEventCrossing *aEvent);
+static int is_parent_grab_leave(GdkEventCrossing *aEvent);
+
+static void GetBrandName(nsXPIDLString& brandName);
+
+/* callbacks from widgets */
+#if (MOZ_WIDGET_GTK == 2)
+static gboolean expose_event_cb (GtkWidget *widget,
+ GdkEventExpose *event);
+#else
+static gboolean expose_event_cb (GtkWidget *widget,
+ cairo_t *rect);
+#endif
+static gboolean configure_event_cb (GtkWidget *widget,
+ GdkEventConfigure *event);
+static void container_unrealize_cb (GtkWidget *widget);
+static void size_allocate_cb (GtkWidget *widget,
+ GtkAllocation *allocation);
+static gboolean delete_event_cb (GtkWidget *widget,
+ GdkEventAny *event);
+static gboolean enter_notify_event_cb (GtkWidget *widget,
+ GdkEventCrossing *event);
+static gboolean leave_notify_event_cb (GtkWidget *widget,
+ GdkEventCrossing *event);
+static gboolean motion_notify_event_cb (GtkWidget *widget,
+ GdkEventMotion *event);
+static gboolean button_press_event_cb (GtkWidget *widget,
+ GdkEventButton *event);
+static gboolean button_release_event_cb (GtkWidget *widget,
+ GdkEventButton *event);
+static gboolean focus_in_event_cb (GtkWidget *widget,
+ GdkEventFocus *event);
+static gboolean focus_out_event_cb (GtkWidget *widget,
+ GdkEventFocus *event);
+static gboolean key_press_event_cb (GtkWidget *widget,
+ GdkEventKey *event);
+static gboolean key_release_event_cb (GtkWidget *widget,
+ GdkEventKey *event);
+static gboolean property_notify_event_cb (GtkWidget *widget,
+ GdkEventProperty *event);
+static gboolean scroll_event_cb (GtkWidget *widget,
+ GdkEventScroll *event);
+static gboolean visibility_notify_event_cb(GtkWidget *widget,
+ GdkEventVisibility *event);
+static void hierarchy_changed_cb (GtkWidget *widget,
+ GtkWidget *previous_toplevel);
+static gboolean window_state_event_cb (GtkWidget *widget,
+ GdkEventWindowState *event);
+static void theme_changed_cb (GtkSettings *settings,
+ GParamSpec *pspec,
+ nsWindow *data);
+static void check_resize_cb (GtkContainer* container,
+ gpointer user_data);
+
+#if (MOZ_WIDGET_GTK == 3)
+static void scale_changed_cb (GtkWidget* widget,
+ GParamSpec* aPSpec,
+ gpointer aPointer);
+#endif
+#if GTK_CHECK_VERSION(3,4,0)
+static gboolean touch_event_cb (GtkWidget* aWidget,
+ GdkEventTouch* aEvent);
+#endif
+static nsWindow* GetFirstNSWindowForGDKWindow (GdkWindow *aGdkWindow);
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+#ifdef MOZ_X11
+static GdkFilterReturn popup_take_focus_filter (GdkXEvent *gdk_xevent,
+ GdkEvent *event,
+ gpointer data);
+static GdkFilterReturn plugin_window_filter_func (GdkXEvent *gdk_xevent,
+ GdkEvent *event,
+ gpointer data);
+static GdkFilterReturn plugin_client_message_filter (GdkXEvent *xevent,
+ GdkEvent *event,
+ gpointer data);
+#endif /* MOZ_X11 */
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+static gboolean drag_motion_event_cb (GtkWidget *aWidget,
+ GdkDragContext *aDragContext,
+ gint aX,
+ gint aY,
+ guint aTime,
+ gpointer aData);
+static void drag_leave_event_cb (GtkWidget *aWidget,
+ GdkDragContext *aDragContext,
+ guint aTime,
+ gpointer aData);
+static gboolean drag_drop_event_cb (GtkWidget *aWidget,
+ GdkDragContext *aDragContext,
+ gint aX,
+ gint aY,
+ guint aTime,
+ gpointer aData);
+static void drag_data_received_event_cb(GtkWidget *aWidget,
+ GdkDragContext *aDragContext,
+ gint aX,
+ gint aY,
+ GtkSelectionData *aSelectionData,
+ guint aInfo,
+ guint32 aTime,
+ gpointer aData);
+
+/* initialization static functions */
+static nsresult initialize_prefs (void);
+
+static guint32 sLastUserInputTime = GDK_CURRENT_TIME;
+static guint32 sRetryGrabTime;
+
+static SystemTimeConverter<guint32>&
+TimeConverter() {
+ static SystemTimeConverter<guint32> sTimeConverterSingleton;
+ return sTimeConverterSingleton;
+}
+
+namespace mozilla {
+
+class CurrentX11TimeGetter
+{
+public:
+ explicit CurrentX11TimeGetter(GdkWindow* aWindow)
+ : mWindow(aWindow)
+ , mAsyncUpdateStart()
+ {
+ }
+
+ guint32 GetCurrentTime() const
+ {
+ return gdk_x11_get_server_time(mWindow);
+ }
+
+ void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow)
+ {
+ // Check for in-flight request
+ if (!mAsyncUpdateStart.IsNull()) {
+ return;
+ }
+ mAsyncUpdateStart = aNow;
+
+ Display* xDisplay = GDK_WINDOW_XDISPLAY(mWindow);
+ Window xWindow = GDK_WINDOW_XID(mWindow);
+ unsigned char c = 'a';
+ Atom timeStampPropAtom = TimeStampPropAtom();
+ XChangeProperty(xDisplay, xWindow, timeStampPropAtom,
+ timeStampPropAtom, 8, PropModeReplace, &c, 1);
+ XFlush(xDisplay);
+ }
+
+ gboolean PropertyNotifyHandler(GtkWidget* aWidget,
+ GdkEventProperty* aEvent)
+ {
+ if (aEvent->atom !=
+ gdk_x11_xatom_to_atom(TimeStampPropAtom())) {
+ return FALSE;
+ }
+
+ guint32 eventTime = aEvent->time;
+ TimeStamp lowerBound = mAsyncUpdateStart;
+
+ TimeConverter().CompensateForBackwardsSkew(eventTime, lowerBound);
+ mAsyncUpdateStart = TimeStamp();
+ return TRUE;
+ }
+
+private:
+ static Atom TimeStampPropAtom() {
+ return gdk_x11_get_xatom_by_name_for_display(
+ gdk_display_get_default(), "GDK_TIMESTAMP_PROP");
+ }
+
+ // This is safe because this class is stored as a member of mWindow and
+ // won't outlive it.
+ GdkWindow* mWindow;
+ TimeStamp mAsyncUpdateStart;
+};
+
+} // namespace mozilla
+
+static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID);
+
+// The window from which the focus manager asks us to dispatch key events.
+static nsWindow *gFocusWindow = nullptr;
+static bool gBlockActivateEvent = false;
+static bool gGlobalsInitialized = false;
+static bool gRaiseWindows = true;
+static nsWindow *gPluginFocusWindow = nullptr;
+
+#if GTK_CHECK_VERSION(3,4,0)
+static uint32_t gLastTouchID = 0;
+#endif
+
+#define NS_WINDOW_TITLE_MAX_LENGTH 4095
+
+// If after selecting profile window, the startup fail, please refer to
+// http://bugzilla.gnome.org/show_bug.cgi?id=88940
+
+// needed for imgIContainer cursors
+// GdkDisplay* was added in 2.2
+typedef struct _GdkDisplay GdkDisplay;
+
+#define kWindowPositionSlop 20
+
+// cursor cache
+static GdkCursor *gCursorCache[eCursorCount];
+
+static GtkWidget *gInvisibleContainer = nullptr;
+
+// Sometimes this actually also includes the state of the modifier keys, but
+// only the button state bits are used.
+static guint gButtonState;
+
+static inline int32_t
+GetBitmapStride(int32_t width)
+{
+#if defined(MOZ_X11) || (MOZ_WIDGET_GTK == 2)
+ return (width+7)/8;
+#else
+ return cairo_format_stride_for_width(CAIRO_FORMAT_A1, width);
+#endif
+}
+
+static inline bool TimestampIsNewerThan(guint32 a, guint32 b)
+{
+ // Timestamps are just the least significant bits of a monotonically
+ // increasing function, and so the use of unsigned overflow arithmetic.
+ return a - b <= G_MAXUINT32/2;
+}
+
+static void
+UpdateLastInputEventTime(void *aGdkEvent)
+{
+ nsCOMPtr<nsIIdleServiceInternal> idleService =
+ do_GetService("@mozilla.org/widget/idleservice;1");
+ if (idleService) {
+ idleService->ResetIdleTimeOut(0);
+ }
+
+ guint timestamp = gdk_event_get_time(static_cast<GdkEvent*>(aGdkEvent));
+ if (timestamp == GDK_CURRENT_TIME)
+ return;
+
+ sLastUserInputTime = timestamp;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget)
+
+nsWindow::nsWindow()
+{
+ mIsTopLevel = false;
+ mIsDestroyed = false;
+ mListenForResizes = false;
+ mNeedsDispatchResized = false;
+ mIsShown = false;
+ mNeedsShow = false;
+ mEnabled = true;
+ mCreated = false;
+#if GTK_CHECK_VERSION(3,4,0)
+ mHandleTouchEvent = false;
+#endif
+ mIsDragPopup = false;
+ mIsX11Display = GDK_IS_X11_DISPLAY(gdk_display_get_default());
+
+ mContainer = nullptr;
+ mGdkWindow = nullptr;
+ mShell = nullptr;
+ mPluginNativeWindow = nullptr;
+ mHasMappedToplevel = false;
+ mIsFullyObscured = false;
+ mRetryPointerGrab = false;
+ mWindowType = eWindowType_child;
+ mSizeState = nsSizeMode_Normal;
+ mLastSizeMode = nsSizeMode_Normal;
+ mSizeConstraints.mMaxSize = GetSafeWindowSize(mSizeConstraints.mMaxSize);
+
+#ifdef MOZ_X11
+ mOldFocusWindow = 0;
+
+ mXDisplay = nullptr;
+ mXWindow = X11None;
+ mXVisual = nullptr;
+ mXDepth = 0;
+#endif /* MOZ_X11 */
+ mPluginType = PluginType_NONE;
+
+ if (!gGlobalsInitialized) {
+ gGlobalsInitialized = true;
+
+ // It's OK if either of these fail, but it may not be one day.
+ initialize_prefs();
+ }
+
+ mLastMotionPressure = 0;
+
+#ifdef ACCESSIBILITY
+ mRootAccessible = nullptr;
+#endif
+
+ mIsTransparent = false;
+ mTransparencyBitmap = nullptr;
+
+ mTransparencyBitmapWidth = 0;
+ mTransparencyBitmapHeight = 0;
+
+#if GTK_CHECK_VERSION(3,4,0)
+ mLastScrollEventTime = GDK_CURRENT_TIME;
+#endif
+ mPendingConfigures = 0;
+}
+
+nsWindow::~nsWindow()
+{
+ LOG(("nsWindow::~nsWindow() [%p]\n", (void *)this));
+
+ delete[] mTransparencyBitmap;
+ mTransparencyBitmap = nullptr;
+
+ Destroy();
+}
+
+/* static */ void
+nsWindow::ReleaseGlobals()
+{
+ for (uint32_t i = 0; i < ArrayLength(gCursorCache); ++i) {
+ if (gCursorCache[i]) {
+#if (MOZ_WIDGET_GTK == 3)
+ g_object_unref(gCursorCache[i]);
+#else
+ gdk_cursor_unref(gCursorCache[i]);
+#endif
+ gCursorCache[i] = nullptr;
+ }
+ }
+}
+
+void
+nsWindow::CommonCreate(nsIWidget *aParent, bool aListenForResizes)
+{
+ mParent = aParent;
+ mListenForResizes = aListenForResizes;
+ mCreated = true;
+}
+
+void
+nsWindow::DispatchActivateEvent(void)
+{
+ NS_ASSERTION(mContainer || mIsDestroyed,
+ "DispatchActivateEvent only intended for container windows");
+
+#ifdef ACCESSIBILITY
+ DispatchActivateEventAccessible();
+#endif //ACCESSIBILITY
+
+ if (mWidgetListener)
+ mWidgetListener->WindowActivated();
+}
+
+void
+nsWindow::DispatchDeactivateEvent(void)
+{
+ if (mWidgetListener)
+ mWidgetListener->WindowDeactivated();
+
+#ifdef ACCESSIBILITY
+ DispatchDeactivateEventAccessible();
+#endif //ACCESSIBILITY
+}
+
+void
+nsWindow::DispatchResized()
+{
+ mNeedsDispatchResized = false;
+ if (mWidgetListener) {
+ mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
+ }
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->WindowResized(this,
+ mBounds.width, mBounds.height);
+ }
+}
+
+void
+nsWindow::MaybeDispatchResized()
+{
+ if (mNeedsDispatchResized && !mIsDestroyed) {
+ DispatchResized();
+ }
+}
+
+nsIWidgetListener*
+nsWindow::GetListener()
+{
+ return mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener;
+}
+
+nsresult
+nsWindow::DispatchEvent(WidgetGUIEvent* aEvent, nsEventStatus& aStatus)
+{
+#ifdef DEBUG
+ debug_DumpEvent(stdout, aEvent->mWidget, aEvent,
+ "something", 0);
+#endif
+ aStatus = nsEventStatus_eIgnore;
+ nsIWidgetListener* listener = GetListener();
+ if (listener) {
+ aStatus = listener->HandleEvent(aEvent, mUseAttachedEvents);
+ }
+
+ return NS_OK;
+}
+
+void
+nsWindow::OnDestroy(void)
+{
+ if (mOnDestroyCalled)
+ return;
+
+ mOnDestroyCalled = true;
+
+ // Prevent deletion.
+ nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
+
+ // release references to children, device context, toolkit + app shell
+ nsBaseWidget::OnDestroy();
+
+ // Remove association between this object and its parent and siblings.
+ nsBaseWidget::Destroy();
+ mParent = nullptr;
+
+ NotifyWindowDestroyed();
+}
+
+bool
+nsWindow::AreBoundsSane(void)
+{
+ if (mBounds.width > 0 && mBounds.height > 0)
+ return true;
+
+ return false;
+}
+
+static GtkWidget*
+EnsureInvisibleContainer()
+{
+ if (!gInvisibleContainer) {
+ // GtkWidgets need to be anchored to a GtkWindow to be realized (to
+ // have a window). Using GTK_WINDOW_POPUP rather than
+ // GTK_WINDOW_TOPLEVEL in the hope that POPUP results in less
+ // initialization and window manager interaction.
+ GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
+ gInvisibleContainer = moz_container_new();
+ gtk_container_add(GTK_CONTAINER(window), gInvisibleContainer);
+ gtk_widget_realize(gInvisibleContainer);
+
+ }
+ return gInvisibleContainer;
+}
+
+static void
+CheckDestroyInvisibleContainer()
+{
+ NS_PRECONDITION(gInvisibleContainer, "oh, no");
+
+ if (!gdk_window_peek_children(gtk_widget_get_window(gInvisibleContainer))) {
+ // No children, so not in use.
+ // Make sure to destroy the GtkWindow also.
+ gtk_widget_destroy(gtk_widget_get_parent(gInvisibleContainer));
+ gInvisibleContainer = nullptr;
+ }
+}
+
+// Change the containing GtkWidget on a sub-hierarchy of GdkWindows belonging
+// to aOldWidget and rooted at aWindow, and reparent any child GtkWidgets of
+// the GdkWindow hierarchy to aNewWidget.
+static void
+SetWidgetForHierarchy(GdkWindow *aWindow,
+ GtkWidget *aOldWidget,
+ GtkWidget *aNewWidget)
+{
+ gpointer data;
+ gdk_window_get_user_data(aWindow, &data);
+
+ if (data != aOldWidget) {
+ if (!GTK_IS_WIDGET(data))
+ return;
+
+ GtkWidget* widget = static_cast<GtkWidget*>(data);
+ if (gtk_widget_get_parent(widget) != aOldWidget)
+ return;
+
+ // This window belongs to a child widget, which will no longer be a
+ // child of aOldWidget.
+ gtk_widget_reparent(widget, aNewWidget);
+
+ return;
+ }
+
+ GList *children = gdk_window_get_children(aWindow);
+ for(GList *list = children; list; list = list->next) {
+ SetWidgetForHierarchy(GDK_WINDOW(list->data), aOldWidget, aNewWidget);
+ }
+ g_list_free(children);
+
+ gdk_window_set_user_data(aWindow, aNewWidget);
+}
+
+// Walk the list of child windows and call destroy on them.
+void
+nsWindow::DestroyChildWindows()
+{
+ if (!mGdkWindow)
+ return;
+
+ while (GList *children = gdk_window_peek_children(mGdkWindow)) {
+ GdkWindow *child = GDK_WINDOW(children->data);
+ nsWindow *kid = get_window_for_gdk_window(child);
+ if (kid) {
+ kid->Destroy();
+ } else {
+ // This child is not an nsWindow.
+ // Destroy the child GtkWidget.
+ gpointer data;
+ gdk_window_get_user_data(child, &data);
+ if (GTK_IS_WIDGET(data)) {
+ gtk_widget_destroy(static_cast<GtkWidget*>(data));
+ }
+ }
+ }
+}
+
+void
+nsWindow::Destroy()
+{
+ if (mIsDestroyed || !mCreated)
+ return;
+
+ LOG(("nsWindow::Destroy [%p]\n", (void *)this));
+ mIsDestroyed = true;
+ mCreated = false;
+
+ /** Need to clean our LayerManager up while still alive */
+ if (mLayerManager) {
+ mLayerManager->Destroy();
+ }
+ mLayerManager = nullptr;
+
+ // It is safe to call DestroyeCompositor several times (here and
+ // in the parent class) since it will take effect only once.
+ // The reason we call it here is because on gtk platforms we need
+ // to destroy the compositor before we destroy the gdk window (which
+ // destroys the the gl context attached to it).
+ DestroyCompositor();
+
+#ifdef MOZ_X11
+ // Ensure any resources assigned to the window get cleaned up first
+ // to avoid double-freeing.
+ mSurfaceProvider.CleanupResources();
+#endif
+
+ ClearCachedResources();
+
+ g_signal_handlers_disconnect_by_func(gtk_settings_get_default(),
+ FuncToGpointer(theme_changed_cb),
+ this);
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ if (rollupListener) {
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (static_cast<nsIWidget *>(this) == rollupWidget) {
+ rollupListener->Rollup(0, false, nullptr, nullptr);
+ }
+ }
+
+ // dragService will be null after shutdown of the service manager.
+ nsDragService *dragService = nsDragService::GetInstance();
+ if (dragService && this == dragService->GetMostRecentDestWindow()) {
+ dragService->ScheduleLeaveEvent();
+ }
+
+ NativeShow(false);
+
+ if (mIMContext) {
+ mIMContext->OnDestroyWindow(this);
+ }
+
+ // make sure that we remove ourself as the focus window
+ if (gFocusWindow == this) {
+ LOGFOCUS(("automatically losing focus...\n"));
+ gFocusWindow = nullptr;
+ }
+
+#if (MOZ_WIDGET_GTK == 2) && defined(MOZ_X11)
+ // make sure that we remove ourself as the plugin focus window
+ if (gPluginFocusWindow == this) {
+ gPluginFocusWindow->LoseNonXEmbedPluginFocus();
+ }
+#endif /* MOZ_X11 && MOZ_WIDGET_GTK == 2 && defined(MOZ_X11) */
+
+ GtkWidget *owningWidget = GetMozContainerWidget();
+ if (mShell) {
+ gtk_widget_destroy(mShell);
+ mShell = nullptr;
+ mContainer = nullptr;
+ MOZ_ASSERT(!mGdkWindow,
+ "mGdkWindow should be NULL when mContainer is destroyed");
+ }
+ else if (mContainer) {
+ gtk_widget_destroy(GTK_WIDGET(mContainer));
+ mContainer = nullptr;
+ MOZ_ASSERT(!mGdkWindow,
+ "mGdkWindow should be NULL when mContainer is destroyed");
+ }
+ else if (mGdkWindow) {
+ // Destroy child windows to ensure that their mThebesSurfaces are
+ // released and to remove references from GdkWindows back to their
+ // container widget. (OnContainerUnrealize() does this when the
+ // MozContainer widget is destroyed.)
+ DestroyChildWindows();
+
+ gdk_window_set_user_data(mGdkWindow, nullptr);
+ g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", nullptr);
+ gdk_window_destroy(mGdkWindow);
+ mGdkWindow = nullptr;
+ }
+
+ if (gInvisibleContainer && owningWidget == gInvisibleContainer) {
+ CheckDestroyInvisibleContainer();
+ }
+
+#ifdef ACCESSIBILITY
+ if (mRootAccessible) {
+ mRootAccessible = nullptr;
+ }
+#endif
+
+ // Save until last because OnDestroy() may cause us to be deleted.
+ OnDestroy();
+}
+
+nsIWidget *
+nsWindow::GetParent(void)
+{
+ return mParent;
+}
+
+float
+nsWindow::GetDPI()
+{
+ GdkScreen *screen = gdk_display_get_default_screen(gdk_display_get_default());
+ double heightInches = gdk_screen_get_height_mm(screen)/MM_PER_INCH_FLOAT;
+ if (heightInches < 0.25) {
+ // Something's broken, but we'd better not crash.
+ return 96.0f;
+ }
+ return float(gdk_screen_get_height(screen)/heightInches);
+}
+
+double
+nsWindow::GetDefaultScaleInternal()
+{
+ return GdkScaleFactor() * gfxPlatformGtk::GetDPIScale();
+}
+
+NS_IMETHODIMP
+nsWindow::SetParent(nsIWidget *aNewParent)
+{
+ if (mContainer || !mGdkWindow) {
+ NS_NOTREACHED("nsWindow::SetParent called illegally");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
+ if (mParent) {
+ mParent->RemoveChild(this);
+ }
+
+ mParent = aNewParent;
+
+ GtkWidget* oldContainer = GetMozContainerWidget();
+ if (!oldContainer) {
+ // The GdkWindows have been destroyed so there is nothing else to
+ // reparent.
+ MOZ_ASSERT(gdk_window_is_destroyed(mGdkWindow),
+ "live GdkWindow with no widget");
+ return NS_OK;
+ }
+
+ if (aNewParent) {
+ aNewParent->AddChild(this);
+ ReparentNativeWidget(aNewParent);
+ } else {
+ // aNewParent is nullptr, but reparent to a hidden window to avoid
+ // destroying the GdkWindow and its descendants.
+ // An invisible container widget is needed to hold descendant
+ // GtkWidgets.
+ GtkWidget* newContainer = EnsureInvisibleContainer();
+ GdkWindow* newParentWindow = gtk_widget_get_window(newContainer);
+ ReparentNativeWidgetInternal(aNewParent, newContainer, newParentWindow,
+ oldContainer);
+ }
+ return NS_OK;
+}
+
+bool
+nsWindow::WidgetTypeSupportsAcceleration()
+{
+ return !IsSmallPopup();
+}
+
+void
+nsWindow::ReparentNativeWidget(nsIWidget* aNewParent)
+{
+ NS_PRECONDITION(aNewParent, "");
+ NS_ASSERTION(!mIsDestroyed, "");
+ NS_ASSERTION(!static_cast<nsWindow*>(aNewParent)->mIsDestroyed, "");
+
+ GtkWidget* oldContainer = GetMozContainerWidget();
+ if (!oldContainer) {
+ // The GdkWindows have been destroyed so there is nothing else to
+ // reparent.
+ MOZ_ASSERT(gdk_window_is_destroyed(mGdkWindow),
+ "live GdkWindow with no widget");
+ return;
+ }
+ MOZ_ASSERT(!gdk_window_is_destroyed(mGdkWindow),
+ "destroyed GdkWindow with widget");
+
+ nsWindow* newParent = static_cast<nsWindow*>(aNewParent);
+ GdkWindow* newParentWindow = newParent->mGdkWindow;
+ GtkWidget* newContainer = newParent->GetMozContainerWidget();
+ GtkWindow* shell = GTK_WINDOW(mShell);
+
+ if (shell && gtk_window_get_transient_for(shell)) {
+ GtkWindow* topLevelParent =
+ GTK_WINDOW(gtk_widget_get_toplevel(newContainer));
+ gtk_window_set_transient_for(shell, topLevelParent);
+ }
+
+ ReparentNativeWidgetInternal(aNewParent, newContainer, newParentWindow,
+ oldContainer);
+}
+
+void
+nsWindow::ReparentNativeWidgetInternal(nsIWidget* aNewParent,
+ GtkWidget* aNewContainer,
+ GdkWindow* aNewParentWindow,
+ GtkWidget* aOldContainer)
+{
+ if (!aNewContainer) {
+ // The new parent GdkWindow has been destroyed.
+ MOZ_ASSERT(!aNewParentWindow ||
+ gdk_window_is_destroyed(aNewParentWindow),
+ "live GdkWindow with no widget");
+ Destroy();
+ } else {
+ if (aNewContainer != aOldContainer) {
+ MOZ_ASSERT(!gdk_window_is_destroyed(aNewParentWindow),
+ "destroyed GdkWindow with widget");
+ SetWidgetForHierarchy(mGdkWindow, aOldContainer, aNewContainer);
+
+ if (aOldContainer == gInvisibleContainer) {
+ CheckDestroyInvisibleContainer();
+ }
+ }
+
+ if (!mIsTopLevel) {
+ gdk_window_reparent(mGdkWindow, aNewParentWindow,
+ DevicePixelsToGdkCoordRoundDown(mBounds.x),
+ DevicePixelsToGdkCoordRoundDown(mBounds.y));
+ }
+ }
+
+ nsWindow* newParent = static_cast<nsWindow*>(aNewParent);
+ bool parentHasMappedToplevel =
+ newParent && newParent->mHasMappedToplevel;
+ if (mHasMappedToplevel != parentHasMappedToplevel) {
+ SetHasMappedToplevel(parentHasMappedToplevel);
+ }
+}
+
+void
+nsWindow::SetModal(bool aModal)
+{
+ LOG(("nsWindow::SetModal [%p] %d\n", (void *)this, aModal));
+ if (mIsDestroyed)
+ return;
+ if (!mIsTopLevel || !mShell)
+ return;
+ gtk_window_set_modal(GTK_WINDOW(mShell), aModal ? TRUE : FALSE);
+}
+
+// nsIWidget method, which means IsShown.
+bool
+nsWindow::IsVisible() const
+{
+ return mIsShown;
+}
+
+void
+nsWindow::RegisterTouchWindow()
+{
+#if GTK_CHECK_VERSION(3,4,0)
+ mHandleTouchEvent = true;
+ mTouches.Clear();
+#endif
+}
+
+void
+nsWindow::ConstrainPosition(bool aAllowSlop, int32_t *aX, int32_t *aY)
+{
+ if (!mIsTopLevel || !mShell)
+ return;
+
+ double dpiScale = GetDefaultScale().scale;
+
+ // we need to use the window size in logical screen pixels
+ int32_t logWidth = std::max(NSToIntRound(mBounds.width / dpiScale), 1);
+ int32_t logHeight = std::max(NSToIntRound(mBounds.height / dpiScale), 1);
+
+ /* get our playing field. use the current screen, or failing that
+ for any reason, use device caps for the default screen. */
+ nsCOMPtr<nsIScreen> screen;
+ nsCOMPtr<nsIScreenManager> screenmgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (screenmgr) {
+ screenmgr->ScreenForRect(*aX, *aY, logWidth, logHeight,
+ getter_AddRefs(screen));
+ }
+
+ // We don't have any screen so leave the coordinates as is
+ if (!screen)
+ return;
+
+ nsIntRect screenRect;
+ if (mSizeMode != nsSizeMode_Fullscreen) {
+ // For normalized windows, use the desktop work area.
+ screen->GetAvailRectDisplayPix(&screenRect.x, &screenRect.y,
+ &screenRect.width, &screenRect.height);
+ } else {
+ // For full screen windows, use the desktop.
+ screen->GetRectDisplayPix(&screenRect.x, &screenRect.y,
+ &screenRect.width, &screenRect.height);
+ }
+
+ if (aAllowSlop) {
+ if (*aX < screenRect.x - logWidth + kWindowPositionSlop)
+ *aX = screenRect.x - logWidth + kWindowPositionSlop;
+ else if (*aX >= screenRect.XMost() - kWindowPositionSlop)
+ *aX = screenRect.XMost() - kWindowPositionSlop;
+
+ if (*aY < screenRect.y - logHeight + kWindowPositionSlop)
+ *aY = screenRect.y - logHeight + kWindowPositionSlop;
+ else if (*aY >= screenRect.YMost() - kWindowPositionSlop)
+ *aY = screenRect.YMost() - kWindowPositionSlop;
+ } else {
+ if (*aX < screenRect.x)
+ *aX = screenRect.x;
+ else if (*aX >= screenRect.XMost() - logWidth)
+ *aX = screenRect.XMost() - logWidth;
+
+ if (*aY < screenRect.y)
+ *aY = screenRect.y;
+ else if (*aY >= screenRect.YMost() - logHeight)
+ *aY = screenRect.YMost() - logHeight;
+ }
+}
+
+void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints)
+{
+ mSizeConstraints.mMinSize = GetSafeWindowSize(aConstraints.mMinSize);
+ mSizeConstraints.mMaxSize = GetSafeWindowSize(aConstraints.mMaxSize);
+
+ if (mShell) {
+ GdkGeometry geometry;
+ geometry.min_width = DevicePixelsToGdkCoordRoundUp(
+ mSizeConstraints.mMinSize.width);
+ geometry.min_height = DevicePixelsToGdkCoordRoundUp(
+ mSizeConstraints.mMinSize.height);
+ geometry.max_width = DevicePixelsToGdkCoordRoundDown(
+ mSizeConstraints.mMaxSize.width);
+ geometry.max_height = DevicePixelsToGdkCoordRoundDown(
+ mSizeConstraints.mMaxSize.height);
+
+ uint32_t hints = 0;
+ if (aConstraints.mMinSize != LayoutDeviceIntSize(0, 0)) {
+ hints |= GDK_HINT_MIN_SIZE;
+ }
+ if (aConstraints.mMaxSize !=
+ LayoutDeviceIntSize(NS_MAXSIZE, NS_MAXSIZE)) {
+ hints |= GDK_HINT_MAX_SIZE;
+ }
+ gtk_window_set_geometry_hints(GTK_WINDOW(mShell), nullptr,
+ &geometry, GdkWindowHints(hints));
+ }
+}
+
+NS_IMETHODIMP
+nsWindow::Show(bool aState)
+{
+ if (aState == mIsShown)
+ return NS_OK;
+
+ // Clear our cached resources when the window is hidden.
+ if (mIsShown && !aState) {
+ ClearCachedResources();
+ }
+
+ mIsShown = aState;
+
+ LOG(("nsWindow::Show [%p] state %d\n", (void *)this, aState));
+
+ if (aState) {
+ // Now that this window is shown, mHasMappedToplevel needs to be
+ // tracked on viewable descendants.
+ SetHasMappedToplevel(mHasMappedToplevel);
+ }
+
+ // Ok, someone called show on a window that isn't sized to a sane
+ // value. Mark this window as needing to have Show() called on it
+ // and return.
+ if ((aState && !AreBoundsSane()) || !mCreated) {
+ LOG(("\tbounds are insane or window hasn't been created yet\n"));
+ mNeedsShow = true;
+ return NS_OK;
+ }
+
+ // If someone is hiding this widget, clear any needing show flag.
+ if (!aState)
+ mNeedsShow = false;
+
+#ifdef ACCESSIBILITY
+ if (aState && a11y::ShouldA11yBeEnabled())
+ CreateRootAccessible();
+#endif
+
+ NativeShow(aState);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::Resize(double aWidth, double aHeight, bool aRepaint)
+{
+ double scale = BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t width = NSToIntRound(scale * aWidth);
+ int32_t height = NSToIntRound(scale * aHeight);
+ ConstrainSize(&width, &height);
+
+ // For top-level windows, aWidth and aHeight should possibly be
+ // interpreted as frame bounds, but NativeResize treats these as window
+ // bounds (Bug 581866).
+
+ mBounds.SizeTo(width, height);
+
+ if (!mCreated)
+ return NS_OK;
+
+ NativeResize();
+
+ NotifyRollupGeometryChange();
+ ResizePluginSocketWidget();
+
+ // send a resize notification if this is a toplevel
+ if (mIsTopLevel || mListenForResizes) {
+ DispatchResized();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint)
+{
+ double scale = BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t width = NSToIntRound(scale * aWidth);
+ int32_t height = NSToIntRound(scale * aHeight);
+ ConstrainSize(&width, &height);
+
+ int32_t x = NSToIntRound(scale * aX);
+ int32_t y = NSToIntRound(scale * aY);
+ mBounds.x = x;
+ mBounds.y = y;
+ mBounds.SizeTo(width, height);
+
+ if (!mCreated)
+ return NS_OK;
+
+ NativeMoveResize();
+
+ NotifyRollupGeometryChange();
+ ResizePluginSocketWidget();
+
+ if (mIsTopLevel || mListenForResizes) {
+ DispatchResized();
+ }
+
+ return NS_OK;
+}
+
+void
+nsWindow::ResizePluginSocketWidget()
+{
+ // e10s specific, a eWindowType_plugin_ipc_chrome holds its own
+ // nsPluginNativeWindowGtk wrapper. We are responsible for resizing
+ // the embedded socket widget.
+ if (mWindowType == eWindowType_plugin_ipc_chrome) {
+ nsPluginNativeWindowGtk* wrapper = (nsPluginNativeWindowGtk*)
+ GetNativeData(NS_NATIVE_PLUGIN_OBJECT_PTR);
+ if (wrapper) {
+ wrapper->width = mBounds.width;
+ wrapper->height = mBounds.height;
+ wrapper->SetAllocation();
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsWindow::Enable(bool aState)
+{
+ mEnabled = aState;
+
+ return NS_OK;
+}
+
+bool
+nsWindow::IsEnabled() const
+{
+ return mEnabled;
+}
+
+
+
+NS_IMETHODIMP
+nsWindow::Move(double aX, double aY)
+{
+ LOG(("nsWindow::Move [%p] %f %f\n", (void *)this,
+ aX, aY));
+
+ double scale = BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t x = NSToIntRound(aX * scale);
+ int32_t y = NSToIntRound(aY * scale);
+
+ if (mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog) {
+ SetSizeMode(nsSizeMode_Normal);
+ }
+
+ // Since a popup window's x/y coordinates are in relation to to
+ // the parent, the parent might have moved so we always move a
+ // popup window.
+ if (x == mBounds.x && y == mBounds.y &&
+ mWindowType != eWindowType_popup)
+ return NS_OK;
+
+ // XXX Should we do some AreBoundsSane check here?
+
+ mBounds.x = x;
+ mBounds.y = y;
+
+ if (!mCreated)
+ return NS_OK;
+
+ NativeMove();
+
+ NotifyRollupGeometryChange();
+ return NS_OK;
+}
+
+
+void
+nsWindow::NativeMove()
+{
+ GdkPoint point = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
+
+ if (mIsTopLevel) {
+ gtk_window_move(GTK_WINDOW(mShell), point.x, point.y);
+ }
+ else if (mGdkWindow) {
+ gdk_window_move(mGdkWindow, point.x, point.y);
+ }
+}
+
+void
+nsWindow::SetZIndex(int32_t aZIndex)
+{
+ nsIWidget* oldPrev = GetPrevSibling();
+
+ nsBaseWidget::SetZIndex(aZIndex);
+
+ if (GetPrevSibling() == oldPrev) {
+ return;
+ }
+
+ NS_ASSERTION(!mContainer, "Expected Mozilla child widget");
+
+ // We skip the nsWindows that don't have mGdkWindows.
+ // These are probably in the process of being destroyed.
+
+ if (!GetNextSibling()) {
+ // We're to be on top.
+ if (mGdkWindow)
+ gdk_window_raise(mGdkWindow);
+ } else {
+ // All the siblings before us need to be below our widget.
+ for (nsWindow* w = this; w;
+ w = static_cast<nsWindow*>(w->GetPrevSibling())) {
+ if (w->mGdkWindow)
+ gdk_window_lower(w->mGdkWindow);
+ }
+ }
+}
+
+void
+nsWindow::SetSizeMode(nsSizeMode aMode)
+{
+ LOG(("nsWindow::SetSizeMode [%p] %d\n", (void *)this, aMode));
+
+ // Save the requested state.
+ nsBaseWidget::SetSizeMode(aMode);
+
+ // return if there's no shell or our current state is the same as
+ // the mode we were just set to.
+ if (!mShell || mSizeState == mSizeMode) {
+ return;
+ }
+
+ switch (aMode) {
+ case nsSizeMode_Maximized:
+ gtk_window_maximize(GTK_WINDOW(mShell));
+ break;
+ case nsSizeMode_Minimized:
+ gtk_window_iconify(GTK_WINDOW(mShell));
+ break;
+ case nsSizeMode_Fullscreen:
+ MakeFullScreen(true);
+ break;
+
+ default:
+ // nsSizeMode_Normal, really.
+ if (mSizeState == nsSizeMode_Minimized)
+ gtk_window_deiconify(GTK_WINDOW(mShell));
+ else if (mSizeState == nsSizeMode_Maximized)
+ gtk_window_unmaximize(GTK_WINDOW(mShell));
+ break;
+ }
+
+ mSizeState = mSizeMode;
+}
+
+typedef void (* SetUserTimeFunc)(GdkWindow* aWindow, guint32 aTimestamp);
+
+// This will become obsolete when new GTK APIs are widely supported,
+// as described here: http://bugzilla.gnome.org/show_bug.cgi?id=347375
+static void
+SetUserTimeAndStartupIDForActivatedWindow(GtkWidget* aWindow)
+{
+ nsGTKToolkit* GTKToolkit = nsGTKToolkit::GetToolkit();
+ if (!GTKToolkit)
+ return;
+
+ nsAutoCString desktopStartupID;
+ GTKToolkit->GetDesktopStartupID(&desktopStartupID);
+ if (desktopStartupID.IsEmpty()) {
+ // We don't have the data we need. Fall back to an
+ // approximation ... using the timestamp of the remote command
+ // being received as a guess for the timestamp of the user event
+ // that triggered it.
+ uint32_t timestamp = GTKToolkit->GetFocusTimestamp();
+ if (timestamp) {
+ gdk_window_focus(gtk_widget_get_window(aWindow), timestamp);
+ GTKToolkit->SetFocusTimestamp(0);
+ }
+ return;
+ }
+
+#if defined(MOZ_ENABLE_STARTUP_NOTIFICATION)
+ // TODO - Implement for non-X11 Gtk backends (Bug 726479)
+ if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+ GdkWindow* gdkWindow = gtk_widget_get_window(aWindow);
+
+ GdkScreen* screen = gdk_window_get_screen(gdkWindow);
+ SnDisplay* snd =
+ sn_display_new(gdk_x11_display_get_xdisplay(gdk_window_get_display(gdkWindow)),
+ nullptr, nullptr);
+ if (!snd)
+ return;
+ SnLauncheeContext* ctx =
+ sn_launchee_context_new(snd, gdk_screen_get_number(screen),
+ desktopStartupID.get());
+ if (!ctx) {
+ sn_display_unref(snd);
+ return;
+ }
+
+ if (sn_launchee_context_get_id_has_timestamp(ctx)) {
+ gdk_x11_window_set_user_time(gdkWindow,
+ sn_launchee_context_get_timestamp(ctx));
+ }
+
+ sn_launchee_context_setup_window(ctx, gdk_x11_window_get_xid(gdkWindow));
+ sn_launchee_context_complete(ctx);
+
+ sn_launchee_context_unref(ctx);
+ sn_display_unref(snd);
+ }
+#endif
+
+ // If we used the startup ID, that already contains the focus timestamp;
+ // we don't want to reuse the timestamp next time we raise the window
+ GTKToolkit->SetFocusTimestamp(0);
+ GTKToolkit->SetDesktopStartupID(EmptyCString());
+}
+
+/* static */ guint32
+nsWindow::GetLastUserInputTime()
+{
+ // gdk_x11_display_get_user_time tracks button and key presses,
+ // DESKTOP_STARTUP_ID used to start the app, drop events from external
+ // drags, WM_DELETE_WINDOW delete events, but not usually mouse motion nor
+ // button and key releases. Therefore use the most recent of
+ // gdk_x11_display_get_user_time and the last time that we have seen.
+ guint32 timestamp =
+ gdk_x11_display_get_user_time(gdk_display_get_default());
+ if (sLastUserInputTime != GDK_CURRENT_TIME &&
+ TimestampIsNewerThan(sLastUserInputTime, timestamp)) {
+ return sLastUserInputTime;
+ }
+
+ return timestamp;
+}
+
+NS_IMETHODIMP
+nsWindow::SetFocus(bool aRaise)
+{
+ // Make sure that our owning widget has focus. If it doesn't try to
+ // grab it. Note that we don't set our focus flag in this case.
+
+ LOGFOCUS((" SetFocus %d [%p]\n", aRaise, (void *)this));
+
+ GtkWidget *owningWidget = GetMozContainerWidget();
+ if (!owningWidget)
+ return NS_ERROR_FAILURE;
+
+ // Raise the window if someone passed in true and the prefs are
+ // set properly.
+ GtkWidget *toplevelWidget = gtk_widget_get_toplevel(owningWidget);
+
+ if (gRaiseWindows && aRaise && toplevelWidget &&
+ !gtk_widget_has_focus(owningWidget) &&
+ !gtk_widget_has_focus(toplevelWidget)) {
+ GtkWidget* top_window = GetToplevelWidget();
+ if (top_window && (gtk_widget_get_visible(top_window)))
+ {
+ gdk_window_show_unraised(gtk_widget_get_window(top_window));
+ // Unset the urgency hint if possible.
+ SetUrgencyHint(top_window, false);
+ }
+ }
+
+ RefPtr<nsWindow> owningWindow = get_window_for_gtk_widget(owningWidget);
+ if (!owningWindow)
+ return NS_ERROR_FAILURE;
+
+ if (aRaise) {
+ // aRaise == true means request toplevel activation.
+
+ // This is asynchronous.
+ // If and when the window manager accepts the request, then the focus
+ // widget will get a focus-in-event signal.
+ if (gRaiseWindows && owningWindow->mIsShown && owningWindow->mShell &&
+ !gtk_window_is_active(GTK_WINDOW(owningWindow->mShell))) {
+
+ uint32_t timestamp = GDK_CURRENT_TIME;
+
+ nsGTKToolkit* GTKToolkit = nsGTKToolkit::GetToolkit();
+ if (GTKToolkit)
+ timestamp = GTKToolkit->GetFocusTimestamp();
+
+ LOGFOCUS((" requesting toplevel activation [%p]\n", (void *)this));
+ NS_ASSERTION(owningWindow->mWindowType != eWindowType_popup
+ || mParent,
+ "Presenting an override-redirect window");
+ gtk_window_present_with_time(GTK_WINDOW(owningWindow->mShell), timestamp);
+
+ if (GTKToolkit)
+ GTKToolkit->SetFocusTimestamp(0);
+ }
+
+ return NS_OK;
+ }
+
+ // aRaise == false means that keyboard events should be dispatched
+ // from this widget.
+
+ // Ensure owningWidget is the focused GtkWidget within its toplevel window.
+ //
+ // For eWindowType_popup, this GtkWidget may not actually be the one that
+ // receives the key events as it may be the parent window that is active.
+ if (!gtk_widget_is_focus(owningWidget)) {
+ // This is synchronous. It takes focus from a plugin or from a widget
+ // in an embedder. The focus manager already knows that this window
+ // is active so gBlockActivateEvent avoids another (unnecessary)
+ // activate notification.
+ gBlockActivateEvent = true;
+ gtk_widget_grab_focus(owningWidget);
+ gBlockActivateEvent = false;
+ }
+
+ // If this is the widget that already has focus, return.
+ if (gFocusWindow == this) {
+ LOGFOCUS((" already have focus [%p]\n", (void *)this));
+ return NS_OK;
+ }
+
+ // Set this window to be the focused child window
+ gFocusWindow = this;
+
+ if (mIMContext) {
+ mIMContext->OnFocusWindow(this);
+ }
+
+ LOGFOCUS((" widget now has focus in SetFocus() [%p]\n",
+ (void *)this));
+
+ return NS_OK;
+}
+
+LayoutDeviceIntRect
+nsWindow::GetScreenBounds()
+{
+ LayoutDeviceIntRect rect;
+ if (mIsTopLevel && mContainer) {
+ // use the point including window decorations
+ gint x, y;
+ gdk_window_get_root_origin(gtk_widget_get_window(GTK_WIDGET(mContainer)), &x, &y);
+ rect.MoveTo(GdkPointToDevicePixels({ x, y }));
+ } else {
+ rect.MoveTo(WidgetToScreenOffset());
+ }
+ // mBounds.Size() is the window bounds, not the window-manager frame
+ // bounds (bug 581863). gdk_window_get_frame_extents would give the
+ // frame bounds, but mBounds.Size() is returned here for consistency
+ // with Resize.
+ rect.SizeTo(mBounds.Size());
+ LOG(("GetScreenBounds %d,%d | %dx%d\n",
+ rect.x, rect.y, rect.width, rect.height));
+ return rect;
+}
+
+LayoutDeviceIntSize
+nsWindow::GetClientSize()
+{
+ return LayoutDeviceIntSize(mBounds.width, mBounds.height);
+}
+
+LayoutDeviceIntRect
+nsWindow::GetClientBounds()
+{
+ // GetBounds returns a rect whose top left represents the top left of the
+ // outer bounds, but whose width/height represent the size of the inner
+ // bounds (which is messed up).
+ LayoutDeviceIntRect rect = GetBounds();
+ rect.MoveBy(GetClientOffset());
+ return rect;
+}
+
+void
+nsWindow::UpdateClientOffset()
+{
+ PROFILER_LABEL("nsWindow", "UpdateClientOffset", js::ProfileEntry::Category::GRAPHICS);
+
+ if (!mIsTopLevel || !mShell || !mGdkWindow || !mIsX11Display ||
+ gtk_window_get_window_type(GTK_WINDOW(mShell)) == GTK_WINDOW_POPUP) {
+ mClientOffset = nsIntPoint(0, 0);
+ return;
+ }
+
+ GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
+
+ GdkAtom type_returned;
+ int format_returned;
+ int length_returned;
+ long *frame_extents;
+
+ if (!gdk_property_get(mGdkWindow,
+ gdk_atom_intern ("_NET_FRAME_EXTENTS", FALSE),
+ cardinal_atom,
+ 0, // offset
+ 4*4, // length
+ FALSE, // delete
+ &type_returned,
+ &format_returned,
+ &length_returned,
+ (guchar **) &frame_extents) ||
+ length_returned/sizeof(glong) != 4) {
+ mClientOffset = nsIntPoint(0, 0);
+ return;
+ }
+
+ // data returned is in the order left, right, top, bottom
+ int32_t left = int32_t(frame_extents[0]);
+ int32_t top = int32_t(frame_extents[2]);
+
+ g_free(frame_extents);
+
+ mClientOffset = nsIntPoint(left, top);
+}
+
+LayoutDeviceIntPoint
+nsWindow::GetClientOffset()
+{
+ return LayoutDeviceIntPoint::FromUnknownPoint(mClientOffset);
+}
+
+gboolean
+nsWindow::OnPropertyNotifyEvent(GtkWidget* aWidget, GdkEventProperty* aEvent)
+
+{
+ if (aEvent->atom == gdk_atom_intern("_NET_FRAME_EXTENTS", FALSE)) {
+ UpdateClientOffset();
+ return FALSE;
+ }
+
+ if (GetCurrentTimeGetter()->PropertyNotifyHandler(aWidget, aEvent)) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+NS_IMETHODIMP
+nsWindow::SetCursor(nsCursor aCursor)
+{
+ // if we're not the toplevel window pass up the cursor request to
+ // the toplevel window to handle it.
+ if (!mContainer && mGdkWindow) {
+ nsWindow *window = GetContainerWindow();
+ if (!window)
+ return NS_ERROR_FAILURE;
+
+ return window->SetCursor(aCursor);
+ }
+
+ // Only change cursor if it's actually been changed
+ if (aCursor != mCursor || mUpdateCursor) {
+ GdkCursor *newCursor = nullptr;
+ mUpdateCursor = false;
+
+ newCursor = get_gtk_cursor(aCursor);
+
+ if (nullptr != newCursor) {
+ mCursor = aCursor;
+
+ if (!mContainer)
+ return NS_OK;
+
+ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)), newCursor);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::SetCursor(imgIContainer* aCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY)
+{
+ // if we're not the toplevel window pass up the cursor request to
+ // the toplevel window to handle it.
+ if (!mContainer && mGdkWindow) {
+ nsWindow *window = GetContainerWindow();
+ if (!window)
+ return NS_ERROR_FAILURE;
+
+ return window->SetCursor(aCursor, aHotspotX, aHotspotY);
+ }
+
+ mCursor = nsCursor(-1);
+
+ // Get the image's current frame
+ GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(aCursor);
+ if (!pixbuf)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ int width = gdk_pixbuf_get_width(pixbuf);
+ int height = gdk_pixbuf_get_height(pixbuf);
+ // Reject cursors greater than 128 pixels in some direction, to prevent
+ // spoofing.
+ // XXX ideally we should rescale. Also, we could modify the API to
+ // allow trusted content to set larger cursors.
+ if (width > 128 || height > 128) {
+ g_object_unref(pixbuf);
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Looks like all cursors need an alpha channel (tested on Gtk 2.4.4). This
+ // is of course not documented anywhere...
+ // So add one if there isn't one yet
+ if (!gdk_pixbuf_get_has_alpha(pixbuf)) {
+ GdkPixbuf* alphaBuf = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
+ g_object_unref(pixbuf);
+ if (!alphaBuf) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ pixbuf = alphaBuf;
+ }
+
+ GdkCursor* cursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(),
+ pixbuf,
+ aHotspotX, aHotspotY);
+ g_object_unref(pixbuf);
+ nsresult rv = NS_ERROR_OUT_OF_MEMORY;
+ if (cursor) {
+ if (mContainer) {
+ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)), cursor);
+ rv = NS_OK;
+ }
+#if (MOZ_WIDGET_GTK == 3)
+ g_object_unref(cursor);
+#else
+ gdk_cursor_unref(cursor);
+#endif
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWindow::Invalidate(const LayoutDeviceIntRect& aRect)
+{
+ if (!mGdkWindow)
+ return NS_OK;
+
+ GdkRectangle rect = DevicePixelsToGdkRectRoundOut(aRect);
+ gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
+
+ LOGDRAW(("Invalidate (rect) [%p]: %d %d %d %d\n", (void *)this,
+ rect.x, rect.y, rect.width, rect.height));
+
+ return NS_OK;
+}
+
+void*
+nsWindow::GetNativeData(uint32_t aDataType)
+{
+ switch (aDataType) {
+ case NS_NATIVE_WINDOW:
+ case NS_NATIVE_WIDGET: {
+ if (!mGdkWindow)
+ return nullptr;
+
+ return mGdkWindow;
+ }
+
+ case NS_NATIVE_PLUGIN_PORT:
+ return SetupPluginPort();
+
+ case NS_NATIVE_PLUGIN_ID:
+ if (!mPluginNativeWindow) {
+ NS_WARNING("no native plugin instance!");
+ return nullptr;
+ }
+ // Return the socket widget XID
+ return (void*)mPluginNativeWindow->window;
+
+ case NS_NATIVE_DISPLAY: {
+#ifdef MOZ_X11
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ if (GDK_IS_X11_DISPLAY(gdkDisplay)) {
+ return GDK_DISPLAY_XDISPLAY(gdkDisplay);
+ }
+#endif /* MOZ_X11 */
+ return nullptr;
+ }
+ case NS_NATIVE_SHELLWIDGET:
+ return GetToplevelWidget();
+
+ case NS_NATIVE_SHAREABLE_WINDOW:
+ return (void *) GDK_WINDOW_XID(gdk_window_get_toplevel(mGdkWindow));
+ case NS_NATIVE_PLUGIN_OBJECT_PTR:
+ return (void *) mPluginNativeWindow;
+ case NS_RAW_NATIVE_IME_CONTEXT: {
+ void* pseudoIMEContext = GetPseudoIMEContext();
+ if (pseudoIMEContext) {
+ return pseudoIMEContext;
+ }
+ // If IME context isn't available on this widget, we should set |this|
+ // instead of nullptr.
+ if (!mIMContext) {
+ return this;
+ }
+ return mIMContext.get();
+ }
+ case NS_NATIVE_OPENGL_CONTEXT:
+ return nullptr;
+#ifdef MOZ_X11
+ case NS_NATIVE_COMPOSITOR_DISPLAY:
+ return gfxPlatformGtk::GetPlatform()->GetCompositorDisplay();
+#endif // MOZ_X11
+ default:
+ NS_WARNING("nsWindow::GetNativeData called with bad value");
+ return nullptr;
+ }
+}
+
+void
+nsWindow::SetNativeData(uint32_t aDataType, uintptr_t aVal)
+{
+ if (aDataType != NS_NATIVE_PLUGIN_OBJECT_PTR) {
+ NS_WARNING("nsWindow::SetNativeData called with bad value");
+ return;
+ }
+ mPluginNativeWindow = (nsPluginNativeWindowGtk*)aVal;
+}
+
+NS_IMETHODIMP
+nsWindow::SetTitle(const nsAString& aTitle)
+{
+ if (!mShell)
+ return NS_OK;
+
+ // convert the string into utf8 and set the title.
+#define UTF8_FOLLOWBYTE(ch) (((ch) & 0xC0) == 0x80)
+ NS_ConvertUTF16toUTF8 titleUTF8(aTitle);
+ if (titleUTF8.Length() > NS_WINDOW_TITLE_MAX_LENGTH) {
+ // Truncate overlong titles (bug 167315). Make sure we chop after a
+ // complete sequence by making sure the next char isn't a follow-byte.
+ uint32_t len = NS_WINDOW_TITLE_MAX_LENGTH;
+ while(UTF8_FOLLOWBYTE(titleUTF8[len]))
+ --len;
+ titleUTF8.Truncate(len);
+ }
+ gtk_window_set_title(GTK_WINDOW(mShell), (const char *)titleUTF8.get());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::SetIcon(const nsAString& aIconSpec)
+{
+ if (!mShell)
+ return NS_OK;
+
+ nsAutoCString iconName;
+
+ if (aIconSpec.EqualsLiteral("default")) {
+ nsXPIDLString brandName;
+ GetBrandName(brandName);
+ AppendUTF16toUTF8(brandName, iconName);
+ ToLowerCase(iconName);
+ } else {
+ AppendUTF16toUTF8(aIconSpec, iconName);
+ }
+
+ nsCOMPtr<nsIFile> iconFile;
+ nsAutoCString path;
+
+ gint *iconSizes =
+ gtk_icon_theme_get_icon_sizes(gtk_icon_theme_get_default(),
+ iconName.get());
+ bool foundIcon = (iconSizes[0] != 0);
+ g_free(iconSizes);
+
+ if (!foundIcon) {
+ // Look for icons with the following suffixes appended to the base name
+ // The last two entries (for the old XPM format) will be ignored unless
+ // no icons are found using other suffixes. XPM icons are deprecated.
+
+ const char extensions[6][7] = { ".png", "16.png", "32.png", "48.png",
+ ".xpm", "16.xpm" };
+
+ for (uint32_t i = 0; i < ArrayLength(extensions); i++) {
+ // Don't bother looking for XPM versions if we found a PNG.
+ if (i == ArrayLength(extensions) - 2 && foundIcon)
+ break;
+
+ nsAutoString extension;
+ extension.AppendASCII(extensions[i]);
+
+ ResolveIconName(aIconSpec, extension, getter_AddRefs(iconFile));
+ if (iconFile) {
+ iconFile->GetNativePath(path);
+ GdkPixbuf *icon = gdk_pixbuf_new_from_file(path.get(), nullptr);
+ if (icon) {
+ gtk_icon_theme_add_builtin_icon(iconName.get(),
+ gdk_pixbuf_get_height(icon),
+ icon);
+ g_object_unref(icon);
+ foundIcon = true;
+ }
+ }
+ }
+ }
+
+ // leave the default icon intact if no matching icons were found
+ if (foundIcon) {
+ gtk_window_set_icon_name(GTK_WINDOW(mShell), iconName.get());
+ }
+
+ return NS_OK;
+}
+
+
+LayoutDeviceIntPoint
+nsWindow::WidgetToScreenOffset()
+{
+ gint x = 0, y = 0;
+
+ if (mGdkWindow) {
+ gdk_window_get_origin(mGdkWindow, &x, &y);
+ }
+
+ return GdkPointToDevicePixels({ x, y });
+}
+
+void
+nsWindow::CaptureMouse(bool aCapture)
+{
+ LOG(("CaptureMouse %p\n", (void *)this));
+
+ if (!mGdkWindow)
+ return;
+
+ if (!mContainer)
+ return;
+
+ if (aCapture) {
+ gtk_grab_add(GTK_WIDGET(mContainer));
+ GrabPointer(GetLastUserInputTime());
+ }
+ else {
+ ReleaseGrabs();
+ gtk_grab_remove(GTK_WIDGET(mContainer));
+ }
+}
+
+void
+nsWindow::CaptureRollupEvents(nsIRollupListener *aListener,
+ bool aDoCapture)
+{
+ if (!mGdkWindow)
+ return;
+
+ if (!mContainer)
+ return;
+
+ LOG(("CaptureRollupEvents %p %i\n", this, int(aDoCapture)));
+
+ if (aDoCapture) {
+ gRollupListener = aListener;
+ // Don't add a grab if a drag is in progress, or if the widget is a drag
+ // feedback popup. (panels with type="drag").
+ if (!mIsDragPopup && !nsWindow::DragInProgress()) {
+ gtk_grab_add(GTK_WIDGET(mContainer));
+ GrabPointer(GetLastUserInputTime());
+ }
+ }
+ else {
+ if (!nsWindow::DragInProgress()) {
+ ReleaseGrabs();
+ }
+ // There may not have been a drag in process when aDoCapture was set,
+ // so make sure to remove any added grab. This is a no-op if the grab
+ // was not added to this widget.
+ gtk_grab_remove(GTK_WIDGET(mContainer));
+ gRollupListener = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsWindow::GetAttention(int32_t aCycleCount)
+{
+ LOG(("nsWindow::GetAttention [%p]\n", (void *)this));
+
+ GtkWidget* top_window = GetToplevelWidget();
+ GtkWidget* top_focused_window =
+ gFocusWindow ? gFocusWindow->GetToplevelWidget() : nullptr;
+
+ // Don't get attention if the window is focused anyway.
+ if (top_window && (gtk_widget_get_visible(top_window)) &&
+ top_window != top_focused_window) {
+ SetUrgencyHint(top_window, true);
+ }
+
+ return NS_OK;
+}
+
+bool
+nsWindow::HasPendingInputEvent()
+{
+ // This sucks, but gtk/gdk has no way to answer the question we want while
+ // excluding paint events, and there's no X API that will let us peek
+ // without blocking or removing. To prevent event reordering, peek
+ // anything except expose events. Reordering expose and others should be
+ // ok, hopefully.
+ bool haveEvent = false;
+#ifdef MOZ_X11
+ XEvent ev;
+ if (mIsX11Display) {
+ Display *display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+ haveEvent =
+ XCheckMaskEvent(display,
+ KeyPressMask | KeyReleaseMask | ButtonPressMask |
+ ButtonReleaseMask | EnterWindowMask | LeaveWindowMask |
+ PointerMotionMask | PointerMotionHintMask |
+ Button1MotionMask | Button2MotionMask |
+ Button3MotionMask | Button4MotionMask |
+ Button5MotionMask | ButtonMotionMask | KeymapStateMask |
+ VisibilityChangeMask | StructureNotifyMask |
+ ResizeRedirectMask | SubstructureNotifyMask |
+ SubstructureRedirectMask | FocusChangeMask |
+ PropertyChangeMask | ColormapChangeMask |
+ OwnerGrabButtonMask, &ev);
+ if (haveEvent) {
+ XPutBackEvent(display, &ev);
+ }
+ }
+#endif
+ return haveEvent;
+}
+
+#if 0
+#ifdef DEBUG
+// Paint flashing code (disabled for cairo - see below)
+
+#define CAPS_LOCK_IS_ON \
+(KeymapWrapper::AreModifiersCurrentlyActive(KeymapWrapper::CAPS_LOCK))
+
+#define WANT_PAINT_FLASHING \
+(debug_WantPaintFlashing() && CAPS_LOCK_IS_ON)
+
+#ifdef MOZ_X11
+static void
+gdk_window_flash(GdkWindow * aGdkWindow,
+ unsigned int aTimes,
+ unsigned int aInterval, // Milliseconds
+ GdkRegion * aRegion)
+{
+ gint x;
+ gint y;
+ gint width;
+ gint height;
+ guint i;
+ GdkGC * gc = 0;
+ GdkColor white;
+
+#if (MOZ_WIDGET_GTK == 2)
+ gdk_window_get_geometry(aGdkWindow,nullptr,nullptr,&width,&height,nullptr);
+#else
+ gdk_window_get_geometry(aGdkWindow,nullptr,nullptr,&width,&height);
+#endif
+
+ gdk_window_get_origin (aGdkWindow,
+ &x,
+ &y);
+
+ gc = gdk_gc_new(gdk_get_default_root_window());
+
+ white.pixel = WhitePixel(gdk_display,DefaultScreen(gdk_display));
+
+ gdk_gc_set_foreground(gc,&white);
+ gdk_gc_set_function(gc,GDK_XOR);
+ gdk_gc_set_subwindow(gc,GDK_INCLUDE_INFERIORS);
+
+ gdk_region_offset(aRegion, x, y);
+ gdk_gc_set_clip_region(gc, aRegion);
+
+ /*
+ * Need to do this twice so that the XOR effect can replace
+ * the original window contents.
+ */
+ for (i = 0; i < aTimes * 2; i++)
+ {
+ gdk_draw_rectangle(gdk_get_default_root_window(),
+ gc,
+ TRUE,
+ x,
+ y,
+ width,
+ height);
+
+ gdk_flush();
+
+ PR_Sleep(PR_MillisecondsToInterval(aInterval));
+ }
+
+ gdk_gc_destroy(gc);
+
+ gdk_region_offset(aRegion, -x, -y);
+}
+#endif /* MOZ_X11 */
+#endif // DEBUG
+#endif
+
+#if (MOZ_WIDGET_GTK == 2)
+static bool
+ExtractExposeRegion(LayoutDeviceIntRegion& aRegion, GdkEventExpose* aEvent)
+{
+ GdkRectangle* rects;
+ gint nrects;
+ gdk_region_get_rectangles(aEvent->region, &rects, &nrects);
+
+ if (nrects > MAX_RECTS_IN_REGION) {
+ // Just use the bounding box
+ rects[0] = aEvent->area;
+ nrects = 1;
+ }
+
+ for (GdkRectangle* r = rects; r < rects + nrects; r++) {
+ aRegion.Or(aRegion, LayoutDeviceIntRect(r->x, r->y, r->width, r->height));
+ LOGDRAW(("\t%d %d %d %d\n", r->x, r->y, r->width, r->height));
+ }
+
+ g_free(rects);
+ return true;
+}
+
+#else
+# ifdef cairo_copy_clip_rectangle_list
+# error "Looks like we're including Mozilla's cairo instead of system cairo"
+# endif
+static bool
+ExtractExposeRegion(LayoutDeviceIntRegion& aRegion, cairo_t* cr)
+{
+ cairo_rectangle_list_t* rects = cairo_copy_clip_rectangle_list(cr);
+ if (rects->status != CAIRO_STATUS_SUCCESS) {
+ NS_WARNING("Failed to obtain cairo rectangle list.");
+ return false;
+ }
+
+ for (int i = 0; i < rects->num_rectangles; i++) {
+ const cairo_rectangle_t& r = rects->rectangles[i];
+ aRegion.Or(aRegion, LayoutDeviceIntRect::Truncate(r.x, r.y, r.width, r.height));
+ LOGDRAW(("\t%d %d %d %d\n", r.x, r.y, r.width, r.height));
+ }
+
+ cairo_rectangle_list_destroy(rects);
+ return true;
+}
+#endif
+
+#if (MOZ_WIDGET_GTK == 2)
+gboolean
+nsWindow::OnExposeEvent(GdkEventExpose *aEvent)
+#else
+gboolean
+nsWindow::OnExposeEvent(cairo_t *cr)
+#endif
+{
+ // Send any pending resize events so that layout can update.
+ // May run event loop.
+ MaybeDispatchResized();
+
+ if (mIsDestroyed) {
+ return FALSE;
+ }
+
+ // Windows that are not visible will be painted after they become visible.
+ if (!mGdkWindow || mIsFullyObscured || !mHasMappedToplevel)
+ return FALSE;
+
+ nsIWidgetListener *listener = GetListener();
+ if (!listener)
+ return FALSE;
+
+ LayoutDeviceIntRegion exposeRegion;
+#if (MOZ_WIDGET_GTK == 2)
+ if (!ExtractExposeRegion(exposeRegion, aEvent)) {
+#else
+ if (!ExtractExposeRegion(exposeRegion, cr)) {
+#endif
+ return FALSE;
+ }
+
+ gint scale = GdkScaleFactor();
+ LayoutDeviceIntRegion region = exposeRegion;
+ region.ScaleRoundOut(scale, scale);
+
+ ClientLayerManager *clientLayers = GetLayerManager()->AsClientLayerManager();
+
+ if (clientLayers && mCompositorSession) {
+ // We need to paint to the screen even if nothing changed, since if we
+ // don't have a compositing window manager, our pixels could be stale.
+ clientLayers->SetNeedsComposite(true);
+ clientLayers->SendInvalidRegion(region.ToUnknownRegion());
+ }
+
+ RefPtr<nsWindow> strongThis(this);
+
+ // Dispatch WillPaintWindow notification to allow scripts etc. to run
+ // before we paint
+ {
+ listener->WillPaintWindow(this);
+
+ // If the window has been destroyed during the will paint notification,
+ // there is nothing left to do.
+ if (!mGdkWindow)
+ return TRUE;
+
+ // Re-get the listener since the will paint notification might have
+ // killed it.
+ listener = GetListener();
+ if (!listener)
+ return FALSE;
+ }
+
+ if (clientLayers && clientLayers->NeedsComposite()) {
+ clientLayers->Composite();
+ clientLayers->SetNeedsComposite(false);
+ }
+
+ LOGDRAW(("sending expose event [%p] %p 0x%lx (rects follow):\n",
+ (void *)this, (void *)mGdkWindow,
+ gdk_x11_window_get_xid(mGdkWindow)));
+
+ // Our bounds may have changed after calling WillPaintWindow. Clip
+ // to the new bounds here. The region is relative to this
+ // window.
+ region.And(region, LayoutDeviceIntRect(0, 0, mBounds.width, mBounds.height));
+
+ 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)) {
+ // Remove possible shape mask from when window manger was not
+ // previously compositing.
+ static_cast<nsWindow*>(GetTopLevelWidget())->
+ ClearTransparencyBitmap();
+ } else {
+ shaped = true;
+ }
+ }
+
+ if (!shaped) {
+ GList *children =
+ gdk_window_peek_children(mGdkWindow);
+ while (children) {
+ GdkWindow *gdkWin = GDK_WINDOW(children->data);
+ nsWindow *kid = get_window_for_gdk_window(gdkWin);
+ if (kid && gdk_window_is_visible(gdkWin)) {
+ AutoTArray<LayoutDeviceIntRect,1> clipRects;
+ kid->GetWindowClipRegion(&clipRects);
+ LayoutDeviceIntRect bounds = kid->GetBounds();
+ for (uint32_t i = 0; i < clipRects.Length(); ++i) {
+ LayoutDeviceIntRect r = clipRects[i] + bounds.TopLeft();
+ region.Sub(region, r);
+ }
+ }
+ children = children->next;
+ }
+ }
+
+ if (region.IsEmpty()) {
+ return TRUE;
+ }
+
+ // If this widget uses OMTC...
+ if (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT) {
+ listener->PaintWindow(this, region);
+
+ // Re-get the listener since the will paint notification might have
+ // killed it.
+ listener = GetListener();
+ if (!listener)
+ return TRUE;
+
+ listener->DidPaintWindow();
+ return TRUE;
+ }
+
+ BufferMode layerBuffering = BufferMode::BUFFERED;
+ RefPtr<DrawTarget> dt = StartRemoteDrawingInRegion(region, &layerBuffering);
+ if (!dt || !dt->IsValid()) {
+ return FALSE;
+ }
+ RefPtr<gfxContext> ctx;
+ IntRect boundsRect = region.GetBounds().ToUnknownRect();
+ IntPoint offset(0, 0);
+ if (dt->GetSize() == boundsRect.Size()) {
+ offset = boundsRect.TopLeft();
+ dt->SetTransform(Matrix::Translation(-offset));
+ }
+
+#ifdef MOZ_X11
+ if (shaped) {
+ // Collapse update area to the bounding box. This is so we only have to
+ // call UpdateTranslucentWindowAlpha once. After we have dropped
+ // support for non-Thebes graphics, UpdateTranslucentWindowAlpha will be
+ // our private interface so we can rework things to avoid this.
+ dt->PushClipRect(Rect(boundsRect));
+
+ // The double buffering is done here to extract the shape mask.
+ // (The shape mask won't be necessary when a visual with an alpha
+ // channel is used on compositing window managers.)
+ layerBuffering = BufferMode::BUFFER_NONE;
+ RefPtr<DrawTarget> destDT = dt->CreateSimilarDrawTarget(boundsRect.Size(), SurfaceFormat::B8G8R8A8);
+ if (!destDT || !destDT->IsValid()) {
+ return FALSE;
+ }
+ destDT->SetTransform(Matrix::Translation(-boundsRect.TopLeft()));
+ ctx = gfxContext::CreatePreservingTransformOrNull(destDT);
+ } else {
+ gfxUtils::ClipToRegion(dt, region.ToUnknownRegion());
+ ctx = gfxContext::CreatePreservingTransformOrNull(dt);
+ }
+ MOZ_ASSERT(ctx); // checked both dt and destDT valid draw target above
+
+#if 0
+ // NOTE: Paint flashing region would be wrong for cairo, since
+ // cairo inflates the update region, etc. So don't paint flash
+ // for cairo.
+#ifdef DEBUG
+ // XXX aEvent->region may refer to a newly-invalid area. FIXME
+ if (0 && WANT_PAINT_FLASHING && gtk_widget_get_window(aEvent))
+ gdk_window_flash(mGdkWindow, 1, 100, aEvent->region);
+#endif
+#endif
+
+#endif // MOZ_X11
+
+ 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)) {
+ // 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.
+ dt->ClearRect(Rect(boundsRect));
+ }
+ AutoLayerManagerSetup setupLayerManager(this, ctx, layerBuffering);
+ painted = listener->PaintWindow(this, region);
+
+ // Re-get the listener since the will paint notification might have
+ // killed it.
+ listener = GetListener();
+ if (!listener)
+ return TRUE;
+
+ }
+ }
+
+#ifdef MOZ_X11
+ // PaintWindow can Destroy us (bug 378273), avoid doing any paint
+ // operations below if that happened - it will lead to XError and exit().
+ if (shaped) {
+ if (MOZ_LIKELY(!mIsDestroyed)) {
+ if (painted) {
+ RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
+
+ UpdateAlpha(surf, boundsRect);
+
+ dt->DrawSurface(surf, Rect(boundsRect), Rect(0, 0, boundsRect.width, boundsRect.height),
+ DrawSurfaceOptions(SamplingFilter::POINT),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+ }
+ }
+ }
+
+ ctx = nullptr;
+ dt->PopClip();
+
+#endif // MOZ_X11
+
+ EndRemoteDrawingInRegion(dt, region);
+
+ listener->DidPaintWindow();
+
+ // Synchronously flush any new dirty areas
+#if (MOZ_WIDGET_GTK == 2)
+ GdkRegion* dirtyArea = gdk_window_get_update_area(mGdkWindow);
+#else
+ cairo_region_t* dirtyArea = gdk_window_get_update_area(mGdkWindow);
+#endif
+
+ if (dirtyArea) {
+ gdk_window_invalidate_region(mGdkWindow, dirtyArea, false);
+#if (MOZ_WIDGET_GTK == 2)
+ gdk_region_destroy(dirtyArea);
+#else
+ cairo_region_destroy(dirtyArea);
+#endif
+ gdk_window_process_updates(mGdkWindow, false);
+ }
+
+ // check the return value!
+ return TRUE;
+}
+
+void
+nsWindow::UpdateAlpha(SourceSurface* aSourceSurface, nsIntRect aBoundsRect)
+{
+ // We need to create our own buffer to force the stride to match the
+ // expected stride.
+ int32_t stride = GetAlignedStride<4>(aBoundsRect.width,
+ BytesPerPixel(SurfaceFormat::A8));
+ if (stride == 0) {
+ return;
+ }
+ int32_t bufferSize = stride * aBoundsRect.height;
+ auto imageBuffer = MakeUniqueFallible<uint8_t[]>(bufferSize);
+ {
+ RefPtr<DrawTarget> drawTarget = gfxPlatform::CreateDrawTargetForData(
+ imageBuffer.get(),
+ aBoundsRect.Size(),
+ stride, SurfaceFormat::A8);
+
+ if (drawTarget) {
+ drawTarget->DrawSurface(aSourceSurface, Rect(0, 0, aBoundsRect.width, aBoundsRect.height),
+ Rect(0, 0, aSourceSurface->GetSize().width, aSourceSurface->GetSize().height),
+ DrawSurfaceOptions(SamplingFilter::POINT),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+ }
+ }
+ UpdateTranslucentWindowAlphaInternal(aBoundsRect, imageBuffer.get(), stride);
+}
+
+gboolean
+nsWindow::OnConfigureEvent(GtkWidget *aWidget, GdkEventConfigure *aEvent)
+{
+ // These events are only received on toplevel windows.
+ //
+ // GDK ensures that the coordinates are the client window top-left wrt the
+ // root window.
+ //
+ // GDK calculates the cordinates for real ConfigureNotify events on
+ // managed windows (that would normally be relative to the parent
+ // window).
+ //
+ // Synthetic ConfigureNotify events are from the window manager and
+ // already relative to the root window. GDK creates all X windows with
+ // border_width = 0, so synthetic events also indicate the top-left of
+ // the client window.
+ //
+ // Override-redirect windows are children of the root window so parent
+ // coordinates are root coordinates.
+
+ LOG(("configure event [%p] %d %d %d %d\n", (void *)this,
+ aEvent->x, aEvent->y, aEvent->width, aEvent->height));
+
+ if (mPendingConfigures > 0) {
+ mPendingConfigures--;
+ }
+
+ LayoutDeviceIntRect screenBounds = GetScreenBounds();
+
+ if (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) {
+ // This check avoids unwanted rollup on spurious configure events from
+ // Cygwin/X (bug 672103).
+ if (mBounds.x != screenBounds.x ||
+ mBounds.y != screenBounds.y) {
+ CheckForRollup(0, 0, false, true);
+ }
+ }
+
+ // This event indicates that the window position may have changed.
+ // mBounds.Size() is updated in OnSizeAllocate().
+
+ NS_ASSERTION(GTK_IS_WINDOW(aWidget),
+ "Configure event on widget that is not a GtkWindow");
+ if (gtk_window_get_window_type(GTK_WINDOW(aWidget)) == GTK_WINDOW_POPUP) {
+ // Override-redirect window
+ //
+ // 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
+ // up-to-date than the position in the ConfigureNotify event if the
+ // event is from an earlier window move.
+ //
+ // Skipping the WindowMoved call saves context menus from an infinite
+ // loop when nsXULPopupManager::PopupMoved moves the window to the new
+ // position and nsMenuPopupFrame::SetPopupPosition adds
+ // offsetForContextMenu on each iteration.
+ return FALSE;
+ }
+
+ mBounds.MoveTo(screenBounds.TopLeft());
+
+ // XXX mozilla will invalidate the entire window after this move
+ // complete. wtf?
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+
+ return FALSE;
+}
+
+void
+nsWindow::OnContainerUnrealize()
+{
+ // The GdkWindows are about to be destroyed (but not deleted), so remove
+ // their references back to their container widget while the GdkWindow
+ // hierarchy is still available.
+
+ if (mGdkWindow) {
+ DestroyChildWindows();
+
+ g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", nullptr);
+ mGdkWindow = nullptr;
+ }
+}
+
+void
+nsWindow::OnSizeAllocate(GtkAllocation *aAllocation)
+{
+ LOG(("size_allocate [%p] %d %d %d %d\n",
+ (void *)this, aAllocation->x, aAllocation->y,
+ aAllocation->width, aAllocation->height));
+
+ LayoutDeviceIntSize size = GdkRectToDevicePixels(*aAllocation).Size();
+
+ if (mBounds.Size() == size)
+ return;
+
+ // Invalidate the new part of the window now for the pending paint to
+ // minimize background flashes (GDK does not do this for external resizes
+ // of toplevels.)
+ if (mBounds.width < size.width) {
+ GdkRectangle rect = DevicePixelsToGdkRectRoundOut(
+ LayoutDeviceIntRect(mBounds.width, 0,
+ size.width - mBounds.width, size.height));
+ gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
+ }
+ if (mBounds.height < size.height) {
+ GdkRectangle rect = DevicePixelsToGdkRectRoundOut(
+ LayoutDeviceIntRect(0, mBounds.height,
+ size.width, size.height - mBounds.height));
+ gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
+ }
+
+ mBounds.SizeTo(size);
+
+#ifdef MOZ_X11
+ // Notify the X11CompositorWidget of a ClientSizeChange
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
+ }
+#endif
+
+ // Gecko permits running nested event loops during processing of events,
+ // GtkWindow callers of gtk_widget_size_allocate expect the signal
+ // handlers to return sometime in the near future.
+ mNeedsDispatchResized = true;
+ NS_DispatchToCurrentThread(NewRunnableMethod(this, &nsWindow::MaybeDispatchResized));
+}
+
+void
+nsWindow::OnDeleteEvent()
+{
+ if (mWidgetListener)
+ mWidgetListener->RequestWindowClose(this);
+}
+
+void
+nsWindow::OnEnterNotifyEvent(GdkEventCrossing *aEvent)
+{
+ // This skips NotifyVirtual and NotifyNonlinearVirtual enter notify events
+ // when the pointer enters a child window. If the destination window is a
+ // Gecko window then we'll catch the corresponding event on that window,
+ // but we won't notice when the pointer directly enters a foreign (plugin)
+ // child window without passing over a visible portion of a Gecko window.
+ if (aEvent->subwindow != nullptr)
+ return;
+
+ // Check before is_parent_ungrab_enter() as the button state may have
+ // changed while a non-Gecko ancestor window had a pointer grab.
+ DispatchMissedButtonReleases(aEvent);
+
+ if (is_parent_ungrab_enter(aEvent))
+ return;
+
+ WidgetMouseEvent event(true, eMouseEnterIntoWidget, this,
+ WidgetMouseEvent::eReal);
+
+ event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+
+ LOG(("OnEnterNotify: %p\n", (void *)this));
+
+ DispatchInputEvent(&event);
+}
+
+// XXX Is this the right test for embedding cases?
+static bool
+is_top_level_mouse_exit(GdkWindow* aWindow, GdkEventCrossing *aEvent)
+{
+ gint x = gint(aEvent->x_root);
+ gint y = gint(aEvent->y_root);
+ GdkDisplay* display = gdk_window_get_display(aWindow);
+ GdkWindow* winAtPt = gdk_display_get_window_at_pointer(display, &x, &y);
+ if (!winAtPt)
+ return true;
+ GdkWindow* topLevelAtPt = gdk_window_get_toplevel(winAtPt);
+ GdkWindow* topLevelWidget = gdk_window_get_toplevel(aWindow);
+ return topLevelAtPt != topLevelWidget;
+}
+
+void
+nsWindow::OnLeaveNotifyEvent(GdkEventCrossing *aEvent)
+{
+ // This ignores NotifyVirtual and NotifyNonlinearVirtual leave notify
+ // events when the pointer leaves a child window. If the destination
+ // window is a Gecko window then we'll catch the corresponding event on
+ // that window.
+ //
+ // XXXkt However, we will miss toplevel exits when the pointer directly
+ // leaves a foreign (plugin) child window without passing over a visible
+ // portion of a Gecko window.
+ if (aEvent->subwindow != nullptr)
+ return;
+
+ WidgetMouseEvent event(true, eMouseExitFromWidget, this,
+ WidgetMouseEvent::eReal);
+
+ event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+
+ event.mExitFrom = is_top_level_mouse_exit(mGdkWindow, aEvent)
+ ? WidgetMouseEvent::eTopLevel : WidgetMouseEvent::eChild;
+
+ LOG(("OnLeaveNotify: %p\n", (void *)this));
+
+ DispatchInputEvent(&event);
+}
+
+template <typename Event> static LayoutDeviceIntPoint
+GetRefPoint(nsWindow* aWindow, Event* aEvent)
+{
+ if (aEvent->window == aWindow->GetGdkWindow()) {
+ // we are the window that the event happened on so no need for expensive WidgetToScreenOffset
+ return aWindow->GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+ }
+ // XXX we're never quite sure which GdkWindow the event came from due to our custom bubbling
+ // in scroll_event_cb(), so use ScreenToWidget to translate the screen root coordinates into
+ // coordinates relative to this widget.
+ return aWindow->GdkEventCoordsToDevicePixels(
+ aEvent->x_root, aEvent->y_root) - aWindow->WidgetToScreenOffset();
+}
+
+void
+nsWindow::OnMotionNotifyEvent(GdkEventMotion *aEvent)
+{
+ // see if we can compress this event
+ // XXXldb Why skip every other motion event when we have multiple,
+ // but not more than that?
+ bool synthEvent = false;
+#ifdef MOZ_X11
+ XEvent xevent;
+
+ if (mIsX11Display) {
+ while (XPending (GDK_WINDOW_XDISPLAY(aEvent->window))) {
+ XEvent peeked;
+ XPeekEvent (GDK_WINDOW_XDISPLAY(aEvent->window), &peeked);
+ if (peeked.xany.window != gdk_x11_window_get_xid(aEvent->window)
+ || peeked.type != MotionNotify)
+ break;
+
+ synthEvent = true;
+ XNextEvent (GDK_WINDOW_XDISPLAY(aEvent->window), &xevent);
+ }
+#if (MOZ_WIDGET_GTK == 2)
+ // if plugins still keeps the focus, get it back
+ if (gPluginFocusWindow && gPluginFocusWindow != this) {
+ RefPtr<nsWindow> kungFuDeathGrip = gPluginFocusWindow;
+ gPluginFocusWindow->LoseNonXEmbedPluginFocus();
+ }
+#endif /* MOZ_WIDGET_GTK == 2 */
+ }
+#endif /* MOZ_X11 */
+
+ WidgetMouseEvent event(true, eMouseMove, this, WidgetMouseEvent::eReal);
+
+ gdouble pressure = 0;
+ gdk_event_get_axis ((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
+ // Sometime gdk generate 0 pressure value between normal values
+ // We have to ignore that and use last valid value
+ if (pressure)
+ mLastMotionPressure = pressure;
+ event.pressure = mLastMotionPressure;
+
+ guint modifierState;
+ if (synthEvent) {
+#ifdef MOZ_X11
+ event.mRefPoint.x = nscoord(xevent.xmotion.x);
+ event.mRefPoint.y = nscoord(xevent.xmotion.y);
+
+ modifierState = xevent.xmotion.state;
+
+ event.AssignEventTime(GetWidgetEventTime(xevent.xmotion.time));
+#else
+ event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+
+ modifierState = aEvent->state;
+
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+#endif /* MOZ_X11 */
+ } else {
+ event.mRefPoint = GetRefPoint(this, aEvent);
+
+ modifierState = aEvent->state;
+
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+ }
+
+ KeymapWrapper::InitInputEvent(event, modifierState);
+
+ DispatchInputEvent(&event);
+}
+
+// If the automatic pointer grab on ButtonPress has deactivated before
+// ButtonRelease, and the mouse button is released while the pointer is not
+// over any a Gecko window, then the ButtonRelease event will not be received.
+// (A similar situation exists when the pointer is grabbed with owner_events
+// True as the ButtonRelease may be received on a foreign [plugin] window).
+// Use this method to check for released buttons when the pointer returns to a
+// Gecko window.
+void
+nsWindow::DispatchMissedButtonReleases(GdkEventCrossing *aGdkEvent)
+{
+ guint changed = aGdkEvent->state ^ gButtonState;
+ // Only consider button releases.
+ // (Ignore button presses that occurred outside Gecko.)
+ guint released = changed & gButtonState;
+ gButtonState = aGdkEvent->state;
+
+ // Loop over each button, excluding mouse wheel buttons 4 and 5 for which
+ // GDK ignores releases.
+ for (guint buttonMask = GDK_BUTTON1_MASK;
+ buttonMask <= GDK_BUTTON3_MASK;
+ buttonMask <<= 1) {
+
+ if (released & buttonMask) {
+ int16_t buttonType;
+ switch (buttonMask) {
+ case GDK_BUTTON1_MASK:
+ buttonType = WidgetMouseEvent::eLeftButton;
+ break;
+ case GDK_BUTTON2_MASK:
+ buttonType = WidgetMouseEvent::eMiddleButton;
+ break;
+ default:
+ NS_ASSERTION(buttonMask == GDK_BUTTON3_MASK,
+ "Unexpected button mask");
+ buttonType = WidgetMouseEvent::eRightButton;
+ }
+
+ LOG(("Synthesized button %u release on %p\n",
+ guint(buttonType + 1), (void *)this));
+
+ // Dispatch a synthesized button up event to tell Gecko about the
+ // change in state. This event is marked as synthesized so that
+ // it is not dispatched as a DOM event, because we don't know the
+ // position, widget, modifiers, or time/order.
+ WidgetMouseEvent synthEvent(true, eMouseUp, this,
+ WidgetMouseEvent::eSynthesized);
+ synthEvent.button = buttonType;
+ DispatchInputEvent(&synthEvent);
+ }
+ }
+}
+
+void
+nsWindow::InitButtonEvent(WidgetMouseEvent& aEvent,
+ GdkEventButton* aGdkEvent)
+{
+ aEvent.mRefPoint = GetRefPoint(this, aGdkEvent);
+
+ guint modifierState = aGdkEvent->state;
+ // aEvent's state includes the button state from immediately before this
+ // event. If aEvent is a mousedown or mouseup event, we need to update
+ // the button state.
+ guint buttonMask = 0;
+ switch (aGdkEvent->button) {
+ case 1:
+ buttonMask = GDK_BUTTON1_MASK;
+ break;
+ case 2:
+ buttonMask = GDK_BUTTON2_MASK;
+ break;
+ case 3:
+ buttonMask = GDK_BUTTON3_MASK;
+ break;
+ }
+ if (aGdkEvent->type == GDK_BUTTON_RELEASE) {
+ modifierState &= ~buttonMask;
+ } else {
+ modifierState |= buttonMask;
+ }
+
+ KeymapWrapper::InitInputEvent(aEvent, modifierState);
+
+ aEvent.AssignEventTime(GetWidgetEventTime(aGdkEvent->time));
+
+ switch (aGdkEvent->type) {
+ case GDK_2BUTTON_PRESS:
+ aEvent.mClickCount = 2;
+ break;
+ case GDK_3BUTTON_PRESS:
+ aEvent.mClickCount = 3;
+ break;
+ // default is one click
+ default:
+ aEvent.mClickCount = 1;
+ }
+}
+
+static guint ButtonMaskFromGDKButton(guint button)
+{
+ return GDK_BUTTON1_MASK << (button - 1);
+}
+
+void
+nsWindow::OnButtonPressEvent(GdkEventButton *aEvent)
+{
+ LOG(("Button %u press on %p\n", aEvent->button, (void *)this));
+
+ // If you double click in GDK, it will actually generate a second
+ // GDK_BUTTON_PRESS before sending the GDK_2BUTTON_PRESS, and this is
+ // different than the DOM spec. GDK puts this in the queue
+ // programatically, so it's safe to assume that if there's a
+ // double click in the queue, it was generated so we can just drop
+ // this click.
+ GdkEvent *peekedEvent = gdk_event_peek();
+ if (peekedEvent) {
+ GdkEventType type = peekedEvent->any.type;
+ gdk_event_free(peekedEvent);
+ if (type == GDK_2BUTTON_PRESS || type == GDK_3BUTTON_PRESS)
+ return;
+ }
+
+ nsWindow *containerWindow = GetContainerWindow();
+ if (!gFocusWindow && containerWindow) {
+ containerWindow->DispatchActivateEvent();
+ }
+
+ // check to see if we should rollup
+ if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false))
+ return;
+
+ gdouble pressure = 0;
+ gdk_event_get_axis ((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
+ mLastMotionPressure = pressure;
+
+ uint16_t domButton;
+ switch (aEvent->button) {
+ case 1:
+ domButton = WidgetMouseEvent::eLeftButton;
+ break;
+ case 2:
+ domButton = WidgetMouseEvent::eMiddleButton;
+ break;
+ case 3:
+ domButton = WidgetMouseEvent::eRightButton;
+ break;
+ // These are mapped to horizontal scroll
+ case 6:
+ case 7:
+ NS_WARNING("We're not supporting legacy horizontal scroll event");
+ return;
+ // Map buttons 8-9 to back/forward
+ case 8:
+ DispatchCommandEvent(nsGkAtoms::Back);
+ return;
+ case 9:
+ DispatchCommandEvent(nsGkAtoms::Forward);
+ return;
+ default:
+ return;
+ }
+
+ gButtonState |= ButtonMaskFromGDKButton(aEvent->button);
+
+ WidgetMouseEvent event(true, eMouseDown, this, WidgetMouseEvent::eReal);
+ event.button = domButton;
+ InitButtonEvent(event, aEvent);
+ event.pressure = mLastMotionPressure;
+
+ DispatchInputEvent(&event);
+
+ // right menu click on linux should also pop up a context menu
+ if (domButton == WidgetMouseEvent::eRightButton &&
+ MOZ_LIKELY(!mIsDestroyed)) {
+ WidgetMouseEvent contextMenuEvent(true, eContextMenu, this,
+ WidgetMouseEvent::eReal);
+ InitButtonEvent(contextMenuEvent, aEvent);
+ contextMenuEvent.pressure = mLastMotionPressure;
+ DispatchInputEvent(&contextMenuEvent);
+ }
+}
+
+void
+nsWindow::OnButtonReleaseEvent(GdkEventButton *aEvent)
+{
+ LOG(("Button %u release on %p\n", aEvent->button, (void *)this));
+
+ uint16_t domButton;
+ switch (aEvent->button) {
+ case 1:
+ domButton = WidgetMouseEvent::eLeftButton;
+ break;
+ case 2:
+ domButton = WidgetMouseEvent::eMiddleButton;
+ break;
+ case 3:
+ domButton = WidgetMouseEvent::eRightButton;
+ break;
+ default:
+ return;
+ }
+
+ gButtonState &= ~ButtonMaskFromGDKButton(aEvent->button);
+
+ WidgetMouseEvent event(true, eMouseUp, this,
+ WidgetMouseEvent::eReal);
+ event.button = domButton;
+ InitButtonEvent(event, aEvent);
+ gdouble pressure = 0;
+ gdk_event_get_axis ((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
+ event.pressure = pressure ? pressure : mLastMotionPressure;
+
+ DispatchInputEvent(&event);
+ mLastMotionPressure = pressure;
+}
+
+void
+nsWindow::OnContainerFocusInEvent(GdkEventFocus *aEvent)
+{
+ LOGFOCUS(("OnContainerFocusInEvent [%p]\n", (void *)this));
+
+ // Unset the urgency hint, if possible
+ GtkWidget* top_window = GetToplevelWidget();
+ if (top_window && (gtk_widget_get_visible(top_window)))
+ SetUrgencyHint(top_window, false);
+
+ // Return if being called within SetFocus because the focus manager
+ // already knows that the window is active.
+ if (gBlockActivateEvent) {
+ LOGFOCUS(("activated notification is blocked [%p]\n", (void *)this));
+ return;
+ }
+
+ // If keyboard input will be accepted, the focus manager will call
+ // SetFocus to set the correct window.
+ gFocusWindow = nullptr;
+
+ DispatchActivateEvent();
+
+ if (!gFocusWindow) {
+ // We don't really have a window for dispatching key events, but
+ // setting a non-nullptr value here prevents OnButtonPressEvent() from
+ // dispatching an activation notification if the widget is already
+ // active.
+ gFocusWindow = this;
+ }
+
+ LOGFOCUS(("Events sent from focus in event [%p]\n", (void *)this));
+}
+
+void
+nsWindow::OnContainerFocusOutEvent(GdkEventFocus *aEvent)
+{
+ LOGFOCUS(("OnContainerFocusOutEvent [%p]\n", (void *)this));
+
+ if (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) {
+ nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID);
+ nsCOMPtr<nsIDragSession> dragSession;
+ dragService->GetCurrentSession(getter_AddRefs(dragSession));
+
+ // Rollup popups when a window is focused out unless a drag is occurring.
+ // This check is because drags grab the keyboard and cause a focus out on
+ // versions of GTK before 2.18.
+ bool shouldRollup = !dragSession;
+ if (!shouldRollup) {
+ // we also roll up when a drag is from a different application
+ nsCOMPtr<nsIDOMNode> sourceNode;
+ dragSession->GetSourceNode(getter_AddRefs(sourceNode));
+ shouldRollup = (sourceNode == nullptr);
+ }
+
+ if (shouldRollup) {
+ CheckForRollup(0, 0, false, true);
+ }
+ }
+
+#if (MOZ_WIDGET_GTK == 2) && defined(MOZ_X11)
+ // plugin lose focus
+ if (gPluginFocusWindow) {
+ RefPtr<nsWindow> kungFuDeathGrip = gPluginFocusWindow;
+ gPluginFocusWindow->LoseNonXEmbedPluginFocus();
+ }
+#endif /* MOZ_X11 && MOZ_WIDGET_GTK == 2 */
+
+ if (gFocusWindow) {
+ RefPtr<nsWindow> kungFuDeathGrip = gFocusWindow;
+ if (gFocusWindow->mIMContext) {
+ gFocusWindow->mIMContext->OnBlurWindow(gFocusWindow);
+ }
+ gFocusWindow = nullptr;
+ }
+
+ DispatchDeactivateEvent();
+
+ LOGFOCUS(("Done with container focus out [%p]\n", (void *)this));
+}
+
+bool
+nsWindow::DispatchCommandEvent(nsIAtom* aCommand)
+{
+ nsEventStatus status;
+ WidgetCommandEvent event(true, nsGkAtoms::onAppCommand, aCommand, this);
+ DispatchEvent(&event, status);
+ return TRUE;
+}
+
+bool
+nsWindow::DispatchContentCommandEvent(EventMessage aMsg)
+{
+ nsEventStatus status;
+ WidgetContentCommandEvent event(true, aMsg, this);
+ DispatchEvent(&event, status);
+ return TRUE;
+}
+
+static bool
+IsCtrlAltTab(GdkEventKey *aEvent)
+{
+ return aEvent->keyval == GDK_Tab &&
+ KeymapWrapper::AreModifiersActive(
+ KeymapWrapper::CTRL | KeymapWrapper::ALT, aEvent->state);
+}
+
+bool
+nsWindow::DispatchKeyDownEvent(GdkEventKey *aEvent, bool *aCancelled)
+{
+ NS_PRECONDITION(aCancelled, "aCancelled must not be null");
+
+ *aCancelled = false;
+
+ if (IsCtrlAltTab(aEvent)) {
+ return false;
+ }
+
+ RefPtr<TextEventDispatcher> 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;
+}
+
+WidgetEventTime
+nsWindow::GetWidgetEventTime(guint32 aEventTime)
+{
+ return WidgetEventTime(aEventTime, GetEventTimeStamp(aEventTime));
+}
+
+TimeStamp
+nsWindow::GetEventTimeStamp(guint32 aEventTime)
+{
+ if (MOZ_UNLIKELY(!mGdkWindow)) {
+ // nsWindow has been Destroy()ed.
+ return TimeStamp::Now();
+ }
+ if (aEventTime == 0) {
+ // Some X11 and GDK events may be received with a time of 0 to indicate
+ // that they are synthetic events. Some input method editors do this.
+ // In this case too, just return the current timestamp.
+ return TimeStamp::Now();
+ }
+ CurrentX11TimeGetter* getCurrentTime = GetCurrentTimeGetter();
+ MOZ_ASSERT(getCurrentTime,
+ "Null current time getter despite having a window");
+ return TimeConverter().GetTimeStampFromSystemTime(aEventTime,
+ *getCurrentTime);
+}
+
+mozilla::CurrentX11TimeGetter*
+nsWindow::GetCurrentTimeGetter() {
+ MOZ_ASSERT(mGdkWindow, "Expected mGdkWindow to be set");
+ if (MOZ_UNLIKELY(!mCurrentTimeGetter)) {
+ mCurrentTimeGetter = MakeUnique<CurrentX11TimeGetter>(mGdkWindow);
+ }
+ return mCurrentTimeGetter.get();
+}
+
+gboolean
+nsWindow::OnKeyPressEvent(GdkEventKey *aEvent)
+{
+ LOGFOCUS(("OnKeyPressEvent [%p]\n", (void *)this));
+
+ // if we are in the middle of composing text, XIM gets to see it
+ // before mozilla does.
+ // FYI: Don't dispatch keydown event before notifying IME of the event
+ // because IME may send a key event synchronously and consume the
+ // original event.
+ bool IMEWasEnabled = false;
+ if (mIMContext) {
+ IMEWasEnabled = mIMContext->IsEnabled();
+ if (mIMContext->OnKeyEvent(this, aEvent)) {
+ return TRUE;
+ }
+ }
+
+ // work around for annoying things.
+ if (IsCtrlAltTab(aEvent)) {
+ return TRUE;
+ }
+
+ nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
+
+ // Dispatch keydown event always. At auto repeating, we should send
+ // KEYDOWN -> KEYPRESS -> KEYDOWN -> KEYPRESS ... -> KEYUP
+ // However, old distributions (e.g., Ubuntu 9.10) sent native key
+ // release event, so, on such platform, the DOM events will be:
+ // KEYDOWN -> KEYPRESS -> KEYUP -> KEYDOWN -> KEYPRESS -> KEYUP...
+
+ bool isKeyDownCancelled = false;
+ if (DispatchKeyDownEvent(aEvent, &isKeyDownCancelled) &&
+ (MOZ_UNLIKELY(mIsDestroyed) || isKeyDownCancelled)) {
+ return TRUE;
+ }
+
+ // If a keydown event handler causes to enable IME, i.e., it moves
+ // focus from IME unusable content to IME usable editor, we should
+ // send the native key event to IME for the first input on the editor.
+ if (!IMEWasEnabled && mIMContext && mIMContext->IsEnabled()) {
+ // Notice our keydown event was already dispatched. This prevents
+ // unnecessary DOM keydown event in the editor.
+ if (mIMContext->OnKeyEvent(this, aEvent, true)) {
+ return TRUE;
+ }
+ }
+
+ // Look for specialized app-command keys
+ switch (aEvent->keyval) {
+ case GDK_Back:
+ return DispatchCommandEvent(nsGkAtoms::Back);
+ case GDK_Forward:
+ return DispatchCommandEvent(nsGkAtoms::Forward);
+ case GDK_Refresh:
+ return DispatchCommandEvent(nsGkAtoms::Reload);
+ case GDK_Stop:
+ return DispatchCommandEvent(nsGkAtoms::Stop);
+ case GDK_Search:
+ return DispatchCommandEvent(nsGkAtoms::Search);
+ case GDK_Favorites:
+ return DispatchCommandEvent(nsGkAtoms::Bookmarks);
+ case GDK_HomePage:
+ return DispatchCommandEvent(nsGkAtoms::Home);
+ case GDK_Copy:
+ case GDK_F16: // F16, F20, F18, F14 are old keysyms for Copy Cut Paste Undo
+ return DispatchContentCommandEvent(eContentCommandCopy);
+ case GDK_Cut:
+ case GDK_F20:
+ return DispatchContentCommandEvent(eContentCommandCut);
+ case GDK_Paste:
+ case GDK_F18:
+ return DispatchContentCommandEvent(eContentCommandPaste);
+ case GDK_Redo:
+ return DispatchContentCommandEvent(eContentCommandRedo);
+ case GDK_Undo:
+ case GDK_F14:
+ return DispatchContentCommandEvent(eContentCommandUndo);
+ }
+
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, this);
+ KeymapWrapper::InitKeyEvent(keypressEvent, aEvent);
+
+ // before we dispatch a key, check if it's the context menu key.
+ // If so, send a context menu key event instead.
+ if (is_context_menu_key(keypressEvent)) {
+ WidgetMouseEvent contextMenuEvent(true, eContextMenu, this,
+ WidgetMouseEvent::eReal,
+ WidgetMouseEvent::eContextMenuKey);
+
+ contextMenuEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ contextMenuEvent.AssignEventTime(GetWidgetEventTime(aEvent->time));
+ contextMenuEvent.mClickCount = 1;
+ KeymapWrapper::InitInputEvent(contextMenuEvent, aEvent->state);
+ DispatchInputEvent(&contextMenuEvent);
+ } else {
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return TRUE;
+ }
+
+ // If the character code is in the BMP, send the key press event.
+ // Otherwise, send a compositionchange event with the equivalent UTF-16
+ // string.
+ // TODO: Investigate other browser's behavior in this case because
+ // this hack is odd for UI Events.
+ nsEventStatus status = nsEventStatus_eIgnore;
+ if (keypressEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING ||
+ keypressEvent.mKeyValue.Length() == 1) {
+ dispatcher->MaybeDispatchKeypressEvents(keypressEvent,
+ status, aEvent);
+ } else {
+ WidgetEventTime eventTime = GetWidgetEventTime(aEvent->time);
+ dispatcher->CommitComposition(status, &keypressEvent.mKeyValue,
+ &eventTime);
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+nsWindow::OnKeyReleaseEvent(GdkEventKey *aEvent)
+{
+ LOGFOCUS(("OnKeyReleaseEvent [%p]\n", (void *)this));
+
+ if (mIMContext && mIMContext->OnKeyEvent(this, aEvent)) {
+ return TRUE;
+ }
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ WidgetKeyboardEvent keyupEvent(true, eKeyUp, this);
+ KeymapWrapper::InitKeyEvent(keyupEvent, aEvent);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ dispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status, aEvent);
+
+ return TRUE;
+}
+
+void
+nsWindow::OnScrollEvent(GdkEventScroll *aEvent)
+{
+ // check to see if we should rollup
+ if (CheckForRollup(aEvent->x_root, aEvent->y_root, true, false))
+ return;
+#if GTK_CHECK_VERSION(3,4,0)
+ // check for duplicate legacy scroll event, see GNOME bug 726878
+ if (aEvent->direction != GDK_SCROLL_SMOOTH &&
+ mLastScrollEventTime == aEvent->time)
+ return;
+#endif
+ WidgetWheelEvent wheelEvent(true, eWheel, this);
+ wheelEvent.mDeltaMode = nsIDOMWheelEvent::DOM_DELTA_LINE;
+ switch (aEvent->direction) {
+#if GTK_CHECK_VERSION(3,4,0)
+ case GDK_SCROLL_SMOOTH:
+ {
+ // As of GTK 3.4, all directional scroll events are provided by
+ // the GDK_SCROLL_SMOOTH direction on XInput2 devices.
+ mLastScrollEventTime = aEvent->time;
+ // TODO - use a more appropriate scrolling unit than lines.
+ // Multiply event deltas by 3 to emulate legacy behaviour.
+ wheelEvent.mDeltaX = aEvent->delta_x * 3;
+ wheelEvent.mDeltaY = aEvent->delta_y * 3;
+ wheelEvent.mIsNoLineOrPageDelta = true;
+ // This next step manually unsets smooth scrolling for touch devices
+ // that trigger GDK_SCROLL_SMOOTH. We use the slave device, which
+ // represents the actual input.
+ GdkDevice *device = gdk_event_get_source_device((GdkEvent*)aEvent);
+ GdkInputSource source = gdk_device_get_source(device);
+ if (source == GDK_SOURCE_TOUCHSCREEN ||
+ source == GDK_SOURCE_TOUCHPAD) {
+ wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY;
+ }
+ break;
+ }
+#endif
+ case GDK_SCROLL_UP:
+ wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = -3;
+ break;
+ case GDK_SCROLL_DOWN:
+ wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = 3;
+ break;
+ case GDK_SCROLL_LEFT:
+ wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = -1;
+ break;
+ case GDK_SCROLL_RIGHT:
+ wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = 1;
+ break;
+ }
+
+ wheelEvent.mRefPoint = GetRefPoint(this, aEvent);
+
+ KeymapWrapper::InitInputEvent(wheelEvent, aEvent->state);
+
+ wheelEvent.AssignEventTime(GetWidgetEventTime(aEvent->time));
+
+ DispatchInputEvent(&wheelEvent);
+}
+
+void
+nsWindow::OnVisibilityNotifyEvent(GdkEventVisibility *aEvent)
+{
+ LOGDRAW(("Visibility event %i on [%p] %p\n",
+ aEvent->state, this, aEvent->window));
+
+ if (!mGdkWindow)
+ return;
+
+ switch (aEvent->state) {
+ case GDK_VISIBILITY_UNOBSCURED:
+ case GDK_VISIBILITY_PARTIAL:
+ if (mIsFullyObscured && mHasMappedToplevel) {
+ // GDK_EXPOSE events have been ignored, so make sure GDK
+ // doesn't think that the window has already been painted.
+ gdk_window_invalidate_rect(mGdkWindow, nullptr, FALSE);
+ }
+
+ mIsFullyObscured = false;
+
+ // if we have to retry the grab, retry it.
+ EnsureGrabs();
+ break;
+ default: // includes GDK_VISIBILITY_FULLY_OBSCURED
+ mIsFullyObscured = true;
+ break;
+ }
+}
+
+void
+nsWindow::OnWindowStateEvent(GtkWidget *aWidget, GdkEventWindowState *aEvent)
+{
+ LOG(("nsWindow::OnWindowStateEvent [%p] changed %d new_window_state %d\n",
+ (void *)this, aEvent->changed_mask, aEvent->new_window_state));
+
+ if (IS_MOZ_CONTAINER(aWidget)) {
+ // This event is notifying the container widget of changes to the
+ // toplevel window. Just detect changes affecting whether windows are
+ // viewable.
+ //
+ // (A visibility notify event is sent to each window that becomes
+ // viewable when the toplevel is mapped, but we can't rely on that for
+ // setting mHasMappedToplevel because these toplevel window state
+ // events are asynchronous. The windows in the hierarchy now may not
+ // be the same windows as when the toplevel was mapped, so they may
+ // not get VisibilityNotify events.)
+ bool mapped =
+ !(aEvent->new_window_state &
+ (GDK_WINDOW_STATE_ICONIFIED|GDK_WINDOW_STATE_WITHDRAWN));
+ if (mHasMappedToplevel != mapped) {
+ SetHasMappedToplevel(mapped);
+ }
+ return;
+ }
+ // else the widget is a shell widget.
+
+ // We don't care about anything but changes in the maximized/icon/fullscreen
+ // states
+ if ((aEvent->changed_mask
+ & (GDK_WINDOW_STATE_ICONIFIED |
+ GDK_WINDOW_STATE_MAXIMIZED |
+ GDK_WINDOW_STATE_FULLSCREEN)) == 0) {
+ return;
+ }
+
+ if (aEvent->new_window_state & GDK_WINDOW_STATE_ICONIFIED) {
+ LOG(("\tIconified\n"));
+ mSizeState = nsSizeMode_Minimized;
+#ifdef ACCESSIBILITY
+ DispatchMinimizeEventAccessible();
+#endif //ACCESSIBILITY
+ }
+ else if (aEvent->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) {
+ LOG(("\tFullscreen\n"));
+ mSizeState = nsSizeMode_Fullscreen;
+ }
+ else if (aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
+ LOG(("\tMaximized\n"));
+ mSizeState = nsSizeMode_Maximized;
+#ifdef ACCESSIBILITY
+ DispatchMaximizeEventAccessible();
+#endif //ACCESSIBILITY
+ }
+ else {
+ LOG(("\tNormal\n"));
+ mSizeState = nsSizeMode_Normal;
+#ifdef ACCESSIBILITY
+ DispatchRestoreEventAccessible();
+#endif //ACCESSIBILITY
+ }
+
+ if (mWidgetListener) {
+ mWidgetListener->SizeModeChanged(mSizeState);
+ if (aEvent->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
+ mWidgetListener->FullscreenChanged(
+ aEvent->new_window_state & GDK_WINDOW_STATE_FULLSCREEN);
+ }
+ }
+}
+
+void
+nsWindow::ThemeChanged()
+{
+ NotifyThemeChanged();
+
+ if (!mGdkWindow || MOZ_UNLIKELY(mIsDestroyed))
+ return;
+
+ // Dispatch theme change notification to all child windows
+ GList *children =
+ gdk_window_peek_children(mGdkWindow);
+ while (children) {
+ GdkWindow *gdkWin = GDK_WINDOW(children->data);
+
+ nsWindow *win = (nsWindow*) g_object_get_data(G_OBJECT(gdkWin),
+ "nsWindow");
+
+ if (win && win != this) { // guard against infinite recursion
+ RefPtr<nsWindow> kungFuDeathGrip = win;
+ win->ThemeChanged();
+ }
+
+ children = children->next;
+ }
+}
+
+void
+nsWindow::OnDPIChanged()
+{
+ if (mWidgetListener) {
+ nsIPresShell* presShell = mWidgetListener->GetPresShell();
+ if (presShell) {
+ presShell->BackingScaleFactorChanged();
+ // Update menu's font size etc
+ presShell->ThemeChanged();
+ }
+ }
+}
+
+void
+nsWindow::OnCheckResize()
+{
+ mPendingConfigures++;
+}
+
+void
+nsWindow::DispatchDragEvent(EventMessage aMsg, const LayoutDeviceIntPoint& aRefPoint,
+ guint aTime)
+{
+ WidgetDragEvent event(true, aMsg, this);
+
+ if (aMsg == eDragOver) {
+ InitDragEvent(event);
+ }
+
+ event.mRefPoint = aRefPoint;
+ event.AssignEventTime(GetWidgetEventTime(aTime));
+
+ DispatchInputEvent(&event);
+}
+
+void
+nsWindow::OnDragDataReceivedEvent(GtkWidget *aWidget,
+ GdkDragContext *aDragContext,
+ gint aX,
+ gint aY,
+ GtkSelectionData *aSelectionData,
+ guint aInfo,
+ guint aTime,
+ gpointer aData)
+{
+ LOGDRAG(("nsWindow::OnDragDataReceived(%p)\n", (void*)this));
+
+ nsDragService::GetInstance()->
+ TargetDataReceived(aWidget, aDragContext, aX, aY,
+ aSelectionData, aInfo, aTime);
+}
+
+#if GTK_CHECK_VERSION(3,4,0)
+gboolean
+nsWindow::OnTouchEvent(GdkEventTouch* aEvent)
+{
+ if (!mHandleTouchEvent) {
+ return FALSE;
+ }
+
+ EventMessage msg;
+ switch (aEvent->type) {
+ case GDK_TOUCH_BEGIN:
+ msg = eTouchStart;
+ break;
+ case GDK_TOUCH_UPDATE:
+ msg = eTouchMove;
+ break;
+ case GDK_TOUCH_END:
+ msg = eTouchEnd;
+ break;
+ case GDK_TOUCH_CANCEL:
+ msg = eTouchCancel;
+ break;
+ default:
+ return FALSE;
+ }
+
+ LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent);
+
+ int32_t id;
+ RefPtr<dom::Touch> touch;
+ if (mTouches.Remove(aEvent->sequence, getter_AddRefs(touch))) {
+ id = touch->mIdentifier;
+ } else {
+ id = ++gLastTouchID & 0x7FFFFFFF;
+ }
+
+ touch = new dom::Touch(id, touchPoint, LayoutDeviceIntPoint(1, 1),
+ 0.0f, 0.0f);
+
+ WidgetTouchEvent event(true, msg, this);
+ KeymapWrapper::InitInputEvent(event, aEvent->state);
+ event.mTime = aEvent->time;
+
+ if (aEvent->type == GDK_TOUCH_BEGIN || aEvent->type == GDK_TOUCH_UPDATE) {
+ mTouches.Put(aEvent->sequence, touch.forget());
+ // add all touch points to event object
+ for (auto iter = mTouches.Iter(); !iter.Done(); iter.Next()) {
+ event.mTouches.AppendElement(new dom::Touch(*iter.UserData()));
+ }
+ } else if (aEvent->type == GDK_TOUCH_END ||
+ aEvent->type == GDK_TOUCH_CANCEL) {
+ *event.mTouches.AppendElement() = touch.forget();
+ }
+
+ DispatchInputEvent(&event);
+ return TRUE;
+}
+#endif
+
+static void
+GetBrandName(nsXPIDLString& brandName)
+{
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ if (bundleService)
+ bundleService->CreateBundle(
+ "chrome://branding/locale/brand.properties",
+ getter_AddRefs(bundle));
+
+ if (bundle)
+ bundle->GetStringFromName(
+ u"brandShortName",
+ getter_Copies(brandName));
+
+ if (brandName.IsEmpty())
+ brandName.AssignLiteral(u"Mozilla");
+}
+
+static GdkWindow *
+CreateGdkWindow(GdkWindow *parent, GtkWidget *widget)
+{
+ GdkWindowAttr attributes;
+ gint attributes_mask = GDK_WA_VISUAL;
+
+ attributes.event_mask = kEvents;
+
+ attributes.width = 1;
+ attributes.height = 1;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual(widget);
+ attributes.window_type = GDK_WINDOW_CHILD;
+
+#if (MOZ_WIDGET_GTK == 2)
+ attributes_mask |= GDK_WA_COLORMAP;
+ attributes.colormap = gtk_widget_get_colormap(widget);
+#endif
+
+ GdkWindow *window = gdk_window_new(parent, &attributes, attributes_mask);
+ gdk_window_set_user_data(window, widget);
+
+// GTK3 TODO?
+#if (MOZ_WIDGET_GTK == 2)
+ /* set the default pixmap to None so that you don't end up with the
+ gtk default which is BlackPixel. */
+ gdk_window_set_back_pixmap(window, nullptr, FALSE);
+#endif
+
+ return window;
+}
+
+nsresult
+nsWindow::Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData)
+{
+ // only set the base parent if we're going to be a dialog or a
+ // toplevel
+ nsIWidget *baseParent = aInitData &&
+ (aInitData->mWindowType == eWindowType_dialog ||
+ aInitData->mWindowType == eWindowType_toplevel ||
+ aInitData->mWindowType == eWindowType_invisible) ?
+ nullptr : aParent;
+
+#ifdef ACCESSIBILITY
+ // Send a DBus message to check whether a11y is enabled
+ a11y::PreInit();
+#endif
+
+ // Ensure that the toolkit is created.
+ nsGTKToolkit::GetToolkit();
+
+ // initialize all the common bits of this class
+ BaseCreate(baseParent, aInitData);
+
+ // Do we need to listen for resizes?
+ bool listenForResizes = false;;
+ if (aNativeParent || (aInitData && aInitData->mListenForResizes))
+ listenForResizes = true;
+
+ // and do our common creation
+ CommonCreate(aParent, listenForResizes);
+
+ // save our bounds
+ mBounds = aRect;
+ ConstrainSize(&mBounds.width, &mBounds.height);
+
+ // figure out our parent window
+ GtkWidget *parentMozContainer = nullptr;
+ GtkContainer *parentGtkContainer = nullptr;
+ GdkWindow *parentGdkWindow = nullptr;
+ GtkWindow *topLevelParent = nullptr;
+ nsWindow *parentnsWindow = nullptr;
+ GtkWidget *eventWidget = nullptr;
+ bool shellHasCSD = false;
+
+ if (aParent) {
+ parentnsWindow = static_cast<nsWindow*>(aParent);
+ parentGdkWindow = parentnsWindow->mGdkWindow;
+ } else if (aNativeParent && GDK_IS_WINDOW(aNativeParent)) {
+ parentGdkWindow = GDK_WINDOW(aNativeParent);
+ parentnsWindow = get_window_for_gdk_window(parentGdkWindow);
+ if (!parentnsWindow)
+ return NS_ERROR_FAILURE;
+
+ } else if (aNativeParent && GTK_IS_CONTAINER(aNativeParent)) {
+ parentGtkContainer = GTK_CONTAINER(aNativeParent);
+ }
+
+ if (parentGdkWindow) {
+ // get the widget for the window - it should be a moz container
+ parentMozContainer = parentnsWindow->GetMozContainerWidget();
+ if (!parentMozContainer)
+ return NS_ERROR_FAILURE;
+
+ // get the toplevel window just in case someone needs to use it
+ // for setting transients or whatever.
+ topLevelParent =
+ GTK_WINDOW(gtk_widget_get_toplevel(parentMozContainer));
+ }
+
+ // ok, create our windows
+ switch (mWindowType) {
+ case eWindowType_dialog:
+ case eWindowType_popup:
+ case eWindowType_toplevel:
+ case eWindowType_invisible: {
+ mIsTopLevel = true;
+
+ // Popups that are not noautohide are only temporary. The are used
+ // for menus and the like and disappear when another window is used.
+ // For most popups, use the standard GtkWindowType GTK_WINDOW_POPUP,
+ // which will use a Window with the override-redirect attribute
+ // (for temporary windows).
+ // For long-lived windows, their stacking order is managed by the
+ // window manager, as indicated by GTK_WINDOW_TOPLEVEL ...
+ GtkWindowType type =
+ mWindowType != eWindowType_popup || aInitData->mNoAutoHide ?
+ GTK_WINDOW_TOPLEVEL : GTK_WINDOW_POPUP;
+ mShell = gtk_window_new(type);
+
+ // We only move a general managed toplevel window if someone has
+ // actually placed the window somewhere. If no placement has taken
+ // place, we just let the window manager Do The Right Thing.
+ NativeResize();
+
+ if (mWindowType == eWindowType_dialog) {
+ SetDefaultIcon();
+ gtk_window_set_wmclass(GTK_WINDOW(mShell), "Dialog",
+ gdk_get_program_class());
+ gtk_window_set_type_hint(GTK_WINDOW(mShell),
+ GDK_WINDOW_TYPE_HINT_DIALOG);
+ gtk_window_set_transient_for(GTK_WINDOW(mShell),
+ topLevelParent);
+ }
+ else if (mWindowType == eWindowType_popup) {
+ // With popup windows, we want to control their position, so don't
+ // wait for the window manager to place them (which wouldn't
+ // happen with override-redirect windows anyway).
+ NativeMove();
+
+ gtk_window_set_wmclass(GTK_WINDOW(mShell), "Popup",
+ gdk_get_program_class());
+
+ if (aInitData->mSupportTranslucency) {
+ // 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.
+ GdkScreen *screen = gtk_widget_get_screen(mShell);
+ if (gdk_screen_is_composited(screen)) {
+#if (MOZ_WIDGET_GTK == 2)
+ GdkColormap *colormap =
+ gdk_screen_get_rgba_colormap(screen);
+ gtk_widget_set_colormap(mShell, colormap);
+#else
+ GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
+ gtk_widget_set_visual(mShell, visual);
+#endif
+ }
+ }
+ if (aInitData->mNoAutoHide) {
+ // ... but the window manager does not decorate this window,
+ // nor provide a separate taskbar icon.
+ if (mBorderStyle == eBorderStyle_default) {
+ gtk_window_set_decorated(GTK_WINDOW(mShell), FALSE);
+ }
+ else {
+ bool decorate = mBorderStyle & eBorderStyle_title;
+ gtk_window_set_decorated(GTK_WINDOW(mShell), decorate);
+ if (decorate) {
+ gtk_window_set_deletable(GTK_WINDOW(mShell), mBorderStyle & eBorderStyle_close);
+ }
+ }
+ gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mShell), TRUE);
+ // Element focus is managed by the parent window so the
+ // WM_HINTS input field is set to False to tell the window
+ // manager not to set input focus to this window ...
+ gtk_window_set_accept_focus(GTK_WINDOW(mShell), FALSE);
+#ifdef MOZ_X11
+ // ... but when the window manager offers focus through
+ // WM_TAKE_FOCUS, focus is requested on the parent window.
+ gtk_widget_realize(mShell);
+ gdk_window_add_filter(gtk_widget_get_window(mShell),
+ popup_take_focus_filter, nullptr);
+#endif
+ }
+
+ GdkWindowTypeHint gtkTypeHint;
+ if (aInitData->mIsDragPopup) {
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_DND;
+ mIsDragPopup = true;
+ }
+ else {
+ switch (aInitData->mPopupHint) {
+ case ePopupTypeMenu:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
+ break;
+ case ePopupTypeTooltip:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
+ break;
+ default:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
+ break;
+ }
+ }
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
+
+ if (topLevelParent) {
+ gtk_window_set_transient_for(GTK_WINDOW(mShell),
+ topLevelParent);
+ }
+ }
+ else { // must be eWindowType_toplevel
+ SetDefaultIcon();
+ gtk_window_set_wmclass(GTK_WINDOW(mShell), "Toplevel",
+ gdk_get_program_class());
+
+ // each toplevel window gets its own window group
+ GtkWindowGroup *group = gtk_window_group_new();
+ gtk_window_group_add_window(group, GTK_WINDOW(mShell));
+ g_object_unref(group);
+ }
+
+ // Create a container to hold child windows and child GtkWidgets.
+ GtkWidget *container = moz_container_new();
+ mContainer = MOZ_CONTAINER(container);
+
+#if (MOZ_WIDGET_GTK == 3)
+ // "csd" style is set when widget is realized so we need to call
+ // it explicitly now.
+ gtk_widget_realize(mShell);
+
+ // We can't draw directly to top-level window when client side
+ // decorations are enabled. We use container with GdkWindow instead.
+ GtkStyleContext* style = gtk_widget_get_style_context(mShell);
+ shellHasCSD = gtk_style_context_has_class(style, "csd");
+#endif
+ if (!shellHasCSD) {
+ // Use mShell's window for drawing and events.
+ gtk_widget_set_has_window(container, FALSE);
+ // Prevent GtkWindow from painting a background to flicker.
+ gtk_widget_set_app_paintable(mShell, TRUE);
+ }
+ // Set up event widget
+ eventWidget = shellHasCSD ? container : mShell;
+ gtk_widget_add_events(eventWidget, kEvents);
+
+ gtk_container_add(GTK_CONTAINER(mShell), container);
+ gtk_widget_realize(container);
+
+ // make sure this is the focus widget in the container
+ gtk_widget_show(container);
+ gtk_widget_grab_focus(container);
+
+ // the drawing window
+ mGdkWindow = gtk_widget_get_window(eventWidget);
+
+ if (mWindowType == eWindowType_popup) {
+ // gdk does not automatically set the cursor for "temporary"
+ // windows, which are what gtk uses for popups.
+
+ mCursor = eCursor_wait; // force SetCursor to actually set the
+ // cursor, even though our internal state
+ // indicates that we already have the
+ // standard cursor.
+ SetCursor(eCursor_standard);
+
+ if (aInitData->mNoAutoHide) {
+ gint wmd = ConvertBorderStyles(mBorderStyle);
+ if (wmd != -1)
+ gdk_window_set_decorations(mGdkWindow, (GdkWMDecoration) wmd);
+ }
+
+ // If the popup ignores mouse events, set an empty input shape.
+ if (aInitData->mMouseTransparent) {
+#if (MOZ_WIDGET_GTK == 2)
+ GdkRectangle rect = { 0, 0, 0, 0 };
+ GdkRegion *region = gdk_region_rectangle(&rect);
+
+ gdk_window_input_shape_combine_region(mGdkWindow, region, 0, 0);
+ gdk_region_destroy(region);
+#else
+ cairo_rectangle_int_t rect = { 0, 0, 0, 0 };
+ cairo_region_t *region = cairo_region_create_rectangle(&rect);
+
+ gdk_window_input_shape_combine_region(mGdkWindow, region, 0, 0);
+ cairo_region_destroy(region);
+#endif
+ }
+ }
+ }
+ break;
+ case eWindowType_plugin:
+ case eWindowType_plugin_ipc_chrome:
+ case eWindowType_plugin_ipc_content:
+ case eWindowType_child: {
+ if (parentMozContainer) {
+ mGdkWindow = CreateGdkWindow(parentGdkWindow, parentMozContainer);
+ mHasMappedToplevel = parentnsWindow->mHasMappedToplevel;
+ }
+ else if (parentGtkContainer) {
+ // This MozContainer has its own window for drawing and receives
+ // events because there is no mShell widget (corresponding to this
+ // nsWindow).
+ GtkWidget *container = moz_container_new();
+ mContainer = MOZ_CONTAINER(container);
+ eventWidget = container;
+ gtk_widget_add_events(eventWidget, kEvents);
+ gtk_container_add(parentGtkContainer, container);
+ gtk_widget_realize(container);
+
+ mGdkWindow = gtk_widget_get_window(container);
+ }
+ else {
+ NS_WARNING("Warning: tried to create a new child widget with no parent!");
+ return NS_ERROR_FAILURE;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ // label the drawing window with this object so we can find our way home
+ g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", this);
+
+ if (mContainer)
+ g_object_set_data(G_OBJECT(mContainer), "nsWindow", this);
+
+ if (mShell)
+ g_object_set_data(G_OBJECT(mShell), "nsWindow", this);
+
+ // attach listeners for events
+ if (mShell) {
+ g_signal_connect(mShell, "configure_event",
+ G_CALLBACK(configure_event_cb), nullptr);
+ g_signal_connect(mShell, "delete_event",
+ G_CALLBACK(delete_event_cb), nullptr);
+ g_signal_connect(mShell, "window_state_event",
+ G_CALLBACK(window_state_event_cb), nullptr);
+ g_signal_connect(mShell, "check-resize",
+ G_CALLBACK(check_resize_cb), nullptr);
+
+ GtkSettings* default_settings = gtk_settings_get_default();
+ g_signal_connect_after(default_settings,
+ "notify::gtk-theme-name",
+ G_CALLBACK(theme_changed_cb), this);
+ g_signal_connect_after(default_settings,
+ "notify::gtk-font-name",
+ G_CALLBACK(theme_changed_cb), this);
+ }
+
+ if (mContainer) {
+ // Widget signals
+ g_signal_connect(mContainer, "unrealize",
+ G_CALLBACK(container_unrealize_cb), nullptr);
+ g_signal_connect_after(mContainer, "size_allocate",
+ G_CALLBACK(size_allocate_cb), nullptr);
+ g_signal_connect(mContainer, "hierarchy-changed",
+ G_CALLBACK(hierarchy_changed_cb), nullptr);
+#if (MOZ_WIDGET_GTK == 3)
+ g_signal_connect(mContainer, "notify::scale-factor",
+ G_CALLBACK(scale_changed_cb), nullptr);
+#endif
+ // Initialize mHasMappedToplevel.
+ hierarchy_changed_cb(GTK_WIDGET(mContainer), nullptr);
+ // Expose, focus, key, and drag events are sent even to GTK_NO_WINDOW
+ // widgets.
+#if (MOZ_WIDGET_GTK == 2)
+ g_signal_connect(mContainer, "expose_event",
+ G_CALLBACK(expose_event_cb), nullptr);
+#else
+ g_signal_connect(G_OBJECT(mContainer), "draw",
+ G_CALLBACK(expose_event_cb), nullptr);
+#endif
+ g_signal_connect(mContainer, "focus_in_event",
+ G_CALLBACK(focus_in_event_cb), nullptr);
+ g_signal_connect(mContainer, "focus_out_event",
+ G_CALLBACK(focus_out_event_cb), nullptr);
+ g_signal_connect(mContainer, "key_press_event",
+ G_CALLBACK(key_press_event_cb), nullptr);
+ g_signal_connect(mContainer, "key_release_event",
+ G_CALLBACK(key_release_event_cb), nullptr);
+
+ gtk_drag_dest_set((GtkWidget *)mContainer,
+ (GtkDestDefaults)0,
+ nullptr,
+ 0,
+ (GdkDragAction)0);
+
+ g_signal_connect(mContainer, "drag_motion",
+ G_CALLBACK(drag_motion_event_cb), nullptr);
+ g_signal_connect(mContainer, "drag_leave",
+ G_CALLBACK(drag_leave_event_cb), nullptr);
+ g_signal_connect(mContainer, "drag_drop",
+ G_CALLBACK(drag_drop_event_cb), nullptr);
+ g_signal_connect(mContainer, "drag_data_received",
+ G_CALLBACK(drag_data_received_event_cb), nullptr);
+
+ GtkWidget *widgets[] = { GTK_WIDGET(mContainer),
+ !shellHasCSD ? mShell : nullptr };
+ for (size_t i = 0; i < ArrayLength(widgets) && widgets[i]; ++i) {
+ // Visibility events are sent to the owning widget of the relevant
+ // window but do not propagate to parent widgets so connect on
+ // mShell (if it exists) as well as mContainer.
+ g_signal_connect(widgets[i], "visibility-notify-event",
+ G_CALLBACK(visibility_notify_event_cb), nullptr);
+ // Similarly double buffering is controlled by the window's owning
+ // widget. Disable double buffering for painting directly to the
+ // X Window.
+ gtk_widget_set_double_buffered(widgets[i], FALSE);
+ }
+
+ // We create input contexts for all containers, except for
+ // toplevel popup windows
+ if (mWindowType != eWindowType_popup) {
+ mIMContext = new IMContextWrapper(this);
+ }
+ } else if (!mIMContext) {
+ nsWindow *container = GetContainerWindow();
+ if (container) {
+ mIMContext = container->mIMContext;
+ }
+ }
+
+ if (eventWidget) {
+#if (MOZ_WIDGET_GTK == 2)
+ // Don't let GTK mess with the shapes of our GdkWindows
+ GTK_PRIVATE_SET_FLAG(eventWidget, GTK_HAS_SHAPE_MASK);
+#endif
+
+ // These events are sent to the owning widget of the relevant window
+ // and propagate up to the first widget that handles the events, so we
+ // need only connect on mShell, if it exists, to catch events on its
+ // window and windows of mContainer.
+ g_signal_connect(eventWidget, "enter-notify-event",
+ G_CALLBACK(enter_notify_event_cb), nullptr);
+ g_signal_connect(eventWidget, "leave-notify-event",
+ G_CALLBACK(leave_notify_event_cb), nullptr);
+ g_signal_connect(eventWidget, "motion-notify-event",
+ G_CALLBACK(motion_notify_event_cb), nullptr);
+ g_signal_connect(eventWidget, "button-press-event",
+ G_CALLBACK(button_press_event_cb), nullptr);
+ g_signal_connect(eventWidget, "button-release-event",
+ G_CALLBACK(button_release_event_cb), nullptr);
+ g_signal_connect(eventWidget, "property-notify-event",
+ G_CALLBACK(property_notify_event_cb), nullptr);
+ g_signal_connect(eventWidget, "scroll-event",
+ G_CALLBACK(scroll_event_cb), nullptr);
+#if GTK_CHECK_VERSION(3,4,0)
+ g_signal_connect(eventWidget, "touch-event",
+ G_CALLBACK(touch_event_cb), nullptr);
+#endif
+ }
+
+ LOG(("nsWindow [%p]\n", (void *)this));
+ if (mShell) {
+ LOG(("\tmShell %p mContainer %p mGdkWindow %p 0x%lx\n",
+ mShell, mContainer, mGdkWindow,
+ gdk_x11_window_get_xid(mGdkWindow)));
+ } else if (mContainer) {
+ LOG(("\tmContainer %p mGdkWindow %p\n", mContainer, mGdkWindow));
+ }
+ else if (mGdkWindow) {
+ LOG(("\tmGdkWindow %p parent %p\n",
+ mGdkWindow, gdk_window_get_parent(mGdkWindow)));
+ }
+
+ // resize so that everything is set to the right dimensions
+ if (!mIsTopLevel)
+ Resize(mBounds.x, mBounds.y, mBounds.width, mBounds.height, false);
+
+#ifdef MOZ_X11
+ if (mIsX11Display && mGdkWindow) {
+ mXDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
+ mXWindow = gdk_x11_window_get_xid(mGdkWindow);
+
+ GdkVisual* gdkVisual = gdk_window_get_visual(mGdkWindow);
+ mXVisual = gdk_x11_visual_get_xvisual(gdkVisual);
+ mXDepth = gdk_visual_get_depth(gdkVisual);
+
+ mSurfaceProvider.Initialize(mXDisplay, mXWindow, mXVisual, mXDepth);
+ }
+#endif
+
+ return NS_OK;
+}
+
+void
+nsWindow::SetWindowClass(const nsAString &xulWinType)
+{
+ if (!mShell)
+ return;
+
+ const char *res_class = gdk_get_program_class();
+ if (!res_class)
+ return;
+
+ char *res_name = ToNewCString(xulWinType);
+ if (!res_name)
+ return;
+
+ const char *role = nullptr;
+
+ // 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;
+
+ gdk_window_set_role(mGdkWindow, role);
+
+#ifdef MOZ_X11
+ if (mIsX11Display) {
+ XClassHint *class_hint = XAllocClassHint();
+ if (!class_hint) {
+ free(res_name);
+ return;
+ }
+ class_hint->res_name = res_name;
+ class_hint->res_class = const_cast<char*>(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(mGdkWindow),
+ class_hint);
+ XFree(class_hint);
+ }
+#endif /* MOZ_X11 */
+
+ free(res_name);
+}
+
+void
+nsWindow::NativeResize()
+{
+ if (!AreBoundsSane()) {
+ // If someone has set this so that the needs show flag is false
+ // and it needs to be hidden, update the flag and hide the
+ // window. This flag will be cleared the next time someone
+ // hides the window or shows it. It also prevents us from
+ // calling NativeShow(false) excessively on the window which
+ // causes unneeded X traffic.
+ if (!mNeedsShow && mIsShown) {
+ mNeedsShow = true;
+ NativeShow(false);
+ }
+ return;
+ }
+
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
+
+ LOG(("nsWindow::NativeResize [%p] %d %d\n", (void *)this,
+ size.width, size.height));
+
+ if (mIsTopLevel) {
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ }
+ else if (mContainer) {
+ GtkWidget *widget = GTK_WIDGET(mContainer);
+ GtkAllocation allocation, prev_allocation;
+ gtk_widget_get_allocation(widget, &prev_allocation);
+ allocation.x = prev_allocation.x;
+ allocation.y = prev_allocation.y;
+ allocation.width = size.width;
+ allocation.height = size.height;
+ gtk_widget_size_allocate(widget, &allocation);
+ }
+ else if (mGdkWindow) {
+ gdk_window_resize(mGdkWindow, size.width, size.height);
+ }
+
+#ifdef MOZ_X11
+ // Notify the X11CompositorWidget of a ClientSizeChange
+ // This is different than OnSizeAllocate to catch initial sizing
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
+ }
+#endif
+
+ // Does it need to be shown because bounds were previously insane?
+ if (mNeedsShow && mIsShown) {
+ NativeShow(true);
+ }
+}
+
+void
+nsWindow::NativeMoveResize()
+{
+ if (!AreBoundsSane()) {
+ // If someone has set this so that the needs show flag is false
+ // and it needs to be hidden, update the flag and hide the
+ // window. This flag will be cleared the next time someone
+ // hides the window or shows it. It also prevents us from
+ // calling NativeShow(false) excessively on the window which
+ // causes unneeded X traffic.
+ if (!mNeedsShow && mIsShown) {
+ mNeedsShow = true;
+ NativeShow(false);
+ }
+ NativeMove();
+ }
+
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
+ GdkPoint topLeft = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
+
+ LOG(("nsWindow::NativeMoveResize [%p] %d %d %d %d\n", (void *)this,
+ topLeft.x, topLeft.y, size.width, size.height));
+
+ if (mIsTopLevel) {
+ // 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.
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ }
+ else if (mContainer) {
+ GtkAllocation allocation;
+ allocation.x = topLeft.x;
+ allocation.y = topLeft.y;
+ allocation.width = size.width;
+ allocation.height = size.height;
+ gtk_widget_size_allocate(GTK_WIDGET(mContainer), &allocation);
+ }
+ else if (mGdkWindow) {
+ gdk_window_move_resize(mGdkWindow,
+ topLeft.x, topLeft.y, size.width, size.height);
+ }
+
+#ifdef MOZ_X11
+ // Notify the X11CompositorWidget of a ClientSizeChange
+ // This is different than OnSizeAllocate to catch initial sizing
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
+ }
+#endif
+
+ // Does it need to be shown because bounds were previously insane?
+ if (mNeedsShow && mIsShown) {
+ NativeShow(true);
+ }
+}
+
+void
+nsWindow::NativeShow(bool aAction)
+{
+ if (aAction) {
+ // unset our flag now that our window has been shown
+ mNeedsShow = false;
+
+ if (mIsTopLevel) {
+ // Set up usertime/startupID metadata for the created window.
+ if (mWindowType != eWindowType_invisible) {
+ SetUserTimeAndStartupIDForActivatedWindow(mShell);
+ }
+
+ gtk_widget_show(mShell);
+ }
+ else if (mContainer) {
+ gtk_widget_show(GTK_WIDGET(mContainer));
+ }
+ else if (mGdkWindow) {
+ gdk_window_show_unraised(mGdkWindow);
+ }
+ }
+ else {
+ if (mIsTopLevel) {
+ // Workaround window freezes on GTK versions before 3.21.2 by
+ // ensuring that configure events get dispatched to windows before
+ // they are unmapped. See bug 1225044.
+ if (gtk_check_version(3, 21, 2) != nullptr && mPendingConfigures > 0) {
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(GTK_WIDGET(mShell), &allocation);
+
+ GdkEventConfigure event;
+ PodZero(&event);
+ event.type = GDK_CONFIGURE;
+ event.window = mGdkWindow;
+ event.send_event = TRUE;
+ event.x = allocation.x;
+ event.y = allocation.y;
+ event.width = allocation.width;
+ event.height = allocation.height;
+
+ auto shellClass = GTK_WIDGET_GET_CLASS(mShell);
+ for (unsigned int i = 0; i < mPendingConfigures; i++) {
+ Unused << shellClass->configure_event(mShell, &event);
+ }
+ mPendingConfigures = 0;
+ }
+
+ gtk_widget_hide(mShell);
+
+ ClearTransparencyBitmap(); // Release some resources
+ }
+ else if (mContainer) {
+ gtk_widget_hide(GTK_WIDGET(mContainer));
+ }
+ else if (mGdkWindow) {
+ gdk_window_hide(mGdkWindow);
+ }
+ }
+}
+
+void
+nsWindow::SetHasMappedToplevel(bool aState)
+{
+ // Even when aState == mHasMappedToplevel (as when this method is called
+ // from Show()), child windows need to have their state checked, so don't
+ // return early.
+ bool oldState = mHasMappedToplevel;
+ mHasMappedToplevel = aState;
+
+ // mHasMappedToplevel is not updated for children of windows that are
+ // hidden; GDK knows not to send expose events for these windows. The
+ // state is recorded on the hidden window itself, but, for child trees of
+ // hidden windows, their state essentially becomes disconnected from their
+ // hidden parent. When the hidden parent gets shown, the child trees are
+ // reconnected, and the state of the window being shown can be easily
+ // propagated.
+ if (!mIsShown || !mGdkWindow)
+ return;
+
+ if (aState && !oldState && !mIsFullyObscured) {
+ // GDK_EXPOSE events have been ignored but the window is now visible,
+ // so make sure GDK doesn't think that the window has already been
+ // painted.
+ gdk_window_invalidate_rect(mGdkWindow, nullptr, FALSE);
+
+ // Check that a grab didn't fail due to the window not being
+ // viewable.
+ EnsureGrabs();
+ }
+
+ for (GList *children = gdk_window_peek_children(mGdkWindow);
+ children;
+ children = children->next) {
+ GdkWindow *gdkWin = GDK_WINDOW(children->data);
+ nsWindow *child = get_window_for_gdk_window(gdkWin);
+
+ if (child && child->mHasMappedToplevel != aState) {
+ child->SetHasMappedToplevel(aState);
+ }
+ }
+}
+
+LayoutDeviceIntSize
+nsWindow::GetSafeWindowSize(LayoutDeviceIntSize aSize)
+{
+ // The X protocol uses CARD32 for window sizes, but the server (1.11.3)
+ // reads it as CARD16. Sizes of pixmaps, used for drawing, are (unsigned)
+ // CARD16 in the protocol, but the server's ProcCreatePixmap returns
+ // BadAlloc if dimensions cannot be represented by signed shorts.
+ LayoutDeviceIntSize result = aSize;
+ const int32_t kInt16Max = 32767;
+ if (result.width > kInt16Max) {
+ result.width = kInt16Max;
+ }
+ if (result.height > kInt16Max) {
+ result.height = kInt16Max;
+ }
+ return result;
+}
+
+void
+nsWindow::EnsureGrabs(void)
+{
+ if (mRetryPointerGrab)
+ GrabPointer(sRetryGrabTime);
+}
+
+void
+nsWindow::CleanLayerManagerRecursive(void) {
+ if (mLayerManager) {
+ mLayerManager->Destroy();
+ mLayerManager = nullptr;
+ }
+
+ DestroyCompositor();
+
+ GList* children = gdk_window_peek_children(mGdkWindow);
+ for (GList* list = children; list; list = list->next) {
+ nsWindow* window = get_window_for_gdk_window(GDK_WINDOW(list->data));
+ if (window) {
+ window->CleanLayerManagerRecursive();
+ }
+ }
+}
+
+void
+nsWindow::SetTransparencyMode(nsTransparencyMode aMode)
+{
+ if (!mShell) {
+ // Pass the request to the toplevel window
+ GtkWidget *topWidget = GetToplevelWidget();
+ if (!topWidget)
+ return;
+
+ nsWindow *topWindow = get_window_for_gtk_widget(topWidget);
+ if (!topWindow)
+ return;
+
+ topWindow->SetTransparencyMode(aMode);
+ return;
+ }
+ bool isTransparent = aMode == eTransparencyTransparent;
+
+ if (mIsTransparent == isTransparent)
+ return;
+
+ if (!isTransparent) {
+ ClearTransparencyBitmap();
+ } // else the new default alpha values are "all 1", so we don't
+ // need to change anything yet
+
+ mIsTransparent = isTransparent;
+
+ // Need to clean our LayerManager up while still alive because
+ // we don't want to use layers acceleration on shaped windows
+ CleanLayerManagerRecursive();
+}
+
+nsTransparencyMode
+nsWindow::GetTransparencyMode()
+{
+ if (!mShell) {
+ // Pass the request to the toplevel window
+ GtkWidget *topWidget = GetToplevelWidget();
+ if (!topWidget) {
+ return eTransparencyOpaque;
+ }
+
+ nsWindow *topWindow = get_window_for_gtk_widget(topWidget);
+ if (!topWindow) {
+ return eTransparencyOpaque;
+ }
+
+ return topWindow->GetTransparencyMode();
+ }
+
+ return mIsTransparent ? eTransparencyTransparent : eTransparencyOpaque;
+}
+
+nsresult
+nsWindow::ConfigureChildren(const nsTArray<Configuration>& aConfigurations)
+{
+ // If this is a remotely updated widget we receive clipping, position, and
+ // size information from a source other than our owner. Don't let our parent
+ // update this information.
+ if (mWindowType == eWindowType_plugin_ipc_chrome) {
+ return NS_OK;
+ }
+
+ for (uint32_t i = 0; i < aConfigurations.Length(); ++i) {
+ const Configuration& configuration = aConfigurations[i];
+ nsWindow* w = static_cast<nsWindow*>(configuration.mChild.get());
+ NS_ASSERTION(w->GetParent() == this,
+ "Configured widget is not a child");
+ w->SetWindowClipRegion(configuration.mClipRegion, true);
+ if (w->mBounds.Size() != configuration.mBounds.Size()) {
+ w->Resize(configuration.mBounds.x, configuration.mBounds.y,
+ configuration.mBounds.width, configuration.mBounds.height,
+ true);
+ } else if (w->mBounds.TopLeft() != configuration.mBounds.TopLeft()) {
+ w->Move(configuration.mBounds.x, configuration.mBounds.y);
+ }
+ w->SetWindowClipRegion(configuration.mClipRegion, false);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsWindow::SetWindowClipRegion(const nsTArray<LayoutDeviceIntRect>& aRects,
+ bool aIntersectWithExisting)
+{
+ const nsTArray<LayoutDeviceIntRect>* newRects = &aRects;
+
+ AutoTArray<LayoutDeviceIntRect,1> intersectRects;
+ if (aIntersectWithExisting) {
+ AutoTArray<LayoutDeviceIntRect,1> existingRects;
+ GetWindowClipRegion(&existingRects);
+
+ LayoutDeviceIntRegion existingRegion = RegionFromArray(existingRects);
+ LayoutDeviceIntRegion newRegion = RegionFromArray(aRects);
+ LayoutDeviceIntRegion intersectRegion;
+ intersectRegion.And(newRegion, existingRegion);
+
+ // If mClipRects is null we haven't set a clip rect yet, so we
+ // need to set the clip even if it is equal.
+ if (mClipRects && intersectRegion.IsEqual(existingRegion)) {
+ return NS_OK;
+ }
+
+ if (!intersectRegion.IsEqual(newRegion)) {
+ ArrayFromRegion(intersectRegion, intersectRects);
+ newRects = &intersectRects;
+ }
+ }
+
+ if (IsWindowClipRegionEqual(*newRects))
+ return NS_OK;
+
+ StoreWindowClipRegion(*newRects);
+
+ if (!mGdkWindow)
+ return NS_OK;
+
+#if (MOZ_WIDGET_GTK == 2)
+ GdkRegion *region = gdk_region_new(); // aborts on OOM
+ for (uint32_t i = 0; i < newRects->Length(); ++i) {
+ const LayoutDeviceIntRect& r = newRects->ElementAt(i);
+ GdkRectangle rect = { r.x, r.y, r.width, r.height };
+ gdk_region_union_with_rect(region, &rect);
+ }
+
+ gdk_window_shape_combine_region(mGdkWindow, region, 0, 0);
+ gdk_region_destroy(region);
+#else
+ cairo_region_t *region = cairo_region_create();
+ for (uint32_t i = 0; i < newRects->Length(); ++i) {
+ const LayoutDeviceIntRect& r = newRects->ElementAt(i);
+ cairo_rectangle_int_t rect = { r.x, r.y, r.width, r.height };
+ cairo_region_union_rectangle(region, &rect);
+ }
+
+ gdk_window_shape_combine_region(mGdkWindow, region, 0, 0);
+ cairo_region_destroy(region);
+#endif
+
+ return NS_OK;
+}
+
+void
+nsWindow::ResizeTransparencyBitmap()
+{
+ if (!mTransparencyBitmap)
+ return;
+
+ if (mBounds.width == mTransparencyBitmapWidth &&
+ mBounds.height == mTransparencyBitmapHeight)
+ return;
+
+ int32_t newRowBytes = GetBitmapStride(mBounds.width);
+ int32_t newSize = newRowBytes * mBounds.height;
+ gchar* newBits = new gchar[newSize];
+ // fill new mask with "transparent", first
+ memset(newBits, 0, newSize);
+
+ // Now copy the intersection of the old and new areas into the new mask
+ int32_t copyWidth = std::min(mBounds.width, mTransparencyBitmapWidth);
+ int32_t copyHeight = std::min(mBounds.height, mTransparencyBitmapHeight);
+ int32_t oldRowBytes = GetBitmapStride(mTransparencyBitmapWidth);
+ int32_t copyBytes = GetBitmapStride(copyWidth);
+
+ int32_t i;
+ gchar* fromPtr = mTransparencyBitmap;
+ gchar* toPtr = newBits;
+ for (i = 0; i < copyHeight; i++) {
+ memcpy(toPtr, fromPtr, copyBytes);
+ fromPtr += oldRowBytes;
+ toPtr += newRowBytes;
+ }
+
+ delete[] mTransparencyBitmap;
+ mTransparencyBitmap = newBits;
+ mTransparencyBitmapWidth = mBounds.width;
+ mTransparencyBitmapHeight = mBounds.height;
+}
+
+static bool
+ChangedMaskBits(gchar* aMaskBits, int32_t aMaskWidth, int32_t aMaskHeight,
+ const nsIntRect& aRect, uint8_t* aAlphas, int32_t aStride)
+{
+ 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 = aAlphas;
+ for (x = aRect.x; x < xMax; x++) {
+ bool newBit = *alphas > 0x7f;
+ alphas++;
+
+ gchar maskByte = maskBytes[x >> 3];
+ bool maskBit = (maskByte & (1 << (x & 7))) != 0;
+
+ if (maskBit != newBit) {
+ return true;
+ }
+ }
+ aAlphas += aStride;
+ }
+
+ return false;
+}
+
+static
+void UpdateMaskBits(gchar* aMaskBits, int32_t aMaskWidth, int32_t aMaskHeight,
+ const nsIntRect& aRect, uint8_t* aAlphas, int32_t aStride)
+{
+ 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 = aAlphas;
+ for (x = aRect.x; x < xMax; x++) {
+ bool newBit = *alphas > 0x7f;
+ alphas++;
+
+ 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);
+ }
+ aAlphas += aStride;
+ }
+}
+
+void
+nsWindow::ApplyTransparencyBitmap()
+{
+#ifdef MOZ_X11
+ // 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 = GDK_WINDOW_XDISPLAY(mGdkWindow);
+ Window xDrawable = GDK_WINDOW_XID(mGdkWindow);
+ Pixmap maskPixmap = XCreateBitmapFromData(xDisplay,
+ xDrawable,
+ mTransparencyBitmap,
+ mTransparencyBitmapWidth,
+ mTransparencyBitmapHeight);
+ XShapeCombineMask(xDisplay, xDrawable,
+ ShapeBounding, 0, 0,
+ maskPixmap, ShapeSet);
+ XFreePixmap(xDisplay, maskPixmap);
+#else
+#if (MOZ_WIDGET_GTK == 2)
+ gtk_widget_reset_shapes(mShell);
+ GdkBitmap* maskBitmap = gdk_bitmap_create_from_data(mGdkWindow,
+ mTransparencyBitmap,
+ mTransparencyBitmapWidth, mTransparencyBitmapHeight);
+ if (!maskBitmap)
+ return;
+
+ gtk_widget_shape_combine_mask(mShell, maskBitmap, 0, 0);
+ g_object_unref(maskBitmap);
+#else
+ cairo_surface_t *maskBitmap;
+ maskBitmap = cairo_image_surface_create_for_data((unsigned char*)mTransparencyBitmap,
+ CAIRO_FORMAT_A1,
+ mTransparencyBitmapWidth,
+ mTransparencyBitmapHeight,
+ GetBitmapStride(mTransparencyBitmapWidth));
+ if (!maskBitmap)
+ return;
+
+ cairo_region_t * maskRegion = gdk_cairo_region_create_from_surface(maskBitmap);
+ gtk_widget_shape_combine_region(mShell, maskRegion);
+ cairo_region_destroy(maskRegion);
+ cairo_surface_destroy(maskBitmap);
+#endif // MOZ_WIDGET_GTK == 2
+#endif // MOZ_X11
+}
+
+void
+nsWindow::ClearTransparencyBitmap()
+{
+ if (!mTransparencyBitmap)
+ return;
+
+ delete[] mTransparencyBitmap;
+ mTransparencyBitmap = nullptr;
+ mTransparencyBitmapWidth = 0;
+ mTransparencyBitmapHeight = 0;
+
+ if (!mShell)
+ return;
+
+#ifdef MOZ_X11
+ if (!mGdkWindow)
+ return;
+
+ Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
+ Window xWindow = gdk_x11_window_get_xid(mGdkWindow);
+
+ XShapeCombineMask(xDisplay, xWindow, ShapeBounding, 0, 0, X11None, ShapeSet);
+#endif
+}
+
+nsresult
+nsWindow::UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
+ uint8_t* aAlphas, int32_t aStride)
+{
+ if (!mShell) {
+ // Pass the request to the toplevel window
+ GtkWidget *topWidget = GetToplevelWidget();
+ if (!topWidget)
+ return NS_ERROR_FAILURE;
+
+ nsWindow *topWindow = get_window_for_gtk_widget(topWidget);
+ if (!topWindow)
+ return NS_ERROR_FAILURE;
+
+ return topWindow->UpdateTranslucentWindowAlphaInternal(aRect, aAlphas, aStride);
+ }
+
+ NS_ASSERTION(mIsTransparent, "Window is not transparent");
+
+ if (mTransparencyBitmap == nullptr) {
+ int32_t size = GetBitmapStride(mBounds.width)*mBounds.height;
+ mTransparencyBitmap = new gchar[size];
+ memset(mTransparencyBitmap, 255, size);
+ mTransparencyBitmapWidth = mBounds.width;
+ mTransparencyBitmapHeight = mBounds.height;
+ } else {
+ ResizeTransparencyBitmap();
+ }
+
+ nsIntRect rect;
+ rect.IntersectRect(aRect, nsIntRect(0, 0, mBounds.width, mBounds.height));
+
+ if (!ChangedMaskBits(mTransparencyBitmap, mBounds.width, mBounds.height,
+ rect, aAlphas, aStride))
+ // skip the expensive stuff if the mask bits haven't changed; hopefully
+ // this is the common case
+ return NS_OK;
+
+ UpdateMaskBits(mTransparencyBitmap, mBounds.width, mBounds.height,
+ rect, aAlphas, aStride);
+
+ if (!mNeedsShow) {
+ ApplyTransparencyBitmap();
+ }
+ return NS_OK;
+}
+
+void
+nsWindow::GrabPointer(guint32 aTime)
+{
+ LOG(("GrabPointer time=0x%08x retry=%d\n",
+ (unsigned int)aTime, mRetryPointerGrab));
+
+ mRetryPointerGrab = false;
+ sRetryGrabTime = aTime;
+
+ // If the window isn't visible, just set the flag to retry the
+ // grab. When this window becomes visible, the grab will be
+ // retried.
+ if (!mHasMappedToplevel || mIsFullyObscured) {
+ LOG(("GrabPointer: window not visible\n"));
+ mRetryPointerGrab = true;
+ return;
+ }
+
+ if (!mGdkWindow)
+ return;
+
+ gint retval;
+ retval = gdk_pointer_grab(mGdkWindow, TRUE,
+ (GdkEventMask)(GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_ENTER_NOTIFY_MASK |
+ GDK_LEAVE_NOTIFY_MASK |
+ GDK_POINTER_MOTION_MASK),
+ (GdkWindow *)nullptr, nullptr, aTime);
+
+ if (retval == GDK_GRAB_NOT_VIEWABLE) {
+ LOG(("GrabPointer: window not viewable; will retry\n"));
+ mRetryPointerGrab = true;
+ } else if (retval != GDK_GRAB_SUCCESS) {
+ LOG(("GrabPointer: pointer grab failed: %i\n", retval));
+ // A failed grab indicates that another app has grabbed the pointer.
+ // Check for rollup now, because, without the grab, we likely won't
+ // get subsequent button press events. Do this with an event so that
+ // popups don't rollup while potentially adjusting the grab for
+ // this popup.
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &nsWindow::CheckForRollupDuringGrab);
+ NS_DispatchToCurrentThread(event.forget());
+ }
+}
+
+void
+nsWindow::ReleaseGrabs(void)
+{
+ LOG(("ReleaseGrabs\n"));
+
+ mRetryPointerGrab = false;
+ gdk_pointer_ungrab(GDK_CURRENT_TIME);
+}
+
+GtkWidget *
+nsWindow::GetToplevelWidget()
+{
+ if (mShell) {
+ return mShell;
+ }
+
+ GtkWidget *widget = GetMozContainerWidget();
+ if (!widget)
+ return nullptr;
+
+ return gtk_widget_get_toplevel(widget);
+}
+
+GtkWidget *
+nsWindow::GetMozContainerWidget()
+{
+ if (!mGdkWindow)
+ return nullptr;
+
+ if (mContainer)
+ return GTK_WIDGET(mContainer);
+
+ GtkWidget *owningWidget =
+ get_gtk_widget_for_gdk_window(mGdkWindow);
+ return owningWidget;
+}
+
+nsWindow *
+nsWindow::GetContainerWindow()
+{
+ GtkWidget *owningWidget = GetMozContainerWidget();
+ if (!owningWidget)
+ return nullptr;
+
+ nsWindow *window = get_window_for_gtk_widget(owningWidget);
+ NS_ASSERTION(window, "No nsWindow for container widget");
+ return window;
+}
+
+void
+nsWindow::SetUrgencyHint(GtkWidget *top_window, bool state)
+{
+ if (!top_window)
+ return;
+
+ gdk_window_set_urgency_hint(gtk_widget_get_window(top_window), state);
+}
+
+void *
+nsWindow::SetupPluginPort(void)
+{
+ if (!mGdkWindow)
+ return nullptr;
+
+ if (gdk_window_is_destroyed(mGdkWindow) == TRUE)
+ return nullptr;
+
+ Window window = gdk_x11_window_get_xid(mGdkWindow);
+
+ // we have to flush the X queue here so that any plugins that
+ // might be running on separate X connections will be able to use
+ // this window in case it was just created
+#ifdef MOZ_X11
+ XWindowAttributes xattrs;
+ Display *display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+ XGetWindowAttributes(display, window, &xattrs);
+ XSelectInput (display, window,
+ xattrs.your_event_mask |
+ SubstructureNotifyMask);
+
+ gdk_window_add_filter(mGdkWindow, plugin_window_filter_func, this);
+
+ XSync(display, False);
+#endif /* MOZ_X11 */
+
+ return (void *)window;
+}
+
+void
+nsWindow::SetDefaultIcon(void)
+{
+ SetIcon(NS_LITERAL_STRING("default"));
+}
+
+void
+nsWindow::SetPluginType(PluginType aPluginType)
+{
+ mPluginType = aPluginType;
+}
+
+#ifdef MOZ_X11
+void
+nsWindow::SetNonXEmbedPluginFocus()
+{
+ if (gPluginFocusWindow == this || mPluginType!=PluginType_NONXEMBED) {
+ return;
+ }
+
+ if (gPluginFocusWindow) {
+ RefPtr<nsWindow> kungFuDeathGrip = gPluginFocusWindow;
+ gPluginFocusWindow->LoseNonXEmbedPluginFocus();
+ }
+
+ LOGFOCUS(("nsWindow::SetNonXEmbedPluginFocus\n"));
+
+ Window curFocusWindow;
+ int focusState;
+
+ GdkDisplay *gdkDisplay = gdk_window_get_display(mGdkWindow);
+ XGetInputFocus(gdk_x11_display_get_xdisplay(gdkDisplay),
+ &curFocusWindow,
+ &focusState);
+
+ LOGFOCUS(("\t curFocusWindow=%p\n", curFocusWindow));
+
+ GdkWindow* toplevel = gdk_window_get_toplevel(mGdkWindow);
+#if (MOZ_WIDGET_GTK == 2)
+ GdkWindow *gdkfocuswin = gdk_window_lookup(curFocusWindow);
+#else
+ GdkWindow *gdkfocuswin = gdk_x11_window_lookup_for_display(gdkDisplay,
+ curFocusWindow);
+#endif
+
+ // lookup with the focus proxy window is supposed to get the
+ // same GdkWindow as toplevel. If the current focused window
+ // is not the focus proxy, we return without any change.
+ if (gdkfocuswin != toplevel) {
+ return;
+ }
+
+ // switch the focus from the focus proxy to the plugin window
+ mOldFocusWindow = curFocusWindow;
+ XRaiseWindow(GDK_WINDOW_XDISPLAY(mGdkWindow),
+ gdk_x11_window_get_xid(mGdkWindow));
+ gdk_error_trap_push();
+ XSetInputFocus(GDK_WINDOW_XDISPLAY(mGdkWindow),
+ gdk_x11_window_get_xid(mGdkWindow),
+ RevertToNone,
+ CurrentTime);
+ gdk_flush();
+#if (MOZ_WIDGET_GTK == 3)
+ gdk_error_trap_pop_ignored();
+#else
+ gdk_error_trap_pop();
+#endif
+ gPluginFocusWindow = this;
+ gdk_window_add_filter(nullptr, plugin_client_message_filter, this);
+
+ LOGFOCUS(("nsWindow::SetNonXEmbedPluginFocus oldfocus=%p new=%p\n",
+ mOldFocusWindow, gdk_x11_window_get_xid(mGdkWindow)));
+}
+
+void
+nsWindow::LoseNonXEmbedPluginFocus()
+{
+ LOGFOCUS(("nsWindow::LoseNonXEmbedPluginFocus\n"));
+
+ // This method is only for the nsWindow which contains a
+ // Non-XEmbed plugin, for example, JAVA plugin.
+ if (gPluginFocusWindow != this || mPluginType!=PluginType_NONXEMBED) {
+ return;
+ }
+
+ Window curFocusWindow;
+ int focusState;
+
+ XGetInputFocus(GDK_WINDOW_XDISPLAY(mGdkWindow),
+ &curFocusWindow,
+ &focusState);
+
+ // we only switch focus between plugin window and focus proxy. If the
+ // current focused window is not the plugin window, just removing the
+ // event filter that blocks the WM_TAKE_FOCUS is enough. WM and gtk2
+ // will take care of the focus later.
+ if (!curFocusWindow ||
+ curFocusWindow == gdk_x11_window_get_xid(mGdkWindow)) {
+
+ gdk_error_trap_push();
+ XRaiseWindow(GDK_WINDOW_XDISPLAY(mGdkWindow),
+ mOldFocusWindow);
+ XSetInputFocus(GDK_WINDOW_XDISPLAY(mGdkWindow),
+ mOldFocusWindow,
+ RevertToParent,
+ CurrentTime);
+ gdk_flush();
+#if (MOZ_WIDGET_GTK == 3)
+ gdk_error_trap_pop_ignored();
+#else
+ gdk_error_trap_pop();
+#endif
+ }
+ gPluginFocusWindow = nullptr;
+ mOldFocusWindow = 0;
+ gdk_window_remove_filter(nullptr, plugin_client_message_filter, this);
+
+ LOGFOCUS(("nsWindow::LoseNonXEmbedPluginFocus end\n"));
+}
+#endif /* MOZ_X11 */
+
+gint
+nsWindow::ConvertBorderStyles(nsBorderStyle aStyle)
+{
+ gint w = 0;
+
+ if (aStyle == eBorderStyle_default)
+ return -1;
+
+ // note that we don't handle eBorderStyle_close yet
+ if (aStyle & eBorderStyle_all)
+ w |= GDK_DECOR_ALL;
+ if (aStyle & eBorderStyle_border)
+ w |= GDK_DECOR_BORDER;
+ if (aStyle & eBorderStyle_resizeh)
+ w |= GDK_DECOR_RESIZEH;
+ if (aStyle & eBorderStyle_title)
+ w |= GDK_DECOR_TITLE;
+ if (aStyle & eBorderStyle_menu)
+ w |= GDK_DECOR_MENU;
+ if (aStyle & eBorderStyle_minimize)
+ w |= GDK_DECOR_MINIMIZE;
+ if (aStyle & eBorderStyle_maximize)
+ w |= GDK_DECOR_MAXIMIZE;
+
+ return w;
+}
+
+class FullscreenTransitionWindow final : public nsISupports
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit FullscreenTransitionWindow(GtkWidget* aWidget);
+
+ GtkWidget* mWindow;
+
+private:
+ ~FullscreenTransitionWindow();
+};
+
+NS_IMPL_ISUPPORTS0(FullscreenTransitionWindow)
+
+FullscreenTransitionWindow::FullscreenTransitionWindow(GtkWidget* aWidget)
+{
+ mWindow = gtk_window_new(GTK_WINDOW_POPUP);
+ GtkWindow* gtkWin = GTK_WINDOW(mWindow);
+
+ gtk_window_set_type_hint(gtkWin, GDK_WINDOW_TYPE_HINT_SPLASHSCREEN);
+ gtk_window_set_transient_for(gtkWin, GTK_WINDOW(aWidget));
+ gtk_window_set_decorated(gtkWin, false);
+
+ GdkWindow* gdkWin = gtk_widget_get_window(aWidget);
+ GdkScreen* screen = gtk_widget_get_screen(aWidget);
+ gint monitorNum = gdk_screen_get_monitor_at_window(screen, gdkWin);
+ GdkRectangle monitorRect;
+ gdk_screen_get_monitor_geometry(screen, monitorNum, &monitorRect);
+ gtk_window_set_screen(gtkWin, screen);
+ gtk_window_move(gtkWin, monitorRect.x, monitorRect.y);
+ gtk_window_resize(gtkWin, monitorRect.width, monitorRect.height);
+
+ GdkColor bgColor;
+ bgColor.red = bgColor.green = bgColor.blue = 0;
+ gtk_widget_modify_bg(mWindow, GTK_STATE_NORMAL, &bgColor);
+
+ gtk_window_set_opacity(gtkWin, 0.0);
+ gtk_widget_show(mWindow);
+}
+
+FullscreenTransitionWindow::~FullscreenTransitionWindow()
+{
+ gtk_widget_destroy(mWindow);
+}
+
+class FullscreenTransitionData
+{
+public:
+ FullscreenTransitionData(nsIWidget::FullscreenTransitionStage aStage,
+ uint16_t aDuration, nsIRunnable* aCallback,
+ FullscreenTransitionWindow* aWindow)
+ : mStage(aStage)
+ , mStartTime(TimeStamp::Now())
+ , mDuration(TimeDuration::FromMilliseconds(aDuration))
+ , mCallback(aCallback)
+ , mWindow(aWindow) { }
+
+ static const guint sInterval = 1000 / 30; // 30fps
+ static gboolean TimeoutCallback(gpointer aData);
+
+private:
+ nsIWidget::FullscreenTransitionStage mStage;
+ TimeStamp mStartTime;
+ TimeDuration mDuration;
+ nsCOMPtr<nsIRunnable> mCallback;
+ RefPtr<FullscreenTransitionWindow> mWindow;
+};
+
+/* static */ gboolean
+FullscreenTransitionData::TimeoutCallback(gpointer aData)
+{
+ bool finishing = false;
+ auto data = static_cast<FullscreenTransitionData*>(aData);
+ gdouble opacity = (TimeStamp::Now() - data->mStartTime) / data->mDuration;
+ if (opacity >= 1.0) {
+ opacity = 1.0;
+ finishing = true;
+ }
+ if (data->mStage == nsIWidget::eAfterFullscreenToggle) {
+ opacity = 1.0 - opacity;
+ }
+ gtk_window_set_opacity(GTK_WINDOW(data->mWindow->mWindow), opacity);
+
+ if (!finishing) {
+ return TRUE;
+ }
+ NS_DispatchToMainThread(data->mCallback.forget());
+ delete data;
+ return FALSE;
+}
+
+/* virtual */ bool
+nsWindow::PrepareForFullscreenTransition(nsISupports** aData)
+{
+ GdkScreen* screen = gtk_widget_get_screen(mShell);
+ if (!gdk_screen_is_composited(screen)) {
+ return false;
+ }
+ *aData = do_AddRef(new FullscreenTransitionWindow(mShell)).take();
+ return true;
+}
+
+/* virtual */ void
+nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration, nsISupports* aData,
+ nsIRunnable* aCallback)
+{
+ auto data = static_cast<FullscreenTransitionWindow*>(aData);
+ // This will be released at the end of the last timeout callback for it.
+ auto transitionData = new FullscreenTransitionData(aStage, aDuration,
+ aCallback, data);
+ g_timeout_add_full(G_PRIORITY_HIGH,
+ FullscreenTransitionData::sInterval,
+ FullscreenTransitionData::TimeoutCallback,
+ transitionData, nullptr);
+}
+
+static bool
+IsFullscreenSupported(GtkWidget* aShell)
+{
+#ifdef MOZ_X11
+ GdkScreen* screen = gtk_widget_get_screen(aShell);
+ GdkAtom atom = gdk_atom_intern("_NET_WM_STATE_FULLSCREEN", FALSE);
+ if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
+ return false;
+ }
+#endif
+ return true;
+}
+
+nsresult
+nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen)
+{
+ LOG(("nsWindow::MakeFullScreen [%p] aFullScreen %d\n",
+ (void *)this, aFullScreen));
+
+ if (!IsFullscreenSupported(mShell)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (aFullScreen) {
+ if (mSizeMode != nsSizeMode_Fullscreen)
+ mLastSizeMode = mSizeMode;
+
+ mSizeMode = nsSizeMode_Fullscreen;
+ gtk_window_fullscreen(GTK_WINDOW(mShell));
+ }
+ else {
+ mSizeMode = mLastSizeMode;
+ gtk_window_unfullscreen(GTK_WINDOW(mShell));
+ }
+
+ NS_ASSERTION(mLastSizeMode != nsSizeMode_Fullscreen,
+ "mLastSizeMode should never be fullscreen");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::HideWindowChrome(bool aShouldHide)
+{
+ if (!mShell) {
+ // Pass the request to the toplevel window
+ GtkWidget *topWidget = GetToplevelWidget();
+ if (!topWidget)
+ return NS_ERROR_FAILURE;
+
+ nsWindow *topWindow = get_window_for_gtk_widget(topWidget);
+ if (!topWindow)
+ return NS_ERROR_FAILURE;
+
+ return topWindow->HideWindowChrome(aShouldHide);
+ }
+
+ // Sawfish, metacity, and presumably other window managers get
+ // confused if we change the window decorations while the window
+ // is visible.
+ bool wasVisible = false;
+ if (gdk_window_is_visible(mGdkWindow)) {
+ gdk_window_hide(mGdkWindow);
+ wasVisible = true;
+ }
+
+ gint wmd;
+ if (aShouldHide)
+ wmd = 0;
+ else
+ wmd = ConvertBorderStyles(mBorderStyle);
+
+ if (wmd != -1)
+ gdk_window_set_decorations(mGdkWindow, (GdkWMDecoration) wmd);
+
+ if (wasVisible)
+ gdk_window_show(mGdkWindow);
+
+ // For some window managers, adding or removing window decorations
+ // requires unmapping and remapping our toplevel window. Go ahead
+ // and flush the queue here so that we don't end up with a BadWindow
+ // error later when this happens (when the persistence timer fires
+ // and GetWindowPos is called)
+#ifdef MOZ_X11
+ XSync(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()) , False);
+#else
+ gdk_flush ();
+#endif /* MOZ_X11 */
+
+ return NS_OK;
+}
+
+bool
+nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY,
+ bool aIsWheel, bool aAlwaysRollup)
+{
+ nsIRollupListener* rollupListener = GetActiveRollupListener();
+ nsCOMPtr<nsIWidget> rollupWidget;
+ if (rollupListener) {
+ rollupWidget = rollupListener->GetRollupWidget();
+ }
+ if (!rollupWidget) {
+ nsBaseWidget::gRollupListener = nullptr;
+ return false;
+ }
+
+ bool retVal = false;
+ GdkWindow *currentPopup =
+ (GdkWindow *)rollupWidget->GetNativeData(NS_NATIVE_WINDOW);
+ if (aAlwaysRollup || !is_mouse_in_window(currentPopup, aMouseX, aMouseY)) {
+ bool rollup = true;
+ if (aIsWheel) {
+ rollup = rollupListener->ShouldRollupOnMouseWheelEvent();
+ retVal = rollupListener->ShouldConsumeOnMouseWheelEvent();
+ }
+ // if we're dealing with menus, we probably have submenus and
+ // we don't want to rollup if the click is in a parent menu of
+ // the current submenu
+ uint32_t popupsToRollup = UINT32_MAX;
+ if (!aAlwaysRollup) {
+ AutoTArray<nsIWidget*, 5> widgetChain;
+ uint32_t sameTypeCount = rollupListener->GetSubmenuWidgetChain(&widgetChain);
+ for (uint32_t i=0; i<widgetChain.Length(); ++i) {
+ nsIWidget* widget = widgetChain[i];
+ GdkWindow* currWindow =
+ (GdkWindow*) widget->GetNativeData(NS_NATIVE_WINDOW);
+ if (is_mouse_in_window(currWindow, aMouseX, aMouseY)) {
+ // don't roll up if the mouse event occurred within a
+ // menu of the same type. If the mouse event occurred
+ // in a menu higher than that, roll up, but pass the
+ // number of popups to Rollup so that only those of the
+ // same type close up.
+ if (i < sameTypeCount) {
+ rollup = false;
+ }
+ else {
+ popupsToRollup = sameTypeCount;
+ }
+ break;
+ }
+ } // foreach parent menu widget
+ } // if rollup listener knows about menus
+
+ // if we've determined that we should still rollup, do it.
+ bool usePoint = !aIsWheel && !aAlwaysRollup;
+ IntPoint point = IntPoint::Truncate(aMouseX, aMouseY);
+ if (rollup && rollupListener->Rollup(popupsToRollup, true, usePoint ? &point : nullptr, nullptr)) {
+ retVal = true;
+ }
+ }
+ return retVal;
+}
+
+/* static */
+bool
+nsWindow::DragInProgress(void)
+{
+ nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID);
+
+ if (!dragService)
+ return false;
+
+ nsCOMPtr<nsIDragSession> currentDragSession;
+ dragService->GetCurrentSession(getter_AddRefs(currentDragSession));
+
+ return currentDragSession != nullptr;
+}
+
+static bool
+is_mouse_in_window (GdkWindow* aWindow, gdouble aMouseX, gdouble aMouseY)
+{
+ gint x = 0;
+ gint y = 0;
+ gint w, h;
+
+ gint offsetX = 0;
+ gint offsetY = 0;
+
+ GdkWindow *window = aWindow;
+
+ while (window) {
+ gint tmpX = 0;
+ gint tmpY = 0;
+
+ gdk_window_get_position(window, &tmpX, &tmpY);
+ GtkWidget *widget = get_gtk_widget_for_gdk_window(window);
+
+ // if this is a window, compute x and y given its origin and our
+ // offset
+ if (GTK_IS_WINDOW(widget)) {
+ x = tmpX + offsetX;
+ y = tmpY + offsetY;
+ break;
+ }
+
+ offsetX += tmpX;
+ offsetY += tmpY;
+ window = gdk_window_get_parent(window);
+ }
+
+#if (MOZ_WIDGET_GTK == 2)
+ gdk_drawable_get_size(aWindow, &w, &h);
+#else
+ w = gdk_window_get_width(aWindow);
+ h = gdk_window_get_height(aWindow);
+#endif
+
+ if (aMouseX > x && aMouseX < x + w &&
+ aMouseY > y && aMouseY < y + h)
+ return true;
+
+ return false;
+}
+
+static nsWindow *
+get_window_for_gtk_widget(GtkWidget *widget)
+{
+ gpointer user_data = g_object_get_data(G_OBJECT(widget), "nsWindow");
+
+ return static_cast<nsWindow *>(user_data);
+}
+
+static nsWindow *
+get_window_for_gdk_window(GdkWindow *window)
+{
+ gpointer user_data = g_object_get_data(G_OBJECT(window), "nsWindow");
+
+ return static_cast<nsWindow *>(user_data);
+}
+
+static GtkWidget *
+get_gtk_widget_for_gdk_window(GdkWindow *window)
+{
+ gpointer user_data = nullptr;
+ gdk_window_get_user_data(window, &user_data);
+
+ return GTK_WIDGET(user_data);
+}
+
+static GdkCursor *
+get_gtk_cursor(nsCursor aCursor)
+{
+ GdkCursor *gdkcursor = nullptr;
+ uint8_t newType = 0xff;
+
+ if ((gdkcursor = gCursorCache[aCursor])) {
+ return gdkcursor;
+ }
+
+ GdkDisplay *defaultDisplay = gdk_display_get_default();
+
+ // The strategy here is to use standard GDK cursors, and, if not available,
+ // load by standard name with gdk_cursor_new_from_name.
+ // Spec is here: http://www.freedesktop.org/wiki/Specifications/cursor-spec/
+ switch (aCursor) {
+ case eCursor_standard:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_PTR);
+ break;
+ case eCursor_wait:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_WATCH);
+ break;
+ case eCursor_select:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_XTERM);
+ break;
+ case eCursor_hyperlink:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_HAND2);
+ break;
+ case eCursor_n_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_SIDE);
+ break;
+ case eCursor_s_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_SIDE);
+ break;
+ case eCursor_w_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_SIDE);
+ break;
+ case eCursor_e_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_RIGHT_SIDE);
+ break;
+ case eCursor_nw_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay,
+ GDK_TOP_LEFT_CORNER);
+ break;
+ case eCursor_se_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay,
+ GDK_BOTTOM_RIGHT_CORNER);
+ break;
+ case eCursor_ne_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay,
+ GDK_TOP_RIGHT_CORNER);
+ break;
+ case eCursor_sw_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay,
+ GDK_BOTTOM_LEFT_CORNER);
+ break;
+ case eCursor_crosshair:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_CROSSHAIR);
+ break;
+ case eCursor_move:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_FLEUR);
+ break;
+ case eCursor_help:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay,
+ GDK_QUESTION_ARROW);
+ break;
+ case eCursor_copy: // CSS3
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "copy");
+ if (!gdkcursor)
+ newType = MOZ_CURSOR_COPY;
+ break;
+ case eCursor_alias:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "alias");
+ if (!gdkcursor)
+ newType = MOZ_CURSOR_ALIAS;
+ break;
+ case eCursor_context_menu:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "context-menu");
+ if (!gdkcursor)
+ newType = MOZ_CURSOR_CONTEXT_MENU;
+ break;
+ case eCursor_cell:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_PLUS);
+ break;
+ // Those two aren’t standardized. Trying both KDE’s and GNOME’s names
+ case eCursor_grab:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "openhand");
+ if (!gdkcursor)
+ newType = MOZ_CURSOR_HAND_GRAB;
+ break;
+ case eCursor_grabbing:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "closedhand");
+ if (!gdkcursor)
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "grabbing");
+ if (!gdkcursor)
+ newType = MOZ_CURSOR_HAND_GRABBING;
+ break;
+ case eCursor_spinning:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "progress");
+ if (!gdkcursor)
+ newType = MOZ_CURSOR_SPINNING;
+ break;
+ case eCursor_zoom_in:
+ newType = MOZ_CURSOR_ZOOM_IN;
+ break;
+ case eCursor_zoom_out:
+ newType = MOZ_CURSOR_ZOOM_OUT;
+ break;
+ case eCursor_not_allowed:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "not-allowed");
+ if (!gdkcursor) // nonstandard, yet common
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "crossed_circle");
+ if (!gdkcursor)
+ newType = MOZ_CURSOR_NOT_ALLOWED;
+ break;
+ case eCursor_no_drop:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "no-drop");
+ if (!gdkcursor) // this nonstandard sequence makes it work on KDE and GNOME
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "forbidden");
+ if (!gdkcursor)
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "circle");
+ if (!gdkcursor)
+ newType = MOZ_CURSOR_NOT_ALLOWED;
+ break;
+ case eCursor_vertical_text:
+ newType = MOZ_CURSOR_VERTICAL_TEXT;
+ break;
+ case eCursor_all_scroll:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_FLEUR);
+ break;
+ case eCursor_nesw_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "size_bdiag");
+ if (!gdkcursor)
+ newType = MOZ_CURSOR_NESW_RESIZE;
+ break;
+ case eCursor_nwse_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "size_fdiag");
+ if (!gdkcursor)
+ newType = MOZ_CURSOR_NWSE_RESIZE;
+ break;
+ case eCursor_ns_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay,
+ GDK_SB_V_DOUBLE_ARROW);
+ break;
+ case eCursor_ew_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay,
+ GDK_SB_H_DOUBLE_ARROW);
+ break;
+ // Here, two better fitting cursors exist in some cursor themes. Try those first
+ case eCursor_row_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "split_v");
+ if (!gdkcursor)
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay,
+ GDK_SB_V_DOUBLE_ARROW);
+ break;
+ case eCursor_col_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "split_h");
+ if (!gdkcursor)
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay,
+ GDK_SB_H_DOUBLE_ARROW);
+ break;
+ case eCursor_none:
+ newType = MOZ_CURSOR_NONE;
+ break;
+ default:
+ NS_ASSERTION(aCursor, "Invalid cursor type");
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_PTR);
+ break;
+ }
+
+ // If by now we don't have a xcursor, this means we have to make a custom
+ // one. First, we try creating a named cursor based on the hash of our
+ // custom bitmap, as libXcursor has some magic to convert bitmapped cursors
+ // to themed cursors
+ if (newType != 0xFF && GtkCursors[newType].hash) {
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, GtkCursors[newType].hash);
+ }
+
+ // If we still don't have a xcursor, we now really create a bitmap cursor
+ if (newType != 0xff && !gdkcursor) {
+ GdkPixbuf * cursor_pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32);
+ if (!cursor_pixbuf)
+ return nullptr;
+
+ guchar *data = gdk_pixbuf_get_pixels(cursor_pixbuf);
+
+ // Read data from GtkCursors and compose RGBA surface from 1bit bitmap and mask
+ // GtkCursors bits and mask are 32x32 monochrome bitmaps (1 bit for each pixel)
+ // so it's 128 byte array (4 bytes for are one bitmap row and there are 32 rows here).
+ const unsigned char *bits = GtkCursors[newType].bits;
+ const unsigned char *mask_bits = GtkCursors[newType].mask_bits;
+
+ for (int i = 0; i < 128; i++) {
+ char bit = *bits++;
+ char mask = *mask_bits++;
+ for (int j = 0; j < 8; j++) {
+ unsigned char pix = ~(((bit >> j) & 0x01) * 0xff);
+ *data++ = pix;
+ *data++ = pix;
+ *data++ = pix;
+ *data++ = (((mask >> j) & 0x01) * 0xff);
+ }
+ }
+
+ gdkcursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(), cursor_pixbuf,
+ GtkCursors[newType].hot_x,
+ GtkCursors[newType].hot_y);
+
+ g_object_unref(cursor_pixbuf);
+ }
+
+ gCursorCache[aCursor] = gdkcursor;
+
+ return gdkcursor;
+}
+
+// gtk callbacks
+
+#if (MOZ_WIDGET_GTK == 2)
+static gboolean
+expose_event_cb(GtkWidget *widget, GdkEventExpose *event)
+{
+ RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
+ if (!window)
+ return FALSE;
+
+ window->OnExposeEvent(event);
+ return FALSE;
+}
+#else
+void
+draw_window_of_widget(GtkWidget *widget, GdkWindow *aWindow, cairo_t *cr)
+{
+ if (gtk_cairo_should_draw_window(cr, aWindow)) {
+ RefPtr<nsWindow> window = get_window_for_gdk_window(aWindow);
+ if (!window) {
+ NS_WARNING("Cannot get nsWindow from GtkWidget");
+ }
+ else {
+ cairo_save(cr);
+ gtk_cairo_transform_to_window(cr, widget, aWindow);
+ // TODO - window->OnExposeEvent() can destroy this or other windows,
+ // do we need to handle it somehow?
+ window->OnExposeEvent(cr);
+ cairo_restore(cr);
+ }
+ }
+
+ GList *children = gdk_window_get_children(aWindow);
+ GList *child = children;
+ while (child) {
+ GdkWindow *window = GDK_WINDOW(child->data);
+ gpointer windowWidget;
+ gdk_window_get_user_data(window, &windowWidget);
+ if (windowWidget == widget) {
+ draw_window_of_widget(widget, window, cr);
+ }
+ child = g_list_next(child);
+ }
+ g_list_free(children);
+}
+
+/* static */
+gboolean
+expose_event_cb(GtkWidget *widget, cairo_t *cr)
+{
+ draw_window_of_widget(widget, gtk_widget_get_window(widget), cr);
+
+ // A strong reference is already held during "draw" signal emission,
+ // but GTK+ 3.4 wants the object to live a little longer than that
+ // (bug 1225970).
+ g_object_ref(widget);
+ g_idle_add(
+ [](gpointer data) -> gboolean {
+ g_object_unref(data);
+ return G_SOURCE_REMOVE;
+ },
+ widget);
+
+ return FALSE;
+}
+#endif //MOZ_WIDGET_GTK == 2
+
+static gboolean
+configure_event_cb(GtkWidget *widget,
+ GdkEventConfigure *event)
+{
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window)
+ return FALSE;
+
+ return window->OnConfigureEvent(widget, event);
+}
+
+static void
+container_unrealize_cb (GtkWidget *widget)
+{
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window)
+ return;
+
+ window->OnContainerUnrealize();
+}
+
+static void
+size_allocate_cb (GtkWidget *widget, GtkAllocation *allocation)
+{
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window)
+ return;
+
+ window->OnSizeAllocate(allocation);
+}
+
+static gboolean
+delete_event_cb(GtkWidget *widget, GdkEventAny *event)
+{
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window)
+ return FALSE;
+
+ window->OnDeleteEvent();
+
+ return TRUE;
+}
+
+static gboolean
+enter_notify_event_cb(GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
+ if (!window)
+ return TRUE;
+
+ window->OnEnterNotifyEvent(event);
+
+ return TRUE;
+}
+
+static gboolean
+leave_notify_event_cb(GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ if (is_parent_grab_leave(event)) {
+ return TRUE;
+ }
+
+ // bug 369599: Suppress LeaveNotify events caused by pointer grabs to
+ // avoid generating spurious mouse exit events.
+ gint x = gint(event->x_root);
+ gint y = gint(event->y_root);
+ GdkDisplay* display = gtk_widget_get_display(widget);
+ GdkWindow* winAtPt = gdk_display_get_window_at_pointer(display, &x, &y);
+ if (winAtPt == event->window) {
+ return TRUE;
+ }
+
+ RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
+ if (!window)
+ return TRUE;
+
+ window->OnLeaveNotifyEvent(event);
+
+ return TRUE;
+}
+
+static nsWindow*
+GetFirstNSWindowForGDKWindow(GdkWindow *aGdkWindow)
+{
+ nsWindow* window;
+ while (!(window = get_window_for_gdk_window(aGdkWindow))) {
+ // The event has bubbled to the moz_container widget as passed into each caller's *widget parameter,
+ // but its corresponding nsWindow is an ancestor of the window that we need. Instead, look at
+ // event->window and find the first ancestor nsWindow of it because event->window may be in a plugin.
+ aGdkWindow = gdk_window_get_parent(aGdkWindow);
+ if (!aGdkWindow) {
+ window = nullptr;
+ break;
+ }
+ }
+ return window;
+}
+
+static gboolean
+motion_notify_event_cb(GtkWidget *widget, GdkEventMotion *event)
+{
+ UpdateLastInputEventTime(event);
+
+ nsWindow *window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window)
+ return FALSE;
+
+ window->OnMotionNotifyEvent(event);
+
+ return TRUE;
+}
+
+static gboolean
+button_press_event_cb(GtkWidget *widget, GdkEventButton *event)
+{
+ UpdateLastInputEventTime(event);
+
+ nsWindow *window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window)
+ return FALSE;
+
+ window->OnButtonPressEvent(event);
+
+ return TRUE;
+}
+
+static gboolean
+button_release_event_cb(GtkWidget *widget, GdkEventButton *event)
+{
+ UpdateLastInputEventTime(event);
+
+ nsWindow *window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window)
+ return FALSE;
+
+ window->OnButtonReleaseEvent(event);
+
+ return TRUE;
+}
+
+static gboolean
+focus_in_event_cb(GtkWidget *widget, GdkEventFocus *event)
+{
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window)
+ return FALSE;
+
+ window->OnContainerFocusInEvent(event);
+
+ return FALSE;
+}
+
+static gboolean
+focus_out_event_cb(GtkWidget *widget, GdkEventFocus *event)
+{
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window)
+ return FALSE;
+
+ window->OnContainerFocusOutEvent(event);
+
+ return FALSE;
+}
+
+#ifdef MOZ_X11
+// For long-lived popup windows that don't really take focus themselves but
+// may have elements that accept keyboard input when the parent window is
+// active, focus is handled specially. These windows include noautohide
+// panels. (This special handling is not necessary for temporary popups where
+// the keyboard is grabbed.)
+//
+// Mousing over or clicking on these windows should not cause them to steal
+// focus from their parent windows, so, the input field of WM_HINTS is set to
+// False to request that the window manager not set the input focus to this
+// window. http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7
+//
+// However, these windows can still receive WM_TAKE_FOCUS messages from the
+// window manager, so they can still detect when the user has indicated that
+// they wish to direct keyboard input at these windows. When the window
+// manager offers focus to these windows (after a mouse over or click, for
+// example), a request to make the parent window active is issued. When the
+// parent window becomes active, keyboard events will be received.
+
+static GdkFilterReturn
+popup_take_focus_filter(GdkXEvent *gdk_xevent,
+ GdkEvent *event,
+ gpointer data)
+{
+ XEvent* xevent = static_cast<XEvent*>(gdk_xevent);
+ if (xevent->type != ClientMessage)
+ return GDK_FILTER_CONTINUE;
+
+ XClientMessageEvent& xclient = xevent->xclient;
+ if (xclient.message_type != gdk_x11_get_xatom_by_name("WM_PROTOCOLS"))
+ return GDK_FILTER_CONTINUE;
+
+ Atom atom = xclient.data.l[0];
+ if (atom != gdk_x11_get_xatom_by_name("WM_TAKE_FOCUS"))
+ return GDK_FILTER_CONTINUE;
+
+ guint32 timestamp = xclient.data.l[1];
+
+ GtkWidget* widget = get_gtk_widget_for_gdk_window(event->any.window);
+ if (!widget)
+ return GDK_FILTER_CONTINUE;
+
+ GtkWindow* parent = gtk_window_get_transient_for(GTK_WINDOW(widget));
+ if (!parent)
+ return GDK_FILTER_CONTINUE;
+
+ if (gtk_window_is_active(parent))
+ return GDK_FILTER_REMOVE; // leave input focus on the parent
+
+ GdkWindow* parent_window = gtk_widget_get_window(GTK_WIDGET(parent));
+ if (!parent_window)
+ return GDK_FILTER_CONTINUE;
+
+ // In case the parent has not been deconified.
+ gdk_window_show_unraised(parent_window);
+
+ // Request focus on the parent window.
+ // Use gdk_window_focus rather than gtk_window_present to avoid
+ // raising the parent window.
+ gdk_window_focus(parent_window, timestamp);
+ return GDK_FILTER_REMOVE;
+}
+
+static GdkFilterReturn
+plugin_window_filter_func(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data)
+{
+ GdkWindow *plugin_window;
+ XEvent *xevent;
+ Window xeventWindow;
+
+ RefPtr<nsWindow> nswindow = (nsWindow*)data;
+ GdkFilterReturn return_val;
+
+ xevent = (XEvent *)gdk_xevent;
+ return_val = GDK_FILTER_CONTINUE;
+
+ switch (xevent->type)
+ {
+ case CreateNotify:
+ case ReparentNotify:
+ if (xevent->type==CreateNotify) {
+ xeventWindow = xevent->xcreatewindow.window;
+ }
+ else {
+ if (xevent->xreparent.event != xevent->xreparent.parent)
+ break;
+ xeventWindow = xevent->xreparent.window;
+ }
+#if (MOZ_WIDGET_GTK == 2)
+ plugin_window = gdk_window_lookup(xeventWindow);
+#else
+ plugin_window = gdk_x11_window_lookup_for_display(
+ gdk_x11_lookup_xdisplay(xevent->xcreatewindow.display), xeventWindow);
+#endif
+ if (plugin_window) {
+ GtkWidget *widget =
+ get_gtk_widget_for_gdk_window(plugin_window);
+
+// TODO GTK3
+#if (MOZ_WIDGET_GTK == 2)
+ if (GTK_IS_XTBIN(widget)) {
+ nswindow->SetPluginType(nsWindow::PluginType_NONXEMBED);
+ break;
+ }
+ else
+#endif
+ if(GTK_IS_SOCKET(widget)) {
+ if (!g_object_get_data(G_OBJECT(widget), "enable-xt-focus")) {
+ nswindow->SetPluginType(nsWindow::PluginType_XEMBED);
+ break;
+ }
+ }
+ }
+ nswindow->SetPluginType(nsWindow::PluginType_NONXEMBED);
+ return_val = GDK_FILTER_REMOVE;
+ break;
+ case EnterNotify:
+ nswindow->SetNonXEmbedPluginFocus();
+ break;
+ case DestroyNotify:
+ gdk_window_remove_filter
+ ((GdkWindow*)(nswindow->GetNativeData(NS_NATIVE_WINDOW)),
+ plugin_window_filter_func,
+ nswindow);
+ // Currently we consider all plugins are non-xembed and calls
+ // LoseNonXEmbedPluginFocus without any checking.
+ nswindow->LoseNonXEmbedPluginFocus();
+ break;
+ default:
+ break;
+ }
+ return return_val;
+}
+
+static GdkFilterReturn
+plugin_client_message_filter(GdkXEvent *gdk_xevent,
+ GdkEvent *event,
+ gpointer data)
+{
+ XEvent *xevent;
+ xevent = (XEvent *)gdk_xevent;
+
+ GdkFilterReturn return_val;
+ return_val = GDK_FILTER_CONTINUE;
+
+ if (!gPluginFocusWindow || xevent->type!=ClientMessage) {
+ return return_val;
+ }
+
+ // When WM sends out WM_TAKE_FOCUS, gtk2 will use XSetInputFocus
+ // to set the focus to the focus proxy. To prevent this happen
+ // while the focus is on the plugin, we filter the WM_TAKE_FOCUS
+ // out.
+ if (gdk_x11_get_xatom_by_name("WM_PROTOCOLS")
+ != xevent->xclient.message_type) {
+ return return_val;
+ }
+
+ if ((Atom) xevent->xclient.data.l[0] ==
+ gdk_x11_get_xatom_by_name("WM_TAKE_FOCUS")) {
+ // block it from gtk2.0 focus proxy
+ return_val = GDK_FILTER_REMOVE;
+ }
+
+ return return_val;
+}
+#endif /* MOZ_X11 */
+
+static gboolean
+key_press_event_cb(GtkWidget *widget, GdkEventKey *event)
+{
+ LOG(("key_press_event_cb\n"));
+
+ UpdateLastInputEventTime(event);
+
+ // find the window with focus and dispatch this event to that widget
+ nsWindow *window = get_window_for_gtk_widget(widget);
+ if (!window)
+ return FALSE;
+
+ RefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
+
+#ifdef MOZ_X11
+ // Keyboard repeat can cause key press events to queue up when there are
+ // slow event handlers (bug 301029). Throttle these events by removing
+ // consecutive pending duplicate KeyPress events to the same window.
+ // We use the event time of the last one.
+ // Note: GDK calls XkbSetDetectableAutorepeat so that KeyRelease events
+ // are generated only when the key is physically released.
+#define NS_GDKEVENT_MATCH_MASK 0x1FFF /* GDK_SHIFT_MASK .. GDK_BUTTON5_MASK */
+ GdkDisplay* gdkDisplay = gtk_widget_get_display(widget);
+ if (GDK_IS_X11_DISPLAY(gdkDisplay)) {
+ Display* dpy = GDK_DISPLAY_XDISPLAY(gdkDisplay);
+ while (XPending(dpy)) {
+ XEvent next_event;
+ XPeekEvent(dpy, &next_event);
+ GdkWindow* nextGdkWindow =
+ gdk_x11_window_lookup_for_display(gdkDisplay, next_event.xany.window);
+ if (nextGdkWindow != event->window ||
+ next_event.type != KeyPress ||
+ next_event.xkey.keycode != event->hardware_keycode ||
+ next_event.xkey.state != (event->state & NS_GDKEVENT_MATCH_MASK)) {
+ break;
+ }
+ XNextEvent(dpy, &next_event);
+ event->time = next_event.xkey.time;
+ }
+ }
+#endif
+
+ return focusWindow->OnKeyPressEvent(event);
+}
+
+static gboolean
+key_release_event_cb(GtkWidget *widget, GdkEventKey *event)
+{
+ LOG(("key_release_event_cb\n"));
+
+ UpdateLastInputEventTime(event);
+
+ // find the window with focus and dispatch this event to that widget
+ nsWindow *window = get_window_for_gtk_widget(widget);
+ if (!window)
+ return FALSE;
+
+ RefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
+
+ return focusWindow->OnKeyReleaseEvent(event);
+}
+
+static gboolean
+property_notify_event_cb(GtkWidget* aWidget, GdkEventProperty* aEvent)
+{
+ RefPtr<nsWindow> window = get_window_for_gdk_window(aEvent->window);
+ if (!window)
+ return FALSE;
+
+ return window->OnPropertyNotifyEvent(aWidget, aEvent);
+}
+
+static gboolean
+scroll_event_cb(GtkWidget *widget, GdkEventScroll *event)
+{
+ nsWindow *window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window)
+ return FALSE;
+
+ window->OnScrollEvent(event);
+
+ return TRUE;
+}
+
+static gboolean
+visibility_notify_event_cb (GtkWidget *widget, GdkEventVisibility *event)
+{
+ RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
+ if (!window)
+ return FALSE;
+
+ window->OnVisibilityNotifyEvent(event);
+
+ return TRUE;
+}
+
+static void
+hierarchy_changed_cb (GtkWidget *widget,
+ GtkWidget *previous_toplevel)
+{
+ GtkWidget *toplevel = gtk_widget_get_toplevel(widget);
+ GdkWindowState old_window_state = GDK_WINDOW_STATE_WITHDRAWN;
+ GdkEventWindowState event;
+
+ event.new_window_state = GDK_WINDOW_STATE_WITHDRAWN;
+
+ if (GTK_IS_WINDOW(previous_toplevel)) {
+ g_signal_handlers_disconnect_by_func(previous_toplevel,
+ FuncToGpointer(window_state_event_cb),
+ widget);
+ GdkWindow *win = gtk_widget_get_window(previous_toplevel);
+ if (win) {
+ old_window_state = gdk_window_get_state(win);
+ }
+ }
+
+ if (GTK_IS_WINDOW(toplevel)) {
+ g_signal_connect_swapped(toplevel, "window-state-event",
+ G_CALLBACK(window_state_event_cb), widget);
+ GdkWindow *win = gtk_widget_get_window(toplevel);
+ if (win) {
+ event.new_window_state = gdk_window_get_state(win);
+ }
+ }
+
+ event.changed_mask = static_cast<GdkWindowState>
+ (old_window_state ^ event.new_window_state);
+
+ if (event.changed_mask) {
+ event.type = GDK_WINDOW_STATE;
+ event.window = nullptr;
+ event.send_event = TRUE;
+ window_state_event_cb(widget, &event);
+ }
+}
+
+static gboolean
+window_state_event_cb (GtkWidget *widget, GdkEventWindowState *event)
+{
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window)
+ return FALSE;
+
+ window->OnWindowStateEvent(widget, event);
+
+ return FALSE;
+}
+
+static void
+theme_changed_cb (GtkSettings *settings, GParamSpec *pspec, nsWindow *data)
+{
+ RefPtr<nsWindow> window = data;
+ window->ThemeChanged();
+}
+
+static void
+check_resize_cb (GtkContainer* container, gpointer user_data)
+{
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(GTK_WIDGET(container));
+ if (!window) {
+ return;
+ }
+ window->OnCheckResize();
+}
+
+#if (MOZ_WIDGET_GTK == 3)
+static void
+scale_changed_cb (GtkWidget* widget, GParamSpec* aPSpec, gpointer aPointer)
+{
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+ window->OnDPIChanged();
+
+ // configure_event is already fired before scale-factor signal,
+ // but size-allocate isn't fired by changing scale
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(widget, &allocation);
+ window->OnSizeAllocate(&allocation);
+}
+#endif
+
+#if GTK_CHECK_VERSION(3,4,0)
+static gboolean
+touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent)
+{
+ UpdateLastInputEventTime(aEvent);
+
+ nsWindow* window = GetFirstNSWindowForGDKWindow(aEvent->window);
+ if (!window) {
+ return FALSE;
+ }
+
+ return window->OnTouchEvent(aEvent);
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// These are all of our drag and drop operations
+
+void
+nsWindow::InitDragEvent(WidgetDragEvent &aEvent)
+{
+ // set the keyboard modifiers
+ guint modifierState = KeymapWrapper::GetCurrentModifierState();
+ KeymapWrapper::InitInputEvent(aEvent, modifierState);
+}
+
+static gboolean
+drag_motion_event_cb(GtkWidget *aWidget,
+ GdkDragContext *aDragContext,
+ gint aX,
+ gint aY,
+ guint aTime,
+ gpointer aData)
+{
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window)
+ return FALSE;
+
+ // figure out which internal widget this drag motion actually happened on
+ nscoord retx = 0;
+ nscoord rety = 0;
+
+ GdkWindow *innerWindow =
+ get_inner_gdk_window(gtk_widget_get_window(aWidget), aX, aY,
+ &retx, &rety);
+ RefPtr<nsWindow> innerMostWindow = get_window_for_gdk_window(innerWindow);
+
+ if (!innerMostWindow) {
+ innerMostWindow = window;
+ }
+
+ LOGDRAG(("nsWindow drag-motion signal for %p\n", (void*)innerMostWindow));
+
+ LayoutDeviceIntPoint point = window->GdkPointToDevicePixels({ retx, rety });
+
+ return nsDragService::GetInstance()->
+ ScheduleMotionEvent(innerMostWindow, aDragContext,
+ point, aTime);
+}
+
+static void
+drag_leave_event_cb(GtkWidget *aWidget,
+ GdkDragContext *aDragContext,
+ guint aTime,
+ gpointer aData)
+{
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window)
+ return;
+
+ nsDragService *dragService = nsDragService::GetInstance();
+
+ nsWindow *mostRecentDragWindow = dragService->GetMostRecentDestWindow();
+ if (!mostRecentDragWindow) {
+ // This can happen when the target will not accept a drop. A GTK drag
+ // source sends the leave message to the destination before the
+ // drag-failed signal on the source widget, but the leave message goes
+ // via the X server, and so doesn't get processed at least until the
+ // event loop runs again.
+ return;
+ }
+
+ GtkWidget *mozContainer = mostRecentDragWindow->GetMozContainerWidget();
+ if (aWidget != mozContainer)
+ {
+ // When the drag moves between widgets, GTK can send leave signal for
+ // the old widget after the motion or drop signal for the new widget.
+ // We'll send the leave event when the motion or drop event is run.
+ return;
+ }
+
+ LOGDRAG(("nsWindow drag-leave signal for %p\n",
+ (void*)mostRecentDragWindow));
+
+ dragService->ScheduleLeaveEvent();
+}
+
+
+static gboolean
+drag_drop_event_cb(GtkWidget *aWidget,
+ GdkDragContext *aDragContext,
+ gint aX,
+ gint aY,
+ guint aTime,
+ gpointer aData)
+{
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window)
+ return FALSE;
+
+ // figure out which internal widget this drag motion actually happened on
+ nscoord retx = 0;
+ nscoord rety = 0;
+
+ GdkWindow *innerWindow =
+ get_inner_gdk_window(gtk_widget_get_window(aWidget), aX, aY,
+ &retx, &rety);
+ RefPtr<nsWindow> innerMostWindow = get_window_for_gdk_window(innerWindow);
+
+ if (!innerMostWindow) {
+ innerMostWindow = window;
+ }
+
+ LOGDRAG(("nsWindow drag-drop signal for %p\n", (void*)innerMostWindow));
+
+ LayoutDeviceIntPoint point = window->GdkPointToDevicePixels({ retx, rety });
+
+ return nsDragService::GetInstance()->
+ ScheduleDropEvent(innerMostWindow, aDragContext,
+ point, aTime);
+}
+
+static void
+drag_data_received_event_cb(GtkWidget *aWidget,
+ GdkDragContext *aDragContext,
+ gint aX,
+ gint aY,
+ GtkSelectionData *aSelectionData,
+ guint aInfo,
+ guint aTime,
+ gpointer aData)
+{
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window)
+ return;
+
+ window->OnDragDataReceivedEvent(aWidget,
+ aDragContext,
+ aX, aY,
+ aSelectionData,
+ aInfo, aTime, aData);
+}
+
+static nsresult
+initialize_prefs(void)
+{
+ gRaiseWindows =
+ Preferences::GetBool("mozilla.widget.raise-on-setfocus", true);
+
+ return NS_OK;
+}
+
+static GdkWindow *
+get_inner_gdk_window (GdkWindow *aWindow,
+ gint x, gint y,
+ gint *retx, gint *rety)
+{
+ gint cx, cy, cw, ch;
+ GList *children = gdk_window_peek_children(aWindow);
+ for (GList *child = g_list_last(children);
+ child;
+ child = g_list_previous(child)) {
+ GdkWindow *childWindow = (GdkWindow *) child->data;
+ if (get_window_for_gdk_window(childWindow)) {
+#if (MOZ_WIDGET_GTK == 2)
+ gdk_window_get_geometry(childWindow, &cx, &cy, &cw, &ch, nullptr);
+#else
+ gdk_window_get_geometry(childWindow, &cx, &cy, &cw, &ch);
+#endif
+ if ((cx < x) && (x < (cx + cw)) &&
+ (cy < y) && (y < (cy + ch)) &&
+ gdk_window_is_visible(childWindow)) {
+ return get_inner_gdk_window(childWindow,
+ x - cx, y - cy,
+ retx, rety);
+ }
+ }
+ }
+ *retx = x;
+ *rety = y;
+ return aWindow;
+}
+
+static inline bool
+is_context_menu_key(const WidgetKeyboardEvent& aKeyEvent)
+{
+ return ((aKeyEvent.mKeyCode == NS_VK_F10 && aKeyEvent.IsShift() &&
+ !aKeyEvent.IsControl() && !aKeyEvent.IsMeta() &&
+ !aKeyEvent.IsAlt()) ||
+ (aKeyEvent.mKeyCode == NS_VK_CONTEXT_MENU && !aKeyEvent.IsShift() &&
+ !aKeyEvent.IsControl() && !aKeyEvent.IsMeta() &&
+ !aKeyEvent.IsAlt()));
+}
+
+static int
+is_parent_ungrab_enter(GdkEventCrossing *aEvent)
+{
+ return (GDK_CROSSING_UNGRAB == aEvent->mode) &&
+ ((GDK_NOTIFY_ANCESTOR == aEvent->detail) ||
+ (GDK_NOTIFY_VIRTUAL == aEvent->detail));
+
+}
+
+static int
+is_parent_grab_leave(GdkEventCrossing *aEvent)
+{
+ return (GDK_CROSSING_GRAB == aEvent->mode) &&
+ ((GDK_NOTIFY_ANCESTOR == aEvent->detail) ||
+ (GDK_NOTIFY_VIRTUAL == aEvent->detail));
+}
+
+#ifdef ACCESSIBILITY
+void
+nsWindow::CreateRootAccessible()
+{
+ if (mIsTopLevel && !mRootAccessible) {
+ LOG(("nsWindow:: Create Toplevel Accessibility\n"));
+ mRootAccessible = GetRootAccessible();
+ }
+}
+
+void
+nsWindow::DispatchEventToRootAccessible(uint32_t aEventType)
+{
+ if (!a11y::ShouldA11yBeEnabled()) {
+ return;
+ }
+
+ nsAccessibilityService* accService = GetOrCreateAccService();
+ if (!accService) {
+ return;
+ }
+
+ // Get the root document accessible and fire event to it.
+ a11y::Accessible* acc = GetRootAccessible();
+ if (acc) {
+ accService->FireAccessibleEvent(aEventType, acc);
+ }
+}
+
+void
+nsWindow::DispatchActivateEventAccessible(void)
+{
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_ACTIVATE);
+}
+
+void
+nsWindow::DispatchDeactivateEventAccessible(void)
+{
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_DEACTIVATE);
+}
+
+void
+nsWindow::DispatchMaximizeEventAccessible(void)
+{
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_MAXIMIZE);
+}
+
+void
+nsWindow::DispatchMinimizeEventAccessible(void)
+{
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_MINIMIZE);
+}
+
+void
+nsWindow::DispatchRestoreEventAccessible(void)
+{
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_RESTORE);
+}
+
+#endif /* #ifdef ACCESSIBILITY */
+
+// nsChildWindow class
+
+nsChildWindow::nsChildWindow()
+{
+}
+
+nsChildWindow::~nsChildWindow()
+{
+}
+
+NS_IMETHODIMP_(void)
+nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ if (!mIMContext) {
+ return;
+ }
+ mIMContext->SetInputContext(this, &aContext, &aAction);
+}
+
+NS_IMETHODIMP_(InputContext)
+nsWindow::GetInputContext()
+{
+ InputContext context;
+ if (!mIMContext) {
+ context.mIMEState.mEnabled = IMEState::DISABLED;
+ context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
+ } else {
+ context = mIMContext->GetInputContext();
+ }
+ return context;
+}
+
+nsIMEUpdatePreference
+nsWindow::GetIMEUpdatePreference()
+{
+ if (!mIMContext) {
+ return nsIMEUpdatePreference();
+ }
+ return mIMContext->GetIMEUpdatePreference();
+}
+
+NS_IMETHODIMP_(TextEventDispatcherListener*)
+nsWindow::GetNativeTextEventDispatcherListener()
+{
+ if (NS_WARN_IF(!mIMContext)) {
+ return nullptr;
+ }
+ return mIMContext;
+}
+
+bool
+nsWindow::ExecuteNativeKeyBindingRemapped(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData,
+ uint32_t aGeckoKeyCode,
+ uint32_t aNativeKeyCode)
+{
+ WidgetKeyboardEvent modifiedEvent(aEvent);
+ modifiedEvent.mKeyCode = aGeckoKeyCode;
+ static_cast<GdkEventKey*>(modifiedEvent.mNativeKeyEvent)->keyval =
+ aNativeKeyCode;
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ return keyBindings->Execute(modifiedEvent, aCallback, aCallbackData);
+}
+
+NS_IMETHODIMP_(bool)
+nsWindow::ExecuteNativeKeyBinding(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData)
+{
+ if (aEvent.mKeyCode >= NS_VK_LEFT && aEvent.mKeyCode <= NS_VK_DOWN) {
+
+ // Check if we're targeting content with vertical writing mode,
+ // and if so remap the arrow keys.
+ WidgetQueryContentEvent query(true, eQuerySelectedText, this);
+ nsEventStatus status;
+ DispatchEvent(&query, status);
+
+ if (query.mSucceeded && query.mReply.mWritingMode.IsVertical()) {
+ uint32_t geckoCode = 0;
+ uint32_t gdkCode = 0;
+ switch (aEvent.mKeyCode) {
+ case NS_VK_LEFT:
+ if (query.mReply.mWritingMode.IsVerticalLR()) {
+ geckoCode = NS_VK_UP;
+ gdkCode = GDK_Up;
+ } else {
+ geckoCode = NS_VK_DOWN;
+ gdkCode = GDK_Down;
+ }
+ break;
+
+ case NS_VK_RIGHT:
+ if (query.mReply.mWritingMode.IsVerticalLR()) {
+ geckoCode = NS_VK_DOWN;
+ gdkCode = GDK_Down;
+ } else {
+ geckoCode = NS_VK_UP;
+ gdkCode = GDK_Up;
+ }
+ break;
+
+ case NS_VK_UP:
+ geckoCode = NS_VK_LEFT;
+ gdkCode = GDK_Left;
+ break;
+
+ case NS_VK_DOWN:
+ geckoCode = NS_VK_RIGHT;
+ gdkCode = GDK_Right;
+ break;
+ }
+
+ return ExecuteNativeKeyBindingRemapped(aType, aEvent, aCallback,
+ aCallbackData,
+ geckoCode, gdkCode);
+ }
+ }
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ return keyBindings->Execute(aEvent, aCallback, aCallbackData);
+}
+
+#if defined(MOZ_X11) && (MOZ_WIDGET_GTK == 2)
+/* static */ already_AddRefed<DrawTarget>
+nsWindow::GetDrawTargetForGdkDrawable(GdkDrawable* aDrawable,
+ const IntSize& aSize)
+{
+ GdkVisual* visual = gdk_drawable_get_visual(aDrawable);
+ Screen* xScreen =
+ gdk_x11_screen_get_xscreen(gdk_drawable_get_screen(aDrawable));
+ Display* xDisplay = DisplayOfScreen(xScreen);
+ Drawable xDrawable = gdk_x11_drawable_get_xid(aDrawable);
+
+ RefPtr<gfxASurface> surface;
+
+ if (visual) {
+ Visual* xVisual = gdk_x11_visual_get_xvisual(visual);
+
+ surface = new gfxXlibSurface(xDisplay, xDrawable, xVisual, aSize);
+ } else {
+ // no visual? we must be using an xrender format. Find a format
+ // for this depth.
+ XRenderPictFormat *pf = nullptr;
+ switch (gdk_drawable_get_depth(aDrawable)) {
+ case 32:
+ pf = XRenderFindStandardFormat(xDisplay, PictStandardARGB32);
+ break;
+ case 24:
+ pf = XRenderFindStandardFormat(xDisplay, PictStandardRGB24);
+ break;
+ default:
+ NS_ERROR("Don't know how to handle the given depth!");
+ break;
+ }
+
+ surface = new gfxXlibSurface(xScreen, xDrawable, pf, aSize);
+ }
+
+ RefPtr<DrawTarget> dt =
+ gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(surface, aSize);
+
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+
+ return dt.forget();
+}
+#endif
+
+already_AddRefed<DrawTarget>
+nsWindow::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode)
+{
+ return mSurfaceProvider.StartRemoteDrawingInRegion(aInvalidRegion, aBufferMode);
+}
+
+void
+nsWindow::EndRemoteDrawingInRegion(DrawTarget* aDrawTarget,
+ LayoutDeviceIntRegion& aInvalidRegion)
+{
+ mSurfaceProvider.EndRemoteDrawingInRegion(aDrawTarget, aInvalidRegion);
+}
+
+// Code shared begin BeginMoveDrag and BeginResizeDrag
+bool
+nsWindow::GetDragInfo(WidgetMouseEvent* aMouseEvent,
+ GdkWindow** aWindow, gint* aButton,
+ gint* aRootX, gint* aRootY)
+{
+ if (aMouseEvent->button != WidgetMouseEvent::eLeftButton) {
+ // we can only begin a move drag with the left mouse button
+ return false;
+ }
+ *aButton = 1;
+
+ // get the gdk window for this widget
+ GdkWindow* gdk_window = mGdkWindow;
+ if (!gdk_window) {
+ return false;
+ }
+#ifdef DEBUG
+ // GDK_IS_WINDOW(...) expands to a statement-expression, and
+ // statement-expressions are not allowed in template-argument lists. So we
+ // have to make the MOZ_ASSERT condition indirect.
+ if (!GDK_IS_WINDOW(gdk_window)) {
+ MOZ_ASSERT(false, "must really be window");
+ }
+#endif
+
+ // find the top-level window
+ gdk_window = gdk_window_get_toplevel(gdk_window);
+ MOZ_ASSERT(gdk_window,
+ "gdk_window_get_toplevel should not return null");
+ *aWindow = gdk_window;
+
+ if (!aMouseEvent->mWidget) {
+ return false;
+ }
+
+ // FIXME: It would be nice to have the widget position at the time
+ // of the event, but it's relatively unlikely that the widget has
+ // moved since the mousedown. (On the other hand, it's quite likely
+ // that the mouse has moved, which is why we use the mouse position
+ // from the event.)
+ LayoutDeviceIntPoint offset = aMouseEvent->mWidget->WidgetToScreenOffset();
+ *aRootX = aMouseEvent->mRefPoint.x + offset.x;
+ *aRootY = aMouseEvent->mRefPoint.y + offset.y;
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsWindow::BeginMoveDrag(WidgetMouseEvent* aEvent)
+{
+ MOZ_ASSERT(aEvent, "must have event");
+ MOZ_ASSERT(aEvent->mClass == eMouseEventClass,
+ "event must have correct struct type");
+
+ GdkWindow *gdk_window;
+ gint button, screenX, screenY;
+ if (!GetDragInfo(aEvent, &gdk_window, &button, &screenX, &screenY)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // tell the window manager to start the move
+ screenX = DevicePixelsToGdkCoordRoundDown(screenX);
+ screenY = DevicePixelsToGdkCoordRoundDown(screenY);
+ gdk_window_begin_move_drag(gdk_window, button, screenX, screenY,
+ aEvent->mTime);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::BeginResizeDrag(WidgetGUIEvent* aEvent,
+ int32_t aHorizontal,
+ int32_t aVertical)
+{
+ NS_ENSURE_ARG_POINTER(aEvent);
+
+ if (aEvent->mClass != eMouseEventClass) {
+ // you can only begin a resize drag with a mouse event
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ GdkWindow *gdk_window;
+ gint button, screenX, screenY;
+ if (!GetDragInfo(aEvent->AsMouseEvent(), &gdk_window, &button,
+ &screenX, &screenY)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // work out what GdkWindowEdge we're talking about
+ GdkWindowEdge window_edge;
+ if (aVertical < 0) {
+ if (aHorizontal < 0) {
+ window_edge = GDK_WINDOW_EDGE_NORTH_WEST;
+ } else if (aHorizontal == 0) {
+ window_edge = GDK_WINDOW_EDGE_NORTH;
+ } else {
+ window_edge = GDK_WINDOW_EDGE_NORTH_EAST;
+ }
+ } else if (aVertical == 0) {
+ if (aHorizontal < 0) {
+ window_edge = GDK_WINDOW_EDGE_WEST;
+ } else if (aHorizontal == 0) {
+ return NS_ERROR_INVALID_ARG;
+ } else {
+ window_edge = GDK_WINDOW_EDGE_EAST;
+ }
+ } else {
+ if (aHorizontal < 0) {
+ window_edge = GDK_WINDOW_EDGE_SOUTH_WEST;
+ } else if (aHorizontal == 0) {
+ window_edge = GDK_WINDOW_EDGE_SOUTH;
+ } else {
+ window_edge = GDK_WINDOW_EDGE_SOUTH_EAST;
+ }
+ }
+
+ // tell the window manager to start the resize
+ gdk_window_begin_resize_drag(gdk_window, window_edge, button,
+ screenX, screenY, aEvent->mTime);
+
+ return NS_OK;
+}
+
+nsIWidget::LayerManager*
+nsWindow::GetLayerManager(PLayerTransactionChild* aShadowManager,
+ LayersBackend aBackendHint,
+ LayerManagerPersistence aPersistence)
+{
+ if (mIsDestroyed) {
+ // Prevent external code from triggering the re-creation of the LayerManager/Compositor
+ // during shutdown. Just return what we currently have, which is most likely null.
+ return mLayerManager;
+ }
+ if (!mLayerManager && eTransparencyTransparent == GetTransparencyMode()) {
+ mLayerManager = CreateBasicLayerManager();
+ }
+
+ return nsBaseWidget::GetLayerManager(aShadowManager, aBackendHint, aPersistence);
+}
+
+void
+nsWindow::ClearCachedResources()
+{
+ if (mLayerManager &&
+ mLayerManager->GetBackendType() == mozilla::layers::LayersBackend::LAYERS_BASIC) {
+ mLayerManager->ClearCachedResources();
+ }
+
+ GList* children = gdk_window_peek_children(mGdkWindow);
+ for (GList* list = children; list; list = list->next) {
+ nsWindow* window = get_window_for_gdk_window(GDK_WINDOW(list->data));
+ if (window) {
+ window->ClearCachedResources();
+ }
+ }
+}
+
+gint
+nsWindow::GdkScaleFactor()
+{
+#if (MOZ_WIDGET_GTK >= 3)
+ // Available as of GTK 3.10+
+ static auto sGdkWindowGetScaleFactorPtr = (gint (*)(GdkWindow*))
+ dlsym(RTLD_DEFAULT, "gdk_window_get_scale_factor");
+ if (sGdkWindowGetScaleFactorPtr && mGdkWindow)
+ return (*sGdkWindowGetScaleFactorPtr)(mGdkWindow);
+#endif
+ return nsScreenGtk::GetGtkMonitorScaleFactor();
+}
+
+
+gint
+nsWindow::DevicePixelsToGdkCoordRoundUp(int pixels) {
+ gint scale = GdkScaleFactor();
+ return (pixels + scale - 1) / scale;
+}
+
+gint
+nsWindow::DevicePixelsToGdkCoordRoundDown(int pixels) {
+ gint scale = GdkScaleFactor();
+ return pixels / scale;
+}
+
+GdkPoint
+nsWindow::DevicePixelsToGdkPointRoundDown(LayoutDeviceIntPoint point) {
+ gint scale = GdkScaleFactor();
+ return { point.x / scale, point.y / scale };
+}
+
+GdkRectangle
+nsWindow::DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect rect) {
+ gint scale = GdkScaleFactor();
+ int x = rect.x / scale;
+ int y = rect.y / scale;
+ int right = (rect.x + rect.width + scale - 1) / scale;
+ int bottom = (rect.y + rect.height + scale - 1) / scale;
+ return { x, y, right - x, bottom - y };
+}
+
+GdkRectangle
+nsWindow::DevicePixelsToGdkSizeRoundUp(LayoutDeviceIntSize pixelSize) {
+ gint scale = GdkScaleFactor();
+ gint width = (pixelSize.width + scale - 1) / scale;
+ gint height = (pixelSize.height + scale - 1) / scale;
+ return { 0, 0, width, height };
+}
+
+int
+nsWindow::GdkCoordToDevicePixels(gint coord) {
+ return coord * GdkScaleFactor();
+}
+
+LayoutDeviceIntPoint
+nsWindow::GdkEventCoordsToDevicePixels(gdouble x, gdouble y)
+{
+ gint scale = GdkScaleFactor();
+ return LayoutDeviceIntPoint::Round(x * scale, y * scale);
+}
+
+LayoutDeviceIntPoint
+nsWindow::GdkPointToDevicePixels(GdkPoint point) {
+ gint scale = GdkScaleFactor();
+ return LayoutDeviceIntPoint(point.x * scale,
+ point.y * scale);
+}
+
+LayoutDeviceIntRect
+nsWindow::GdkRectToDevicePixels(GdkRectangle rect) {
+ gint scale = GdkScaleFactor();
+ return LayoutDeviceIntRect(rect.x * scale,
+ rect.y * scale,
+ rect.width * scale,
+ rect.height * scale);
+}
+
+nsresult
+nsWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver)
+{
+ AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+
+ GdkDisplay* display = gdk_window_get_display(mGdkWindow);
+
+ // When a button-press/release event is requested, create it here and put it in the
+ // event queue. This will not emit a motion event - this needs to be done
+ // explicitly *before* requesting a button-press/release. You will also need to wait
+ // for the motion event to be dispatched before requesting a button-press/release
+ // event to maintain the desired event order.
+ if (aNativeMessage == GDK_BUTTON_PRESS || aNativeMessage == GDK_BUTTON_RELEASE) {
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+ event.type = (GdkEventType)aNativeMessage;
+ event.button.button = 1;
+ event.button.window = mGdkWindow;
+ event.button.time = GDK_CURRENT_TIME;
+
+#if (MOZ_WIDGET_GTK == 3)
+ // Get device for event source
+ GdkDeviceManager *device_manager = gdk_display_get_device_manager(display);
+ event.button.device = gdk_device_manager_get_client_pointer(device_manager);
+#endif
+
+ event.button.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
+ event.button.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ event.button.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
+ event.button.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
+
+ gdk_event_put(&event);
+ } else {
+ // We don't support specific events other than button-press/release. In all
+ // other cases we'll synthesize a motion event that will be emitted by
+ // gdk_display_warp_pointer().
+ GdkScreen* screen = gdk_window_get_screen(mGdkWindow);
+ GdkPoint point = DevicePixelsToGdkPointRoundDown(aPoint);
+ gdk_display_warp_pointer(display, screen, point.x, point.y);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsWindow::SynthesizeNativeMouseScrollEvent(mozilla::LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ double aDeltaX,
+ double aDeltaY,
+ double aDeltaZ,
+ uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags,
+ nsIObserver* aObserver)
+{
+ AutoObserverNotifier notifier(aObserver, "mousescrollevent");
+
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+ event.type = GDK_SCROLL;
+ event.scroll.window = mGdkWindow;
+ event.scroll.time = GDK_CURRENT_TIME;
+#if (MOZ_WIDGET_GTK == 3)
+ // Get device for event source
+ GdkDisplay* display = gdk_window_get_display(mGdkWindow);
+ GdkDeviceManager *device_manager = gdk_display_get_device_manager(display);
+ event.scroll.device = gdk_device_manager_get_client_pointer(device_manager);
+#endif
+ event.scroll.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
+ event.scroll.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ event.scroll.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
+ event.scroll.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
+
+ // The delta values are backwards on Linux compared to Windows and Cocoa,
+ // hence the negation.
+#if GTK_CHECK_VERSION(3,4,0)
+ // TODO: is this correct? I don't have GTK 3.4+ so I can't check
+ event.scroll.direction = GDK_SCROLL_SMOOTH;
+ event.scroll.delta_x = -aDeltaX;
+ event.scroll.delta_y = -aDeltaY;
+#else
+ if (aDeltaX < 0) {
+ event.scroll.direction = GDK_SCROLL_RIGHT;
+ } else if (aDeltaX > 0) {
+ event.scroll.direction = GDK_SCROLL_LEFT;
+ } else if (aDeltaY < 0) {
+ event.scroll.direction = GDK_SCROLL_DOWN;
+ } else if (aDeltaY > 0) {
+ event.scroll.direction = GDK_SCROLL_UP;
+ } else {
+ return NS_OK;
+ }
+#endif
+
+ gdk_event_put(&event);
+
+ return NS_OK;
+}
+
+#if GTK_CHECK_VERSION(3,4,0)
+nsresult
+nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver)
+{
+ AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+
+ static std::map<uint32_t, GdkEventSequence*> sKnownPointers;
+
+ auto result = sKnownPointers.find(aPointerId);
+ switch (aPointerState) {
+ case TOUCH_CONTACT:
+ if (result == sKnownPointers.end()) {
+ // GdkEventSequence isn't a thing we can instantiate, and never gets
+ // dereferenced in the gtk code. It's an opaque pointer, the only
+ // requirement is that it be distinct from other instances of
+ // GdkEventSequence*.
+ event.touch.sequence = (GdkEventSequence*)((uintptr_t)aPointerId);
+ sKnownPointers[aPointerId] = event.touch.sequence;
+ event.type = GDK_TOUCH_BEGIN;
+ } else {
+ event.touch.sequence = result->second;
+ event.type = GDK_TOUCH_UPDATE;
+ }
+ break;
+ case TOUCH_REMOVE:
+ event.type = GDK_TOUCH_END;
+ if (result == sKnownPointers.end()) {
+ NS_WARNING("Tried to synthesize touch-end for unknown pointer!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ event.touch.sequence = result->second;
+ sKnownPointers.erase(result);
+ break;
+ case TOUCH_CANCEL:
+ event.type = GDK_TOUCH_CANCEL;
+ if (result == sKnownPointers.end()) {
+ NS_WARNING("Tried to synthesize touch-cancel for unknown pointer!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ event.touch.sequence = result->second;
+ sKnownPointers.erase(result);
+ break;
+ case TOUCH_HOVER:
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ event.touch.window = mGdkWindow;
+ event.touch.time = GDK_CURRENT_TIME;
+
+ GdkDisplay* display = gdk_window_get_display(mGdkWindow);
+ GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
+ event.touch.device = gdk_device_manager_get_client_pointer(device_manager);
+
+ event.touch.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
+ event.touch.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ event.touch.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
+ event.touch.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
+
+ gdk_event_put(&event);
+
+ return NS_OK;
+}
+#endif
+
+int32_t
+nsWindow::RoundsWidgetCoordinatesTo()
+{
+ return GdkScaleFactor();
+}
+
+void nsWindow::GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData)
+{
+ #ifdef MOZ_X11
+ *aInitData = mozilla::widget::CompositorWidgetInitData(
+ mXWindow,
+ nsCString(XDisplayString(mXDisplay)),
+ GetClientSize());
+ #endif
+}
diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h
new file mode 100644
index 000000000..49a8d4baf
--- /dev/null
+++ b/widget/gtk/nsWindow.h
@@ -0,0 +1,580 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 __nsWindow_h__
+#define __nsWindow_h__
+
+#include "mozcontainer.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIDragService.h"
+#include "nsITimer.h"
+#include "nsGkAtoms.h"
+#include "nsRefPtrHashtable.h"
+
+#include "nsBaseWidget.h"
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#ifdef MOZ_X11
+#include <gdk/gdkx.h>
+#endif /* MOZ_X11 */
+
+#include "mozilla/widget/WindowSurface.h"
+#include "mozilla/widget/WindowSurfaceProvider.h"
+
+#ifdef ACCESSIBILITY
+#include "mozilla/a11y/Accessible.h"
+#endif
+#include "mozilla/EventForwards.h"
+#include "mozilla/TouchEvents.h"
+
+#include "IMContextWrapper.h"
+
+#undef LOG
+#ifdef MOZ_LOGGING
+
+#include "mozilla/Logging.h"
+#include "nsTArray.h"
+#include "Units.h"
+
+extern PRLogModuleInfo *gWidgetLog;
+extern PRLogModuleInfo *gWidgetFocusLog;
+extern PRLogModuleInfo *gWidgetDragLog;
+extern PRLogModuleInfo *gWidgetDrawLog;
+
+#define LOG(args) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, args)
+#define LOGFOCUS(args) MOZ_LOG(gWidgetFocusLog, mozilla::LogLevel::Debug, args)
+#define LOGDRAG(args) MOZ_LOG(gWidgetDragLog, mozilla::LogLevel::Debug, args)
+#define LOGDRAW(args) MOZ_LOG(gWidgetDrawLog, mozilla::LogLevel::Debug, args)
+
+#else
+
+#define LOG(args)
+#define LOGFOCUS(args)
+#define LOGDRAG(args)
+#define LOGDRAW(args)
+
+#endif /* MOZ_LOGGING */
+
+class gfxPattern;
+class nsPluginNativeWindowGtk;
+
+namespace mozilla {
+class TimeStamp;
+class CurrentX11TimeGetter;
+}
+
+class nsWindow : public nsBaseWidget
+{
+public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::WidgetEventTime WidgetEventTime;
+
+ nsWindow();
+
+ static void ReleaseGlobals();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ void CommonCreate(nsIWidget *aParent, bool aListenForResizes);
+
+ virtual nsresult DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override;
+
+ // called when we are destroyed
+ virtual void OnDestroy(void) override;
+
+ // called to check and see if a widget's dimensions are sane
+ bool AreBoundsSane(void);
+
+ // nsIWidget
+ using nsBaseWidget::Create; // for Create signature not overridden here
+ virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) override;
+ virtual void Destroy() override;
+ virtual nsIWidget *GetParent() override;
+ virtual float GetDPI() override;
+ virtual double GetDefaultScaleInternal() override;
+ // Under Gtk, we manage windows using device pixels so no scaling is needed:
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() final
+ {
+ return mozilla::DesktopToLayoutDeviceScale(1.0);
+ }
+ virtual nsresult SetParent(nsIWidget* aNewParent) override;
+ virtual void SetModal(bool aModal) override;
+ virtual bool IsVisible() const override;
+ virtual void ConstrainPosition(bool aAllowSlop,
+ int32_t *aX,
+ int32_t *aY) override;
+ virtual void SetSizeConstraints(const SizeConstraints& aConstraints) override;
+ NS_IMETHOD Move(double aX,
+ double aY) override;
+ NS_IMETHOD Show (bool aState) override;
+ NS_IMETHOD Resize (double aWidth,
+ double aHeight,
+ bool aRepaint) override;
+ NS_IMETHOD Resize (double aX,
+ double aY,
+ double aWidth,
+ double aHeight,
+ bool aRepaint) override;
+ virtual bool IsEnabled() const override;
+
+ void SetZIndex(int32_t aZIndex) override;
+ virtual void SetSizeMode(nsSizeMode aMode) override;
+ NS_IMETHOD Enable(bool aState) override;
+ NS_IMETHOD SetFocus(bool aRaise = false) override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+ virtual LayoutDeviceIntRect GetClientBounds() override;
+ virtual LayoutDeviceIntSize GetClientSize() override;
+ virtual LayoutDeviceIntPoint GetClientOffset() override;
+ NS_IMETHOD SetCursor(nsCursor aCursor) override;
+ NS_IMETHOD SetCursor(imgIContainer* aCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY) override;
+ NS_IMETHOD Invalidate(const LayoutDeviceIntRect& aRect) override;
+ virtual void* GetNativeData(uint32_t aDataType) override;
+ void SetNativeData(uint32_t aDataType, uintptr_t aVal) override;
+ NS_IMETHOD SetTitle(const nsAString& aTitle) override;
+ NS_IMETHOD SetIcon(const nsAString& aIconSpec) override;
+ virtual void SetWindowClass(const nsAString& xulWinType) override;
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ virtual void CaptureMouse(bool aCapture) override;
+ virtual void CaptureRollupEvents(nsIRollupListener *aListener,
+ bool aDoCapture) override;
+ NS_IMETHOD GetAttention(int32_t aCycleCount) override;
+ virtual nsresult SetWindowClipRegion(const nsTArray<LayoutDeviceIntRect>& aRects,
+ bool aIntersectWithExisting) override;
+ virtual bool HasPendingInputEvent() override;
+
+ virtual bool PrepareForFullscreenTransition(nsISupports** aData) override;
+ virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback) override;
+ virtual nsresult MakeFullScreen(bool aFullScreen,
+ nsIScreen* aTargetScreen = nullptr) override;
+ NS_IMETHOD HideWindowChrome(bool aShouldHide) override;
+
+ /**
+ * GetLastUserInputTime returns a timestamp for the most recent user input
+ * event. This is intended for pointer grab requests (including drags).
+ */
+ static guint32 GetLastUserInputTime();
+
+ // utility method, -1 if no change should be made, otherwise returns a
+ // value that can be passed to gdk_window_set_decorations
+ gint ConvertBorderStyles(nsBorderStyle aStyle);
+
+ GdkRectangle DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect aRect);
+
+ // event callbacks
+#if (MOZ_WIDGET_GTK == 2)
+ gboolean OnExposeEvent(GdkEventExpose *aEvent);
+#else
+ gboolean OnExposeEvent(cairo_t *cr);
+#endif
+ gboolean OnConfigureEvent(GtkWidget *aWidget,
+ GdkEventConfigure *aEvent);
+ void OnContainerUnrealize();
+ void OnSizeAllocate(GtkAllocation *aAllocation);
+ void OnDeleteEvent();
+ void OnEnterNotifyEvent(GdkEventCrossing *aEvent);
+ void OnLeaveNotifyEvent(GdkEventCrossing *aEvent);
+ void OnMotionNotifyEvent(GdkEventMotion *aEvent);
+ void OnButtonPressEvent(GdkEventButton *aEvent);
+ void OnButtonReleaseEvent(GdkEventButton *aEvent);
+ void OnContainerFocusInEvent(GdkEventFocus *aEvent);
+ void OnContainerFocusOutEvent(GdkEventFocus *aEvent);
+ gboolean OnKeyPressEvent(GdkEventKey *aEvent);
+ gboolean OnKeyReleaseEvent(GdkEventKey *aEvent);
+ void OnScrollEvent(GdkEventScroll *aEvent);
+ void OnVisibilityNotifyEvent(GdkEventVisibility *aEvent);
+ void OnWindowStateEvent(GtkWidget *aWidget,
+ GdkEventWindowState *aEvent);
+ void OnDragDataReceivedEvent(GtkWidget *aWidget,
+ GdkDragContext *aDragContext,
+ gint aX,
+ gint aY,
+ GtkSelectionData*aSelectionData,
+ guint aInfo,
+ guint aTime,
+ gpointer aData);
+ gboolean OnPropertyNotifyEvent(GtkWidget *aWidget,
+ GdkEventProperty *aEvent);
+#if GTK_CHECK_VERSION(3,4,0)
+ gboolean OnTouchEvent(GdkEventTouch* aEvent);
+#endif
+
+ virtual already_AddRefed<mozilla::gfx::DrawTarget>
+ StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion,
+ mozilla::layers::BufferMode* aBufferMode) override;
+ virtual void EndRemoteDrawingInRegion(mozilla::gfx::DrawTarget* aDrawTarget,
+ LayoutDeviceIntRegion& aInvalidRegion) override;
+
+private:
+ void UpdateAlpha(mozilla::gfx::SourceSurface* aSourceSurface, nsIntRect aBoundsRect);
+
+ void NativeMove();
+ void NativeResize();
+ void NativeMoveResize();
+
+ void NativeShow (bool aAction);
+ void SetHasMappedToplevel(bool aState);
+ LayoutDeviceIntSize GetSafeWindowSize(LayoutDeviceIntSize aSize);
+
+ void EnsureGrabs (void);
+ void GrabPointer (guint32 aTime);
+ void ReleaseGrabs (void);
+
+ void UpdateClientOffset();
+
+public:
+ enum PluginType {
+ PluginType_NONE = 0, /* do not have any plugin */
+ PluginType_XEMBED, /* the plugin support xembed */
+ PluginType_NONXEMBED /* the plugin does not support xembed */
+ };
+
+ void SetPluginType(PluginType aPluginType);
+#ifdef MOZ_X11
+ void SetNonXEmbedPluginFocus(void);
+ void LoseNonXEmbedPluginFocus(void);
+#endif /* MOZ_X11 */
+
+ void ThemeChanged(void);
+ void OnDPIChanged(void);
+ void OnCheckResize(void);
+
+#ifdef MOZ_X11
+ Window mOldFocusWindow;
+#endif /* MOZ_X11 */
+
+ static guint32 sLastButtonPressTime;
+
+ NS_IMETHOD BeginResizeDrag(mozilla::WidgetGUIEvent* aEvent,
+ int32_t aHorizontal,
+ int32_t aVertical) override;
+ NS_IMETHOD BeginMoveDrag(mozilla::WidgetMouseEvent* aEvent) override;
+
+ MozContainer* GetMozContainer() { return mContainer; }
+ // GetMozContainerWidget returns the MozContainer even for undestroyed
+ // descendant windows
+ GtkWidget* GetMozContainerWidget();
+ GdkWindow* GetGdkWindow() { return mGdkWindow; }
+ bool IsDestroyed() { return mIsDestroyed; }
+
+ void DispatchDragEvent(mozilla::EventMessage aMsg,
+ const LayoutDeviceIntPoint& aRefPoint,
+ 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);
+ WidgetEventTime GetWidgetEventTime(guint32 aEventTime);
+ mozilla::TimeStamp GetEventTimeStamp(guint32 aEventTime);
+ mozilla::CurrentX11TimeGetter* GetCurrentTimeGetter();
+
+ NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ NS_IMETHOD_(InputContext) GetInputContext() override;
+ virtual nsIMEUpdatePreference GetIMEUpdatePreference() override;
+ NS_IMETHOD_(TextEventDispatcherListener*)
+ GetNativeTextEventDispatcherListener() override;
+ bool ExecuteNativeKeyBindingRemapped(
+ NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData,
+ uint32_t aGeckoKeyCode,
+ uint32_t aNativeKeyCode);
+ NS_IMETHOD_(bool) ExecuteNativeKeyBinding(
+ NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData) override;
+
+ // These methods are for toplevel windows only.
+ void ResizeTransparencyBitmap();
+ void ApplyTransparencyBitmap();
+ void ClearTransparencyBitmap();
+
+ virtual void SetTransparencyMode(nsTransparencyMode aMode) override;
+ virtual nsTransparencyMode GetTransparencyMode() override;
+ virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override;
+ nsresult UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
+ uint8_t* aAlphas, int32_t aStride);
+
+#if (MOZ_WIDGET_GTK == 2)
+ static already_AddRefed<DrawTarget> GetDrawTargetForGdkDrawable(GdkDrawable* aDrawable,
+ const mozilla::gfx::IntSize& aSize);
+#endif
+ virtual void ReparentNativeWidget(nsIWidget* aNewParent) override;
+
+ virtual nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) override;
+
+ virtual nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+ nsIObserver* aObserver) override
+ { return SynthesizeNativeMouseEvent(aPoint, GDK_MOTION_NOTIFY, 0, aObserver); }
+
+ virtual nsresult SynthesizeNativeMouseScrollEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ double aDeltaX,
+ double aDeltaY,
+ double aDeltaZ,
+ uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags,
+ nsIObserver* aObserver) override;
+
+#if GTK_CHECK_VERSION(3,4,0)
+ virtual nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) override;
+#endif
+
+#ifdef MOZ_X11
+ Display* XDisplay() { return mXDisplay; }
+#endif
+ virtual void GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData) override;
+
+ // HiDPI scale conversion
+ gint GdkScaleFactor();
+
+ // To GDK
+ gint DevicePixelsToGdkCoordRoundUp(int pixels);
+ gint DevicePixelsToGdkCoordRoundDown(int pixels);
+ GdkPoint DevicePixelsToGdkPointRoundDown(LayoutDeviceIntPoint point);
+ GdkRectangle DevicePixelsToGdkSizeRoundUp(LayoutDeviceIntSize pixelSize);
+
+ // From GDK
+ int GdkCoordToDevicePixels(gint coord);
+ LayoutDeviceIntPoint GdkPointToDevicePixels(GdkPoint point);
+ LayoutDeviceIntPoint GdkEventCoordsToDevicePixels(gdouble x, gdouble y);
+ LayoutDeviceIntRect GdkRectToDevicePixels(GdkRectangle rect);
+
+ virtual bool WidgetTypeSupportsAcceleration() override;
+protected:
+ virtual ~nsWindow();
+
+ // event handling code
+ void DispatchActivateEvent(void);
+ void DispatchDeactivateEvent(void);
+ void DispatchResized();
+ void MaybeDispatchResized();
+
+ // Helper for SetParent and ReparentNativeWidget.
+ void ReparentNativeWidgetInternal(nsIWidget* aNewParent,
+ GtkWidget* aNewContainer,
+ GdkWindow* aNewParentWindow,
+ GtkWidget* aOldContainer);
+
+ virtual void RegisterTouchWindow() override;
+
+ nsCOMPtr<nsIWidget> mParent;
+ // Is this a toplevel window?
+ bool mIsTopLevel;
+ // Has this widget been destroyed yet?
+ bool mIsDestroyed;
+
+ // Should we send resize events on all resizes?
+ bool mListenForResizes;
+ // Does WindowResized need to be called on listeners?
+ bool mNeedsDispatchResized;
+ // This flag tracks if we're hidden or shown.
+ bool mIsShown;
+ bool mNeedsShow;
+ // is this widget enabled?
+ bool mEnabled;
+ // has the native window for this been created yet?
+ bool mCreated;
+#if GTK_CHECK_VERSION(3,4,0)
+ // whether we handle touch event
+ bool mHandleTouchEvent;
+#endif
+ // true if this is a drag and drop feedback popup
+ bool mIsDragPopup;
+ // Can we access X?
+ bool mIsX11Display;
+
+private:
+ void DestroyChildWindows();
+ GtkWidget *GetToplevelWidget();
+ nsWindow *GetContainerWindow();
+ void SetUrgencyHint(GtkWidget *top_window, bool state);
+ void *SetupPluginPort(void);
+ void SetDefaultIcon(void);
+ void InitButtonEvent(mozilla::WidgetMouseEvent& aEvent,
+ GdkEventButton* aGdkEvent);
+ bool DispatchCommandEvent(nsIAtom* aCommand);
+ bool DispatchContentCommandEvent(mozilla::EventMessage aMsg);
+ bool CheckForRollup(gdouble aMouseX, gdouble aMouseY,
+ bool aIsWheel, bool aAlwaysRollup);
+ void CheckForRollupDuringGrab()
+ {
+ CheckForRollup(0, 0, false, true);
+ }
+
+ bool GetDragInfo(mozilla::WidgetMouseEvent* aMouseEvent,
+ GdkWindow** aWindow, gint* aButton,
+ gint* aRootX, gint* aRootY);
+ void ClearCachedResources();
+ nsIWidgetListener* GetListener();
+
+ GtkWidget *mShell;
+ MozContainer *mContainer;
+ GdkWindow *mGdkWindow;
+
+ uint32_t mHasMappedToplevel : 1,
+ mIsFullyObscured : 1,
+ mRetryPointerGrab : 1;
+ nsSizeMode mSizeState;
+ PluginType mPluginType;
+
+ int32_t mTransparencyBitmapWidth;
+ int32_t mTransparencyBitmapHeight;
+
+ nsIntPoint mClientOffset;
+
+#if GTK_CHECK_VERSION(3,4,0)
+ // This field omits duplicate scroll events caused by GNOME bug 726878.
+ guint32 mLastScrollEventTime;
+
+ // for touch event handling
+ nsRefPtrHashtable<nsPtrHashKey<GdkEventSequence>, mozilla::dom::Touch> mTouches;
+#endif
+
+#ifdef MOZ_X11
+ Display* mXDisplay;
+ Window mXWindow;
+ Visual* mXVisual;
+ int mXDepth;
+ mozilla::widget::WindowSurfaceProvider mSurfaceProvider;
+#endif
+
+ // Upper bound on pending ConfigureNotify events to be dispatched to the
+ // window. See bug 1225044.
+ unsigned int mPendingConfigures;
+
+#ifdef ACCESSIBILITY
+ RefPtr<mozilla::a11y::Accessible> mRootAccessible;
+
+ /**
+ * Request to create the accessible for this window if it is top level.
+ */
+ void CreateRootAccessible();
+
+ /**
+ * Dispatch accessible event for the top level window accessible.
+ *
+ * @param aEventType [in] the accessible event type to dispatch
+ */
+ void DispatchEventToRootAccessible(uint32_t aEventType);
+
+ /**
+ * Dispatch accessible window activate event for the top level window
+ * accessible.
+ */
+ void DispatchActivateEventAccessible();
+
+ /**
+ * Dispatch accessible window deactivate event for the top level window
+ * accessible.
+ */
+ void DispatchDeactivateEventAccessible();
+
+ /**
+ * Dispatch accessible window maximize event for the top level window
+ * accessible.
+ */
+ void DispatchMaximizeEventAccessible();
+
+ /**
+ * Dispatch accessible window minize event for the top level window
+ * accessible.
+ */
+ void DispatchMinimizeEventAccessible();
+
+ /**
+ * Dispatch accessible window restore event for the top level window
+ * accessible.
+ */
+ void DispatchRestoreEventAccessible();
+#endif
+
+ // Updates the bounds of the socket widget we manage for remote plugins.
+ void ResizePluginSocketWidget();
+
+ // e10s specific - for managing the socket widget this window hosts.
+ nsPluginNativeWindowGtk* mPluginNativeWindow;
+
+ // The cursor cache
+ static GdkCursor *gsGtkCursorCache[eCursorCount];
+
+ // Transparency
+ bool mIsTransparent;
+ // This bitmap tracks which pixels are transparent. We don't support
+ // full translucency at this time; each pixel is either fully opaque
+ // or fully transparent.
+ gchar* mTransparencyBitmap;
+
+ // all of our DND stuff
+ void InitDragEvent(mozilla::WidgetDragEvent& aEvent);
+
+ float mLastMotionPressure;
+
+ // Remember the last sizemode so that we can restore it when
+ // leaving fullscreen
+ nsSizeMode mLastSizeMode;
+
+ static bool DragInProgress(void);
+
+ void DispatchMissedButtonReleases(GdkEventCrossing *aGdkEvent);
+
+ // nsBaseWidget
+ virtual LayerManager* GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
+ LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
+ LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override;
+
+ void CleanLayerManagerRecursive();
+
+ virtual int32_t RoundsWidgetCoordinatesTo() override;
+
+ /**
+ * |mIMContext| takes all IME related stuff.
+ *
+ * This is owned by the top-level nsWindow or the topmost child
+ * nsWindow embedded in a non-Gecko widget.
+ *
+ * The instance is created when the top level widget is created. And when
+ * the widget is destroyed, it's released. All child windows refer its
+ * ancestor widget's instance. So, one set of IM contexts is created for
+ * all windows in a hierarchy. If the children are released after the top
+ * level window is released, the children still have a valid pointer,
+ * however, IME doesn't work at that time.
+ */
+ RefPtr<mozilla::widget::IMContextWrapper> mIMContext;
+
+ mozilla::UniquePtr<mozilla::CurrentX11TimeGetter> mCurrentTimeGetter;
+};
+
+class nsChildWindow : public nsWindow {
+public:
+ nsChildWindow();
+ ~nsChildWindow();
+};
+
+#endif /* __nsWindow_h__ */