Bug 420491 - Implement HideWindowChrome on Mac OS X by creating a new borderless native window and reparenting the content view. r=smichaud, r=josh
authorMarkus Stange <mstange@themasta.com>
Wed, 22 Jul 2009 10:57:39 +0200
changeset 30549 afe42cf52913db7c7310514c858f2240e89c962c
parent 30548 7974ea41cf2fed5093e60508b70645f1776b2c5c
child 30550 d7c1d5b9dcd147978ba2dfc4f8adb6b27a848a0c
push id8128
push usermstange@themasta.com
push dateWed, 22 Jul 2009 09:04:45 +0000
treeherdermozilla-central@d7c1d5b9dcd1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmichaud, josh
bugs420491
milestone1.9.2a1pre
Bug 420491 - Implement HideWindowChrome on Mac OS X by creating a new borderless native window and reparenting the content view. r=smichaud, r=josh
widget/src/cocoa/nsChildView.h
widget/src/cocoa/nsChildView.mm
widget/src/cocoa/nsCocoaWindow.h
widget/src/cocoa/nsCocoaWindow.mm
--- a/widget/src/cocoa/nsChildView.h
+++ b/widget/src/cocoa/nsChildView.h
@@ -408,16 +408,17 @@ public:
 #endif
 
   virtual gfxASurface* GetThebesSurface();
 
   NS_IMETHOD BeginSecureKeyboardInput();
   NS_IMETHOD EndSecureKeyboardInput();
 
   void              HidePlugin();
+  void              UpdatePluginPort();
 
   void              ResetParent();
 
   static PRBool DoHasPendingInputEvent();
   static PRUint32 GetCurrentInputEventCount();
   static void UpdateCurrentInputEventCount();
 
   static void ApplyConfiguration(nsIWidget* aExpectedParent,
--- a/widget/src/cocoa/nsChildView.mm
+++ b/widget/src/cocoa/nsChildView.mm
@@ -771,71 +771,28 @@ void* nsChildView::GetNativeData(PRUint3
       retVal = 0;
       break;
 
     case NS_NATIVE_OFFSETY:
       retVal = 0;
       break;
 
     case NS_NATIVE_PLUGIN_PORT:
-#ifndef NP_NO_QUICKDRAW
     case NS_NATIVE_PLUGIN_PORT_QD:
+    case NS_NATIVE_PLUGIN_PORT_CG:
     {
-      mPluginIsCG = PR_FALSE;
+#ifdef NP_NO_QUICKDRAW
+      aDataType = NS_NATIVE_PLUGIN_PORT_CG;
+#endif
+      mPluginIsCG = (aDataType == NS_NATIVE_PLUGIN_PORT_CG);
       mIsPluginView = PR_TRUE;
       if ([mView isKindOfClass:[ChildView class]])
         [(ChildView*)mView setIsPluginView:YES];
 
-      NSWindow* window = [mView nativeWindow];
-      if (window) {
-        WindowRef topLevelWindow = (WindowRef)[window windowRef];
-        if (topLevelWindow) {
-          mPluginPort.qdPort.port = ::GetWindowPort(topLevelWindow);
-
-          NSPoint viewOrigin = [mView convertPoint:NSZeroPoint toView:nil];
-          NSRect frame = [[window contentView] frame];
-          viewOrigin.y = frame.size.height - viewOrigin.y;
-          
-          // need to convert view's origin to window coordinates.
-          // then, encode as "SetOrigin" ready values.
-          mPluginPort.qdPort.portx = (PRInt32)-viewOrigin.x;
-          mPluginPort.qdPort.porty = (PRInt32)-viewOrigin.y;
-        }
-      }
-
-      retVal = (void*)&mPluginPort;
-      break;
-    }
-#endif
-
-    case NS_NATIVE_PLUGIN_PORT_CG:
-    {
-      mPluginIsCG = PR_TRUE;
-      mIsPluginView = PR_TRUE;
-      if ([mView isKindOfClass:[ChildView class]])
-        [(ChildView*)mView setIsPluginView:YES];
-
-      NSWindow* window = [mView nativeWindow];
-      if (window) {
-        // [NSGraphicsContext currentContext] is supposed to "return the
-        // current graphics context of the current thread."  But sometimes
-        // (when called while mView isn't focused for drawing) it returns a
-        // graphics context for the wrong window.  [window graphicsContext]
-        // (which "provides the graphics context associated with the window
-        // for the current thread") seems always to return the "right"
-        // graphics context.  See bug 500130.
-        mPluginPort.cgPort.context = (CGContextRef)
-          [[window graphicsContext] graphicsPort];
-        WindowRef topLevelWindow = (WindowRef)[window windowRef];
-        mPluginPort.cgPort.window = topLevelWindow;
-      } else {
-        mPluginPort.cgPort.context = nil;
-        mPluginPort.cgPort.window = nil;
-      }
-
+      UpdatePluginPort();
       retVal = (void*)&mPluginPort;
       break;
     }
   }
 
   return retVal;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
@@ -934,16 +891,56 @@ void nsChildView::HidePlugin()
        window->clipRect.left = 0;
        window->clipRect.bottom = 0;
        window->clipRect.right = 0;
        instance->SetWindow(window);
     }
   }
 }
 
