Blob Blame History Raw
/* -*- 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 "ScreenHelperGTK.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 "nsGkAtoms.h"
#include "nsAttrValueInlines.h"

#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/EventStates.h"
#include "mozilla/Services.h"

#include <gdk/gdkprivate.h>
#include <gtk/gtk.h>
#include <gtk/gtkx.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"
#include "mozilla/Preferences.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;
using namespace mozilla::widget;
using mozilla::dom::HTMLInputElement;

NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeGTK, nsNativeTheme, nsITheme,
                            nsIObserver)

static int gLastGdkError;

// Return scale factor of the monitor where the window is located
// by the most part or layout.css.devPixelsPerPx pref if set to > 0.
static inline gint GetMonitorScaleFactor(nsIFrame* aFrame) {
  // When the layout.css.devPixelsPerPx is set the scale can be < 1,
  // the real monitor scale cannot go under 1.
  double scale = nsIWidget::DefaultScaleOverride();
  if (scale <= 0) {
    nsIWidget* rootWidget = aFrame->PresContext()->GetRootWidget();
    if (rootWidget) {
      // We need to use GetDefaultScale() despite it returns monitor scale
      // factor multiplied by font scale factor because it is the only scale
      // updated in nsPuppetWidget.
      // Since we don't want to apply font scale factor for UI elements
      // (because GTK does not do so) we need to remove that from returned
      // value. The computed monitor scale factor needs to be rounded before
      // casting to integer to avoid rounding errors which would lead to
      // returning 0.
      int monitorScale = int(round(rootWidget->GetDefaultScale().scale /
                                   gfxPlatformGtk::GetFontScaleFactor()));
      // Monitor scale can be negative if it has not been initialized in the
      // puppet widget yet. We also make sure that we return positive value.
      if (monitorScale < 1) {
        return 1;
      }
      return monitorScale;
    }
  }
  // Use monitor scaling factor where devPixelsPerPx is set
  return ScreenHelperGTK::GetGTKMonitorScaleFactor();
}

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);

  ThemeChanged();
}

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) {
  MOZ_ASSERT(aWidgetType < ThemeWidgetType_COUNT);
  return (aDisabledVector[aWidgetType >> 3] & (1 << (aWidgetType & 7))) != 0;
}

static void SetWidgetTypeDisabled(uint8_t* aDisabledVector,
                                  uint8_t aWidgetType) {
  MOZ_ASSERT(aWidgetType < ThemeWidgetType_COUNT);
  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) {
  MOZ_ASSERT(aWidgetType < ThemeWidgetType_COUNT);
  uint16_t key = GetWidgetStateKey(aWidgetType, aWidgetState);
  return (aSafeVector[key >> 3] & (1 << (key & 7))) != 0;
}

static void SetWidgetStateSafe(uint8_t* aSafeVector, uint8_t aWidgetType,
                               GtkWidgetState* aWidgetState) {
  MOZ_ASSERT(aWidgetType < ThemeWidgetType_COUNT);
  uint16_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)) {
      nsAtom* 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) {
          *aWidgetFlags = 0;
          HTMLInputElement* inputElt =
              HTMLInputElement::FromContent(aFrame->GetContent());
          if (inputElt && inputElt->Checked())
            *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() &&
              aFrame->GetContent()->IsElement()) {
            *aWidgetFlags = aFrame->GetContent()->AsElement()->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_INNER_SPIN_BUTTON:
      aGtkWidgetType = MOZ_GTK_INNER_SPIN_BUTTON;
      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:
#ifdef MOZ_WIDGET_GTK
      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;
    case NS_THEME_WINDOW_TITLEBAR:
      aGtkWidgetType = MOZ_GTK_HEADER_BAR;
      break;
    case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
      aGtkWidgetType = MOZ_GTK_HEADER_BAR_MAXIMIZED;
      break;
    case NS_THEME_WINDOW_BUTTON_CLOSE:
      aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_CLOSE;
      break;
    case NS_THEME_WINDOW_BUTTON_MINIMIZE:
      aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE;
      break;
    case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
      aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE;
      break;
    case NS_THEME_WINDOW_BUTTON_RESTORE:
      aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE;
      break;
    default:
      return false;
  }

  return true;
}

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) {
        // 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));
        cairo_destroy(cr);
      }

      if (surf) {
        cairo_surface_destroy(surf);
      }
    }
  }
}

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;

    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 = GetMonitorScaleFactor(aFrame);
  aExtra->top *= scale;
  aExtra->right *= scale;
  aExtra->bottom *= scale;
  aExtra->left *= scale;
  return true;
}

NS_IMETHODIMP
nsNativeThemeGTK::DrawWidgetBackground(gfxContext* 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;
  nsPresContext* presContext = aFrame->PresContext();

  gfxRect rect = presContext->AppUnitsToGfxUnits(aRect);
  gfxRect dirtyRect = presContext->AppUnitsToGfxUnits(aDirtyRect);
  gint scaleFactor = GetMonitorScaleFactor(aFrame);

  // 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};

  // Save actual widget scale to GtkWidgetState as we don't provide
  // nsFrame to gtk3drawing routines.
  state.scale = scaleFactor;

  // translate everything so (0,0) is the top left of the drawingRect
  gfxPoint origin = rect.TopLeft() + drawingRect.TopLeft();

  DrawThemeWithCairo(ctx, aContext->GetDrawTarget(), state, gtkWidgetType,
                     flags, direction, scaleFactor, snapped, ToPoint(origin),
                     drawingRect.Size(), gdk_rect, transparency);

  if (!safeState) {
    // gdk_flush() call from expose event crashes Gtk+ on Wayland
    // (Gnome BZ #773307)
    if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
      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;
}

bool nsNativeThemeGTK::CreateWebRenderCommandsForWidget(
    mozilla::wr::DisplayListBuilder& aBuilder,
    mozilla::wr::IpcResourceUpdateQueue& aResources,
    const mozilla::layers::StackingContextHelper& aSc,
    mozilla::layers::WebRenderLayerManager* aManager, nsIFrame* aFrame,
    uint8_t aWidgetType, const nsRect& aRect) {
  nsPresContext* presContext = aFrame->PresContext();
  wr::LayoutRect bounds =
      aSc.ToRelativeLayoutRect(LayoutDeviceRect::FromAppUnits(
          aRect, presContext->AppUnitsPerDevPixel()));

  switch (aWidgetType) {
    case NS_THEME_WINDOW:
    case NS_THEME_DIALOG:
      aBuilder.PushRect(
          bounds, bounds, true,
          wr::ToColorF(Color::FromABGR(LookAndFeel::GetColor(
              LookAndFeel::eColorID_WindowBackground, NS_RGBA(0, 0, 0, 0)))));
      return true;

    default:
      return false;
  }
}

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;
}

void nsNativeThemeGTK::GetCachedWidgetBorder(nsIFrame* aFrame,
                                             uint8_t aWidgetType,
                                             GtkTextDirection aDirection,
                                             nsIntMargin* aResult) {
  aResult->SizeTo(0, 0, 0, 0);

  WidgetNodeType gtkWidgetType;
  gint unusedFlags;
  if (GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
                           &unusedFlags)) {
    MOZ_ASSERT(0 <= gtkWidgetType && gtkWidgetType < MOZ_GTK_WIDGET_NODE_COUNT);
    uint8_t cacheIndex = gtkWidgetType / 8;
    uint8_t cacheBit = 1u << (gtkWidgetType % 8);

    if (mBorderCacheValid[cacheIndex] & cacheBit) {
      *aResult = mBorderCache[gtkWidgetType];
    } else {
      moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top,
                                &aResult->right, &aResult->bottom, aDirection);
      if (aWidgetType != MOZ_GTK_DROPDOWN) {  // depends on aDirection
        mBorderCacheValid[cacheIndex] |= cacheBit;
        mBorderCache[gtkWidgetType] = *aResult;
      }
    }
  }
}

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_SCROLLBAR_HORIZONTAL:
    case NS_THEME_SCROLLBAR_VERTICAL: {
      GtkOrientation orientation = aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL
                                       ? GTK_ORIENTATION_HORIZONTAL
                                       : GTK_ORIENTATION_VERTICAL;
      const ScrollbarGTKMetrics* metrics =
          GetScrollbarMetrics(orientation, true);

      const GtkBorder& border = metrics->border.scrollbar;
      aResult->top = border.top;
      aResult->right = border.right;
      aResult->bottom = border.bottom;
      aResult->left = border.left;
    } break;
    case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
    case NS_THEME_SCROLLBARTRACK_VERTICAL: {
      GtkOrientation orientation =
          aWidgetType == NS_THEME_SCROLLBARTRACK_HORIZONTAL
              ? GTK_ORIENTATION_HORIZONTAL
              : GTK_ORIENTATION_VERTICAL;
      const ScrollbarGTKMetrics* metrics =
          GetScrollbarMetrics(orientation, true);

      const GtkBorder& border = metrics->border.track;
      aResult->top = border.top;
      aResult->right = border.right;
      aResult->bottom = border.bottom;
      aResult->left = border.left;
    } break;
    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: { GetCachedWidgetBorder(aFrame, aWidgetType, direction, aResult); }
  }

  gint scale = GetMonitorScaleFactor(aFrame);
  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_WINDOW_BUTTON_CLOSE:
    case NS_THEME_WINDOW_BUTTON_MINIMIZE:
    case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
    case NS_THEME_WINDOW_BUTTON_RESTORE:
    case NS_THEME_DUALBUTTON:
    case NS_THEME_TAB_SCROLL_ARROW_BACK:
    case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
    case NS_THEME_MENULIST_BUTTON:
    case NS_THEME_TOOLBARBUTTON_DROPDOWN:
    case NS_THEME_BUTTON_ARROW_UP:
    case NS_THEME_BUTTON_ARROW_DOWN:
    case NS_THEME_BUTTON_ARROW_NEXT:
    case NS_THEME_BUTTON_ARROW_PREVIOUS:
    case NS_THEME_RANGE_THUMB:
    // 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;

      GetCachedWidgetBorder(aFrame, aWidgetType, GetTextDirection(aFrame),
                            aResult);

      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 = GetMonitorScaleFactor(aFrame);
      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: {
      const ScrollbarGTKMetrics* metrics =
          GetScrollbarMetrics(GTK_ORIENTATION_VERTICAL, true);

      aResult->width = metrics->size.button.width;
      aResult->height = metrics->size.button.height;
      *aIsOverridable = false;
    } break;
    case NS_THEME_SCROLLBARBUTTON_LEFT:
    case NS_THEME_SCROLLBARBUTTON_RIGHT: {
      const ScrollbarGTKMetrics* metrics =
          GetScrollbarMetrics(GTK_ORIENTATION_HORIZONTAL, true);

      aResult->width = metrics->size.button.width;
      aResult->height = metrics->size.button.height;
      *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. */
      GtkOrientation orientation = aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL
                                       ? GTK_ORIENTATION_HORIZONTAL
                                       : GTK_ORIENTATION_VERTICAL;
      const ScrollbarGTKMetrics* metrics =
          GetScrollbarMetrics(orientation, true);

      aResult->width = metrics->size.scrollbar.width;
      aResult->height = metrics->size.scrollbar.height;
    } break;
    case NS_THEME_SCROLLBARTHUMB_VERTICAL:
    case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: {
      GtkOrientation orientation =
          aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL
              ? GTK_ORIENTATION_HORIZONTAL
              : GTK_ORIENTATION_VERTICAL;
      const ScrollbarGTKMetrics* metrics =
          GetScrollbarMetrics(orientation, true);

      aResult->width = metrics->size.thumb.width;
      aResult->height = metrics->size.thumb.height;
      *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: {
      const ToggleGTKMetrics* metrics =
          GetToggleMetrics(aWidgetType == NS_THEME_RADIO);
      aResult->width = metrics->minSizeWithBorder.width;
      aResult->height = metrics->minSizeWithBorder.height;
    } break;
    case NS_THEME_TOOLBARBUTTON_DROPDOWN:
    case NS_THEME_BUTTON_ARROW_UP:
    case NS_THEME_BUTTON_ARROW_DOWN:
    case NS_THEME_BUTTON_ARROW_NEXT:
    case NS_THEME_BUTTON_ARROW_PREVIOUS: {
      moz_gtk_get_arrow_size(MOZ_GTK_TOOLBARBUTTON_ARROW, &aResult->width,
                             &aResult->height);
      *aIsOverridable = false;
    } break;
    case NS_THEME_WINDOW_BUTTON_CLOSE: {
      const ToolbarButtonGTKMetrics* metrics =
          GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
      aResult->width = metrics->minSizeWithBorderMargin.width;
      aResult->height = metrics->minSizeWithBorderMargin.height;
      break;
    }
    case NS_THEME_WINDOW_BUTTON_MINIMIZE: {
      const ToolbarButtonGTKMetrics* metrics =
          GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE);
      aResult->width = metrics->minSizeWithBorderMargin.width;
      aResult->height = metrics->minSizeWithBorderMargin.height;
      break;
    }
    case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
    case NS_THEME_WINDOW_BUTTON_RESTORE: {
      const ToolbarButtonGTKMetrics* metrics =
          GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE);
      aResult->width = metrics->minSizeWithBorderMargin.width;
      aResult->height = metrics->minSizeWithBorderMargin.height;
      break;
    }
    case NS_THEME_CHECKBOX_CONTAINER:
    case NS_THEME_RADIO_CONTAINER:
    case NS_THEME_CHECKBOX_LABEL:
    case NS_THEME_RADIO_LABEL:
    case NS_THEME_BUTTON:
    case NS_THEME_MENULIST:
    case NS_THEME_TOOLBARBUTTON:
    case NS_THEME_TREEHEADERCELL: {
      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;
      GetCachedWidgetBorder(aFrame, aWidgetType, GetTextDirection(aFrame),
                            &border);
      aResult->width += border.left + border.right;
      aResult->height += border.top + border.bottom;
    } break;
