Bug 1373079 - (2) Cache GetMinimumWidgetSize r?jimm draft
authorDoug Thayer <dothayer@mozilla.com>
Thu, 22 Jun 2017 16:19:13 -0400
changeset 605365 4b0d899a222abf56ac0da5641d135e612ad176e8
parent 605364 e7b03356747b99d5b66b520fde52d25eff496c19
child 605366 8cd016b3085bc9bb289ccd308f796cea099456df
push id67396
push userbmo:dothayer@mozilla.com
push dateFri, 07 Jul 2017 17:15:59 +0000
reviewersjimm
bugs1373079
milestone56.0a1
Bug 1373079 - (2) Cache GetMinimumWidgetSize r?jimm See commit (1) for more detail about the bug. This patch caches the expensive parts of GetMinimumWidgetSize, which are when we call GetDC and ReleaseDC. The exits before this cached section don't have their results cached partly because they don't seem to show up in profiles, and partly because we don't necessarily have a theme part at that point, which means we would need to have a more complicated caching scheme directly involving the aWidgetType. MozReview-Commit-ID: 886N4tTHVVk
widget/windows/nsNativeThemeWin.cpp
widget/windows/nsNativeThemeWin.h
--- a/widget/windows/nsNativeThemeWin.cpp
+++ b/widget/windows/nsNativeThemeWin.cpp
@@ -43,17 +43,18 @@ using namespace mozilla::widget;
 
 extern mozilla::LazyLogModule gWindowsLog;
 
 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeWin, nsNativeTheme, nsITheme)
 
 nsNativeThemeWin::nsNativeThemeWin() :
   mProgressDeterminateTimeStamp(TimeStamp::Now()),
   mProgressIndeterminateTimeStamp(TimeStamp::Now()),
-  mBorderCacheValid()
+  mBorderCacheValid(),
+  mMinimumWidgetSizeCacheValid()
 {
   // If there is a relevant change in forms.css for windows platform,
   // static widget style variables (e.g. sButtonBorderSize) should be 
   // reinitialized here.
 }
 
 nsNativeThemeWin::~nsNativeThemeWin()
 {
@@ -626,16 +627,72 @@ nsresult nsNativeThemeWin::GetCachedWidg
   aResult->right = outerRect.right - contentRect.right;
 
   mBorderCacheValid[cacheBitIndex] |= cacheBit;
   mBorderCache[cacheIndex] = *aResult;
 
   return NS_OK;
 }
 
