File: nsNativeThemeGTK.cpp

package info (click to toggle)
firefox 148.0.2-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,719,544 kB
  • sloc: cpp: 7,618,291; javascript: 6,701,749; ansic: 3,781,787; python: 1,418,389; xml: 638,647; asm: 438,962; java: 186,285; sh: 62,894; makefile: 19,011; objc: 13,092; perl: 12,763; yacc: 4,583; cs: 3,846; pascal: 3,448; lex: 1,720; ruby: 1,003; php: 436; lisp: 258; awk: 247; sql: 66; sed: 54; csh: 10; exp: 6
file content (344 lines) | stat: -rw-r--r-- 13,265 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
/* -*- 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 "cairo.h"
#include "nsDeviceContext.h"
#include "gtk/gtk.h"
#include "nsPresContext.h"
#include "GtkWidgets.h"
#include "nsIFrame.h"

#include "gfxContext.h"
#include "mozilla/gfx/HelpersCairo.h"
#include "mozilla/WidgetUtilsGtk.h"
#include "mozilla/StaticPrefs_widget.h"

#include <dlfcn.h>

using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::widget;

nsNativeThemeGTK::nsNativeThemeGTK() : Theme(ScrollbarStyle()) {}

nsNativeThemeGTK::~nsNativeThemeGTK() { GtkWidgets::Shutdown(); }

// This is easy to extend to 9-patch if we ever paint native widgets
// again, but we are very unlikely to do that.
static RefPtr<DataSourceSurface> GetWidgetFourPatch(
    nsIFrame* aFrame, GtkWidgets::Type aWidget, CSSIntCoord aSectionSize,
    CSSToLayoutDeviceScale aScale) {
  static auto sCairoSurfaceSetDeviceScalePtr =
      (void (*)(cairo_surface_t*, double, double))dlsym(
          RTLD_DEFAULT, "cairo_surface_set_device_scale");

  CSSIntRect rect(0, 0, aSectionSize * 2, aSectionSize * 2);
  // Save actual widget scale to GtkWidgetState as we don't provide
  // the frame to gtk3drawing routines.
  GtkWidgets::DrawingParams params{
      .widget = aWidget,
      .rect = {rect.x, rect.y, rect.width, rect.height},
      .state = GTK_STATE_FLAG_NORMAL,
      .image_scale = gint(std::ceil(aScale.scale)),
  };

  if (aFrame->PresContext()->Document()->State().HasState(
          dom::DocumentState::WINDOW_INACTIVE)) {
    params.state = GtkStateFlags(gint(params.state) | GTK_STATE_FLAG_BACKDROP);
  }

  auto surfaceRect = RoundedOut(rect * aScale);
  RefPtr<DataSourceSurface> dataSurface = Factory::CreateDataSourceSurface(
      surfaceRect.Size().ToUnknownSize(), SurfaceFormat::B8G8R8A8,
      /* aZero = */ true);
  if (NS_WARN_IF(!dataSurface)) {
    return nullptr;
  }
  DataSourceSurface::ScopedMap map(dataSurface,
                                   DataSourceSurface::MapType::WRITE);
  if (NS_WARN_IF(!map.IsMapped())) {
    return nullptr;
  }
  // Create a Cairo image surface wrapping the data surface.
  cairo_surface_t* surf = cairo_image_surface_create_for_data(
      map.GetData(), GfxFormatToCairoFormat(dataSurface->GetFormat()),
      surfaceRect.width, surfaceRect.height, map.GetStride());
  if (NS_WARN_IF(!surf)) {
    return nullptr;
  }
  if (cairo_t* cr = cairo_create(surf)) {
    if (aScale.scale != 1.0) {
      if (sCairoSurfaceSetDeviceScalePtr) {
        sCairoSurfaceSetDeviceScalePtr(surf, aScale.scale, aScale.scale);
      } else {
        cairo_scale(cr, aScale.scale, aScale.scale);
      }
    }
    GtkWidgets::Draw(cr, &params);
    cairo_destroy(cr);
  }
  cairo_surface_destroy(surf);
  return dataSurface;
}