#ifdef MOZ_WIDGET_GTK
    case NS_THEME_NUMBER_INPUT:
    case NS_THEME_TEXTFIELD: {
      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_INNER_SPIN_BUTTON:
    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 * GetMonitorScaleFactor(aFrame);

  return NS_OK;
}

NS_IMETHODIMP
nsNativeThemeGTK::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
                                     nsAtom* aAttribute, bool* aShouldRepaint,
                                     const nsAttrValue* aOldValue) {
  // Some widget types just never change state.
  if (aWidgetType == NS_THEME_TOOLBOX || aWidgetType == NS_THEME_TOOLBAR ||
      aWidgetType == NS_THEME_STATUSBAR ||
      aWidgetType == NS_THEME_STATUSBARPANEL ||
      aWidgetType == NS_THEME_RESIZERPANEL ||
      aWidgetType == NS_THEME_PROGRESSCHUNK ||
      aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL ||
      aWidgetType == NS_THEME_PROGRESSBAR ||
      aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL ||
      aWidgetType == NS_THEME_MENUBAR || aWidgetType == NS_THEME_MENUPOPUP ||
      aWidgetType == NS_THEME_TOOLTIP ||
      aWidgetType == NS_THEME_MENUSEPARATOR || aWidgetType == NS_THEME_WINDOW ||
      aWidgetType == NS_THEME_DIALOG) {
    *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));
  memset(mSafeWidgetStates, 0, sizeof(mSafeWidgetStates));
  memset(mBorderCacheValid, 0, sizeof(mBorderCacheValid));
  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_INNER_SPIN_BUTTON:
    case NS_THEME_SPINNER:
    case NS_THEME_SPINNER_UPBUTTON:
    case NS_THEME_SPINNER_DOWNBUTTON:
    case NS_THEME_SPINNER_TEXTFIELD:
      // case NS_THEME_SCROLLBAR:  (n/a for gtk)
      // case NS_THEME_SCROLLBAR_SMALL: (n/a for gtk)
    case NS_THEME_SCROLLBARBUTTON_UP:
    case NS_THEME_SCROLLBARBUTTON_DOWN:
    case NS_THEME_SCROLLBARBUTTON_LEFT:
    case NS_THEME_SCROLLBARBUTTON_RIGHT:
    case NS_THEME_SCROLLBAR_HORIZONTAL:
    case NS_THEME_SCROLLBAR_VERTICAL:
    case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
    case NS_THEME_SCROLLBARTRACK_VERTICAL:
    case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
    case NS_THEME_SCROLLBARTHUMB_VERTICAL:
    case NS_THEME_NUMBER_INPUT:
    case NS_THEME_TEXTFIELD:
    case NS_THEME_TEXTFIELD_MULTILINE:
    case NS_THEME_RANGE:
    case NS_THEME_RANGE_THUMB:
    case NS_THEME_SCALE_HORIZONTAL:
    case NS_THEME_SCALETHUMB_HORIZONTAL:
    case NS_THEME_SCALE_VERTICAL:
    case NS_THEME_SCALETHUMB_VERTICAL:
      // case NS_THEME_SCALETHUMBSTART:
      // case NS_THEME_SCALETHUMBEND:
      // case NS_THEME_SCALETHUMBTICK:
    case NS_THEME_CHECKBOX_CONTAINER:
    case NS_THEME_RADIO_CONTAINER:
    case NS_THEME_CHECKBOX_LABEL:
    case NS_THEME_RADIO_LABEL:
    case NS_THEME_MENUBAR:
    case NS_THEME_MENUPOPUP:
    case NS_THEME_MENUITEM:
    case NS_THEME_MENUARROW:
    case NS_THEME_MENUSEPARATOR:
    case NS_THEME_CHECKMENUITEM:
    case NS_THEME_RADIOMENUITEM:
    case NS_THEME_SPLITTER:
    case NS_THEME_WINDOW:
    case NS_THEME_DIALOG:
#ifdef MOZ_WIDGET_GTK
    case NS_THEME_GTK_INFO_BAR:
#endif
      return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);

    case NS_THEME_WINDOW_BUTTON_CLOSE:
    case NS_THEME_WINDOW_BUTTON_MINIMIZE:
    case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
    case NS_THEME_WINDOW_BUTTON_RESTORE:
    case NS_THEME_WINDOW_TITLEBAR:
    case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
      // GtkHeaderBar is available on GTK 3.10+, which is used for styling
      // title bars and title buttons.
      return gtk_check_version(3, 10, 0) == nullptr &&
             !IsWidgetStyled(aPresContext, aFrame, aWidgetType);

    case NS_THEME_MENULIST_BUTTON:
      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.
    case NS_THEME_MENUPOPUP:
    case NS_THEME_WINDOW:
    case NS_THEME_DIALOG:
      return eOpaque;
    case NS_THEME_SCROLLBAR_VERTICAL:
    case NS_THEME_SCROLLBAR_HORIZONTAL:
#ifdef MOZ_WIDGET_GTK
      // 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:
      return eTransparent;
  }

  return eUnknownTransparency;
}