Backed out changeset b4a0f8afc02e (bug 1857946) for causing bc failures at browser...
[gecko.git] / widget / cocoa / nsCocoaWindow.mm
blob033a99089bf97c9364c78ce4ec124497e7d32398
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 "VibrancyManager.h"
38 #include "nsPresContext.h"
39 #include "nsDocShell.h"
41 #include "gfxPlatform.h"
42 #include "qcms.h"
44 #include "mozilla/AutoRestore.h"
45 #include "mozilla/BasicEvents.h"
46 #include "mozilla/dom/Document.h"
47 #include "mozilla/Maybe.h"
48 #include "mozilla/NativeKeyBindingsType.h"
49 #include "mozilla/Preferences.h"
50 #include "mozilla/PresShell.h"
51 #include "mozilla/ScopeExit.h"
52 #include "mozilla/StaticPrefs_gfx.h"
53 #include "mozilla/StaticPrefs_widget.h"
54 #include "mozilla/WritingModes.h"
55 #include "mozilla/layers/CompositorBridgeChild.h"
56 #include "mozilla/widget/Screen.h"
57 #include <algorithm>
59 namespace mozilla {
60 namespace layers {
61 class LayerManager;
62 }  // namespace layers
63 }  // namespace mozilla
64 using namespace mozilla::layers;
65 using namespace mozilla::widget;
66 using namespace mozilla;
68 int32_t gXULModalLevel = 0;
70 // In principle there should be only one app-modal window at any given time.
71 // But sometimes, despite our best efforts, another window appears above the
72 // current app-modal window.  So we need to keep a linked list of app-modal
73 // windows.  (A non-sheet window that appears above an app-modal window is
74 // also made app-modal.)  See nsCocoaWindow::SetModal().
75 nsCocoaWindowList* gGeckoAppModalWindowList = NULL;
77 BOOL sTouchBarIsInitialized = NO;
79 // defined in nsMenuBarX.mm
80 extern NSMenu* sApplicationMenu;  // Application menu shared by all menubars
82 // defined in nsChildView.mm
83 extern BOOL gSomeMenuBarPainted;
85 extern "C" {
86 // CGSPrivate.h
87 typedef NSInteger CGSConnection;
88 typedef NSUInteger CGSSpaceID;
89 typedef NSInteger CGSWindow;
90 typedef enum {
91   kCGSSpaceIncludesCurrent = 1 << 0,
92   kCGSSpaceIncludesOthers = 1 << 1,
93   kCGSSpaceIncludesUser = 1 << 2,
95   kCGSAllSpacesMask =
96       kCGSSpaceIncludesCurrent | kCGSSpaceIncludesOthers | kCGSSpaceIncludesUser
97 } CGSSpaceMask;
98 static NSString* const CGSSpaceIDKey = @"ManagedSpaceID";
99 static NSString* const CGSSpacesKey = @"Spaces";
100 extern CGSConnection _CGSDefaultConnection(void);
101 extern CGError CGSSetWindowTransform(CGSConnection cid, CGSWindow wid,
102                                      CGAffineTransform transform);
105 #define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1"
107 NS_IMPL_ISUPPORTS_INHERITED(nsCocoaWindow, Inherited, nsPIWidgetCocoa)
109 // A note on testing to see if your object is a sheet...
110 // |mWindowType == WindowType::Sheet| is true if your gecko nsIWidget is a sheet
111 // widget - whether or not the sheet is showing. |[mWindow isSheet]| will return
112 // true *only when the sheet is actually showing*. Choose your test wisely.
114 static void RollUpPopups(nsIRollupListener::AllowAnimations aAllowAnimations =
115                              nsIRollupListener::AllowAnimations::Yes) {
116   nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
117   NS_ENSURE_TRUE_VOID(rollupListener);
119   if (rollupListener->RollupNativeMenu()) {
120     return;
121   }
123   nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
124   if (!rollupWidget) {
125     return;
126   }
127   nsIRollupListener::RollupOptions options{
128       0, nsIRollupListener::FlushViews::Yes, nullptr, aAllowAnimations};
129   rollupListener->Rollup(options);
132 nsCocoaWindow::nsCocoaWindow()
133     : mParent(nullptr),
134       mAncestorLink(nullptr),
135       mWindow(nil),
136       mDelegate(nil),
137       mSheetWindowParent(nil),
138       mPopupContentView(nil),
139       mFullscreenTransitionAnimation(nil),
140       mShadowStyle(StyleWindowShadow::Default),
141       mBackingScaleFactor(0.0),
142       mAnimationType(nsIWidget::eGenericWindowAnimation),
143       mWindowMadeHere(false),
144       mSheetNeedsShow(false),
145       mSizeMode(nsSizeMode_Normal),
146       mInFullScreenMode(false),
147       mInNativeFullScreenMode(false),
148       mIgnoreOcclusionCount(0),
149       mHasStartedNativeFullscreen(false),
150       mModal(false),
151       mFakeModal(false),
152       mIsAnimationSuppressed(false),
153       mInReportMoveEvent(false),
154       mInResize(false),
155       mWindowTransformIsIdentity(true),
156       mAlwaysOnTop(false),
157       mAspectRatioLocked(false),
158       mNumModalDescendents(0),
159       mWindowAnimationBehavior(NSWindowAnimationBehaviorDefault),
160       mWasShown(false) {
161   // Disable automatic tabbing. We need to do this before we
162   // orderFront any of our windows.
163   [NSWindow setAllowsAutomaticWindowTabbing:NO];
166 void nsCocoaWindow::DestroyNativeWindow() {
167   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
169   if (!mWindow) return;
171   // Define a helper function for checking our fullscreen window status.
172   bool (^inNativeFullscreen)(void) = ^{
173     return ((mWindow.styleMask & NSWindowStyleMaskFullScreen) ==
174             NSWindowStyleMaskFullScreen);
175   };
177   // If we are in native fullscreen, or we are in the middle of a native
178   // fullscreen transition, spin our run loop until both those things
179   // are false. This ensures that we've completed all our native
180   // fullscreen transitions and updated our class state before we close
181   // the window. We need to do this here (rather than in some other
182   // nsCocoaWindow trying to do a native fullscreen transition) because
183   // part of closing our window is clearing out our delegate, and the
184   // delegate callback is the only other way to clear our class state.
185   //
186   // While spinning this run loop, check to see if we are still in native
187   // fullscreen. If we are, request that we leave fullscreen (only once)
188   // and continue to spin the run loop until we're out of fullscreen.
189   //
190   // We also spin this run loop while mWaitingOnFinishCurrentTransition
191   // is true, which indicates we are waiting for an async call to
192   // FinishCurrentTransition to complete. We don't want to allow our own
193   // destruction while we have this pending runnable.
194   //
195   // However, it's possible that we are *already* in a run loop. In such
196   // a case, we can't spin the run loop again, so we don't even enter this
197   // loop if mInLocalRunLoop is true.
198   if (!mInLocalRunLoop) {
199     mInLocalRunLoop = true;
200     bool haveRequestedFullscreenExit = false;
201     NSRunLoop* localRunLoop = [NSRunLoop currentRunLoop];
202     while ((mWaitingOnFinishCurrentTransition || inNativeFullscreen() ||
203             WeAreInNativeTransition()) &&
204            [localRunLoop runMode:NSDefaultRunLoopMode
205                       beforeDate:[NSDate distantFuture]]) {
206       // This loop continues to process events until mWindow is fully out
207       // of native fullscreen.
209       // Check to see if we should one-time request an exit from fullscreen.
210       // We do this if we are in native fullscreen and no window is in a
211       // native fullscreen transition.
212       if (!haveRequestedFullscreenExit && inNativeFullscreen() &&
213           CanStartNativeTransition()) {
214         [mWindow toggleFullScreen:nil];
215         haveRequestedFullscreenExit = true;
216       }
217     }
218     mInLocalRunLoop = false;
219   }
221   [mWindow releaseJSObjects];
222   // We want to unhook the delegate here because we don't want events
223   // sent to it after this object has been destroyed.
224   [mWindow setDelegate:nil];
225   [mWindow close];
226   mWindow = nil;
227   [mDelegate autorelease];
229   // We've lost access to our delegate. If we are still in a native
230   // fullscreen transition, we have to give up on it now even if it
231   // isn't finished, because the delegate is the only way that the
232   // class state would ever be cleared.
233   EndOurNativeTransition();
235   NS_OBJC_END_TRY_IGNORE_BLOCK;
238 nsCocoaWindow::~nsCocoaWindow() {
239   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
241   // Notify the children that we're gone.  Popup windows (e.g. tooltips) can
242   // have nsChildView children.  'kid' is an nsChildView object if and only if
243   // its 'type' is 'WindowType::Child'.
244   // childView->ResetParent() can change our list of children while it's
245   // being iterated, so the way we iterate the list must allow for this.
246   for (nsIWidget* kid = mLastChild; kid;) {
247     WindowType kidType = kid->GetWindowType();
248     if (kidType == WindowType::Child) {
249       nsChildView* childView = static_cast<nsChildView*>(kid);
250       kid = kid->GetPrevSibling();
251       childView->ResetParent();
252     } else {
253       nsCocoaWindow* childWindow = static_cast<nsCocoaWindow*>(kid);
254       childWindow->mParent = nullptr;
255       childWindow->mAncestorLink = mAncestorLink;
256       kid = kid->GetPrevSibling();
257     }
258   }
260   if (mWindow && mWindowMadeHere) {
261     DestroyNativeWindow();
262   }
264   NS_IF_RELEASE(mPopupContentView);
266   // Deal with the possiblity that we're being destroyed while running modal.
267   if (mModal) {
268     NS_WARNING("Widget destroyed while running modal!");
269     --gXULModalLevel;
270     NS_ASSERTION(gXULModalLevel >= 0, "Weirdness setting modality!");
271   }
273   NS_OBJC_END_TRY_IGNORE_BLOCK;
276 // Find the screen that overlaps aRect the most,
277 // if none are found default to the mainScreen.
278 static NSScreen* FindTargetScreenForRect(const DesktopIntRect& aRect) {
279   NSScreen* targetScreen = [NSScreen mainScreen];
280   NSEnumerator* screenEnum = [[NSScreen screens] objectEnumerator];
281   int largestIntersectArea = 0;
282   while (NSScreen* screen = [screenEnum nextObject]) {
283     DesktopIntRect screenRect =
284         nsCocoaUtils::CocoaRectToGeckoRect([screen visibleFrame]);
285     screenRect = screenRect.Intersect(aRect);
286     int area = screenRect.width * screenRect.height;
287     if (area > largestIntersectArea) {
288       largestIntersectArea = area;
289       targetScreen = screen;
290     }
291   }
292   return targetScreen;
295 // fits the rect to the screen that contains the largest area of it,
296 // or to aScreen if a screen is passed in
297 // NB: this operates with aRect in desktop pixels
298 static void FitRectToVisibleAreaForScreen(DesktopIntRect& aRect,
299                                           NSScreen* aScreen) {
300   if (!aScreen) {
301     aScreen = FindTargetScreenForRect(aRect);
302   }
304   DesktopIntRect screenBounds =
305       nsCocoaUtils::CocoaRectToGeckoRect([aScreen visibleFrame]);
307   if (aRect.width > screenBounds.width) {
308     aRect.width = screenBounds.width;
309   }
310   if (aRect.height > screenBounds.height) {
311     aRect.height = screenBounds.height;
312   }
314   if (aRect.x - screenBounds.x + aRect.width > screenBounds.width) {
315     aRect.x += screenBounds.width - (aRect.x - screenBounds.x + aRect.width);
316   }
317   if (aRect.y - screenBounds.y + aRect.height > screenBounds.height) {
318     aRect.y += screenBounds.height - (aRect.y - screenBounds.y + aRect.height);
319   }
321   // If the left/top edge of the window is off the screen in either direction,
322   // then set the window to start at the left/top edge of the screen.
323   if (aRect.x < screenBounds.x ||
324       aRect.x > (screenBounds.x + screenBounds.width)) {
325     aRect.x = screenBounds.x;
326   }
327   if (aRect.y < screenBounds.y ||
328       aRect.y > (screenBounds.y + screenBounds.height)) {
329     aRect.y = screenBounds.y;
330   }
333 DesktopToLayoutDeviceScale ParentBackingScaleFactor(nsIWidget* aParent,
334                                                     NSView* aParentView) {
335   if (aParent) {
336     return aParent->GetDesktopToDeviceScale();
337   }
338   NSWindow* parentWindow = [aParentView window];
339   if (parentWindow) {
340     return DesktopToLayoutDeviceScale([parentWindow backingScaleFactor]);
341   }
342   return DesktopToLayoutDeviceScale(1.0);
345 // Returns the screen rectangle for the given widget.
346 // Child widgets are positioned relative to this rectangle.
347 // Exactly one of the arguments must be non-null.
348 static DesktopRect GetWidgetScreenRectForChildren(nsIWidget* aWidget,
349                                                   NSView* aView) {
350   if (aWidget) {
351     mozilla::DesktopToLayoutDeviceScale scale =
352         aWidget->GetDesktopToDeviceScale();
353     if (aWidget->GetWindowType() == WindowType::Child) {
354       return aWidget->GetScreenBounds() / scale;
355     }
356     return aWidget->GetClientBounds() / scale;
357   }
359   MOZ_RELEASE_ASSERT(aView);
361   // 1. Transform the view rect into window coords.
362   // The returned rect is in "origin bottom-left" coordinates.
363   NSRect rectInWindowCoordinatesOBL = [aView convertRect:[aView bounds]
364                                                   toView:nil];
366   // 2. Turn the window-coord rect into screen coords, still origin bottom-left.
367   NSRect rectInScreenCoordinatesOBL =
368       [[aView window] convertRectToScreen:rectInWindowCoordinatesOBL];
370   // 3. Convert the NSRect to a DesktopRect. This will convert to coordinates
371   // with the origin in the top left corner of the primary screen.
372   return DesktopRect(
373       nsCocoaUtils::CocoaRectToGeckoRect(rectInScreenCoordinatesOBL));
376 // aRect here is specified in desktop pixels
378 // For child windows (where either aParent or aNativeParent is non-null),
379 // aRect.{x,y} are offsets from the origin of the parent window and not an
380 // absolute position.
381 nsresult nsCocoaWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
382                                const DesktopIntRect& aRect,
383                                widget::InitData* aInitData) {
384   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
386   // Because the hidden window is created outside of an event loop,
387   // we have to provide an autorelease pool (see bug 559075).
388   nsAutoreleasePool localPool;
390   DesktopIntRect newBounds = aRect;
391   FitRectToVisibleAreaForScreen(newBounds, nullptr);
393   // Set defaults which can be overriden from aInitData in BaseCreate
394   mWindowType = WindowType::TopLevel;
395   mBorderStyle = BorderStyle::Default;
397   // Ensure that the toolkit is created.
398   nsToolkit::GetToolkit();
400   Inherited::BaseCreate(aParent, aInitData);
402   mParent = aParent;
403   mAncestorLink = aParent;
404   mAlwaysOnTop = aInitData->mAlwaysOnTop;
406   // If we have a parent widget, the new widget will be offset from the
407   // parent widget by aRect.{x,y}. Otherwise, we'll use aRect for the
408   // new widget coordinates.
409   DesktopIntPoint parentOrigin;
411   // Do we have a parent widget?
412   if (aParent || aNativeParent) {
413     DesktopRect parentDesktopRect =
414         GetWidgetScreenRectForChildren(aParent, (NSView*)aNativeParent);
415     parentOrigin = gfx::RoundedToInt(parentDesktopRect.TopLeft());
416   }
418   DesktopIntRect widgetRect = aRect + parentOrigin;
420   nsresult rv =
421       CreateNativeWindow(nsCocoaUtils::GeckoRectToCocoaRect(widgetRect),
422                          mBorderStyle, false, aInitData->mIsPrivate);
423   NS_ENSURE_SUCCESS(rv, rv);
425   if (mWindowType == WindowType::Popup) {
426     // now we can convert widgetRect to device pixels for the window we created,
427     // as the child view expects a rect expressed in the dev pix of its parent
428     LayoutDeviceIntRect devRect =
429         RoundedToInt(newBounds * GetDesktopToDeviceScale());
430     return CreatePopupContentView(devRect, aInitData);
431   }
433   mIsAnimationSuppressed = aInitData->mIsAnimationSuppressed;
435   return NS_OK;
437   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
440 nsresult nsCocoaWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
441                                const LayoutDeviceIntRect& aRect,
442                                widget::InitData* aInitData) {
443   DesktopIntRect desktopRect = RoundedToInt(
444       aRect / ParentBackingScaleFactor(aParent, (NSView*)aNativeParent));
445   return Create(aParent, aNativeParent, desktopRect, aInitData);
448 static unsigned int WindowMaskForBorderStyle(BorderStyle aBorderStyle) {
449   bool allOrDefault = (aBorderStyle == BorderStyle::All ||
450                        aBorderStyle == BorderStyle::Default);
452   /* Apple's docs on NSWindow styles say that "a window's style mask should
453    * include NSWindowStyleMaskTitled if it includes any of the others [besides
454    * NSWindowStyleMaskBorderless]".  This implies that a borderless window
455    * shouldn't have any other styles than NSWindowStyleMaskBorderless.
456    */
457   if (!allOrDefault && !(aBorderStyle & BorderStyle::Title)) {
458     if (aBorderStyle & BorderStyle::Minimize) {
459       /* It appears that at a minimum, borderless windows can be miniaturizable,
460        * effectively contradicting some of Apple's documentation referenced
461        * above. One such exception is the screen share indicator, see
462        * bug 1742877.
463        */
464       return NSWindowStyleMaskBorderless | NSWindowStyleMaskMiniaturizable;
465     }
466     return NSWindowStyleMaskBorderless;
467   }
469   unsigned int mask = NSWindowStyleMaskTitled;
470   if (allOrDefault || aBorderStyle & BorderStyle::Close) {
471     mask |= NSWindowStyleMaskClosable;
472   }
473   if (allOrDefault || aBorderStyle & BorderStyle::Minimize) {
474     mask |= NSWindowStyleMaskMiniaturizable;
475   }
476   if (allOrDefault || aBorderStyle & BorderStyle::ResizeH) {
477     mask |= NSWindowStyleMaskResizable;
478   }
480   return mask;
483 // If aRectIsFrameRect, aRect specifies the frame rect of the new window.
484 // Otherwise, aRect.x/y specify the position of the window's frame relative to
485 // the bottom of the menubar and aRect.width/height specify the size of the
486 // content rect.
487 nsresult nsCocoaWindow::CreateNativeWindow(const NSRect& aRect,
488                                            BorderStyle aBorderStyle,
489                                            bool aRectIsFrameRect,
490                                            bool aIsPrivateBrowsing) {
491   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
493   // We default to NSWindowStyleMaskBorderless, add features if needed.
494   unsigned int features = NSWindowStyleMaskBorderless;
496   // Configure the window we will create based on the window type.
497   switch (mWindowType) {
498     case WindowType::Invisible:
499     case WindowType::Child:
500       break;
501     case WindowType::Popup:
502       if (aBorderStyle != BorderStyle::Default &&
503           mBorderStyle & BorderStyle::Title) {
504         features |= NSWindowStyleMaskTitled;
505         if (aBorderStyle & BorderStyle::Close) {
506           features |= NSWindowStyleMaskClosable;
507         }
508       }
509       break;
510     case WindowType::TopLevel:
511     case WindowType::Dialog:
512       features = WindowMaskForBorderStyle(aBorderStyle);
513       break;
514     case WindowType::Sheet:
515       if (mParent->GetWindowType() != WindowType::Invisible &&
516           aBorderStyle & BorderStyle::ResizeH) {
517         features = NSWindowStyleMaskResizable;
518       } else {
519         features = NSWindowStyleMaskMiniaturizable;
520       }
521       features |= NSWindowStyleMaskTitled;
522       break;
523     default:
524       NS_ERROR("Unhandled window type!");
525       return NS_ERROR_FAILURE;
526   }
528   NSRect contentRect;
530   if (aRectIsFrameRect) {
531     contentRect = [NSWindow contentRectForFrameRect:aRect styleMask:features];
532   } else {
533     /*
534      * We pass a content area rect to initialize the native Cocoa window. The
535      * content rect we give is the same size as the size we're given by gecko.
536      * The origin we're given for non-popup windows is moved down by the height
537      * of the menu bar so that an origin of (0,100) from gecko puts the window
538      * 100 pixels below the top of the available desktop area. We also move the
539      * origin down by the height of a title bar if it exists. This is so the
540      * origin that gecko gives us for the top-left of  the window turns out to
541      * be the top-left of the window we create. This is how it was done in
542      * Carbon. If it ought to be different we'll probably need to look at all
543      * the callers.
544      *
545      * Note: This means that if you put a secondary screen on top of your main
546      * screen and open a window in the top screen, it'll be incorrectly shifted
547      * down by the height of the menu bar. Same thing would happen in Carbon.
548      *
549      * Note: If you pass a rect with 0,0 for an origin, the window ends up in a
550      * weird place for some reason. This stops that without breaking popups.
551      */
552     // Compensate for difference between frame and content area height (e.g.
553     // title bar).
554     NSRect newWindowFrame = [NSWindow frameRectForContentRect:aRect
555                                                     styleMask:features];
557     contentRect = aRect;
558     contentRect.origin.y -= (newWindowFrame.size.height - aRect.size.height);
560     if (mWindowType != WindowType::Popup)
561       contentRect.origin.y -= [[NSApp mainMenu] menuBarHeight];
562   }
564   // NSLog(@"Top-level window being created at Cocoa rect: %f, %f, %f, %f\n",
565   //       rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
567   Class windowClass = [BaseWindow class];
568   // If we have a titlebar on a top-level window, we want to be able to control
569   // the titlebar color (for unified windows), so use the special ToolbarWindow
570   // class. Note that we need to check the window type because we mark sheets as
571   // having titlebars.
572   if ((mWindowType == WindowType::TopLevel ||
573        mWindowType == WindowType::Dialog) &&
574       (features & NSWindowStyleMaskTitled))
575     windowClass = [ToolbarWindow class];
576   // If we're a popup window we need to use the PopupWindow class.
577   else if (mWindowType == WindowType::Popup)
578     windowClass = [PopupWindow class];
579   // If we're a non-popup borderless window we need to use the
580   // BorderlessWindow class.
581   else if (features == NSWindowStyleMaskBorderless)
582     windowClass = [BorderlessWindow class];
584   // Create the window
585   mWindow = [[windowClass alloc] initWithContentRect:contentRect
586                                            styleMask:features
587                                              backing:NSBackingStoreBuffered
588                                                defer:YES];
590   // Make sure that window titles don't leak to disk in private browsing mode
591   // due to macOS' resume feature.
592   [mWindow setRestorable:!aIsPrivateBrowsing];
593   if (aIsPrivateBrowsing) {
594     [mWindow disableSnapshotRestoration];
595   }
597   // setup our notification delegate. Note that setDelegate: does NOT retain.
598   mDelegate = [[WindowDelegate alloc] initWithGeckoWindow:this];
599   [mWindow setDelegate:mDelegate];
601   // Make sure that the content rect we gave has been honored.
602   NSRect wantedFrame = [mWindow frameRectForChildViewRect:contentRect];
603   if (!NSEqualRects([mWindow frame], wantedFrame)) {
604     // This can happen when the window is not on the primary screen.
605     [mWindow setFrame:wantedFrame display:NO];
606   }
607   UpdateBounds();
609   if (mWindowType == WindowType::Invisible) {
610     [mWindow setLevel:kCGDesktopWindowLevelKey];
611   }
613   if (mWindowType == WindowType::Popup) {
614     SetPopupWindowLevel();
615     [mWindow setBackgroundColor:[NSColor clearColor]];
616     [mWindow setOpaque:NO];
618     // When multiple spaces are in use and the browser is assigned to a
619     // particular space, override the "Assign To" space and display popups on
620     // the active space. Does not work with multiple displays. See
621     // NeedsRecreateToReshow() for multi-display with multi-space workaround.
622     if (!mAlwaysOnTop) {
623       NSWindowCollectionBehavior behavior = [mWindow collectionBehavior];
624       behavior |= NSWindowCollectionBehaviorMoveToActiveSpace;
625       [mWindow setCollectionBehavior:behavior];
626     }
627   } else {
628     // Non-popup windows are always opaque.
629     [mWindow setOpaque:YES];
630   }
632   NSWindowCollectionBehavior newBehavior = [mWindow collectionBehavior];
633   if (mAlwaysOnTop) {
634     [mWindow setLevel:NSFloatingWindowLevel];
635     newBehavior |= NSWindowCollectionBehaviorCanJoinAllSpaces;
636   }
637   [mWindow setCollectionBehavior:newBehavior];
639   [mWindow setContentMinSize:NSMakeSize(60, 60)];
640   [mWindow disableCursorRects];
642   // Make the window use CoreAnimation from the start, so that we don't
643   // switch from a non-CA window to a CA-window in the middle.
644   [[mWindow contentView] setWantsLayer:YES];
646   // Make sure the window starts out not draggable by the background.
647   // We will turn it on as necessary.
648   [mWindow setMovableByWindowBackground:NO];
650   [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow];
651   mWindowMadeHere = true;
653   // Make the window respect the global appearance, which follows the
654   // browser.theme.toolbar-theme pref.
655   mWindow.appearanceSource = MOZGlobalAppearance.sharedInstance;
657   return NS_OK;
659   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
662 nsresult nsCocoaWindow::CreatePopupContentView(const LayoutDeviceIntRect& aRect,
663                                                widget::InitData* aInitData) {
664   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
666   // We need to make our content view a ChildView.
667   mPopupContentView = new nsChildView();
668   if (!mPopupContentView) return NS_ERROR_FAILURE;
670   NS_ADDREF(mPopupContentView);
672   nsIWidget* thisAsWidget = static_cast<nsIWidget*>(this);
673   nsresult rv =
674       mPopupContentView->Create(thisAsWidget, nullptr, aRect, aInitData);
675   if (NS_WARN_IF(NS_FAILED(rv))) {
676     return rv;
677   }
679   NSView* contentView = [mWindow contentView];
680   ChildView* childView =
681       (ChildView*)mPopupContentView->GetNativeData(NS_NATIVE_WIDGET);
682   [childView setFrame:[contentView bounds]];
683   [childView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
684   [contentView addSubview:childView];
686   return NS_OK;
688   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
691 void nsCocoaWindow::Destroy() {
692   if (mOnDestroyCalled) return;
693   mOnDestroyCalled = true;
695   // SetFakeModal(true) is called for non-modal window opened by modal window.
696   // On Cocoa, it needs corresponding SetFakeModal(false) on destroy to restore
697   // ancestor windows' state.
698   if (mFakeModal) {
699     SetFakeModal(false);
700   }
702   // If we don't hide here we run into problems with panels, this is not ideal.
703   // (Bug 891424)
704   Show(false);
706   if (mPopupContentView) mPopupContentView->Destroy();
708   if (mFullscreenTransitionAnimation) {
709     [mFullscreenTransitionAnimation stopAnimation];
710     ReleaseFullscreenTransitionAnimation();
711   }
713   nsBaseWidget::Destroy();
714   // nsBaseWidget::Destroy() calls GetParent()->RemoveChild(this). But we
715   // don't implement GetParent(), so we need to do the equivalent here.
716   if (mParent) {
717     mParent->RemoveChild(this);
718   }
719   nsBaseWidget::OnDestroy();
721   if (mInFullScreenMode) {
722     // On Lion we don't have to mess with the OS chrome when in Full Screen
723     // mode.  But we do have to destroy the native window here (and not wait
724     // for that to happen in our destructor).  We don't switch away from the
725     // native window's space until the window is destroyed, and otherwise this
726     // might not happen for several seconds (because at least one object
727     // holding a reference to ourselves is usually waiting to be garbage-
728     // collected).  See bug 757618.
729     if (mInNativeFullScreenMode) {
730       DestroyNativeWindow();
731     } else if (mWindow) {
732       nsCocoaUtils::HideOSChromeOnScreen(false);
733     }
734   }
737 nsIWidget* nsCocoaWindow::GetSheetWindowParent(void) {
738   if (mWindowType != WindowType::Sheet) return nullptr;
739   nsCocoaWindow* parent = static_cast<nsCocoaWindow*>(mParent);
740   while (parent && (parent->mWindowType == WindowType::Sheet))
741     parent = static_cast<nsCocoaWindow*>(parent->mParent);
742   return parent;
745 void* nsCocoaWindow::GetNativeData(uint32_t aDataType) {
746   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
748   void* retVal = nullptr;
750   switch (aDataType) {
751     // to emulate how windows works, we always have to return a NSView
752     // for NS_NATIVE_WIDGET
753     case NS_NATIVE_WIDGET:
754       retVal = [mWindow contentView];
755       break;
757     case NS_NATIVE_WINDOW:
758       retVal = mWindow;
759       break;
761     case NS_NATIVE_GRAPHIC:
762       // There isn't anything that makes sense to return here,
763       // and it doesn't matter so just return nullptr.
764       NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a top-level window!");
765       break;
766     case NS_RAW_NATIVE_IME_CONTEXT: {
767       retVal = GetPseudoIMEContext();
768       if (retVal) {
769         break;
770       }
771       NSView* view = mWindow ? [mWindow contentView] : nil;
772       if (view) {
773         retVal = [view inputContext];
774       }
775       // If inputContext isn't available on this window, return this window's
776       // pointer instead of nullptr since if this returns nullptr,
777       // IMEStateManager cannot manage composition with TextComposition
778       // instance.  Although, this case shouldn't occur.
779       if (NS_WARN_IF(!retVal)) {
780         retVal = this;
781       }
782       break;
783     }
784   }
786   return retVal;
788   NS_OBJC_END_TRY_BLOCK_RETURN(nullptr);
791 bool nsCocoaWindow::IsVisible() const {
792   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
794   return (mWindow && ([mWindow isVisibleOrBeingShown] || mSheetNeedsShow));
796   NS_OBJC_END_TRY_BLOCK_RETURN(false);
799 void nsCocoaWindow::SetModal(bool aState) {
800   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
802   if (!mWindow) return;
804   // This is used during startup (outside the event loop) when creating
805   // the add-ons compatibility checking dialog and the profile manager UI;
806   // therefore, it needs to provide an autorelease pool to avoid cocoa
807   // objects leaking.
808   nsAutoreleasePool localPool;
810   mModal = aState;
811   nsCocoaWindow* ancestor = static_cast<nsCocoaWindow*>(mAncestorLink);
812   if (aState) {
813     ++gXULModalLevel;
814     // When a non-sheet window gets "set modal", make the window(s) that it
815     // appears over behave as they should.  We can't rely on native methods to
816     // do this, for the following reason:  The OS runs modal non-sheet windows
817     // in an event loop (using [NSApplication runModalForWindow:] or similar
818     // methods) that's incompatible with the modal event loop in AppWindow::
819     // ShowModal() (each of these event loops is "exclusive", and can't run at
820     // the same time as other (similar) event loops).
821     if (mWindowType != WindowType::Sheet) {
822       while (ancestor) {
823         if (ancestor->mNumModalDescendents++ == 0) {
824           NSWindow* aWindow = ancestor->GetCocoaWindow();
825           if (ancestor->mWindowType != WindowType::Invisible) {
826             [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:NO];
827             [[aWindow standardWindowButton:NSWindowMiniaturizeButton]
828                 setEnabled:NO];
829             [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:NO];
830           }
831         }
832         ancestor = static_cast<nsCocoaWindow*>(ancestor->mParent);
833       }
834       [mWindow setLevel:NSModalPanelWindowLevel];
835       nsCocoaWindowList* windowList = new nsCocoaWindowList;
836       if (windowList) {
837         windowList->window = this;  // Don't ADDREF
838         windowList->prev = gGeckoAppModalWindowList;
839         gGeckoAppModalWindowList = windowList;
840       }
841     }
842   } else {
843     --gXULModalLevel;
844     NS_ASSERTION(gXULModalLevel >= 0,
845                  "Mismatched call to nsCocoaWindow::SetModal(false)!");
846     if (mWindowType != WindowType::Sheet) {
847       while (ancestor) {
848         if (--ancestor->mNumModalDescendents == 0) {
849           NSWindow* aWindow = ancestor->GetCocoaWindow();
850           if (ancestor->mWindowType != WindowType::Invisible) {
851             [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:YES];
852             [[aWindow standardWindowButton:NSWindowMiniaturizeButton]
853                 setEnabled:YES];
854             [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:YES];
855           }
856         }
857         NS_ASSERTION(ancestor->mNumModalDescendents >= 0,
858                      "Widget hierarchy changed while modal!");
859         ancestor = static_cast<nsCocoaWindow*>(ancestor->mParent);
860       }
861       if (gGeckoAppModalWindowList) {
862         NS_ASSERTION(gGeckoAppModalWindowList->window == this,
863                      "Widget hierarchy changed while modal!");
864         nsCocoaWindowList* saved = gGeckoAppModalWindowList;
865         gGeckoAppModalWindowList = gGeckoAppModalWindowList->prev;
866         delete saved;  // "window" not ADDREFed
867       }
868       if (mWindowType == WindowType::Popup)
869         SetPopupWindowLevel();
870       else
871         [mWindow setLevel:NSNormalWindowLevel];
872     }
873   }
875   NS_OBJC_END_TRY_IGNORE_BLOCK;
878 void nsCocoaWindow::SetFakeModal(bool aState) {
879   mFakeModal = aState;
880   SetModal(aState);
883 bool nsCocoaWindow::IsRunningAppModal() { return [NSApp _isRunningAppModal]; }
885 // Hide or show this window
886 void nsCocoaWindow::Show(bool bState) {
887   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
889   if (!mWindow) return;
891   if (!mSheetNeedsShow) {
892     // Early exit if our current visibility state is already the requested
893     // state.
894     if (bState == ([mWindow isVisible] || [mWindow isBeingShown])) {
895       return;
896     }
897   }
899   [mWindow setBeingShown:bState];
900   if (bState && !mWasShown) {
901     mWasShown = true;
902   }
904   nsIWidget* parentWidget = mParent;
905   nsCOMPtr<nsPIWidgetCocoa> piParentWidget(do_QueryInterface(parentWidget));
906   NSWindow* nativeParentWindow =
907       (parentWidget) ? (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW)
908                      : nil;
910   if (bState && !mBounds.IsEmpty()) {
911     // If we had set the activationPolicy to accessory, then right now we won't
912     // have a dock icon. Make sure that we undo that and show a dock icon now
913     // that we're going to show a window.
914     if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular) {
915       [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
916       PR_SetEnv("MOZ_APP_NO_DOCK=");
917     }
919     // Don't try to show a popup when the parent isn't visible or is minimized.
920     if (mWindowType == WindowType::Popup && nativeParentWindow) {
921       if (![nativeParentWindow isVisible] ||
922           [nativeParentWindow isMiniaturized]) {
923         return;
924       }
925     }
927     if (mPopupContentView) {
928       // Ensure our content view is visible. We never need to hide it.
929       mPopupContentView->Show(true);
930     }
932     if (mWindowType == WindowType::Sheet) {
933       // bail if no parent window (its basically what we do in Carbon)
934       if (!nativeParentWindow || !piParentWidget) return;
936       NSWindow* topNonSheetWindow = nativeParentWindow;
938       // If this sheet is the child of another sheet, hide the parent so that
939       // this sheet can be displayed. Leave the parent mSheetNeedsShow alone,
940       // that is only used to handle sibling sheet contention. The parent will
941       // return once there are no more child sheets.
942       bool parentIsSheet = false;
943       if (NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) &&
944           parentIsSheet) {
945         piParentWidget->GetSheetWindowParent(&topNonSheetWindow);
946 #ifdef MOZ_THUNDERBIRD
947         [NSApp endSheet:nativeParentWindow];
948 #else
949         [nativeParentWindow.sheetParent endSheet:nativeParentWindow];
950 #endif
951       }
953       nsCOMPtr<nsIWidget> sheetShown;
954       if (NS_SUCCEEDED(piParentWidget->GetChildSheet(
955               true, getter_AddRefs(sheetShown))) &&
956           (!sheetShown || sheetShown == this)) {
957         // If this sheet is already the sheet actually being shown, don't
958         // tell it to show again. Otherwise the number of calls to
959 #ifdef MOZ_THUNDERBIRD
960         // [NSApp beginSheet...] won't match up with [NSApp endSheet...].
961 #else
962         // [NSWindow beginSheet...] won't match up with [NSWindow endSheet...].
963 #endif
964         if (![mWindow isVisible]) {
965           mSheetNeedsShow = false;
966           mSheetWindowParent = topNonSheetWindow;
967 #ifdef MOZ_THUNDERBIRD
968           // Only set contextInfo if our parent isn't a sheet.
969           NSWindow* contextInfo = parentIsSheet ? nil : mSheetWindowParent;
970           [TopLevelWindowData deactivateInWindow:mSheetWindowParent];
971           [NSApp beginSheet:mWindow
972               modalForWindow:mSheetWindowParent
973                modalDelegate:mDelegate
974               didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
975                  contextInfo:contextInfo];
976 #else
977           NSWindow* sheet = mWindow;
978           NSWindow* nonSheetParent = parentIsSheet ? nil : mSheetWindowParent;
979           [TopLevelWindowData deactivateInWindow:mSheetWindowParent];
980           [mSheetWindowParent beginSheet:sheet
981                        completionHandler:^(NSModalResponse returnCode) {
982                          // Note: 'nonSheetParent' (if it is set) is the window
983                          // that is the parent of the sheet. If it's set,
984                          // 'nonSheetParent' is always the top- level window,
985                          // not another sheet itself.  But 'nonSheetParent' is
986                          // nil if our parent window is also a sheet -- in that
987                          // case we shouldn't send the top-level window any
988                          // activate events (because it's our parent window that
989                          // needs to get these events, not the top-level
990                          // window).
991                          [TopLevelWindowData deactivateInWindow:sheet];
992                          [sheet orderOut:nil];
993                          if (nonSheetParent) {
994                            [TopLevelWindowData activateInWindow:nonSheetParent];
995                          }
996                        }];
997 #endif
998           [TopLevelWindowData activateInWindow:mWindow];
999           SendSetZLevelEvent();
1000         }
1001       } else {
1002         // A sibling of this sheet is active, don't show this sheet yet.
1003         // When the active sheet hides, its brothers and sisters that have
1004         // mSheetNeedsShow set will have their opportunities to display.
1005         mSheetNeedsShow = true;
1006       }
1007     } else if (mWindowType == WindowType::Popup) {
1008       // For reasons that aren't yet clear, calls to [NSWindow orderFront:] or
1009       // [NSWindow makeKeyAndOrderFront:] can sometimes trigger "Error (1000)
1010       // creating CGSWindow", which in turn triggers an internal inconsistency
1011       // NSException.  These errors shouldn't be fatal.  So we need to wrap
1012       // calls to ...orderFront: in TRY blocks.  See bmo bug 470864.
1013       NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1014       [[mWindow contentView] setNeedsDisplay:YES];
1015       [mWindow orderFront:nil];
1016       NS_OBJC_END_TRY_IGNORE_BLOCK;
1017       SendSetZLevelEvent();
1018       // If our popup window is a non-native context menu, tell the OS (and
1019       // other programs) that a menu has opened.  This is how the OS knows to
1020       // close other programs' context menus when ours open.
1021       if ([mWindow isKindOfClass:[PopupWindow class]] &&
1022           [(PopupWindow*)mWindow isContextMenu]) {
1023         [[NSDistributedNotificationCenter defaultCenter]
1024             postNotificationName:
1025                 @"com.apple.HIToolbox.beginMenuTrackingNotification"
1026                           object:@"org.mozilla.gecko.PopupWindow"];
1027       }
1029       // If a parent window was supplied and this is a popup at the parent
1030       // level, set its child window. This will cause the child window to
1031       // appear above the parent and move when the parent does. Setting this
1032       // needs to happen after the _setWindowNumber calls above, otherwise the
1033       // window doesn't focus properly.
1034       if (nativeParentWindow && mPopupLevel == PopupLevel::Parent)
1035         [nativeParentWindow addChildWindow:mWindow ordered:NSWindowAbove];
1036     } else {
1037       NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1038       if (mWindowType == WindowType::TopLevel &&
1039           [mWindow respondsToSelector:@selector(setAnimationBehavior:)]) {
1040         NSWindowAnimationBehavior behavior;
1041         if (mIsAnimationSuppressed) {
1042           behavior = NSWindowAnimationBehaviorNone;
1043         } else {
1044           switch (mAnimationType) {
1045             case nsIWidget::eDocumentWindowAnimation:
1046               behavior = NSWindowAnimationBehaviorDocumentWindow;
1047               break;
1048             default:
1049               MOZ_FALLTHROUGH_ASSERT("unexpected mAnimationType value");
1050             case nsIWidget::eGenericWindowAnimation:
1051               behavior = NSWindowAnimationBehaviorDefault;
1052               break;
1053           }
1054         }
1055         [mWindow setAnimationBehavior:behavior];
1056         mWindowAnimationBehavior = behavior;
1057       }
1059       // We don't want alwaysontop windows to pull focus when they're opened,
1060       // as these tend to be for peripheral indicators and displays.
1061       if (mAlwaysOnTop) {
1062         [mWindow orderFront:nil];
1063       } else {
1064         [mWindow makeKeyAndOrderFront:nil];
1065       }
1066       NS_OBJC_END_TRY_IGNORE_BLOCK;
1067       SendSetZLevelEvent();
1068     }
1069   } else {
1070     // roll up any popups if a top-level window is going away
1071     if (mWindowType == WindowType::TopLevel ||
1072         mWindowType == WindowType::Dialog) {
1073       RollUpPopups();
1074     }
1076     // now get rid of the window/sheet
1077     if (mWindowType == WindowType::Sheet) {
1078       if (mSheetNeedsShow) {
1079         // This is an attempt to hide a sheet that never had a chance to
1080         // be shown. There's nothing to do other than make sure that it
1081         // won't show.
1082         mSheetNeedsShow = false;
1083       } else {
1084         // get sheet's parent *before* hiding the sheet (which breaks the
1085         // linkage)
1086         NSWindow* sheetParent = mSheetWindowParent;
1088         // hide the sheet
1089 #ifdef MOZ_THUNDERBIRD
1090         [NSApp endSheet:mWindow];
1091 #else
1092         [mSheetWindowParent endSheet:mWindow];
1093 #endif
1094         [TopLevelWindowData deactivateInWindow:mWindow];
1096         nsCOMPtr<nsIWidget> siblingSheetToShow;
1097         bool parentIsSheet = false;
1099         if (nativeParentWindow && piParentWidget &&
1100             NS_SUCCEEDED(piParentWidget->GetChildSheet(
1101                 false, getter_AddRefs(siblingSheetToShow))) &&
1102             siblingSheetToShow) {
1103           // First, give sibling sheets an opportunity to show.
1104           siblingSheetToShow->Show(true);
1105         } else if (nativeParentWindow && piParentWidget &&
1106                    NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) &&
1107                    parentIsSheet) {
1108 #ifdef MOZ_THUNDERBIRD
1109           // Only set contextInfo if the parent of the parent sheet we're about
1110           // to restore isn't itself a sheet.
1111           NSWindow* contextInfo = sheetParent;
1112 #else
1113           // Only set nonSheetGrandparent if the parent of the parent sheet
1114           // we're about to restore isn't itself a sheet.
1115           NSWindow* nonSheetGrandparent = sheetParent;
1116 #endif
1117           nsIWidget* grandparentWidget = nil;
1118           if (NS_SUCCEEDED(piParentWidget->GetRealParent(&grandparentWidget)) &&
1119               grandparentWidget) {
1120             nsCOMPtr<nsPIWidgetCocoa> piGrandparentWidget(
1121                 do_QueryInterface(grandparentWidget));
1122             bool grandparentIsSheet = false;
1123             if (piGrandparentWidget &&
1124                 NS_SUCCEEDED(
1125                     piGrandparentWidget->GetIsSheet(&grandparentIsSheet)) &&
1126                 grandparentIsSheet) {
1127 #ifdef MOZ_THUNDERBIRD
1128               contextInfo = nil;
1129 #else
1130               nonSheetGrandparent = nil;
1131 #endif
1132             }
1133           }
1134           // If there are no sibling sheets, but the parent is a sheet, restore
1135           // it.  It wasn't sent any deactivate events when it was hidden, so
1136           // don't call through Show, just let the OS put it back up.
1137 #ifdef MOZ_THUNDERBIRD
1138           [NSApp beginSheet:nativeParentWindow
1139               modalForWindow:sheetParent
1140                modalDelegate:[nativeParentWindow delegate]
1141               didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
1142                  contextInfo:contextInfo];
1143 #else
1144           [nativeParentWindow
1145                      beginSheet:sheetParent
1146               completionHandler:^(NSModalResponse returnCode) {
1147                 // Note: 'nonSheetGrandparent' (if it is set) is the window that
1148                 // is the parent of sheetParent. If it's set,
1149                 // 'nonSheetGrandparent' is always the top-level window, not
1150                 // another sheet itself.  But 'nonSheetGrandparent' is nil if
1151                 // our parent window is also a sheet -- in that case we
1152                 // shouldn't send the top-level window any activate events
1153                 // (because it's our parent window that needs to get these
1154                 // events, not the top-level window).
1155                 [TopLevelWindowData deactivateInWindow:sheetParent];
1156                 [sheetParent orderOut:nil];
1157                 if (nonSheetGrandparent) {
1158                   [TopLevelWindowData activateInWindow:nonSheetGrandparent];
1159                 }
1160               }];
1161 #endif
1162         } else {
1163           // Sheet, that was hard.  No more siblings or parents, going back
1164           // to a real window.
1165           NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1166           [sheetParent makeKeyAndOrderFront:nil];
1167           NS_OBJC_END_TRY_IGNORE_BLOCK;
1168         }
1169         SendSetZLevelEvent();
1170       }
1171     } else {
1172       // If the window is a popup window with a parent window we need to
1173       // unhook it here before ordering it out. When you order out the child
1174       // of a window it hides the parent window.
1175       if (mWindowType == WindowType::Popup && nativeParentWindow)
1176         [nativeParentWindow removeChildWindow:mWindow];
1178       [mWindow orderOut:nil];
1180       // If our popup window is a non-native context menu, tell the OS (and
1181       // other programs) that a menu has closed.
1182       if ([mWindow isKindOfClass:[PopupWindow class]] &&
1183           [(PopupWindow*)mWindow isContextMenu]) {
1184         [[NSDistributedNotificationCenter defaultCenter]
1185             postNotificationName:
1186                 @"com.apple.HIToolbox.endMenuTrackingNotification"
1187                           object:@"org.mozilla.gecko.PopupWindow"];
1188       }
1189     }
1190   }
1192   [mWindow setBeingShown:NO];
1194   NS_OBJC_END_TRY_IGNORE_BLOCK;
1197 // Work around a problem where with multiple displays and multiple spaces
1198 // enabled, where the browser is assigned to a single display or space, popup
1199 // windows that are reshown after being hidden with [NSWindow orderOut] show on
1200 // the assigned space even when opened from another display. Apply the
1201 // workaround whenever more than one display is enabled.
1202 bool nsCocoaWindow::NeedsRecreateToReshow() {
1203   // Limit the workaround to popup windows because only they need to override
1204   // the "Assign To" setting. i.e., to display where the parent window is.
1205   return (mWindowType == WindowType::Popup) && mWasShown &&
1206          ([[NSScreen screens] count] > 1);
1209 WindowRenderer* nsCocoaWindow::GetWindowRenderer() {
1210   if (mPopupContentView) {
1211     return mPopupContentView->GetWindowRenderer();
1212   }
1213   return nullptr;
1216 TransparencyMode nsCocoaWindow::GetTransparencyMode() {
1217   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1219   return (!mWindow || [mWindow isOpaque]) ? TransparencyMode::Opaque
1220                                           : TransparencyMode::Transparent;
1222   NS_OBJC_END_TRY_BLOCK_RETURN(TransparencyMode::Opaque);
1225 // This is called from nsMenuPopupFrame when making a popup transparent.
1226 void nsCocoaWindow::SetTransparencyMode(TransparencyMode aMode) {
1227   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1229   // Only respect calls for popup windows.
1230   if (!mWindow || mWindowType != WindowType::Popup) {
1231     return;
1232   }
1234   BOOL isTransparent = aMode == TransparencyMode::Transparent;
1235   BOOL currentTransparency = ![mWindow isOpaque];
1236   if (isTransparent != currentTransparency) {
1237     [mWindow setOpaque:!isTransparent];
1238     [mWindow setBackgroundColor:(isTransparent ? [NSColor clearColor]
1239                                                : [NSColor whiteColor])];
1240   }
1242   NS_OBJC_END_TRY_IGNORE_BLOCK;
1245 void nsCocoaWindow::Enable(bool aState) {}
1247 bool nsCocoaWindow::IsEnabled() const { return true; }
1249 void nsCocoaWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
1250   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1252   if (!mWindow || ![mWindow screen]) {
1253     return;
1254   }
1256   nsIntRect screenBounds;
1258   int32_t width, height;
1260   NSRect frame = [mWindow frame];
1262   // zero size rects confuse the screen manager
1263   width = std::max<int32_t>(frame.size.width, 1);
1264   height = std::max<int32_t>(frame.size.height, 1);
1266   nsCOMPtr<nsIScreenManager> screenMgr =
1267       do_GetService("@mozilla.org/gfx/screenmanager;1");
1268   if (screenMgr) {
1269     nsCOMPtr<nsIScreen> screen;
1270     screenMgr->ScreenForRect(aPoint.x, aPoint.y, width, height,
1271                              getter_AddRefs(screen));
1273     if (screen) {
1274       screen->GetRectDisplayPix(&(screenBounds.x), &(screenBounds.y),
1275                                 &(screenBounds.width), &(screenBounds.height));
1276     }
1277   }
1279   if (aPoint.x < screenBounds.x) {
1280     aPoint.x = screenBounds.x;
1281   } else if (aPoint.x >= screenBounds.x + screenBounds.width - width) {
1282     aPoint.x = screenBounds.x + screenBounds.width - width;
1283   }
1285   if (aPoint.y < screenBounds.y) {
1286     aPoint.y = screenBounds.y;
1287   } else if (aPoint.y >= screenBounds.y + screenBounds.height - height) {
1288     aPoint.y = screenBounds.y + screenBounds.height - height;
1289   }
1291   NS_OBJC_END_TRY_IGNORE_BLOCK;
1294 void nsCocoaWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
1295   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1297   // Popups can be smaller than (32, 32)
1298   NSRect rect = (mWindowType == WindowType::Popup)
1299                     ? NSZeroRect
1300                     : NSMakeRect(0.0, 0.0, 32, 32);
1301   rect = [mWindow frameRectForChildViewRect:rect];
1303   SizeConstraints c = aConstraints;
1305   if (c.mScale.scale == MOZ_WIDGET_INVALID_SCALE) {
1306     c.mScale.scale = BackingScaleFactor();
1307   }
1309   c.mMinSize.width = std::max(
1310       nsCocoaUtils::CocoaPointsToDevPixels(rect.size.width, c.mScale.scale),
1311       c.mMinSize.width);
1312   c.mMinSize.height = std::max(
1313       nsCocoaUtils::CocoaPointsToDevPixels(rect.size.height, c.mScale.scale),
1314       c.mMinSize.height);
1316   NSSize minSize = {
1317       nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.width, c.mScale.scale),
1318       nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.height, c.mScale.scale)};
1319   [mWindow setMinSize:minSize];
1321   c.mMaxSize.width = std::max(
1322       nsCocoaUtils::CocoaPointsToDevPixels(c.mMaxSize.width, c.mScale.scale),
1323       c.mMaxSize.width);
1324   c.mMaxSize.height = std::max(
1325       nsCocoaUtils::CocoaPointsToDevPixels(c.mMaxSize.height, c.mScale.scale),
1326       c.mMaxSize.height);
1328   NSSize maxSize = {
1329       c.mMaxSize.width == NS_MAXSIZE ? FLT_MAX
1330                                      : nsCocoaUtils::DevPixelsToCocoaPoints(
1331                                            c.mMaxSize.width, c.mScale.scale),
1332       c.mMaxSize.height == NS_MAXSIZE ? FLT_MAX
1333                                       : nsCocoaUtils::DevPixelsToCocoaPoints(
1334                                             c.mMaxSize.height, c.mScale.scale)};
1335   [mWindow setMaxSize:maxSize];
1337   nsBaseWidget::SetSizeConstraints(c);
1339   NS_OBJC_END_TRY_IGNORE_BLOCK;
1342 // Coordinates are desktop pixels
1343 void nsCocoaWindow::Move(double aX, double aY) {
1344   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1346   if (!mWindow) {
1347     return;
1348   }
1350   // The point we have is in Gecko coordinates (origin top-left). Convert
1351   // it to Cocoa ones (origin bottom-left).
1352   NSPoint coord = {
1353       static_cast<float>(aX),
1354       static_cast<float>(nsCocoaUtils::FlippedScreenY(NSToIntRound(aY)))};
1356   NSRect frame = [mWindow frame];
1357   if (frame.origin.x != coord.x ||
1358       frame.origin.y + frame.size.height != coord.y) {
1359     [mWindow setFrameTopLeftPoint:coord];
1360   }
1362   NS_OBJC_END_TRY_IGNORE_BLOCK;
1365 void nsCocoaWindow::SetSizeMode(nsSizeMode aMode) {
1366   if (aMode == nsSizeMode_Normal) {
1367     QueueTransition(TransitionType::Windowed);
1368   } else if (aMode == nsSizeMode_Minimized) {
1369     QueueTransition(TransitionType::Miniaturize);
1370   } else if (aMode == nsSizeMode_Maximized) {
1371     QueueTransition(TransitionType::Zoom);
1372   } else if (aMode == nsSizeMode_Fullscreen) {
1373     MakeFullScreen(true);
1374   }
1377 // The (work)space switching implementation below was inspired by Phoenix:
1378 // https://github.com/kasper/phoenix/tree/d6c877f62b30a060dff119d8416b0934f76af534
1379 // License: MIT.
1381 // Runtime `CGSGetActiveSpace` library function feature detection.
1382 typedef CGSSpaceID (*CGSGetActiveSpaceFunc)(CGSConnection cid);
1383 static CGSGetActiveSpaceFunc GetCGSGetActiveSpaceFunc() {
1384   static CGSGetActiveSpaceFunc func = nullptr;
1385   static bool lookedUpFunc = false;
1386   if (!lookedUpFunc) {
1387     func = (CGSGetActiveSpaceFunc)dlsym(RTLD_DEFAULT, "CGSGetActiveSpace");
1388     lookedUpFunc = true;
1389   }
1390   return func;
1392 // Runtime `CGSCopyManagedDisplaySpaces` library function feature detection.
1393 typedef CFArrayRef (*CGSCopyManagedDisplaySpacesFunc)(CGSConnection cid);
1394 static CGSCopyManagedDisplaySpacesFunc GetCGSCopyManagedDisplaySpacesFunc() {
1395   static CGSCopyManagedDisplaySpacesFunc func = nullptr;
1396   static bool lookedUpFunc = false;
1397   if (!lookedUpFunc) {
1398     func = (CGSCopyManagedDisplaySpacesFunc)dlsym(
1399         RTLD_DEFAULT, "CGSCopyManagedDisplaySpaces");
1400     lookedUpFunc = true;
1401   }
1402   return func;
1404 // Runtime `CGSCopySpacesForWindows` library function feature detection.
1405 typedef CFArrayRef (*CGSCopySpacesForWindowsFunc)(CGSConnection cid,
1406                                                   CGSSpaceMask mask,
1407                                                   CFArrayRef windowIDs);
1408 static CGSCopySpacesForWindowsFunc GetCGSCopySpacesForWindowsFunc() {
1409   static CGSCopySpacesForWindowsFunc func = nullptr;
1410   static bool lookedUpFunc = false;
1411   if (!lookedUpFunc) {
1412     func = (CGSCopySpacesForWindowsFunc)dlsym(RTLD_DEFAULT,
1413                                               "CGSCopySpacesForWindows");
1414     lookedUpFunc = true;
1415   }
1416   return func;
1418 // Runtime `CGSAddWindowsToSpaces` library function feature detection.
1419 typedef void (*CGSAddWindowsToSpacesFunc)(CGSConnection cid,
1420                                           CFArrayRef windowIDs,
1421                                           CFArrayRef spaceIDs);
1422 static CGSAddWindowsToSpacesFunc GetCGSAddWindowsToSpacesFunc() {
1423   static CGSAddWindowsToSpacesFunc func = nullptr;
1424   static bool lookedUpFunc = false;
1425   if (!lookedUpFunc) {
1426     func =
1427         (CGSAddWindowsToSpacesFunc)dlsym(RTLD_DEFAULT, "CGSAddWindowsToSpaces");
1428     lookedUpFunc = true;
1429   }
1430   return func;
1432 // Runtime `CGSRemoveWindowsFromSpaces` library function feature detection.
1433 typedef void (*CGSRemoveWindowsFromSpacesFunc)(CGSConnection cid,
1434                                                CFArrayRef windowIDs,
1435                                                CFArrayRef spaceIDs);
1436 static CGSRemoveWindowsFromSpacesFunc GetCGSRemoveWindowsFromSpacesFunc() {
1437   static CGSRemoveWindowsFromSpacesFunc func = nullptr;
1438   static bool lookedUpFunc = false;
1439   if (!lookedUpFunc) {
1440     func = (CGSRemoveWindowsFromSpacesFunc)dlsym(RTLD_DEFAULT,
1441                                                  "CGSRemoveWindowsFromSpaces");
1442     lookedUpFunc = true;
1443   }
1444   return func;
1447 void nsCocoaWindow::GetWorkspaceID(nsAString& workspaceID) {
1448   workspaceID.Truncate();
1449   int32_t sid = GetWorkspaceID();
1450   if (sid != 0) {
1451     workspaceID.AppendInt(sid);
1452   }
1455 int32_t nsCocoaWindow::GetWorkspaceID() {
1456   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1458   // Mac OSX space IDs start at '1' (default space), so '0' means 'unknown',
1459   // effectively.
1460   CGSSpaceID sid = 0;
1462   CGSCopySpacesForWindowsFunc CopySpacesForWindows =
1463       GetCGSCopySpacesForWindowsFunc();
1464   if (!CopySpacesForWindows) {
1465     return sid;
1466   }
1468   CGSConnection cid = _CGSDefaultConnection();
1469   // Fetch all spaces that this window belongs to (in order).
1470   NSArray<NSNumber*>* spaceIDs = CFBridgingRelease(CopySpacesForWindows(
1471       cid, kCGSAllSpacesMask,
1472       (__bridge CFArrayRef) @[ @([mWindow windowNumber]) ]));
1473   if ([spaceIDs count]) {
1474     // When spaces are found, return the first one.
1475     // We don't support a single window painted across multiple places for now.
1476     sid = [spaceIDs[0] integerValue];
1477   } else {
1478     // Fall back to the workspace that's currently active, which is '1' in the
1479     // common case.
1480     CGSGetActiveSpaceFunc GetActiveSpace = GetCGSGetActiveSpaceFunc();
1481     if (GetActiveSpace) {
1482       sid = GetActiveSpace(cid);
1483     }
1484   }
1486   return sid;
1488   NS_OBJC_END_TRY_IGNORE_BLOCK;
1491 void nsCocoaWindow::MoveToWorkspace(const nsAString& workspaceIDStr) {
1492   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1494   if ([NSScreen screensHaveSeparateSpaces] && [[NSScreen screens] count] > 1) {
1495     // We don't support moving to a workspace when the user has this option
1496     // enabled in Mission Control.
1497     return;
1498   }
1500   nsresult rv = NS_OK;
1501   int32_t workspaceID = workspaceIDStr.ToInteger(&rv);
1502   if (NS_FAILED(rv)) {
1503     return;
1504   }
1506   CGSConnection cid = _CGSDefaultConnection();
1507   int32_t currentSpace = GetWorkspaceID();
1508   // If an empty workspace ID is passed in (not valid on OSX), or when the
1509   // window is already on this workspace, we don't need to do anything.
1510   if (!workspaceID || workspaceID == currentSpace) {
1511     return;
1512   }
1514   CGSCopyManagedDisplaySpacesFunc CopyManagedDisplaySpaces =
1515       GetCGSCopyManagedDisplaySpacesFunc();
1516   CGSAddWindowsToSpacesFunc AddWindowsToSpaces = GetCGSAddWindowsToSpacesFunc();
1517   CGSRemoveWindowsFromSpacesFunc RemoveWindowsFromSpaces =
1518       GetCGSRemoveWindowsFromSpacesFunc();
1519   if (!CopyManagedDisplaySpaces || !AddWindowsToSpaces ||
1520       !RemoveWindowsFromSpaces) {
1521     return;
1522   }
1524   // Fetch an ordered list of all known spaces.
1525   NSArray* displaySpacesInfo = CFBridgingRelease(CopyManagedDisplaySpaces(cid));
1526   // When we found the space we're looking for, we can bail out of the loop
1527   // early, which this local variable is used for.
1528   BOOL found = false;
1529   for (NSDictionary<NSString*, id>* spacesInfo in displaySpacesInfo) {
1530     NSArray<NSNumber*>* sids =
1531         [spacesInfo[CGSSpacesKey] valueForKey:CGSSpaceIDKey];
1532     for (NSNumber* sid in sids) {
1533       // If we found our space in the list, we're good to go and can jump out of
1534       // this loop.
1535       if ((int)[sid integerValue] == workspaceID) {
1536         found = true;
1537         break;
1538       }
1539     }
1540     if (found) {
1541       break;
1542     }
1543   }
1545   // We were unable to find the space to correspond with the workspaceID as
1546   // requested, so let's bail out.
1547   if (!found) {
1548     return;
1549   }
1551   // First we add the window to the appropriate space.
1552   AddWindowsToSpaces(cid, (__bridge CFArrayRef) @[ @([mWindow windowNumber]) ],
1553                      (__bridge CFArrayRef) @[ @(workspaceID) ]);
1554   // Then we remove the window from the active space.
1555   RemoveWindowsFromSpaces(cid,
1556                           (__bridge CFArrayRef) @[ @([mWindow windowNumber]) ],
1557                           (__bridge CFArrayRef) @[ @(currentSpace) ]);
1559   NS_OBJC_END_TRY_IGNORE_BLOCK;
1562 void nsCocoaWindow::SuppressAnimation(bool aSuppress) {
1563   if ([mWindow respondsToSelector:@selector(setAnimationBehavior:)]) {
1564     if (aSuppress) {
1565       [mWindow setIsAnimationSuppressed:YES];
1566       [mWindow setAnimationBehavior:NSWindowAnimationBehaviorNone];
1567     } else {
1568       [mWindow setIsAnimationSuppressed:NO];
1569       [mWindow setAnimationBehavior:mWindowAnimationBehavior];
1570     }
1571   }
1574 // This has to preserve the window's frame bounds.
1575 // This method requires (as does the Windows impl.) that you call Resize shortly
1576 // after calling HideWindowChrome. See bug 498835 for fixing this.
1577 void nsCocoaWindow::HideWindowChrome(bool aShouldHide) {
1578   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1580   if (!mWindow || !mWindowMadeHere ||
1581       (mWindowType != WindowType::TopLevel &&
1582        mWindowType != WindowType::Dialog))
1583     return;
1585   BOOL isVisible = [mWindow isVisible];
1587   // Remove child windows.
1588   NSArray* childWindows = [mWindow childWindows];
1589   NSEnumerator* enumerator = [childWindows objectEnumerator];
1590   NSWindow* child = nil;
1591   while ((child = [enumerator nextObject])) {
1592     [mWindow removeChildWindow:child];
1593   }
1595   // Remove the views in the old window's content view.
1596   // The NSArray is autoreleased and retains its NSViews.
1597   NSArray<NSView*>* contentViewContents = [mWindow contentViewContents];
1598   for (NSView* view in contentViewContents) {
1599     [view removeFromSuperviewWithoutNeedingDisplay];
1600   }
1602   // Save state (like window title).
1603   NSMutableDictionary* state = [mWindow exportState];
1605   // Recreate the window with the right border style.
1606   NSRect frameRect = [mWindow frame];
1607   DestroyNativeWindow();
1608   nsresult rv = CreateNativeWindow(
1609       frameRect, aShouldHide ? BorderStyle::None : mBorderStyle, true,
1610       mWindow.restorable);
1611   NS_ENSURE_SUCCESS_VOID(rv);
1613   // Re-import state.
1614   [mWindow importState:state];
1616   // Add the old content view subviews to the new window's content view.
1617   for (NSView* view in contentViewContents) {
1618     [[mWindow contentView] addSubview:view];
1619   }
1621   // Reparent child windows.
1622   enumerator = [childWindows objectEnumerator];
1623   while ((child = [enumerator nextObject])) {
1624     [mWindow addChildWindow:child ordered:NSWindowAbove];
1625   }
1627   // Show the new window.
1628   if (isVisible) {
1629     bool wasAnimationSuppressed = mIsAnimationSuppressed;
1630     mIsAnimationSuppressed = true;
1631     Show(true);
1632     mIsAnimationSuppressed = wasAnimationSuppressed;
1633   }
1635   NS_OBJC_END_TRY_IGNORE_BLOCK;
1638 class FullscreenTransitionData : public nsISupports {
1639  public:
1640   NS_DECL_ISUPPORTS
1642   explicit FullscreenTransitionData(NSWindow* aWindow)
1643       : mTransitionWindow(aWindow) {}
1645   NSWindow* mTransitionWindow;
1647  private:
1648   virtual ~FullscreenTransitionData() { [mTransitionWindow close]; }
1651 NS_IMPL_ISUPPORTS0(FullscreenTransitionData)
1653 @interface FullscreenTransitionDelegate : NSObject <NSAnimationDelegate> {
1654  @public
1655   nsCocoaWindow* mWindow;
1656   nsIRunnable* mCallback;
1658 @end
1660 @implementation FullscreenTransitionDelegate
1661 - (void)cleanupAndDispatch:(NSAnimation*)animation {
1662   [animation setDelegate:nil];
1663   [self autorelease];
1664   // The caller should have added ref for us.
1665   NS_DispatchToMainThread(already_AddRefed<nsIRunnable>(mCallback));
1668 - (void)animationDidEnd:(NSAnimation*)animation {
1669   MOZ_ASSERT(animation == mWindow->FullscreenTransitionAnimation(),
1670              "Should be handling the only animation on the window");
1671   mWindow->ReleaseFullscreenTransitionAnimation();
1672   [self cleanupAndDispatch:animation];
1675 - (void)animationDidStop:(NSAnimation*)animation {
1676   [self cleanupAndDispatch:animation];
1678 @end
1680 static bool AlwaysUsesNativeFullScreen() {
1681   return Preferences::GetBool("full-screen-api.macos-native-full-screen",
1682                               false);
1685 /* virtual */ bool nsCocoaWindow::PrepareForFullscreenTransition(
1686     nsISupports** aData) {
1687   if (AlwaysUsesNativeFullScreen()) {
1688     return false;
1689   }
1691   // Our fullscreen transition creates a new window occluding this window.
1692   // That triggers an occlusion event which can cause DOM fullscreen requests
1693   // to fail due to the context not being focused at the time the focus check
1694   // is performed in the child process. Until the transition is cleaned up in
1695   // CleanupFullscreenTransition(), ignore occlusion events for this window.
1696   // If this method is changed to return false, the transition will not be
1697   // performed and mIgnoreOcclusionCount should not be incremented.
1698   MOZ_ASSERT(mIgnoreOcclusionCount >= 0);
1699   mIgnoreOcclusionCount++;
1701   nsCOMPtr<nsIScreen> widgetScreen = GetWidgetScreen();
1702   NSScreen* cocoaScreen = ScreenHelperCocoa::CocoaScreenForScreen(widgetScreen);
1704   NSWindow* win =
1705       [[NSWindow alloc] initWithContentRect:[cocoaScreen frame]
1706                                   styleMask:NSWindowStyleMaskBorderless
1707                                     backing:NSBackingStoreBuffered
1708                                       defer:YES];
1709   [win setBackgroundColor:[NSColor blackColor]];
1710   [win setAlphaValue:0];
1711   [win setIgnoresMouseEvents:YES];
1712   [win setLevel:NSScreenSaverWindowLevel];
1713   [win makeKeyAndOrderFront:nil];
1715   auto data = new FullscreenTransitionData(win);
1716   *aData = data;
1717   NS_ADDREF(data);
1718   return true;
1721 /* virtual */ void nsCocoaWindow::CleanupFullscreenTransition() {
1722   MOZ_ASSERT(mIgnoreOcclusionCount > 0);
1723   mIgnoreOcclusionCount--;
1726 /* virtual */ void nsCocoaWindow::PerformFullscreenTransition(
1727     FullscreenTransitionStage aStage, uint16_t aDuration, nsISupports* aData,
1728     nsIRunnable* aCallback) {
1729   auto data = static_cast<FullscreenTransitionData*>(aData);
1730   FullscreenTransitionDelegate* delegate =
1731       [[FullscreenTransitionDelegate alloc] init];
1732   delegate->mWindow = this;
1733   // Storing already_AddRefed directly could cause static checking fail.
1734   delegate->mCallback = nsCOMPtr<nsIRunnable>(aCallback).forget().take();
1736   if (mFullscreenTransitionAnimation) {
1737     [mFullscreenTransitionAnimation stopAnimation];
1738     ReleaseFullscreenTransitionAnimation();
1739   }
1741   NSDictionary* dict = @{
1742     NSViewAnimationTargetKey : data->mTransitionWindow,
1743     NSViewAnimationEffectKey : aStage == eBeforeFullscreenToggle
1744         ? NSViewAnimationFadeInEffect
1745         : NSViewAnimationFadeOutEffect
1746   };
1747   mFullscreenTransitionAnimation =
1748       [[NSViewAnimation alloc] initWithViewAnimations:@[ dict ]];
1749   [mFullscreenTransitionAnimation setDelegate:delegate];
1750   [mFullscreenTransitionAnimation setDuration:aDuration / 1000.0];
1751   [mFullscreenTransitionAnimation startAnimation];
1754 void nsCocoaWindow::CocoaWindowWillEnterFullscreen(bool aFullscreen) {
1755   MOZ_ASSERT(mUpdateFullscreenOnResize.isNothing());
1757   mHasStartedNativeFullscreen = true;
1759   // Ensure that we update our fullscreen state as early as possible, when the
1760   // resize happens.
1761   mUpdateFullscreenOnResize =
1762       Some(aFullscreen ? TransitionType::Fullscreen : TransitionType::Windowed);
1765 void nsCocoaWindow::CocoaWindowDidEnterFullscreen(bool aFullscreen) {
1766   mHasStartedNativeFullscreen = false;
1767   EndOurNativeTransition();
1768   DispatchOcclusionEvent();
1770   // Check if aFullscreen matches our expected fullscreen state. It might not if
1771   // there was a failure somewhere along the way, in which case we'll recover
1772   // from that.
1773   bool receivedExpectedFullscreen = false;
1774   if (mUpdateFullscreenOnResize.isSome()) {
1775     bool expectingFullscreen =
1776         (*mUpdateFullscreenOnResize == TransitionType::Fullscreen);
1777     receivedExpectedFullscreen = (expectingFullscreen == aFullscreen);
1778   } else {
1779     receivedExpectedFullscreen = (mInFullScreenMode == aFullscreen);
1780   }
1782   TransitionType transition =
1783       aFullscreen ? TransitionType::Fullscreen : TransitionType::Windowed;
1784   if (receivedExpectedFullscreen) {
1785     // Everything is as expected. Update our state if needed.
1786     HandleUpdateFullscreenOnResize();
1787   } else {
1788     // We weren't expecting this fullscreen state. Update our fullscreen state
1789     // to the new reality.
1790     UpdateFullscreenState(aFullscreen, true);
1792     // If we have a current transition, switch it to match what we just did.
1793     if (mTransitionCurrent.isSome()) {
1794       mTransitionCurrent = Some(transition);
1795     }
1796   }
1798   // Whether we expected this transition or not, we're ready to finish it.
1799   FinishCurrentTransitionIfMatching(transition);
1802 void nsCocoaWindow::UpdateFullscreenState(bool aFullScreen, bool aNativeMode) {
1803   bool wasInFullscreen = mInFullScreenMode;
1804   mInFullScreenMode = aFullScreen;
1805   if (aNativeMode || mInNativeFullScreenMode) {
1806     mInNativeFullScreenMode = aFullScreen;
1807   }
1809   if (aFullScreen == wasInFullscreen) {
1810     return;
1811   }
1813   DispatchSizeModeEvent();
1815   // Notify the mainChildView with our new fullscreen state.
1816   nsChildView* mainChildView =
1817       static_cast<nsChildView*>([[mWindow mainChildView] widget]);
1818   if (mainChildView) {
1819     mainChildView->UpdateFullscreen(aFullScreen);
1820   }
1823 nsresult nsCocoaWindow::MakeFullScreen(bool aFullScreen) {
1824   return DoMakeFullScreen(aFullScreen, AlwaysUsesNativeFullScreen());
1827 nsresult nsCocoaWindow::MakeFullScreenWithNativeTransition(bool aFullScreen) {
1828   return DoMakeFullScreen(aFullScreen, true);
1831 nsresult nsCocoaWindow::DoMakeFullScreen(bool aFullScreen,
1832                                          bool aUseSystemTransition) {
1833   if (!mWindow) {
1834     return NS_OK;
1835   }
1837   // Figure out what type of transition is being requested.
1838   TransitionType transition = TransitionType::Windowed;
1839   if (aFullScreen) {
1840     // Decide whether to use fullscreen or emulated fullscreen.
1841     transition =
1842         (aUseSystemTransition && (mWindow.collectionBehavior &
1843                                   NSWindowCollectionBehaviorFullScreenPrimary))
1844             ? TransitionType::Fullscreen
1845             : TransitionType::EmulatedFullscreen;
1846   }
1848   QueueTransition(transition);
1849   return NS_OK;
1852 void nsCocoaWindow::QueueTransition(const TransitionType& aTransition) {
1853   mTransitionsPending.push(aTransition);
1854   ProcessTransitions();
1857 void nsCocoaWindow::ProcessTransitions() {
1858   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK
1860   if (mInProcessTransitions) {
1861     return;
1862   }
1864   mInProcessTransitions = true;
1866   // Start a loop that will continue as long as we have transitions to process
1867   // and we aren't waiting on an asynchronous transition to complete. Any
1868   // transition that starts something async will `continue` this loop to exit.
1869   while (!mTransitionsPending.empty() && !IsInTransition()) {
1870     TransitionType nextTransition = mTransitionsPending.front();
1872     // We have to check for some incompatible transition states, and if we find
1873     // one, instead perform an alternative transition and leave the queue
1874     // untouched. If we add one of these transitions, we set
1875     // mIsTransitionCurrentAdded because we don't want to confuse listeners who
1876     // are expecting to receive exactly one event when the requested transition
1877     // has completed.
1878     switch (nextTransition) {
1879       case TransitionType::Fullscreen:
1880       case TransitionType::EmulatedFullscreen:
1881       case TransitionType::Windowed:
1882       case TransitionType::Zoom:
1883         // These can't handle miniaturized windows, so deminiaturize first.
1884         if (mWindow.miniaturized) {
1885           mTransitionCurrent = Some(TransitionType::Deminiaturize);
1886           mIsTransitionCurrentAdded = true;
1887         }
1888         break;
1889       case TransitionType::Miniaturize:
1890         // This can't handle fullscreen, so go to windowed first.
1891         if (mInFullScreenMode) {
1892           mTransitionCurrent = Some(TransitionType::Windowed);
1893           mIsTransitionCurrentAdded = true;
1894         }
1895         break;
1896       default:
1897         break;
1898     }
1900     // If mTransitionCurrent is still empty, then we use the nextTransition and
1901     // pop the queue.
1902     if (mTransitionCurrent.isNothing()) {
1903       mTransitionCurrent = Some(nextTransition);
1904       mTransitionsPending.pop();
1905     }
1907     switch (*mTransitionCurrent) {
1908       case TransitionType::Fullscreen: {
1909         if (!mInFullScreenMode) {
1910           // Check our global state to see if it is safe to start a native
1911           // fullscreen transition. While that is false, run our own event loop.
1912           // Eventually, the native fullscreen transition will finish, and we
1913           // can safely continue.
1914           if (!mInLocalRunLoop) {
1915             mInLocalRunLoop = true;
1916             NSRunLoop* localRunLoop = [NSRunLoop currentRunLoop];
1917             while (mWindow && !CanStartNativeTransition() &&
1918                    [localRunLoop runMode:NSDefaultRunLoopMode
1919                               beforeDate:[NSDate distantFuture]]) {
1920               // This loop continues to process events until
1921               // CanStartNativeTransition() returns true.
1922             }
1923             mInLocalRunLoop = false;
1924           }
1926           // This triggers an async animation, so continue.
1927           [mWindow toggleFullScreen:nil];
1928           continue;
1929         }
1930         break;
1931       }
1933       case TransitionType::EmulatedFullscreen: {
1934         if (!mInFullScreenMode) {
1935           NSDisableScreenUpdates();
1936           mSuppressSizeModeEvents = true;
1937           // The order here matters. When we exit full screen mode, we need to
1938           // show the Dock first, otherwise the newly-created window won't have
1939           // its minimize button enabled. See bug 526282.
1940           nsCocoaUtils::HideOSChromeOnScreen(true);
1941           nsBaseWidget::InfallibleMakeFullScreen(true);
1942           mSuppressSizeModeEvents = false;
1943           NSEnableScreenUpdates();
1944           UpdateFullscreenState(true, false);
1945         }
1946         break;
1947       }
1949       case TransitionType::Windowed: {
1950         if (mInFullScreenMode) {
1951           if (mInNativeFullScreenMode) {
1952             // Check our global state to see if it is safe to start a native
1953             // fullscreen transition. While that is false, run our own event
1954             // loop. Eventually, the native fullscreen transition will finish,
1955             // and we can safely continue.
1956             if (!mInLocalRunLoop) {
1957               mInLocalRunLoop = true;
1958               NSRunLoop* localRunLoop = [NSRunLoop currentRunLoop];
1959               while (mWindow && !CanStartNativeTransition() &&
1960                      [localRunLoop runMode:NSDefaultRunLoopMode
1961                                 beforeDate:[NSDate distantFuture]]) {
1962                 // This loop continues to process events until
1963                 // CanStartNativeTransition() returns true.
1964               }
1965               mInLocalRunLoop = false;
1966             }
1968             // This triggers an async animation, so continue.
1969             [mWindow toggleFullScreen:nil];
1970             continue;
1971           } else {
1972             NSDisableScreenUpdates();
1973             mSuppressSizeModeEvents = true;
1974             // The order here matters. When we exit full screen mode, we need to
1975             // show the Dock first, otherwise the newly-created window won't
1976             // have its minimize button enabled. See bug 526282.
1977             nsCocoaUtils::HideOSChromeOnScreen(false);
1978             nsBaseWidget::InfallibleMakeFullScreen(false);
1979             mSuppressSizeModeEvents = false;
1980             NSEnableScreenUpdates();
1981             UpdateFullscreenState(false, false);
1982           }
1983         } else if (mWindow.zoomed) {
1984           [mWindow zoom:nil];
1986           // Check if we're still zoomed. If we are, we need to do *something*
1987           // to make the window smaller than the zoom size so Cocoa will treat
1988           // us as being out of the zoomed state. Otherwise, we could stay
1989           // zoomed and never be able to be "normal" from calls to SetSizeMode.
1990           if (mWindow.zoomed) {
1991             NSRect maximumFrame = mWindow.frame;
1992             const CGFloat INSET_OUT_OF_ZOOM = 20.0f;
1993             [mWindow setFrame:NSInsetRect(maximumFrame, INSET_OUT_OF_ZOOM,
1994                                           INSET_OUT_OF_ZOOM)
1995                       display:YES];
1996             MOZ_ASSERT(
1997                 !mWindow.zoomed,
1998                 "We should be able to unzoom by shrinking the frame a bit.");
1999           }
2000         }
2001         break;
2002       }
2004       case TransitionType::Miniaturize:
2005         if (!mWindow.miniaturized) {
2006           // This triggers an async animation, so continue.
2007           [mWindow miniaturize:nil];
2008           continue;
2009         }
2010         break;
2012       case TransitionType::Deminiaturize:
2013         if (mWindow.miniaturized) {
2014           // This triggers an async animation, so continue.
2015           [mWindow deminiaturize:nil];
2016           continue;
2017         }
2018         break;
2020       case TransitionType::Zoom:
2021         if (!mWindow.zoomed) {
2022           [mWindow zoom:nil];
2023         }
2024         break;
2026       default:
2027         break;
2028     }
2030     mTransitionCurrent.reset();
2031     mIsTransitionCurrentAdded = false;
2032   }
2034   mInProcessTransitions = false;
2036   // When we finish processing transitions, dispatch a size mode event to cover
2037   // the cases where an inserted transition suppressed one, and the original
2038   // transition never sent one because it detected it was at the desired state
2039   // when it ran. If we've already sent a size mode event, then this will be a
2040   // no-op.
2041   if (!IsInTransition()) {
2042     DispatchSizeModeEvent();
2043   }
2045   NS_OBJC_END_TRY_IGNORE_BLOCK;
2048 void nsCocoaWindow::FinishCurrentTransition() {
2049   mWaitingOnFinishCurrentTransition = false;
2050   mTransitionCurrent.reset();
2051   mIsTransitionCurrentAdded = false;
2052   ProcessTransitions();
2055 void nsCocoaWindow::FinishCurrentTransitionIfMatching(
2056     const TransitionType& aTransition) {
2057   // We've just finished some transition activity, and we're not sure whether it
2058   // was triggered programmatically, or by the user. If it matches our current
2059   // transition, then assume it was triggered programmatically and we can clean
2060   // up that transition and start processing transitions again.
2062   // Whether programmatic or user-initiated, we send out a size mode event.
2063   DispatchSizeModeEvent();
2065   if (mTransitionCurrent.isSome() && (*mTransitionCurrent == aTransition)) {
2066     // This matches our current transition. Since this function is called from
2067     // nsWindowDelegate transition callbacks, we want to make sure those
2068     // callbacks are all the way done before we start processing more
2069     // transitions. To accomplish this, we dispatch our cleanup to happen on the
2070     // next event loop. Doing this will ensure that any async native transition
2071     // methods we call (like toggleFullScreen) will succeed.
2072     if (NS_SUCCEEDED(NS_DispatchToCurrentThread(
2073             NewRunnableMethod("FinishCurrentTransition", this,
2074                               &nsCocoaWindow::FinishCurrentTransition)))) {
2075       mWaitingOnFinishCurrentTransition = true;
2076     } else {
2077       // Couldn't dispatch FinishCurrentTransition -- that's weird. Clean up
2078       // what we can, without re-entering ProcessTransitions.
2079       MOZ_ASSERT(mTransitionsPending.empty(),
2080                  "Pending transitions won't be executed until the next call to "
2081                  "QueueTransition.");
2082       mTransitionCurrent.reset();
2083       mIsTransitionCurrentAdded = false;
2084       DispatchSizeModeEvent();
2085     }
2086   }
2089 bool nsCocoaWindow::HandleUpdateFullscreenOnResize() {
2090   if (mUpdateFullscreenOnResize.isNothing()) {
2091     return false;
2092   }
2094   bool toFullscreen =
2095       (*mUpdateFullscreenOnResize == TransitionType::Fullscreen);
2096   mUpdateFullscreenOnResize.reset();
2097   UpdateFullscreenState(toFullscreen, true);
2099   return true;
2102 /* static */ mozilla::StaticDataMutex<nsCocoaWindow*>
2103     nsCocoaWindow::sWindowInNativeTransition(nullptr);
2105 bool nsCocoaWindow::CanStartNativeTransition() {
2106   auto window = sWindowInNativeTransition.Lock();
2107   if (*window == nullptr) {
2108     // Claim it and return true, indicating that the caller has permission to
2109     // start the native fullscreen transition.
2110     *window = this;
2111     return true;
2112   }
2113   return false;
2116 bool nsCocoaWindow::WeAreInNativeTransition() {
2117   auto window = sWindowInNativeTransition.Lock();
2118   return (*window == this);
2121 void nsCocoaWindow::EndOurNativeTransition() {
2122   auto window = sWindowInNativeTransition.Lock();
2123   if (*window == this) {
2124     *window = nullptr;
2125   }
2128 // Coordinates are desktop pixels
2129 void nsCocoaWindow::DoResize(double aX, double aY, double aWidth,
2130                              double aHeight, bool aRepaint,
2131                              bool aConstrainToCurrentScreen) {
2132   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2134   if (!mWindow || mInResize) {
2135     return;
2136   }
2138   // We are able to resize a window outside of any aspect ratio contraints
2139   // applied to it, but in order to "update" the aspect ratio contraint to the
2140   // new window dimensions, we must re-lock the aspect ratio.
2141   auto relockAspectRatio = MakeScopeExit([&]() {
2142     if (mAspectRatioLocked) {
2143       LockAspectRatio(true);
2144     }
2145   });
2147   AutoRestore<bool> reentrantResizeGuard(mInResize);
2148   mInResize = true;
2150   CGFloat scale = mSizeConstraints.mScale.scale;
2151   if (scale == MOZ_WIDGET_INVALID_SCALE) {
2152     scale = BackingScaleFactor();
2153   }
2155   // mSizeConstraints is in device pixels.
2156   int32_t width = NSToIntRound(aWidth * scale);
2157   int32_t height = NSToIntRound(aHeight * scale);
2159   width = std::max(mSizeConstraints.mMinSize.width,
2160                    std::min(mSizeConstraints.mMaxSize.width, width));
2161   height = std::max(mSizeConstraints.mMinSize.height,
2162                     std::min(mSizeConstraints.mMaxSize.height, height));
2164   DesktopIntRect newBounds(NSToIntRound(aX), NSToIntRound(aY),
2165                            NSToIntRound(width / scale),
2166                            NSToIntRound(height / scale));
2168   // constrain to the screen that contains the largest area of the new rect
2169   FitRectToVisibleAreaForScreen(
2170       newBounds, aConstrainToCurrentScreen ? [mWindow screen] : nullptr);
2172   // convert requested bounds into Cocoa coordinate system
2173   NSRect newFrame = nsCocoaUtils::GeckoRectToCocoaRect(newBounds);
2175   NSRect frame = [mWindow frame];
2176   BOOL isMoving = newFrame.origin.x != frame.origin.x ||
2177                   newFrame.origin.y != frame.origin.y;
2178   BOOL isResizing = newFrame.size.width != frame.size.width ||
2179                     newFrame.size.height != frame.size.height;
2181   if (!isMoving && !isResizing) {
2182     return;
2183   }
2185   // We ignore aRepaint -- we have to call display:YES, otherwise the
2186   // title bar doesn't immediately get repainted and is displayed in
2187   // the wrong place, leading to a visual jump.
2188   [mWindow setFrame:newFrame display:YES];
2190   NS_OBJC_END_TRY_IGNORE_BLOCK;
2193 // Coordinates are desktop pixels
2194 void nsCocoaWindow::Resize(double aX, double aY, double aWidth, double aHeight,
2195                            bool aRepaint) {
2196   DoResize(aX, aY, aWidth, aHeight, aRepaint, false);
2199 // Coordinates are desktop pixels
2200 void nsCocoaWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
2201   double invScale = 1.0 / BackingScaleFactor();
2202   DoResize(mBounds.x * invScale, mBounds.y * invScale, aWidth, aHeight,
2203            aRepaint, true);
2206 // Return the area that the Gecko ChildView in our window should cover, as an
2207 // NSRect in screen coordinates (with 0,0 being the bottom left corner of the
2208 // primary screen).
2209 NSRect nsCocoaWindow::GetClientCocoaRect() {
2210   if (!mWindow) {
2211     return NSZeroRect;
2212   }
2214   return [mWindow childViewRectForFrameRect:[mWindow frame]];
2217 LayoutDeviceIntRect nsCocoaWindow::GetClientBounds() {
2218   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2220   CGFloat scaleFactor = BackingScaleFactor();
2221   return nsCocoaUtils::CocoaRectToGeckoRectDevPix(GetClientCocoaRect(),
2222                                                   scaleFactor);
2224   NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntRect(0, 0, 0, 0));
2227 void nsCocoaWindow::UpdateBounds() {
2228   NSRect frame = NSZeroRect;
2229   if (mWindow) {
2230     frame = [mWindow frame];
2231   }
2232   mBounds =
2233       nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
2235   if (mPopupContentView) {
2236     mPopupContentView->UpdateBoundsFromView();
2237   }
2240 LayoutDeviceIntRect nsCocoaWindow::GetScreenBounds() {
2241   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2243 #ifdef DEBUG
2244   LayoutDeviceIntRect r = nsCocoaUtils::CocoaRectToGeckoRectDevPix(
2245       [mWindow frame], BackingScaleFactor());
2246   NS_ASSERTION(mWindow && mBounds == r, "mBounds out of sync!");
2247 #endif
2249   return mBounds;
2251   NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntRect(0, 0, 0, 0));
2254 double nsCocoaWindow::GetDefaultScaleInternal() { return BackingScaleFactor(); }
2256 static CGFloat GetBackingScaleFactor(NSWindow* aWindow) {
2257   NSRect frame = [aWindow frame];
2258   if (frame.size.width > 0 && frame.size.height > 0) {
2259     return nsCocoaUtils::GetBackingScaleFactor(aWindow);
2260   }
2262   // For windows with zero width or height, the backingScaleFactor method
2263   // is broken - it will always return 2 on a retina macbook, even when
2264   // the window position implies it's on a non-hidpi external display
2265   // (to the extent that a zero-area window can be said to be "on" a
2266   // display at all!)
2267   // And to make matters worse, Cocoa even fires a
2268   // windowDidChangeBackingProperties notification with the
2269   // NSBackingPropertyOldScaleFactorKey key when a window on an
2270   // external display is resized to/from zero height, even though it hasn't
2271   // really changed screens.
2273   // This causes us to handle popup window sizing incorrectly when the
2274   // popup is resized to zero height (bug 820327) - nsXULPopupManager
2275   // becomes (incorrectly) convinced the popup has been explicitly forced
2276   // to a non-default size and needs to have size attributes attached.
2278   // Workaround: instead of asking the window, we'll find the screen it is on
2279   // and ask that for *its* backing scale factor.
2281   // (See bug 853252 and additional comments in windowDidChangeScreen: below
2282   // for further complications this causes.)
2284   // First, expand the rect so that it actually has a measurable area,
2285   // for FindTargetScreenForRect to use.
2286   if (frame.size.width == 0) {
2287     frame.size.width = 1;
2288   }
2289   if (frame.size.height == 0) {
2290     frame.size.height = 1;
2291   }
2293   // Then identify the screen it belongs to, and return its scale factor.
2294   NSScreen* screen =
2295       FindTargetScreenForRect(nsCocoaUtils::CocoaRectToGeckoRect(frame));
2296   return nsCocoaUtils::GetBackingScaleFactor(screen);
2299 CGFloat nsCocoaWindow::BackingScaleFactor() {
2300   if (mBackingScaleFactor > 0.0) {
2301     return mBackingScaleFactor;
2302   }
2303   if (!mWindow) {
2304     return 1.0;
2305   }
2306   mBackingScaleFactor = GetBackingScaleFactor(mWindow);
2307   return mBackingScaleFactor;
2310 void nsCocoaWindow::BackingScaleFactorChanged() {
2311   CGFloat newScale = GetBackingScaleFactor(mWindow);
2313   // ignore notification if it hasn't really changed (or maybe we have
2314   // disabled HiDPI mode via prefs)
2315   if (mBackingScaleFactor == newScale) {
2316     return;
2317   }
2319   mBackingScaleFactor = newScale;
2321   if (!mWidgetListener || mWidgetListener->GetAppWindow()) {
2322     return;
2323   }
2325   if (PresShell* presShell = mWidgetListener->GetPresShell()) {
2326     presShell->BackingScaleFactorChanged();
2327   }
2328   mWidgetListener->UIResolutionChanged();
2331 int32_t nsCocoaWindow::RoundsWidgetCoordinatesTo() {
2332   if (BackingScaleFactor() == 2.0) {
2333     return 2;
2334   }
2335   return 1;
2338 void nsCocoaWindow::SetCursor(const Cursor& aCursor) {
2339   if (mPopupContentView) {
2340     mPopupContentView->SetCursor(aCursor);
2341   }
2344 nsresult nsCocoaWindow::SetTitle(const nsAString& aTitle) {
2345   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2347   if (!mWindow) {
2348     return NS_OK;
2349   }
2351   const nsString& strTitle = PromiseFlatString(aTitle);
2352   const unichar* uniTitle = reinterpret_cast<const unichar*>(strTitle.get());
2353   NSString* title = [NSString stringWithCharacters:uniTitle
2354                                             length:strTitle.Length()];
2355   if ([mWindow drawsContentsIntoWindowFrame] && ![mWindow wantsTitleDrawn]) {
2356     // Don't cause invalidations when the title isn't displayed.
2357     [mWindow disableSetNeedsDisplay];
2358     [mWindow setTitle:title];
2359     [mWindow enableSetNeedsDisplay];
2360   } else {
2361     [mWindow setTitle:title];
2362   }
2364   return NS_OK;
2366   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2369 void nsCocoaWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
2370   if (mPopupContentView) {
2371     mPopupContentView->Invalidate(aRect);
2372   }
2375 // Pass notification of some drag event to Gecko
2377 // The drag manager has let us know that something related to a drag has
2378 // occurred in this window. It could be any number of things, ranging from
2379 // a drop, to a drag enter/leave, or a drag over event. The actual event
2380 // is passed in |aMessage| and is passed along to our event hanlder so Gecko
2381 // knows about it.
2382 bool nsCocoaWindow::DragEvent(unsigned int aMessage,
2383                               mozilla::gfx::Point aMouseGlobal,
2384                               UInt16 aKeyModifiers) {
2385   return false;
2388 NS_IMETHODIMP nsCocoaWindow::SendSetZLevelEvent() {
2389   nsWindowZ placement = nsWindowZTop;
2390   nsCOMPtr<nsIWidget> actualBelow;
2391   if (mWidgetListener)
2392     mWidgetListener->ZLevelChanged(true, &placement, nullptr,
2393                                    getter_AddRefs(actualBelow));
2394   return NS_OK;
2397 NS_IMETHODIMP nsCocoaWindow::GetChildSheet(bool aShown, nsIWidget** _retval) {
2398   nsIWidget* child = GetFirstChild();
2400   while (child) {
2401     if (child->GetWindowType() == WindowType::Sheet) {
2402       // if it's a sheet, it must be an nsCocoaWindow
2403       nsCocoaWindow* cocoaWindow = static_cast<nsCocoaWindow*>(child);
2404       if (cocoaWindow->mWindow &&
2405           ((aShown && [cocoaWindow->mWindow isVisible]) ||
2406            (!aShown && cocoaWindow->mSheetNeedsShow))) {
2407         nsCOMPtr<nsIWidget> widget = cocoaWindow;
2408         widget.forget(_retval);
2409         return NS_OK;
2410       }
2411     }
2412     child = child->GetNextSibling();
2413   }
2415   *_retval = nullptr;
2417   return NS_OK;
2420 NS_IMETHODIMP nsCocoaWindow::GetRealParent(nsIWidget** parent) {
2421   *parent = mParent;
2422   return NS_OK;
2425 NS_IMETHODIMP nsCocoaWindow::GetIsSheet(bool* isSheet) {
2426   mWindowType == WindowType::Sheet ? * isSheet = true : * isSheet = false;
2427   return NS_OK;
2430 NS_IMETHODIMP nsCocoaWindow::GetSheetWindowParent(
2431     NSWindow** sheetWindowParent) {
2432   *sheetWindowParent = mSheetWindowParent;
2433   return NS_OK;
2436 // Invokes callback and ProcessEvent methods on Event Listener object
2437 nsresult nsCocoaWindow::DispatchEvent(WidgetGUIEvent* event,
2438                                       nsEventStatus& aStatus) {
2439   aStatus = nsEventStatus_eIgnore;
2441   nsCOMPtr<nsIWidget> kungFuDeathGrip(event->mWidget);
2442   mozilla::Unused << kungFuDeathGrip;  // Not used within this function
2444   if (mWidgetListener)
2445     aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents);
2447   return NS_OK;
2450 // aFullScreen should be the window's mInFullScreenMode. We don't have access to
2451 // that from here, so we need to pass it in. mInFullScreenMode should be the
2452 // canonical indicator that a window is currently full screen and it makes sense
2453 // to keep all sizemode logic here.
2454 static nsSizeMode GetWindowSizeMode(NSWindow* aWindow, bool aFullScreen) {
2455   if (aFullScreen) return nsSizeMode_Fullscreen;
2456   if ([aWindow isMiniaturized]) return nsSizeMode_Minimized;
2457   if (([aWindow styleMask] & NSWindowStyleMaskResizable) && [aWindow isZoomed])
2458     return nsSizeMode_Maximized;
2459   return nsSizeMode_Normal;
2462 void nsCocoaWindow::ReportMoveEvent() {
2463   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2465   // Prevent recursion, which can become infinite (see bug 708278).  This
2466   // can happen when the call to [NSWindow setFrameTopLeftPoint:] in
2467   // nsCocoaWindow::Move() triggers an immediate NSWindowDidMove notification
2468   // (and a call to [WindowDelegate windowDidMove:]).
2469   if (mInReportMoveEvent) {
2470     return;
2471   }
2472   mInReportMoveEvent = true;
2474   UpdateBounds();
2476   // The zoomed state can change when we're moving, in which case we need to
2477   // update our internal mSizeMode. This can happen either if we're maximized
2478   // and then moved, or if we're not maximized and moved back to zoomed state.
2479   if (mWindow && ((mSizeMode == nsSizeMode_Maximized) ^ [mWindow isZoomed])) {
2480     DispatchSizeModeEvent();
2481   }
2483   // Dispatch the move event to Gecko
2484   NotifyWindowMoved(mBounds.x, mBounds.y);
2486   mInReportMoveEvent = false;
2488   NS_OBJC_END_TRY_IGNORE_BLOCK;
2491 void nsCocoaWindow::DispatchSizeModeEvent() {
2492   if (!mWindow) {
2493     return;
2494   }
2496   if (mSuppressSizeModeEvents || mIsTransitionCurrentAdded) {
2497     return;
2498   }
2500   nsSizeMode newMode = GetWindowSizeMode(mWindow, mInFullScreenMode);
2501   if (mSizeMode == newMode) {
2502     return;
2503   }
2505   mSizeMode = newMode;
2506   if (mWidgetListener) {
2507     mWidgetListener->SizeModeChanged(newMode);
2508   }
2511 void nsCocoaWindow::DispatchOcclusionEvent() {
2512   if (!mWindow) {
2513     return;
2514   }
2516   // Our new occlusion state is true if the window is not visible.
2517   bool newOcclusionState =
2518       !(mHasStartedNativeFullscreen ||
2519         ([mWindow occlusionState] & NSWindowOcclusionStateVisible));
2521   // Don't dispatch if the new occlustion state is the same as the current
2522   // state.
2523   if (mIsFullyOccluded == newOcclusionState) {
2524     return;
2525   }
2527   MOZ_ASSERT(mIgnoreOcclusionCount >= 0);
2528   if (newOcclusionState && mIgnoreOcclusionCount > 0) {
2529     return;
2530   }
2532   mIsFullyOccluded = newOcclusionState;
2533   if (mWidgetListener) {
2534     mWidgetListener->OcclusionStateChanged(mIsFullyOccluded);
2535   }
2538 void nsCocoaWindow::ReportSizeEvent() {
2539   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2541   UpdateBounds();
2542   if (mWidgetListener) {
2543     LayoutDeviceIntRect innerBounds = GetClientBounds();
2544     mWidgetListener->WindowResized(this, innerBounds.width, innerBounds.height);
2545   }
2547   NS_OBJC_END_TRY_IGNORE_BLOCK;
2550 void nsCocoaWindow::SetMenuBar(RefPtr<nsMenuBarX>&& aMenuBar) {
2551   if (!mWindow) {
2552     mMenuBar = nullptr;
2553     return;
2554   }
2555   mMenuBar = std::move(aMenuBar);
2557   // Only paint for active windows, or paint the hidden window menu bar if no
2558   // other menu bar has been painted yet so that some reasonable menu bar is
2559   // displayed when the app starts up.
2560   if (mMenuBar && ((!gSomeMenuBarPainted &&
2561                     nsMenuUtilsX::GetHiddenWindowMenuBar() == mMenuBar) ||
2562                    [mWindow isMainWindow]))
2563     mMenuBar->Paint();
2566 void nsCocoaWindow::SetFocus(Raise aRaise,
2567                              mozilla::dom::CallerType aCallerType) {
2568   if (!mWindow) return;
2570   if (mPopupContentView) {
2571     return mPopupContentView->SetFocus(aRaise, aCallerType);
2572   }
2574   if (aRaise == Raise::Yes &&
2575       ([mWindow isVisible] || [mWindow isMiniaturized])) {
2576     if ([mWindow isMiniaturized]) {
2577       [mWindow deminiaturize:nil];
2578     }
2580     [mWindow makeKeyAndOrderFront:nil];
2581     SendSetZLevelEvent();
2582   }
2585 LayoutDeviceIntPoint nsCocoaWindow::WidgetToScreenOffset() {
2586   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2588   return nsCocoaUtils::CocoaRectToGeckoRectDevPix(GetClientCocoaRect(),
2589                                                   BackingScaleFactor())
2590       .TopLeft();
2592   NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0));
2595 LayoutDeviceIntPoint nsCocoaWindow::GetClientOffset() {
2596   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2598   LayoutDeviceIntRect clientRect = GetClientBounds();
2600   return clientRect.TopLeft() - mBounds.TopLeft();
2602   NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0));
2605 LayoutDeviceIntMargin nsCocoaWindow::ClientToWindowMargin() {
2606   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2608   if (!mWindow || mWindow.drawsContentsIntoWindowFrame ||
2609       mWindowType == WindowType::Popup) {
2610     return {};
2611   }
2613   NSRect clientNSRect = mWindow.contentLayoutRect;
2614   NSRect frameNSRect = [mWindow frameRectForChildViewRect:clientNSRect];
2616   CGFloat backingScale = BackingScaleFactor();
2617   const auto clientRect =
2618       nsCocoaUtils::CocoaRectToGeckoRectDevPix(clientNSRect, backingScale);
2619   const auto frameRect =
2620       nsCocoaUtils::CocoaRectToGeckoRectDevPix(frameNSRect, backingScale);
2622   return frameRect - clientRect;
2624   NS_OBJC_END_TRY_BLOCK_RETURN({});
2627 nsMenuBarX* nsCocoaWindow::GetMenuBar() { return mMenuBar; }
2629 void nsCocoaWindow::CaptureRollupEvents(bool aDoCapture) {
2630   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2632   if (aDoCapture) {
2633     if (![NSApp isActive]) {
2634       // We need to capture mouse event if we aren't
2635       // the active application. We only set this up when needed
2636       // because they cause spurious mouse event after crash
2637       // and gdb sessions. See bug 699538.
2638       nsToolkit::GetToolkit()->MonitorAllProcessMouseEvents();
2639     }
2641     // Sometimes more than one popup window can be visible at the same time
2642     // (e.g. nested non-native context menus, or the test case (attachment
2643     // 276885) for bmo bug 392389, which displays a non-native combo-box in a
2644     // non-native popup window).  In these cases the "active" popup window
2645     // should be the topmost -- the (nested) context menu the mouse is currently
2646     // over, or the combo-box's drop-down list (when it's displayed).  But
2647     // (among windows that have the same "level") OS X makes topmost the window
2648     // that last received a mouse-down event, which may be incorrect (in the
2649     // combo-box case, it makes topmost the window containing the combo-box).
2650     // So here we fiddle with a non-native popup window's level to make sure the
2651     // "active" one is always above any other non-native popup windows that
2652     // may be visible.
2653     if (mWindow && (mWindowType == WindowType::Popup)) SetPopupWindowLevel();
2654   } else {
2655     nsToolkit::GetToolkit()->StopMonitoringAllProcessMouseEvents();
2657     // XXXndeakin this doesn't make sense.
2658     // Why is the new window assumed to be a modal panel?
2659     if (mWindow && (mWindowType == WindowType::Popup))
2660       [mWindow setLevel:NSModalPanelWindowLevel];
2661   }
2663   NS_OBJC_END_TRY_IGNORE_BLOCK;
2666 nsresult nsCocoaWindow::GetAttention(int32_t aCycleCount) {
2667   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2669   [NSApp requestUserAttention:NSInformationalRequest];
2670   return NS_OK;
2672   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2675 bool nsCocoaWindow::HasPendingInputEvent() {
2676   return nsChildView::DoHasPendingInputEvent();
2679 void nsCocoaWindow::SetWindowShadowStyle(StyleWindowShadow aStyle) {
2680   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2682   mShadowStyle = aStyle;
2684   if (!mWindow || mWindowType != WindowType::Popup) {
2685     return;
2686   }
2688   mWindow.shadowStyle = mShadowStyle;
2689   [mWindow setUseMenuStyle:mShadowStyle == StyleWindowShadow::Menu];
2690   [mWindow setHasShadow:aStyle != StyleWindowShadow::None];
2692   NS_OBJC_END_TRY_IGNORE_BLOCK;
2695 void nsCocoaWindow::SetWindowOpacity(float aOpacity) {
2696   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2698   if (!mWindow) {
2699     return;
2700   }
2702   [mWindow setAlphaValue:(CGFloat)aOpacity];
2704   NS_OBJC_END_TRY_IGNORE_BLOCK;
2707 void nsCocoaWindow::SetColorScheme(const Maybe<ColorScheme>& aScheme) {
2708   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2710   if (!mWindow) {
2711     return;
2712   }
2714   mWindow.appearance = aScheme ? NSAppearanceForColorScheme(*aScheme) : nil;
2716   NS_OBJC_END_TRY_IGNORE_BLOCK;
2719 static inline CGAffineTransform GfxMatrixToCGAffineTransform(
2720     const gfx::Matrix& m) {
2721   CGAffineTransform t;
2722   t.a = m._11;
2723   t.b = m._12;
2724   t.c = m._21;
2725   t.d = m._22;
2726   t.tx = m._31;
2727   t.ty = m._32;
2728   return t;
2731 void nsCocoaWindow::SetWindowTransform(const gfx::Matrix& aTransform) {
2732   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2734   if (!mWindow) {
2735     return;
2736   }
2738   // Calling CGSSetWindowTransform when the window is not visible results in
2739   // misplacing the window into doubled x,y coordinates (see bug 1448132).
2740   if (![mWindow isVisible] || NSIsEmptyRect([mWindow frame])) {
2741     return;
2742   }
2744   if (StaticPrefs::widget_window_transforms_disabled()) {
2745     // CGSSetWindowTransform is a private API. In case calling it causes
2746     // problems either now or in the future, we'll want to have an easy kill
2747     // switch. So we allow disabling it with a pref.
2748     return;
2749   }
2751   gfx::Matrix transform = aTransform;
2753   // aTransform is a transform that should be applied to the window relative
2754   // to its regular position: If aTransform._31 is 100, then we want the
2755   // window to be displayed 100 pixels to the right of its regular position.
2756   // The transform that CGSSetWindowTransform accepts has a different meaning:
2757   // It's used to answer the question "For the screen pixel at x,y (with the
2758   // origin at the top left), what pixel in the window's buffer (again with
2759   // origin top left) should be displayed at that position?"
2760   // In the example above, this means that we need to call
2761   // CGSSetWindowTransform with a horizontal translation of -windowPos.x - 100.
2762   // So we need to invert the transform and adjust it by the window's position.
2763   if (!transform.Invert()) {
2764     // Treat non-invertible transforms as the identity transform.
2765     transform = gfx::Matrix();
2766   }
2768   bool isIdentity = transform.IsIdentity();
2769   if (isIdentity && mWindowTransformIsIdentity) {
2770     return;
2771   }
2773   transform.PreTranslate(-mBounds.x, -mBounds.y);
2775   // Snap translations to device pixels, to match what we do for CSS transforms
2776   // and because the window server rounds down instead of to nearest.
2777   if (!transform.HasNonTranslation() && transform.HasNonIntegerTranslation()) {
2778     auto snappedTranslation = gfx::IntPoint::Round(transform.GetTranslation());
2779     transform =
2780         gfx::Matrix::Translation(snappedTranslation.x, snappedTranslation.y);
2781   }
2783   // We also need to account for the backing scale factor: aTransform is given
2784   // in device pixels, but CGSSetWindowTransform works with logical display
2785   // pixels.
2786   CGFloat backingScale = BackingScaleFactor();
2787   transform.PreScale(backingScale, backingScale);
2788   transform.PostScale(1 / backingScale, 1 / backingScale);
2790   CGSConnection cid = _CGSDefaultConnection();
2791   CGSSetWindowTransform(cid, [mWindow windowNumber],
2792                         GfxMatrixToCGAffineTransform(transform));
2794   mWindowTransformIsIdentity = isIdentity;
2796   NS_OBJC_END_TRY_IGNORE_BLOCK;
2799 void nsCocoaWindow::SetInputRegion(const InputRegion& aInputRegion) {
2800   MOZ_ASSERT(mWindowType == WindowType::Popup,
2801              "This should only be called on popup windows.");
2802   // TODO: Somehow support aInputRegion.mMargin? Though maybe not.
2803   if (aInputRegion.mFullyTransparent) {
2804     [mWindow setIgnoresMouseEvents:YES];
2805   } else {
2806     [mWindow setIgnoresMouseEvents:NO];
2807   }
2810 void nsCocoaWindow::SetShowsToolbarButton(bool aShow) {
2811   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2813   if (mWindow) [mWindow setShowsToolbarButton:aShow];
2815   NS_OBJC_END_TRY_IGNORE_BLOCK;
2818 void nsCocoaWindow::SetSupportsNativeFullscreen(
2819     bool aSupportsNativeFullscreen) {
2820   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2822   if (mWindow) {
2823     // This determines whether we tell cocoa that the window supports native
2824     // full screen. If we do so, and another window is in native full screen,
2825     // this window will also appear in native full screen. We generally only
2826     // want to do this for primary application windows. We'll set the
2827     // relevant macnativefullscreen attribute on those, which will lead to us
2828     // being called with aSupportsNativeFullscreen set to `true` here.
2829     NSWindowCollectionBehavior newBehavior = [mWindow collectionBehavior];
2830     if (aSupportsNativeFullscreen) {
2831       newBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
2832     } else {
2833       newBehavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
2834     }
2835     [mWindow setCollectionBehavior:newBehavior];
2836   }
2838   NS_OBJC_END_TRY_IGNORE_BLOCK;
2841 void nsCocoaWindow::SetWindowAnimationType(
2842     nsIWidget::WindowAnimationType aType) {
2843   mAnimationType = aType;
2846 void nsCocoaWindow::SetDrawsTitle(bool aDrawTitle) {
2847   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2849   if (![mWindow drawsContentsIntoWindowFrame]) {
2850     // If we don't draw into the window frame, we always want to display window
2851     // titles.
2852     [mWindow setWantsTitleDrawn:YES];
2853   } else {
2854     [mWindow setWantsTitleDrawn:aDrawTitle];
2855   }
2857   NS_OBJC_END_TRY_IGNORE_BLOCK;
2860 nsresult nsCocoaWindow::SetNonClientMargins(
2861     const LayoutDeviceIntMargin& margins) {
2862   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2864   SetDrawsInTitlebar(margins.top == 0);
2866   return NS_OK;
2868   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2871 void nsCocoaWindow::SetDrawsInTitlebar(bool aState) {
2872   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2874   if (mWindow) {
2875     [mWindow setDrawsContentsIntoWindowFrame:aState];
2876   }
2878   NS_OBJC_END_TRY_IGNORE_BLOCK;
2881 NS_IMETHODIMP nsCocoaWindow::SynthesizeNativeMouseEvent(
2882     LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
2883     MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
2884     nsIObserver* aObserver) {
2885   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2887   AutoObserverNotifier notifier(aObserver, "mouseevent");
2888   if (mPopupContentView) {
2889     return mPopupContentView->SynthesizeNativeMouseEvent(
2890         aPoint, aNativeMessage, aButton, aModifierFlags, nullptr);
2891   }
2893   return NS_OK;
2895   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2898 NS_IMETHODIMP nsCocoaWindow::SynthesizeNativeMouseScrollEvent(
2899     LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX,
2900     double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
2901     uint32_t aAdditionalFlags, nsIObserver* aObserver) {
2902   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2904   AutoObserverNotifier notifier(aObserver, "mousescrollevent");
2905   if (mPopupContentView) {
2906     // Pass nullptr as the observer so that the AutoObserverNotification in
2907     // nsChildView::SynthesizeNativeMouseScrollEvent will be ignored.
2908     return mPopupContentView->SynthesizeNativeMouseScrollEvent(
2909         aPoint, aNativeMessage, aDeltaX, aDeltaY, aDeltaZ, aModifierFlags,
2910         aAdditionalFlags, nullptr);
2911   }
2913   return NS_OK;
2915   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2918 void nsCocoaWindow::LockAspectRatio(bool aShouldLock) {
2919   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2921   if (aShouldLock) {
2922     [mWindow setContentAspectRatio:mWindow.frame.size];
2923     mAspectRatioLocked = true;
2924   } else {
2925     // According to
2926     // https://developer.apple.com/documentation/appkit/nswindow/1419507-aspectratio,
2927     // aspect ratios and resize increments are mutually exclusive, and the
2928     // accepted way of cancelling an established aspect ratio is to set the
2929     // resize increments to 1.0, 1.0
2930     [mWindow setResizeIncrements:NSMakeSize(1.0, 1.0)];
2931     mAspectRatioLocked = false;
2932   }
2934   NS_OBJC_END_TRY_IGNORE_BLOCK;
2937 void nsCocoaWindow::UpdateThemeGeometries(
2938     const nsTArray<ThemeGeometry>& aThemeGeometries) {
2939   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2941   if (mPopupContentView) {
2942     return mPopupContentView->UpdateThemeGeometries(aThemeGeometries);
2943   }
2945   NS_OBJC_END_TRY_IGNORE_BLOCK;
2948 void nsCocoaWindow::SetPopupWindowLevel() {
2949   if (!mWindow) return;
2951   // Floating popups are at the floating level and hide when the window is
2952   // deactivated.
2953   if (mPopupLevel == PopupLevel::Floating) {
2954     [mWindow setLevel:NSFloatingWindowLevel];
2955     [mWindow setHidesOnDeactivate:YES];
2956   } else {
2957     // Otherwise, this is a top-level or parent popup. Parent popups always
2958     // appear just above their parent and essentially ignore the level.
2959     [mWindow setLevel:NSPopUpMenuWindowLevel];
2960     [mWindow setHidesOnDeactivate:NO];
2961   }
2964 void nsCocoaWindow::SetInputContext(const InputContext& aContext,
2965                                     const InputContextAction& aAction) {
2966   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2968   mInputContext = aContext;
2970   NS_OBJC_END_TRY_IGNORE_BLOCK;
2973 bool nsCocoaWindow::GetEditCommands(NativeKeyBindingsType aType,
2974                                     const WidgetKeyboardEvent& aEvent,
2975                                     nsTArray<CommandInt>& aCommands) {
2976   // Validate the arguments.
2977   if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
2978     return false;
2979   }
2981   NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
2982   // When the keyboard event is fired from this widget, it must mean that no web
2983   // content has focus because any web contents should be on `nsChildView`.  And
2984   // in any locales, the system UI is always horizontal layout.  So, let's pass
2985   // `Nothing()` for the writing mode here, it won't be treated as in a vertical
2986   // content.
2987   keyBindings->GetEditCommands(aEvent, Nothing(), aCommands);
2988   return true;
2991 void nsCocoaWindow::PauseOrResumeCompositor(bool aPause) {
2992   if (auto* mainChildView =
2993           static_cast<nsIWidget*>([[mWindow mainChildView] widget])) {
2994     mainChildView->PauseOrResumeCompositor(aPause);
2995   }
2998 bool nsCocoaWindow::AsyncPanZoomEnabled() const {
2999   if (mPopupContentView) {
3000     return mPopupContentView->AsyncPanZoomEnabled();
3001   }
3002   return nsBaseWidget::AsyncPanZoomEnabled();
3005 bool nsCocoaWindow::StartAsyncAutoscroll(const ScreenPoint& aAnchorLocation,
3006                                          const ScrollableLayerGuid& aGuid) {
3007   if (mPopupContentView) {
3008     return mPopupContentView->StartAsyncAutoscroll(aAnchorLocation, aGuid);
3009   }
3010   return nsBaseWidget::StartAsyncAutoscroll(aAnchorLocation, aGuid);
3013 void nsCocoaWindow::StopAsyncAutoscroll(const ScrollableLayerGuid& aGuid) {
3014   if (mPopupContentView) {
3015     mPopupContentView->StopAsyncAutoscroll(aGuid);
3016     return;
3017   }
3018   nsBaseWidget::StopAsyncAutoscroll(aGuid);
3021 already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
3022   nsCOMPtr<nsIWidget> window = new nsCocoaWindow();
3023   return window.forget();
3026 already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
3027   nsCOMPtr<nsIWidget> window = new nsChildView();
3028   return window.forget();
3031 @implementation WindowDelegate
3033 // We try to find a gecko menu bar to paint. If one does not exist, just paint
3034 // the application menu by itself so that a window doesn't have some other
3035 // window's menu bar.
3036 + (void)paintMenubarForWindow:(NSWindow*)aWindow {
3037   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3039   // make sure we only act on windows that have this kind of
3040   // object as a delegate
3041   id windowDelegate = [aWindow delegate];
3042   if ([windowDelegate class] != [self class]) return;
3044   nsCocoaWindow* geckoWidget = [windowDelegate geckoWidget];
3045   NS_ASSERTION(geckoWidget, "Window delegate not returning a gecko widget!");
3047   nsMenuBarX* geckoMenuBar = geckoWidget->GetMenuBar();
3048   if (geckoMenuBar) {
3049     geckoMenuBar->Paint();
3050   } else {
3051     // sometimes we don't have a native application menu early in launching
3052     if (!sApplicationMenu) return;
3054     NSMenu* mainMenu = [NSApp mainMenu];
3055     NS_ASSERTION(
3056         [mainMenu numberOfItems] > 0,
3057         "Main menu does not have any items, something is terribly wrong!");
3059     // Create a new menu bar.
3060     // We create a GeckoNSMenu because all menu bar NSMenu objects should use
3061     // that subclass for key handling reasons.
3062     GeckoNSMenu* newMenuBar =
3063         [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar"];
3065     // move the application menu from the existing menu bar to the new one
3066     NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain];
3067     [mainMenu removeItemAtIndex:0];
3068     [newMenuBar insertItem:firstMenuItem atIndex:0];
3069     [firstMenuItem release];
3071     // set our new menu bar as the main menu
3072     [NSApp setMainMenu:newMenuBar];
3073     [newMenuBar release];
3074   }
3076   NS_OBJC_END_TRY_IGNORE_BLOCK;
3079 - (id)initWithGeckoWindow:(nsCocoaWindow*)geckoWind {
3080   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3082   [super init];
3083   mGeckoWindow = geckoWind;
3084   mToplevelActiveState = false;
3085   mHasEverBeenZoomed = false;
3086   return self;
3088   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
3091 - (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)proposedFrameSize {
3092   RollUpPopups();
3093   return proposedFrameSize;
3096 - (NSRect)windowWillUseStandardFrame:(NSWindow*)window
3097                         defaultFrame:(NSRect)newFrame {
3098   // This function needs to return a rect representing the frame a window would
3099   // have if it is in its "maximized" size mode. The parameter newFrame is
3100   // supposed to be a frame representing the maximum window size on the screen
3101   // where the window currently appears. However, in practice, newFrame can be a
3102   // much smaller size. So, we ignore newframe and instead return the frame of
3103   // the entire screen associated with the window. That frame is bigger than the
3104   // window could actually be, due to the presence of the menubar and possibly
3105   // the dock, but we never call this function directly, and Cocoa callers will
3106   // shrink it to its true maximum size.
3107   return window.screen.frame;
3110 void nsCocoaWindow::CocoaSendToplevelActivateEvents() {
3111   if (mWidgetListener) {
3112     mWidgetListener->WindowActivated();
3113   }
3116 void nsCocoaWindow::CocoaSendToplevelDeactivateEvents() {
3117   if (mWidgetListener) {
3118     mWidgetListener->WindowDeactivated();
3119   }
3122 void nsCocoaWindow::CocoaWindowDidResize() {
3123   // It's important to update our bounds before we trigger any listeners. This
3124   // ensures that our bounds are correct when GetScreenBounds is called.
3125   UpdateBounds();
3127   if (HandleUpdateFullscreenOnResize()) {
3128     ReportSizeEvent();
3129     return;
3130   }
3132   // Resizing might have changed our zoom state.
3133   DispatchSizeModeEvent();
3134   ReportSizeEvent();
3137 - (void)windowDidResize:(NSNotification*)aNotification {
3138   BaseWindow* window = [aNotification object];
3139   [window updateTrackingArea];
3141   if (!mGeckoWindow) return;
3143   mGeckoWindow->CocoaWindowDidResize();
3146 - (void)windowDidChangeScreen:(NSNotification*)aNotification {
3147   if (!mGeckoWindow) return;
3149   // Because of Cocoa's peculiar treatment of zero-size windows (see comments
3150   // at GetBackingScaleFactor() above), we sometimes have a situation where
3151   // our concept of backing scale (based on the screen where the zero-sized
3152   // window is positioned) differs from Cocoa's idea (always based on the
3153   // Retina screen, AFAICT, even when an external non-Retina screen is the
3154   // primary display).
3155   //
3156   // As a result, if the window was created with zero size on an external
3157   // display, but then made visible on the (secondary) Retina screen, we
3158   // will *not* get a windowDidChangeBackingProperties notification for it.
3159   // This leads to an incorrect GetDefaultScale(), and widget coordinate
3160   // confusion, as per bug 853252.
3161   //
3162   // To work around this, we check for a backing scale mismatch when we
3163   // receive a windowDidChangeScreen notification, as we will receive this
3164   // even if Cocoa was already treating the zero-size window as having
3165   // Retina backing scale.
3166   NSWindow* window = (NSWindow*)[aNotification object];
3167   if ([window respondsToSelector:@selector(backingScaleFactor)]) {
3168     if (GetBackingScaleFactor(window) != mGeckoWindow->BackingScaleFactor()) {
3169       mGeckoWindow->BackingScaleFactorChanged();
3170     }
3171   }
3173   mGeckoWindow->ReportMoveEvent();
3176 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
3177   if (!mGeckoWindow) {
3178     return;
3179   }
3180   mGeckoWindow->CocoaWindowWillEnterFullscreen(true);
3183 // Lion's full screen mode will bypass our internal fullscreen tracking, so
3184 // we need to catch it when we transition and call our own methods, which in
3185 // turn will fire "fullscreen" events.
3186 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
3187   // On Yosemite, the NSThemeFrame class has two new properties --
3188   // titlebarView (an NSTitlebarView object) and titlebarContainerView (an
3189   // NSTitlebarContainerView object).  These are used to display the titlebar
3190   // in fullscreen mode.  In Safari they're not transparent.  But in Firefox
3191   // for some reason they are, which causes bug 1069658.  The following code
3192   // works around this Apple bug or design flaw.
3193   NSWindow* window = (NSWindow*)[notification object];
3194   NSView* frameView = [[window contentView] superview];
3195   NSView* titlebarView = nil;
3196   NSView* titlebarContainerView = nil;
3197   if ([frameView respondsToSelector:@selector(titlebarView)]) {
3198     titlebarView = [frameView titlebarView];
3199   }
3200   if ([frameView respondsToSelector:@selector(titlebarContainerView)]) {
3201     titlebarContainerView = [frameView titlebarContainerView];
3202   }
3203   if ([titlebarView respondsToSelector:@selector(setTransparent:)]) {
3204     [titlebarView setTransparent:NO];
3205   }
3206   if ([titlebarContainerView respondsToSelector:@selector(setTransparent:)]) {
3207     [titlebarContainerView setTransparent:NO];
3208   }
3210   if (!mGeckoWindow) {
3211     return;
3212   }
3213   mGeckoWindow->CocoaWindowDidEnterFullscreen(true);
3216 - (void)windowWillExitFullScreen:(NSNotification*)notification {
3217   if (!mGeckoWindow) {
3218     return;
3219   }
3220   mGeckoWindow->CocoaWindowWillEnterFullscreen(false);
3223 - (void)windowDidExitFullScreen:(NSNotification*)notification {
3224   if (!mGeckoWindow) {
3225     return;
3226   }
3227   mGeckoWindow->CocoaWindowDidEnterFullscreen(false);
3230 - (void)windowDidBecomeMain:(NSNotification*)aNotification {
3231   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3233   RollUpPopups();
3234   ChildViewMouseTracker::ReEvaluateMouseEnterState();
3236   // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
3237   // app modally. If one of those is up then we want it to retain its menu bar.
3238   if ([NSApp _isRunningAppModal]) return;
3239   NSWindow* window = [aNotification object];
3240   if (window) [WindowDelegate paintMenubarForWindow:window];
3242   if ([window isKindOfClass:[ToolbarWindow class]]) {
3243     [(ToolbarWindow*)window windowMainStateChanged];
3244   }
3246   NS_OBJC_END_TRY_IGNORE_BLOCK;
3249 - (void)windowDidResignMain:(NSNotification*)aNotification {
3250   RollUpPopups();
3251   ChildViewMouseTracker::ReEvaluateMouseEnterState();
3253   // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
3254   // app modally. If one of those is up then we want it to retain its menu bar.
3255   if ([NSApp _isRunningAppModal]) return;
3256   RefPtr<nsMenuBarX> hiddenWindowMenuBar =
3257       nsMenuUtilsX::GetHiddenWindowMenuBar();
3258   if (hiddenWindowMenuBar) {
3259     // printf("painting hidden window menu bar due to window losing main
3260     // status\n");
3261     hiddenWindowMenuBar->Paint();
3262   }
3264   NSWindow* window = [aNotification object];
3265   if ([window isKindOfClass:[ToolbarWindow class]]) {
3266     [(ToolbarWindow*)window windowMainStateChanged];
3267   }
3270 - (void)windowDidBecomeKey:(NSNotification*)aNotification {
3271   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3273   RollUpPopups();
3274   ChildViewMouseTracker::ReEvaluateMouseEnterState();
3276   NSWindow* window = [aNotification object];
3277   if ([window isSheet]) [WindowDelegate paintMenubarForWindow:window];
3279   nsChildView* mainChildView =
3280       static_cast<nsChildView*>([[(BaseWindow*)window mainChildView] widget]);
3281   if (mainChildView) {
3282     if (mainChildView->GetInputContext().IsPasswordEditor()) {
3283       TextInputHandler::EnableSecureEventInput();
3284     } else {
3285       TextInputHandler::EnsureSecureEventInputDisabled();
3286     }
3287   }
3289   NS_OBJC_END_TRY_IGNORE_BLOCK;
3292 - (void)windowDidResignKey:(NSNotification*)aNotification {
3293   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3295   RollUpPopups(nsIRollupListener::AllowAnimations::No);
3297   ChildViewMouseTracker::ReEvaluateMouseEnterState();
3299   // If a sheet just resigned key then we should paint the menu bar
3300   // for whatever window is now main.
3301   NSWindow* window = [aNotification object];
3302   if ([window isSheet])
3303     [WindowDelegate paintMenubarForWindow:[NSApp mainWindow]];
3305   TextInputHandler::EnsureSecureEventInputDisabled();
3307   NS_OBJC_END_TRY_IGNORE_BLOCK;
3310 - (void)windowWillMove:(NSNotification*)aNotification {
3311   RollUpPopups();
3314 - (void)windowDidMove:(NSNotification*)aNotification {
3315   if (mGeckoWindow) mGeckoWindow->ReportMoveEvent();
3318 - (BOOL)windowShouldClose:(id)sender {
3319   nsIWidgetListener* listener =
3320       mGeckoWindow ? mGeckoWindow->GetWidgetListener() : nullptr;
3321   if (listener) listener->RequestWindowClose(mGeckoWindow);
3322   return NO;  // gecko will do it
3325 - (void)windowWillClose:(NSNotification*)aNotification {
3326   RollUpPopups();
3329 - (void)windowWillMiniaturize:(NSNotification*)aNotification {
3330   RollUpPopups();
3333 - (void)windowDidMiniaturize:(NSNotification*)aNotification {
3334   if (!mGeckoWindow) {
3335     return;
3336   }
3337   mGeckoWindow->FinishCurrentTransitionIfMatching(
3338       nsCocoaWindow::TransitionType::Miniaturize);
3341 - (void)windowDidDeminiaturize:(NSNotification*)aNotification {
3342   if (!mGeckoWindow) {
3343     return;
3344   }
3345   mGeckoWindow->FinishCurrentTransitionIfMatching(
3346       nsCocoaWindow::TransitionType::Deminiaturize);
3349 - (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)proposedFrame {
3350   if (!mHasEverBeenZoomed && [window isZoomed]) return NO;  // See bug 429954.
3352   mHasEverBeenZoomed = YES;
3353   return YES;
3356 - (NSRect)window:(NSWindow*)window
3357     willPositionSheet:(NSWindow*)sheet
3358             usingRect:(NSRect)rect {
3359   if ([window isKindOfClass:[ToolbarWindow class]]) {
3360     rect.origin.y = [(ToolbarWindow*)window sheetAttachmentPosition];
3361   }
3362   return rect;
3365 #ifdef MOZ_THUNDERBIRD
3366 - (void)didEndSheet:(NSWindow*)sheet
3367          returnCode:(int)returnCode
3368         contextInfo:(void*)contextInfo {
3369   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3371   // Note: 'contextInfo' (if it is set) is the window that is the parent of
3372   // the sheet.  The value of contextInfo is determined in
3373   // nsCocoaWindow::Show().  If it's set, 'contextInfo' is always the top-
3374   // level window, not another sheet itself.  But 'contextInfo' is nil if
3375   // our parent window is also a sheet -- in that case we shouldn't send
3376   // the top-level window any activate events (because it's our parent
3377   // window that needs to get these events, not the top-level window).
3378   [TopLevelWindowData deactivateInWindow:sheet];
3379   [sheet orderOut:self];
3380   if (contextInfo) [TopLevelWindowData activateInWindow:(NSWindow*)contextInfo];
3382   NS_OBJC_END_TRY_ABORT_BLOCK;
3384 #endif
3386 - (void)windowDidChangeBackingProperties:(NSNotification*)aNotification {
3387   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3389   NSWindow* window = (NSWindow*)[aNotification object];
3391   if ([window respondsToSelector:@selector(backingScaleFactor)]) {
3392     CGFloat oldFactor = [[[aNotification userInfo]
3393         objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue];
3394     if ([window backingScaleFactor] != oldFactor) {
3395       mGeckoWindow->BackingScaleFactorChanged();
3396     }
3397   }
3399   NS_OBJC_END_TRY_IGNORE_BLOCK;
3402 // This method is on NSWindowDelegate starting with 10.9
3403 - (void)windowDidChangeOcclusionState:(NSNotification*)aNotification {
3404   if (mGeckoWindow) {
3405     mGeckoWindow->DispatchOcclusionEvent();
3406   }
3409 - (nsCocoaWindow*)geckoWidget {
3410   return mGeckoWindow;
3413 - (bool)toplevelActiveState {
3414   return mToplevelActiveState;
3417 - (void)sendToplevelActivateEvents {
3418   if (!mToplevelActiveState && mGeckoWindow) {
3419     mGeckoWindow->CocoaSendToplevelActivateEvents();
3421     mToplevelActiveState = true;
3422   }
3425 - (void)sendToplevelDeactivateEvents {
3426   if (mToplevelActiveState && mGeckoWindow) {
3427     mGeckoWindow->CocoaSendToplevelDeactivateEvents();
3428     mToplevelActiveState = false;
3429   }
3432 @end
3434 @interface NSView (FrameViewMethodSwizzling)
3435 - (NSPoint)FrameView__closeButtonOrigin;
3436 - (CGFloat)FrameView__titlebarHeight;
3437 @end
3439 @implementation NSView (FrameViewMethodSwizzling)
3441 - (NSPoint)FrameView__closeButtonOrigin {
3442   if (![self.window isKindOfClass:[ToolbarWindow class]]) {
3443     return self.FrameView__closeButtonOrigin;
3444   }
3445   ToolbarWindow* win = (ToolbarWindow*)[self window];
3446   if (win.drawsContentsIntoWindowFrame &&
3447       !(win.styleMask & NSWindowStyleMaskFullScreen) &&
3448       (win.styleMask & NSWindowStyleMaskTitled)) {
3449     const NSRect buttonsRect = win.windowButtonsRect;
3450     if (NSIsEmptyRect(buttonsRect)) {
3451       // Empty rect. Let's hide the buttons.
3452       // Position is in non-flipped window coordinates. Using frame's height
3453       // for the vertical coordinate will move the buttons above the window,
3454       // making them invisible.
3455       return NSMakePoint(buttonsRect.origin.x, win.frame.size.height);
3456     } else if (win.windowTitlebarLayoutDirection ==
3457                NSUserInterfaceLayoutDirectionRightToLeft) {
3458       // We're in RTL mode, which means that the close button is the rightmost
3459       // button of the three window buttons. and buttonsRect.origin is the
3460       // bottom left corner of the green (zoom) button. The close button is 40px
3461       // to the right of the zoom button. This is confirmed to be the same on
3462       // all macOS versions between 10.12 - 12.0.
3463       return NSMakePoint(buttonsRect.origin.x + 40.0f, buttonsRect.origin.y);
3464     }
3465     return buttonsRect.origin;
3466   }
3467   return self.FrameView__closeButtonOrigin;
3470 - (CGFloat)FrameView__titlebarHeight {
3471   CGFloat height = [self FrameView__titlebarHeight];
3472   if ([[self window] isKindOfClass:[ToolbarWindow class]]) {
3473     // Make sure that the titlebar height includes our shifted buttons.
3474     // The following coordinates are in window space, with the origin being at
3475     // the bottom left corner of the window.
3476     ToolbarWindow* win = (ToolbarWindow*)[self window];
3477     CGFloat frameHeight = [self frame].size.height;
3478     CGFloat windowButtonY = frameHeight;
3479     if (!NSIsEmptyRect(win.windowButtonsRect) &&
3480         win.drawsContentsIntoWindowFrame &&
3481         !(win.styleMask & NSWindowStyleMaskFullScreen) &&
3482         (win.styleMask & NSWindowStyleMaskTitled)) {
3483       windowButtonY = win.windowButtonsRect.origin.y;
3484     }
3485     height = std::max(height, frameHeight - windowButtonY);
3486   }
3487   return height;
3490 @end
3492 static NSMutableSet* gSwizzledFrameViewClasses = nil;
3494 @interface NSWindow (PrivateSetNeedsDisplayInRectMethod)
3495 - (void)_setNeedsDisplayInRect:(NSRect)aRect;
3496 @end
3498 @interface NSView (NSVisualEffectViewSetMaskImage)
3499 - (void)setMaskImage:(NSImage*)image;
3500 @end
3502 @interface BaseWindow (Private)
3503 - (void)removeTrackingArea;
3504 - (void)cursorUpdated:(NSEvent*)aEvent;
3505 - (void)reflowTitlebarElements;
3506 @end
3508 @implementation BaseWindow
3510 // The frame of a window is implemented using undocumented NSView subclasses.
3511 // We offset the window buttons by overriding the method _closeButtonOrigin on
3512 // these frame view classes. The class which is
3513 // used for a window is determined in the window's frameViewClassForStyleMask:
3514 // method, so this is where we make sure that we have swizzled the method on
3515 // all encountered classes.
3516 + (Class)frameViewClassForStyleMask:(NSUInteger)styleMask {
3517   Class frameViewClass = [super frameViewClassForStyleMask:styleMask];
3519   if (!gSwizzledFrameViewClasses) {
3520     gSwizzledFrameViewClasses = [[NSMutableSet setWithCapacity:3] retain];
3521     if (!gSwizzledFrameViewClasses) {
3522       return frameViewClass;
3523     }
3524   }
3526   static IMP our_closeButtonOrigin = class_getMethodImplementation(
3527       [NSView class], @selector(FrameView__closeButtonOrigin));
3528   static IMP our_titlebarHeight = class_getMethodImplementation(
3529       [NSView class], @selector(FrameView__titlebarHeight));
3531   if (![gSwizzledFrameViewClasses containsObject:frameViewClass]) {
3532     // Either of these methods might be implemented in both a subclass of
3533     // NSFrameView and one of its own subclasses.  Which means that if we
3534     // aren't careful we might end up swizzling the same method twice.
3535     // Since method swizzling involves swapping pointers, this would break
3536     // things.
3537     IMP _closeButtonOrigin = class_getMethodImplementation(
3538         frameViewClass, @selector(_closeButtonOrigin));
3539     if (_closeButtonOrigin && _closeButtonOrigin != our_closeButtonOrigin) {
3540       nsToolkit::SwizzleMethods(frameViewClass, @selector(_closeButtonOrigin),
3541                                 @selector(FrameView__closeButtonOrigin));
3542     }
3544     // Override _titlebarHeight so that the floating titlebar doesn't clip the
3545     // bottom of the window buttons which we move down with our override of
3546     // _closeButtonOrigin.
3547     IMP _titlebarHeight = class_getMethodImplementation(
3548         frameViewClass, @selector(_titlebarHeight));
3549     if (_titlebarHeight && _titlebarHeight != our_titlebarHeight) {
3550       nsToolkit::SwizzleMethods(frameViewClass, @selector(_titlebarHeight),
3551                                 @selector(FrameView__titlebarHeight));
3552     }
3554     [gSwizzledFrameViewClasses addObject:frameViewClass];
3555   }
3557   return frameViewClass;
3560 - (id)initWithContentRect:(NSRect)aContentRect
3561                 styleMask:(NSUInteger)aStyle
3562                   backing:(NSBackingStoreType)aBufferingType
3563                     defer:(BOOL)aFlag {
3564   mDrawsIntoWindowFrame = NO;
3565   [super initWithContentRect:aContentRect
3566                    styleMask:aStyle
3567                      backing:aBufferingType
3568                        defer:aFlag];
3569   mState = nil;
3570   mDisabledNeedsDisplay = NO;
3571   mTrackingArea = nil;
3572   mDirtyRect = NSZeroRect;
3573   mBeingShown = NO;
3574   mDrawTitle = NO;
3575   mUseMenuStyle = NO;
3576   mTouchBar = nil;
3577   mIsAnimationSuppressed = NO;
3578   [self updateTrackingArea];
3580   return self;
3583 // Returns an autoreleased NSImage.
3584 static NSImage* GetMenuMaskImage() {
3585   CGFloat radius = 4.0f;
3586   NSEdgeInsets insets = {5, 5, 5, 5};
3587   NSSize maskSize = {12, 12};
3588   NSImage* maskImage = [NSImage
3589        imageWithSize:maskSize
3590              flipped:YES
3591       drawingHandler:^BOOL(NSRect dstRect) {
3592         NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:dstRect
3593                                                              xRadius:radius
3594                                                              yRadius:radius];
3595         [[NSColor colorWithDeviceWhite:1.0 alpha:1.0] set];
3596         [path fill];
3597         return YES;
3598       }];
3599   [maskImage setCapInsets:insets];
3600   return maskImage;
3603 - (void)swapOutChildViewWrapper:(NSView*)aNewWrapper {
3604   [aNewWrapper setFrame:[[self contentView] frame]];
3605   NSView* childView = [[self mainChildView] retain];
3606   [childView removeFromSuperview];
3607   [aNewWrapper addSubview:childView];
3608   [childView release];
3609   [super setContentView:aNewWrapper];
3612 - (void)setUseMenuStyle:(BOOL)aValue {
3613   if (aValue && !mUseMenuStyle) {
3614     // Turn on rounded corner masking.
3615     NSView* effectView =
3616         VibrancyManager::CreateEffectView(VibrancyType::MENU, YES);
3617     [effectView setMaskImage:GetMenuMaskImage()];
3618     [self swapOutChildViewWrapper:effectView];
3619     [effectView release];
3620   } else if (mUseMenuStyle && !aValue) {
3621     // Turn off rounded corner masking.
3622     NSView* wrapper = [[NSView alloc] initWithFrame:NSZeroRect];
3623     [wrapper setWantsLayer:YES];
3624     [self swapOutChildViewWrapper:wrapper];
3625     [wrapper release];
3626   }
3627   mUseMenuStyle = aValue;
3630 - (NSTouchBar*)makeTouchBar {
3631   mTouchBar = [[nsTouchBar alloc] init];
3632   if (mTouchBar) {
3633     sTouchBarIsInitialized = YES;
3634   }
3635   return mTouchBar;
3638 - (void)setBeingShown:(BOOL)aValue {
3639   mBeingShown = aValue;
3642 - (BOOL)isBeingShown {
3643   return mBeingShown;
3646 - (BOOL)isVisibleOrBeingShown {
3647   return [super isVisible] || mBeingShown;
3650 - (void)setIsAnimationSuppressed:(BOOL)aValue {
3651   mIsAnimationSuppressed = aValue;
3654 - (BOOL)isAnimationSuppressed {
3655   return mIsAnimationSuppressed;
3658 - (void)disableSetNeedsDisplay {
3659   mDisabledNeedsDisplay = YES;
3662 - (void)enableSetNeedsDisplay {
3663   mDisabledNeedsDisplay = NO;
3666 - (void)dealloc {
3667   [mTouchBar release];
3668   [self removeTrackingArea];
3669   ChildViewMouseTracker::OnDestroyWindow(self);
3670   [super dealloc];
3673 static const NSString* kStateTitleKey = @"title";
3674 static const NSString* kStateDrawsContentsIntoWindowFrameKey =
3675     @"drawsContentsIntoWindowFrame";
3676 static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
3677 static const NSString* kStateCollectionBehavior = @"collectionBehavior";
3678 static const NSString* kStateWantsTitleDrawn = @"wantsTitleDrawn";
3680 - (void)importState:(NSDictionary*)aState {
3681   if (NSString* title = [aState objectForKey:kStateTitleKey]) {
3682     [self setTitle:title];
3683   }
3684   [self setDrawsContentsIntoWindowFrame:
3685             [[aState objectForKey:kStateDrawsContentsIntoWindowFrameKey]
3686                 boolValue]];
3687   [self setShowsToolbarButton:[[aState objectForKey:kStateShowsToolbarButton]
3688                                   boolValue]];
3689   [self setCollectionBehavior:[[aState objectForKey:kStateCollectionBehavior]
3690                                   unsignedIntValue]];
3691   [self setWantsTitleDrawn:[[aState objectForKey:kStateWantsTitleDrawn]
3692                                boolValue]];
3695 - (NSMutableDictionary*)exportState {
3696   NSMutableDictionary* state = [NSMutableDictionary dictionaryWithCapacity:10];
3697   if (NSString* title = [self title]) {
3698     [state setObject:title forKey:kStateTitleKey];
3699   }
3700   [state setObject:[NSNumber numberWithBool:[self drawsContentsIntoWindowFrame]]
3701             forKey:kStateDrawsContentsIntoWindowFrameKey];
3702   [state setObject:[NSNumber numberWithBool:[self showsToolbarButton]]
3703             forKey:kStateShowsToolbarButton];
3704   [state setObject:[NSNumber numberWithUnsignedInt:[self collectionBehavior]]
3705             forKey:kStateCollectionBehavior];
3706   [state setObject:[NSNumber numberWithBool:[self wantsTitleDrawn]]
3707             forKey:kStateWantsTitleDrawn];
3708   return state;
3711 - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState {
3712   bool changed = (aState != mDrawsIntoWindowFrame);
3713   mDrawsIntoWindowFrame = aState;
3714   if (changed) {
3715     [self reflowTitlebarElements];
3716   }
3719 - (BOOL)drawsContentsIntoWindowFrame {
3720   return mDrawsIntoWindowFrame;
3723 - (NSRect)childViewRectForFrameRect:(NSRect)aFrameRect {
3724   if (mDrawsIntoWindowFrame) {
3725     return aFrameRect;
3726   }
3727   NSUInteger styleMask = [self styleMask];
3728   styleMask &= ~NSWindowStyleMaskFullSizeContentView;
3729   return [NSWindow contentRectForFrameRect:aFrameRect styleMask:styleMask];
3732 - (NSRect)frameRectForChildViewRect:(NSRect)aChildViewRect {
3733   if (mDrawsIntoWindowFrame) {
3734     return aChildViewRect;
3735   }
3736   NSUInteger styleMask = [self styleMask];
3737   styleMask &= ~NSWindowStyleMaskFullSizeContentView;
3738   return [NSWindow frameRectForContentRect:aChildViewRect styleMask:styleMask];
3741 - (NSTimeInterval)animationResizeTime:(NSRect)newFrame {
3742   if (mIsAnimationSuppressed) {
3743     // Should not animate the initial session-restore size change
3744     return 0.0;
3745   }
3747   return [super animationResizeTime:newFrame];
3750 - (void)setWantsTitleDrawn:(BOOL)aDrawTitle {
3751   mDrawTitle = aDrawTitle;
3752   [self setTitleVisibility:mDrawTitle ? NSWindowTitleVisible
3753                                       : NSWindowTitleHidden];
3756 - (BOOL)wantsTitleDrawn {
3757   return mDrawTitle;
3760 - (NSView*)trackingAreaView {
3761   NSView* contentView = [self contentView];
3762   return [contentView superview] ? [contentView superview] : contentView;
3765 - (NSArray<NSView*>*)contentViewContents {
3766   return [[[[self contentView] subviews] copy] autorelease];
3769 - (ChildView*)mainChildView {
3770   NSView* contentView = [self contentView];
3771   NSView* lastView = [[contentView subviews] lastObject];
3772   if ([lastView isKindOfClass:[ChildView class]]) {
3773     return (ChildView*)lastView;
3774   }
3775   return nil;
3778 - (void)removeTrackingArea {
3779   if (mTrackingArea) {
3780     [[self trackingAreaView] removeTrackingArea:mTrackingArea];
3781     [mTrackingArea release];
3782     mTrackingArea = nil;
3783   }
3786 - (void)updateTrackingArea {
3787   [self removeTrackingArea];
3789   NSView* view = [self trackingAreaView];
3790   const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
3791                                         NSTrackingMouseMoved |
3792                                         NSTrackingActiveAlways;
3793   mTrackingArea = [[NSTrackingArea alloc] initWithRect:[view bounds]
3794                                                options:options
3795                                                  owner:self
3796                                               userInfo:nil];
3797   [view addTrackingArea:mTrackingArea];
3800 - (void)mouseEntered:(NSEvent*)aEvent {
3801   ChildViewMouseTracker::MouseEnteredWindow(aEvent);
3804 - (void)mouseExited:(NSEvent*)aEvent {
3805   ChildViewMouseTracker::MouseExitedWindow(aEvent);
3808 - (void)mouseMoved:(NSEvent*)aEvent {
3809   ChildViewMouseTracker::MouseMoved(aEvent);
3812 - (void)cursorUpdated:(NSEvent*)aEvent {
3813   // Nothing to do here, but NSTrackingArea wants us to implement this method.
3816 - (void)_setNeedsDisplayInRect:(NSRect)aRect {
3817   // Prevent unnecessary invalidations due to moving NSViews (e.g. for plugins)
3818   if (!mDisabledNeedsDisplay) {
3819     // This method is only called by Cocoa, so when we're here, we know that
3820     // it's available and don't need to check whether our superclass responds
3821     // to the selector.
3822     [super _setNeedsDisplayInRect:aRect];
3823     mDirtyRect = NSUnionRect(mDirtyRect, aRect);
3824   }
3827 - (NSRect)getAndResetNativeDirtyRect {
3828   NSRect dirtyRect = mDirtyRect;
3829   mDirtyRect = NSZeroRect;
3830   return dirtyRect;
3833 // Possibly move the titlebar buttons.
3834 - (void)reflowTitlebarElements {
3835   NSView* frameView = [[self contentView] superview];
3836   if ([frameView respondsToSelector:@selector(_tileTitlebarAndRedisplay:)]) {
3837     [frameView _tileTitlebarAndRedisplay:NO];
3838   }
3841 - (BOOL)respondsToSelector:(SEL)aSelector {
3842   // Claim the window doesn't respond to this so that the system
3843   // doesn't steal keyboard equivalents for it. Bug 613710.
3844   if (aSelector == @selector(cancelOperation:)) {
3845     return NO;
3846   }
3848   return [super respondsToSelector:aSelector];
3851 - (void)doCommandBySelector:(SEL)aSelector {
3852   // We override this so that it won't beep if it can't act.
3853   // We want to control the beeping for missing or disabled
3854   // commands ourselves.
3855   [self tryToPerform:aSelector with:nil];
3858 - (id)accessibilityAttributeValue:(NSString*)attribute {
3859   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3861   id retval = [super accessibilityAttributeValue:attribute];
3863   // The following works around a problem with Text-to-Speech on OS X 10.7.
3864   // See bug 674612 for more info.
3865   //
3866   // When accessibility is off, AXUIElementCopyAttributeValue(), when called
3867   // on an AXApplication object to get its AXFocusedUIElement attribute,
3868   // always returns an AXWindow object (the actual browser window -- never a
3869   // mozAccessible object).  This also happens with accessibility turned on,
3870   // if no other object in the browser window has yet been focused.  But if
3871   // the browser window has a title bar (as it currently always does), the
3872   // AXWindow object will always have four "accessible" children, one of which
3873   // is an AXStaticText object (the title bar's "title"; the other three are
3874   // the close, minimize and zoom buttons).  This means that (for complicated
3875   // reasons, for which see bug 674612) Text-to-Speech on OS X 10.7 will often
3876   // "speak" the window title, no matter what text is selected, or even if no
3877   // text at all is selected.  (This always happens when accessibility is off.
3878   // It doesn't happen in Firefox releases because Apple has (on OS X 10.7)
3879   // special-cased the handling of apps whose CFBundleIdentifier is
3880   // org.mozilla.firefox.)
3881   //
3882   // We work around this problem by only returning AXChildren that are
3883   // mozAccessible object or are one of the titlebar's buttons (which
3884   // instantiate subclasses of NSButtonCell).
3885   if ([retval isKindOfClass:[NSArray class]] &&
3886       [attribute isEqualToString:@"AXChildren"]) {
3887     NSMutableArray* holder = [NSMutableArray arrayWithCapacity:10];
3888     [holder addObjectsFromArray:(NSArray*)retval];
3889     NSUInteger count = [holder count];
3890     for (NSInteger i = count - 1; i >= 0; --i) {
3891       id item = [holder objectAtIndex:i];
3892       // Remove anything from holder that isn't one of the titlebar's buttons
3893       // (which instantiate subclasses of NSButtonCell) or a mozAccessible
3894       // object (or one of its subclasses).
3895       if (![item isKindOfClass:[NSButtonCell class]] &&
3896           ![item respondsToSelector:@selector(hasRepresentedView)]) {
3897         [holder removeObjectAtIndex:i];
3898       }
3899     }
3900     retval = [NSArray arrayWithArray:holder];
3901   }
3903   return retval;
3905   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
3908 - (void)releaseJSObjects {
3909   [mTouchBar releaseJSObjects];
3912 @end
3914 @interface NSView (NSThemeFrame)
3915 - (void)_drawTitleStringInClip:(NSRect)aRect;
3916 - (void)_maskCorners:(NSUInteger)aFlags clipRect:(NSRect)aRect;
3917 @end
3919 @implementation MOZTitlebarView
3921 - (instancetype)initWithFrame:(NSRect)aFrame {
3922   self = [super initWithFrame:aFrame];
3924   self.material = NSVisualEffectMaterialTitlebar;
3925   self.blendingMode = NSVisualEffectBlendingModeWithinWindow;
3927   // Add a separator line at the bottom of the titlebar. NSBoxSeparator isn't a
3928   // perfect match for a native titlebar separator, but it's better than
3929   // nothing. We really want the appearance that _NSTitlebarDecorationView
3930   // creates with the help of CoreUI, but there's no public API for that.
3931   NSBox* separatorLine =
3932       [[NSBox alloc] initWithFrame:NSMakeRect(0, 0, aFrame.size.width, 1)];
3933   separatorLine.autoresizingMask = NSViewWidthSizable | NSViewMaxYMargin;
3934   separatorLine.boxType = NSBoxSeparator;
3935   [self addSubview:separatorLine];
3936   [separatorLine release];
3938   return self;
3941 - (BOOL)mouseDownCanMoveWindow {
3942   return YES;
3945 - (void)mouseUp:(NSEvent*)event {
3946   if ([event clickCount] == 2) {
3947     // Handle titlebar double click. We don't get the window's default behavior
3948     // here because the window uses NSWindowStyleMaskFullSizeContentView, and
3949     // this view (the titlebar gradient view) is technically part of the window
3950     // "contents" (it's a subview of the content view).
3951     if (nsCocoaUtils::ShouldZoomOnTitlebarDoubleClick()) {
3952       [[self window] performZoom:nil];
3953     } else if (nsCocoaUtils::ShouldMinimizeOnTitlebarDoubleClick()) {
3954       [[self window] performMiniaturize:nil];
3955     }
3956   }
3959 @end
3961 @interface MOZTitlebarAccessoryView : NSView
3962 @end
3964 @implementation MOZTitlebarAccessoryView : NSView
3965 - (void)viewWillMoveToWindow:(NSWindow*)aWindow {
3966   if (aWindow) {
3967     // When entering full screen mode, titlebar accessory views are inserted
3968     // into a floating NSWindow which houses the window titlebar and toolbars.
3969     // In order to work around a drawing bug with titlebarAppearsTransparent
3970     // windows in full screen mode, disable titlebar separators for all
3971     // NSWindows that this view is used in, including the floating full screen
3972     // toolbar window. The drawing bug was filed as FB9056136. See bug 1700211
3973     // for more details.
3974     if (@available(macOS 11.0, *)) {
3975       aWindow.titlebarSeparatorStyle = NSTitlebarSeparatorStyleNone;
3976     }
3977   }
3979 @end
3981 @implementation FullscreenTitlebarTracker
3982 - (FullscreenTitlebarTracker*)init {
3983   [super init];
3984   self.view =
3985       [[[MOZTitlebarAccessoryView alloc] initWithFrame:NSZeroRect] autorelease];
3986   self.hidden = YES;
3987   return self;
3989 @end
3991 // This class allows us to exercise control over the window's title bar. It is
3992 // used for all windows with titlebars.
3994 // ToolbarWindow supports two modes:
3995 //  - drawsContentsIntoWindowFrame mode: In this mode, the Gecko ChildView is
3996 //    sized to cover the entire window frame and manages titlebar drawing.
3997 //  - separate titlebar mode, with support for unified toolbars: In this mode,
3998 //    the Gecko ChildView does not extend into the titlebar. However, this
3999 //    window's content view (which is the ChildView's superview) *does* extend
4000 //    into the titlebar. Moreover, in this mode, we place a MOZTitlebarView
4001 //    in the content view, as a sibling of the ChildView.
4003 // The "separate titlebar mode" supports the "unified toolbar" look:
4004 // If there's a toolbar right below the titlebar, the two can "connect" and
4005 // form a single gradient without a separator line in between.
4007 // The following mechanism communicates the height of the unified toolbar to
4008 // the ToolbarWindow:
4010 // 1) In the style sheet we set the toolbar's -moz-appearance to toolbar.
4011 // 2) When the toolbar is visible and we paint the application chrome
4012 //    window, the array that Gecko passes nsChildView::UpdateThemeGeometries
4013 //    will contain an entry for the widget type StyleAppearance::Toolbar.
4014 // 3) nsChildView::UpdateThemeGeometries passes the toolbar's height, plus the
4015 //    titlebar height, to -[ToolbarWindow setUnifiedToolbarHeight:].
4017 // The actual drawing of the gradient happens in two parts: The titlebar part
4018 // (i.e. the top 22 pixels of the gradient) is drawn by the MOZTitlebarView,
4019 // which is a subview of the window's content view and a sibling of the
4020 // ChildView. The rest of the gradient is drawn by Gecko into the ChildView, as
4021 // part of the -moz-appearance rendering of the toolbar.
4022 @implementation ToolbarWindow
4024 - (id)initWithContentRect:(NSRect)aChildViewRect
4025                 styleMask:(NSUInteger)aStyle
4026                   backing:(NSBackingStoreType)aBufferingType
4027                     defer:(BOOL)aFlag {
4028   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4030   // We treat aChildViewRect as the rectangle that the window's main ChildView
4031   // should be sized to. Get the right frameRect for the requested child view
4032   // rect.
4033   NSRect frameRect = [NSWindow frameRectForContentRect:aChildViewRect
4034                                              styleMask:aStyle];
4036   // Always size the content view to the full frame size of the window.
4037   // We do this even if we want this window to have a titlebar; in that case,
4038   // the window's content view covers the entire window but the ChildView inside
4039   // it will only cover the content area. We do this so that we can render the
4040   // titlebar gradient manually, with a subview of our content view that's
4041   // positioned in the titlebar area. This lets us have a smooth connection
4042   // between titlebar and toolbar gradient in case the window has a "unified
4043   // toolbar + titlebar" look. Moreover, always using a full size content view
4044   // lets us toggle the titlebar on and off without changing the window's style
4045   // mask (which would have other subtle effects, for example on keyboard
4046   // focus).
4047   aStyle |= NSWindowStyleMaskFullSizeContentView;
4049   // -[NSWindow initWithContentRect:styleMask:backing:defer:] calls
4050   // [self frameRectForContentRect:styleMask:] to convert the supplied content
4051   // rect to the window's frame rect. We've overridden that method to be a
4052   // pass-through function. So, in order to get the intended frameRect, we need
4053   // to supply frameRect itself as the "content rect".
4054   NSRect contentRect = frameRect;
4056   if ((self = [super initWithContentRect:contentRect
4057                                styleMask:aStyle
4058                                  backing:aBufferingType
4059                                    defer:aFlag])) {
4060     mTitlebarView = nil;
4061     mUnifiedToolbarHeight = 22.0f;
4062     mSheetAttachmentPosition = aChildViewRect.size.height;
4063     mWindowButtonsRect = NSZeroRect;
4064     mInitialTitlebarHeight = [self titlebarHeight];
4066     [self setTitlebarAppearsTransparent:YES];
4067     if (@available(macOS 11.0, *)) {
4068       self.titlebarSeparatorStyle = NSTitlebarSeparatorStyleNone;
4069     }
4070     [self updateTitlebarView];
4072     mFullscreenTitlebarTracker = [[FullscreenTitlebarTracker alloc] init];
4073     // revealAmount is an undocumented property of
4074     // NSTitlebarAccessoryViewController that updates whenever the menubar
4075     // slides down in fullscreen mode.
4076     [mFullscreenTitlebarTracker addObserver:self
4077                                  forKeyPath:@"revealAmount"
4078                                     options:NSKeyValueObservingOptionNew
4079                                     context:nil];
4080     // Adding this accessory view controller allows us to shift the toolbar down
4081     // when the user mouses to the top of the screen in fullscreen.
4082     [(NSWindow*)self
4083         addTitlebarAccessoryViewController:mFullscreenTitlebarTracker];
4084   }
4085   return self;
4087   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
4090 - (void)observeValueForKeyPath:(NSString*)keyPath
4091                       ofObject:(id)object
4092                         change:(NSDictionary<NSKeyValueChangeKey, id>*)change
4093                        context:(void*)context {
4094   if ([keyPath isEqualToString:@"revealAmount"]) {
4095     [[self mainChildView] ensureNextCompositeIsAtomicWithMainThreadPaint];
4096     NSNumber* revealAmount = (change[NSKeyValueChangeNewKey]);
4097     [self updateTitlebarShownAmount:[revealAmount doubleValue]];
4098   } else {
4099     [super observeValueForKeyPath:keyPath
4100                          ofObject:object
4101                            change:change
4102                           context:context];
4103   }
4106 static bool ScreenHasNotch(nsCocoaWindow* aGeckoWindow) {
4107   if (@available(macOS 12.0, *)) {
4108     nsCOMPtr<nsIScreen> widgetScreen = aGeckoWindow->GetWidgetScreen();
4109     NSScreen* cocoaScreen =
4110         ScreenHelperCocoa::CocoaScreenForScreen(widgetScreen);
4111     return cocoaScreen.safeAreaInsets.top != 0.0f;
4112   }
4113   return false;
4116 static bool ShouldShiftByMenubarHeightInFullscreen(nsCocoaWindow* aWindow) {
4117   switch (StaticPrefs::widget_macos_shift_by_menubar_on_fullscreen()) {
4118     case 0:
4119       return false;
4120     case 1:
4121       return true;
4122     default:
4123       break;
4124   }
4125   // TODO: On notch-less macbooks, this creates extra space when the
4126   // "automatically show and hide the menubar on fullscreen" option is unchecked
4127   // (default checked). We tried to detect that in bug 1737831 but it wasn't
4128   // reliable enough, see the regressions from that bug. For now, stick to the
4129   // good behavior for default configurations (that is, shift by menubar height
4130   // on notch-less macbooks, and don't for devices that have a notch). This will
4131   // need refinement in the future.
4132   return !ScreenHasNotch(aWindow);
4135 - (void)updateTitlebarShownAmount:(CGFloat)aShownAmount {
4136   NSInteger styleMask = [self styleMask];
4137   if (!(styleMask & NSWindowStyleMaskFullScreen)) {
4138     // We are not interested in the size of the titlebar unless we are in
4139     // fullscreen.
4140     return;
4141   }
4143   // [NSApp mainMenu] menuBarHeight] returns one of two values: the full height
4144   // if the menubar is shown or is in the process of being shown, and 0
4145   // otherwise. Since we are multiplying the menubar height by aShownAmount, we
4146   // always want the full height.
4147   CGFloat menuBarHeight = NSApp.mainMenu.menuBarHeight;
4148   if (menuBarHeight > 0.0f) {
4149     mMenuBarHeight = menuBarHeight;
4150   }
4151   if ([[self delegate] isKindOfClass:[WindowDelegate class]]) {
4152     WindowDelegate* windowDelegate = (WindowDelegate*)[self delegate];
4153     nsCocoaWindow* geckoWindow = [windowDelegate geckoWidget];
4154     if (!geckoWindow) {
4155       return;
4156     }
4158     if (nsIWidgetListener* listener = geckoWindow->GetWidgetListener()) {
4159       // Use the titlebar height cached in our frame rather than
4160       // [ToolbarWindow titlebarHeight]. titlebarHeight returns 0 when we're in
4161       // fullscreen.
4162       CGFloat shiftByPixels = mInitialTitlebarHeight * aShownAmount;
4163       if (ShouldShiftByMenubarHeightInFullscreen(geckoWindow)) {
4164         shiftByPixels += mMenuBarHeight * aShownAmount;
4165       }
4166       // Use mozilla::DesktopToLayoutDeviceScale rather than the
4167       // DesktopToLayoutDeviceScale in nsCocoaWindow. The latter accounts for
4168       // screen DPI. We don't want that because the revealAmount property
4169       // already accounts for it, so we'd be compounding DPI scales > 1.
4170       mozilla::DesktopCoord coord = LayoutDeviceCoord(shiftByPixels) /
4171                                     mozilla::DesktopToLayoutDeviceScale();
4173       listener->MacFullscreenMenubarOverlapChanged(coord);
4174     }
4175   }
4178 - (void)dealloc {
4179   [mTitlebarView release];
4180   [mFullscreenTitlebarTracker removeObserver:self forKeyPath:@"revealAmount"];
4181   [mFullscreenTitlebarTracker removeFromParentViewController];
4182   [mFullscreenTitlebarTracker release];
4184   [super dealloc];
4187 - (NSArray<NSView*>*)contentViewContents {
4188   NSMutableArray<NSView*>* contents =
4189       [[[self contentView] subviews] mutableCopy];
4190   if (mTitlebarView) {
4191     // Do not include the titlebar gradient view in the returned array.
4192     [contents removeObject:mTitlebarView];
4193   }
4194   return [contents autorelease];
4197 - (void)updateTitlebarView {
4198   BOOL needTitlebarView =
4199       ![self drawsContentsIntoWindowFrame] || mUnifiedToolbarHeight > 0;
4200   if (needTitlebarView && !mTitlebarView) {
4201     mTitlebarView =
4202         [[MOZTitlebarView alloc] initWithFrame:[self unifiedToolbarRect]];
4203     mTitlebarView.autoresizingMask = NSViewWidthSizable | NSViewMinYMargin;
4204     [self.contentView addSubview:mTitlebarView
4205                       positioned:NSWindowBelow
4206                       relativeTo:nil];
4207   } else if (needTitlebarView && mTitlebarView) {
4208     mTitlebarView.frame = [self unifiedToolbarRect];
4209   } else if (!needTitlebarView && mTitlebarView) {
4210     [mTitlebarView removeFromSuperview];
4211     [mTitlebarView release];
4212     mTitlebarView = nil;
4213   }
4216 - (void)windowMainStateChanged {
4217   [self setTitlebarNeedsDisplay];
4218   [[self mainChildView] ensureNextCompositeIsAtomicWithMainThreadPaint];
4221 - (void)setTitlebarNeedsDisplay {
4222   [mTitlebarView setNeedsDisplay:YES];
4225 - (NSRect)titlebarRect {
4226   CGFloat titlebarHeight = [self titlebarHeight];
4227   return NSMakeRect(0, [self frame].size.height - titlebarHeight,
4228                     [self frame].size.width, titlebarHeight);
4231 // In window contentView coordinates (origin bottom left)
4232 - (NSRect)unifiedToolbarRect {
4233   return NSMakeRect(0, [self frame].size.height - mUnifiedToolbarHeight,
4234                     [self frame].size.width, mUnifiedToolbarHeight);
4237 // Returns the unified height of titlebar + toolbar.
4238 - (CGFloat)unifiedToolbarHeight {
4239   return mUnifiedToolbarHeight;
4242 - (CGFloat)titlebarHeight {
4243   // We use the original content rect here, not what we return from
4244   // [self contentRectForFrameRect:], because that would give us a
4245   // titlebarHeight of zero.
4246   NSRect frameRect = [self frame];
4247   NSUInteger styleMask = [self styleMask];
4248   styleMask &= ~NSWindowStyleMaskFullSizeContentView;
4249   NSRect originalContentRect = [NSWindow contentRectForFrameRect:frameRect
4250                                                        styleMask:styleMask];
4251   return NSMaxY(frameRect) - NSMaxY(originalContentRect);
4254 // Stores the complete height of titlebar + toolbar.
4255 - (void)setUnifiedToolbarHeight:(CGFloat)aHeight {
4256   if (aHeight == mUnifiedToolbarHeight) return;
4258   mUnifiedToolbarHeight = aHeight;
4260   [self updateTitlebarView];
4263 // Extending the content area into the title bar works by resizing the
4264 // mainChildView so that it covers the titlebar.
4265 - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState {
4266   BOOL stateChanged = ([self drawsContentsIntoWindowFrame] != aState);
4267   [super setDrawsContentsIntoWindowFrame:aState];
4268   if (stateChanged && [[self delegate] isKindOfClass:[WindowDelegate class]]) {
4269     // Here we extend / shrink our mainChildView. We do that by firing a resize
4270     // event which will cause the ChildView to be resized to the rect returned
4271     // by nsCocoaWindow::GetClientBounds. GetClientBounds bases its return
4272     // value on what we return from drawsContentsIntoWindowFrame.
4273     WindowDelegate* windowDelegate = (WindowDelegate*)[self delegate];
4274     nsCocoaWindow* geckoWindow = [windowDelegate geckoWidget];
4275     if (geckoWindow) {
4276       // Re-layout our contents.
4277       geckoWindow->ReportSizeEvent();
4278     }
4280     // Resizing the content area causes a reflow which would send a synthesized
4281     // mousemove event to the old mouse position relative to the top left
4282     // corner of the content area. But the mouse has shifted relative to the
4283     // content area, so that event would have wrong position information. So
4284     // we'll send a mouse move event with the correct new position.
4285     ChildViewMouseTracker::ResendLastMouseMoveEvent();
4286   }
4288   [self updateTitlebarView];
4291 - (void)setWantsTitleDrawn:(BOOL)aDrawTitle {
4292   [super setWantsTitleDrawn:aDrawTitle];
4293   [self setTitlebarNeedsDisplay];
4296 - (void)setSheetAttachmentPosition:(CGFloat)aY {
4297   mSheetAttachmentPosition = aY;
4300 - (CGFloat)sheetAttachmentPosition {
4301   return mSheetAttachmentPosition;
4304 - (void)placeWindowButtons:(NSRect)aRect {
4305   if (!NSEqualRects(mWindowButtonsRect, aRect)) {
4306     mWindowButtonsRect = aRect;
4307     [self reflowTitlebarElements];
4308   }
4311 - (NSRect)windowButtonsRect {
4312   return mWindowButtonsRect;
4315 // Returning YES here makes the setShowsToolbarButton method work even though
4316 // the window doesn't contain an NSToolbar.
4317 - (BOOL)_hasToolbar {
4318   return YES;
4321 // Dispatch a toolbar pill button clicked message to Gecko.
4322 - (void)_toolbarPillButtonClicked:(id)sender {
4323   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4325   RollUpPopups();
4327   if ([[self delegate] isKindOfClass:[WindowDelegate class]]) {
4328     WindowDelegate* windowDelegate = (WindowDelegate*)[self delegate];
4329     nsCocoaWindow* geckoWindow = [windowDelegate geckoWidget];
4330     if (!geckoWindow) return;
4332     nsIWidgetListener* listener = geckoWindow->GetWidgetListener();
4333     if (listener) listener->OSToolbarButtonPressed();
4334   }
4336   NS_OBJC_END_TRY_IGNORE_BLOCK;
4339 // Retain and release "self" to avoid crashes when our widget (and its native
4340 // window) is closed as a result of processing a key equivalent (e.g.
4341 // Command+w or Command+q).  This workaround is only needed for a window
4342 // that can become key.
4343 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
4344   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4346   NSWindow* nativeWindow = [self retain];
4347   BOOL retval = [super performKeyEquivalent:theEvent];
4348   [nativeWindow release];
4349   return retval;
4351   NS_OBJC_END_TRY_BLOCK_RETURN(NO);
4354 - (void)sendEvent:(NSEvent*)anEvent {
4355   NSEventType type = [anEvent type];
4357   switch (type) {
4358     case NSEventTypeScrollWheel:
4359     case NSEventTypeLeftMouseDown:
4360     case NSEventTypeLeftMouseUp:
4361     case NSEventTypeRightMouseDown:
4362     case NSEventTypeRightMouseUp:
4363     case NSEventTypeOtherMouseDown:
4364     case NSEventTypeOtherMouseUp:
4365     case NSEventTypeMouseMoved:
4366     case NSEventTypeLeftMouseDragged:
4367     case NSEventTypeRightMouseDragged:
4368     case NSEventTypeOtherMouseDragged: {
4369       // Drop all mouse events if a modal window has appeared above us.
4370       // This helps make us behave as if the OS were running a "real" modal
4371       // event loop.
4372       id delegate = [self delegate];
4373       if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
4374         nsCocoaWindow* widget = [(WindowDelegate*)delegate geckoWidget];
4375         if (widget) {
4376           if (gGeckoAppModalWindowList &&
4377               (widget != gGeckoAppModalWindowList->window))
4378             return;
4379           if (widget->HasModalDescendents()) return;
4380         }
4381       }
4382       break;
4383     }
4384     default:
4385       break;
4386   }
4388   [super sendEvent:anEvent];
4391 @end
4393 @implementation PopupWindow
4395 - (id)initWithContentRect:(NSRect)contentRect
4396                 styleMask:(NSUInteger)styleMask
4397                   backing:(NSBackingStoreType)bufferingType
4398                     defer:(BOOL)deferCreation {
4399   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4401   mIsContextMenu = false;
4402   return [super initWithContentRect:contentRect
4403                           styleMask:styleMask
4404                             backing:bufferingType
4405                               defer:deferCreation];
4407   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
4410 // Override the private API _backdropBleedAmount. This determines how much the
4411 // desktop wallpaper contributes to the vibrancy backdrop.
4412 // Return 0 in order to match what the system does for sheet windows and
4413 // _NSPopoverWindows.
4414 - (CGFloat)_backdropBleedAmount {
4415   return 0.0;
4418 // Override the private API shadowOptions.
4419 // The constants below were found in AppKit's implementations of the
4420 // shadowOptions method on the various window types.
4421 static const NSUInteger kWindowShadowOptionsNoShadow = 0;
4422 static const NSUInteger kWindowShadowOptionsMenu = 2;
4423 static const NSUInteger kWindowShadowOptionsTooltip = 4;
4425 - (NSUInteger)shadowOptions {
4426   if (!self.hasShadow) {
4427     return kWindowShadowOptionsNoShadow;
4428   }
4430   switch (self.shadowStyle) {
4431     case StyleWindowShadow::None:
4432       return kWindowShadowOptionsNoShadow;
4434     case StyleWindowShadow::Default:  // we treat "default" as "default panel"
4435     case StyleWindowShadow::Menu:
4436       return kWindowShadowOptionsMenu;
4438     case StyleWindowShadow::Tooltip:
4439       return kWindowShadowOptionsTooltip;
4440   }
4443 - (BOOL)isContextMenu {
4444   return mIsContextMenu;
4447 - (void)setIsContextMenu:(BOOL)flag {
4448   mIsContextMenu = flag;
4451 - (BOOL)canBecomeMainWindow {
4452   // This is overriden because the default is 'yes' when a titlebar is present.
4453   return NO;
4456 @end
4458 // According to Apple's docs on [NSWindow canBecomeKeyWindow] and [NSWindow
4459 // canBecomeMainWindow], windows without a title bar or resize bar can't (by
4460 // default) become key or main.  But if a window can't become key, it can't
4461 // accept keyboard input (bmo bug 393250).  And it should also be possible for
4462 // an otherwise "ordinary" window to become main.  We need to override these
4463 // two methods to make this happen.
4464 @implementation BorderlessWindow
4466 - (BOOL)canBecomeKeyWindow {
4467   return YES;
4470 - (void)sendEvent:(NSEvent*)anEvent {
4471   NSEventType type = [anEvent type];
4473   switch (type) {
4474     case NSEventTypeScrollWheel:
4475     case NSEventTypeLeftMouseDown:
4476     case NSEventTypeLeftMouseUp:
4477     case NSEventTypeRightMouseDown:
4478     case NSEventTypeRightMouseUp:
4479     case NSEventTypeOtherMouseDown:
4480     case NSEventTypeOtherMouseUp:
4481     case NSEventTypeMouseMoved:
4482     case NSEventTypeLeftMouseDragged:
4483     case NSEventTypeRightMouseDragged:
4484     case NSEventTypeOtherMouseDragged: {
4485       // Drop all mouse events if a modal window has appeared above us.
4486       // This helps make us behave as if the OS were running a "real" modal
4487       // event loop.
4488       id delegate = [self delegate];
4489       if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
4490         nsCocoaWindow* widget = [(WindowDelegate*)delegate geckoWidget];
4491         if (widget) {
4492           if (gGeckoAppModalWindowList &&
4493               (widget != gGeckoAppModalWindowList->window))
4494             return;
4495           if (widget->HasModalDescendents()) return;
4496         }
4497       }
4498       break;
4499     }
4500     default:
4501       break;
4502   }
4504   [super sendEvent:anEvent];
4507 // Apple's doc on this method says that the NSWindow class's default is not to
4508 // become main if the window isn't "visible" -- so we should replicate that
4509 // behavior here.  As best I can tell, the [NSWindow isVisible] method is an
4510 // accurate test of what Apple means by "visibility".
4511 - (BOOL)canBecomeMainWindow {
4512   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4514   if (![self isVisible]) return NO;
4515   return YES;
4517   NS_OBJC_END_TRY_BLOCK_RETURN(NO);
4520 // Retain and release "self" to avoid crashes when our widget (and its native
4521 // window) is closed as a result of processing a key equivalent (e.g.
4522 // Command+w or Command+q).  This workaround is only needed for a window
4523 // that can become key.
4524 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
4525   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4527   NSWindow* nativeWindow = [self retain];
4528   BOOL retval = [super performKeyEquivalent:theEvent];
4529   [nativeWindow release];
4530   return retval;
4532   NS_OBJC_END_TRY_BLOCK_RETURN(NO);
4535 @end