+void nsChildView::UpdatePluginPort()
+{
+  NS_ASSERTION(mIsPluginView, "UpdatePluginPort called on non-plugin view");
+
+  NSWindow* window = [mView nativeWindow];
+  WindowRef topLevelWindow = window ? (WindowRef)[window windowRef] : nil;
+  if (mPluginIsCG) {
+    if (topLevelWindow) {
+      // [NSGraphicsContext currentContext] is supposed to "return the
+      // current graphics context of the current thread."  But sometimes
+      // (when called while mView isn't focused for drawing) it returns a
+      // graphics context for the wrong window.  [window graphicsContext]
+      // (which "provides the graphics context associated with the window
+      // for the current thread") seems always to return the "right"
+      // graphics context.  See bug 500130.
+      mPluginPort.cgPort.context = (CGContextRef)
+        [[window graphicsContext] graphicsPort];
+      mPluginPort.cgPort.window = topLevelWindow;
+    } else {
+      mPluginPort.cgPort.context = nil;
+      mPluginPort.cgPort.window = nil;
+    }
+  } else {
+    if (topLevelWindow) {
+      mPluginPort.qdPort.port = ::GetWindowPort(topLevelWindow);
+
+      NSPoint viewOrigin = [mView convertPoint:NSZeroPoint toView:nil];
+      NSRect frame = [[window contentView] frame];
+      viewOrigin.y = frame.size.height - viewOrigin.y;
+
+      // need to convert view's origin to window coordinates.
+      // then, encode as "SetOrigin" ready values.
+      mPluginPort.qdPort.portx = (PRInt32)-viewOrigin.x;
+      mPluginPort.qdPort.porty = (PRInt32)-viewOrigin.y;
+    } else {
+      mPluginPort.qdPort.port = nil;
+    }
+  }
+}
+
 static void HideChildPluginViews(NSView* aView)
 {
   NSArray* subviews = [aView subviews];
 
   for (unsigned int i = 0; i < [subviews count]; ++i) {
     NSView* view = [subviews objectAtIndex: i];
 
     if (![view isKindOfClass:[ChildView class]])
@@ -2272,16 +2269,19 @@ NSEvent* gLastDragEvent = nil;
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 // mozView method, set the NSWindow that this view is associated with (even when
 // not in the view hierarchy).
 - (void)setNativeWindow:(NSWindow*)aWindow
 {
   mWindow = aWindow;
+  if (aWindow && [self isPluginView] && mGeckoChild) {
+    mGeckoChild->UpdatePluginPort();
+  }
 }
 
 - (void)systemMetricsChanged
 {
   if (!mGeckoChild)
     return;
 
   nsGUIEvent guiEvent(PR_TRUE, NS_THEMECHANGED, mGeckoChild);
--- a/widget/src/cocoa/nsCocoaWindow.h
+++ b/widget/src/cocoa/nsCocoaWindow.h
@@ -209,17 +209,17 @@ public:
     virtual void* GetNativeData(PRUint32 aDataType) ;
 
     NS_IMETHOD              ConstrainPosition(PRBool aAllowSlop,
                                               PRInt32 *aX, PRInt32 *aY);
     NS_IMETHOD              Move(PRInt32 aX, PRInt32 aY);
     NS_IMETHOD              PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
                                         nsIWidget *aWidget, PRBool aActivate);
     NS_IMETHOD              SetSizeMode(PRInt32 aMode);
-
+    NS_IMETHOD              HideWindowChrome(PRBool aShouldHide);
     NS_IMETHOD              Resize(PRInt32 aWidth,PRInt32 aHeight, PRBool aRepaint);
     NS_IMETHOD              Resize(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, PRBool aRepaint);
     NS_IMETHOD              GetScreenBounds(nsIntRect &aRect);
     virtual PRBool          OnPaint(nsPaintEvent &event);
     void                    ReportSizeEvent(NSRect *overrideRect = nsnull);
 
     NS_IMETHOD              SetTitle(const nsAString& aTitle);
 
@@ -276,18 +276,19 @@ protected:
   nsresult             StandardCreate(nsIWidget *aParent,
                                       const nsIntRect &aRect,
                                       EVENT_CALLBACK aHandleEventFunction,
                                       nsIDeviceContext *aContext,
                                       nsIAppShell *aAppShell,
                                       nsIToolkit *aToolkit,
                                       nsWidgetInitData *aInitData,
                                       nsNativeWidget aNativeWindow = nsnull);
-  nsresult             CreateNativeWindow(const nsIntRect &aRect,
-                                          nsBorderStyle aBorderStyle);
+  nsresult             CreateNativeWindow(const NSRect &aRect,
+                                          nsBorderStyle aBorderStyle,
+                                          PRBool aRectIsFrameRect);
   nsresult             CreatePopupContentView(const nsIntRect &aRect,
                                               EVENT_CALLBACK aHandleEventFunction,
                                               nsIDeviceContext *aContext,
                                               nsIAppShell *aAppShell,
                                               nsIToolkit *aToolkit);
   void                 DestroyNativeWindow();
 
   nsIWidget*           mParent;         // if we're a popup, this is our parent [WEAK]
--- a/widget/src/cocoa/nsCocoaWindow.mm
+++ b/widget/src/cocoa/nsCocoaWindow.mm
@@ -119,32 +119,41 @@ nsCocoaWindow::nsCocoaWindow()
 , mWindowMadeHere(PR_FALSE)
 , mSheetNeedsShow(PR_FALSE)
 , mModal(PR_FALSE)
 , mNumModalDescendents(0)
 {
 
 }
 
+// Sometimes NSViews are removed from a window or moved to a new window.
+// Since our ChildViews have their own mWindow field instead of always using
+// [view window], we need to notify them when this happens.
+static void SetNativeWindowOnSubviews(NSView *aNativeView, NSWindow *aWin)
+{
+  if (!aNativeView)
+    return;
+  if ([aNativeView respondsToSelector:@selector(setNativeWindow:)])
+    [(NSView<mozView>*)aNativeView setNativeWindow:aWin];
+  NSArray *immediateSubviews = [aNativeView subviews];
+  int count = [immediateSubviews count];
+  for (int i = 0; i < count; ++i)
+    SetNativeWindowOnSubviews((NSView *)[immediateSubviews objectAtIndex:i], aWin);
+}
+
+
 // Under unusual circumstances, an nsCocoaWindow object can be destroyed
 // before the nsChildView objects it contains are destroyed.  But this will
 // invalidate the (weak) mWindow variable in these nsChildView objects
 // before their own destructors have been called.  So we need to null-out
 // this variable in our nsChildView objects as we're destroyed.  This helps
 // resolve bmo bug 479749.
 static void TellNativeViewsGoodbye(NSView *aNativeView)
 {
-  if (!aNativeView)
-    return;
-  if ([aNativeView respondsToSelector:@selector(setNativeWindow:)])
-    [(NSView<mozView>*)aNativeView setNativeWindow:nil];
-  NSArray *immediateSubviews = [aNativeView subviews];
-  int count = [immediateSubviews count];
-  for (int i = 0; i < count; ++i)
-    TellNativeViewsGoodbye((NSView *)[immediateSubviews objectAtIndex:i]);
+  SetNativeWindowOnSubviews(aNativeView, nil);
 }
 
 void nsCocoaWindow::DestroyNativeWindow()
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   // We want to unhook the delegate here because we don't want events
   // sent to it after this object has been destroyed.
@@ -251,29 +260,29 @@ nsresult nsCocoaWindow::StandardCreate(n
                         aToolkit, aInitData);
 
   mParent = aParent;
   SetWindowType(aInitData ? aInitData->mWindowType : eWindowType_toplevel);
   SetBorderStyle(aInitData ? aInitData->mBorderStyle : eBorderStyle_default);
 
   // Create a window if we aren't given one, or if this should be a non-native popup.
   if ((mWindowType == eWindowType_popup) ? !UseNativePopupWindows() : !aNativeWindow) {
-    nsresult rv = CreateNativeWindow(aRect, mBorderStyle);
+    nsresult rv = CreateNativeWindow(nsCocoaUtils::GeckoRectToCocoaRect(aRect),
+                                     mBorderStyle, PR_FALSE);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (mWindowType == eWindowType_popup) {
       rv = CreatePopupContentView(aRect, aHandleEventFunction, aContext, aAppShell, aToolkit);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   } else {
     mWindow = (NSWindow*)aNativeWindow;
+    [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow];
   }
 
-  [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow];
-
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 static unsigned int WindowMaskForBorderStyle(nsBorderStyle aBorderStyle)
 {
   PRBool allOrDefault = (aBorderStyle == eBorderStyle_all ||
@@ -291,18 +300,23 @@ static unsigned int WindowMaskForBorderS
   if (allOrDefault || aBorderStyle & eBorderStyle_close)
     mask |= NSClosableWindowMask;
   if (allOrDefault || aBorderStyle & eBorderStyle_resizeh)
     mask |= NSResizableWindowMask;
 
   return mask;
 }
 
-nsresult nsCocoaWindow::CreateNativeWindow(const nsIntRect &aRect,
-                                           nsBorderStyle aBorderStyle)
+// If aRectIsFrameRect, aRect specifies the frame rect of the new window.
+// Otherwise, aRect.x/y specify the position of the window's frame relative to
+// the bottom of the menubar and aRect.width/height specify the size of the
+// content rect.
+nsresult nsCocoaWindow::CreateNativeWindow(const NSRect &aRect,
+                                           nsBorderStyle aBorderStyle,
+                                           PRBool aRectIsFrameRect)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   // We default to NSBorderlessWindowMask, add features if needed.
   unsigned int features = NSBorderlessWindowMask;
 
   // Configure the window we will create based on the window type.
   switch (mWindowType)
@@ -327,44 +341,49 @@ nsresult nsCocoaWindow::CreateNativeWind
       }
       features |= NSTitledWindowMask;
       break;
     default:
       NS_ERROR("Unhandled window type!");
       return NS_ERROR_FAILURE;
   }
 
-  /* 
-   * We pass a content area rect to initialize the native Cocoa window. The
-   * content rect we give is the same size as the size we're given by gecko.
-   * The origin we're given for non-popup windows is moved down by the height
-   * of the menu bar so that an origin of (0,100) from gecko puts the window
-   * 100 pixels below the top of the available desktop area. We also move the
-   * origin down by the height of a title bar if it exists. This is so the
-   * origin that gecko gives us for the top-left of  the window turns out to
-   * be the top-left of the window we create. This is how it was done in
-   * Carbon. If it ought to be different we'll probably need to look at all
-   * the callers.
-   *
-   * Note: This means that if you put a secondary screen on top of your main
-   * screen and open a window in the top screen, it'll be incorrectly shifted
-   * down by the height of the menu bar. Same thing would happen in Carbon.
-   *
-   * Note: If you pass a rect with 0,0 for an origin, the window ends up in a
-   * weird place for some reason. This stops that without breaking popups.
-   */
-  NSRect rect = nsCocoaUtils::GeckoRectToCocoaRect(aRect);
+  NSRect contentRect;
 
-  // compensate for difference between frame and content area height (e.g. title bar)
-  NSRect newWindowFrame = [NSWindow frameRectForContentRect:rect styleMask:features];
+  if (aRectIsFrameRect) {
+    contentRect = [NSWindow contentRectForFrameRect:aRect styleMask:features];
+  } else {
+    /* 
+     * We pass a content area rect to initialize the native Cocoa window. The
+     * content rect we give is the same size as the size we're given by gecko.
+     * The origin we're given for non-popup windows is moved down by the height
+     * of the menu bar so that an origin of (0,100) from gecko puts the window
+     * 100 pixels below the top of the available desktop area. We also move the
+     * origin down by the height of a title bar if it exists. This is so the
+     * origin that gecko gives us for the top-left of  the window turns out to
+     * be the top-left of the window we create. This is how it was done in
+     * Carbon. If it ought to be different we'll probably need to look at all
+     * the callers.
+     *
+     * Note: This means that if you put a secondary screen on top of your main
+     * screen and open a window in the top screen, it'll be incorrectly shifted
+     * down by the height of the menu bar. Same thing would happen in Carbon.
+     *
+     * Note: If you pass a rect with 0,0 for an origin, the window ends up in a
+     * weird place for some reason. This stops that without breaking popups.
+     */
+    // Compensate for difference between frame and content area height (e.g. title bar).
+    NSRect newWindowFrame = [NSWindow frameRectForContentRect:aRect styleMask:features];
 
-  rect.origin.y -= (newWindowFrame.size.height - rect.size.height);
+    contentRect = aRect;
+    contentRect.origin.y -= (newWindowFrame.size.height - aRect.size.height);
 
-  if (mWindowType != eWindowType_popup)
-    rect.origin.y -= ::GetMBarHeight();
+    if (mWindowType != eWindowType_popup)
+      contentRect.origin.y -= ::GetMBarHeight();
+  }
 
   // NSLog(@"Top-level window being created at Cocoa rect: %f, %f, %f, %f\n",
   //       rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
 
   Class windowClass = [NSWindow class];
   // If we have a titlebar on a top-level window, we want to be able to control the 
   // titlebar color (for unified windows), so use the special ToolbarWindow class. 
   // Note that we need to check the window type because we mark sheets as 
@@ -376,34 +395,42 @@ nsresult nsCocoaWindow::CreateNativeWind
   else if (mWindowType == eWindowType_popup)
     windowClass = [PopupWindow class];
   // If we're a non-popup borderless window we need to use the
   // BorderlessWindow class.
   else if (features == NSBorderlessWindowMask)
     windowClass = [BorderlessWindow class];
 
   // Create the window
-  mWindow = [[windowClass alloc] initWithContentRect:rect styleMask:features 
+  mWindow = [[windowClass alloc] initWithContentRect:contentRect styleMask:features 
                                  backing:NSBackingStoreBuffered defer:YES];
 
+  // Make sure that the content rect we gave has been honored.
+  NSRect wantedFrame = [mWindow frameRectForContentRect:contentRect];
+  if (!NSEqualRects([mWindow frame], wantedFrame)) {
+    // This can happen when the window is not on the primary screen.
+    [mWindow setFrame:wantedFrame display:NO];
+  }
+
   if (mWindowType == eWindowType_invisible) {
     [mWindow setLevel:kCGDesktopWindowLevelKey];
   } else if (mWindowType == eWindowType_popup) {
     [mWindow setLevel:NSPopUpMenuWindowLevel];
     [mWindow setHasShadow:YES];
   }
 
   [mWindow setBackgroundColor:[NSColor whiteColor]];
   [mWindow setContentMinSize:NSMakeSize(60, 60)];
   [mWindow disableCursorRects];
 
   // setup our notification delegate. Note that setDelegate: does NOT retain.
   mDelegate = [[WindowDelegate alloc] initWithGeckoWindow:this];
   [mWindow setDelegate:mDelegate];
 
+  [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow];
   mWindowMadeHere = PR_TRUE;
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 NS_IMETHODIMP nsCocoaWindow::CreatePopupContentView(const nsIntRect &aRect,
@@ -951,16 +978,71 @@ NS_METHOD nsCocoaWindow::SetSizeMode(PRI
       [mWindow zoom:nil];
   }
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
+// This has to preserve the window's frame bounds.
+// This method requires (as does the Windows impl.) that you call Resize shortly
+// after calling HideWindowChrome. See bug 498835 for fixing this.
+NS_IMETHODIMP nsCocoaWindow::HideWindowChrome(PRBool aShouldHide)
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  if (!mWindowMadeHere ||
+      (mWindowType != eWindowType_toplevel && mWindowType != eWindowType_dialog))
+    return NS_ERROR_FAILURE;
+
+  BOOL isVisible = [mWindow isVisible];
+
+  // Remove child windows.
+  NSArray* childWindows = [mWindow childWindows];
+  NSEnumerator* enumerator = [childWindows objectEnumerator];
+  NSWindow* child = nil;
+  while ((child = [enumerator nextObject])) {
+    [mWindow removeChildWindow:child];
+  }
+
+  // Remove the content view.
+  NSView* contentView = [mWindow contentView];
+  [contentView retain];
+  [contentView removeFromSuperviewWithoutNeedingDisplay];
+
+  // Recreate the window with the right border style.
+  NSRect frameRect = [mWindow frame];
+  DestroyNativeWindow();
+  nsresult rv = CreateNativeWindow(frameRect, aShouldHide ? eBorderStyle_none : mBorderStyle, PR_TRUE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Reparent the content view.
+  [mWindow setContentView:contentView];
+  [contentView release];
+  SetNativeWindowOnSubviews(contentView, mWindow);
+
+  // Reparent child windows.
+  enumerator = [childWindows objectEnumerator];
+  while ((child = [enumerator nextObject])) {
+    [mWindow addChildWindow:child ordered:NSWindowAbove];
+  }
+
+  // Show the new window.
+  if (isVisible) {
+    rv = Show(PR_TRUE);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+
 NS_IMETHODIMP nsCocoaWindow::Resize(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, PRBool aRepaint)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   if (!WindowSizeAllowed(aWidth, aHeight))
     return NS_ERROR_FAILURE;
 
   nsIntRect windowBounds(nsCocoaUtils::CocoaRectToGeckoRect([mWindow frame]));