Bug 1699506 - Part 1 - Add a NSTitlebarAccessoryViewController to ToolbarWindow to track when the titlebar is shown in fullscreen. r=mac-reviewers,mstange
authorharry <htwyford@mozilla.com>
Thu, 18 Mar 2021 20:20:39 +0000
changeset 571858 f4dfcad8b8817e955a3c781820474768492557f4
parent 571857 1f963fd58e0ae8062a8233f46ca9ce41819c2937
child 571859 8ec37432c5b12fa65a4bf68272461ba9591e1126
push id38300
push userbtara@mozilla.com
push dateFri, 19 Mar 2021 09:53:39 +0000
treeherdermozilla-central@092ee6b0c9f2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmac-reviewers, mstange
bugs1699506
milestone88.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1699506 - Part 1 - Add a NSTitlebarAccessoryViewController to ToolbarWindow to track when the titlebar is shown in fullscreen. r=mac-reviewers,mstange Differential Revision: https://phabricator.services.mozilla.com/D106846
dom/base/nsGlobalWindowOuter.cpp
dom/base/nsGlobalWindowOuter.h
dom/base/nsPIDOMWindow.h
widget/cocoa/nsCocoaWindow.h
widget/cocoa/nsCocoaWindow.mm
widget/nsIWidgetListener.cpp
widget/nsIWidgetListener.h
xpfe/appshell/AppWindow.cpp
xpfe/appshell/AppWindow.h
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -11,16 +11,17 @@
 #include <algorithm>
 
 #include "mozilla/MemoryReporting.h"
 
 // Local Includes
 #include "Navigator.h"
 #include "mozilla/Encoding.h"
 #include "nsContentSecurityManager.h"
+#include "nsGlobalWindowOuter.h"
 #include "nsScreen.h"
 #include "nsHistory.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsIDOMStorageManager.h"
 #include "nsISecureBrowserUI.h"
 #include "nsIWebProgressListener.h"
 #include "mozilla/AntiTrackingUtils.h"
 #include "mozilla/ContentBlocking.h"
@@ -4711,16 +4712,49 @@ void nsGlobalWindowOuter::FinishFullscre
       if (nsRefreshDriver* rd = presShell->GetRefreshDriver()) {
         rd->Thaw();
       }
       mChromeFields.mFullscreenPresShell = nullptr;
     }
   }
 }
 
