Bug 1373079 - (1) Cache GetWidgetBorder r?jimm draft
authorDoug Thayer <dothayer@mozilla.com>
Wed, 21 Jun 2017 16:43:27 -0400
changeset 605364 e7b03356747b99d5b66b520fde52d25eff496c19
parent 604236 97a8f71407cc40b39d86d71a6907a5505d3eb867
child 605365 4b0d899a222abf56ac0da5641d135e612ad176e8
push id67396
push userbmo:dothayer@mozilla.com
push dateFri, 07 Jul 2017 17:15:59 +0000
reviewersjimm
bugs1373079
milestone56.0a1
Bug 1373079 - (1) Cache GetWidgetBorder r?jimm Both GetWidgetBorder and GetMinimumWidgetSize are showing up in some profiles (see bug for more details.) This is the first patch in a series of patches which cache the results of these functions. Because aWidgetType can map to multiple theme parts, in order to cover as much as possible with our cache we decided to cache based off of the theme class and the theme part, which are derived from the aWidgetType and misc. other state. (Assumption: the widget border and minimum widget size should not changed based on the theme "state" (the value that accompanies the "part".)) The total cache size for these, if we use plain arrays, is 18KB. We could reduce this by some amount by using a sparse dynamically sized cache or by just using aWidgetType and discarding the overloaded values, which are few. I don't have a great intuition for how much we care about saving a few KB, or how much time this could cause us to lose on L1 and L2 cache misses. Accordingly it might be more optimal to go with something else, and I am open to criticism/suggestions. MozReview-Commit-ID: 4LG9BnaRG7l
widget/windows/nsNativeThemeWin.cpp
widget/windows/nsNativeThemeWin.h
widget/windows/nsUXThemeConstants.h
--- a/widget/windows/nsNativeThemeWin.cpp
+++ b/widget/windows/nsNativeThemeWin.cpp
@@ -42,17 +42,18 @@ using namespace mozilla;
 using namespace mozilla::widget;
 
 extern mozilla::LazyLogModule gWindowsLog;
 
 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeWin, nsNativeTheme, nsITheme)
 
 nsNativeThemeWin::nsNativeThemeWin() :
   mProgressDeterminateTimeStamp(TimeStamp::Now()),
-  mProgressIndeterminateTimeStamp(TimeStamp::Now())
+  mProgressIndeterminateTimeStamp(TimeStamp::Now()),
+  mBorderCacheValid()
 {
   // 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()
 {
@@ -587,119 +588,166 @@ nsNativeThemeWin::DrawThemedProgressMete
                         &adjClipRect);
 
     if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) {
       NS_WARNING("unable to animate progress widget!");
     }
   }
 }
 