static void DrawWindowDecorationsWithCairo(nsIFrame* aFrame,
                                           gfxContext* aContext, bool aSnapped,
                                           const Point& aDrawOrigin,
                                           const nsIntSize& aDrawSize) {
  DrawTarget* dt = aContext->GetDrawTarget();
  // If we are not snapped, we depend on the DT for translation.
  // Otherwise, we only need to take the device offset into account.
  const Point drawOffset = aSnapped ? aDrawOrigin -
                                          dt->GetTransform().GetTranslation() -
                                          aContext->GetDeviceOffset()
                                    : aDrawOrigin;

  const CSSIntCoord sectionSize =
      LookAndFeel::GetInt(LookAndFeel::IntID::TitlebarRadius);
  if (!sectionSize) {
    return;
  }

  const CSSToLayoutDeviceScale scaleFactor{
      float(AppUnitsPerCSSPixel()) /
      float(aFrame->PresContext()
                ->DeviceContext()
                ->AppUnitsPerDevPixelAtUnitFullZoom())};
  RefPtr dataSurface = GetWidgetFourPatch(
      aFrame, GtkWidgets::Type::WindowDecoration, sectionSize, scaleFactor);
  if (NS_WARN_IF(!dataSurface)) {
    return;
  }

  LayoutDeviceSize scaledSize(CSSCoord(sectionSize) * scaleFactor,
                              CSSCoord(sectionSize) * scaleFactor);

  // Top left.
  dt->DrawSurface(dataSurface, Rect(drawOffset, scaledSize.ToUnknownSize()),
                  Rect(Point(), scaledSize.ToUnknownSize()));
  // Top right.
  dt->DrawSurface(dataSurface,
                  Rect(Point(drawOffset.x + aDrawSize.width - scaledSize.width,
                             drawOffset.y),
                       scaledSize.ToUnknownSize()),
                  Rect(Point(scaledSize.width, 0), scaledSize.ToUnknownSize()));
  if (StaticPrefs::widget_gtk_rounded_bottom_corners_enabled()) {
    // Bottom left.
    dt->DrawSurface(
        dataSurface,
        Rect(Point(drawOffset.x,
                   drawOffset.y + aDrawSize.height - scaledSize.height),
             scaledSize.ToUnknownSize()),
        Rect(Point(0, scaledSize.height), scaledSize.ToUnknownSize()));

    // Bottom right
    dt->DrawSurface(
        dataSurface,
        Rect(Point(drawOffset.x + aDrawSize.width - scaledSize.width,
                   drawOffset.y + aDrawSize.height - scaledSize.height),
             scaledSize.ToUnknownSize()),
        Rect(Point(scaledSize.width, scaledSize.height),
             scaledSize.ToUnknownSize()));
  }
}

void nsNativeThemeGTK::DrawWidgetBackground(
    gfxContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance,
    const nsRect& aRect, const nsRect& aDirtyRect, DrawOverflow aDrawOverflow) {
  if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
    return Theme::DrawWidgetBackground(aContext, aFrame, aAppearance, aRect,
                                       aDirtyRect, aDrawOverflow);
  }

  if (NS_WARN_IF(aAppearance != StyleAppearance::MozWindowDecorations)) {
    return;
  }

  if (GdkIsWaylandDisplay()) {
    // We don't need to paint window decorations on Wayland, see the comments in
    // browser.css
    return;
  }

  gfxContext* ctx = aContext;
  nsPresContext* presContext = aFrame->PresContext();

  gfxRect rect = presContext->AppUnitsToGfxUnits(aRect);
  gfxRect dirtyRect = presContext->AppUnitsToGfxUnits(aDirtyRect);

  // 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.
  // We prioritize the size when snapping in order to avoid distorting widgets
  // that should be square, which can occur if edges are snapped independently.
  bool snapped = ctx->UserToDevicePixelSnapped(
      rect, gfxContext::SnapOption::PrioritizeSize);
  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).
  LayoutDeviceIntRect widgetRect(0, 0, NS_lround(rect.Width()),
                                 NS_lround(rect.Height()));

  // This is the rectangle that will actually be drawn, in gdk pixels
  LayoutDeviceIntRect drawingRect(
      int32_t(dirtyRect.X()), int32_t(dirtyRect.Y()),
      int32_t(dirtyRect.Width()), int32_t(dirtyRect.Height()));
  if (widgetRect.IsEmpty() ||
      !drawingRect.IntersectRect(widgetRect, drawingRect)) {
    return;
  }

  // translate everything so (0,0) is the top left of the drawingRect
  gfxPoint origin = rect.TopLeft() + drawingRect.TopLeft().ToUnknownPoint();
  DrawWindowDecorationsWithCairo(aFrame, ctx, snapped, ToPoint(origin),
                                 drawingRect.Size().ToUnknownSize());
}

bool nsNativeThemeGTK::CreateWebRenderCommandsForWidget(
    mozilla::wr::DisplayListBuilder& aBuilder,
    mozilla::wr::IpcResourceUpdateQueue& aResources,
    const mozilla::layers::StackingContextHelper& aSc,
    mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
    StyleAppearance aAppearance, const nsRect& aRect) {
  if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
    return Theme::CreateWebRenderCommandsForWidget(
        aBuilder, aResources, aSc, aManager, aFrame, aAppearance, aRect);
  }
  if (aAppearance == StyleAppearance::MozWindowDecorations &&
      GdkIsWaylandDisplay()) {
    // On wayland we don't need to draw window decorations.
    return true;
  }
  return false;
}