+/* virtual */
+void nsGlobalWindowOuter::MacFullscreenMenubarOverlapChanged(
+    mozilla::DesktopCoord aOverlapAmount) {
+  ErrorResult res;
+  RefPtr<Event> domEvent =
+      mDoc->CreateEvent(u"CustomEvent"_ns, CallerType::System, res);
+  if (res.Failed()) {
+    return;
+  }
+
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JSContext* cx = jsapi.cx();
+  JSAutoRealm ar(cx, GetWrapperPreserveColor());
+
+  JS::Rooted<JS::Value> detailValue(cx);
+  if (!ToJSValue(cx, aOverlapAmount, &detailValue)) {
+    return;
+  }
+
+  CustomEvent* customEvent = static_cast<CustomEvent*>(domEvent.get());
+  customEvent->InitCustomEvent(cx, u"MacFullscreenMenubarRevealUpdate"_ns,
+                               /* aCanBubble = */ true,
+                               /* aCancelable = */ true, detailValue);
+  domEvent->SetTrusted(true);
+  domEvent->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+
+  nsCOMPtr<EventTarget> target = this;
+  domEvent->SetTarget(target);
+
+  target->DispatchEvent(*domEvent, CallerType::System, IgnoreErrors());
+}
+
 bool nsGlobalWindowOuter::Fullscreen() const {
   NS_ENSURE_TRUE(mDocShell, mFullscreen);
 
   // Get the fullscreen value of the root window, to always have the value
   // accurate, even when called from content.
   nsCOMPtr<nsIDocShellTreeItem> rootItem;
   mDocShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
   if (rootItem == mDocShell) {
--- a/dom/base/nsGlobalWindowOuter.h
+++ b/dom/base/nsGlobalWindowOuter.h
@@ -344,16 +344,18 @@ class nsGlobalWindowOuter final : public
   friend class FullscreenTransitionTask;
 
   // Outer windows only.
   nsresult SetFullscreenInternal(FullscreenReason aReason,
                                  bool aIsFullscreen) final;
   void FullscreenWillChange(bool aIsFullscreen) final;
   void FinishFullscreenChange(bool aIsFullscreen) final;
   void ForceFullScreenInWidget() final;
+  void MacFullscreenMenubarOverlapChanged(
+      mozilla::DesktopCoord aOverlapAmount) final;
   bool SetWidgetFullscreen(FullscreenReason aReason, bool aIsFullscreen,
                            nsIWidget* aWidget, nsIScreen* aScreen);
   bool Fullscreen() const;
 
   // nsIInterfaceRequestor
   NS_DECL_NSIINTERFACEREQUESTOR
 
   mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> IndexedGetterOuter(
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -7,16 +7,17 @@
 #ifndef nsPIDOMWindow_h__
 #define nsPIDOMWindow_h__
 
 #include "nsIDOMWindow.h"
 #include "mozIDOMWindow.h"
 
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
+#include "Units.h"
 #include "mozilla/dom/EventTarget.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/TaskCategory.h"
 #include "js/TypeDecls.h"
 #include "nsRefPtrHashtable.h"
 #include "nsILoadInfo.h"
 
@@ -928,16 +929,19 @@ class nsPIDOMWindowOuter : public mozIDO
    * finishes its change to or from fullscreen.
    *
    * @param aIsFullscreen indicates whether the widget is in fullscreen.
    */
   virtual void FinishFullscreenChange(bool aIsFullscreen) = 0;
 
   virtual void ForceFullScreenInWidget() = 0;
 
+  virtual void MacFullscreenMenubarOverlapChanged(
+      mozilla::DesktopCoord aOverlapAmount) = 0;
+
   // XXX: These focus methods all forward to the inner, could we change
   // consumers to call these on the inner directly?
 
   /*
    * Get and set the currently focused element within the document. If
    * aNeedsFocus is true, then set mNeedsFocus to true to indicate that a
    * document focus event is needed.
    *
--- a/widget/cocoa/nsCocoaWindow.h
+++ b/widget/cocoa/nsCocoaWindow.h
@@ -169,26 +169,39 @@ typedef struct _nsCocoaWindowList {
 - (bool)toplevelActiveState;
 - (void)sendToplevelActivateEvents;
 - (void)sendToplevelDeactivateEvents;
 @end
 
 @interface MOZTitlebarView : NSVisualEffectView
 @end
 
+@interface FullscreenTitlebarTracker : NSTitlebarAccessoryViewController
+- (FullscreenTitlebarTracker*)init;
+@end
+
 // NSWindow subclass for handling windows with toolbars.
 @interface ToolbarWindow : BaseWindow {
   // This window's titlebar view, if present.
   // Will be nil if the window has neither a titlebar nor a unified toolbar.
   // This view is a subview of the window's content view and gets created and
   // destroyed by updateTitlebarView.
   MOZTitlebarView* mTitlebarView;  // [STRONG]
+  // mFullscreenTitlebarTracker attaches an invisible rectangle to the system
+  // title bar. This allows us to detect when the title bar is showing in
+  // fullscreen.
+  FullscreenTitlebarTracker* mFullscreenTitlebarTracker;
 
   CGFloat mUnifiedToolbarHeight;
   CGFloat mSheetAttachmentPosition;
+  CGFloat mMenuBarHeight;
+  /* Store the height of the titlebar when this window is initialized. The
+     titlebarHeight getter returns 0 when in fullscreen, which is not useful in
+     some cases. */
+  CGFloat mInitialTitlebarHeight;
   NSRect mWindowButtonsRect;
 }
 - (void)setUnifiedToolbarHeight:(CGFloat)aHeight;
 - (CGFloat)unifiedToolbarHeight;
 - (CGFloat)titlebarHeight;
 - (NSRect)titlebarRect;
 - (void)setTitlebarNeedsDisplay;
 - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState;
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -3361,16 +3361,25 @@ static const NSString* kStateWantsTitleD
     } else if (nsCocoaUtils::ShouldMinimizeOnTitlebarDoubleClick()) {
       [[self window] performMiniaturize:nil];
     }
   }
 }
 
 @end
 
