Bug 1370040 - Use the public API -[NSVisualEffectView setMaskImage:] instead of the private API -[NSWindow _cornerMask]. r=spohl
☠☠ backed out by 2535923bbb3a ☠ ☠
authorMarkus Stange <mstange@themasta.com>
Sun, 19 Nov 2017 17:01:02 -0500
changeset 437166 84c737eb3c6ec7b10cca42759f7c4b4c138d3ddb
parent 437165 45a167ac79a556513eb8e7d0faa2b0737cdc95b5
child 437167 2535923bbb3a5600f0401b73924ec394905927b3
push id117
push userfmarier@mozilla.com
push dateTue, 28 Nov 2017 20:17:16 +0000
reviewersspohl
bugs1370040
milestone59.0a1
Bug 1370040 - Use the public API -[NSVisualEffectView setMaskImage:] instead of the private API -[NSWindow _cornerMask]. r=spohl The idea is the following: Behind-window vibrancy is mostly rendered by the window server. For a given vibrant region of a window, the window server renders a vibrancy "backdrop", which is a blurred version of everything that's behind that region, modified with a color tint and blended in some way. Then it puts our actual window contents on top of that background. The backdrop's shape is usually a rectangle. If we don't want it to be a rectangle, we need to tell the window server about the shape that we want it to be. We can't just "draw" a different shape in our own rendering, because our own rendering is merely placed on top of the backdrop - but here we want to modify the shape of the backdrop itself. NSVisualEffectView lets us set a mask image on the view. If this view is the content view of a window, then the view will automatically communicate the mask image to the window server. Traditionally, our popup windows have had a ChildView as their content view. If we now want an NSVisualEffectView to be the content view of the window, then we need to nest the ChildView inside that NSVisualEffectView. But this NSVisualEffectView is only needed when the window is vibrant and the vibrancy backdrop needs to have a certain shape. This is the case for our menus which need to have rounded corners. If the window transitions to being non-vibrant, or not needing a special shape, then we can go back to the way our window's NSView hierarchy has worked traditionally. So we need to reparent NSViews during those transitions. MozReview-Commit-ID: Bo2VzjhhR0A
widget/cocoa/VibrancyManager.h
widget/cocoa/VibrancyManager.mm
widget/cocoa/nsCocoaWindow.mm
widget/cocoa/nsNativeThemeCocoa.mm
--- a/widget/cocoa/VibrancyManager.h
+++ b/widget/cocoa/VibrancyManager.h
@@ -95,19 +95,30 @@ public:
 
   /**
    * Check whether the operating system supports vibrancy at all.
    * You may only create a VibrancyManager instance if this returns true.
    * @return Whether VibrancyManager can be used on this OS.
    */
   static bool SystemSupportsVibrancy();
 
+  /**
+   * Create an NSVisualEffectView for the specified vibrancy type. The return
+   * value is not autoreleased. We return an object of type NSView* because we
+   * compile with an SDK that does not contain a definition for
+   * NSVisualEffectView.
+   * @param aIsContainer Whether this NSView will have child views. This value
+   *                     affects hit testing: Container views will pass through
+   *                     hit testing requests to their children, and leaf views
+   *                     will be transparent to hit testing.
+   */
+  static NSView* CreateEffectView(VibrancyType aType, BOOL aIsContainer = NO);
+
 protected:
   void ClearVibrantRegion(const LayoutDeviceIntRegion& aVibrantRegion) const;
-  NSView* CreateEffectView(VibrancyType aType);
 
   const nsChildView& mCoordinateConverter;
   NSView* mContainerView;
   nsClassHashtable<nsUint32HashKey, ViewRegion> mVibrantRegions;
 };
 
 } // namespace mozilla
 
