Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / cocoa / nsCocoaWindow.mm
blob9e749fda1e3e020cb6c1fedf85876d1bc8240cf3
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsCocoaWindow.h"
9 #include "AppearanceOverride.h"
10 #include "NativeKeyBindings.h"
11 #include "ScreenHelperCocoa.h"
12 #include "TextInputHandler.h"
13 #include "nsCocoaUtils.h"
14 #include "nsObjCExceptions.h"
15 #include "nsCOMPtr.h"
16 #include "nsWidgetsCID.h"
17 #include "nsIRollupListener.h"
18 #include "nsChildView.h"
19 #include "nsWindowMap.h"
20 #include "nsAppShell.h"
21 #include "nsIAppShellService.h"
22 #include "nsIBaseWindow.h"
23 #include "nsIInterfaceRequestorUtils.h"
24 #include "nsIAppWindow.h"
25 #include "nsToolkit.h"
26 #include "nsPIDOMWindow.h"
27 #include "nsThreadUtils.h"
28 #include "nsMenuBarX.h"
29 #include "nsMenuUtilsX.h"
30 #include "nsStyleConsts.h"
31 #include "nsNativeThemeColors.h"
32 #include "nsNativeThemeCocoa.h"
33 #include "nsChildView.h"
34 #include "nsCocoaFeatures.h"
35 #include "nsIScreenManager.h"
36 #include "nsIWidgetListener.h"
37 #include "nsXULPopupManager.h"
38 #include "VibrancyManager.h"
39 #include "nsPresContext.h"
40 #include "nsDocShell.h"
42 #include "gfxPlatform.h"
43 #include "qcms.h"
45 #include "mozilla/AutoRestore.h"
46 #include "mozilla/BasicEvents.h"
47 #include "mozilla/dom/Document.h"
48 #include "mozilla/Maybe.h"
49 #include "mozilla/NativeKeyBindingsType.h"
50 #include "mozilla/Preferences.h"
51 #include "mozilla/PresShell.h"
52 #include "mozilla/ScopeExit.h"
53 #include "mozilla/StaticPrefs_gfx.h"
54 #include "mozilla/StaticPrefs_widget.h"
55 #include "mozilla/WritingModes.h"
56 #include "mozilla/layers/CompositorBridgeChild.h"
57 #include "mozilla/widget/Screen.h"
58 #include <algorithm>
60 namespace mozilla {
61 namespace layers {
62 class LayerManager;
63 }  // namespace layers
64 }  // namespace mozilla
65 using namespace mozilla::layers;
66 using namespace mozilla::widget;
67 using namespace mozilla;
69 BOOL sTouchBarIsInitialized = NO;
71 // defined in nsMenuBarX.mm
72 extern NSMenu* sApplicationMenu;  // Application menu shared by all menubars
74 // defined in nsChildView.mm
75 extern BOOL gSomeMenuBarPainted;
77 static uint32_t sModalWindowCount = 0;
79 extern "C" {
80 // CGSPrivate.h
81 typedef NSInteger CGSConnection;
82 typedef NSUInteger CGSSpaceID;
83 typedef NSInteger CGSWindow;
84 typedef enum {
85   kCGSSpaceIncludesCurrent = 1 << 0,
86   kCGSSpaceIncludesOthers = 1 << 1,
87   kCGSSpaceIncludesUser = 1 << 2,
89   kCGSAllSpacesMask =
90       kCGSSpaceIncludesCurrent | kCGSSpaceIncludesOthers | kCGSSpaceIncludesUser
91 } CGSSpaceMask;
92 static NSString* const CGSSpaceIDKey = @"ManagedSpaceID";
93 static NSString* const CGSSpacesKey = @"Spaces";
94 extern CGSConnection _CGSDefaultConnection(void);
95 extern CGError CGSSetWindowTransform(CGSConnection cid, CGSWindow wid,
96                                      CGAffineTransform transform);
99 #define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1"
101 static void RollUpPopups(nsIRollupListener::AllowAnimations aAllowAnimations =
102                              nsIRollupListener::AllowAnimations::Yes) {
103   if (RefPtr pm = nsXULPopupManager::GetInstance()) {
104     pm->RollupTooltips();
105   }
107   nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
108   if (!rollupListener) {
109     return;
110   }
111   if (rollupListener->RollupNativeMenu()) {
112     return;
113   }
114   nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
115   if (!rollupWidget) {
116     return;
117   }
118   nsIRollupListener::RollupOptions options{
119       0, nsIRollupListener::FlushViews::Yes, nullptr, aAllowAnimations};
120   rollupListener->Rollup(options);
123 nsCocoaWindow::nsCocoaWindow()
124     : mParent(nullptr),
125       mAncestorLink(nullptr),
126       mWindow(nil),
127       mDelegate(nil),
128       mPopupContentView(nil),
129       mFullscreenTransitionAnimation(nil),
130       mShadowStyle(WindowShadow::None),
131       mBackingScaleFactor(0.0),
132       mAnimationType(nsIWidget::eGenericWindowAnimation),
133       mWindowMadeHere(false),
134       mSizeMode(nsSizeMode_Normal),
135       mInFullScreenMode(false),
136       mInNativeFullScreenMode(false),
137       mIgnoreOcclusionCount(0),
138       mHasStartedNativeFullscreen(false),
139       mWindowAnimationBehavior(NSWindowAnimationBehaviorDefault) {
140   // Disable automatic tabbing. We need to do this before we
141   // orderFront any of our windows.
142   NSWindow.allowsAutomaticWindowTabbing = NO;
145 void nsCocoaWindow::DestroyNativeWindow() {
146   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
148   if (!mWindow) {
149     return;
150   }
152   MOZ_ASSERT(mWindowMadeHere,
153              "We shouldn't be trying to destroy a window we didn't create.");
155   // Clear our class state that is keyed off of mWindow. It's our last
156   // chance! This ensures that other nsCocoaWindow instances are not waiting
157   // for us to finish a native transition that will have no listener once
158   // we clear our delegate.
159   EndOurNativeTransition();
161   [mWindow releaseJSObjects];
162   // We want to unhook the delegate here because we don't want events
163   // sent to it after this object has been destroyed.
164   mWindow.delegate = nil;
165   [mWindow close];
166   mWindow = nil;
167   [mDelegate autorelease];
169   NS_OBJC_END_TRY_IGNORE_BLOCK;
172 nsCocoaWindow::~nsCocoaWindow() {
173   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
175   // Notify the children that we're gone.  Popup windows (e.g. tooltips) can
176   // have nsChildView children.  'kid' is an nsChildView object if and only if
177   // its 'type' is 'WindowType::Child'.
178   // childView->ResetParent() can change our list of children while it's
179   // being iterated, so the way we iterate the list must allow for this.
180   for (nsIWidget* kid = mLastChild; kid;) {
181     WindowType kidType = kid->GetWindowType();
182     if (kidType == WindowType::Child) {
183       nsChildView* childView = static_cast<nsChildView*>(kid);
184       kid = kid->GetPrevSibling();
185       childView->ResetParent();
186     } else {
187       nsCocoaWindow* childWindow = static_cast<nsCocoaWindow*>(kid);
188       childWindow->mParent = nullptr;
189       childWindow->mAncestorLink = mAncestorLink;
190       kid = kid->GetPrevSibling();
191     }
192   }
194   if (mWindow && mWindowMadeHere) {
195     CancelAllTransitions();
196     DestroyNativeWindow();
197   }
199   NS_IF_RELEASE(mPopupContentView);
200   NS_OBJC_END_TRY_IGNORE_BLOCK;
203 // Find the screen that overlaps aRect the most,
204 // if none are found default to the mainScreen.
205 static NSScreen* FindTargetScreenForRect(const DesktopIntRect& aRect) {
206   NSScreen* targetScreen = [NSScreen mainScreen];
207   NSEnumerator* screenEnum = [[NSScreen screens] objectEnumerator];
208   int largestIntersectArea = 0;
209   while (NSScreen* screen = [screenEnum nextObject]) {
210     DesktopIntRect screenRect =
211         nsCocoaUtils::CocoaRectToGeckoRect([screen visibleFrame]);
212     screenRect = screenRect.Intersect(aRect);
213     int area = screenRect.width * screenRect.height;
214     if (area > largestIntersectArea) {
215       largestIntersectArea = area;
216       targetScreen = screen;
217     }
218   }
219   return targetScreen;
222 DesktopToLayoutDeviceScale ParentBackingScaleFactor(nsIWidget* aParent,
223                                                     NSView* aParentView) {
224   if (aParent) {
225     return aParent->GetDesktopToDeviceScale();
226   }
227   NSWindow* parentWindow = [aParentView window];
228   if (parentWindow) {
229     return DesktopToLayoutDeviceScale(parentWindow.backingScaleFactor);
230   }
231   return DesktopToLayoutDeviceScale(1.0);
234 // Returns the screen rectangle for the given widget.
235 // Child widgets are positioned relative to this rectangle.
236 // Exactly one of the arguments must be non-null.
237 static DesktopRect GetWidgetScreenRectForChildren(nsIWidget* aWidget,
238                                                   NSView* aView) {
239   if (aWidget) {
240     mozilla::DesktopToLayoutDeviceScale scale =
241         aWidget->GetDesktopToDeviceScale();
242     if (aWidget->GetWindowType() == WindowType::Child) {
243       return aWidget->GetScreenBounds() / scale;
244     }
245     return aWidget->GetClientBounds() / scale;
246   }
248   MOZ_RELEASE_ASSERT(aView);
250   // 1. Transform the view rect into window coords.
251   // The returned rect is in "origin bottom-left" coordinates.
252   NSRect rectInWindowCoordinatesOBL = [aView convertRect:[aView bounds]
253                                                   toView:nil];
255   // 2. Turn the window-coord rect into screen coords, still origin bottom-left.
256   NSRect rectInScreenCoordinatesOBL =
257       [[aView window] convertRectToScreen:rectInWindowCoordinatesOBL];
259   // 3. Convert the NSRect to a DesktopRect. This will convert to coordinates
260   // with the origin in the top left corner of the primary screen.
261   return DesktopRect(
262       nsCocoaUtils::CocoaRectToGeckoRect(rectInScreenCoordinatesOBL));
265 // aRect here is specified in desktop pixels
267 // For child windows (where either aParent or aNativeParent is non-null),
268 // aRect.{x,y} are offsets from the origin of the parent window and not an
269 // absolute position.
270 nsresult nsCocoaWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
271                                const DesktopIntRect& aRect,
272                                widget::InitData* aInitData) {
273   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
275   // Because the hidden window is created outside of an event loop,
276   // we have to provide an autorelease pool (see bug 559075).
277   nsAutoreleasePool localPool;
279   // Set defaults which can be overriden from aInitData in BaseCreate
280   mWindowType = WindowType::TopLevel;
281   mBorderStyle = BorderStyle::Default;
283   // Ensure that the toolkit is created.
284   nsToolkit::GetToolkit();
286   Inherited::BaseCreate(aParent, aInitData);
288   mParent = aParent;
289   mAncestorLink = aParent;
290   mAlwaysOnTop = aInitData->mAlwaysOnTop;
291   mIsAlert = aInitData->mIsAlert;
293   // If we have a parent widget, the new widget will be offset from the
294   // parent widget by aRect.{x,y}. Otherwise, we'll use aRect for the
295   // new widget coordinates.
296   DesktopIntPoint parentOrigin;
298   // Do we have a parent widget?
299   if (aParent || aNativeParent) {
300     DesktopRect parentDesktopRect =
301         GetWidgetScreenRectForChildren(aParent, (NSView*)aNativeParent);
302     parentOrigin = gfx::RoundedToInt(parentDesktopRect.TopLeft());
303   }
305   DesktopIntRect widgetRect = aRect + parentOrigin;
307   nsresult rv =
308       CreateNativeWindow(nsCocoaUtils::GeckoRectToCocoaRect(widgetRect),
309                          mBorderStyle, false, aInitData->mIsPrivate);
310   NS_ENSURE_SUCCESS(rv, rv);
312   if (mWindowType == WindowType::Popup) {
313     // now we can convert widgetRect to device pixels for the window we created,
314     // as the child view expects a rect expressed in the dev pix of its parent
315     LayoutDeviceIntRect devRect =
316         RoundedToInt(aRect * GetDesktopToDeviceScale());
317     return CreatePopupContentView(devRect, aInitData);
318   }
320   mIsAnimationSuppressed = aInitData->mIsAnimationSuppressed;
322   return NS_OK;
324   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
327 nsresult nsCocoaWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
328                                const LayoutDeviceIntRect& aRect,
329                                widget::InitData* aInitData) {
330   DesktopIntRect desktopRect = RoundedToInt(
331       aRect / ParentBackingScaleFactor(aParent, (NSView*)aNativeParent));
332   return Create(aParent, aNativeParent, desktopRect, aInitData);
335 static unsigned int WindowMaskForBorderStyle(BorderStyle aBorderStyle) {
336   bool allOrDefault = (aBorderStyle == BorderStyle::All ||
337                        aBorderStyle == BorderStyle::Default);
339   /* Apple's docs on NSWindow styles say that "a window's style mask should
340    * include NSWindowStyleMaskTitled if it includes any of the others [besides
341    * NSWindowStyleMaskBorderless]".  This implies that a borderless window
342    * shouldn't have any other styles than NSWindowStyleMaskBorderless.
343    */
344   if (!allOrDefault && !(aBorderStyle & BorderStyle::Title)) {
345     if (aBorderStyle & BorderStyle::Minimize) {
346       /* It appears that at a minimum, borderless windows can be miniaturizable,
347        * effectively contradicting some of Apple's documentation referenced
348        * above. One such exception is the screen share indicator, see
349        * bug 1742877.
350        */
351       return NSWindowStyleMaskBorderless | NSWindowStyleMaskMiniaturizable;
352     }
353     return NSWindowStyleMaskBorderless;
354   }
356   unsigned int mask = NSWindowStyleMaskTitled;
357   if (allOrDefault || aBorderStyle & BorderStyle::Close) {
358     mask |= NSWindowStyleMaskClosable;
359   }
360   if (allOrDefault || aBorderStyle & BorderStyle::Minimize) {
361     mask |= NSWindowStyleMaskMiniaturizable;
362   }
363   if (allOrDefault || aBorderStyle & BorderStyle::ResizeH) {
364     mask |= NSWindowStyleMaskResizable;
365   }
367   return mask;
370 // If aRectIsFrameRect, aRect specifies the frame rect of the new window.
371 // Otherwise, aRect.x/y specify the position of the window's frame relative to
372 // the bottom of the menubar and aRect.width/height specify the size of the
373 // content rect.
374 nsresult nsCocoaWindow::CreateNativeWindow(const NSRect& aRect,
375                                            BorderStyle aBorderStyle,
376                                            bool aRectIsFrameRect,
377                                            bool aIsPrivateBrowsing) {
378   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
380   // We default to NSWindowStyleMaskBorderless, add features if needed.
381   unsigned int features = NSWindowStyleMaskBorderless;
383   // Configure the window we will create based on the window type.
384   switch (mWindowType) {
385     case WindowType::Invisible:
386     case WindowType::Child:
387       break;
388     case WindowType::Popup:
389       if (aBorderStyle != BorderStyle::Default &&
390           mBorderStyle & BorderStyle::Title) {
391         features |= NSWindowStyleMaskTitled;
392         if (aBorderStyle & BorderStyle::Close) {
393           features |= NSWindowStyleMaskClosable;
394         }
395       }
396       break;
397     case WindowType::TopLevel:
398     case WindowType::Dialog:
399       features = WindowMaskForBorderStyle(aBorderStyle);
400       break;
401     default:
402       NS_ERROR("Unhandled window type!");
403       return NS_ERROR_FAILURE;
404   }
406   NSRect contentRect;
408   if (aRectIsFrameRect) {
409     contentRect = [NSWindow contentRectForFrameRect:aRect styleMask:features];
410   } else {
411     /*
412      * We pass a content area rect to initialize the native Cocoa window. The
413      * content rect we give is the same size as the size we're given by gecko.
414      * The origin we're given for non-popup windows is moved down by the height
415      * of the menu bar so that an origin of (0,100) from gecko puts the window
416      * 100 pixels below the top of the available desktop area. We also move the
417      * origin down by the height of a title bar if it exists. This is so the
418      * origin that gecko gives us for the top-left of  the window turns out to
419      * be the top-left of the window we create. This is how it was done in
420      * Carbon. If it ought to be different we'll probably need to look at all
421      * the callers.
422      *
423      * Note: This means that if you put a secondary screen on top of your main
424      * screen and open a window in the top screen, it'll be incorrectly shifted
425      * down by the height of the menu bar. Same thing would happen in Carbon.
426      *
427      * Note: If you pass a rect with 0,0 for an origin, the window ends up in a
428      * weird place for some reason. This stops that without breaking popups.
429      */
430     // Compensate for difference between frame and content area height (e.g.
431     // title bar).
432     NSRect newWindowFrame = [NSWindow frameRectForContentRect:aRect
433                                                     styleMask:features];
435     contentRect = aRect;
436     contentRect.origin.y -= (newWindowFrame.size.height - aRect.size.height);
438     if (mWindowType != WindowType::Popup) {
439       contentRect.origin.y -= NSApp.mainMenu.menuBarHeight;
440     }
441   }
443   // NSLog(@"Top-level window being created at Cocoa rect: %f, %f, %f, %f\n",
444   //       rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
446   Class windowClass = [BaseWindow class];
447   if ((mWindowType == WindowType::TopLevel ||
448        mWindowType == WindowType::Dialog) &&
449       (features & NSWindowStyleMaskTitled)) {
450     // If we have a titlebar on a top-level window, we want to be able to
451     // control the titlebar color (for unified windows), so use the special
452     // ToolbarWindow class. Note that we need to check the window type because
453     // we mark sheets as having titlebars.
454     windowClass = [ToolbarWindow class];
455   } else if (mWindowType == WindowType::Popup) {
456     windowClass = [PopupWindow class];
457     // If we're a popup window we need to use the PopupWindow class.
458   } else if (features == NSWindowStyleMaskBorderless) {
459     // If we're a non-popup borderless window we need to use the
460     // BorderlessWindow class.
461     windowClass = [BorderlessWindow class];
462   }
464   // Create the window
465   mWindow = [[windowClass alloc] initWithContentRect:contentRect
466                                            styleMask:features
467                                              backing:NSBackingStoreBuffered
468                                                defer:YES];
470   // Make sure that window titles don't leak to disk in private browsing mode
471   // due to macOS' resume feature.
472   mWindow.restorable = !aIsPrivateBrowsing;
473   if (aIsPrivateBrowsing) {
474     [mWindow disableSnapshotRestoration];
475   }
477   // setup our notification delegate. Note that setDelegate: does NOT retain.
478   mDelegate = [[WindowDelegate alloc] initWithGeckoWindow:this];
479   mWindow.delegate = mDelegate;
481   // Make sure that the content rect we gave has been honored.
482   NSRect wantedFrame = [mWindow frameRectForChildViewRect:contentRect];
483   if (!NSEqualRects(mWindow.frame, wantedFrame)) {
484     // This can happen when the window is not on the primary screen.
485     [mWindow setFrame:wantedFrame display:NO];
486   }
487   UpdateBounds();
489   if (mWindowType == WindowType::Invisible) {
490     mWindow.level = kCGDesktopWindowLevelKey;
491   }
493   if (mWindowType == WindowType::Popup) {
494     SetPopupWindowLevel();
495     mWindow.backgroundColor = NSColor.clearColor;
496     mWindow.opaque = NO;
498     // When multiple spaces are in use and the browser is assigned to a
499     // particular space, override the "Assign To" space and display popups on
500     // the active space. Does not work with multiple displays. See
501     // NeedsRecreateToReshow() for multi-display with multi-space workaround.
502     mWindow.collectionBehavior = mWindow.collectionBehavior |
503                                  NSWindowCollectionBehaviorMoveToActiveSpace;
504   } else {
505     // Non-popup windows are always opaque.
506     mWindow.opaque = YES;
507   }
509   if (mAlwaysOnTop || mIsAlert) {
510     mWindow.level = NSFloatingWindowLevel;
511     mWindow.collectionBehavior =
512         mWindow.collectionBehavior | NSWindowCollectionBehaviorCanJoinAllSpaces;
513   }
514   mWindow.contentMinSize = NSMakeSize(60, 60);
515   [mWindow disableCursorRects];
517   // Make the window use CoreAnimation from the start, so that we don't
518   // switch from a non-CA window to a CA-window in the middle.
519   mWindow.contentView.wantsLayer = YES;
521   // Make sure the window starts out not draggable by the background.
522   // We will turn it on as necessary.
523   mWindow.movableByWindowBackground = NO;
525   [WindowDataMap.sharedWindowDataMap ensureDataForWindow:mWindow];
526   mWindowMadeHere = true;
528   // Make the window respect the global appearance, which follows the
529   // browser.theme.toolbar-theme pref.
530   mWindow.appearanceSource = MOZGlobalAppearance.sharedInstance;
532   return NS_OK;
534   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
537 nsresult nsCocoaWindow::CreatePopupContentView(const LayoutDeviceIntRect& aRect,
538                                                widget::InitData* aInitData) {
539   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
541   // We need to make our content view a ChildView.
542   mPopupContentView = new nsChildView();
543   if (!mPopupContentView) return NS_ERROR_FAILURE;
545   NS_ADDREF(mPopupContentView);
547   nsIWidget* thisAsWidget = static_cast<nsIWidget*>(this);
548   nsresult rv =
549       mPopupContentView->Create(thisAsWidget, nullptr, aRect, aInitData);
550   if (NS_WARN_IF(NS_FAILED(rv))) {
551     return rv;
552   }
554   NSView* contentView = mWindow.contentView;
555   auto* childView = static_cast<ChildView*>(
556       mPopupContentView->GetNativeData(NS_NATIVE_WIDGET));
557   childView.frame = contentView.bounds;
558   childView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
559   [contentView addSubview:childView];
561   return NS_OK;
563   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
566 void nsCocoaWindow::Destroy() {
567   if (mOnDestroyCalled) {
568     return;
569   }
570   mOnDestroyCalled = true;
572   // Deal with the possiblity that we're being destroyed while running modal.
573   if (mModal) {
574     SetModal(false);
575   }
577   // If we don't hide here we run into problems with panels, this is not ideal.
578   // (Bug 891424)
579   Show(false);
581   if (mPopupContentView) mPopupContentView->Destroy();
583   if (mFullscreenTransitionAnimation) {
584     [mFullscreenTransitionAnimation stopAnimation];
585     ReleaseFullscreenTransitionAnimation();
586   }
588   nsBaseWidget::Destroy();
589   // nsBaseWidget::Destroy() calls GetParent()->RemoveChild(this). But we
590   // don't implement GetParent(), so we need to do the equivalent here.
591   if (mParent) {
592     mParent->RemoveChild(this);
593   }
594   nsBaseWidget::OnDestroy();
596   if (mInFullScreenMode && !mInNativeFullScreenMode) {
597     // Keep these calls balanced for emulated fullscreen.
598     nsCocoaUtils::HideOSChromeOnScreen(false);
599   }
601   // Destroy the native window here (and not wait for that to happen in our
602   // destructor). Otherwise this might not happen for several seconds because
603   // at least one object holding a reference to ourselves is usually waiting
604   // to be garbage-collected.
605   if (mWindow && mWindowMadeHere) {
606     CancelAllTransitions();
607     DestroyNativeWindow();
608   }
611 void* nsCocoaWindow::GetNativeData(uint32_t aDataType) {
612   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
614   void* retVal = nullptr;
616   switch (aDataType) {
617     // to emulate how windows works, we always have to return a NSView
618     // for NS_NATIVE_WIDGET
619     case NS_NATIVE_WIDGET:
620       retVal = mWindow.contentView;
621       break;
623     case NS_NATIVE_WINDOW:
624       retVal = mWindow;
625       break;
627     case NS_NATIVE_GRAPHIC:
628       // There isn't anything that makes sense to return here,
629       // and it doesn't matter so just return nullptr.
630       NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a top-level window!");
631       break;
632     case NS_RAW_NATIVE_IME_CONTEXT: {
633       retVal = GetPseudoIMEContext();
634       if (retVal) {
635         break;
636       }
637       NSView* view = mWindow ? mWindow.contentView : nil;
638       if (view) {
639         retVal = view.inputContext;
640       }
641       // If inputContext isn't available on this window, return this window's
642       // pointer instead of nullptr since if this returns nullptr,
643       // IMEStateManager cannot manage composition with TextComposition
644       // instance.  Although, this case shouldn't occur.
645       if (NS_WARN_IF(!retVal)) {
646         retVal = this;
647       }
648       break;
649     }
650   }
652   return retVal;
654   NS_OBJC_END_TRY_BLOCK_RETURN(nullptr);
657 bool nsCocoaWindow::IsVisible() const {
658   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
660   return mWindow && mWindow.isVisibleOrBeingShown;
662   NS_OBJC_END_TRY_BLOCK_RETURN(false);
665 void nsCocoaWindow::SetModal(bool aModal) {
666   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
668   if (mModal == aModal) {
669     return;
670   }
672   // Unlike many functions here, we explicitly *do not check* for the
673   // existence of mWindow. This is to ensure that calls to SetModal have
674   // no early exits and always update state. That way, if the calls are
675   // balanced, we get expected behavior even if the native window has
676   // been destroyed during the modal period. Within this function, all
677   // the calls to mWindow will resolve even if mWindow is nil (as is
678   // guaranteed by Objective-C). And since those calls are only concerned
679   // with changing mWindow appearance/level, it's fine for them to be
680   // no-ops if mWindow has already been destroyed.
682   // This is used during startup (outside the event loop) when creating
683   // the add-ons compatibility checking dialog and the profile manager UI;
684   // therefore, it needs to provide an autorelease pool to avoid cocoa
685   // objects leaking.
686   nsAutoreleasePool localPool;
688   mModal = aModal;
690   if (aModal) {
691     sModalWindowCount++;
692   } else {
693     MOZ_ASSERT(sModalWindowCount);
694     sModalWindowCount--;
695   }
697   // When a window gets "set modal", make the window(s) that it appears over
698   // behave as they should.  We can't rely on native methods to do this, for the
699   // following reason:  The OS runs modal non-sheet windows in an event loop
700   // (using [NSApplication runModalForWindow:] or similar methods) that's
701   // incompatible with the modal event loop in AppWindow::ShowModal() (each of
702   // these event loops is "exclusive", and can't run at the same time as other
703   // (similar) event loops).
704   for (auto* ancestor = static_cast<nsCocoaWindow*>(mAncestorLink); ancestor;
705        ancestor = static_cast<nsCocoaWindow*>(ancestor->mParent)) {
706     const bool changed = aModal ? ancestor->mNumModalDescendants++ == 0
707                                 : --ancestor->mNumModalDescendants == 0;
708     NS_ASSERTION(ancestor->mNumModalDescendants >= 0,
709                  "Widget hierarchy changed while modal!");
710     if (!changed || ancestor->mWindowType == WindowType::Invisible) {
711       continue;
712     }
713     NSWindow* win = ancestor->GetCocoaWindow();
714     [[win standardWindowButton:NSWindowCloseButton] setEnabled:!aModal];
715     [[win standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!aModal];
716     [[win standardWindowButton:NSWindowZoomButton] setEnabled:!aModal];
717   }
718   if (aModal) {
719     mWindow.level = NSModalPanelWindowLevel;
720   } else if (mWindowType == WindowType::Popup) {
721     SetPopupWindowLevel();
722   } else {
723     mWindow.level = NSNormalWindowLevel;
724   }
726   NS_OBJC_END_TRY_IGNORE_BLOCK;
729 bool nsCocoaWindow::IsRunningAppModal() { return [NSApp _isRunningAppModal]; }
731 // Hide or show this window
732 void nsCocoaWindow::Show(bool aState) {
733   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
735   if (!mWindow) {
736     return;
737   }
739   // Early exit if our current visibility state is already the requested
740   // state.
741   if (aState == mWindow.isVisibleOrBeingShown) {
742     return;
743   }
745   [mWindow setBeingShown:aState];
746   if (aState && !mWasShown) {
747     mWasShown = true;
748   }
750   NSWindow* nativeParentWindow =
751       mParent ? (NSWindow*)mParent->GetNativeData(NS_NATIVE_WINDOW) : nil;
753   if (aState && !mBounds.IsEmpty()) {
754     // If we had set the activationPolicy to accessory, then right now we won't
755     // have a dock icon. Make sure that we undo that and show a dock icon now
756     // that we're going to show a window.
757     if (NSApp.activationPolicy != NSApplicationActivationPolicyRegular) {
758       NSApp.activationPolicy = NSApplicationActivationPolicyRegular;
759       PR_SetEnv("MOZ_APP_NO_DOCK=");
760     }
762     // Don't try to show a popup when the parent isn't visible or is minimized.
763     if (mWindowType == WindowType::Popup && nativeParentWindow) {
764       if (!nativeParentWindow.isVisible || nativeParentWindow.isMiniaturized) {
765         return;
766       }
767     }
769     if (mPopupContentView) {
770       // Ensure our content view is visible. We never need to hide it.
771       mPopupContentView->Show(true);
772     }
774     if (mWindowType == WindowType::Popup) {
775       // For reasons that aren't yet clear, calls to [NSWindow orderFront:] or
776       // [NSWindow makeKeyAndOrderFront:] can sometimes trigger "Error (1000)
777       // creating CGSWindow", which in turn triggers an internal inconsistency
778       // NSException.  These errors shouldn't be fatal.  So we need to wrap
779       // calls to ...orderFront: in TRY blocks.  See bmo bug 470864.
780       NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
781       [[mWindow contentView] setNeedsDisplay:YES];
782       if (!nativeParentWindow || mPopupLevel != PopupLevel::Parent) {
783         [mWindow orderFront:nil];
784       }
785       NS_OBJC_END_TRY_IGNORE_BLOCK;
786       SendSetZLevelEvent();
787       // If our popup window is a non-native context menu, tell the OS (and
788       // other programs) that a menu has opened.  This is how the OS knows to
789       // close other programs' context menus when ours open.
790       if ([mWindow isKindOfClass:[PopupWindow class]] &&
791           [(PopupWindow*)mWindow isContextMenu]) {
792         [NSDistributedNotificationCenter.defaultCenter
793             postNotificationName:
794                 @"com.apple.HIToolbox.beginMenuTrackingNotification"
795                           object:@"org.mozilla.gecko.PopupWindow"];
796       }
798       // If a parent window was supplied and this is a popup at the parent
799       // level, set its child window. This will cause the child window to
800       // appear above the parent and move when the parent does.
801       if (nativeParentWindow && mPopupLevel == PopupLevel::Parent) {
802         [nativeParentWindow addChildWindow:mWindow ordered:NSWindowAbove];
803       }
804     } else {
805       NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
806       if (mWindowType == WindowType::TopLevel &&
807           [mWindow respondsToSelector:@selector(setAnimationBehavior:)]) {
808         NSWindowAnimationBehavior behavior;
809         if (mIsAnimationSuppressed) {
810           behavior = NSWindowAnimationBehaviorNone;
811         } else {
812           switch (mAnimationType) {
813             case nsIWidget::eDocumentWindowAnimation:
814               behavior = NSWindowAnimationBehaviorDocumentWindow;
815               break;
816             default:
817               MOZ_FALLTHROUGH_ASSERT("unexpected mAnimationType value");
818             case nsIWidget::eGenericWindowAnimation:
819               behavior = NSWindowAnimationBehaviorDefault;
820               break;
821           }
822         }
823         [mWindow setAnimationBehavior:behavior];
824         mWindowAnimationBehavior = behavior;
825       }
827       // We don't want alwaysontop / alert windows to pull focus when they're
828       // opened, as these tend to be for peripheral indicators and displays.
829       if (mAlwaysOnTop || mIsAlert) {
830         [mWindow orderFront:nil];
831       } else {
832         [mWindow makeKeyAndOrderFront:nil];
833       }
834       NS_OBJC_END_TRY_IGNORE_BLOCK;
835       SendSetZLevelEvent();
836     }
837   } else {
838     // roll up any popups if a top-level window is going away
839     if (mWindowType == WindowType::TopLevel ||
840         mWindowType == WindowType::Dialog) {
841       RollUpPopups();
842     }
844     // If the window is a popup window with a parent window we need to
845     // unhook it here before ordering it out. When you order out the child
846     // of a window it hides the parent window.
847     if (mWindowType == WindowType::Popup && nativeParentWindow) {
848       [nativeParentWindow removeChildWindow:mWindow];
849     }
851     [mWindow orderOut:nil];
852     // If our popup window is a non-native context menu, tell the OS (and
853     // other programs) that a menu has closed.
854     if ([mWindow isKindOfClass:[PopupWindow class]] &&
855         [(PopupWindow*)mWindow isContextMenu]) {
856       [NSDistributedNotificationCenter.defaultCenter
857           postNotificationName:
858               @"com.apple.HIToolbox.endMenuTrackingNotification"
859                         object:@"org.mozilla.gecko.PopupWindow"];
860     }
861   }
863   [mWindow setBeingShown:NO];
865   NS_OBJC_END_TRY_IGNORE_BLOCK;
868 // Work around a problem where with multiple displays and multiple spaces
869 // enabled, where the browser is assigned to a single display or space, popup
870 // windows that are reshown after being hidden with [NSWindow orderOut] show on
871 // the assigned space even when opened from another display. Apply the
872 // workaround whenever more than one display is enabled.
873 bool nsCocoaWindow::NeedsRecreateToReshow() {
874   // Limit the workaround to popup windows because only they need to override
875   // the "Assign To" setting. i.e., to display where the parent window is.
876   return mWindowType == WindowType::Popup && mWasShown &&
877          NSScreen.screens.count > 1;
880 WindowRenderer* nsCocoaWindow::GetWindowRenderer() {
881   if (mPopupContentView) {
882     return mPopupContentView->GetWindowRenderer();
883   }
884   return nullptr;
887 TransparencyMode nsCocoaWindow::GetTransparencyMode() {
888   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
890   return !mWindow || mWindow.isOpaque ? TransparencyMode::Opaque
891                                       : TransparencyMode::Transparent;
893   NS_OBJC_END_TRY_BLOCK_RETURN(TransparencyMode::Opaque);
896 // This is called from nsMenuPopupFrame when making a popup transparent.
897 void nsCocoaWindow::SetTransparencyMode(TransparencyMode aMode) {
898   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
900   if (!mWindow) {
901     return;
902   }
904   BOOL isTransparent = aMode == TransparencyMode::Transparent;
905   BOOL currentTransparency = !mWindow.isOpaque;
906   if (isTransparent == currentTransparency) {
907     return;
908   }
909   mWindow.opaque = !isTransparent;
910   mWindow.backgroundColor =
911       isTransparent ? NSColor.clearColor : NSColor.whiteColor;
913   NS_OBJC_END_TRY_IGNORE_BLOCK;
916 void nsCocoaWindow::Enable(bool aState) {}
918 bool nsCocoaWindow::IsEnabled() const { return true; }
920 void nsCocoaWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
921   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
923   if (!mWindow || ![mWindow screen]) {
924     return;
925   }
927   nsIntRect screenBounds;
929   int32_t width, height;
931   NSRect frame = mWindow.frame;
933   // zero size rects confuse the screen manager
934   width = std::max<int32_t>(frame.size.width, 1);
935   height = std::max<int32_t>(frame.size.height, 1);
937   nsCOMPtr<nsIScreenManager> screenMgr =
938       do_GetService("@mozilla.org/gfx/screenmanager;1");
939   if (screenMgr) {
940     nsCOMPtr<nsIScreen> screen;
941     screenMgr->ScreenForRect(aPoint.x, aPoint.y, width, height,
942                              getter_AddRefs(screen));
944     if (screen) {
945       screen->GetRectDisplayPix(&(screenBounds.x), &(screenBounds.y),
946                                 &(screenBounds.width), &(screenBounds.height));
947     }
948   }
950   if (aPoint.x < screenBounds.x) {
951     aPoint.x = screenBounds.x;
952   } else if (aPoint.x >= screenBounds.x + screenBounds.width - width) {
953     aPoint.x = screenBounds.x + screenBounds.width - width;
954   }
956   if (aPoint.y < screenBounds.y) {
957     aPoint.y = screenBounds.y;
958   } else if (aPoint.y >= screenBounds.y + screenBounds.height - height) {
959     aPoint.y = screenBounds.y + screenBounds.height - height;
960   }
962   NS_OBJC_END_TRY_IGNORE_BLOCK;
965 void nsCocoaWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
966   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
968   // Popups can be smaller than (32, 32)
969   NSRect rect = (mWindowType == WindowType::Popup)
970                     ? NSZeroRect
971                     : NSMakeRect(0.0, 0.0, 32, 32);
972   rect = [mWindow frameRectForChildViewRect:rect];
974   SizeConstraints c = aConstraints;
976   if (c.mScale.scale == MOZ_WIDGET_INVALID_SCALE) {
977     c.mScale.scale = BackingScaleFactor();
978   }
980   c.mMinSize.width = std::max(
981       nsCocoaUtils::CocoaPointsToDevPixels(rect.size.width, c.mScale.scale),
982       c.mMinSize.width);
983   c.mMinSize.height = std::max(
984       nsCocoaUtils::CocoaPointsToDevPixels(rect.size.height, c.mScale.scale),
985       c.mMinSize.height);
987   NSSize minSize = {
988       nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.width, c.mScale.scale),
989       nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.height, c.mScale.scale)};
990   mWindow.minSize = minSize;
992   c.mMaxSize.width = std::max(
993       nsCocoaUtils::CocoaPointsToDevPixels(c.mMaxSize.width, c.mScale.scale),
994       c.mMaxSize.width);
995   c.mMaxSize.height = std::max(
996       nsCocoaUtils::CocoaPointsToDevPixels(c.mMaxSize.height, c.mScale.scale),
997       c.mMaxSize.height);
999   NSSize maxSize = {
1000       c.mMaxSize.width == NS_MAXSIZE ? FLT_MAX
1001                                      : nsCocoaUtils::DevPixelsToCocoaPoints(
1002                                            c.mMaxSize.width, c.mScale.scale),
1003       c.mMaxSize.height == NS_MAXSIZE ? FLT_MAX
1004                                       : nsCocoaUtils::DevPixelsToCocoaPoints(
1005                                             c.mMaxSize.height, c.mScale.scale)};
1006   mWindow.maxSize = maxSize;
1007   nsBaseWidget::SetSizeConstraints(c);
1009   NS_OBJC_END_TRY_IGNORE_BLOCK;
1012 // Coordinates are desktop pixels
1013 void nsCocoaWindow::Move(double aX, double aY) {
1014   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1016   if (!mWindow) {
1017     return;
1018   }
1020   // The point we have is in Gecko coordinates (origin top-left). Convert
1021   // it to Cocoa ones (origin bottom-left).
1022   NSPoint coord = {
1023       static_cast<float>(aX),
1024       static_cast<float>(nsCocoaUtils::FlippedScreenY(NSToIntRound(aY)))};
1026   NSRect frame = mWindow.frame;
1027   if (frame.origin.x != coord.x ||
1028       frame.origin.y + frame.size.height != coord.y) {
1029     [mWindow setFrameTopLeftPoint:coord];
1030   }
1032   NS_OBJC_END_TRY_IGNORE_BLOCK;
1035 void nsCocoaWindow::SetSizeMode(nsSizeMode aMode) {
1036   if (aMode == nsSizeMode_Normal) {
1037     QueueTransition(TransitionType::Windowed);
1038   } else if (aMode == nsSizeMode_Minimized) {
1039     QueueTransition(TransitionType::Miniaturize);
1040   } else if (aMode == nsSizeMode_Maximized) {
1041     QueueTransition(TransitionType::Zoom);
1042   } else if (aMode == nsSizeMode_Fullscreen) {
1043     MakeFullScreen(true);
1044   }
1047 // The (work)space switching implementation below was inspired by Phoenix:
1048 // https://github.com/kasper/phoenix/tree/d6c877f62b30a060dff119d8416b0934f76af534
1049 // License: MIT.
1051 // Runtime `CGSGetActiveSpace` library function feature detection.
1052 typedef CGSSpaceID (*CGSGetActiveSpaceFunc)(CGSConnection cid);
1053 static CGSGetActiveSpaceFunc GetCGSGetActiveSpaceFunc() {
1054   static CGSGetActiveSpaceFunc func = nullptr;
1055   static bool lookedUpFunc = false;
1056   if (!lookedUpFunc) {
1057     func = (CGSGetActiveSpaceFunc)dlsym(RTLD_DEFAULT, "CGSGetActiveSpace");
1058     lookedUpFunc = true;
1059   }
1060   return func;
1062 // Runtime `CGSCopyManagedDisplaySpaces` library function feature detection.
1063 typedef CFArrayRef (*CGSCopyManagedDisplaySpacesFunc)(CGSConnection cid);
1064 static CGSCopyManagedDisplaySpacesFunc GetCGSCopyManagedDisplaySpacesFunc() {
1065   static CGSCopyManagedDisplaySpacesFunc func = nullptr;
1066   static bool lookedUpFunc = false;
1067   if (!lookedUpFunc) {
1068     func = (CGSCopyManagedDisplaySpacesFunc)dlsym(
1069         RTLD_DEFAULT, "CGSCopyManagedDisplaySpaces");
1070     lookedUpFunc = true;
1071   }
1072   return func;
1074 // Runtime `CGSCopySpacesForWindows` library function feature detection.
1075 typedef CFArrayRef (*CGSCopySpacesForWindowsFunc)(CGSConnection cid,
1076                                                   CGSSpaceMask mask,
1077                                                   CFArrayRef windowIDs);
1078 static CGSCopySpacesForWindowsFunc GetCGSCopySpacesForWindowsFunc() {
1079   static CGSCopySpacesForWindowsFunc func = nullptr;
1080   static bool lookedUpFunc = false;
1081   if (!lookedUpFunc) {
1082     func = (CGSCopySpacesForWindowsFunc)dlsym(RTLD_DEFAULT,
1083                                               "CGSCopySpacesForWindows");
1084     lookedUpFunc = true;
1085   }
1086   return func;
1088 // Runtime `CGSAddWindowsToSpaces` library function feature detection.
1089 typedef void (*CGSAddWindowsToSpacesFunc)(CGSConnection cid,
1090                                           CFArrayRef windowIDs,
1091                                           CFArrayRef spaceIDs);
1092 static CGSAddWindowsToSpacesFunc GetCGSAddWindowsToSpacesFunc() {
1093   static CGSAddWindowsToSpacesFunc func = nullptr;
1094   static bool lookedUpFunc = false;
1095   if (!lookedUpFunc) {
1096     func =
1097         (CGSAddWindowsToSpacesFunc)dlsym(RTLD_DEFAULT, "CGSAddWindowsToSpaces");
1098     lookedUpFunc = true;
1099   }
1100   return func;
1102 // Runtime `CGSRemoveWindowsFromSpaces` library function feature detection.
1103 typedef void (*CGSRemoveWindowsFromSpacesFunc)(CGSConnection cid,
1104                                                CFArrayRef windowIDs,
1105                                                CFArrayRef spaceIDs);
1106 static CGSRemoveWindowsFromSpacesFunc GetCGSRemoveWindowsFromSpacesFunc() {
1107   static CGSRemoveWindowsFromSpacesFunc func = nullptr;
1108   static bool lookedUpFunc = false;
1109   if (!lookedUpFunc) {
1110     func = (CGSRemoveWindowsFromSpacesFunc)dlsym(RTLD_DEFAULT,
1111                                                  "CGSRemoveWindowsFromSpaces");
1112     lookedUpFunc = true;
1113   }
1114   return func;
1117 void nsCocoaWindow::GetWorkspaceID(nsAString& workspaceID) {
1118   workspaceID.Truncate();
1119   int32_t sid = GetWorkspaceID();
1120   if (sid != 0) {
1121     workspaceID.AppendInt(sid);
1122   }
1125 int32_t nsCocoaWindow::GetWorkspaceID() {
1126   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1128   // Mac OSX space IDs start at '1' (default space), so '0' means 'unknown',
1129   // effectively.
1130   CGSSpaceID sid = 0;
1132   CGSCopySpacesForWindowsFunc CopySpacesForWindows =
1133       GetCGSCopySpacesForWindowsFunc();
1134   if (!CopySpacesForWindows) {
1135     return sid;
1136   }
1138   CGSConnection cid = _CGSDefaultConnection();
1139   // Fetch all spaces that this window belongs to (in order).
1140   NSArray<NSNumber*>* spaceIDs = CFBridgingRelease(CopySpacesForWindows(
1141       cid, kCGSAllSpacesMask,
1142       (__bridge CFArrayRef) @[ @([mWindow windowNumber]) ]));
1143   if ([spaceIDs count]) {
1144     // When spaces are found, return the first one.
1145     // We don't support a single window painted across multiple places for now.
1146     sid = [spaceIDs[0] integerValue];
1147   } else {
1148     // Fall back to the workspace that's currently active, which is '1' in the
1149     // common case.
1150     CGSGetActiveSpaceFunc GetActiveSpace = GetCGSGetActiveSpaceFunc();
1151     if (GetActiveSpace) {
1152       sid = GetActiveSpace(cid);
1153     }
1154   }
1156   return sid;
1158   NS_OBJC_END_TRY_IGNORE_BLOCK;
1161 void nsCocoaWindow::MoveToWorkspace(const nsAString& workspaceIDStr) {
1162   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1164   if ([NSScreen screensHaveSeparateSpaces] && [[NSScreen screens] count] > 1) {
1165     // We don't support moving to a workspace when the user has this option
1166     // enabled in Mission Control.
1167     return;
1168   }
1170   nsresult rv = NS_OK;
1171   int32_t workspaceID = workspaceIDStr.ToInteger(&rv);
1172   if (NS_FAILED(rv)) {
1173     return;
1174   }
1176   CGSConnection cid = _CGSDefaultConnection();
1177   int32_t currentSpace = GetWorkspaceID();
1178   // If an empty workspace ID is passed in (not valid on OSX), or when the
1179   // window is already on this workspace, we don't need to do anything.
1180   if (!workspaceID || workspaceID == currentSpace) {
1181     return;
1182   }
1184   CGSCopyManagedDisplaySpacesFunc CopyManagedDisplaySpaces =
1185       GetCGSCopyManagedDisplaySpacesFunc();
1186   CGSAddWindowsToSpacesFunc AddWindowsToSpaces = GetCGSAddWindowsToSpacesFunc();
1187   CGSRemoveWindowsFromSpacesFunc RemoveWindowsFromSpaces =
1188       GetCGSRemoveWindowsFromSpacesFunc();
1189   if (!CopyManagedDisplaySpaces || !AddWindowsToSpaces ||
1190       !RemoveWindowsFromSpaces) {
1191     return;
1192   }
1194   // Fetch an ordered list of all known spaces.
1195   NSArray* displaySpacesInfo = CFBridgingRelease(CopyManagedDisplaySpaces(cid));
1196   // When we found the space we're looking for, we can bail out of the loop
1197   // early, which this local variable is used for.
1198   BOOL found = false;
1199   for (NSDictionary<NSString*, id>* spacesInfo in displaySpacesInfo) {
1200     NSArray<NSNumber*>* sids =
1201         [spacesInfo[CGSSpacesKey] valueForKey:CGSSpaceIDKey];
1202     for (NSNumber* sid in sids) {
1203       // If we found our space in the list, we're good to go and can jump out of
1204       // this loop.
1205       if ((int)[sid integerValue] == workspaceID) {
1206         found = true;
1207         break;
1208       }
1209     }
1210     if (found) {
1211       break;
1212     }
1213   }
1215   // We were unable to find the space to correspond with the workspaceID as
1216   // requested, so let's bail out.
1217   if (!found) {
1218     return;
1219   }
1221   // First we add the window to the appropriate space.
1222   AddWindowsToSpaces(cid, (__bridge CFArrayRef) @[ @([mWindow windowNumber]) ],
1223                      (__bridge CFArrayRef) @[ @(workspaceID) ]);
1224   // Then we remove the window from the active space.
1225   RemoveWindowsFromSpaces(cid,
1226                           (__bridge CFArrayRef) @[ @([mWindow windowNumber]) ],
1227                           (__bridge CFArrayRef) @[ @(currentSpace) ]);
1229   NS_OBJC_END_TRY_IGNORE_BLOCK;
1232 void nsCocoaWindow::SuppressAnimation(bool aSuppress) {
1233   if ([mWindow respondsToSelector:@selector(setAnimationBehavior:)]) {
1234     mWindow.isAnimationSuppressed = aSuppress;
1235     mWindow.animationBehavior =
1236         aSuppress ? NSWindowAnimationBehaviorNone : mWindowAnimationBehavior;
1237   }
1240 // This has to preserve the window's frame bounds.
1241 // This method requires (as does the Windows impl.) that you call Resize shortly
1242 // after calling HideWindowChrome. See bug 498835 for fixing this.
1243 void nsCocoaWindow::HideWindowChrome(bool aShouldHide) {
1244   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1246   if (!mWindow || !mWindowMadeHere ||
1247       (mWindowType != WindowType::TopLevel &&
1248        mWindowType != WindowType::Dialog)) {
1249     return;
1250   }
1252   const BOOL isVisible = mWindow.isVisible;
1254   // Remove child windows.
1255   NSArray* childWindows = [mWindow childWindows];
1256   NSEnumerator* enumerator = [childWindows objectEnumerator];
1257   NSWindow* child = nil;
1258   while ((child = [enumerator nextObject])) {
1259     [mWindow removeChildWindow:child];
1260   }
1262   // Remove the views in the old window's content view.
1263   // The NSArray is autoreleased and retains its NSViews.
1264   NSArray<NSView*>* contentViewContents = [mWindow contentViewContents];
1265   for (NSView* view in contentViewContents) {
1266     [view removeFromSuperviewWithoutNeedingDisplay];
1267   }
1269   // Save state (like window title).
1270   NSMutableDictionary* state = [mWindow exportState];
1272   // Recreate the window with the right border style.
1273   NSRect frameRect = mWindow.frame;
1274   DestroyNativeWindow();
1275   nsresult rv = CreateNativeWindow(
1276       frameRect, aShouldHide ? BorderStyle::None : mBorderStyle, true,
1277       mWindow.restorable);
1278   NS_ENSURE_SUCCESS_VOID(rv);
1280   // Re-import state.
1281   [mWindow importState:state];
1283   // Add the old content view subviews to the new window's content view.
1284   for (NSView* view in contentViewContents) {
1285     [[mWindow contentView] addSubview:view];
1286   }
1288   // Reparent child windows.
1289   enumerator = [childWindows objectEnumerator];
1290   while ((child = [enumerator nextObject])) {
1291     [mWindow addChildWindow:child ordered:NSWindowAbove];
1292   }
1294   // Show the new window.
1295   if (isVisible) {
1296     bool wasAnimationSuppressed = mIsAnimationSuppressed;
1297     mIsAnimationSuppressed = true;
1298     Show(true);
1299     mIsAnimationSuppressed = wasAnimationSuppressed;
1300   }
1302   NS_OBJC_END_TRY_IGNORE_BLOCK;
1305 class FullscreenTransitionData : public nsISupports {
1306  public:
1307   NS_DECL_ISUPPORTS
1309   explicit FullscreenTransitionData(NSWindow* aWindow)
1310       : mTransitionWindow(aWindow) {}
1312   NSWindow* mTransitionWindow;
1314  private:
1315   virtual ~FullscreenTransitionData() { [mTransitionWindow close]; }
1318 NS_IMPL_ISUPPORTS0(FullscreenTransitionData)
1320 @interface FullscreenTransitionDelegate : NSObject <NSAnimationDelegate> {
1321  @public
1322   nsCocoaWindow* mWindow;
1323   nsIRunnable* mCallback;
1325 @end
1327 @implementation FullscreenTransitionDelegate
1328 - (void)cleanupAndDispatch:(NSAnimation*)animation {
1329   [animation setDelegate:nil];
1330   [self autorelease];
1331   // The caller should have added ref for us.
1332   NS_DispatchToMainThread(already_AddRefed<nsIRunnable>(mCallback));
1335 - (void)animationDidEnd:(NSAnimation*)animation {
1336   MOZ_ASSERT(animation == mWindow->FullscreenTransitionAnimation(),
1337              "Should be handling the only animation on the window");
1338   mWindow->ReleaseFullscreenTransitionAnimation();
1339   [self cleanupAndDispatch:animation];
1342 - (void)animationDidStop:(NSAnimation*)animation {
1343   [self cleanupAndDispatch:animation];
1345 @end
1347 static bool AlwaysUsesNativeFullScreen() {
1348   return Preferences::GetBool("full-screen-api.macos-native-full-screen",
1349                               false);
1352 /* virtual */ bool nsCocoaWindow::PrepareForFullscreenTransition(
1353     nsISupports** aData) {
1354   if (AlwaysUsesNativeFullScreen()) {
1355     return false;
1356   }
1358   // Our fullscreen transition creates a new window occluding this window.
1359   // That triggers an occlusion event which can cause DOM fullscreen requests
1360   // to fail due to the context not being focused at the time the focus check
1361   // is performed in the child process. Until the transition is cleaned up in
1362   // CleanupFullscreenTransition(), ignore occlusion events for this window.
1363   // If this method is changed to return false, the transition will not be
1364   // performed and mIgnoreOcclusionCount should not be incremented.
1365   MOZ_ASSERT(mIgnoreOcclusionCount >= 0);
1366   mIgnoreOcclusionCount++;
1368   nsCOMPtr<nsIScreen> widgetScreen = GetWidgetScreen();
1369   NSScreen* cocoaScreen = ScreenHelperCocoa::CocoaScreenForScreen(widgetScreen);
1371   NSWindow* win =
1372       [[NSWindow alloc] initWithContentRect:cocoaScreen.frame
1373                                   styleMask:NSWindowStyleMaskBorderless
1374                                     backing:NSBackingStoreBuffered
1375                                       defer:YES];
1376   [win setBackgroundColor:[NSColor blackColor]];
1377   [win setAlphaValue:0];
1378   [win setIgnoresMouseEvents:YES];
1379   [win setLevel:NSScreenSaverWindowLevel];
1380   [win makeKeyAndOrderFront:nil];
1382   auto data = new FullscreenTransitionData(win);
1383   *aData = data;
1384   NS_ADDREF(data);
1385   return true;
1388 /* virtual */ void nsCocoaWindow::CleanupFullscreenTransition() {
1389   MOZ_ASSERT(mIgnoreOcclusionCount > 0);
1390   mIgnoreOcclusionCount--;
1393 /* virtual */ void nsCocoaWindow::PerformFullscreenTransition(
1394     FullscreenTransitionStage aStage, uint16_t aDuration, nsISupports* aData,
1395     nsIRunnable* aCallback) {
1396   auto data = static_cast<FullscreenTransitionData*>(aData);
1397   FullscreenTransitionDelegate* delegate =
1398       [[FullscreenTransitionDelegate alloc] init];
1399   delegate->mWindow = this;
1400   // Storing already_AddRefed directly could cause static checking fail.
1401   delegate->mCallback = nsCOMPtr<nsIRunnable>(aCallback).forget().take();
1403   if (mFullscreenTransitionAnimation) {
1404     [mFullscreenTransitionAnimation stopAnimation];
1405     ReleaseFullscreenTransitionAnimation();
1406   }
1408   NSDictionary* dict = @{
1409     NSViewAnimationTargetKey : data->mTransitionWindow,
1410     NSViewAnimationEffectKey : aStage == eBeforeFullscreenToggle
1411         ? NSViewAnimationFadeInEffect
1412         : NSViewAnimationFadeOutEffect
1413   };
1414   mFullscreenTransitionAnimation =
1415       [[NSViewAnimation alloc] initWithViewAnimations:@[ dict ]];
1416   [mFullscreenTransitionAnimation setDelegate:delegate];
1417   [mFullscreenTransitionAnimation setDuration:aDuration / 1000.0];
1418   [mFullscreenTransitionAnimation startAnimation];
1421 void nsCocoaWindow::CocoaWindowWillEnterFullscreen(bool aFullscreen) {
1422   MOZ_ASSERT(mUpdateFullscreenOnResize.isNothing());
1424   mHasStartedNativeFullscreen = true;
1426   // Ensure that we update our fullscreen state as early as possible, when the
1427   // resize happens.
1428   mUpdateFullscreenOnResize =
1429       Some(aFullscreen ? TransitionType::Fullscreen : TransitionType::Windowed);
1432 void nsCocoaWindow::CocoaWindowDidEnterFullscreen(bool aFullscreen) {
1433   EndOurNativeTransition();
1434   mHasStartedNativeFullscreen = false;
1435   DispatchOcclusionEvent();
1437   // Check if aFullscreen matches our expected fullscreen state. It might not if
1438   // there was a failure somewhere along the way, in which case we'll recover
1439   // from that.
1440   bool receivedExpectedFullscreen = false;
1441   if (mUpdateFullscreenOnResize.isSome()) {
1442     bool expectingFullscreen =
1443         (*mUpdateFullscreenOnResize == TransitionType::Fullscreen);
1444     receivedExpectedFullscreen = (expectingFullscreen == aFullscreen);
1445   } else {
1446     receivedExpectedFullscreen = (mInFullScreenMode == aFullscreen);
1447   }
1449   TransitionType transition =
1450       aFullscreen ? TransitionType::Fullscreen : TransitionType::Windowed;
1451   if (receivedExpectedFullscreen) {
1452     // Everything is as expected. Update our state if needed.
1453     HandleUpdateFullscreenOnResize();
1454   } else {
1455     // We weren't expecting this fullscreen state. Update our fullscreen state
1456     // to the new reality.
1457     UpdateFullscreenState(aFullscreen, true);
1459     // If we have a current transition, switch it to match what we just did.
1460     if (mTransitionCurrent.isSome()) {
1461       mTransitionCurrent = Some(transition);
1462     }
1463   }
1465   // Whether we expected this transition or not, we're ready to finish it.
1466   FinishCurrentTransitionIfMatching(transition);
1469 void nsCocoaWindow::UpdateFullscreenState(bool aFullScreen, bool aNativeMode) {
1470   bool wasInFullscreen = mInFullScreenMode;
1471   mInFullScreenMode = aFullScreen;
1472   if (aNativeMode || mInNativeFullScreenMode) {
1473     mInNativeFullScreenMode = aFullScreen;
1474   }
1476   if (aFullScreen == wasInFullscreen) {
1477     return;
1478   }
1480   DispatchSizeModeEvent();
1482   // Notify the mainChildView with our new fullscreen state.
1483   nsChildView* mainChildView =
1484       static_cast<nsChildView*>([[mWindow mainChildView] widget]);
1485   if (mainChildView) {
1486     mainChildView->UpdateFullscreen(aFullScreen);
1487   }
1490 nsresult nsCocoaWindow::MakeFullScreen(bool aFullScreen) {
1491   return DoMakeFullScreen(aFullScreen, AlwaysUsesNativeFullScreen());
1494 nsresult nsCocoaWindow::MakeFullScreenWithNativeTransition(bool aFullScreen) {
1495   return DoMakeFullScreen(aFullScreen, true);
1498 nsresult nsCocoaWindow::DoMakeFullScreen(bool aFullScreen,
1499                                          bool aUseSystemTransition) {
1500   if (!mWindow) {
1501     return NS_OK;
1502   }
1504   // Figure out what type of transition is being requested.
1505   TransitionType transition = TransitionType::Windowed;
1506   if (aFullScreen) {
1507     // Decide whether to use fullscreen or emulated fullscreen.
1508     transition =
1509         (aUseSystemTransition && (mWindow.collectionBehavior &
1510                                   NSWindowCollectionBehaviorFullScreenPrimary))
1511             ? TransitionType::Fullscreen
1512             : TransitionType::EmulatedFullscreen;
1513   }
1515   QueueTransition(transition);
1516   return NS_OK;
1519 void nsCocoaWindow::QueueTransition(const TransitionType& aTransition) {
1520   mTransitionsPending.push(aTransition);
1521   ProcessTransitions();
1524 void nsCocoaWindow::ProcessTransitions() {
1525   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK
1527   if (mInProcessTransitions) {
1528     return;
1529   }
1531   mInProcessTransitions = true;
1533   // Start a loop that will continue as long as we have transitions to process
1534   // and we aren't waiting on an asynchronous transition to complete. Any
1535   // transition that starts something async will `continue` this loop to exit.
1536   while (!mTransitionsPending.empty() && !IsInTransition()) {
1537     TransitionType nextTransition = mTransitionsPending.front();
1539     // We have to check for some incompatible transition states, and if we find
1540     // one, instead perform an alternative transition and leave the queue
1541     // untouched. If we add one of these transitions, we set
1542     // mIsTransitionCurrentAdded because we don't want to confuse listeners who
1543     // are expecting to receive exactly one event when the requested transition
1544     // has completed.
1545     switch (nextTransition) {
1546       case TransitionType::Fullscreen:
1547       case TransitionType::EmulatedFullscreen:
1548       case TransitionType::Windowed:
1549       case TransitionType::Zoom:
1550         // These can't handle miniaturized windows, so deminiaturize first.
1551         if (mWindow.miniaturized) {
1552           mTransitionCurrent = Some(TransitionType::Deminiaturize);
1553           mIsTransitionCurrentAdded = true;
1554         }
1555         break;
1556       case TransitionType::Miniaturize:
1557         // This can't handle fullscreen, so go to windowed first.
1558         if (mInFullScreenMode) {
1559           mTransitionCurrent = Some(TransitionType::Windowed);
1560           mIsTransitionCurrentAdded = true;
1561         }
1562         break;
1563       default:
1564         break;
1565     }
1567     // If mTransitionCurrent is still empty, then we use the nextTransition and
1568     // pop the queue.
1569     if (mTransitionCurrent.isNothing()) {
1570       mTransitionCurrent = Some(nextTransition);
1571       mTransitionsPending.pop();
1572     }
1574     switch (*mTransitionCurrent) {
1575       case TransitionType::Fullscreen: {
1576         if (!mInFullScreenMode) {
1577           // Run a local run loop until it is safe to start a native fullscreen
1578           // transition.
1579           NSRunLoop* localRunLoop = [NSRunLoop currentRunLoop];
1580           while (mWindow && !CanStartNativeTransition() &&
1581                  [localRunLoop runMode:NSDefaultRunLoopMode
1582                             beforeDate:[NSDate distantFuture]]) {
1583             // This loop continues to process events until
1584             // CanStartNativeTransition() returns true or our native
1585             // window has been destroyed.
1586           }
1588           // This triggers an async animation, so continue.
1589           [mWindow toggleFullScreen:nil];
1590           continue;
1591         }
1592         break;
1593       }
1595       case TransitionType::EmulatedFullscreen: {
1596         if (!mInFullScreenMode) {
1597           NSDisableScreenUpdates();
1598           mSuppressSizeModeEvents = true;
1599           // The order here matters. When we exit full screen mode, we need to
1600           // show the Dock first, otherwise the newly-created window won't have
1601           // its minimize button enabled. See bug 526282.
1602           nsCocoaUtils::HideOSChromeOnScreen(true);
1603           nsBaseWidget::InfallibleMakeFullScreen(true);
1604           mSuppressSizeModeEvents = false;
1605           NSEnableScreenUpdates();
1606           UpdateFullscreenState(true, false);
1607         }
1608         break;
1609       }
1611       case TransitionType::Windowed: {
1612         if (mInFullScreenMode) {
1613           if (mInNativeFullScreenMode) {
1614             // Run a local run loop until it is safe to start a native
1615             // fullscreen transition.
1616             NSRunLoop* localRunLoop = [NSRunLoop currentRunLoop];
1617             while (mWindow && !CanStartNativeTransition() &&
1618                    [localRunLoop runMode:NSDefaultRunLoopMode
1619                               beforeDate:[NSDate distantFuture]]) {
1620               // This loop continues to process events until
1621               // CanStartNativeTransition() returns true or our native
1622               // window has been destroyed.
1623             }
1625             // This triggers an async animation, so continue.
1626             [mWindow toggleFullScreen:nil];
1627             continue;
1628           } else {
1629             NSDisableScreenUpdates();
1630             mSuppressSizeModeEvents = true;
1631             // The order here matters. When we exit full screen mode, we need to
1632             // show the Dock first, otherwise the newly-created window won't
1633             // have its minimize button enabled. See bug 526282.
1634             nsCocoaUtils::HideOSChromeOnScreen(false);
1635             nsBaseWidget::InfallibleMakeFullScreen(false);
1636             mSuppressSizeModeEvents = false;
1637             NSEnableScreenUpdates();
1638             UpdateFullscreenState(false, false);
1639           }
1640         } else if (mWindow.zoomed) {
1641           [mWindow zoom:nil];
1643           // Check if we're still zoomed. If we are, we need to do *something*
1644           // to make the window smaller than the zoom size so Cocoa will treat
1645           // us as being out of the zoomed state. Otherwise, we could stay
1646           // zoomed and never be able to be "normal" from calls to SetSizeMode.
1647           if (mWindow.zoomed) {
1648             NSRect maximumFrame = mWindow.frame;
1649             const CGFloat INSET_OUT_OF_ZOOM = 20.0f;
1650             [mWindow setFrame:NSInsetRect(maximumFrame, INSET_OUT_OF_ZOOM,
1651                                           INSET_OUT_OF_ZOOM)
1652                       display:YES];
1653             MOZ_ASSERT(
1654                 !mWindow.zoomed,
1655                 "We should be able to unzoom by shrinking the frame a bit.");
1656           }
1657         }
1658         break;
1659       }
1661       case TransitionType::Miniaturize:
1662         if (!mWindow.miniaturized) {
1663           // This triggers an async animation, so continue.
1664           [mWindow miniaturize:nil];
1665           continue;
1666         }
1667         break;
1669       case TransitionType::Deminiaturize:
1670         if (mWindow.miniaturized) {
1671           // This triggers an async animation, so continue.
1672           [mWindow deminiaturize:nil];
1673           continue;
1674         }
1675         break;
1677       case TransitionType::Zoom:
1678         if (!mWindow.zoomed) {
1679           [mWindow zoom:nil];
1680         }
1681         break;
1683       default:
1684         break;
1685     }
1687     mTransitionCurrent.reset();
1688     mIsTransitionCurrentAdded = false;
1689   }
1691   mInProcessTransitions = false;
1693   // When we finish processing transitions, dispatch a size mode event to cover
1694   // the cases where an inserted transition suppressed one, and the original
1695   // transition never sent one because it detected it was at the desired state
1696   // when it ran. If we've already sent a size mode event, then this will be a
1697   // no-op.
1698   if (!IsInTransition()) {
1699     DispatchSizeModeEvent();
1700   }
1702   NS_OBJC_END_TRY_IGNORE_BLOCK;
1705 void nsCocoaWindow::CancelAllTransitions() {
1706   // Clear our current and pending transitions. This simplifies our
1707   // reasoning about what happens next, and ensures that whatever is
1708   // currently happening won't trigger another call to
1709   // ProcessTransitions().
1710   mTransitionCurrent.reset();
1711   mIsTransitionCurrentAdded = false;
1712   std::queue<TransitionType>().swap(mTransitionsPending);
1715 void nsCocoaWindow::FinishCurrentTransitionIfMatching(
1716     const TransitionType& aTransition) {
1717   // We've just finished some transition activity, and we're not sure whether it
1718   // was triggered programmatically, or by the user. If it matches our current
1719   // transition, then assume it was triggered programmatically and we can clean
1720   // up that transition and start processing transitions again.
1722   // Whether programmatic or user-initiated, we send out a size mode event.
1723   DispatchSizeModeEvent();
1725   if (mTransitionCurrent.isSome() && (*mTransitionCurrent == aTransition)) {
1726     // This matches our current transition, so do the safe parts of transition
1727     // cleanup.
1728     mTransitionCurrent.reset();
1729     mIsTransitionCurrentAdded = false;
1731     // Since this function is called from nsWindowDelegate transition callbacks,
1732     // we want to make sure those callbacks are all the way done before we
1733     // continue processing more transitions. To accomplish this, we dispatch
1734     // ProcessTransitions on the next event loop. Doing this will ensure that
1735     // any async native transition methods we call (like toggleFullScreen) will
1736     // succeed.
1737     if (!mTransitionsPending.empty()) {
1738       NS_DispatchToCurrentThread(NewRunnableMethod(
1739           "FinishCurrentTransition", this, &nsCocoaWindow::ProcessTransitions));
1740     }
1741   }
1744 bool nsCocoaWindow::HandleUpdateFullscreenOnResize() {
1745   if (mUpdateFullscreenOnResize.isNothing()) {
1746     return false;
1747   }
1749   bool toFullscreen =
1750       (*mUpdateFullscreenOnResize == TransitionType::Fullscreen);
1751   mUpdateFullscreenOnResize.reset();
1752   UpdateFullscreenState(toFullscreen, true);
1754   return true;
1757 /* static */ nsCocoaWindow* nsCocoaWindow::sWindowInNativeTransition(nullptr);
1759 bool nsCocoaWindow::CanStartNativeTransition() {
1760   if (sWindowInNativeTransition == nullptr) {
1761     // Claim it and return true, indicating that the caller has permission to
1762     // start the native fullscreen transition.
1763     sWindowInNativeTransition = this;
1764     return true;
1765   }
1766   return false;
1769 void nsCocoaWindow::EndOurNativeTransition() {
1770   if (sWindowInNativeTransition == this) {
1771     sWindowInNativeTransition = nullptr;
1772   }
1775 // Coordinates are desktop pixels
1776 void nsCocoaWindow::DoResize(double aX, double aY, double aWidth,
1777                              double aHeight, bool aRepaint,
1778                              bool aConstrainToCurrentScreen) {
1779   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1781   if (!mWindow || mInResize) {
1782     return;
1783   }
1785   // We are able to resize a window outside of any aspect ratio contraints
1786   // applied to it, but in order to "update" the aspect ratio contraint to the
1787   // new window dimensions, we must re-lock the aspect ratio.
1788   auto relockAspectRatio = MakeScopeExit([&]() {
1789     if (mAspectRatioLocked) {
1790       LockAspectRatio(true);
1791     }
1792   });
1794   AutoRestore<bool> reentrantResizeGuard(mInResize);
1795   mInResize = true;
1797   CGFloat scale = mSizeConstraints.mScale.scale;
1798   if (scale == MOZ_WIDGET_INVALID_SCALE) {
1799     scale = BackingScaleFactor();
1800   }
1802   // mSizeConstraints is in device pixels.
1803   int32_t width = NSToIntRound(aWidth * scale);
1804   int32_t height = NSToIntRound(aHeight * scale);
1806   width = std::max(mSizeConstraints.mMinSize.width,
1807                    std::min(mSizeConstraints.mMaxSize.width, width));
1808   height = std::max(mSizeConstraints.mMinSize.height,
1809                     std::min(mSizeConstraints.mMaxSize.height, height));
1811   DesktopIntRect newBounds(NSToIntRound(aX), NSToIntRound(aY),
1812                            NSToIntRound(width / scale),
1813                            NSToIntRound(height / scale));
1815   // convert requested bounds into Cocoa coordinate system
1816   NSRect newFrame = nsCocoaUtils::GeckoRectToCocoaRect(newBounds);
1818   NSRect frame = mWindow.frame;
1819   BOOL isMoving = newFrame.origin.x != frame.origin.x ||
1820                   newFrame.origin.y != frame.origin.y;
1821   BOOL isResizing = newFrame.size.width != frame.size.width ||
1822                     newFrame.size.height != frame.size.height;
1824   if (!isMoving && !isResizing) {
1825     return;
1826   }
1828   // We ignore aRepaint -- we have to call display:YES, otherwise the
1829   // title bar doesn't immediately get repainted and is displayed in
1830   // the wrong place, leading to a visual jump.
1831   [mWindow setFrame:newFrame display:YES];
1833   NS_OBJC_END_TRY_IGNORE_BLOCK;
1836 // Coordinates are desktop pixels
1837 void nsCocoaWindow::Resize(double aX, double aY, double aWidth, double aHeight,
1838                            bool aRepaint) {
1839   DoResize(aX, aY, aWidth, aHeight, aRepaint, false);
1842 // Coordinates are desktop pixels
1843 void nsCocoaWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
1844   double invScale = 1.0 / BackingScaleFactor();
1845   DoResize(mBounds.x * invScale, mBounds.y * invScale, aWidth, aHeight,
1846            aRepaint, true);
1849 // Return the area that the Gecko ChildView in our window should cover, as an
1850 // NSRect in screen coordinates (with 0,0 being the bottom left corner of the
1851 // primary screen).
1852 NSRect nsCocoaWindow::GetClientCocoaRect() {
1853   if (!mWindow) {
1854     return NSZeroRect;
1855   }
1857   return [mWindow childViewRectForFrameRect:mWindow.frame];
1860 LayoutDeviceIntRect nsCocoaWindow::GetClientBounds() {
1861   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1863   CGFloat scaleFactor = BackingScaleFactor();
1864   return nsCocoaUtils::CocoaRectToGeckoRectDevPix(GetClientCocoaRect(),
1865                                                   scaleFactor);
1867   NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntRect(0, 0, 0, 0));
1870 void nsCocoaWindow::UpdateBounds() {
1871   NSRect frame = NSZeroRect;
1872   if (mWindow) {
1873     frame = mWindow.frame;
1874   }
1875   mBounds =
1876       nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
1878   if (mPopupContentView) {
1879     mPopupContentView->UpdateBoundsFromView();
1880   }
1883 LayoutDeviceIntRect nsCocoaWindow::GetScreenBounds() {
1884   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1886 #ifdef DEBUG
1887   LayoutDeviceIntRect r = nsCocoaUtils::CocoaRectToGeckoRectDevPix(
1888       mWindow.frame, BackingScaleFactor());
1889   NS_ASSERTION(mWindow && mBounds == r, "mBounds out of sync!");
1890 #endif
1892   return mBounds;
1894   NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntRect(0, 0, 0, 0));
1897 double nsCocoaWindow::GetDefaultScaleInternal() { return BackingScaleFactor(); }
1899 static CGFloat GetBackingScaleFactor(NSWindow* aWindow) {
1900   NSRect frame = aWindow.frame;
1901   if (frame.size.width > 0 && frame.size.height > 0) {
1902     return nsCocoaUtils::GetBackingScaleFactor(aWindow);
1903   }
1905   // For windows with zero width or height, the backingScaleFactor method
1906   // is broken - it will always return 2 on a retina macbook, even when
1907   // the window position implies it's on a non-hidpi external display
1908   // (to the extent that a zero-area window can be said to be "on" a
1909   // display at all!)
1910   // And to make matters worse, Cocoa even fires a
1911   // windowDidChangeBackingProperties notification with the
1912   // NSBackingPropertyOldScaleFactorKey key when a window on an
1913   // external display is resized to/from zero height, even though it hasn't
1914   // really changed screens.
1916   // This causes us to handle popup window sizing incorrectly when the
1917   // popup is resized to zero height (bug 820327) - nsXULPopupManager
1918   // becomes (incorrectly) convinced the popup has been explicitly forced
1919   // to a non-default size and needs to have size attributes attached.
1921   // Workaround: instead of asking the window, we'll find the screen it is on
1922   // and ask that for *its* backing scale factor.
1924   // (See bug 853252 and additional comments in windowDidChangeScreen: below
1925   // for further complications this causes.)
1927   // First, expand the rect so that it actually has a measurable area,
1928   // for FindTargetScreenForRect to use.
1929   if (frame.size.width == 0) {
1930     frame.size.width = 1;
1931   }
1932   if (frame.size.height == 0) {
1933     frame.size.height = 1;
1934   }
1936   // Then identify the screen it belongs to, and return its scale factor.
1937   NSScreen* screen =
1938       FindTargetScreenForRect(nsCocoaUtils::CocoaRectToGeckoRect(frame));
1939   return nsCocoaUtils::GetBackingScaleFactor(screen);
1942 CGFloat nsCocoaWindow::BackingScaleFactor() {
1943   if (mBackingScaleFactor > 0.0) {
1944     return mBackingScaleFactor;
1945   }
1946   if (!mWindow) {
1947     return 1.0;
1948   }
1949   mBackingScaleFactor = GetBackingScaleFactor(mWindow);
1950   return mBackingScaleFactor;
1953 void nsCocoaWindow::BackingScaleFactorChanged() {
1954   CGFloat newScale = GetBackingScaleFactor(mWindow);
1956   // ignore notification if it hasn't really changed (or maybe we have
1957   // disabled HiDPI mode via prefs)
1958   if (mBackingScaleFactor == newScale) {
1959     return;
1960   }
1962   mBackingScaleFactor = newScale;
1963   NotifyAPZOfDPIChange();
1965   if (!mWidgetListener || mWidgetListener->GetAppWindow()) {
1966     return;
1967   }
1969   if (PresShell* presShell = mWidgetListener->GetPresShell()) {
1970     presShell->BackingScaleFactorChanged();
1971   }
1972   mWidgetListener->UIResolutionChanged();
1975 int32_t nsCocoaWindow::RoundsWidgetCoordinatesTo() {
1976   if (BackingScaleFactor() == 2.0) {
1977     return 2;
1978   }
1979   return 1;
1982 void nsCocoaWindow::SetCursor(const Cursor& aCursor) {
1983   if (mPopupContentView) {
1984     mPopupContentView->SetCursor(aCursor);
1985   }
1988 nsresult nsCocoaWindow::SetTitle(const nsAString& aTitle) {
1989   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1991   if (!mWindow) {
1992     return NS_OK;
1993   }
1995   const nsString& strTitle = PromiseFlatString(aTitle);
1996   const unichar* uniTitle = reinterpret_cast<const unichar*>(strTitle.get());
1997   NSString* title = [NSString stringWithCharacters:uniTitle
1998                                             length:strTitle.Length()];
1999   if (mWindow.drawsContentsIntoWindowFrame && !mWindow.wantsTitleDrawn) {
2000     // Don't cause invalidations when the title isn't displayed.
2001     [mWindow disableSetNeedsDisplay];
2002     [mWindow setTitle:title];
2003     [mWindow enableSetNeedsDisplay];
2004   } else {
2005     [mWindow setTitle:title];
2006   }
2008   return NS_OK;
2010   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2013 void nsCocoaWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
2014   if (mPopupContentView) {
2015     mPopupContentView->Invalidate(aRect);
2016   }
2019 // Pass notification of some drag event to Gecko
2021 // The drag manager has let us know that something related to a drag has
2022 // occurred in this window. It could be any number of things, ranging from
2023 // a drop, to a drag enter/leave, or a drag over event. The actual event
2024 // is passed in |aMessage| and is passed along to our event hanlder so Gecko
2025 // knows about it.
2026 bool nsCocoaWindow::DragEvent(unsigned int aMessage,
2027                               mozilla::gfx::Point aMouseGlobal,
2028                               UInt16 aKeyModifiers) {
2029   return false;
2032 void nsCocoaWindow::SendSetZLevelEvent() {
2033   if (mWidgetListener) {
2034     nsWindowZ placement = nsWindowZTop;
2035     nsCOMPtr<nsIWidget> actualBelow;
2036     mWidgetListener->ZLevelChanged(true, &placement, nullptr,
2037                                    getter_AddRefs(actualBelow));
2038   }
2041 // Invokes callback and ProcessEvent methods on Event Listener object
2042 nsresult nsCocoaWindow::DispatchEvent(WidgetGUIEvent* event,
2043                                       nsEventStatus& aStatus) {
2044   aStatus = nsEventStatus_eIgnore;
2046   nsCOMPtr<nsIWidget> kungFuDeathGrip(event->mWidget);
2047   mozilla::Unused << kungFuDeathGrip;  // Not used within this function
2049   if (mWidgetListener) {
2050     aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents);
2051   }
2053   return NS_OK;
2056 // aFullScreen should be the window's mInFullScreenMode. We don't have access to
2057 // that from here, so we need to pass it in. mInFullScreenMode should be the
2058 // canonical indicator that a window is currently full screen and it makes sense
2059 // to keep all sizemode logic here.
2060 static nsSizeMode GetWindowSizeMode(NSWindow* aWindow, bool aFullScreen) {
2061   if (aFullScreen) {
2062     return nsSizeMode_Fullscreen;
2063   }
2064   if (aWindow.isMiniaturized) {
2065     return nsSizeMode_Minimized;
2066   }
2067   if ((aWindow.styleMask & NSWindowStyleMaskResizable) && aWindow.isZoomed) {
2068     return nsSizeMode_Maximized;
2069   }
2070   return nsSizeMode_Normal;
2073 void nsCocoaWindow::ReportMoveEvent() {
2074   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2076   // Prevent recursion, which can become infinite (see bug 708278).  This
2077   // can happen when the call to [NSWindow setFrameTopLeftPoint:] in
2078   // nsCocoaWindow::Move() triggers an immediate NSWindowDidMove notification
2079   // (and a call to [WindowDelegate windowDidMove:]).
2080   if (mInReportMoveEvent) {
2081     return;
2082   }
2083   mInReportMoveEvent = true;
2085   UpdateBounds();
2087   // The zoomed state can change when we're moving, in which case we need to
2088   // update our internal mSizeMode. This can happen either if we're maximized
2089   // and then moved, or if we're not maximized and moved back to zoomed state.
2090   if (mWindow && (mSizeMode == nsSizeMode_Maximized) ^ mWindow.isZoomed) {
2091     DispatchSizeModeEvent();
2092   }
2094   // Dispatch the move event to Gecko
2095   NotifyWindowMoved(mBounds.x, mBounds.y);
2097   mInReportMoveEvent = false;
2099   NS_OBJC_END_TRY_IGNORE_BLOCK;
2102 void nsCocoaWindow::DispatchSizeModeEvent() {
2103   if (!mWindow) {
2104     return;
2105   }
2107   if (mSuppressSizeModeEvents || mIsTransitionCurrentAdded) {
2108     return;
2109   }
2111   nsSizeMode newMode = GetWindowSizeMode(mWindow, mInFullScreenMode);
2112   if (mSizeMode == newMode) {
2113     return;
2114   }
2116   mSizeMode = newMode;
2117   if (mWidgetListener) {
2118     mWidgetListener->SizeModeChanged(newMode);
2119   }
2122 void nsCocoaWindow::DispatchOcclusionEvent() {
2123   if (!mWindow) {
2124     return;
2125   }
2127   // Our new occlusion state is true if the window is not visible.
2128   bool newOcclusionState =
2129       !(mHasStartedNativeFullscreen ||
2130         ([mWindow occlusionState] & NSWindowOcclusionStateVisible));
2132   // Don't dispatch if the new occlustion state is the same as the current
2133   // state.
2134   if (mIsFullyOccluded == newOcclusionState) {
2135     return;
2136   }
2138   MOZ_ASSERT(mIgnoreOcclusionCount >= 0);
2139   if (newOcclusionState && mIgnoreOcclusionCount > 0) {
2140     return;
2141   }
2143   mIsFullyOccluded = newOcclusionState;
2144   if (mWidgetListener) {
2145     mWidgetListener->OcclusionStateChanged(mIsFullyOccluded);
2146   }
2149 void nsCocoaWindow::ReportSizeEvent() {
2150   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2152   UpdateBounds();
2153   if (mWidgetListener) {
2154     LayoutDeviceIntRect innerBounds = GetClientBounds();
2155     mWidgetListener->WindowResized(this, innerBounds.width, innerBounds.height);
2156   }
2158   NS_OBJC_END_TRY_IGNORE_BLOCK;
2161 void nsCocoaWindow::SetMenuBar(RefPtr<nsMenuBarX>&& aMenuBar) {
2162   if (!mWindow) {
2163     mMenuBar = nullptr;
2164     return;
2165   }
2166   mMenuBar = std::move(aMenuBar);
2168   // Only paint for active windows, or paint the hidden window menu bar if no
2169   // other menu bar has been painted yet so that some reasonable menu bar is
2170   // displayed when the app starts up.
2171   if (mMenuBar && ((!gSomeMenuBarPainted &&
2172                     nsMenuUtilsX::GetHiddenWindowMenuBar() == mMenuBar) ||
2173                    mWindow.isMainWindow)) {
2174     mMenuBar->Paint();
2175   }
2178 void nsCocoaWindow::SetFocus(Raise aRaise,
2179                              mozilla::dom::CallerType aCallerType) {
2180   if (!mWindow) return;
2182   if (mPopupContentView) {
2183     return mPopupContentView->SetFocus(aRaise, aCallerType);
2184   }
2186   if (aRaise == Raise::Yes && (mWindow.isVisible || mWindow.isMiniaturized)) {
2187     if (mWindow.isMiniaturized) {
2188       [mWindow deminiaturize:nil];
2189     }
2190     [mWindow makeKeyAndOrderFront:nil];
2191     SendSetZLevelEvent();
2192   }
2195 LayoutDeviceIntPoint nsCocoaWindow::WidgetToScreenOffset() {
2196   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2198   return nsCocoaUtils::CocoaRectToGeckoRectDevPix(GetClientCocoaRect(),
2199                                                   BackingScaleFactor())
2200       .TopLeft();
2202   NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0));
2205 LayoutDeviceIntPoint nsCocoaWindow::GetClientOffset() {
2206   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2208   LayoutDeviceIntRect clientRect = GetClientBounds();
2210   return clientRect.TopLeft() - mBounds.TopLeft();
2212   NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0));
2215 LayoutDeviceIntMargin nsCocoaWindow::ClientToWindowMargin() {
2216   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2218   if (!mWindow || mWindow.drawsContentsIntoWindowFrame ||
2219       mWindowType == WindowType::Popup) {
2220     return {};
2221   }
2223   NSRect clientNSRect = mWindow.contentLayoutRect;
2224   NSRect frameNSRect = [mWindow frameRectForChildViewRect:clientNSRect];
2226   CGFloat backingScale = BackingScaleFactor();
2227   const auto clientRect =
2228       nsCocoaUtils::CocoaRectToGeckoRectDevPix(clientNSRect, backingScale);
2229   const auto frameRect =
2230       nsCocoaUtils::CocoaRectToGeckoRectDevPix(frameNSRect, backingScale);
2232   return frameRect - clientRect;
2234   NS_OBJC_END_TRY_BLOCK_RETURN({});
2237 nsMenuBarX* nsCocoaWindow::GetMenuBar() { return mMenuBar; }
2239 void nsCocoaWindow::CaptureRollupEvents(bool aDoCapture) {
2240   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2242   if (aDoCapture) {
2243     if (!NSApp.isActive) {
2244       // We need to capture mouse event if we aren't
2245       // the active application. We only set this up when needed
2246       // because they cause spurious mouse event after crash
2247       // and gdb sessions. See bug 699538.
2248       nsToolkit::GetToolkit()->MonitorAllProcessMouseEvents();
2249     }
2251     // Sometimes more than one popup window can be visible at the same time
2252     // (e.g. nested non-native context menus, or the test case (attachment
2253     // 276885) for bmo bug 392389, which displays a non-native combo-box in a
2254     // non-native popup window).  In these cases the "active" popup window
2255     // should be the topmost -- the (nested) context menu the mouse is currently
2256     // over, or the combo-box's drop-down list (when it's displayed).  But
2257     // (among windows that have the same "level") OS X makes topmost the window
2258     // that last received a mouse-down event, which may be incorrect (in the
2259     // combo-box case, it makes topmost the window containing the combo-box).
2260     // So here we fiddle with a non-native popup window's level to make sure the
2261     // "active" one is always above any other non-native popup windows that
2262     // may be visible.
2263     if (mWindowType == WindowType::Popup) {
2264       SetPopupWindowLevel();
2265     }
2266   } else {
2267     nsToolkit::GetToolkit()->StopMonitoringAllProcessMouseEvents();
2269     // XXXndeakin this doesn't make sense.
2270     // Why is the new window assumed to be a modal panel?
2271     if (mWindow && mWindowType == WindowType::Popup) {
2272       mWindow.level = NSModalPanelWindowLevel;
2273     }
2274   }
2276   NS_OBJC_END_TRY_IGNORE_BLOCK;
2279 nsresult nsCocoaWindow::GetAttention(int32_t aCycleCount) {
2280   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2282   [NSApp requestUserAttention:NSInformationalRequest];
2283   return NS_OK;
2285   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2288 bool nsCocoaWindow::HasPendingInputEvent() {
2289   return nsChildView::DoHasPendingInputEvent();
2292 void nsCocoaWindow::SetWindowShadowStyle(WindowShadow aStyle) {
2293   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2295   if (mShadowStyle == aStyle) {
2296     return;
2297   }
2299   mShadowStyle = aStyle;
2301   if (!mWindow || mWindowType != WindowType::Popup) {
2302     return;
2303   }
2305   mWindow.shadowStyle = mShadowStyle;
2306   [mWindow setEffectViewWrapperForStyle:mShadowStyle];
2307   [mWindow setHasShadow:aStyle != WindowShadow::None];
2309   NS_OBJC_END_TRY_IGNORE_BLOCK;
2312 void nsCocoaWindow::SetWindowOpacity(float aOpacity) {
2313   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2315   if (!mWindow) {
2316     return;
2317   }
2319   [mWindow setAlphaValue:(CGFloat)aOpacity];
2321   NS_OBJC_END_TRY_IGNORE_BLOCK;
2324 void nsCocoaWindow::SetColorScheme(const Maybe<ColorScheme>& aScheme) {
2325   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2327   if (!mWindow) {
2328     return;
2329   }
2331   mWindow.appearance = aScheme ? NSAppearanceForColorScheme(*aScheme) : nil;
2333   NS_OBJC_END_TRY_IGNORE_BLOCK;
2336 static inline CGAffineTransform GfxMatrixToCGAffineTransform(
2337     const gfx::Matrix& m) {
2338   CGAffineTransform t;
2339   t.a = m._11;
2340   t.b = m._12;
2341   t.c = m._21;
2342   t.d = m._22;
2343   t.tx = m._31;
2344   t.ty = m._32;
2345   return t;
2348 void nsCocoaWindow::SetWindowTransform(const gfx::Matrix& aTransform) {
2349   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2351   if (!mWindow) {
2352     return;
2353   }
2355   // Calling CGSSetWindowTransform when the window is not visible results in
2356   // misplacing the window into doubled x,y coordinates (see bug 1448132).
2357   if (!mWindow.isVisible || NSIsEmptyRect(mWindow.frame)) {
2358     return;
2359   }
2361   if (StaticPrefs::widget_window_transforms_disabled()) {
2362     // CGSSetWindowTransform is a private API. In case calling it causes
2363     // problems either now or in the future, we'll want to have an easy kill
2364     // switch. So we allow disabling it with a pref.
2365     return;
2366   }
2368   gfx::Matrix transform = aTransform;
2370   // aTransform is a transform that should be applied to the window relative
2371   // to its regular position: If aTransform._31 is 100, then we want the
2372   // window to be displayed 100 pixels to the right of its regular position.
2373   // The transform that CGSSetWindowTransform accepts has a different meaning:
2374   // It's used to answer the question "For the screen pixel at x,y (with the
2375   // origin at the top left), what pixel in the window's buffer (again with
2376   // origin top left) should be displayed at that position?"
2377   // In the example above, this means that we need to call
2378   // CGSSetWindowTransform with a horizontal translation of -windowPos.x - 100.
2379   // So we need to invert the transform and adjust it by the window's position.
2380   if (!transform.Invert()) {
2381     // Treat non-invertible transforms as the identity transform.
2382     transform = gfx::Matrix();
2383   }
2385   bool isIdentity = transform.IsIdentity();
2386   if (isIdentity && mWindowTransformIsIdentity) {
2387     return;
2388   }
2390   transform.PreTranslate(-mBounds.x, -mBounds.y);
2392   // Snap translations to device pixels, to match what we do for CSS transforms
2393   // and because the window server rounds down instead of to nearest.
2394   if (!transform.HasNonTranslation() && transform.HasNonIntegerTranslation()) {
2395     auto snappedTranslation = gfx::IntPoint::Round(transform.GetTranslation());
2396     transform =
2397         gfx::Matrix::Translation(snappedTranslation.x, snappedTranslation.y);
2398   }
2400   // We also need to account for the backing scale factor: aTransform is given
2401   // in device pixels, but CGSSetWindowTransform works with logical display
2402   // pixels.
2403   CGFloat backingScale = BackingScaleFactor();
2404   transform.PreScale(backingScale, backingScale);
2405   transform.PostScale(1 / backingScale, 1 / backingScale);
2407   CGSConnection cid = _CGSDefaultConnection();
2408   CGSSetWindowTransform(cid, [mWindow windowNumber],
2409                         GfxMatrixToCGAffineTransform(transform));
2411   mWindowTransformIsIdentity = isIdentity;
2413   NS_OBJC_END_TRY_IGNORE_BLOCK;
2416 void nsCocoaWindow::SetInputRegion(const InputRegion& aInputRegion) {
2417   MOZ_ASSERT(mWindowType == WindowType::Popup,
2418              "This should only be called on popup windows.");
2419   // TODO: Somehow support aInputRegion.mMargin? Though maybe not.
2420   if (aInputRegion.mFullyTransparent) {
2421     [mWindow setIgnoresMouseEvents:YES];
2422   } else {
2423     [mWindow setIgnoresMouseEvents:NO];
2424   }
2427 void nsCocoaWindow::SetShowsToolbarButton(bool aShow) {
2428   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2430   if (mWindow) [mWindow setShowsToolbarButton:aShow];
2432   NS_OBJC_END_TRY_IGNORE_BLOCK;
2435 void nsCocoaWindow::SetSupportsNativeFullscreen(
2436     bool aSupportsNativeFullscreen) {
2437   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2439   if (mWindow) {
2440     // This determines whether we tell cocoa that the window supports native
2441     // full screen. If we do so, and another window is in native full screen,
2442     // this window will also appear in native full screen. We generally only
2443     // want to do this for primary application windows. We'll set the
2444     // relevant macnativefullscreen attribute on those, which will lead to us
2445     // being called with aSupportsNativeFullscreen set to `true` here.
2446     NSWindowCollectionBehavior newBehavior = [mWindow collectionBehavior];
2447     if (aSupportsNativeFullscreen) {
2448       newBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
2449     } else {
2450       newBehavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
2451     }
2452     [mWindow setCollectionBehavior:newBehavior];
2453   }
2455   NS_OBJC_END_TRY_IGNORE_BLOCK;
2458 void nsCocoaWindow::SetWindowAnimationType(
2459     nsIWidget::WindowAnimationType aType) {
2460   mAnimationType = aType;
2463 void nsCocoaWindow::SetDrawsTitle(bool aDrawTitle) {
2464   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2466   // If we don't draw into the window frame, we always want to display window
2467   // titles.
2468   mWindow.wantsTitleDrawn = aDrawTitle || !mWindow.drawsContentsIntoWindowFrame;
2470   NS_OBJC_END_TRY_IGNORE_BLOCK;
2473 nsresult nsCocoaWindow::SetNonClientMargins(
2474     const LayoutDeviceIntMargin& margins) {
2475   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2477   SetDrawsInTitlebar(margins.top == 0);
2479   return NS_OK;
2481   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2484 void nsCocoaWindow::SetDrawsInTitlebar(bool aState) {
2485   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2487   if (mWindow) {
2488     [mWindow setDrawsContentsIntoWindowFrame:aState];
2489   }
2491   NS_OBJC_END_TRY_IGNORE_BLOCK;
2494 NS_IMETHODIMP nsCocoaWindow::SynthesizeNativeMouseEvent(
2495     LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
2496     MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
2497     nsIObserver* aObserver) {
2498   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2500   AutoObserverNotifier notifier(aObserver, "mouseevent");
2501   if (mPopupContentView) {
2502     return mPopupContentView->SynthesizeNativeMouseEvent(
2503         aPoint, aNativeMessage, aButton, aModifierFlags, nullptr);
2504   }
2506   return NS_OK;
2508   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2511 NS_IMETHODIMP nsCocoaWindow::SynthesizeNativeMouseScrollEvent(
2512     LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX,
2513     double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
2514     uint32_t aAdditionalFlags, nsIObserver* aObserver) {
2515   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2517   AutoObserverNotifier notifier(aObserver, "mousescrollevent");
2518   if (mPopupContentView) {
2519     // Pass nullptr as the observer so that the AutoObserverNotification in
2520     // nsChildView::SynthesizeNativeMouseScrollEvent will be ignored.
2521     return mPopupContentView->SynthesizeNativeMouseScrollEvent(
2522         aPoint, aNativeMessage, aDeltaX, aDeltaY, aDeltaZ, aModifierFlags,
2523         aAdditionalFlags, nullptr);
2524   }
2526   return NS_OK;
2528   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2531 void nsCocoaWindow::LockAspectRatio(bool aShouldLock) {
2532   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2534   if (aShouldLock) {
2535     [mWindow setContentAspectRatio:mWindow.frame.size];
2536     mAspectRatioLocked = true;
2537   } else {
2538     // According to
2539     // https://developer.apple.com/documentation/appkit/nswindow/1419507-aspectratio,
2540     // aspect ratios and resize increments are mutually exclusive, and the
2541     // accepted way of cancelling an established aspect ratio is to set the
2542     // resize increments to 1.0, 1.0
2543     [mWindow setResizeIncrements:NSMakeSize(1.0, 1.0)];
2544     mAspectRatioLocked = false;
2545   }
2547   NS_OBJC_END_TRY_IGNORE_BLOCK;
2550 void nsCocoaWindow::UpdateThemeGeometries(
2551     const nsTArray<ThemeGeometry>& aThemeGeometries) {
2552   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2554   if (mPopupContentView) {
2555     return mPopupContentView->UpdateThemeGeometries(aThemeGeometries);
2556   }
2558   NS_OBJC_END_TRY_IGNORE_BLOCK;
2561 void nsCocoaWindow::SetPopupWindowLevel() {
2562   if (!mWindow) {
2563     return;
2564   }
2565   // Otherwise, this is a top-level or parent popup. Parent popups always
2566   // appear just above their parent and essentially ignore the level.
2567   mWindow.level = NSPopUpMenuWindowLevel;
2568   mWindow.hidesOnDeactivate = NO;
2571 void nsCocoaWindow::SetInputContext(const InputContext& aContext,
2572                                     const InputContextAction& aAction) {
2573   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2575   mInputContext = aContext;
2577   NS_OBJC_END_TRY_IGNORE_BLOCK;
2580 bool nsCocoaWindow::GetEditCommands(NativeKeyBindingsType aType,
2581                                     const WidgetKeyboardEvent& aEvent,
2582                                     nsTArray<CommandInt>& aCommands) {
2583   // Validate the arguments.
2584   if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
2585     return false;
2586   }
2588   NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
2589   // When the keyboard event is fired from this widget, it must mean that no web
2590   // content has focus because any web contents should be on `nsChildView`.  And
2591   // in any locales, the system UI is always horizontal layout.  So, let's pass
2592   // `Nothing()` for the writing mode here, it won't be treated as in a vertical
2593   // content.
2594   keyBindings->GetEditCommands(aEvent, Nothing(), aCommands);
2595   return true;
2598 void nsCocoaWindow::PauseOrResumeCompositor(bool aPause) {
2599   if (auto* mainChildView =
2600           static_cast<nsIWidget*>(mWindow.mainChildView.widget)) {
2601     mainChildView->PauseOrResumeCompositor(aPause);
2602   }
2605 bool nsCocoaWindow::AsyncPanZoomEnabled() const {
2606   if (mPopupContentView) {
2607     return mPopupContentView->AsyncPanZoomEnabled();
2608   }
2609   return nsBaseWidget::AsyncPanZoomEnabled();
2612 bool nsCocoaWindow::StartAsyncAutoscroll(const ScreenPoint& aAnchorLocation,
2613                                          const ScrollableLayerGuid& aGuid) {
2614   if (mPopupContentView) {
2615     return mPopupContentView->StartAsyncAutoscroll(aAnchorLocation, aGuid);
2616   }
2617   return nsBaseWidget::StartAsyncAutoscroll(aAnchorLocation, aGuid);
2620 void nsCocoaWindow::StopAsyncAutoscroll(const ScrollableLayerGuid& aGuid) {
2621   if (mPopupContentView) {
2622     mPopupContentView->StopAsyncAutoscroll(aGuid);
2623     return;
2624   }
2625   nsBaseWidget::StopAsyncAutoscroll(aGuid);
2628 already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
2629   nsCOMPtr<nsIWidget> window = new nsCocoaWindow();
2630   return window.forget();
2633 already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
2634   nsCOMPtr<nsIWidget> window = new nsChildView();
2635   return window.forget();
2638 @implementation WindowDelegate
2640 // We try to find a gecko menu bar to paint. If one does not exist, just paint
2641 // the application menu by itself so that a window doesn't have some other
2642 // window's menu bar.
2643 + (void)paintMenubarForWindow:(NSWindow*)aWindow {
2644   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2646   // make sure we only act on windows that have this kind of
2647   // object as a delegate
2648   id windowDelegate = [aWindow delegate];
2649   if ([windowDelegate class] != [self class]) return;
2651   nsCocoaWindow* geckoWidget = [windowDelegate geckoWidget];
2652   NS_ASSERTION(geckoWidget, "Window delegate not returning a gecko widget!");
2654   if (nsMenuBarX* geckoMenuBar = geckoWidget->GetMenuBar()) {
2655     geckoMenuBar->Paint();
2656   } else {
2657     // sometimes we don't have a native application menu early in launching
2658     if (!sApplicationMenu) {
2659       return;
2660     }
2662     NSMenu* mainMenu = NSApp.mainMenu;
2663     NS_ASSERTION(
2664         mainMenu.numberOfItems > 0,
2665         "Main menu does not have any items, something is terribly wrong!");
2667     // Create a new menu bar.
2668     // We create a GeckoNSMenu because all menu bar NSMenu objects should use
2669     // that subclass for key handling reasons.
2670     GeckoNSMenu* newMenuBar =
2671         [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar"];
2673     // move the application menu from the existing menu bar to the new one
2674     NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain];
2675     [mainMenu removeItemAtIndex:0];
2676     [newMenuBar insertItem:firstMenuItem atIndex:0];
2677     [firstMenuItem release];
2679     // set our new menu bar as the main menu
2680     NSApp.mainMenu = newMenuBar;
2681     [newMenuBar release];
2682   }
2684   NS_OBJC_END_TRY_IGNORE_BLOCK;
2687 - (id)initWithGeckoWindow:(nsCocoaWindow*)geckoWind {
2688   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2690   [super init];
2691   mGeckoWindow = geckoWind;
2692   mToplevelActiveState = false;
2693   mHasEverBeenZoomed = false;
2694   return self;
2696   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
2699 - (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)proposedFrameSize {
2700   RollUpPopups();
2701   return proposedFrameSize;
2704 - (NSRect)windowWillUseStandardFrame:(NSWindow*)window
2705                         defaultFrame:(NSRect)newFrame {
2706   // This function needs to return a rect representing the frame a window would
2707   // have if it is in its "maximized" size mode. The parameter newFrame is
2708   // supposed to be a frame representing the maximum window size on the screen
2709   // where the window currently appears. However, in practice, newFrame can be a
2710   // much smaller size. So, we ignore newframe and instead return the frame of
2711   // the entire screen associated with the window. That frame is bigger than the
2712   // window could actually be, due to the presence of the menubar and possibly
2713   // the dock, but we never call this function directly, and Cocoa callers will
2714   // shrink it to its true maximum size.
2715   return window.screen.frame;
2718 void nsCocoaWindow::CocoaSendToplevelActivateEvents() {
2719   if (mWidgetListener) {
2720     mWidgetListener->WindowActivated();
2721   }
2724 void nsCocoaWindow::CocoaSendToplevelDeactivateEvents() {
2725   if (mWidgetListener) {
2726     mWidgetListener->WindowDeactivated();
2727   }
2730 void nsCocoaWindow::CocoaWindowDidResize() {
2731   // It's important to update our bounds before we trigger any listeners. This
2732   // ensures that our bounds are correct when GetScreenBounds is called.
2733   UpdateBounds();
2735   if (HandleUpdateFullscreenOnResize()) {
2736     ReportSizeEvent();
2737     return;
2738   }
2740   // Resizing might have changed our zoom state.
2741   DispatchSizeModeEvent();
2742   ReportSizeEvent();
2745 - (void)windowDidResize:(NSNotification*)aNotification {
2746   BaseWindow* window = [aNotification object];
2747   [window updateTrackingArea];
2749   if (!mGeckoWindow) return;
2751   mGeckoWindow->CocoaWindowDidResize();
2754 - (void)windowDidChangeScreen:(NSNotification*)aNotification {
2755   if (!mGeckoWindow) return;
2757   // Because of Cocoa's peculiar treatment of zero-size windows (see comments
2758   // at GetBackingScaleFactor() above), we sometimes have a situation where
2759   // our concept of backing scale (based on the screen where the zero-sized
2760   // window is positioned) differs from Cocoa's idea (always based on the
2761   // Retina screen, AFAICT, even when an external non-Retina screen is the
2762   // primary display).
2763   //
2764   // As a result, if the window was created with zero size on an external
2765   // display, but then made visible on the (secondary) Retina screen, we
2766   // will *not* get a windowDidChangeBackingProperties notification for it.
2767   // This leads to an incorrect GetDefaultScale(), and widget coordinate
2768   // confusion, as per bug 853252.
2769   //
2770   // To work around this, we check for a backing scale mismatch when we
2771   // receive a windowDidChangeScreen notification, as we will receive this
2772   // even if Cocoa was already treating the zero-size window as having
2773   // Retina backing scale.
2774   NSWindow* window = (NSWindow*)[aNotification object];
2775   if ([window respondsToSelector:@selector(backingScaleFactor)]) {
2776     if (GetBackingScaleFactor(window) != mGeckoWindow->BackingScaleFactor()) {
2777       mGeckoWindow->BackingScaleFactorChanged();
2778     }
2779   }
2781   mGeckoWindow->ReportMoveEvent();
2784 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
2785   if (!mGeckoWindow) {
2786     return;
2787   }
2788   mGeckoWindow->CocoaWindowWillEnterFullscreen(true);
2791 // Lion's full screen mode will bypass our internal fullscreen tracking, so
2792 // we need to catch it when we transition and call our own methods, which in
2793 // turn will fire "fullscreen" events.
2794 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
2795   // On Yosemite, the NSThemeFrame class has two new properties --
2796   // titlebarView (an NSTitlebarView object) and titlebarContainerView (an
2797   // NSTitlebarContainerView object).  These are used to display the titlebar
2798   // in fullscreen mode.  In Safari they're not transparent.  But in Firefox
2799   // for some reason they are, which causes bug 1069658.  The following code
2800   // works around this Apple bug or design flaw.
2801   NSWindow* window = notification.object;
2802   NSView* frameView = window.contentView.superview;
2803   NSView* titlebarView = nil;
2804   NSView* titlebarContainerView = nil;
2805   if ([frameView respondsToSelector:@selector(titlebarView)]) {
2806     titlebarView = [frameView titlebarView];
2807   }
2808   if ([frameView respondsToSelector:@selector(titlebarContainerView)]) {
2809     titlebarContainerView = [frameView titlebarContainerView];
2810   }
2811   if ([titlebarView respondsToSelector:@selector(setTransparent:)]) {
2812     [titlebarView setTransparent:NO];
2813   }
2814   if ([titlebarContainerView respondsToSelector:@selector(setTransparent:)]) {
2815     [titlebarContainerView setTransparent:NO];
2816   }
2818   if (!mGeckoWindow) {
2819     return;
2820   }
2821   mGeckoWindow->CocoaWindowDidEnterFullscreen(true);
2824 - (void)windowWillExitFullScreen:(NSNotification*)notification {
2825   if (!mGeckoWindow) {
2826     return;
2827   }
2828   mGeckoWindow->CocoaWindowWillEnterFullscreen(false);
2831 - (void)windowDidExitFullScreen:(NSNotification*)notification {
2832   if (!mGeckoWindow) {
2833     return;
2834   }
2835   mGeckoWindow->CocoaWindowDidEnterFullscreen(false);
2838 - (void)windowDidBecomeMain:(NSNotification*)aNotification {
2839   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2841   RollUpPopups();
2842   ChildViewMouseTracker::ReEvaluateMouseEnterState();
2844   // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
2845   // app modally. If one of those is up then we want it to retain its menu bar.
2846   if (NSApp._isRunningAppModal) {
2847     return;
2848   }
2849   NSWindow* window = aNotification.object;
2850   if (window) {
2851     [WindowDelegate paintMenubarForWindow:window];
2852   }
2854   if ([window isKindOfClass:[ToolbarWindow class]]) {
2855     [(ToolbarWindow*)window windowMainStateChanged];
2856   }
2858   NS_OBJC_END_TRY_IGNORE_BLOCK;
2861 - (void)windowDidResignMain:(NSNotification*)aNotification {
2862   RollUpPopups();
2863   ChildViewMouseTracker::ReEvaluateMouseEnterState();
2865   // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
2866   // app modally. If one of those is up then we want it to retain its menu bar.
2867   if ([NSApp _isRunningAppModal]) return;
2868   RefPtr<nsMenuBarX> hiddenWindowMenuBar =
2869       nsMenuUtilsX::GetHiddenWindowMenuBar();
2870   if (hiddenWindowMenuBar) {
2871     // printf("painting hidden window menu bar due to window losing main
2872     // status\n");
2873     hiddenWindowMenuBar->Paint();
2874   }
2876   NSWindow* window = [aNotification object];
2877   if ([window isKindOfClass:[ToolbarWindow class]]) {
2878     [(ToolbarWindow*)window windowMainStateChanged];
2879   }
2882 - (void)windowDidBecomeKey:(NSNotification*)aNotification {
2883   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2885   RollUpPopups();
2886   ChildViewMouseTracker::ReEvaluateMouseEnterState();
2888   NSWindow* window = [aNotification object];
2889   auto* mainChildView =
2890       static_cast<nsChildView*>([[(BaseWindow*)window mainChildView] widget]);
2891   if (mainChildView) {
2892     if (mainChildView->GetInputContext().IsPasswordEditor()) {
2893       TextInputHandler::EnableSecureEventInput();
2894     } else {
2895       TextInputHandler::EnsureSecureEventInputDisabled();
2896     }
2897   }
2899   NS_OBJC_END_TRY_IGNORE_BLOCK;
2902 - (void)windowDidResignKey:(NSNotification*)aNotification {
2903   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2905   RollUpPopups(nsIRollupListener::AllowAnimations::No);
2907   ChildViewMouseTracker::ReEvaluateMouseEnterState();
2908   TextInputHandler::EnsureSecureEventInputDisabled();
2910   NS_OBJC_END_TRY_IGNORE_BLOCK;
2913 - (void)windowWillMove:(NSNotification*)aNotification {
2914   RollUpPopups();
2917 - (void)windowDidMove:(NSNotification*)aNotification {
2918   if (mGeckoWindow) mGeckoWindow->ReportMoveEvent();
2921 - (BOOL)windowShouldClose:(id)sender {
2922   nsIWidgetListener* listener =
2923       mGeckoWindow ? mGeckoWindow->GetWidgetListener() : nullptr;
2924   if (listener) listener->RequestWindowClose(mGeckoWindow);
2925   return NO;  // gecko will do it
2928 - (void)windowWillClose:(NSNotification*)aNotification {
2929   RollUpPopups();
2932 - (void)windowWillMiniaturize:(NSNotification*)aNotification {
2933   RollUpPopups();
2936 - (void)windowDidMiniaturize:(NSNotification*)aNotification {
2937   if (!mGeckoWindow) {
2938     return;
2939   }
2940   mGeckoWindow->FinishCurrentTransitionIfMatching(
2941       nsCocoaWindow::TransitionType::Miniaturize);
2944 - (void)windowDidDeminiaturize:(NSNotification*)aNotification {
2945   if (!mGeckoWindow) {
2946     return;
2947   }
2948   mGeckoWindow->FinishCurrentTransitionIfMatching(
2949       nsCocoaWindow::TransitionType::Deminiaturize);
2952 - (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)proposedFrame {
2953   if (!mHasEverBeenZoomed && window.isZoomed) {
2954     return NO;  // See bug 429954.
2955   }
2956   mHasEverBeenZoomed = YES;
2957   return YES;
2960 - (void)windowDidChangeBackingProperties:(NSNotification*)aNotification {
2961   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2963   NSWindow* window = (NSWindow*)[aNotification object];
2965   if ([window respondsToSelector:@selector(backingScaleFactor)]) {
2966     CGFloat oldFactor = [[[aNotification userInfo]
2967         objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue];
2968     if (window.backingScaleFactor != oldFactor) {
2969       mGeckoWindow->BackingScaleFactorChanged();
2970     }
2971   }
2973   NS_OBJC_END_TRY_IGNORE_BLOCK;
2976 // This method is on NSWindowDelegate starting with 10.9
2977 - (void)windowDidChangeOcclusionState:(NSNotification*)aNotification {
2978   if (mGeckoWindow) {
2979     mGeckoWindow->DispatchOcclusionEvent();
2980   }
2983 - (nsCocoaWindow*)geckoWidget {
2984   return mGeckoWindow;
2987 - (bool)toplevelActiveState {
2988   return mToplevelActiveState;
2991 - (void)sendToplevelActivateEvents {
2992   if (!mToplevelActiveState && mGeckoWindow) {
2993     mGeckoWindow->CocoaSendToplevelActivateEvents();
2995     mToplevelActiveState = true;
2996   }
2999 - (void)sendToplevelDeactivateEvents {
3000   if (mToplevelActiveState && mGeckoWindow) {
3001     mGeckoWindow->CocoaSendToplevelDeactivateEvents();
3002     mToplevelActiveState = false;
3003   }
3006 @end
3008 @interface NSView (FrameViewMethodSwizzling)
3009 - (NSPoint)FrameView__closeButtonOrigin;
3010 - (CGFloat)FrameView__titlebarHeight;
3011 @end
3013 @implementation NSView (FrameViewMethodSwizzling)
3015 - (NSPoint)FrameView__closeButtonOrigin {
3016   if (![self.window isKindOfClass:[ToolbarWindow class]]) {
3017     return self.FrameView__closeButtonOrigin;
3018   }
3019   auto* win = static_cast<ToolbarWindow*>(self.window);
3020   if (win.drawsContentsIntoWindowFrame && !win.wantsTitleDrawn &&
3021       !(win.styleMask & NSWindowStyleMaskFullScreen) &&
3022       (win.styleMask & NSWindowStyleMaskTitled)) {
3023     const NSRect buttonsRect = win.windowButtonsRect;
3024     if (NSIsEmptyRect(buttonsRect)) {
3025       // Empty rect. Let's hide the buttons.
3026       // Position is in non-flipped window coordinates. Using frame's height
3027       // for the vertical coordinate will move the buttons above the window,
3028       // making them invisible.
3029       return NSMakePoint(buttonsRect.origin.x, win.frame.size.height);
3030     }
3031     if (win.windowTitlebarLayoutDirection ==
3032         NSUserInterfaceLayoutDirectionRightToLeft) {
3033       // We're in RTL mode, which means that the close button is the rightmost
3034       // button of the three window buttons. and buttonsRect.origin is the
3035       // bottom left corner of the green (zoom) button. The close button is 40px
3036       // to the right of the zoom button. This is confirmed to be the same on
3037       // all macOS versions between 10.12 - 12.0.
3038       return NSMakePoint(buttonsRect.origin.x + 40.0f, buttonsRect.origin.y);
3039     }
3040     return buttonsRect.origin;
3041   }
3042   return self.FrameView__closeButtonOrigin;
3045 - (CGFloat)FrameView__titlebarHeight {
3046   // XXX: Shouldn't this be [super FrameView__titlebarHeight]?
3047   CGFloat height = [self FrameView__titlebarHeight];
3048   if ([self.window isKindOfClass:[ToolbarWindow class]]) {
3049     // Make sure that the titlebar height includes our shifted buttons.
3050     // The following coordinates are in window space, with the origin being at
3051     // the bottom left corner of the window.
3052     auto* win = static_cast<ToolbarWindow*>(self.window);
3053     CGFloat frameHeight = self.frame.size.height;
3054     CGFloat windowButtonY = frameHeight;
3055     if (!NSIsEmptyRect(win.windowButtonsRect) &&
3056         win.drawsContentsIntoWindowFrame &&
3057         !(win.styleMask & NSWindowStyleMaskFullScreen) &&
3058         (win.styleMask & NSWindowStyleMaskTitled)) {
3059       windowButtonY = win.windowButtonsRect.origin.y;
3060     }
3061     height = std::max(height, frameHeight - windowButtonY);
3062   }
3063   return height;
3066 @end
3068 static NSMutableSet* gSwizzledFrameViewClasses = nil;
3070 @interface NSWindow (PrivateSetNeedsDisplayInRectMethod)
3071 - (void)_setNeedsDisplayInRect:(NSRect)aRect;
3072 @end
3074 @interface BaseWindow (Private)
3075 - (void)removeTrackingArea;
3076 - (void)cursorUpdated:(NSEvent*)aEvent;
3077 - (void)reflowTitlebarElements;
3078 @end
3080 @implementation BaseWindow
3082 // The frame of a window is implemented using undocumented NSView subclasses.
3083 // We offset the window buttons by overriding the method _closeButtonOrigin on
3084 // these frame view classes. The class which is
3085 // used for a window is determined in the window's frameViewClassForStyleMask:
3086 // method, so this is where we make sure that we have swizzled the method on
3087 // all encountered classes.
3088 + (Class)frameViewClassForStyleMask:(NSUInteger)styleMask {
3089   Class frameViewClass = [super frameViewClassForStyleMask:styleMask];
3091   if (!gSwizzledFrameViewClasses) {
3092     gSwizzledFrameViewClasses = [[NSMutableSet setWithCapacity:3] retain];
3093     if (!gSwizzledFrameViewClasses) {
3094       return frameViewClass;
3095     }
3096   }
3098   static IMP our_closeButtonOrigin = class_getMethodImplementation(
3099       [NSView class], @selector(FrameView__closeButtonOrigin));
3100   static IMP our_titlebarHeight = class_getMethodImplementation(
3101       [NSView class], @selector(FrameView__titlebarHeight));
3103   if (![gSwizzledFrameViewClasses containsObject:frameViewClass]) {
3104     // Either of these methods might be implemented in both a subclass of
3105     // NSFrameView and one of its own subclasses.  Which means that if we
3106     // aren't careful we might end up swizzling the same method twice.
3107     // Since method swizzling involves swapping pointers, this would break
3108     // things.
3109     IMP _closeButtonOrigin = class_getMethodImplementation(
3110         frameViewClass, @selector(_closeButtonOrigin));
3111     if (_closeButtonOrigin && _closeButtonOrigin != our_closeButtonOrigin) {
3112       nsToolkit::SwizzleMethods(frameViewClass, @selector(_closeButtonOrigin),
3113                                 @selector(FrameView__closeButtonOrigin));
3114     }
3116     // Override _titlebarHeight so that the floating titlebar doesn't clip the
3117     // bottom of the window buttons which we move down with our override of
3118     // _closeButtonOrigin.
3119     IMP _titlebarHeight = class_getMethodImplementation(
3120         frameViewClass, @selector(_titlebarHeight));
3121     if (_titlebarHeight && _titlebarHeight != our_titlebarHeight) {
3122       nsToolkit::SwizzleMethods(frameViewClass, @selector(_titlebarHeight),
3123                                 @selector(FrameView__titlebarHeight));
3124     }
3126     [gSwizzledFrameViewClasses addObject:frameViewClass];
3127   }
3129   return frameViewClass;
3132 - (id)initWithContentRect:(NSRect)aContentRect
3133                 styleMask:(NSUInteger)aStyle
3134                   backing:(NSBackingStoreType)aBufferingType
3135                     defer:(BOOL)aFlag {
3136   mDrawsIntoWindowFrame = NO;
3137   [super initWithContentRect:aContentRect
3138                    styleMask:aStyle
3139                      backing:aBufferingType
3140                        defer:aFlag];
3141   mState = nil;
3142   mDisabledNeedsDisplay = NO;
3143   mTrackingArea = nil;
3144   mDirtyRect = NSZeroRect;
3145   mBeingShown = NO;
3146   mDrawTitle = NO;
3147   mTouchBar = nil;
3148   mIsAnimationSuppressed = NO;
3149   [self updateTrackingArea];
3151   return self;
3154 // Returns an autoreleased NSImage.
3155 static NSImage* GetMenuMaskImage() {
3156   const CGFloat radius = 6.0f;
3157   const NSSize maskSize = {radius * 3.0f, radius * 3.0f};
3158   NSImage* maskImage = [NSImage imageWithSize:maskSize
3159                                       flipped:FALSE
3160                                drawingHandler:^BOOL(NSRect dstRect) {
3161                                  NSBezierPath* path = [NSBezierPath
3162                                      bezierPathWithRoundedRect:dstRect
3163                                                        xRadius:radius
3164                                                        yRadius:radius];
3165                                  [NSColor.blackColor set];
3166                                  [path fill];
3167                                  return YES;
3168                                }];
3169   maskImage.capInsets = NSEdgeInsetsMake(radius, radius, radius, radius);
3170   return maskImage;
3173 // Add an effect view wrapper if needed so that the OS draws the appropriate
3174 // vibrancy effect and window border.
3175 - (void)setEffectViewWrapperForStyle:(WindowShadow)aStyle {
3176   NSView* wrapper = [&]() -> NSView* {
3177     if (aStyle == WindowShadow::Menu || aStyle == WindowShadow::Tooltip) {
3178       const bool isMenu = aStyle == WindowShadow::Menu;
3179       auto* effectView =
3180           [[NSVisualEffectView alloc] initWithFrame:self.contentView.frame];
3181       effectView.material =
3182           isMenu ? NSVisualEffectMaterialMenu : NSVisualEffectMaterialToolTip;
3183       // Tooltip and menu windows are never "key", so we need to tell the
3184       // vibrancy effect to look active regardless of window state.
3185       effectView.state = NSVisualEffectStateActive;
3186       effectView.blendingMode = NSVisualEffectBlendingModeBehindWindow;
3187       if (isMenu) {
3188         // Turn on rounded corner masking.
3189         effectView.maskImage = GetMenuMaskImage();
3190       }
3191       return effectView;
3192     }
3193     return [[NSView alloc] initWithFrame:self.contentView.frame];
3194   }();
3196   wrapper.wantsLayer = YES;
3197   // Swap out our content view by the new view. Setting .contentView releases
3198   // the old view.
3199   NSView* childView = [self.mainChildView retain];
3200   [childView removeFromSuperview];
3201   [wrapper addSubview:childView];
3202   [childView release];
3203   super.contentView = wrapper;
3204   [wrapper release];
3207 - (NSTouchBar*)makeTouchBar {
3208   mTouchBar = [[nsTouchBar alloc] init];
3209   if (mTouchBar) {
3210     sTouchBarIsInitialized = YES;
3211   }
3212   return mTouchBar;
3215 - (void)setBeingShown:(BOOL)aValue {
3216   mBeingShown = aValue;
3219 - (BOOL)isBeingShown {
3220   return mBeingShown;
3223 - (BOOL)isVisibleOrBeingShown {
3224   return [super isVisible] || mBeingShown;
3227 - (void)setIsAnimationSuppressed:(BOOL)aValue {
3228   mIsAnimationSuppressed = aValue;
3231 - (BOOL)isAnimationSuppressed {
3232   return mIsAnimationSuppressed;
3235 - (void)disableSetNeedsDisplay {
3236   mDisabledNeedsDisplay = YES;
3239 - (void)enableSetNeedsDisplay {
3240   mDisabledNeedsDisplay = NO;
3243 - (void)dealloc {
3244   [mTouchBar release];
3245   [self removeTrackingArea];
3246   ChildViewMouseTracker::OnDestroyWindow(self);
3247   [super dealloc];
3250 static const NSString* kStateTitleKey = @"title";
3251 static const NSString* kStateDrawsContentsIntoWindowFrameKey =
3252     @"drawsContentsIntoWindowFrame";
3253 static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
3254 static const NSString* kStateCollectionBehavior = @"collectionBehavior";
3255 static const NSString* kStateWantsTitleDrawn = @"wantsTitleDrawn";
3257 - (void)importState:(NSDictionary*)aState {
3258   if (NSString* title = [aState objectForKey:kStateTitleKey]) {
3259     [self setTitle:title];
3260   }
3261   [self setDrawsContentsIntoWindowFrame:
3262             [[aState objectForKey:kStateDrawsContentsIntoWindowFrameKey]
3263                 boolValue]];
3264   [self setShowsToolbarButton:[[aState objectForKey:kStateShowsToolbarButton]
3265                                   boolValue]];
3266   [self setCollectionBehavior:[[aState objectForKey:kStateCollectionBehavior]
3267                                   unsignedIntValue]];
3268   [self setWantsTitleDrawn:[[aState objectForKey:kStateWantsTitleDrawn]
3269                                boolValue]];
3272 - (NSMutableDictionary*)exportState {
3273   NSMutableDictionary* state = [NSMutableDictionary dictionaryWithCapacity:10];
3274   if (NSString* title = self.title) {
3275     [state setObject:title forKey:kStateTitleKey];
3276   }
3277   [state setObject:[NSNumber numberWithBool:self.drawsContentsIntoWindowFrame]
3278             forKey:kStateDrawsContentsIntoWindowFrameKey];
3279   [state setObject:[NSNumber numberWithBool:self.showsToolbarButton]
3280             forKey:kStateShowsToolbarButton];
3281   [state setObject:[NSNumber numberWithUnsignedInt:self.collectionBehavior]
3282             forKey:kStateCollectionBehavior];
3283   [state setObject:[NSNumber numberWithBool:self.wantsTitleDrawn]
3284             forKey:kStateWantsTitleDrawn];
3285   return state;
3288 - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState {
3289   bool changed = aState != mDrawsIntoWindowFrame;
3290   mDrawsIntoWindowFrame = aState;
3291   if (changed) {
3292     [self reflowTitlebarElements];
3293   }
3296 - (BOOL)drawsContentsIntoWindowFrame {
3297   return mDrawsIntoWindowFrame;
3300 - (NSRect)childViewRectForFrameRect:(NSRect)aFrameRect {
3301   if (mDrawsIntoWindowFrame) {
3302     return aFrameRect;
3303   }
3304   NSUInteger styleMask = [self styleMask];
3305   styleMask &= ~NSWindowStyleMaskFullSizeContentView;
3306   return [NSWindow contentRectForFrameRect:aFrameRect styleMask:styleMask];
3309 - (NSRect)frameRectForChildViewRect:(NSRect)aChildViewRect {
3310   if (mDrawsIntoWindowFrame) {
3311     return aChildViewRect;
3312   }
3313   NSUInteger styleMask = [self styleMask];
3314   styleMask &= ~NSWindowStyleMaskFullSizeContentView;
3315   return [NSWindow frameRectForContentRect:aChildViewRect styleMask:styleMask];
3318 - (NSTimeInterval)animationResizeTime:(NSRect)newFrame {
3319   if (mIsAnimationSuppressed) {
3320     // Should not animate the initial session-restore size change
3321     return 0.0;
3322   }
3324   return [super animationResizeTime:newFrame];
3327 - (void)setWantsTitleDrawn:(BOOL)aDrawTitle {
3328   mDrawTitle = aDrawTitle;
3329   [self setTitleVisibility:mDrawTitle ? NSWindowTitleVisible
3330                                       : NSWindowTitleHidden];
3333 - (BOOL)wantsTitleDrawn {
3334   return mDrawTitle;
3337 - (NSView*)trackingAreaView {
3338   NSView* contentView = self.contentView;
3339   return contentView.superview ? contentView.superview : contentView;
3342 - (NSArray<NSView*>*)contentViewContents {
3343   return [[self.contentView.subviews copy] autorelease];
3346 - (ChildView*)mainChildView {
3347   NSView* contentView = self.contentView;
3348   NSView* lastView = contentView.subviews.lastObject;
3349   if ([lastView isKindOfClass:[ChildView class]]) {
3350     return (ChildView*)lastView;
3351   }
3352   return nil;
3355 - (void)removeTrackingArea {
3356   if (mTrackingArea) {
3357     [self.trackingAreaView removeTrackingArea:mTrackingArea];
3358     [mTrackingArea release];
3359     mTrackingArea = nil;
3360   }
3363 - (void)updateTrackingArea {
3364   [self removeTrackingArea];
3366   NSView* view = self.trackingAreaView;
3367   const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
3368                                         NSTrackingMouseMoved |
3369                                         NSTrackingActiveAlways;
3370   mTrackingArea = [[NSTrackingArea alloc] initWithRect:[view bounds]
3371                                                options:options
3372                                                  owner:self
3373                                               userInfo:nil];
3374   [view addTrackingArea:mTrackingArea];
3377 - (void)mouseEntered:(NSEvent*)aEvent {
3378   ChildViewMouseTracker::MouseEnteredWindow(aEvent);
3381 - (void)mouseExited:(NSEvent*)aEvent {
3382   ChildViewMouseTracker::MouseExitedWindow(aEvent);
3385 - (void)mouseMoved:(NSEvent*)aEvent {
3386   ChildViewMouseTracker::MouseMoved(aEvent);
3389 - (void)cursorUpdated:(NSEvent*)aEvent {
3390   // Nothing to do here, but NSTrackingArea wants us to implement this method.
3393 - (void)_setNeedsDisplayInRect:(NSRect)aRect {
3394   // Prevent unnecessary invalidations due to moving NSViews (e.g. for plugins)
3395   if (!mDisabledNeedsDisplay) {
3396     // This method is only called by Cocoa, so when we're here, we know that
3397     // it's available and don't need to check whether our superclass responds
3398     // to the selector.
3399     [super _setNeedsDisplayInRect:aRect];
3400     mDirtyRect = NSUnionRect(mDirtyRect, aRect);
3401   }
3404 - (NSRect)getAndResetNativeDirtyRect {
3405   NSRect dirtyRect = mDirtyRect;
3406   mDirtyRect = NSZeroRect;
3407   return dirtyRect;
3410 // Possibly move the titlebar buttons.
3411 - (void)reflowTitlebarElements {
3412   NSView* frameView = self.contentView.superview;
3413   if ([frameView respondsToSelector:@selector(_tileTitlebarAndRedisplay:)]) {
3414     [frameView _tileTitlebarAndRedisplay:NO];
3415   }
3418 - (BOOL)respondsToSelector:(SEL)aSelector {
3419   // Claim the window doesn't respond to this so that the system
3420   // doesn't steal keyboard equivalents for it. Bug 613710.
3421   if (aSelector == @selector(cancelOperation:)) {
3422     return NO;
3423   }
3425   return [super respondsToSelector:aSelector];
3428 - (void)doCommandBySelector:(SEL)aSelector {
3429   // We override this so that it won't beep if it can't act.
3430   // We want to control the beeping for missing or disabled
3431   // commands ourselves.
3432   [self tryToPerform:aSelector with:nil];
3435 - (id)accessibilityAttributeValue:(NSString*)attribute {
3436   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3438   id retval = [super accessibilityAttributeValue:attribute];
3440   // The following works around a problem with Text-to-Speech on OS X 10.7.
3441   // See bug 674612 for more info.
3442   //
3443   // When accessibility is off, AXUIElementCopyAttributeValue(), when called
3444   // on an AXApplication object to get its AXFocusedUIElement attribute,
3445   // always returns an AXWindow object (the actual browser window -- never a
3446   // mozAccessible object).  This also happens with accessibility turned on,
3447   // if no other object in the browser window has yet been focused.  But if
3448   // the browser window has a title bar (as it currently always does), the
3449   // AXWindow object will always have four "accessible" children, one of which
3450   // is an AXStaticText object (the title bar's "title"; the other three are
3451   // the close, minimize and zoom buttons).  This means that (for complicated
3452   // reasons, for which see bug 674612) Text-to-Speech on OS X 10.7 will often
3453   // "speak" the window title, no matter what text is selected, or even if no
3454   // text at all is selected.  (This always happens when accessibility is off.
3455   // It doesn't happen in Firefox releases because Apple has (on OS X 10.7)
3456   // special-cased the handling of apps whose CFBundleIdentifier is
3457   // org.mozilla.firefox.)
3458   //
3459   // We work around this problem by only returning AXChildren that are
3460   // mozAccessible object or are one of the titlebar's buttons (which
3461   // instantiate subclasses of NSButtonCell).
3462   if ([retval isKindOfClass:[NSArray class]] &&
3463       [attribute isEqualToString:@"AXChildren"]) {
3464     NSMutableArray* holder = [NSMutableArray arrayWithCapacity:10];
3465     [holder addObjectsFromArray:(NSArray*)retval];
3466     NSUInteger count = [holder count];
3467     for (NSInteger i = count - 1; i >= 0; --i) {
3468       id item = [holder objectAtIndex:i];
3469       // Remove anything from holder that isn't one of the titlebar's buttons
3470       // (which instantiate subclasses of NSButtonCell) or a mozAccessible
3471       // object (or one of its subclasses).
3472       if (![item isKindOfClass:[NSButtonCell class]] &&
3473           ![item respondsToSelector:@selector(hasRepresentedView)]) {
3474         [holder removeObjectAtIndex:i];
3475       }
3476     }
3477     retval = [NSArray arrayWithArray:holder];
3478   }
3480   return retval;
3482   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
3485 - (void)releaseJSObjects {
3486   [mTouchBar releaseJSObjects];
3489 @end
3491 @interface MOZTitlebarAccessoryView : NSView
3492 @end
3494 @implementation MOZTitlebarAccessoryView : NSView
3495 - (void)viewWillMoveToWindow:(NSWindow*)aWindow {
3496   if (aWindow) {
3497     // When entering full screen mode, titlebar accessory views are inserted
3498     // into a floating NSWindow which houses the window titlebar and toolbars.
3499     // In order to work around a drawing bug with titlebarAppearsTransparent
3500     // windows in full screen mode, disable titlebar separators for all
3501     // NSWindows that this view is used in, including the floating full screen
3502     // toolbar window. The drawing bug was filed as FB9056136. See bug 1700211
3503     // for more details.
3504     if (@available(macOS 11.0, *)) {
3505       aWindow.titlebarSeparatorStyle = NSTitlebarSeparatorStyleNone;
3506     }
3507   }
3509 @end
3511 @implementation FullscreenTitlebarTracker
3512 - (FullscreenTitlebarTracker*)init {
3513   [super init];
3514   self.hidden = YES;
3515   return self;
3517 - (void)loadView {
3518   self.view =
3519       [[[MOZTitlebarAccessoryView alloc] initWithFrame:NSZeroRect] autorelease];
3521 @end
3523 // Drop all mouse events if a modal window has appeared above us.
3524 // This helps make us behave as if the OS were running a "real" modal event
3525 // loop.
3526 static bool MaybeDropEventForModalWindow(NSEvent* aEvent, id aDelegate) {
3527   if (!sModalWindowCount) {
3528     return false;
3529   }
3531   NSEventType type = [aEvent type];
3532   switch (type) {
3533     case NSEventTypeScrollWheel:
3534     case NSEventTypeLeftMouseDown:
3535     case NSEventTypeLeftMouseUp:
3536     case NSEventTypeRightMouseDown:
3537     case NSEventTypeRightMouseUp:
3538     case NSEventTypeOtherMouseDown:
3539     case NSEventTypeOtherMouseUp:
3540     case NSEventTypeMouseMoved:
3541     case NSEventTypeLeftMouseDragged:
3542     case NSEventTypeRightMouseDragged:
3543     case NSEventTypeOtherMouseDragged:
3544       break;
3545     default:
3546       return false;
3547   }
3549   if (aDelegate && [aDelegate isKindOfClass:[WindowDelegate class]]) {
3550     if (nsCocoaWindow* widget = [(WindowDelegate*)aDelegate geckoWidget]) {
3551       if (!widget->IsModal() || widget->HasModalDescendants()) {
3552         return true;
3553       }
3554     }
3555   }
3556   return false;
3559 @implementation ToolbarWindow
3561 - (id)initWithContentRect:(NSRect)aChildViewRect
3562                 styleMask:(NSUInteger)aStyle
3563                   backing:(NSBackingStoreType)aBufferingType
3564                     defer:(BOOL)aFlag {
3565   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3567   // We treat aChildViewRect as the rectangle that the window's main ChildView
3568   // should be sized to. Get the right frameRect for the requested child view
3569   // rect.
3570   NSRect frameRect = [NSWindow frameRectForContentRect:aChildViewRect
3571                                              styleMask:aStyle];
3573   // Always size the content view to the full frame size of the window.
3574   // We do this even if we want this window to have a titlebar; in that case,
3575   // the window's content view covers the entire window but the ChildView inside
3576   // it will only cover the content area. We do this so that we can render the
3577   // titlebar gradient manually, with a subview of our content view that's
3578   // positioned in the titlebar area. This lets us have a smooth connection
3579   // between titlebar and toolbar gradient in case the window has a "unified
3580   // toolbar + titlebar" look. Moreover, always using a full size content view
3581   // lets us toggle the titlebar on and off without changing the window's style
3582   // mask (which would have other subtle effects, for example on keyboard
3583   // focus).
3584   aStyle |= NSWindowStyleMaskFullSizeContentView;
3586   // -[NSWindow initWithContentRect:styleMask:backing:defer:] calls
3587   // [self frameRectForContentRect:styleMask:] to convert the supplied content
3588   // rect to the window's frame rect. We've overridden that method to be a
3589   // pass-through function. So, in order to get the intended frameRect, we need
3590   // to supply frameRect itself as the "content rect".
3591   NSRect contentRect = frameRect;
3593   if ((self = [super initWithContentRect:contentRect
3594                                styleMask:aStyle
3595                                  backing:aBufferingType
3596                                    defer:aFlag])) {
3597     mWindowButtonsRect = NSZeroRect;
3599     self.titlebarAppearsTransparent = YES;
3600     if (@available(macOS 11.0, *)) {
3601       self.titlebarSeparatorStyle = NSTitlebarSeparatorStyleNone;
3602     }
3604     mFullscreenTitlebarTracker = [[FullscreenTitlebarTracker alloc] init];
3605     // revealAmount is an undocumented property of
3606     // NSTitlebarAccessoryViewController that updates whenever the menubar
3607     // slides down in fullscreen mode.
3608     [mFullscreenTitlebarTracker addObserver:self
3609                                  forKeyPath:@"revealAmount"
3610                                     options:NSKeyValueObservingOptionNew
3611                                     context:nil];
3612     // Adding this accessory view controller allows us to shift the toolbar down
3613     // when the user mouses to the top of the screen in fullscreen.
3614     [(NSWindow*)self
3615         addTitlebarAccessoryViewController:mFullscreenTitlebarTracker];
3616   }
3617   return self;
3619   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
3622 - (void)observeValueForKeyPath:(NSString*)keyPath
3623                       ofObject:(id)object
3624                         change:(NSDictionary<NSKeyValueChangeKey, id>*)change
3625                        context:(void*)context {
3626   if ([keyPath isEqualToString:@"revealAmount"]) {
3627     [[self mainChildView] ensureNextCompositeIsAtomicWithMainThreadPaint];
3628     NSNumber* revealAmount = (change[NSKeyValueChangeNewKey]);
3629     [self updateTitlebarShownAmount:[revealAmount doubleValue]];
3630   } else {
3631     [super observeValueForKeyPath:keyPath
3632                          ofObject:object
3633                            change:change
3634                           context:context];
3635   }
3638 static bool ScreenHasNotch(nsCocoaWindow* aGeckoWindow) {
3639   if (@available(macOS 12.0, *)) {
3640     nsCOMPtr<nsIScreen> widgetScreen = aGeckoWindow->GetWidgetScreen();
3641     NSScreen* cocoaScreen =
3642         ScreenHelperCocoa::CocoaScreenForScreen(widgetScreen);
3643     return cocoaScreen.safeAreaInsets.top != 0.0f;
3644   }
3645   return false;
3648 static bool ShouldShiftByMenubarHeightInFullscreen(nsCocoaWindow* aWindow) {
3649   switch (StaticPrefs::widget_macos_shift_by_menubar_on_fullscreen()) {
3650     case 0:
3651       return false;
3652     case 1:
3653       return true;
3654     default:
3655       break;
3656   }
3657   // TODO: On notch-less macbooks, this creates extra space when the
3658   // "automatically show and hide the menubar on fullscreen" option is unchecked
3659   // (default checked). We tried to detect that in bug 1737831 but it wasn't
3660   // reliable enough, see the regressions from that bug. For now, stick to the
3661   // good behavior for default configurations (that is, shift by menubar height
3662   // on notch-less macbooks, and don't for devices that have a notch). This will
3663   // need refinement in the future.
3664   return !ScreenHasNotch(aWindow);
3667 - (void)updateTitlebarShownAmount:(CGFloat)aShownAmount {
3668   if (!(self.styleMask & NSWindowStyleMaskFullScreen)) {
3669     // We are not interested in the size of the titlebar unless we are in
3670     // fullscreen.
3671     return;
3672   }
3674   // [NSApp mainMenu] menuBarHeight] returns one of two values: the full height
3675   // if the menubar is shown or is in the process of being shown, and 0
3676   // otherwise. Since we are multiplying the menubar height by aShownAmount, we
3677   // always want the full height.
3678   CGFloat menuBarHeight = NSApp.mainMenu.menuBarHeight;
3679   if (menuBarHeight > 0.0f) {
3680     mMenuBarHeight = menuBarHeight;
3681   }
3682   if ([[self delegate] isKindOfClass:[WindowDelegate class]]) {
3683     WindowDelegate* windowDelegate = (WindowDelegate*)[self delegate];
3684     nsCocoaWindow* geckoWindow = [windowDelegate geckoWidget];
3685     if (!geckoWindow) {
3686       return;
3687     }
3689     if (nsIWidgetListener* listener = geckoWindow->GetWidgetListener()) {
3690       // titlebarHeight returns 0 when we're in fullscreen, return the default
3691       // titlebar height.
3692       CGFloat shiftByPixels =
3693           LookAndFeel::GetInt(LookAndFeel::IntID::MacTitlebarHeight) *
3694           aShownAmount;
3695       if (ShouldShiftByMenubarHeightInFullscreen(geckoWindow)) {
3696         shiftByPixels += mMenuBarHeight * aShownAmount;
3697       }
3698       // Use desktop pixels rather than the DesktopToLayoutDeviceScale in
3699       // nsCocoaWindow. The latter accounts for screen DPI. We don't want that
3700       // because the revealAmount property already accounts for it, so we'd be
3701       // compounding DPI scales > 1.
3702       listener->MacFullscreenMenubarOverlapChanged(DesktopCoord(shiftByPixels));
3703     }
3704   }
3707 - (void)dealloc {
3708   [mFullscreenTitlebarTracker removeObserver:self forKeyPath:@"revealAmount"];
3709   [mFullscreenTitlebarTracker removeFromParentViewController];
3710   [mFullscreenTitlebarTracker release];
3712   [super dealloc];
3715 - (NSArray<NSView*>*)contentViewContents {
3716   return [[self.contentView.subviews copy] autorelease];
3719 - (void)windowMainStateChanged {
3720   [[self mainChildView] ensureNextCompositeIsAtomicWithMainThreadPaint];
3723 // Extending the content area into the title bar works by resizing the
3724 // mainChildView so that it covers the titlebar.
3725 - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState {
3726   BOOL stateChanged = self.drawsContentsIntoWindowFrame != aState;
3727   [super setDrawsContentsIntoWindowFrame:aState];
3728   if (stateChanged && [self.delegate isKindOfClass:[WindowDelegate class]]) {
3729     // Here we extend / shrink our mainChildView. We do that by firing a resize
3730     // event which will cause the ChildView to be resized to the rect returned
3731     // by nsCocoaWindow::GetClientBounds. GetClientBounds bases its return
3732     // value on what we return from drawsContentsIntoWindowFrame.
3733     auto* windowDelegate = static_cast<WindowDelegate*>(self.delegate);
3734     if (nsCocoaWindow* geckoWindow = windowDelegate.geckoWidget) {
3735       // Re-layout our contents.
3736       geckoWindow->ReportSizeEvent();
3737     }
3739     // Resizing the content area causes a reflow which would send a synthesized
3740     // mousemove event to the old mouse position relative to the top left
3741     // corner of the content area. But the mouse has shifted relative to the
3742     // content area, so that event would have wrong position information. So
3743     // we'll send a mouse move event with the correct new position.
3744     ChildViewMouseTracker::ResendLastMouseMoveEvent();
3745   }
3748 - (void)placeWindowButtons:(NSRect)aRect {
3749   if (!NSEqualRects(mWindowButtonsRect, aRect)) {
3750     mWindowButtonsRect = aRect;
3751     [self reflowTitlebarElements];
3752   }
3755 - (NSRect)windowButtonsRect {
3756   return mWindowButtonsRect;
3759 // Returning YES here makes the setShowsToolbarButton method work even though
3760 // the window doesn't contain an NSToolbar.
3761 - (BOOL)_hasToolbar {
3762   return YES;
3765 // Dispatch a toolbar pill button clicked message to Gecko.
3766 - (void)_toolbarPillButtonClicked:(id)sender {
3767   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3769   RollUpPopups();
3771   if ([self.delegate isKindOfClass:[WindowDelegate class]]) {
3772     auto* windowDelegate = static_cast<WindowDelegate*>(self.delegate);
3773     nsCocoaWindow* geckoWindow = windowDelegate.geckoWidget;
3774     if (!geckoWindow) {
3775       return;
3776     }
3778     if (nsIWidgetListener* listener = geckoWindow->GetWidgetListener()) {
3779       listener->OSToolbarButtonPressed();
3780     }
3781   }
3783   NS_OBJC_END_TRY_IGNORE_BLOCK;
3786 // Retain and release "self" to avoid crashes when our widget (and its native
3787 // window) is closed as a result of processing a key equivalent (e.g.
3788 // Command+w or Command+q).  This workaround is only needed for a window
3789 // that can become key.
3790 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
3791   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3793   NSWindow* nativeWindow = [self retain];
3794   BOOL retval = [super performKeyEquivalent:theEvent];
3795   [nativeWindow release];
3796   return retval;
3798   NS_OBJC_END_TRY_BLOCK_RETURN(NO);
3801 - (void)sendEvent:(NSEvent*)anEvent {
3802   if (MaybeDropEventForModalWindow(anEvent, self.delegate)) {
3803     return;
3804   }
3805   [super sendEvent:anEvent];
3808 @end
3810 @implementation PopupWindow
3812 - (id)initWithContentRect:(NSRect)contentRect
3813                 styleMask:(NSUInteger)styleMask
3814                   backing:(NSBackingStoreType)bufferingType
3815                     defer:(BOOL)deferCreation {
3816   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3818   mIsContextMenu = false;
3819   return [super initWithContentRect:contentRect
3820                           styleMask:styleMask
3821                             backing:bufferingType
3822                               defer:deferCreation];
3824   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
3827 // Override the private API _backdropBleedAmount. This determines how much the
3828 // desktop wallpaper contributes to the vibrancy backdrop.
3829 // Return 0 in order to match what the system does for sheet windows and
3830 // _NSPopoverWindows.
3831 - (CGFloat)_backdropBleedAmount {
3832   return 0.0;
3835 // Override the private API shadowOptions.
3836 // The constants below were found in AppKit's implementations of the
3837 // shadowOptions method on the various window types.
3838 static const NSUInteger kWindowShadowOptionsNoShadow = 0;
3839 static const NSUInteger kWindowShadowOptionsMenu = 2;
3840 static const NSUInteger kWindowShadowOptionsTooltip = 4;
3842 - (NSDictionary*)shadowParameters {
3843   NSDictionary* parent = [super shadowParameters];
3844   // NSLog(@"%@", parent);
3845   if (self.shadowStyle != WindowShadow::Panel) {
3846     return parent;
3847   }
3848   NSMutableDictionary* copy = [parent mutableCopy];
3849   for (auto* key : {@"com.apple.WindowShadowRimDensityActive",
3850                     @"com.apple.WindowShadowRimDensityInactive"}) {
3851     if ([parent objectForKey:key] != nil) {
3852       [copy setValue:@(0) forKey:key];
3853     }
3854   }
3855   return copy;
3858 - (NSUInteger)shadowOptions {
3859   if (!self.hasShadow) {
3860     return kWindowShadowOptionsNoShadow;
3861   }
3863   switch (self.shadowStyle) {
3864     case WindowShadow::None:
3865       return kWindowShadowOptionsNoShadow;
3867     case WindowShadow::Menu:
3868     case WindowShadow::Panel:
3869       return kWindowShadowOptionsMenu;
3871     case WindowShadow::Tooltip:
3872       return kWindowShadowOptionsTooltip;
3873   }
3876 - (BOOL)isContextMenu {
3877   return mIsContextMenu;
3880 - (void)setIsContextMenu:(BOOL)flag {
3881   mIsContextMenu = flag;
3884 - (BOOL)canBecomeMainWindow {
3885   // This is overriden because the default is 'yes' when a titlebar is present.
3886   return NO;
3889 @end
3891 // According to Apple's docs on [NSWindow canBecomeKeyWindow] and [NSWindow
3892 // canBecomeMainWindow], windows without a title bar or resize bar can't (by
3893 // default) become key or main.  But if a window can't become key, it can't
3894 // accept keyboard input (bmo bug 393250).  And it should also be possible for
3895 // an otherwise "ordinary" window to become main.  We need to override these
3896 // two methods to make this happen.
3897 @implementation BorderlessWindow
3899 - (BOOL)canBecomeKeyWindow {
3900   return YES;
3903 - (void)sendEvent:(NSEvent*)anEvent {
3904   if (MaybeDropEventForModalWindow(anEvent, self.delegate)) {
3905     return;
3906   }
3908   [super sendEvent:anEvent];
3911 // Apple's doc on this method says that the NSWindow class's default is not to
3912 // become main if the window isn't "visible" -- so we should replicate that
3913 // behavior here.  As best I can tell, the [NSWindow isVisible] method is an
3914 // accurate test of what Apple means by "visibility".
3915 - (BOOL)canBecomeMainWindow {
3916   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3918   return self.isVisible;
3920   NS_OBJC_END_TRY_BLOCK_RETURN(NO);
3923 // Retain and release "self" to avoid crashes when our widget (and its native
3924 // window) is closed as a result of processing a key equivalent (e.g.
3925 // Command+w or Command+q).  This workaround is only needed for a window
3926 // that can become key.
3927 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
3928   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3930   NSWindow* nativeWindow = [self retain];
3931   BOOL retval = [super performKeyEquivalent:theEvent];
3932   [nativeWindow release];
3933   return retval;
3935   NS_OBJC_END_TRY_BLOCK_RETURN(NO);
3938 @end