+nsresult nsNativeThemeWin::GetCachedMinimumWidgetSize(nsIFrame * aFrame, HANDLE aTheme,
+                                                      nsUXThemeClass aThemeClass, uint8_t aWidgetType,
+                                                      int32_t aPart, int32_t aState, THEMESIZE aSizeReq,
+                                                      mozilla::LayoutDeviceIntSize * aResult)
+{
+  MOZ_ASSERT(aPart < THEME_PART_DISTINCT_VALUE_COUNT);
+  int32_t cacheIndex = aThemeClass * THEME_PART_DISTINCT_VALUE_COUNT + aPart;
+  int32_t cacheBitIndex = cacheIndex / 8;
+  uint8_t cacheBit = 1u << (cacheIndex % 8);
+
+  if (mMinimumWidgetSizeCacheValid[cacheBitIndex] & cacheBit) {
+    *aResult = mMinimumWidgetSizeCache[cacheIndex];
+    return NS_OK;
+  }
+
+  HDC hdc = ::GetDC(NULL);
+  if (!hdc) {
+    return NS_ERROR_FAILURE;
+  }
+
+  SIZE sz;
+  GetThemePartSize(aTheme, hdc, aPart, aState, nullptr, aSizeReq, &sz);
+  aResult->width = sz.cx;
+  aResult->height = sz.cy;
+
+  switch (aWidgetType) {
+    case NS_THEME_SPINNER_UPBUTTON:
+    case NS_THEME_SPINNER_DOWNBUTTON:
+      aResult->width++;
+      aResult->height = aResult->height / 2 + 1;
+      break;
+
+    case NS_THEME_MENUSEPARATOR:
+    {
+      SIZE gutterSize(GetGutterSize(aTheme, hdc));
+      aResult->width += gutterSize.cx;
+      break;
+    }
+
+    case NS_THEME_MENUARROW:
+    {
+      // Use the width of the arrow glyph as padding. See the drawing
+      // code for details.
+      aResult->width *= 2;
+      break;
+    }
+  }
+
+  ::ReleaseDC(nullptr, hdc);
+
+  mMinimumWidgetSizeCacheValid[cacheBitIndex] |= cacheBit;
+  mMinimumWidgetSizeCache[cacheIndex] = *aResult;
+
+  return NS_OK;
+}
+
 mozilla::Maybe<nsUXThemeClass> nsNativeThemeWin::GetThemeClass(uint8_t aWidgetType)
 {
   switch (aWidgetType) {
     case NS_THEME_BUTTON:
     case NS_THEME_RADIO:
     case NS_THEME_CHECKBOX:
     case NS_THEME_GROUPBOX:
       return Some(eUXButton);
@@ -2138,22 +2195,24 @@ NS_IMETHODIMP
 nsNativeThemeWin::GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
                                        uint8_t aWidgetType,
                                        LayoutDeviceIntSize* aResult, bool* aIsOverridable)
 {
   aResult->width = aResult->height = 0;
   *aIsOverridable = true;
   nsresult rv = NS_OK;
 
-  HANDLE theme = GetTheme(aWidgetType);
-  if (!theme) {
+  mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aWidgetType);
+  if (themeClass.isNothing()) {
     rv = ClassicGetMinimumWidgetSize(aFrame, aWidgetType, aResult, aIsOverridable);
     ScaleForFrameDPI(aResult, aFrame);
     return rv;
   }
+
+  HANDLE theme = nsUXThemeData::GetTheme(themeClass.value());
   switch (aWidgetType) {
     case NS_THEME_GROUPBOX:
     case NS_THEME_NUMBER_INPUT:
     case NS_THEME_TEXTFIELD:
     case NS_THEME_TOOLBOX:
     case NS_THEME_WIN_MEDIA_TOOLBOX:
     case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX:
     case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX:
@@ -2347,49 +2406,18 @@ nsNativeThemeWin::GetMinimumWidgetSize(n
       return rv;
   }
 
   int32_t part, state;
   rv = GetThemePartAndState(aFrame, aWidgetType, part, state);
   if (NS_FAILED(rv))
     return rv;
 
-  HDC hdc = ::GetDC(NULL);
-  if (!hdc)
-    return NS_ERROR_FAILURE;
-
-  SIZE sz;
-  GetThemePartSize(theme, hdc, part, state, nullptr, sizeReq, &sz);
-  aResult->width = sz.cx;
-  aResult->height = sz.cy;
-
-  switch(aWidgetType) {
-    case NS_THEME_SPINNER_UPBUTTON:
-    case NS_THEME_SPINNER_DOWNBUTTON:
-      aResult->width++;
-      aResult->height = aResult->height / 2 + 1;
-      break;
-
-    case NS_THEME_MENUSEPARATOR:
-    {
-      SIZE gutterSize(GetGutterSize(theme, hdc));
-      aResult->width += gutterSize.cx;
-      break;
-    }
-
-    case NS_THEME_MENUARROW:
-    {
-      // Use the width of the arrow glyph as padding. See the drawing
-      // code for details.
-      aResult->width *= 2;
-      break;
-    }
-  }
-
-  ::ReleaseDC(nullptr, hdc);
+  rv = GetCachedMinimumWidgetSize(aFrame, theme, themeClass.value(), aWidgetType, part,
+                                  state, sizeReq, aResult);
 
   ScaleForFrameDPI(aResult, aFrame);
   return rv;
 }
 
 NS_IMETHODIMP
 nsNativeThemeWin::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType, 
                                      nsIAtom* aAttribute, bool* aShouldRepaint,
@@ -2464,16 +2492,17 @@ nsNativeThemeWin::WidgetStateChanged(nsI
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNativeThemeWin::ThemeChanged()
 {
   nsUXThemeData::Invalidate();
   memset(mBorderCacheValid, 0, sizeof(mBorderCacheValid));
+  memset(mMinimumWidgetSizeCacheValid, 0, sizeof(mMinimumWidgetSizeCacheValid));
   return NS_OK;
 }
 
 bool 
 nsNativeThemeWin::ThemeSupportsWidget(nsPresContext* aPresContext,
                                       nsIFrame* aFrame,
                                       uint8_t aWidgetType)
 {
--- a/widget/windows/nsNativeThemeWin.h
+++ b/widget/windows/nsNativeThemeWin.h
@@ -123,22 +123,32 @@ protected:
                                HANDLE aTheme, HDC aHdc,
                                int aPart, int aState,
                                RECT* aWidgetRect, RECT* aClipRect);
 
   nsresult GetCachedWidgetBorder(nsIFrame* aFrame, nsUXThemeClass aThemeClass,
                                  uint8_t aWidgetType, int32_t aPart, int32_t aState,
                                  nsIntMargin* aResult);
 
+  nsresult GetCachedMinimumWidgetSize(nsIFrame* aFrame, HANDLE aTheme, nsUXThemeClass aThemeClass,
+                                      uint8_t aWidgetType, int32_t aPart, int32_t aState,
+                                      THEMESIZE aSizeReq, mozilla::LayoutDeviceIntSize* aResult);
+
 private:
   TimeStamp mProgressDeterminateTimeStamp;
   TimeStamp mProgressIndeterminateTimeStamp;
 
   // eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT is about 800 at the time of writing
   // this, and nsIntMargin is 16 bytes wide, which makes this cache (1/8 + 16) * 800
   // bytes, or about ~12KB. We could probably reduce this cache to 3KB by caching on
   // the aWidgetType value instead, but there would be some uncacheable values, since
   // we derive some theme parts from other arguments.
   uint8_t mBorderCacheValid[(eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT + 7) / 8];
   nsIntMargin mBorderCache[eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT];
+
+  // See the above not for mBorderCache and friends. However mozilla::LayoutDeviceIntSize
+  // is half the size of nsIntMargin, making the cache roughly half as large. In total
+  // the caches should come to about 18KB.
+  uint8_t mMinimumWidgetSizeCacheValid[(eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT + 7) / 8];
+  mozilla::LayoutDeviceIntSize mMinimumWidgetSizeCache[eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT];
 };
 
 #endif