--- a/widget/cocoa/VibrancyManager.mm
+++ b/widget/cocoa/VibrancyManager.mm
@@ -96,29 +96,31 @@ HitTestNil(id self, SEL _cmd, NSPoint aP
 static BOOL
 AllowsVibrancyYes(id self, SEL _cmd)
 {
   // Means that the foreground is blended using a vibrant blend mode.
   return YES;
 }
 
 static Class
-CreateEffectViewClass(BOOL aForegroundVibrancy)
+CreateEffectViewClass(BOOL aForegroundVibrancy, BOOL aIsContainer)
 {
   // Create a class called EffectView that inherits from NSVisualEffectView
   // and overrides the methods -[NSVisualEffectView drawRect:] and
   // -[NSView hitTest:].
   Class NSVisualEffectViewClass = NSClassFromString(@"NSVisualEffectView");
   const char* className = aForegroundVibrancy
     ? "EffectViewWithForegroundVibrancy" : "EffectViewWithoutForegroundVibrancy";
   Class EffectViewClass = objc_allocateClassPair(NSVisualEffectViewClass, className, 0);
   class_addMethod(EffectViewClass, @selector(drawRect:), (IMP)DrawRectNothing,
                   "v@:{CGRect={CGPoint=dd}{CGSize=dd}}");
-  class_addMethod(EffectViewClass, @selector(hitTest:), (IMP)HitTestNil,
-                  "@@:{CGPoint=dd}");
+  if (!aIsContainer) {
+    class_addMethod(EffectViewClass, @selector(hitTest:), (IMP)HitTestNil,
+                    "@@:{CGPoint=dd}");
+  }
   if (aForegroundVibrancy) {
     // Also override the -[NSView allowsVibrancy] method to return YES.
     class_addMethod(EffectViewClass, @selector(allowsVibrancy), (IMP)AllowsVibrancyYes, "I@:");
   }
   return EffectViewClass;
 }
 
 static id
@@ -196,24 +198,25 @@ enum {
 #endif
 
 @interface NSView(NSVisualEffectViewMethods)
 - (void)setState:(NSUInteger)state;
 - (void)setMaterial:(NSUInteger)material;
 - (void)setEmphasized:(BOOL)emphasized;
 @end
 
-NSView*
-VibrancyManager::CreateEffectView(VibrancyType aType)
+/* static */ NSView*
+VibrancyManager::CreateEffectView(VibrancyType aType, bool aIsContainer)
 {
-  static Class EffectViewClassWithoutForegroundVibrancy = CreateEffectViewClass(NO);
-  static Class EffectViewClassWithForegroundVibrancy = CreateEffectViewClass(YES);
+  static Class EffectViewClasses[2][2] = {
+    { CreateEffectViewClass(NO, NO), CreateEffectViewClass(NO, YES) },
+    { CreateEffectViewClass(YES, NO), CreateEffectViewClass(YES, YES) }
+  };
 
-  Class EffectViewClass = HasVibrantForeground(aType)
-    ? EffectViewClassWithForegroundVibrancy : EffectViewClassWithoutForegroundVibrancy;
+  Class EffectViewClass = EffectViewClasses[HasVibrantForeground(aType)][aIsContainer];
   NSView* effectView = [[EffectViewClass alloc] initWithFrame:NSZeroRect];
   [effectView performSelector:@selector(setAppearance:)
                    withObject:AppearanceForVibrancyType(aType)];
   [effectView setState:VisualEffectStateForVibrancyType(aType)];
 
   BOOL canUseElCapitanMaterials = nsCocoaFeatures::OnElCapitanOrLater();
   if (aType == VibrancyType::MENU) {
     // Before 10.11 there is no material that perfectly matches the menu
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -30,16 +30,17 @@
 #include "nsStyleConsts.h"
 #include "nsNativeThemeColors.h"
 #include "nsNativeThemeCocoa.h"
 #include "nsChildView.h"
 #include "nsCocoaFeatures.h"
 #include "nsIScreenManager.h"
 #include "nsIWidgetListener.h"
 #include "nsIPresShell.h"
+#include "VibrancyManager.h"
 
 #include "gfxPlatform.h"
 #include "gfxPrefs.h"
 #include "qcms.h"
 
 #include "mozilla/AutoRestore.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/Preferences.h"
@@ -544,17 +545,19 @@ nsCocoaWindow::CreatePopupContentView(co
   nsIWidget* thisAsWidget = static_cast<nsIWidget*>(this);
   nsresult rv = mPopupContentView->Create(thisAsWidget, nullptr, aRect,
                                           aInitData);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   ChildView* newContentView = (ChildView*)mPopupContentView->GetNativeData(NS_NATIVE_WIDGET);
-  [mWindow setContentView:newContentView];
+  [newContentView setFrame:NSZeroRect];
+  [newContentView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+  [[mWindow contentView] addSubview:newContentView];
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 void nsCocoaWindow::Destroy()
 {
@@ -2975,22 +2978,16 @@ static NSMutableSet *gSwizzledFrameViewC
 
 // This method is on NSThemeFrame starting with 10.10, but since NSThemeFrame
 // is not a public class, we declare the method on NSView instead. We only have
 // this declaration in order to avoid compiler warnings.
 @interface NSView(PrivateAddKnownSubviewMethod)
  - (void)_addKnownSubview:(NSView*)aView positioned:(NSWindowOrderingMode)place relativeTo:(NSView*)otherView;
 @end
 
-// Available on 10.10
-@interface NSWindow(PrivateCornerMaskMethod)
- - (id)_cornerMask;
- - (void)_cornerMaskChanged;
-@end
-
 #if !defined(MAC_OS_X_VERSION_10_10) || \
     MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10
 
 @interface NSImage(CapInsets)
 - (void)setCapInsets:(NSEdgeInsets)capInsets;
 @end
 
 #endif
@@ -3001,44 +2998,29 @@ static NSMutableSet *gSwizzledFrameViewC
 @interface NSImage(ImageCreationWithDrawingHandler)
 + (NSImage *)imageWithSize:(NSSize)size
                    flipped:(BOOL)drawingHandlerShouldBeCalledWithFlippedContext
             drawingHandler:(BOOL (^)(NSRect dstRect))drawingHandler;
 @end
 
 #endif
 
+@interface NSView(NSVisualEffectViewSetMaskImage)
+- (void)setMaskImage:(NSImage*)image;
+@end
+
 @interface BaseWindow(Private)
 - (void)removeTrackingArea;
 - (void)cursorUpdated:(NSEvent*)aEvent;
 - (void)updateContentViewSize;
 - (void)reflowTitlebarElements;
 @end
 
 @implementation BaseWindow
 
-- (id)_cornerMask
-{
-  if (!mUseMenuStyle) {
-    return [super _cornerMask];
-  }
-
-  CGFloat radius = 4.0f;
-  NSEdgeInsets insets = { 5, 5, 5, 5 };
-  NSSize maskSize = { 12, 12 };
-  NSImage* maskImage = [NSImage imageWithSize:maskSize flipped:YES drawingHandler:^BOOL(NSRect dstRect) {
-    NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:dstRect xRadius:radius yRadius:radius];
-    [[NSColor colorWithDeviceWhite:1.0 alpha:1.0] set];
-    [path fill];
-    return YES;
-  }];
-  [maskImage setCapInsets:insets];
-  return maskImage;
-}
-
 // The frame of a window is implemented using undocumented NSView subclasses.
 // We offset the window buttons by overriding the methods _closeButtonOrigin
 // and _fullScreenButtonOrigin on these frame view classes. The class which is
 // used for a window is determined in the window's frameViewClassForStyleMask:
 // method, so this is where we make sure that we have swizzled the method on
 // all encountered classes.
 // We also override the _wantsFloatingTitlebar method to return NO in order to
 // avoid some glitches in the titlebar that are caused by the way we mess with
@@ -3113,24 +3095,64 @@ static NSMutableSet *gSwizzledFrameViewC
   mDrawTitle = NO;
   mBrightTitlebarForeground = NO;
   mUseMenuStyle = NO;
   [self updateTrackingArea];
 
   return self;
 }
 
+// Returns an autoreleased NSImage.
+static NSImage*
+GetMenuMaskImage()
+{
+  CGFloat radius = 4.0f;
+  NSEdgeInsets insets = { 5, 5, 5, 5 };
+  NSSize maskSize = { 12, 12 };
+  NSImage* maskImage = [NSImage imageWithSize:maskSize flipped:YES drawingHandler:^BOOL(NSRect dstRect) {
+    NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:dstRect xRadius:radius yRadius:radius];
+    [[NSColor colorWithDeviceWhite:1.0 alpha:1.0] set];
+    [path fill];
+    return YES;
+  }];
+  [maskImage setCapInsets:insets];
+  return maskImage;
+}
+
+- (void)swapOutChildViewWrapper:(NSView*)aNewWrapper
+{
+  [aNewWrapper setFrame:[[self contentView] frame]];
+  NSView* childView = [[self mainChildView] retain];
+  [childView removeFromSuperview];
+  [aNewWrapper addSubview:childView];
+  [childView release];
+  [super setContentView:aNewWrapper];
+}
+
 - (void)setUseMenuStyle:(BOOL)aValue
 {
-  if (aValue != mUseMenuStyle) {
-    mUseMenuStyle = aValue;
-    if ([self respondsToSelector:@selector(_cornerMaskChanged)]) {
-      [self _cornerMaskChanged];
+  if (!VibrancyManager::SystemSupportsVibrancy()) {
+    return;
+  }
+
+  if (aValue && !mUseMenuStyle) {
+    // Turn on rounded corner masking.
+    NSView* effectView = VibrancyManager::CreateEffectView(VibrancyType::MENU, YES);
+    if ([effectView respondsToSelector:@selector(setMaskImage:)]) {
+      [effectView setMaskImage:GetMenuMaskImage()];
     }
+    [self swapOutChildViewWrapper:effectView];
+    [effectView release];
+  } else if (mUseMenuStyle && !aValue) {
+    // Turn off rounded corner masking.
+    NSView* wrapper = [[NSView alloc] initWithFrame:NSZeroRect];
+    [self swapOutChildViewWrapper:wrapper];
+    [wrapper release];
   }
+  mUseMenuStyle = aValue;
 }
 
 - (void)setBeingShown:(BOOL)aValue
 {
   mBeingShown = aValue;
 }
 
 - (BOOL)isBeingShown
@@ -3264,21 +3286,17 @@ static const NSString* kStateCollectionB
 - (NSView*)trackingAreaView
 {
   NSView* contentView = [self contentView];
   return [contentView superview] ? [contentView superview] : contentView;
 }
 
 - (ChildView*)mainChildView
 {
-  NSView *contentView = [self contentView];
-  // A PopupWindow's contentView is a ChildView object.
-  if ([contentView isKindOfClass:[ChildView class]]) {
-    return (ChildView*)contentView;
-  }
+  NSView* contentView = [self contentView];
   NSView* lastView = [[contentView subviews] lastObject];
   if ([lastView isKindOfClass:[ChildView class]]) {
     return (ChildView*)lastView;
   }
   return nil;
 }
 
 - (void)removeTrackingArea
@@ -3884,17 +3902,17 @@ TitlebarDrawCallback(void* aInfo, CGCont
   }
 }
 
 - (void)setFill
 {
   float patternWidth = [mWindow frame].size.width;
 
   CGPatternCallbacks callbacks = {0, &TitlebarDrawCallback, NULL};
-  CGPatternRef pattern = CGPatternCreate(mWindow, CGRectMake(0.0f, 0.0f, patternWidth, [mWindow frame].size.height), 
+  CGPatternRef pattern = CGPatternCreate(mWindow, CGRectMake(0.0f, 0.0f, patternWidth, [mWindow frame].size.height),
                                          CGAffineTransformIdentity, patternWidth, [mWindow frame].size.height,
                                          kCGPatternTilingConstantSpacing, true, &callbacks);
 
   // Set the pattern as the fill, which is what we were asked to do. All our
   // drawing will take place in the patternDraw callback.
   CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
   CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
   CGContextSetFillColorSpace(context, patternSpace);
--- a/widget/cocoa/nsNativeThemeCocoa.mm
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -438,18 +438,18 @@ static ChildView* ChildViewForFrame(nsIF
 {
   if (!aFrame)
     return nil;
 
   nsIWidget* widget = aFrame->GetNearestWidget();
   if (!widget)
     return nil;
 
-  NSView* view = (NSView*)widget->GetNativeData(NS_NATIVE_WIDGET);
-  return [view isKindOfClass:[ChildView class]] ? (ChildView*)view : nil;
+  NSWindow* window = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
+  return [window isKindOfClass:[BaseWindow class]] ? [(BaseWindow*)window mainChildView]  : nil;
 }
 
 static NSWindow* NativeWindowForFrame(nsIFrame* aFrame,
                                       nsIWidget** aTopLevelWidget = NULL)
 {
   if (!aFrame)
     return nil;