-HANDLE
-nsNativeThemeWin::GetTheme(uint8_t aWidgetType)
-{ 
+nsresult nsNativeThemeWin::GetCachedWidgetBorder(nsIFrame * aFrame, nsUXThemeClass aThemeClass,
+                                                 uint8_t aWidgetType, int32_t aPart, int32_t aState,
+                                                 nsIntMargin * aResult)
+{
+  int32_t cacheIndex = aThemeClass * THEME_PART_DISTINCT_VALUE_COUNT + aPart;
+  int32_t cacheBitIndex = cacheIndex / 8;
+  uint8_t cacheBit = 1u << (cacheIndex % 8);
+
+  if (mBorderCacheValid[cacheBitIndex] & cacheBit) {
+    *aResult = mBorderCache[cacheIndex];
+    return NS_OK;
+  }
+
+  HANDLE theme = nsUXThemeData::GetTheme(aThemeClass);
+  // Get our info.
+  RECT outerRect; // Create a fake outer rect.
+  outerRect.top = outerRect.left = 100;
+  outerRect.right = outerRect.bottom = 200;
+  RECT contentRect(outerRect);
+  HRESULT res = GetThemeBackgroundContentRect(theme, nullptr, aPart, aState, &outerRect, &contentRect);
+
+  if (FAILED(res)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Now compute the delta in each direction and place it in our
+  // nsIntMargin struct.
+  aResult->top = contentRect.top - outerRect.top;
+  aResult->bottom = outerRect.bottom - contentRect.bottom;
+  aResult->left = contentRect.left - outerRect.left;
+  aResult->right = outerRect.right - contentRect.right;
+
+  mBorderCacheValid[cacheBitIndex] |= cacheBit;
+  mBorderCache[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 nsUXThemeData::GetTheme(eUXButton);
+      return Some(eUXButton);
     case NS_THEME_NUMBER_INPUT:
     case NS_THEME_TEXTFIELD:
     case NS_THEME_TEXTFIELD_MULTILINE:
     case NS_THEME_FOCUS_OUTLINE:
-      return nsUXThemeData::GetTheme(eUXEdit);
+      return Some(eUXEdit);
     case NS_THEME_TOOLTIP:
-      return nsUXThemeData::GetTheme(eUXTooltip);
+      return Some(eUXTooltip);
     case NS_THEME_TOOLBOX:
-      return nsUXThemeData::GetTheme(eUXRebar);
+      return Some(eUXRebar);
     case NS_THEME_WIN_MEDIA_TOOLBOX:
-      return nsUXThemeData::GetTheme(eUXMediaRebar);
+      return Some(eUXMediaRebar);
     case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX:
-      return nsUXThemeData::GetTheme(eUXCommunicationsRebar);
+      return Some(eUXCommunicationsRebar);
     case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX:
-      return nsUXThemeData::GetTheme(eUXBrowserTabBarRebar);
+      return Some(eUXBrowserTabBarRebar);
     case NS_THEME_TOOLBAR:
     case NS_THEME_TOOLBARBUTTON:
     case NS_THEME_SEPARATOR:
-      return nsUXThemeData::GetTheme(eUXToolbar);
+      return Some(eUXToolbar);
     case NS_THEME_PROGRESSBAR:
     case NS_THEME_PROGRESSBAR_VERTICAL:
     case NS_THEME_PROGRESSCHUNK:
     case NS_THEME_PROGRESSCHUNK_VERTICAL:
-      return nsUXThemeData::GetTheme(eUXProgress);
+      return Some(eUXProgress);
     case NS_THEME_TAB:
     case NS_THEME_TABPANEL:
     case NS_THEME_TABPANELS:
-      return nsUXThemeData::GetTheme(eUXTab);
+      return Some(eUXTab);
     case NS_THEME_SCROLLBAR:
     case NS_THEME_SCROLLBAR_SMALL:
     case NS_THEME_SCROLLBAR_VERTICAL:
     case NS_THEME_SCROLLBAR_HORIZONTAL:
     case NS_THEME_SCROLLBARBUTTON_UP:
     case NS_THEME_SCROLLBARBUTTON_DOWN:
     case NS_THEME_SCROLLBARBUTTON_LEFT:
     case NS_THEME_SCROLLBARBUTTON_RIGHT:
     case NS_THEME_SCROLLBARTHUMB_VERTICAL:
     case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
-      return nsUXThemeData::GetTheme(eUXScrollbar);
+      return Some(eUXScrollbar);
     case NS_THEME_RANGE:
     case NS_THEME_RANGE_THUMB:
     case NS_THEME_SCALE_HORIZONTAL:
     case NS_THEME_SCALE_VERTICAL:
     case NS_THEME_SCALETHUMB_HORIZONTAL:
     case NS_THEME_SCALETHUMB_VERTICAL:
-      return nsUXThemeData::GetTheme(eUXTrackbar);
+      return Some(eUXTrackbar);
     case NS_THEME_SPINNER_UPBUTTON:
     case NS_THEME_SPINNER_DOWNBUTTON:
-      return nsUXThemeData::GetTheme(eUXSpin);
+      return Some(eUXSpin);
     case NS_THEME_STATUSBAR:
     case NS_THEME_STATUSBARPANEL:
     case NS_THEME_RESIZERPANEL:
     case NS_THEME_RESIZER:
-      return nsUXThemeData::GetTheme(eUXStatus);
+      return Some(eUXStatus);
     case NS_THEME_MENULIST:
     case NS_THEME_MENULIST_BUTTON:
-      return nsUXThemeData::GetTheme(eUXCombobox);
+      return Some(eUXCombobox);
     case NS_THEME_TREEHEADERCELL:
     case NS_THEME_TREEHEADERSORTARROW:
-      return nsUXThemeData::GetTheme(eUXHeader);
+      return Some(eUXHeader);
     case NS_THEME_LISTBOX:
     case NS_THEME_LISTITEM:
     case NS_THEME_TREEVIEW:
     case NS_THEME_TREETWISTYOPEN:
     case NS_THEME_TREEITEM:
-      return nsUXThemeData::GetTheme(eUXListview);
+      return Some(eUXListview);
     case NS_THEME_MENUBAR:
     case NS_THEME_MENUPOPUP:
     case NS_THEME_MENUITEM:
     case NS_THEME_CHECKMENUITEM:
     case NS_THEME_RADIOMENUITEM:
     case NS_THEME_MENUCHECKBOX:
     case NS_THEME_MENURADIO:
     case NS_THEME_MENUSEPARATOR:
     case NS_THEME_MENUARROW:
     case NS_THEME_MENUIMAGE:
     case NS_THEME_MENUITEMTEXT:
-      return nsUXThemeData::GetTheme(eUXMenu);
+      return Some(eUXMenu);
     case NS_THEME_WINDOW_TITLEBAR:
     case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
     case NS_THEME_WINDOW_FRAME_LEFT:
     case NS_THEME_WINDOW_FRAME_RIGHT:
     case NS_THEME_WINDOW_FRAME_BOTTOM:
     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_BUTTON_BOX:
     case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
     case NS_THEME_WIN_GLASS:
     case NS_THEME_WIN_BORDERLESS_GLASS:
-      return nsUXThemeData::GetTheme(eUXWindowFrame);
+      return Some(eUXWindowFrame);
   }
-  return nullptr;
+  return Nothing();
+}
+
+HANDLE
+nsNativeThemeWin::GetTheme(uint8_t aWidgetType)
+{
+  mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aWidgetType);
+  if (themeClass.isNothing()) {
+    return nullptr;
+  }
+  return nsUXThemeData::GetTheme(themeClass.value());
 }
 
 int32_t
 nsNativeThemeWin::StandardGetState(nsIFrame* aFrame, uint8_t aWidgetType,
                                    bool wantFocused)
 {
   EventStates eventState = GetContentState(aFrame, aWidgetType);
   if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE))