+@implementation FullscreenTitlebarTracker
+- (FullscreenTitlebarTracker*)init {
+  [super init];
+  self.view = [[[NSView alloc] initWithFrame:NSZeroRect] autorelease];
+  self.hidden = YES;
+  return self;
+}
+@end
+
 // This class allows us to exercise control over the window's title bar. It is
 // used for all windows with titlebars.
 //
 // ToolbarWindow supports two modes:
 //  - drawsContentsIntoWindowFrame mode: In this mode, the Gecko ChildView is
 //    sized to cover the entire window frame and manages titlebar drawing.
 //  - separate titlebar mode, with support for unified toolbars: In this mode,
 //    the Gecko ChildView does not extend into the titlebar. However, this
@@ -3431,27 +3440,93 @@ static const NSString* kStateWantsTitleD
   if ((self = [super initWithContentRect:contentRect
                                styleMask:aStyle
                                  backing:aBufferingType
                                    defer:aFlag])) {
     mTitlebarView = nil;
     mUnifiedToolbarHeight = 22.0f;
     mSheetAttachmentPosition = aChildViewRect.size.height;
     mWindowButtonsRect = NSZeroRect;
+    mInitialTitlebarHeight = [self titlebarHeight];
 
     [self setTitlebarAppearsTransparent:YES];
     [self updateTitlebarView];
+
+    mFullscreenTitlebarTracker = [[FullscreenTitlebarTracker alloc] init];
+    // revealAmount is an undocumented property of
+    // NSTitlebarAccessoryViewController that updates whenever the menubar
+    // slides down in fullscreen mode.
+    [mFullscreenTitlebarTracker addObserver:self
+                                 forKeyPath:@"revealAmount"
+                                    options:NSKeyValueObservingOptionNew
+                                    context:nil];
+    [(NSWindow*)self addTitlebarAccessoryViewController:mFullscreenTitlebarTracker];
   }
   return self;
 
   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
 }
 
