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