@@ -1814,19 +1862,19 @@ ScaleForFrameDPI(LayoutDeviceIntSize* aS
 }
 
 NS_IMETHODIMP
 nsNativeThemeWin::GetWidgetBorder(nsDeviceContext* aContext, 
                                   nsIFrame* aFrame,
                                   uint8_t aWidgetType,
                                   nsIntMargin* aResult)
 {
-  HANDLE theme = GetTheme(aWidgetType);
+  mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aWidgetType);
   nsresult rv = NS_OK;
-  if (!theme) {
+  if (themeClass.isNothing()) {
     rv = ClassicGetWidgetBorder(aContext, aFrame, aWidgetType, aResult);
     ScaleForFrameDPI(aResult, aFrame);
     return rv;
   }
 
   aResult->top = aResult->bottom = aResult->left = aResult->right = 0;
 
   if (!WidgetIsContainer(aWidgetType) ||
@@ -1854,32 +1902,18 @@ nsNativeThemeWin::GetWidgetBorder(nsDevi
 
   if (aWidgetType == NS_THEME_TOOLBAR) {
     // make space for the separator line above all toolbars but the first
     if (state == 0)
       aResult->top = TB_SEPARATOR_HEIGHT;
     return NS_OK;
   }
 
-  // Get our info.
-  RECT outerRect; // Create a fake outer rect.
-  outerRect.top = outerRect.left = 100;
-  outerRect.right = outerRect.bottom = 200;
-  RECT contentRect(outerRect);
-  HRESULT res = GetThemeBackgroundContentRect(theme, nullptr, part, state, &outerRect, &contentRect);
-  
-  if (FAILED(res))
-    return NS_ERROR_FAILURE;
-
-  // Now compute the delta in each direction and place it in our
-  // nsIntMargin struct.
-  aResult->top = contentRect.top - outerRect.top;
-  aResult->bottom = outerRect.bottom - contentRect.bottom;
-  aResult->left = contentRect.left - outerRect.left;
-  aResult->right = outerRect.right - contentRect.right;
+  rv = GetCachedWidgetBorder(aFrame, themeClass.value(), aWidgetType, part, state, aResult);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   // Remove the edges for tabs that are before or after the selected tab,
   if (aWidgetType == NS_THEME_TAB) {
     if (IsLeftToSelectedTab(aFrame))
       // Remove the right edge, since we won't be drawing it.
       aResult->right = 0;
     else if (IsRightToSelectedTab(aFrame))
       // Remove the left edge, since we won't be drawing it.
@@ -2429,16 +2463,17 @@ nsNativeThemeWin::WidgetStateChanged(nsI
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNativeThemeWin::ThemeChanged()
 {
   nsUXThemeData::Invalidate();
+  memset(mBorderCacheValid, 0, sizeof(mBorderCacheValid));
   return NS_OK;
 }
 
 bool 
 nsNativeThemeWin::ThemeSupportsWidget(nsPresContext* aPresContext,
                                       nsIFrame* aFrame,
                                       uint8_t aWidgetType)
 {
--- a/widget/windows/nsNativeThemeWin.h
+++ b/widget/windows/nsNativeThemeWin.h
@@ -6,18 +6,22 @@
 
 #ifndef nsNativeThemeWin_h
 #define nsNativeThemeWin_h
 
 #include "nsITheme.h"
 #include "nsCOMPtr.h"
 #include "nsIAtom.h"
 #include "nsNativeTheme.h"
+#include "nsThemeConstants.h"
+#include "nsUXThemeConstants.h"
+#include "nsUXThemeData.h"
 #include "gfxTypes.h"
 #include <windows.h>
+#include "mozilla/Maybe.h"
 #include "mozilla/TimeStamp.h"
 #include "nsSize.h"
 
 class nsNativeThemeWin : private nsNativeTheme,
                          public nsITheme {
   virtual ~nsNativeThemeWin();
 
 public:
@@ -79,16 +83,17 @@ public:
   virtual ThemeGeometryType ThemeGeometryTypeForWidget(nsIFrame* aFrame,
                                                        uint8_t aWidgetType) override;
 
   virtual bool ShouldHideScrollbars() override;
 
   nsNativeThemeWin();
 
 protected:
+  mozilla::Maybe<nsUXThemeClass> GetThemeClass(uint8_t aWidgetType);
   HANDLE GetTheme(uint8_t aWidgetType);
   nsresult GetThemePartAndState(nsIFrame* aFrame, uint8_t aWidgetType,
                                 int32_t& aPart, int32_t& aState);
   nsresult ClassicGetThemePartAndState(nsIFrame* aFrame, uint8_t aWidgetType,
                                        int32_t& aPart, int32_t& aState, bool& aFocused);
   nsresult ClassicDrawWidgetBackground(gfxContext* aContext,
                                        nsIFrame* aFrame,
                                        uint8_t aWidgetType,
@@ -114,14 +119,26 @@ protected:
   RECT CalculateProgressOverlayRect(nsIFrame* aFrame, RECT* aWidgetRect,
                                     bool aIsVertical, bool aIsIndeterminate,
                                     bool aIsClassic);
   void DrawThemedProgressMeter(nsIFrame* aFrame, int aWidgetType,
                                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);
+
 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];
 };
 
 #endif
--- a/widget/windows/nsUXThemeConstants.h
+++ b/widget/windows/nsUXThemeConstants.h
@@ -225,17 +225,18 @@ enum {
   WP_CAPTIONSIZINGTEMPLATE = 30,
   WP_SMALLCAPTIONSIZINGTEMPLATE = 31,
   WP_FRAMELEFTSIZINGTEMPLATE = 32,
   WP_SMALLFRAMELEFTSIZINGTEMPLATE = 33,
   WP_FRAMERIGHTSIZINGTEMPLATE = 34,
   WP_SMALLFRAMERIGHTSIZINGTEMPLATE = 35,
   WP_FRAMEBOTTOMSIZINGTEMPLATE = 36,
   WP_SMALLFRAMEBOTTOMSIZINGTEMPLATE = 37,
-  WP_FRAME = 38
+  WP_FRAME = 38,
+  WP_Count
 };
 
 enum FRAMESTATES {
   FS_ACTIVE = 1,
   FS_INACTIVE = 2
 };
 
 enum {
@@ -243,9 +244,13 @@ enum {
   BS_HOT = 2,
   BS_PUSHED = 3,
   BS_DISABLED = 4,
   BS_INACTIVE = 5 /* undocumented, inactive caption button */
 };
 
 }}} // mozilla::widget::themeconst
 
+// If any theme part ends up having a value higher than WP_Count, this will
+// need to change.
+#define THEME_PART_DISTINCT_VALUE_COUNT mozilla::widget::themeconst::WP_Count
+
 #endif