+- (void)observeValueForKeyPath:(NSString*)keyPath
+                      ofObject:(id)object
+                        change:(NSDictionary<NSKeyValueChangeKey, id>*)change
+                       context:(void*)context {
+  if ([keyPath isEqualToString:@"revealAmount"]) {
+    NSNumber* revealAmount = (change[NSKeyValueChangeNewKey]);
+    [self updateTitlebarShownAmount:[revealAmount doubleValue]];
+  }
+}
+
+- (void)updateTitlebarShownAmount:(CGFloat)aShownAmount {
+  NSInteger styleMask = [self styleMask];
+  if (!(styleMask & NSWindowStyleMaskFullScreen)) {
+    // We are not interested in the size of the titlebar unless we are in
+    // fullscreen.
+    return;
+  }
+
+  // [NSApp mainMenu] menuBarHeight] returns one of two values: the full height
+  // if the menubar is shown or is in the process of being shown, and 0
+  // otherwise. Since we are multiplying the menubar height by aShownAmount, we
+  // always want the full height.
+  if ([[NSApp mainMenu] menuBarHeight] > 0) {
+    mMenuBarHeight = [[NSApp mainMenu] menuBarHeight];
+  }
+
+  if ([[self delegate] isKindOfClass:[WindowDelegate class]]) {
+    WindowDelegate* windowDelegate = (WindowDelegate*)[self delegate];
+    nsCocoaWindow* geckoWindow = [windowDelegate geckoWidget];
+    if (!geckoWindow) {
+      return;
+    }
+
+    nsIWidgetListener* listener = geckoWindow->GetWidgetListener();
+    if (listener) {
+      // Use the titlebar height cached in our frame rather than
+      // [ToolbarWindow titlebarHeight]. titlebarHeight returns 0 when we're in
+      // fullscreen.
+      CGFloat shiftByPixels = (mInitialTitlebarHeight + mMenuBarHeight) * aShownAmount;
+      // Use mozilla::DesktopToLayoutDeviceScale rather than the
+      // DesktopToLayoutDeviceScale in nsCocoaWindow. The latter accounts for
+      // screen DPI. We don't want that because the revealAmount property
+      // already accounts for it, so we'd be compounding DPI scales > 1.
+      mozilla::DesktopCoord coord =
+          LayoutDeviceCoord(shiftByPixels) / mozilla::DesktopToLayoutDeviceScale();
+
+      listener->MacFullscreenMenubarOverlapChanged(coord);
+    }
+  }
+}
+
 - (void)dealloc {
   [mTitlebarView release];
+  [mFullscreenTitlebarTracker removeObserver:self forKeyPath:@"revealAmount"];
+  [mFullscreenTitlebarTracker removeFromParentViewController];
+  [mFullscreenTitlebarTracker release];
+
   [super dealloc];
 }
 
 - (NSArray<NSView*>*)contentViewContents {
   NSMutableArray<NSView*>* contents = [[[self contentView] subviews] mutableCopy];
   if (mTitlebarView) {
     // Do not include the titlebar gradient view in the returned array.
     [contents removeObject:mTitlebarView];
--- a/widget/nsIWidgetListener.cpp
+++ b/widget/nsIWidgetListener.cpp
@@ -43,16 +43,19 @@ void nsIWidgetListener::DynamicToolbarMa
 }
 void nsIWidgetListener::DynamicToolbarOffsetChanged(ScreenIntCoord aOffset) {}
 #endif
 
 void nsIWidgetListener::FullscreenWillChange(bool aInFullscreen) {}
 
 void nsIWidgetListener::FullscreenChanged(bool aInFullscreen) {}
 
+void nsIWidgetListener::MacFullscreenMenubarOverlapChanged(
+    mozilla::DesktopCoord aOverlapAmount) {}
+
 bool nsIWidgetListener::ZLevelChanged(bool aImmediate, nsWindowZ* aPlacement,
                                       nsIWidget* aRequestBelow,
                                       nsIWidget** aActualBelow) {
   return false;
 }
 
 void nsIWidgetListener::OcclusionStateChanged(bool aIsFullyOccluded) {}
 
--- a/widget/nsIWidgetListener.h
+++ b/widget/nsIWidgetListener.h
@@ -107,16 +107,22 @@ class nsIWidgetListener {
   virtual void FullscreenWillChange(bool aInFullscreen);
 
   /**
    * Called when the window entered or left the fullscreen state.
    */
   virtual void FullscreenChanged(bool aInFullscreen);
 
   /**
+   * Called when the macOS titlebar is shown while in fullscreen.
+   */
+  virtual void MacFullscreenMenubarOverlapChanged(
+      mozilla::DesktopCoord aOverlapAmount);
+
+  /**
    * Called when the occlusion state is changed.
    */
   virtual void OcclusionStateChanged(bool aIsFullyOccluded);
 
   /**
    * Called when the window is activated and focused.
    */
   virtual void WindowActivated();
--- a/xpfe/appshell/AppWindow.cpp
+++ b/xpfe/appshell/AppWindow.cpp
@@ -2906,16 +2906,25 @@ void AppWindow::FinishFullscreenChange(b
   mFullscreenChangeState = FullscreenChangeState::NotChanging;
   if (mDocShell) {
     if (nsCOMPtr<nsPIDOMWindowOuter> ourWindow = mDocShell->GetWindow()) {
       ourWindow->FinishFullscreenChange(aInFullscreen);
     }
   }
 }
 
+void AppWindow::MacFullscreenMenubarOverlapChanged(
+    mozilla::DesktopCoord aOverlapAmount) {
+  if (mDocShell) {
+    if (nsCOMPtr<nsPIDOMWindowOuter> ourWindow = mDocShell->GetWindow()) {
+      ourWindow->MacFullscreenMenubarOverlapChanged(aOverlapAmount);
+    }
+  }
+}
+
 void AppWindow::OcclusionStateChanged(bool aIsFullyOccluded) {
   nsCOMPtr<nsPIDOMWindowOuter> ourWindow =
       mDocShell ? mDocShell->GetWindow() : nullptr;
   if (ourWindow) {
     // And always fire a user-defined occlusionstatechange event on the window
     ourWindow->DispatchCustomEvent(u"occlusionstatechange"_ns,
                                    ChromeOnlyDispatch::eYes);
   }
@@ -3292,16 +3301,22 @@ void AppWindow::WidgetListenerDelegate::
   holder->FullscreenWillChange(aInFullscreen);
 }
 
 void AppWindow::WidgetListenerDelegate::FullscreenChanged(bool aInFullscreen) {
   RefPtr<AppWindow> holder = mAppWindow;
   holder->FullscreenChanged(aInFullscreen);
 }
 
+void AppWindow::WidgetListenerDelegate::MacFullscreenMenubarOverlapChanged(
+    DesktopCoord aOverlapAmount) {
+  RefPtr<AppWindow> holder = mAppWindow;
+  return holder->MacFullscreenMenubarOverlapChanged(aOverlapAmount);
+}
+
 void AppWindow::WidgetListenerDelegate::OcclusionStateChanged(
     bool aIsFullyOccluded) {
   RefPtr<AppWindow> holder = mAppWindow;
   holder->OcclusionStateChanged(aIsFullyOccluded);
 }
 
 void AppWindow::WidgetListenerDelegate::OSToolbarButtonPressed() {
   RefPtr<AppWindow> holder = mAppWindow;
--- a/xpfe/appshell/AppWindow.h
+++ b/xpfe/appshell/AppWindow.h
@@ -101,16 +101,19 @@ class AppWindow final : public nsIBaseWi
     virtual void SizeModeChanged(nsSizeMode sizeMode) override;
     MOZ_CAN_RUN_SCRIPT_BOUNDARY
     virtual void UIResolutionChanged() override;
     MOZ_CAN_RUN_SCRIPT_BOUNDARY
     virtual void FullscreenWillChange(bool aInFullscreen) override;
     MOZ_CAN_RUN_SCRIPT_BOUNDARY
     virtual void FullscreenChanged(bool aInFullscreen) override;
     MOZ_CAN_RUN_SCRIPT_BOUNDARY
+    virtual void MacFullscreenMenubarOverlapChanged(
+        mozilla::DesktopCoord aOverlapAmount) override;
+    MOZ_CAN_RUN_SCRIPT_BOUNDARY
     virtual void OcclusionStateChanged(bool aIsFullyOccluded) override;
     MOZ_CAN_RUN_SCRIPT_BOUNDARY
     virtual void OSToolbarButtonPressed() override;
     MOZ_CAN_RUN_SCRIPT_BOUNDARY
     virtual bool ZLevelChanged(bool aImmediate, nsWindowZ* aPlacement,
                                nsIWidget* aRequestBelow,
                                nsIWidget** aActualBelow) override;
     MOZ_CAN_RUN_SCRIPT_BOUNDARY
@@ -156,16 +159,18 @@ class AppWindow final : public nsIBaseWi
   bool WindowMoved(nsIWidget* aWidget, int32_t aX, int32_t aY);
   MOZ_CAN_RUN_SCRIPT
   bool WindowResized(nsIWidget* aWidget, int32_t aWidth, int32_t aHeight);
   MOZ_CAN_RUN_SCRIPT bool RequestWindowClose(nsIWidget* aWidget);
   MOZ_CAN_RUN_SCRIPT void SizeModeChanged(nsSizeMode aSizeMode);
   MOZ_CAN_RUN_SCRIPT void UIResolutionChanged();
   MOZ_CAN_RUN_SCRIPT void FullscreenWillChange(bool aInFullscreen);
   MOZ_CAN_RUN_SCRIPT void FullscreenChanged(bool aInFullscreen);
+  MOZ_CAN_RUN_SCRIPT void MacFullscreenMenubarOverlapChanged(
+      mozilla::DesktopCoord aOverlapAmount);
   MOZ_CAN_RUN_SCRIPT void OcclusionStateChanged(bool aIsFullyOccluded);
   MOZ_CAN_RUN_SCRIPT void OSToolbarButtonPressed();
   MOZ_CAN_RUN_SCRIPT
   bool ZLevelChanged(bool aImmediate, nsWindowZ* aPlacement,
                      nsIWidget* aRequestBelow, nsIWidget** aActualBelow);
   MOZ_CAN_RUN_SCRIPT void WindowActivated();
   MOZ_CAN_RUN_SCRIPT void WindowDeactivated();