gecko-dev/widget/cocoa/nsCocoaWindow.h
Brad Werth 44c31504b0 Bug 1880582: Make nsCocoaWindow hold a death grip on its native window until its destructor. r=mstange
This is a simplified version of a fix authored by Steven Michaud. This
creates a death grip in DestroyNativeWindow() and holds it until the
nsCocoaWindow is destroyed. This seems to satisfy the various run loops
in macOS which might invoke DestroyNativeWindow() without holding a
reference to the window itself.

Differential Revision: https://phabricator.services.mozilla.com/D226169
2024-10-23 16:32:15 +00:00

483 lines
18 KiB
Objective-C

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsCocoaWindow_h_
#define nsCocoaWindow_h_
#undef DARWIN
#import <Cocoa/Cocoa.h>
#include "mozilla/RefPtr.h"
#include "nsBaseWidget.h"
#include "nsCocoaUtils.h"
#include "nsTouchBar.h"
#include <dlfcn.h>
#include <queue>
class nsCocoaWindow;
class nsChildView;
class nsMenuBarX;
@class ChildView;
namespace mozilla {
enum class NativeKeyBindingsType : uint8_t;
} // namespace mozilla
// NSWindow subclass that is the base class for all of our own window classes.
// Among other things, this class handles the storage of those settings that
// need to be persisted across window destruction and reconstruction, i.e. when
// switching to and from fullscreen mode.
// We don't save shadow, transparency mode or background color because it's not
// worth the hassle - Gecko will reset them anyway as soon as the window is
// resized.
@interface BaseWindow : NSWindow {
// Data Storage
NSMutableDictionary* mState;
BOOL mDrawsIntoWindowFrame;
// Invalidation disabling
BOOL mDisabledNeedsDisplay;
NSTrackingArea* mTrackingArea;
NSView* mViewWithTrackingArea;
NSRect mDirtyRect;
BOOL mBeingShown;
BOOL mDrawTitle;
BOOL mIsAnimationSuppressed;
nsTouchBar* mTouchBar;
}
- (void)importState:(NSDictionary*)aState;
- (NSMutableDictionary*)exportState;
- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState;
- (BOOL)drawsContentsIntoWindowFrame;
// These two methods are like contentRectForFrameRect and
// frameRectForContentRect, but they deal with the rect of the window's "main
// ChildView" instead of the rect of the window's content view. The two are
// sometimes sized differently: The window's content view always covers the
// entire window, whereas the ChildView only covers the full window when
// drawsContentsIntoWindowFrame is YES. When drawsContentsIntoWindowFrame is NO,
// there's a titlebar-sized gap above the ChildView within the content view.
- (NSRect)childViewRectForFrameRect:(NSRect)aFrameRect;
- (NSRect)frameRectForChildViewRect:(NSRect)aChildViewRect;
- (void)mouseEntered:(NSEvent*)aEvent;
- (void)mouseExited:(NSEvent*)aEvent;
- (void)mouseMoved:(NSEvent*)aEvent;
- (NSView*)trackingAreaView;
- (void)createTrackingArea;
- (void)removeTrackingArea;
- (void)setBeingShown:(BOOL)aValue;
- (BOOL)isBeingShown;
- (BOOL)isVisibleOrBeingShown;
- (void)setIsAnimationSuppressed:(BOOL)aValue;
- (BOOL)isAnimationSuppressed;
// Returns an autoreleased NSArray containing the NSViews that we consider the
// "contents" of this window. All views in the returned array are subviews of
// this window's content view. However, the array may not include all of the
// content view's subviews; concretely, the ToolbarWindow implementation will
// exclude its MOZTitlebarView from the array that is returned here.
// In the vast majority of cases, the array will only have a single element:
// this window's mainChildView.
- (NSArray<NSView*>*)contentViewContents;
- (ChildView*)mainChildView;
- (void)setWantsTitleDrawn:(BOOL)aDrawTitle;
- (BOOL)wantsTitleDrawn;
- (void)disableSetNeedsDisplay;
- (void)enableSetNeedsDisplay;
- (NSRect)getAndResetNativeDirtyRect;
- (void)setEffectViewWrapperForStyle:(mozilla::WindowShadow)aStyle;
@property(nonatomic) mozilla::WindowShadow shadowStyle;
- (void)releaseJSObjects;
@end
@interface NSWindow (Undocumented)
- (NSDictionary*)shadowParameters;
// Present in the same form on OS X since at least OS X 10.5.
- (NSRect)contentRectForFrameRect:(NSRect)windowFrame
styleMask:(NSUInteger)windowStyle;
- (NSRect)frameRectForContentRect:(NSRect)windowContentRect
styleMask:(NSUInteger)windowStyle;
// Present since at least OS X 10.5. The OS calls this method on NSWindow
// (and its subclasses) to find out which NSFrameView subclass to instantiate
// to create its "frame view".
+ (Class)frameViewClassForStyleMask:(NSUInteger)styleMask;
@end
@interface PopupWindow : BaseWindow {
@private
BOOL mIsContextMenu;
}
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)styleMask
backing:(NSBackingStoreType)bufferingType
defer:(BOOL)deferCreation;
- (BOOL)isContextMenu;
- (void)setIsContextMenu:(BOOL)flag;
- (BOOL)canBecomeMainWindow;
@end
@interface BorderlessWindow : BaseWindow {
}
- (BOOL)canBecomeKeyWindow;
- (BOOL)canBecomeMainWindow;
@end
@interface WindowDelegate : NSObject <NSWindowDelegate> {
nsCocoaWindow* mGeckoWindow; // [WEAK] (we are owned by the window)
// Used to avoid duplication when we send NS_ACTIVATE and
// NS_DEACTIVATE to Gecko for toplevel widgets. Starts out
// false.
bool mToplevelActiveState;
BOOL mHasEverBeenZoomed;
}
+ (void)paintMenubarForWindow:(NSWindow*)aWindow;
- (id)initWithGeckoWindow:(nsCocoaWindow*)geckoWind;
- (void)windowDidResize:(NSNotification*)aNotification;
- (nsCocoaWindow*)geckoWidget;
- (bool)toplevelActiveState;
- (void)sendToplevelActivateEvents;
- (void)sendToplevelDeactivateEvents;
@end
@interface FullscreenTitlebarTracker : NSTitlebarAccessoryViewController
- (FullscreenTitlebarTracker*)init;
@end
// NSWindow subclass for handling windows with toolbars.
@interface ToolbarWindow : BaseWindow {
// 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 mMenuBarHeight;
NSRect mWindowButtonsRect;
}
- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState;
- (void)placeWindowButtons:(NSRect)aRect;
- (NSRect)windowButtonsRect;
- (void)windowMainStateChanged;
@end
class nsCocoaWindow final : public nsBaseWidget {
private:
friend class nsChildView;
typedef nsBaseWidget Inherited;
public:
nsCocoaWindow();
[[nodiscard]] nsresult Create(nsIWidget* aParent, const DesktopIntRect& aRect,
InitData* = nullptr) override;
[[nodiscard]] nsresult Create(nsIWidget* aParent,
const LayoutDeviceIntRect& aRect,
InitData* = nullptr) override;
void Destroy() override;
void Show(bool aState) override;
bool NeedsRecreateToReshow() override;
void Enable(bool aState) override;
bool IsEnabled() const override;
void SetModal(bool aState) override;
bool IsRunningAppModal() override;
bool IsVisible() const override;
void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override;
LayoutDeviceIntPoint WidgetToScreenOffset() override;
LayoutDeviceIntPoint GetClientOffset() override;
LayoutDeviceIntMargin ClientToWindowMargin() override;
void* GetNativeData(uint32_t aDataType) override;
void ConstrainPosition(DesktopIntPoint&) override;
void SetSizeConstraints(const SizeConstraints& aConstraints) override;
void Move(double aX, double aY) override;
nsSizeMode SizeMode() override { return mSizeMode; }
void SetSizeMode(nsSizeMode aMode) override;
void GetWorkspaceID(nsAString& workspaceID) override;
void MoveToWorkspace(const nsAString& workspaceID) override;
void SuppressAnimation(bool aSuppress) override;
void HideWindowChrome(bool aShouldHide) override;
bool PrepareForFullscreenTransition(nsISupports** aData) override;
void PerformFullscreenTransition(FullscreenTransitionStage aStage,
uint16_t aDuration, nsISupports* aData,
nsIRunnable* aCallback) override;
void CleanupFullscreenTransition() override;
nsresult MakeFullScreen(bool aFullScreen) final;
nsresult MakeFullScreenWithNativeTransition(bool aFullScreen) final;
NSAnimation* FullscreenTransitionAnimation() const {
return mFullscreenTransitionAnimation;
}
void ReleaseFullscreenTransitionAnimation() {
MOZ_ASSERT(mFullscreenTransitionAnimation,
"Should only be called when there is animation");
[mFullscreenTransitionAnimation release];
mFullscreenTransitionAnimation = nil;
}
void Resize(double aWidth, double aHeight, bool aRepaint) override;
void Resize(double aX, double aY, double aWidth, double aHeight,
bool aRepaint) override;
NSRect GetClientCocoaRect();
LayoutDeviceIntRect GetClientBounds() override;
LayoutDeviceIntRect GetScreenBounds() override;
void ReportMoveEvent();
void ReportSizeEvent();
void SetCursor(const Cursor&) override;
CGFloat BackingScaleFactor();
void BackingScaleFactorChanged();
double GetDefaultScaleInternal() override;
int32_t RoundsWidgetCoordinatesTo() override;
mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() final {
return mozilla::DesktopToLayoutDeviceScale(BackingScaleFactor());
}
nsresult SetTitle(const nsAString& aTitle) override;
void Invalidate(const LayoutDeviceIntRect& aRect) override;
WindowRenderer* GetWindowRenderer() override;
nsresult DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
nsEventStatus& aStatus) override;
void CaptureRollupEvents(bool aDoCapture) override;
[[nodiscard]] nsresult GetAttention(int32_t aCycleCount) override;
bool HasPendingInputEvent() override;
TransparencyMode GetTransparencyMode() override;
void SetTransparencyMode(TransparencyMode aMode) override;
void SetWindowShadowStyle(mozilla::WindowShadow aStyle) override;
void SetWindowOpacity(float aOpacity) override;
void SetWindowTransform(const mozilla::gfx::Matrix& aTransform) override;
void SetInputRegion(const InputRegion&) override;
void SetColorScheme(const mozilla::Maybe<mozilla::ColorScheme>&) override;
void SetShowsToolbarButton(bool aShow) override;
bool GetSupportsNativeFullscreen();
void SetSupportsNativeFullscreen(bool aShow) override;
void SetWindowAnimationType(WindowAnimationType aType) override;
void SetDrawsTitle(bool aDrawTitle) override;
nsresult SetNonClientMargins(const LayoutDeviceIntMargin&) override;
void SetDrawsInTitlebar(bool aState);
void UpdateThemeGeometries(
const nsTArray<ThemeGeometry>& aThemeGeometries) override;
nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
NativeMouseMessage aNativeMessage,
mozilla::MouseButton aButton,
nsIWidget::Modifiers aModifierFlags,
nsIObserver* aObserver) override;
nsresult SynthesizeNativeMouseScrollEvent(
LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX,
double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
uint32_t aAdditionalFlags, nsIObserver* aObserver) override;
void LockAspectRatio(bool aShouldLock) override;
void DispatchSizeModeEvent();
void DispatchOcclusionEvent();
// be notified that a some form of drag event needs to go into Gecko
bool DragEvent(unsigned int aMessage, mozilla::gfx::Point aMouseGlobal,
UInt16 aKeyModifiers);
bool HasModalDescendants() const { return mNumModalDescendants > 0; }
bool IsModal() const { return mModal; }
NSWindow* GetCocoaWindow() { return mWindow; }
void SetMenuBar(RefPtr<nsMenuBarX>&& aMenuBar);
nsMenuBarX* GetMenuBar();
void SetInputContext(const InputContext& aContext,
const InputContextAction& aAction) override;
InputContext GetInputContext() override { return mInputContext; }
MOZ_CAN_RUN_SCRIPT bool GetEditCommands(
mozilla::NativeKeyBindingsType aType,
const mozilla::WidgetKeyboardEvent& aEvent,
nsTArray<mozilla::CommandInt>& aCommands) override;
void SetPopupWindowLevel();
bool InFullScreenMode() const { return mInFullScreenMode; }
void PauseOrResumeCompositor(bool aPause) override;
bool AsyncPanZoomEnabled() const override;
bool StartAsyncAutoscroll(const ScreenPoint& aAnchorLocation,
const ScrollableLayerGuid& aGuid) override;
void StopAsyncAutoscroll(const ScrollableLayerGuid& aGuid) override;
// Class method versions of NSWindow/Delegate callbacks which need to
// access object state.
void CocoaWindowWillEnterFullscreen(bool aFullscreen);
void CocoaWindowDidEnterFullscreen(bool aFullscreen);
void CocoaWindowDidResize();
void CocoaSendToplevelActivateEvents();
void CocoaSendToplevelDeactivateEvents();
enum class TransitionType {
Windowed,
Fullscreen,
EmulatedFullscreen,
Miniaturize,
Deminiaturize,
Zoom,
};
void FinishCurrentTransitionIfMatching(const TransitionType& aTransition);
// Called when something has happened that might cause us to update our
// fullscreen state. Returns true if we updated state. We'll call this
// on window resize, and we'll call it when we enter or exit fullscreen,
// since fullscreen to-and-from zoomed windows won't necessarily trigger
// a resize.
bool HandleUpdateFullscreenOnResize();
protected:
virtual ~nsCocoaWindow();
nsresult CreateNativeWindow(const NSRect& aRect, BorderStyle aBorderStyle,
bool aRectIsFrameRect, bool aIsPrivateBrowsing);
nsresult CreatePopupContentView(const LayoutDeviceIntRect& aRect, InitData*);
void DestroyNativeWindow();
void UpdateBounds();
int32_t GetWorkspaceID();
void DoResize(double aX, double aY, double aWidth, double aHeight,
bool aRepaint, bool aConstrainToCurrentScreen);
void UpdateFullscreenState(bool aFullScreen, bool aNativeMode);
nsresult DoMakeFullScreen(bool aFullScreen, bool aUseSystemTransition);
already_AddRefed<nsIWidget> AllocateChildPopupWidget() override {
return nsIWidget::CreateTopLevelWindow();
}
BaseWindow* mWindow; // our cocoa window [STRONG]
BaseWindow* mClosedRetainedWindow; // a second strong reference to our
// window upon closing it, held through our destructor. This is useful
// to ensure that macOS run loops which reference the window will still
// have something to point to even if they don't use proper retain and
// release patterns.
WindowDelegate*
mDelegate; // our delegate for processing window msgs [STRONG]
RefPtr<nsMenuBarX> mMenuBar;
nsChildView*
mPopupContentView; // if this is a popup, this is its content widget
// if this is a toplevel window, and there is any ongoing fullscreen
// transition, it is the animation object.
NSAnimation* mFullscreenTransitionAnimation;
mozilla::WindowShadow mShadowStyle;
CGFloat mBackingScaleFactor;
CGFloat mAspectRatio;
WindowAnimationType mAnimationType;
bool mWindowMadeHere; // true if we created the window, false for embedding
nsSizeMode mSizeMode;
bool mInFullScreenMode;
// Whether we are currently using native fullscreen. It could be false because
// we are in the emulated fullscreen where we do not use the native
// fullscreen.
bool mInNativeFullScreenMode;
mozilla::Maybe<TransitionType> mTransitionCurrent;
std::queue<TransitionType> mTransitionsPending;
// A runnable we might assign to run ProcessTransitions at a later event loop.
// Cancelable so we can cancel it in CancelAllTransitions(), if needed.
RefPtr<mozilla::CancelableRunnable> mProcessTransitionsPending;
// Sometimes we add a transition that wasn't requested by a caller. We do this
// to manage transitions between states that otherwise would be rejected by
// Cocoa. When we do this, it's useful to know when we are handling an added
// transition because we don't want to send size mode events when they
// execute.
bool mIsTransitionCurrentAdded = false;
// Whether we are treating the next resize as the start of a fullscreen
// transition. If we are, which direction are we going: Fullscreen or
// Windowed.
mozilla::Maybe<TransitionType> mUpdateFullscreenOnResize;
bool IsInTransition() { return mTransitionCurrent.isSome(); }
void QueueTransition(const TransitionType& aTransition);
void ProcessTransitions();
// Call this to stop all transition processing, which is useful during
// window closing and shutdown.
void CancelAllTransitions();
bool mInProcessTransitions = false;
// While running an emulated fullscreen transition, we want to suppress
// sending size mode events due to window resizing. We fix it up at the end
// when the transition is complete.
bool mSuppressSizeModeEvents = false;
// Ignore occlusion events caused by displaying the temporary fullscreen
// window during the fullscreen transition animation because only focused
// contexts are permitted to enter DOM fullscreen.
int mIgnoreOcclusionCount;
// Set to true when a native fullscreen transition is initiated -- either to
// or from fullscreen -- and set to false when it is complete. During this
// period, we presume the window is visible, which prevents us from sending
// unnecessary OcclusionStateChanged events.
bool mHasStartedNativeFullscreen;
bool mModal = false;
bool mIsAnimationSuppressed = false;
bool mInReportMoveEvent = false; // true if in a call to ReportMoveEvent().
bool mInResize = false; // true if in a call to DoResize().
bool mWindowTransformIsIdentity = true;
bool mAlwaysOnTop = false;
bool mAspectRatioLocked = false;
bool mIsAlert = false; // True if this is an non-native alert window.
bool mWasShown = false;
int32_t mNumModalDescendants = 0;
InputContext mInputContext;
NSWindowAnimationBehavior mWindowAnimationBehavior;
private:
// This is class state for tracking which nsCocoaWindow, if any, is in the
// middle of a native fullscreen transition.
static nsCocoaWindow* sWindowInNativeTransition;
// This function returns true if the caller has been able to claim the sole
// permission to start a native transition. It must be followed by a call
// to EndOurNativeTransition() when the native transition is complete.
bool CanStartNativeTransition();
void EndOurNativeTransition();
};
#endif // nsCocoaWindow_h_