LayoutDeviceIntMargin nsNativeThemeGTK::GetWidgetBorder(
    nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
  if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
    return Theme::GetWidgetBorder(aContext, aFrame, aAppearance);
  }
  return {};
}

bool nsNativeThemeGTK::GetWidgetPadding(nsDeviceContext* aContext,
                                        nsIFrame* aFrame,
                                        StyleAppearance aAppearance,
                                        LayoutDeviceIntMargin* aResult) {
  if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
    return Theme::GetWidgetPadding(aContext, aFrame, aAppearance, aResult);
  }
  return false;
}

bool nsNativeThemeGTK::GetWidgetOverflow(nsDeviceContext* aContext,
                                         nsIFrame* aFrame,
                                         StyleAppearance aAppearance,
                                         nsRect* aOverflowRect) {
  if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
    return Theme::GetWidgetOverflow(aContext, aFrame, aAppearance,
                                    aOverflowRect);
  }
  return false;
}

auto nsNativeThemeGTK::IsWidgetNonNative(nsIFrame* aFrame,
                                         StyleAppearance aAppearance)
    -> NonNative {
  if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
    return NonNative::Always;
  }

  // If the current GTK theme color scheme matches our color-scheme, then we
  // can draw a native widget.
  if (LookAndFeel::ColorSchemeForFrame(aFrame) ==
      PreferenceSheet::ColorSchemeForChrome()) {
    return NonNative::No;
  }

  // If the non-native theme doesn't support the widget then oh well...
  if (!Theme::ThemeSupportsWidget(aFrame->PresContext(), aFrame, aAppearance)) {
    return NonNative::No;
  }

  return NonNative::BecauseColorMismatch;
}

bool nsNativeThemeGTK::IsWidgetAlwaysNonNative(nsIFrame* aFrame,
                                               StyleAppearance aAppearance) {
  return Theme::IsWidgetAlwaysNonNative(aFrame, aAppearance) ||
         aAppearance == StyleAppearance::MozMenulistArrowButton ||
         aAppearance == StyleAppearance::Textfield ||
         aAppearance == StyleAppearance::NumberInput ||
         aAppearance == StyleAppearance::PasswordInput ||
         aAppearance == StyleAppearance::Textarea ||
         aAppearance == StyleAppearance::Checkbox ||
         aAppearance == StyleAppearance::Radio ||
         aAppearance == StyleAppearance::Button ||
         aAppearance == StyleAppearance::Listbox ||
         aAppearance == StyleAppearance::Menulist;
}

LayoutDeviceIntSize nsNativeThemeGTK::GetMinimumWidgetSize(
    nsPresContext* aPresContext, nsIFrame* aFrame,
    StyleAppearance aAppearance) {
  if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
    return Theme::GetMinimumWidgetSize(aPresContext, aFrame, aAppearance);
  }
  return {};
}

bool nsNativeThemeGTK::WidgetAttributeChangeRequiresRepaint(
    StyleAppearance aAppearance, nsAtom* aAttribute) {
  // Some widget types just never change state.
  if (aAppearance == StyleAppearance::MozWindowDecorations) {
    return false;
  }
  return Theme::WidgetAttributeChangeRequiresRepaint(aAppearance, aAttribute);
}

bool nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext,
                                           nsIFrame* aFrame,
                                           StyleAppearance aAppearance) {
  if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
    return Theme::ThemeSupportsWidget(aPresContext, aFrame, aAppearance);
  }
  return aAppearance == StyleAppearance::MozWindowDecorations;
}

bool nsNativeThemeGTK::ThemeDrawsFocusForWidget(nsIFrame* aFrame,
                                                StyleAppearance aAppearance) {
  if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
    return Theme::ThemeDrawsFocusForWidget(aFrame, aAppearance);
  }
  return false;
}

nsITheme::Transparency nsNativeThemeGTK::GetWidgetTransparency(
    nsIFrame* aFrame, StyleAppearance aAppearance) {
  if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
    return Theme::GetWidgetTransparency(aFrame, aAppearance);
  }

  return eUnknownTransparency;
}

already_AddRefed<Theme> do_CreateNativeThemeDoNotUseDirectly() {
  if (gfxPlatform::IsHeadless()) {
    return do_AddRef(new Theme(Theme::ScrollbarStyle()));
  }
  return do_AddRef(new nsNativeThemeGTK());
}