Bug 1646817 - Support DocumentChannel process switching in sidebars and popups r...
[gecko.git] / widget / cocoa / nsCocoaWindow.mm
blob4db44df9d8a34b39c79969a8e623fa2debffba34
1 /* -*- Mode: Objective-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 "NativeKeyBindings.h"
10 #include "ScreenHelperCocoa.h"
11 #include "TextInputHandler.h"
12 #include "nsObjCExceptions.h"
13 #include "nsCOMPtr.h"
14 #include "nsWidgetsCID.h"
15 #include "nsIRollupListener.h"
16 #include "nsChildView.h"
17 #include "nsWindowMap.h"
18 #include "nsAppShell.h"
19 #include "nsIAppShellService.h"
20 #include "nsIBaseWindow.h"
21 #include "nsIInterfaceRequestorUtils.h"
22 #include "nsIAppWindow.h"
23 #include "nsToolkit.h"
24 #include "nsTouchBarNativeAPIDefines.h"
25 #include "nsPIDOMWindow.h"
26 #include "nsThreadUtils.h"
27 #include "nsMenuBarX.h"
28 #include "nsMenuUtilsX.h"
29 #include "nsStyleConsts.h"
30 #include "nsNativeThemeColors.h"
31 #include "nsNativeThemeCocoa.h"
32 #include "nsChildView.h"
33 #include "nsCocoaFeatures.h"
34 #include "nsIScreenManager.h"
35 #include "nsIWidgetListener.h"
36 #include "VibrancyManager.h"
37 #include "nsPresContext.h"
38 #include "nsDocShell.h"
40 #include "gfxPlatform.h"
41 #include "qcms.h"
43 #include "mozilla/AutoRestore.h"
44 #include "mozilla/BasicEvents.h"
45 #include "mozilla/Preferences.h"
46 #include "mozilla/StaticPrefs_gfx.h"
47 #include "mozilla/StaticPrefs_widget.h"
48 #include "mozilla/PresShell.h"
49 #include "mozilla/layers/CompositorBridgeChild.h"
50 #include <algorithm>
52 namespace mozilla {
53 namespace layers {
54 class LayerManager;
55 }  // namespace layers
56 }  // namespace mozilla
57 using namespace mozilla::layers;
58 using namespace mozilla::widget;
59 using namespace mozilla;
61 int32_t gXULModalLevel = 0;
63 // In principle there should be only one app-modal window at any given time.
64 // But sometimes, despite our best efforts, another window appears above the
65 // current app-modal window.  So we need to keep a linked list of app-modal
66 // windows.  (A non-sheet window that appears above an app-modal window is
67 // also made app-modal.)  See nsCocoaWindow::SetModal().
68 nsCocoaWindowList* gGeckoAppModalWindowList = NULL;
70 BOOL sTouchBarIsInitialized = NO;
72 // defined in nsMenuBarX.mm
73 extern NSMenu* sApplicationMenu;  // Application menu shared by all menubars
75 // defined in nsChildView.mm
76 extern BOOL gSomeMenuBarPainted;
78 #if !defined(MAC_OS_X_VERSION_10_9) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9
80 enum NSWindowOcclusionState { NSWindowOcclusionStateVisible = 0x1 << 1 };
82 @interface NSWindow (OcclusionState)
83 - (NSWindowOcclusionState)occlusionState;
84 @end
86 #endif
88 #if !defined(MAC_OS_X_VERSION_10_10) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10
90 enum NSWindowTitleVisibility { NSWindowTitleVisible = 0, NSWindowTitleHidden = 1 };
92 @interface NSWindow (TitleVisibility)
93 - (void)setTitleVisibility:(NSWindowTitleVisibility)visibility;
94 - (void)setTitlebarAppearsTransparent:(BOOL)isTitlebarTransparent;
95 @end
97 #endif
99 #if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
101 @interface NSWindow (AutomaticWindowTabbing)
102 + (void)setAllowsAutomaticWindowTabbing:(BOOL)allow;
103 @end
105 #endif
107 extern "C" {
108 // CGSPrivate.h
109 typedef NSInteger CGSConnection;
110 typedef NSUInteger CGSSpaceID;
111 typedef NSInteger CGSWindow;
112 typedef NSUInteger CGSWindowFilterRef;
113 typedef enum {
114   kCGSSpaceIncludesCurrent = 1 << 0,
115   kCGSSpaceIncludesOthers = 1 << 1,
116   kCGSSpaceIncludesUser = 1 << 2,
118   kCGSAllSpacesMask = kCGSSpaceIncludesCurrent | kCGSSpaceIncludesOthers | kCGSSpaceIncludesUser
119 } CGSSpaceMask;
120 static NSString* const CGSSpaceIDKey = @"ManagedSpaceID";
121 static NSString* const CGSSpacesKey = @"Spaces";
122 extern CGSConnection _CGSDefaultConnection(void);
123 extern CGError CGSSetWindowShadowAndRimParameters(const CGSConnection cid, CGSWindow wid,
124                                                   float standardDeviation, float density,
125                                                   int offsetX, int offsetY, unsigned int flags);
126 extern CGError CGSSetWindowBackgroundBlurRadius(CGSConnection cid, CGSWindow wid, NSUInteger blur);
127 extern CGError CGSSetWindowTransform(CGSConnection cid, CGSWindow wid, CGAffineTransform transform);
130 #define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1"
132 NS_IMPL_ISUPPORTS_INHERITED(nsCocoaWindow, Inherited, nsPIWidgetCocoa)
134 // A note on testing to see if your object is a sheet...
135 // |mWindowType == eWindowType_sheet| is true if your gecko nsIWidget is a sheet
136 // widget - whether or not the sheet is showing. |[mWindow isSheet]| will return
137 // true *only when the sheet is actually showing*. Choose your test wisely.
139 static void RollUpPopups() {
140   nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
141   NS_ENSURE_TRUE_VOID(rollupListener);
142   nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
143   if (!rollupWidget) return;
144   rollupListener->Rollup(0, true, nullptr, nullptr);
147 nsCocoaWindow::nsCocoaWindow()
148     : mParent(nullptr),
149       mAncestorLink(nullptr),
150       mWindow(nil),
151       mDelegate(nil),
152       mSheetWindowParent(nil),
153       mPopupContentView(nil),
154       mFullscreenTransitionAnimation(nil),
155       mShadowStyle(StyleWindowShadow::Default),
156       mBackingScaleFactor(0.0),
157       mAnimationType(nsIWidget::eGenericWindowAnimation),
158       mWindowMadeHere(false),
159       mSheetNeedsShow(false),
160       mInFullScreenMode(false),
161       mInFullScreenTransition(false),
162       mModal(false),
163       mFakeModal(false),
164       mInNativeFullScreenMode(false),
165       mIsAnimationSuppressed(false),
166       mInReportMoveEvent(false),
167       mInResize(false),
168       mWindowTransformIsIdentity(true),
169       mAlwaysOnTop(false),
170       mAspectRatioLocked(false),
171       mNumModalDescendents(0),
172       mWindowAnimationBehavior(NSWindowAnimationBehaviorDefault),
173       mWasShown(false) {
174   if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)]) {
175     // Disable automatic tabbing on 10.12. We need to do this before we
176     // orderFront any of our windows.
177     [NSWindow setAllowsAutomaticWindowTabbing:NO];
178   }
181 void nsCocoaWindow::DestroyNativeWindow() {
182   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
184   if (!mWindow) return;
186   [mWindow releaseJSObjects];
187   // We want to unhook the delegate here because we don't want events
188   // sent to it after this object has been destroyed.
189   [mWindow setDelegate:nil];
190   [mWindow close];
191   mWindow = nil;
192   [mDelegate autorelease];
194   NS_OBJC_END_TRY_ABORT_BLOCK;
197 nsCocoaWindow::~nsCocoaWindow() {
198   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
200   // Notify the children that we're gone.  Popup windows (e.g. tooltips) can
201   // have nsChildView children.  'kid' is an nsChildView object if and only if
202   // its 'type' is 'eWindowType_child'.
203   // childView->ResetParent() can change our list of children while it's
204   // being iterated, so the way we iterate the list must allow for this.
205   for (nsIWidget* kid = mLastChild; kid;) {
206     nsWindowType kidType = kid->WindowType();
207     if (kidType == eWindowType_child) {
208       nsChildView* childView = static_cast<nsChildView*>(kid);
209       kid = kid->GetPrevSibling();
210       childView->ResetParent();
211     } else {
212       nsCocoaWindow* childWindow = static_cast<nsCocoaWindow*>(kid);
213       childWindow->mParent = nullptr;
214       childWindow->mAncestorLink = mAncestorLink;
215       kid = kid->GetPrevSibling();
216     }
217   }
219   if (mWindow && mWindowMadeHere) {
220     DestroyNativeWindow();
221   }
223   NS_IF_RELEASE(mPopupContentView);
225   // Deal with the possiblity that we're being destroyed while running modal.
226   if (mModal) {
227     NS_WARNING("Widget destroyed while running modal!");
228     --gXULModalLevel;
229     NS_ASSERTION(gXULModalLevel >= 0, "Weirdness setting modality!");
230   }
232   NS_OBJC_END_TRY_ABORT_BLOCK;
235 // Find the screen that overlaps aRect the most,
236 // if none are found default to the mainScreen.
237 static NSScreen* FindTargetScreenForRect(const DesktopIntRect& aRect) {
238   NSScreen* targetScreen = [NSScreen mainScreen];
239   NSEnumerator* screenEnum = [[NSScreen screens] objectEnumerator];
240   int largestIntersectArea = 0;
241   while (NSScreen* screen = [screenEnum nextObject]) {
242     DesktopIntRect screenRect = nsCocoaUtils::CocoaRectToGeckoRect([screen visibleFrame]);
243     screenRect = screenRect.Intersect(aRect);
244     int area = screenRect.width * screenRect.height;
245     if (area > largestIntersectArea) {
246       largestIntersectArea = area;
247       targetScreen = screen;
248     }
249   }
250   return targetScreen;
253 // fits the rect to the screen that contains the largest area of it,
254 // or to aScreen if a screen is passed in
255 // NB: this operates with aRect in desktop pixels
256 static void FitRectToVisibleAreaForScreen(DesktopIntRect& aRect, NSScreen* aScreen) {
257   if (!aScreen) {
258     aScreen = FindTargetScreenForRect(aRect);
259   }
261   DesktopIntRect screenBounds = nsCocoaUtils::CocoaRectToGeckoRect([aScreen visibleFrame]);
263   if (aRect.width > screenBounds.width) {
264     aRect.width = screenBounds.width;
265   }
266   if (aRect.height > screenBounds.height) {
267     aRect.height = screenBounds.height;
268   }
270   if (aRect.x - screenBounds.x + aRect.width > screenBounds.width) {
271     aRect.x += screenBounds.width - (aRect.x - screenBounds.x + aRect.width);
272   }
273   if (aRect.y - screenBounds.y + aRect.height > screenBounds.height) {
274     aRect.y += screenBounds.height - (aRect.y - screenBounds.y + aRect.height);
275   }
277   // If the left/top edge of the window is off the screen in either direction,
278   // then set the window to start at the left/top edge of the screen.
279   if (aRect.x < screenBounds.x || aRect.x > (screenBounds.x + screenBounds.width)) {
280     aRect.x = screenBounds.x;
281   }
282   if (aRect.y < screenBounds.y || aRect.y > (screenBounds.y + screenBounds.height)) {
283     aRect.y = screenBounds.y;
284   }
287 // Some applications use native popup windows
288 // (native context menus, native tooltips)
289 static bool UseNativePopupWindows() {
290 #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
291   return true;
292 #else
293   return false;
294 #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
297 // aRect here is specified in desktop pixels
298 nsresult nsCocoaWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
299                                const DesktopIntRect& aRect, nsWidgetInitData* aInitData) {
300   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
302   // Because the hidden window is created outside of an event loop,
303   // we have to provide an autorelease pool (see bug 559075).
304   nsAutoreleasePool localPool;
306   DesktopIntRect newBounds = aRect;
307   FitRectToVisibleAreaForScreen(newBounds, nullptr);
309   // Set defaults which can be overriden from aInitData in BaseCreate
310   mWindowType = eWindowType_toplevel;
311   mBorderStyle = eBorderStyle_default;
313   // Ensure that the toolkit is created.
314   nsToolkit::GetToolkit();
316   Inherited::BaseCreate(aParent, aInitData);
318   mParent = aParent;
319   mAncestorLink = aParent;
320   mAlwaysOnTop = aInitData->mAlwaysOnTop;
322   // Applications that use native popups don't want us to create popup windows.
323   if ((mWindowType == eWindowType_popup) && UseNativePopupWindows()) return NS_OK;
325   nsresult rv =
326       CreateNativeWindow(nsCocoaUtils::GeckoRectToCocoaRect(newBounds), mBorderStyle, false);
327   NS_ENSURE_SUCCESS(rv, rv);
329   if (mWindowType == eWindowType_popup) {
330     SetWindowMouseTransparent(aInitData->mMouseTransparent);
332     // now we can convert newBounds to device pixels for the window we created,
333     // as the child view expects a rect expressed in the dev pix of its parent
334     LayoutDeviceIntRect devRect = RoundedToInt(newBounds * GetDesktopToDeviceScale());
335     return CreatePopupContentView(devRect, aInitData);
336   }
338   mIsAnimationSuppressed = aInitData->mIsAnimationSuppressed;
340   return NS_OK;
342   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
345 nsresult nsCocoaWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
346                                const LayoutDeviceIntRect& aRect, nsWidgetInitData* aInitData) {
347   DesktopIntRect desktopRect = RoundedToInt(aRect / GetDesktopToDeviceScale());
348   return Create(aParent, aNativeParent, desktopRect, aInitData);
351 static unsigned int WindowMaskForBorderStyle(nsBorderStyle aBorderStyle) {
352   bool allOrDefault = (aBorderStyle == eBorderStyle_all || aBorderStyle == eBorderStyle_default);
354   /* Apple's docs on NSWindow styles say that "a window's style mask should
355    * include NSWindowStyleMaskTitled if it includes any of the others [besides
356    * NSWindowStyleMaskBorderless]".  This implies that a borderless window
357    * shouldn't have any other styles than NSWindowStyleMaskBorderless.
358    */
359   if (!allOrDefault && !(aBorderStyle & eBorderStyle_title)) return NSWindowStyleMaskBorderless;
361   unsigned int mask = NSWindowStyleMaskTitled;
362   if (allOrDefault || aBorderStyle & eBorderStyle_close) mask |= NSWindowStyleMaskClosable;
363   if (allOrDefault || aBorderStyle & eBorderStyle_minimize) mask |= NSWindowStyleMaskMiniaturizable;
364   if (allOrDefault || aBorderStyle & eBorderStyle_resizeh) mask |= NSWindowStyleMaskResizable;
366   return mask;
369 // If aRectIsFrameRect, aRect specifies the frame rect of the new window.
370 // Otherwise, aRect.x/y specify the position of the window's frame relative to
371 // the bottom of the menubar and aRect.width/height specify the size of the
372 // content rect.
373 nsresult nsCocoaWindow::CreateNativeWindow(const NSRect& aRect, nsBorderStyle aBorderStyle,
374                                            bool aRectIsFrameRect) {
375   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
377   // We default to NSWindowStyleMaskBorderless, add features if needed.
378   unsigned int features = NSWindowStyleMaskBorderless;
380   // Configure the window we will create based on the window type.
381   switch (mWindowType) {
382     case eWindowType_invisible:
383     case eWindowType_child:
384     case eWindowType_plugin:
385       break;
386     case eWindowType_popup:
387       if (aBorderStyle != eBorderStyle_default && mBorderStyle & eBorderStyle_title) {
388         features |= NSWindowStyleMaskTitled;
389         if (aBorderStyle & eBorderStyle_close) {
390           features |= NSWindowStyleMaskClosable;
391         }
392       }
393       break;
394     case eWindowType_toplevel:
395     case eWindowType_dialog:
396       features = WindowMaskForBorderStyle(aBorderStyle);
397       break;
398     case eWindowType_sheet:
399       if (mParent->WindowType() != eWindowType_invisible && aBorderStyle & eBorderStyle_resizeh) {
400         features = NSWindowStyleMaskResizable;
401       } else {
402         features = NSWindowStyleMaskMiniaturizable;
403       }
404       features |= NSWindowStyleMaskTitled;
405       break;
406     default:
407       NS_ERROR("Unhandled window type!");
408       return NS_ERROR_FAILURE;
409   }
411   NSRect contentRect;
413   if (aRectIsFrameRect) {
414     contentRect = [NSWindow contentRectForFrameRect:aRect styleMask:features];
415   } else {
416     /*
417      * We pass a content area rect to initialize the native Cocoa window. The
418      * content rect we give is the same size as the size we're given by gecko.
419      * The origin we're given for non-popup windows is moved down by the height
420      * of the menu bar so that an origin of (0,100) from gecko puts the window
421      * 100 pixels below the top of the available desktop area. We also move the
422      * origin down by the height of a title bar if it exists. This is so the
423      * origin that gecko gives us for the top-left of  the window turns out to
424      * be the top-left of the window we create. This is how it was done in
425      * Carbon. If it ought to be different we'll probably need to look at all
426      * the callers.
427      *
428      * Note: This means that if you put a secondary screen on top of your main
429      * screen and open a window in the top screen, it'll be incorrectly shifted
430      * down by the height of the menu bar. Same thing would happen in Carbon.
431      *
432      * Note: If you pass a rect with 0,0 for an origin, the window ends up in a
433      * weird place for some reason. This stops that without breaking popups.
434      */
435     // Compensate for difference between frame and content area height (e.g. title bar).
436     NSRect newWindowFrame = [NSWindow frameRectForContentRect:aRect styleMask:features];
438     contentRect = aRect;
439     contentRect.origin.y -= (newWindowFrame.size.height - aRect.size.height);
441     if (mWindowType != eWindowType_popup) contentRect.origin.y -= [[NSApp mainMenu] menuBarHeight];
442   }
444   // NSLog(@"Top-level window being created at Cocoa rect: %f, %f, %f, %f\n",
445   //       rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
447   Class windowClass = [BaseWindow class];
448   // If we have a titlebar on a top-level window, we want to be able to control the
449   // titlebar color (for unified windows), so use the special ToolbarWindow class.
450   // Note that we need to check the window type because we mark sheets as
451   // having titlebars.
452   if ((mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) &&
453       (features & NSWindowStyleMaskTitled))
454     windowClass = [ToolbarWindow class];
455   // If we're a popup window we need to use the PopupWindow class.
456   else if (mWindowType == eWindowType_popup)
457     windowClass = [PopupWindow class];
458   // If we're a non-popup borderless window we need to use the
459   // BorderlessWindow class.
460   else if (features == NSWindowStyleMaskBorderless)
461     windowClass = [BorderlessWindow class];
463   // Create the window
464   mWindow = [[windowClass alloc] initWithContentRect:contentRect
465                                            styleMask:features
466                                              backing:NSBackingStoreBuffered
467                                                defer:YES];
469   // Make sure that window titles don't leak to disk in private browsing mode
470   // due to macOS' resume feature.
471   [mWindow setRestorable:NO];
472   [mWindow disableSnapshotRestoration];
474   // setup our notification delegate. Note that setDelegate: does NOT retain.
475   mDelegate = [[WindowDelegate alloc] initWithGeckoWindow:this];
476   [mWindow setDelegate:mDelegate];
478   // Make sure that the content rect we gave has been honored.
479   NSRect wantedFrame = [mWindow frameRectForChildViewRect:contentRect];
480   if (!NSEqualRects([mWindow frame], wantedFrame)) {
481     // This can happen when the window is not on the primary screen.
482     [mWindow setFrame:wantedFrame display:NO];
483   }
484   UpdateBounds();
486   if (mWindowType == eWindowType_invisible) {
487     [mWindow setLevel:kCGDesktopWindowLevelKey];
488   }
490   if (mWindowType == eWindowType_popup) {
491     SetPopupWindowLevel();
492     [mWindow setBackgroundColor:[NSColor clearColor]];
493     [mWindow setOpaque:NO];
495     // When multiple spaces are in use and the browser is assigned to a
496     // particular space, override the "Assign To" space and display popups on
497     // the active space. Does not work with multiple displays. See
498     // NeedsRecreateToReshow() for multi-display with multi-space workaround.
499     if (!mAlwaysOnTop) {
500       NSWindowCollectionBehavior behavior = [mWindow collectionBehavior];
501       behavior |= NSWindowCollectionBehaviorMoveToActiveSpace;
502       [mWindow setCollectionBehavior:behavior];
503     }
504   } else {
505     // Non-popup windows are always opaque.
506     [mWindow setOpaque:YES];
507   }
509   NSWindowCollectionBehavior newBehavior = [mWindow collectionBehavior];
510   if (mAlwaysOnTop) {
511     [mWindow setLevel:NSFloatingWindowLevel];
512     newBehavior |= NSWindowCollectionBehaviorCanJoinAllSpaces;
513   }
514   [mWindow setCollectionBehavior:newBehavior];
516   [mWindow setContentMinSize:NSMakeSize(60, 60)];
517   [mWindow disableCursorRects];
519   // Make the window use CoreAnimation from the start, so that we don't
520   // switch from a non-CA window to a CA-window in the middle.
521   [[mWindow contentView] setWantsLayer:YES];
523   // Make sure the window starts out not draggable by the background.
524   // We will turn it on as necessary.
525   [mWindow setMovableByWindowBackground:NO];
527   [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow];
528   mWindowMadeHere = true;
530   return NS_OK;
532   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
535 nsresult nsCocoaWindow::CreatePopupContentView(const LayoutDeviceIntRect& aRect,
536                                                nsWidgetInitData* aInitData) {
537   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
539   // We need to make our content view a ChildView.
540   mPopupContentView = new nsChildView();
541   if (!mPopupContentView) return NS_ERROR_FAILURE;
543   NS_ADDREF(mPopupContentView);
545   nsIWidget* thisAsWidget = static_cast<nsIWidget*>(this);
546   nsresult rv = mPopupContentView->Create(thisAsWidget, nullptr, aRect, aInitData);
547   if (NS_WARN_IF(NS_FAILED(rv))) {
548     return rv;
549   }
551   NSView* contentView = [mWindow contentView];
552   ChildView* childView = (ChildView*)mPopupContentView->GetNativeData(NS_NATIVE_WIDGET);
553   [childView setFrame:[contentView bounds]];
554   [childView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
555   [contentView addSubview:childView];
557   return NS_OK;
559   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
562 void nsCocoaWindow::Destroy() {
563   if (mOnDestroyCalled) return;
564   mOnDestroyCalled = true;
566   // SetFakeModal(true) is called for non-modal window opened by modal window.
567   // On Cocoa, it needs corresponding SetFakeModal(false) on destroy to restore
568   // ancestor windows' state.
569   if (mFakeModal) {
570     SetFakeModal(false);
571   }
573   // If we don't hide here we run into problems with panels, this is not ideal.
574   // (Bug 891424)
575   Show(false);
577   if (mPopupContentView) mPopupContentView->Destroy();
579   if (mFullscreenTransitionAnimation) {
580     [mFullscreenTransitionAnimation stopAnimation];
581     ReleaseFullscreenTransitionAnimation();
582   }
584   nsBaseWidget::Destroy();
585   // nsBaseWidget::Destroy() calls GetParent()->RemoveChild(this). But we
586   // don't implement GetParent(), so we need to do the equivalent here.
587   if (mParent) {
588     mParent->RemoveChild(this);
589   }
590   nsBaseWidget::OnDestroy();
592   if (mInFullScreenMode) {
593     // On Lion we don't have to mess with the OS chrome when in Full Screen
594     // mode.  But we do have to destroy the native window here (and not wait
595     // for that to happen in our destructor).  We don't switch away from the
596     // native window's space until the window is destroyed, and otherwise this
597     // might not happen for several seconds (because at least one object
598     // holding a reference to ourselves is usually waiting to be garbage-
599     // collected).  See bug 757618.
600     if (mInNativeFullScreenMode) {
601       DestroyNativeWindow();
602     } else if (mWindow) {
603       nsCocoaUtils::HideOSChromeOnScreen(false);
604     }
605   }
608 nsIWidget* nsCocoaWindow::GetSheetWindowParent(void) {
609   if (mWindowType != eWindowType_sheet) return nullptr;
610   nsCocoaWindow* parent = static_cast<nsCocoaWindow*>(mParent);
611   while (parent && (parent->mWindowType == eWindowType_sheet))
612     parent = static_cast<nsCocoaWindow*>(parent->mParent);
613   return parent;
616 void* nsCocoaWindow::GetNativeData(uint32_t aDataType) {
617   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL;
619   void* retVal = nullptr;
621   switch (aDataType) {
622     // to emulate how windows works, we always have to return a NSView
623     // for NS_NATIVE_WIDGET
624     case NS_NATIVE_WIDGET:
625     case NS_NATIVE_DISPLAY:
626       retVal = [mWindow contentView];
627       break;
629     case NS_NATIVE_WINDOW:
630       retVal = mWindow;
631       break;
633     case NS_NATIVE_GRAPHIC:
634       // There isn't anything that makes sense to return here,
635       // and it doesn't matter so just return nullptr.
636       NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a top-level window!");
637       break;
638     case NS_RAW_NATIVE_IME_CONTEXT: {
639       retVal = GetPseudoIMEContext();
640       if (retVal) {
641         break;
642       }
643       NSView* view = mWindow ? [mWindow contentView] : nil;
644       if (view) {
645         retVal = [view inputContext];
646       }
647       // If inputContext isn't available on this window, return this window's
648       // pointer instead of nullptr since if this returns nullptr,
649       // IMEStateManager cannot manage composition with TextComposition
650       // instance.  Although, this case shouldn't occur.
651       if (NS_WARN_IF(!retVal)) {
652         retVal = this;
653       }
654       break;
655     }
656   }
658   return retVal;
660   NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
663 bool nsCocoaWindow::IsVisible() const {
664   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
666   return (mWindow && ([mWindow isVisibleOrBeingShown] || mSheetNeedsShow));
668   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
671 void nsCocoaWindow::SetModal(bool aState) {
672   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
674   if (!mWindow) return;
676   // This is used during startup (outside the event loop) when creating
677   // the add-ons compatibility checking dialog and the profile manager UI;
678   // therefore, it needs to provide an autorelease pool to avoid cocoa
679   // objects leaking.
680   nsAutoreleasePool localPool;
682   mModal = aState;
683   nsCocoaWindow* ancestor = static_cast<nsCocoaWindow*>(mAncestorLink);
684   if (aState) {
685     ++gXULModalLevel;
686     // When a non-sheet window gets "set modal", make the window(s) that it
687     // appears over behave as they should.  We can't rely on native methods to
688     // do this, for the following reason:  The OS runs modal non-sheet windows
689     // in an event loop (using [NSApplication runModalForWindow:] or similar
690     // methods) that's incompatible with the modal event loop in AppWindow::
691     // ShowModal() (each of these event loops is "exclusive", and can't run at
692     // the same time as other (similar) event loops).
693     if (mWindowType != eWindowType_sheet) {
694       while (ancestor) {
695         if (ancestor->mNumModalDescendents++ == 0) {
696           NSWindow* aWindow = ancestor->GetCocoaWindow();
697           if (ancestor->mWindowType != eWindowType_invisible) {
698             [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:NO];
699             [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:NO];
700             [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:NO];
701           }
702         }
703         ancestor = static_cast<nsCocoaWindow*>(ancestor->mParent);
704       }
705       [mWindow setLevel:NSModalPanelWindowLevel];
706       nsCocoaWindowList* windowList = new nsCocoaWindowList;
707       if (windowList) {
708         windowList->window = this;  // Don't ADDREF
709         windowList->prev = gGeckoAppModalWindowList;
710         gGeckoAppModalWindowList = windowList;
711       }
712     }
713   } else {
714     --gXULModalLevel;
715     NS_ASSERTION(gXULModalLevel >= 0, "Mismatched call to nsCocoaWindow::SetModal(false)!");
716     if (mWindowType != eWindowType_sheet) {
717       while (ancestor) {
718         if (--ancestor->mNumModalDescendents == 0) {
719           NSWindow* aWindow = ancestor->GetCocoaWindow();
720           if (ancestor->mWindowType != eWindowType_invisible) {
721             [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:YES];
722             [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:YES];
723             [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:YES];
724           }
725         }
726         NS_ASSERTION(ancestor->mNumModalDescendents >= 0, "Widget hierarchy changed while modal!");
727         ancestor = static_cast<nsCocoaWindow*>(ancestor->mParent);
728       }
729       if (gGeckoAppModalWindowList) {
730         NS_ASSERTION(gGeckoAppModalWindowList->window == this,
731                      "Widget hierarchy changed while modal!");
732         nsCocoaWindowList* saved = gGeckoAppModalWindowList;
733         gGeckoAppModalWindowList = gGeckoAppModalWindowList->prev;
734         delete saved;  // "window" not ADDREFed
735       }
736       if (mWindowType == eWindowType_popup)
737         SetPopupWindowLevel();
738       else
739         [mWindow setLevel:NSNormalWindowLevel];
740     }
741   }
743   NS_OBJC_END_TRY_ABORT_BLOCK;
746 void nsCocoaWindow::SetFakeModal(bool aState) {
747   mFakeModal = aState;
748   SetModal(aState);
751 bool nsCocoaWindow::IsRunningAppModal() { return [NSApp _isRunningAppModal]; }
753 // Hide or show this window
754 void nsCocoaWindow::Show(bool bState) {
755   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
757   if (!mWindow) return;
759   // We need to re-execute sometimes in order to bring already-visible
760   // windows forward.
761   if (!mSheetNeedsShow && !bState && ![mWindow isVisible]) return;
763   // Protect against re-entering.
764   if (bState && [mWindow isBeingShown]) return;
766   [mWindow setBeingShown:bState];
767   if (bState && !mWasShown) {
768     mWasShown = true;
769   }
771   nsIWidget* parentWidget = mParent;
772   nsCOMPtr<nsPIWidgetCocoa> piParentWidget(do_QueryInterface(parentWidget));
773   NSWindow* nativeParentWindow =
774       (parentWidget) ? (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW) : nil;
776   if (bState && !mBounds.IsEmpty()) {
777     // Don't try to show a popup when the parent isn't visible or is minimized.
778     if (mWindowType == eWindowType_popup && nativeParentWindow) {
779       if (![nativeParentWindow isVisible] || [nativeParentWindow isMiniaturized]) {
780         return;
781       }
782     }
784     if (mPopupContentView) {
785       // Ensure our content view is visible. We never need to hide it.
786       mPopupContentView->Show(true);
787     }
789     if (mWindowType == eWindowType_sheet) {
790       // bail if no parent window (its basically what we do in Carbon)
791       if (!nativeParentWindow || !piParentWidget) return;
793       NSWindow* topNonSheetWindow = nativeParentWindow;
795       // If this sheet is the child of another sheet, hide the parent so that
796       // this sheet can be displayed. Leave the parent mSheetNeedsShow alone,
797       // that is only used to handle sibling sheet contention. The parent will
798       // return once there are no more child sheets.
799       bool parentIsSheet = false;
800       if (NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) && parentIsSheet) {
801         piParentWidget->GetSheetWindowParent(&topNonSheetWindow);
802         [NSApp endSheet:nativeParentWindow];
803       }
805       nsCOMPtr<nsIWidget> sheetShown;
806       if (NS_SUCCEEDED(piParentWidget->GetChildSheet(true, getter_AddRefs(sheetShown))) &&
807           (!sheetShown || sheetShown == this)) {
808         // If this sheet is already the sheet actually being shown, don't
809         // tell it to show again. Otherwise the number of calls to
810         // [NSApp beginSheet...] won't match up with [NSApp endSheet...].
811         if (![mWindow isVisible]) {
812           mSheetNeedsShow = false;
813           mSheetWindowParent = topNonSheetWindow;
814           // Only set contextInfo if our parent isn't a sheet.
815           NSWindow* contextInfo = parentIsSheet ? nil : mSheetWindowParent;
816           [TopLevelWindowData deactivateInWindow:mSheetWindowParent];
817           [NSApp beginSheet:mWindow
818               modalForWindow:mSheetWindowParent
819                modalDelegate:mDelegate
820               didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
821                  contextInfo:contextInfo];
822           [TopLevelWindowData activateInWindow:mWindow];
823           SendSetZLevelEvent();
824         }
825       } else {
826         // A sibling of this sheet is active, don't show this sheet yet.
827         // When the active sheet hides, its brothers and sisters that have
828         // mSheetNeedsShow set will have their opportunities to display.
829         mSheetNeedsShow = true;
830       }
831     } else if (mWindowType == eWindowType_popup) {
832       // For reasons that aren't yet clear, calls to [NSWindow orderFront:] or
833       // [NSWindow makeKeyAndOrderFront:] can sometimes trigger "Error (1000)
834       // creating CGSWindow", which in turn triggers an internal inconsistency
835       // NSException.  These errors shouldn't be fatal.  So we need to wrap
836       // calls to ...orderFront: in TRY blocks.  See bmo bug 470864.
837       NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
838       [[mWindow contentView] setNeedsDisplay:YES];
839       [mWindow orderFront:nil];
840       NS_OBJC_END_TRY_ABORT_BLOCK;
841       SendSetZLevelEvent();
842       AdjustWindowShadow();
843       SetWindowBackgroundBlur();
844       // If our popup window is a non-native context menu, tell the OS (and
845       // other programs) that a menu has opened.  This is how the OS knows to
846       // close other programs' context menus when ours open.
847       if ([mWindow isKindOfClass:[PopupWindow class]] && [(PopupWindow*)mWindow isContextMenu]) {
848         [[NSDistributedNotificationCenter defaultCenter]
849             postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification"
850                           object:@"org.mozilla.gecko.PopupWindow"];
851       }
853       // If a parent window was supplied and this is a popup at the parent
854       // level, set its child window. This will cause the child window to
855       // appear above the parent and move when the parent does. Setting this
856       // needs to happen after the _setWindowNumber calls above, otherwise the
857       // window doesn't focus properly.
858       if (nativeParentWindow && mPopupLevel == ePopupLevelParent)
859         [nativeParentWindow addChildWindow:mWindow ordered:NSWindowAbove];
860     } else {
861       NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
862       if (mWindowType == eWindowType_toplevel &&
863           [mWindow respondsToSelector:@selector(setAnimationBehavior:)]) {
864         NSWindowAnimationBehavior behavior;
865         if (mIsAnimationSuppressed) {
866           behavior = NSWindowAnimationBehaviorNone;
867         } else {
868           switch (mAnimationType) {
869             case nsIWidget::eDocumentWindowAnimation:
870               behavior = NSWindowAnimationBehaviorDocumentWindow;
871               break;
872             default:
873               MOZ_ASSERT_UNREACHABLE("unexpected mAnimationType value");
874               // fall through
875             case nsIWidget::eGenericWindowAnimation:
876               behavior = NSWindowAnimationBehaviorDefault;
877               break;
878           }
879         }
880         [mWindow setAnimationBehavior:behavior];
881         mWindowAnimationBehavior = behavior;
882       }
884       // We don't want alwaysontop windows to pull focus when they're opened,
885       // as these tend to be for peripheral indicators and displays.
886       if (mAlwaysOnTop) {
887         [mWindow orderFront:nil];
888       } else {
889         [mWindow makeKeyAndOrderFront:nil];
890       }
891       NS_OBJC_END_TRY_ABORT_BLOCK;
892       SendSetZLevelEvent();
893     }
894   } else {
895     // roll up any popups if a top-level window is going away
896     if (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) RollUpPopups();
898     // now get rid of the window/sheet
899     if (mWindowType == eWindowType_sheet) {
900       if (mSheetNeedsShow) {
901         // This is an attempt to hide a sheet that never had a chance to
902         // be shown. There's nothing to do other than make sure that it
903         // won't show.
904         mSheetNeedsShow = false;
905       } else {
906         // get sheet's parent *before* hiding the sheet (which breaks the linkage)
907         NSWindow* sheetParent = mSheetWindowParent;
909         // hide the sheet
910         [NSApp endSheet:mWindow];
912         [TopLevelWindowData deactivateInWindow:mWindow];
914         nsCOMPtr<nsIWidget> siblingSheetToShow;
915         bool parentIsSheet = false;
917         if (nativeParentWindow && piParentWidget &&
918             NS_SUCCEEDED(
919                 piParentWidget->GetChildSheet(false, getter_AddRefs(siblingSheetToShow))) &&
920             siblingSheetToShow) {
921           // First, give sibling sheets an opportunity to show.
922           siblingSheetToShow->Show(true);
923         } else if (nativeParentWindow && piParentWidget &&
924                    NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) && parentIsSheet) {
925           // Only set contextInfo if the parent of the parent sheet we're about
926           // to restore isn't itself a sheet.
927           NSWindow* contextInfo = sheetParent;
928           nsIWidget* grandparentWidget = nil;
929           if (NS_SUCCEEDED(piParentWidget->GetRealParent(&grandparentWidget)) &&
930               grandparentWidget) {
931             nsCOMPtr<nsPIWidgetCocoa> piGrandparentWidget(do_QueryInterface(grandparentWidget));
932             bool grandparentIsSheet = false;
933             if (piGrandparentWidget &&
934                 NS_SUCCEEDED(piGrandparentWidget->GetIsSheet(&grandparentIsSheet)) &&
935                 grandparentIsSheet) {
936               contextInfo = nil;
937             }
938           }
939           // If there are no sibling sheets, but the parent is a sheet, restore
940           // it.  It wasn't sent any deactivate events when it was hidden, so
941           // don't call through Show, just let the OS put it back up.
942           [NSApp beginSheet:nativeParentWindow
943               modalForWindow:sheetParent
944                modalDelegate:[nativeParentWindow delegate]
945               didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
946                  contextInfo:contextInfo];
947         } else {
948           // Sheet, that was hard.  No more siblings or parents, going back
949           // to a real window.
950           NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
951           [sheetParent makeKeyAndOrderFront:nil];
952           NS_OBJC_END_TRY_ABORT_BLOCK;
953         }
954         SendSetZLevelEvent();
955       }
956     } else {
957       // If the window is a popup window with a parent window we need to
958       // unhook it here before ordering it out. When you order out the child
959       // of a window it hides the parent window.
960       if (mWindowType == eWindowType_popup && nativeParentWindow)
961         [nativeParentWindow removeChildWindow:mWindow];
963       [mWindow orderOut:nil];
965       // If our popup window is a non-native context menu, tell the OS (and
966       // other programs) that a menu has closed.
967       if ([mWindow isKindOfClass:[PopupWindow class]] && [(PopupWindow*)mWindow isContextMenu]) {
968         [[NSDistributedNotificationCenter defaultCenter]
969             postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification"
970                           object:@"org.mozilla.gecko.PopupWindow"];
971       }
972     }
973   }
975   [mWindow setBeingShown:NO];
977   NS_OBJC_END_TRY_ABORT_BLOCK;
980 // Work around a problem where with multiple displays and multiple spaces
981 // enabled, where the browser is assigned to a single display or space, popup
982 // windows that are reshown after being hidden with [NSWindow orderOut] show on
983 // the assigned space even when opened from another display. Apply the
984 // workaround whenever more than one display is enabled.
985 bool nsCocoaWindow::NeedsRecreateToReshow() {
986   // Limit the workaround to non-tooltip popup windows because only they need to
987   // override the "Assign To" setting. i.e., to display where the parent window
988   // is.
989   return (mWindowType == eWindowType_popup) && (mPopupType != ePopupTypeTooltip) && mWasShown &&
990          ([[NSScreen screens] count] > 1);
993 struct ShadowParams {
994   float standardDeviation;
995   float density;
996   int offsetX;
997   int offsetY;
998   unsigned int flags;
1001 // These numbers have been determined by looking at the results of
1002 // CGSGetWindowShadowAndRimParameters for native window types.
1003 static const ShadowParams kWindowShadowParametersPreYosemite[] = {
1004     {0.0f, 0.0f, 0, 0, 0},       // none
1005     {8.0f, 0.5f, 0, 6, 1},       // default
1006     {10.0f, 0.44f, 0, 10, 512},  // menu
1007     {8.0f, 0.5f, 0, 6, 1},       // tooltip
1008     {4.0f, 0.6f, 0, 4, 512}      // sheet
1011 static const ShadowParams kWindowShadowParametersPostYosemite[] = {
1012     {0.0f, 0.0f, 0, 0, 0},       // none
1013     {8.0f, 0.5f, 0, 6, 1},       // default
1014     {9.882353f, 0.3f, 0, 4, 0},  // menu
1015     {3.294118f, 0.2f, 0, 1, 0},  // tooltip
1016     {9.882353f, 0.3f, 0, 4, 0}   // sheet
1019 // This method will adjust the window shadow style for popup windows after
1020 // they have been made visible. Before they're visible, their window number
1021 // might be -1, which is not useful.
1022 // We won't attempt to change the shadow for windows that can acquire key state
1023 // since OS X will reset the shadow whenever that happens.
1024 void nsCocoaWindow::AdjustWindowShadow() {
1025   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1027   if (!mWindow || ![mWindow isVisible] || ![mWindow hasShadow] || [mWindow canBecomeKeyWindow] ||
1028       [mWindow windowNumber] == -1)
1029     return;
1031   const ShadowParams& params = kWindowShadowParametersPostYosemite[uint8_t(mShadowStyle)];
1032   CGSConnection cid = _CGSDefaultConnection();
1033   CGSSetWindowShadowAndRimParameters(cid, [mWindow windowNumber], params.standardDeviation,
1034                                      params.density, params.offsetX, params.offsetY, params.flags);
1036   NS_OBJC_END_TRY_ABORT_BLOCK;
1039 static const NSUInteger kWindowBackgroundBlurRadius = 4;
1041 void nsCocoaWindow::SetWindowBackgroundBlur() {
1042   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1044   if (!mWindow || ![mWindow isVisible] || [mWindow windowNumber] == -1) return;
1046   // Only blur the background of menus and fake sheets.
1047   if (mShadowStyle != StyleWindowShadow::Menu && mShadowStyle != StyleWindowShadow::Sheet) return;
1049   CGSConnection cid = _CGSDefaultConnection();
1050   CGSSetWindowBackgroundBlurRadius(cid, [mWindow windowNumber], kWindowBackgroundBlurRadius);
1052   NS_OBJC_END_TRY_ABORT_BLOCK;
1055 nsresult nsCocoaWindow::ConfigureChildren(const nsTArray<Configuration>& aConfigurations) {
1056   if (mPopupContentView) {
1057     mPopupContentView->ConfigureChildren(aConfigurations);
1058   }
1059   return NS_OK;
1062 LayerManager* nsCocoaWindow::GetLayerManager(PLayerTransactionChild* aShadowManager,
1063                                              LayersBackend aBackendHint,
1064                                              LayerManagerPersistence aPersistence) {
1065   if (mPopupContentView) {
1066     return mPopupContentView->GetLayerManager(aShadowManager, aBackendHint, aPersistence);
1067   }
1068   return nullptr;
1071 nsTransparencyMode nsCocoaWindow::GetTransparencyMode() {
1072   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
1074   return (!mWindow || [mWindow isOpaque]) ? eTransparencyOpaque : eTransparencyTransparent;
1076   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(eTransparencyOpaque);
1079 // This is called from nsMenuPopupFrame when making a popup transparent.
1080 void nsCocoaWindow::SetTransparencyMode(nsTransparencyMode aMode) {
1081   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1083   // Only respect calls for popup windows.
1084   if (!mWindow || mWindowType != eWindowType_popup) {
1085     return;
1086   }
1088   BOOL isTransparent = aMode == eTransparencyTransparent;
1089   BOOL currentTransparency = ![mWindow isOpaque];
1090   if (isTransparent != currentTransparency) {
1091     [mWindow setOpaque:!isTransparent];
1092     [mWindow setBackgroundColor:(isTransparent ? [NSColor clearColor] : [NSColor whiteColor])];
1093   }
1095   NS_OBJC_END_TRY_ABORT_BLOCK;
1098 void nsCocoaWindow::Enable(bool aState) {}
1100 bool nsCocoaWindow::IsEnabled() const { return true; }
1102 #define kWindowPositionSlop 20
1104 void nsCocoaWindow::ConstrainPosition(bool aAllowSlop, int32_t* aX, int32_t* aY) {
1105   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1107   if (!mWindow || ![mWindow screen]) {
1108     return;
1109   }
1111   nsIntRect screenBounds;
1113   int32_t width, height;
1115   NSRect frame = [mWindow frame];
1117   // zero size rects confuse the screen manager
1118   width = std::max<int32_t>(frame.size.width, 1);
1119   height = std::max<int32_t>(frame.size.height, 1);
1121   nsCOMPtr<nsIScreenManager> screenMgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
1122   if (screenMgr) {
1123     nsCOMPtr<nsIScreen> screen;
1124     screenMgr->ScreenForRect(*aX, *aY, width, height, getter_AddRefs(screen));
1126     if (screen) {
1127       screen->GetRectDisplayPix(&(screenBounds.x), &(screenBounds.y), &(screenBounds.width),
1128                                 &(screenBounds.height));
1129     }
1130   }
1132   if (aAllowSlop) {
1133     if (*aX < screenBounds.x - width + kWindowPositionSlop) {
1134       *aX = screenBounds.x - width + kWindowPositionSlop;
1135     } else if (*aX >= screenBounds.x + screenBounds.width - kWindowPositionSlop) {
1136       *aX = screenBounds.x + screenBounds.width - kWindowPositionSlop;
1137     }
1139     if (*aY < screenBounds.y - height + kWindowPositionSlop) {
1140       *aY = screenBounds.y - height + kWindowPositionSlop;
1141     } else if (*aY >= screenBounds.y + screenBounds.height - kWindowPositionSlop) {
1142       *aY = screenBounds.y + screenBounds.height - kWindowPositionSlop;
1143     }
1144   } else {
1145     if (*aX < screenBounds.x) {
1146       *aX = screenBounds.x;
1147     } else if (*aX >= screenBounds.x + screenBounds.width - width) {
1148       *aX = screenBounds.x + screenBounds.width - width;
1149     }
1151     if (*aY < screenBounds.y) {
1152       *aY = screenBounds.y;
1153     } else if (*aY >= screenBounds.y + screenBounds.height - height) {
1154       *aY = screenBounds.y + screenBounds.height - height;
1155     }
1156   }
1158   NS_OBJC_END_TRY_ABORT_BLOCK;
1161 void nsCocoaWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
1162   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1164   // Popups can be smaller than (32, 32)
1165   NSRect rect = (mWindowType == eWindowType_popup) ? NSZeroRect : NSMakeRect(0.0, 0.0, 32, 32);
1166   rect = [mWindow frameRectForChildViewRect:rect];
1168   CGFloat scaleFactor = BackingScaleFactor();
1170   SizeConstraints c = aConstraints;
1171   c.mMinSize.width = std::max(nsCocoaUtils::CocoaPointsToDevPixels(rect.size.width, scaleFactor),
1172                               c.mMinSize.width);
1173   c.mMinSize.height = std::max(nsCocoaUtils::CocoaPointsToDevPixels(rect.size.height, scaleFactor),
1174                                c.mMinSize.height);
1176   NSSize minSize = {nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.width, scaleFactor),
1177                     nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.height, scaleFactor)};
1178   [mWindow setMinSize:minSize];
1180   NSSize maxSize = {c.mMaxSize.width == NS_MAXSIZE
1181                         ? FLT_MAX
1182                         : nsCocoaUtils::DevPixelsToCocoaPoints(c.mMaxSize.width, scaleFactor),
1183                     c.mMaxSize.height == NS_MAXSIZE
1184                         ? FLT_MAX
1185                         : nsCocoaUtils::DevPixelsToCocoaPoints(c.mMaxSize.height, scaleFactor)};
1186   [mWindow setMaxSize:maxSize];
1188   nsBaseWidget::SetSizeConstraints(c);
1190   NS_OBJC_END_TRY_ABORT_BLOCK;
1193 // Coordinates are desktop pixels
1194 void nsCocoaWindow::Move(double aX, double aY) {
1195   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1197   if (!mWindow) {
1198     return;
1199   }
1201   // The point we have is in Gecko coordinates (origin top-left). Convert
1202   // it to Cocoa ones (origin bottom-left).
1203   NSPoint coord = {static_cast<float>(aX),
1204                    static_cast<float>(nsCocoaUtils::FlippedScreenY(NSToIntRound(aY)))};
1206   NSRect frame = [mWindow frame];
1207   if (frame.origin.x != coord.x || frame.origin.y + frame.size.height != coord.y) {
1208     [mWindow setFrameTopLeftPoint:coord];
1209   }
1211   NS_OBJC_END_TRY_ABORT_BLOCK;
1214 void nsCocoaWindow::SetSizeMode(nsSizeMode aMode) {
1215   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1217   if (!mWindow) return;
1219   // mSizeMode will be updated in DispatchSizeModeEvent, which will be called
1220   // from a delegate method that handles the state change during one of the
1221   // calls below.
1222   nsSizeMode previousMode = mSizeMode;
1224   if (aMode == nsSizeMode_Normal) {
1225     if ([mWindow isMiniaturized])
1226       [mWindow deminiaturize:nil];
1227     else if (previousMode == nsSizeMode_Maximized && [mWindow isZoomed])
1228       [mWindow zoom:nil];
1229   } else if (aMode == nsSizeMode_Minimized) {
1230     if (![mWindow isMiniaturized]) [mWindow miniaturize:nil];
1231   } else if (aMode == nsSizeMode_Maximized) {
1232     if ([mWindow isMiniaturized]) [mWindow deminiaturize:nil];
1233     if (![mWindow isZoomed]) [mWindow zoom:nil];
1234   } else if (aMode == nsSizeMode_Fullscreen) {
1235     if (!mInFullScreenMode) MakeFullScreen(true);
1236   }
1238   NS_OBJC_END_TRY_ABORT_BLOCK;
1241 // The (work)space switching implementation below was inspired by Phoenix:
1242 // https://github.com/kasper/phoenix/tree/d6c877f62b30a060dff119d8416b0934f76af534
1243 // License: MIT.
1245 // Runtime `CGSGetActiveSpace` library function feature detection.
1246 typedef CGSSpaceID (*CGSGetActiveSpaceFunc)(CGSConnection cid);
1247 static CGSGetActiveSpaceFunc GetCGSGetActiveSpaceFunc() {
1248   static CGSGetActiveSpaceFunc func = nullptr;
1249   static bool lookedUpFunc = false;
1250   if (!lookedUpFunc) {
1251     func = (CGSGetActiveSpaceFunc)dlsym(RTLD_DEFAULT, "CGSGetActiveSpace");
1252     lookedUpFunc = true;
1253   }
1254   return func;
1256 // Runtime `CGSCopyManagedDisplaySpaces` library function feature detection.
1257 typedef CFArrayRef (*CGSCopyManagedDisplaySpacesFunc)(CGSConnection cid);
1258 static CGSCopyManagedDisplaySpacesFunc GetCGSCopyManagedDisplaySpacesFunc() {
1259   static CGSCopyManagedDisplaySpacesFunc func = nullptr;
1260   static bool lookedUpFunc = false;
1261   if (!lookedUpFunc) {
1262     func = (CGSCopyManagedDisplaySpacesFunc)dlsym(RTLD_DEFAULT, "CGSCopyManagedDisplaySpaces");
1263     lookedUpFunc = true;
1264   }
1265   return func;
1267 // Runtime `CGSCopySpacesForWindows` library function feature detection.
1268 typedef CFArrayRef (*CGSCopySpacesForWindowsFunc)(CGSConnection cid, CGSSpaceMask mask,
1269                                                   CFArrayRef windowIDs);
1270 static CGSCopySpacesForWindowsFunc GetCGSCopySpacesForWindowsFunc() {
1271   static CGSCopySpacesForWindowsFunc func = nullptr;
1272   static bool lookedUpFunc = false;
1273   if (!lookedUpFunc) {
1274     func = (CGSCopySpacesForWindowsFunc)dlsym(RTLD_DEFAULT, "CGSCopySpacesForWindows");
1275     lookedUpFunc = true;
1276   }
1277   return func;
1279 // Runtime `CGSAddWindowsToSpaces` library function feature detection.
1280 typedef void (*CGSAddWindowsToSpacesFunc)(CGSConnection cid, CFArrayRef windowIDs,
1281                                           CFArrayRef spaceIDs);
1282 static CGSAddWindowsToSpacesFunc GetCGSAddWindowsToSpacesFunc() {
1283   static CGSAddWindowsToSpacesFunc func = nullptr;
1284   static bool lookedUpFunc = false;
1285   if (!lookedUpFunc) {
1286     func = (CGSAddWindowsToSpacesFunc)dlsym(RTLD_DEFAULT, "CGSAddWindowsToSpaces");
1287     lookedUpFunc = true;
1288   }
1289   return func;
1291 // Runtime `CGSRemoveWindowsFromSpaces` library function feature detection.
1292 typedef void (*CGSRemoveWindowsFromSpacesFunc)(CGSConnection cid, CFArrayRef windowIDs,
1293                                                CFArrayRef spaceIDs);
1294 static CGSRemoveWindowsFromSpacesFunc GetCGSRemoveWindowsFromSpacesFunc() {
1295   static CGSRemoveWindowsFromSpacesFunc func = nullptr;
1296   static bool lookedUpFunc = false;
1297   if (!lookedUpFunc) {
1298     func = (CGSRemoveWindowsFromSpacesFunc)dlsym(RTLD_DEFAULT, "CGSRemoveWindowsFromSpaces");
1299     lookedUpFunc = true;
1300   }
1301   return func;
1304 void nsCocoaWindow::GetWorkspaceID(nsAString& workspaceID) {
1305   workspaceID.Truncate();
1306   int32_t sid = GetWorkspaceID();
1307   if (sid != 0) {
1308     workspaceID.AppendInt(sid);
1309   }
1312 int32_t nsCocoaWindow::GetWorkspaceID() {
1313   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1315   // Mac OSX space IDs start at '1' (default space), so '0' means 'unknown',
1316   // effectively.
1317   CGSSpaceID sid = 0;
1319   CGSCopySpacesForWindowsFunc CopySpacesForWindows = GetCGSCopySpacesForWindowsFunc();
1320   if (!CopySpacesForWindows) {
1321     return sid;
1322   }
1324   CGSConnection cid = _CGSDefaultConnection();
1325   // Fetch all spaces that this window belongs to (in order).
1326   NSArray<NSNumber*>* spaceIDs = CFBridgingRelease(CopySpacesForWindows(
1327       cid, kCGSAllSpacesMask, (__bridge CFArrayRef) @[ @([mWindow windowNumber]) ]));
1328   if ([spaceIDs count]) {
1329     // When spaces are found, return the first one.
1330     // We don't support a single window painted across multiple places for now.
1331     sid = [spaceIDs[0] integerValue];
1332   } else {
1333     // Fall back to the workspace that's currently active, which is '1' in the
1334     // common case.
1335     CGSGetActiveSpaceFunc GetActiveSpace = GetCGSGetActiveSpaceFunc();
1336     if (GetActiveSpace) {
1337       sid = GetActiveSpace(cid);
1338     }
1339   }
1341   return sid;
1343   NS_OBJC_END_TRY_ABORT_BLOCK;
1346 void nsCocoaWindow::MoveToWorkspace(const nsAString& workspaceIDStr) {
1347   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1349   if ([NSScreen screensHaveSeparateSpaces] && [[NSScreen screens] count] > 1) {
1350     // We don't support moving to a workspace when the user has this option
1351     // enabled in Mission Control.
1352     return;
1353   }
1355   nsresult rv = NS_OK;
1356   int32_t workspaceID = workspaceIDStr.ToInteger(&rv);
1357   if (NS_FAILED(rv)) {
1358     return;
1359   }
1361   CGSConnection cid = _CGSDefaultConnection();
1362   int32_t currentSpace = GetWorkspaceID();
1363   // If an empty workspace ID is passed in (not valid on OSX), or when the
1364   // window is already on this workspace, we don't need to do anything.
1365   if (!workspaceID || workspaceID == currentSpace) {
1366     return;
1367   }
1369   CGSCopyManagedDisplaySpacesFunc CopyManagedDisplaySpaces = GetCGSCopyManagedDisplaySpacesFunc();
1370   CGSAddWindowsToSpacesFunc AddWindowsToSpaces = GetCGSAddWindowsToSpacesFunc();
1371   CGSRemoveWindowsFromSpacesFunc RemoveWindowsFromSpaces = GetCGSRemoveWindowsFromSpacesFunc();
1372   if (!CopyManagedDisplaySpaces || !AddWindowsToSpaces || !RemoveWindowsFromSpaces) {
1373     return;
1374   }
1376   // Fetch an ordered list of all known spaces.
1377   NSArray* displaySpacesInfo = CFBridgingRelease(CopyManagedDisplaySpaces(cid));
1378   // When we found the space we're looking for, we can bail out of the loop
1379   // early, which this local variable is used for.
1380   BOOL found = false;
1381   for (NSDictionary<NSString*, id>* spacesInfo in displaySpacesInfo) {
1382     NSArray<NSNumber*>* sids = [spacesInfo[CGSSpacesKey] valueForKey:CGSSpaceIDKey];
1383     for (NSNumber* sid in sids) {
1384       // If we found our space in the list, we're good to go and can jump out of
1385       // this loop.
1386       if ((int)[sid integerValue] == workspaceID) {
1387         found = true;
1388         break;
1389       }
1390     }
1391     if (found) {
1392       break;
1393     }
1394   }
1396   // We were unable to find the space to correspond with the workspaceID as
1397   // requested, so let's bail out.
1398   if (!found) {
1399     return;
1400   }
1402   // First we add the window to the appropriate space.
1403   AddWindowsToSpaces(cid, (__bridge CFArrayRef) @[ @([mWindow windowNumber]) ],
1404                      (__bridge CFArrayRef) @[ @(workspaceID) ]);
1405   // Then we remove the window from the active space.
1406   RemoveWindowsFromSpaces(cid, (__bridge CFArrayRef) @[ @([mWindow windowNumber]) ],
1407                           (__bridge CFArrayRef) @[ @(currentSpace) ]);
1409   NS_OBJC_END_TRY_ABORT_BLOCK;
1412 void nsCocoaWindow::SuppressAnimation(bool aSuppress) {
1413   if ([mWindow respondsToSelector:@selector(setAnimationBehavior:)]) {
1414     if (aSuppress) {
1415       [mWindow setIsAnimationSuppressed:YES];
1416       [mWindow setAnimationBehavior:NSWindowAnimationBehaviorNone];
1417     } else {
1418       [mWindow setIsAnimationSuppressed:NO];
1419       [mWindow setAnimationBehavior:mWindowAnimationBehavior];
1420     }
1421   }
1424 // This has to preserve the window's frame bounds.
1425 // This method requires (as does the Windows impl.) that you call Resize shortly
1426 // after calling HideWindowChrome. See bug 498835 for fixing this.
1427 void nsCocoaWindow::HideWindowChrome(bool aShouldHide) {
1428   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1430   if (!mWindow || !mWindowMadeHere ||
1431       (mWindowType != eWindowType_toplevel && mWindowType != eWindowType_dialog))
1432     return;
1434   BOOL isVisible = [mWindow isVisible];
1436   // Remove child windows.
1437   NSArray* childWindows = [mWindow childWindows];
1438   NSEnumerator* enumerator = [childWindows objectEnumerator];
1439   NSWindow* child = nil;
1440   while ((child = [enumerator nextObject])) {
1441     [mWindow removeChildWindow:child];
1442   }
1444   // Remove the views in the old window's content view.
1445   // The NSArray is autoreleased and retains its NSViews.
1446   NSArray<NSView*>* contentViewContents = [mWindow contentViewContents];
1447   for (NSView* view in contentViewContents) {
1448     [view removeFromSuperviewWithoutNeedingDisplay];
1449   }
1451   // Save state (like window title).
1452   NSMutableDictionary* state = [mWindow exportState];
1454   // Recreate the window with the right border style.
1455   NSRect frameRect = [mWindow frame];
1456   DestroyNativeWindow();
1457   nsresult rv = CreateNativeWindow(frameRect, aShouldHide ? eBorderStyle_none : mBorderStyle, true);
1458   NS_ENSURE_SUCCESS_VOID(rv);
1460   // Re-import state.
1461   [mWindow importState:state];
1463   // Add the old content view subviews to the new window's content view.
1464   for (NSView* view in contentViewContents) {
1465     [[mWindow contentView] addSubview:view];
1466   }
1468   // Reparent child windows.
1469   enumerator = [childWindows objectEnumerator];
1470   while ((child = [enumerator nextObject])) {
1471     [mWindow addChildWindow:child ordered:NSWindowAbove];
1472   }
1474   // Show the new window.
1475   if (isVisible) {
1476     bool wasAnimationSuppressed = mIsAnimationSuppressed;
1477     mIsAnimationSuppressed = true;
1478     Show(true);
1479     mIsAnimationSuppressed = wasAnimationSuppressed;
1480   }
1482   NS_OBJC_END_TRY_ABORT_BLOCK;
1485 class FullscreenTransitionData : public nsISupports {
1486  public:
1487   NS_DECL_ISUPPORTS
1489   explicit FullscreenTransitionData(NSWindow* aWindow) : mTransitionWindow(aWindow) {}
1491   NSWindow* mTransitionWindow;
1493  private:
1494   virtual ~FullscreenTransitionData() { [mTransitionWindow close]; }
1497 NS_IMPL_ISUPPORTS0(FullscreenTransitionData)
1499 @interface FullscreenTransitionDelegate : NSObject <NSAnimationDelegate> {
1500  @public
1501   nsCocoaWindow* mWindow;
1502   nsIRunnable* mCallback;
1504 @end
1506 @implementation FullscreenTransitionDelegate
1507 - (void)cleanupAndDispatch:(NSAnimation*)animation {
1508   [animation setDelegate:nil];
1509   [self autorelease];
1510   // The caller should have added ref for us.
1511   NS_DispatchToMainThread(already_AddRefed<nsIRunnable>(mCallback));
1514 - (void)animationDidEnd:(NSAnimation*)animation {
1515   MOZ_ASSERT(animation == mWindow->FullscreenTransitionAnimation(),
1516              "Should be handling the only animation on the window");
1517   mWindow->ReleaseFullscreenTransitionAnimation();
1518   [self cleanupAndDispatch:animation];
1521 - (void)animationDidStop:(NSAnimation*)animation {
1522   [self cleanupAndDispatch:animation];
1524 @end
1526 static bool AlwaysUsesNativeFullScreen() {
1527   return Preferences::GetBool("full-screen-api.macos-native-full-screen", false);
1530 /* virtual */ bool nsCocoaWindow::PrepareForFullscreenTransition(nsISupports** aData) {
1531   if (AlwaysUsesNativeFullScreen()) {
1532     return false;
1533   }
1534   nsCOMPtr<nsIScreen> widgetScreen = GetWidgetScreen();
1535   NSScreen* cocoaScreen = ScreenHelperCocoa::CocoaScreenForScreen(widgetScreen);
1537   NSWindow* win = [[NSWindow alloc] initWithContentRect:[cocoaScreen frame]
1538                                               styleMask:NSWindowStyleMaskBorderless
1539                                                 backing:NSBackingStoreBuffered
1540                                                   defer:YES];
1541   [win setBackgroundColor:[NSColor blackColor]];
1542   [win setAlphaValue:0];
1543   [win setIgnoresMouseEvents:YES];
1544   [win setLevel:NSScreenSaverWindowLevel];
1545   [win makeKeyAndOrderFront:nil];
1547   auto data = new FullscreenTransitionData(win);
1548   *aData = data;
1549   NS_ADDREF(data);
1550   return true;
1553 /* virtual */ void nsCocoaWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
1554                                                               uint16_t aDuration,
1555                                                               nsISupports* aData,
1556                                                               nsIRunnable* aCallback) {
1557   auto data = static_cast<FullscreenTransitionData*>(aData);
1558   FullscreenTransitionDelegate* delegate = [[FullscreenTransitionDelegate alloc] init];
1559   delegate->mWindow = this;
1560   // Storing already_AddRefed directly could cause static checking fail.
1561   delegate->mCallback = nsCOMPtr<nsIRunnable>(aCallback).forget().take();
1563   if (mFullscreenTransitionAnimation) {
1564     [mFullscreenTransitionAnimation stopAnimation];
1565     ReleaseFullscreenTransitionAnimation();
1566   }
1568   NSDictionary* dict = @{
1569     NSViewAnimationTargetKey : data->mTransitionWindow,
1570     NSViewAnimationEffectKey : aStage == eBeforeFullscreenToggle ? NSViewAnimationFadeInEffect
1571                                                                  : NSViewAnimationFadeOutEffect
1572   };
1573   mFullscreenTransitionAnimation = [[NSViewAnimation alloc] initWithViewAnimations:@[ dict ]];
1574   [mFullscreenTransitionAnimation setDelegate:delegate];
1575   [mFullscreenTransitionAnimation setDuration:aDuration / 1000.0];
1576   [mFullscreenTransitionAnimation startAnimation];
1579 void nsCocoaWindow::WillEnterFullScreen(bool aFullScreen) {
1580   if (mWidgetListener) {
1581     mWidgetListener->FullscreenWillChange(aFullScreen);
1582   }
1583   // Update the state to full screen when we are entering, so that we switch to
1584   // full screen view as soon as possible.
1585   UpdateFullscreenState(aFullScreen, true);
1588 void nsCocoaWindow::EnteredFullScreen(bool aFullScreen, bool aNativeMode) {
1589   mInFullScreenTransition = false;
1590   UpdateFullscreenState(aFullScreen, aNativeMode);
1593 void nsCocoaWindow::UpdateFullscreenState(bool aFullScreen, bool aNativeMode) {
1594   bool wasInFullscreen = mInFullScreenMode;
1595   mInFullScreenMode = aFullScreen;
1596   if (aNativeMode || mInNativeFullScreenMode) {
1597     mInNativeFullScreenMode = aFullScreen;
1598   }
1599   DispatchSizeModeEvent();
1600   if (mWidgetListener && wasInFullscreen != aFullScreen) {
1601     mWidgetListener->FullscreenChanged(aFullScreen);
1602   }
1605 inline bool nsCocoaWindow::ShouldToggleNativeFullscreen(bool aFullScreen,
1606                                                         bool aUseSystemTransition) {
1607   // First check if this window supports entering native fullscreen.
1608   // This is set based on the macnativefullscreen attribute on the window's
1609   // document element.
1610   NSWindowCollectionBehavior colBehavior = [mWindow collectionBehavior];
1611   if (!(colBehavior & NSWindowCollectionBehaviorFullScreenPrimary)) {
1612     return false;
1613   }
1615   if (mInNativeFullScreenMode) {
1616     // If we are using native fullscreen, go ahead to exit it.
1617     return true;
1618   }
1619   if (!aUseSystemTransition) {
1620     // If we do not want the system fullscreen transition,
1621     // don't use the native fullscreen.
1622     return false;
1623   }
1624   // If we are using native fullscreen, we should have returned earlier.
1625   return aFullScreen;
1628 nsresult nsCocoaWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen) {
1629   return DoMakeFullScreen(aFullScreen, AlwaysUsesNativeFullScreen());
1632 nsresult nsCocoaWindow::MakeFullScreenWithNativeTransition(bool aFullScreen,
1633                                                            nsIScreen* aTargetScreen) {
1634   return DoMakeFullScreen(aFullScreen, true);
1637 nsresult nsCocoaWindow::DoMakeFullScreen(bool aFullScreen, bool aUseSystemTransition) {
1638   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1640   if (!mWindow) {
1641     return NS_OK;
1642   }
1644   // We will call into MakeFullScreen redundantly when entering/exiting
1645   // fullscreen mode via OS X controls. When that happens we should just handle
1646   // it gracefully - no need to ASSERT.
1647   if (mInFullScreenMode == aFullScreen) {
1648     return NS_OK;
1649   }
1651   mInFullScreenTransition = true;
1653   if (ShouldToggleNativeFullscreen(aFullScreen, aUseSystemTransition)) {
1654     MOZ_ASSERT(mInNativeFullScreenMode != aFullScreen,
1655                "We shouldn't have been in native fullscreen.");
1656     // Calling toggleFullScreen will result in windowDid(FailTo)?(Enter|Exit)FullScreen
1657     // to be called from the OS. We will call EnteredFullScreen from those methods,
1658     // where mInFullScreenMode will be set and a sizemode event will be dispatched.
1659     [mWindow toggleFullScreen:nil];
1660   } else {
1661     if (mWidgetListener) {
1662       mWidgetListener->FullscreenWillChange(aFullScreen);
1663     }
1664     NSDisableScreenUpdates();
1665     // The order here matters. When we exit full screen mode, we need to show the
1666     // Dock first, otherwise the newly-created window won't have its minimize
1667     // button enabled. See bug 526282.
1668     nsCocoaUtils::HideOSChromeOnScreen(aFullScreen);
1669     nsBaseWidget::InfallibleMakeFullScreen(aFullScreen);
1670     NSEnableScreenUpdates();
1671     EnteredFullScreen(aFullScreen, /* aNativeMode */ false);
1672   }
1674   return NS_OK;
1676   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1679 // Coordinates are desktop pixels
1680 void nsCocoaWindow::DoResize(double aX, double aY, double aWidth, double aHeight, bool aRepaint,
1681                              bool aConstrainToCurrentScreen) {
1682   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1684   if (!mWindow || mInResize) {
1685     return;
1686   }
1688   // We are able to resize a window outside of any aspect ratio contraints
1689   // applied to it, but in order to "update" the aspect ratio contraint to the
1690   // new window dimensions, we must re-lock the aspect ratio.
1691   auto relockAspectRatio = MakeScopeExit([&]() {
1692     if (mAspectRatioLocked) {
1693       LockAspectRatio(true);
1694     }
1695   });
1697   AutoRestore<bool> reentrantResizeGuard(mInResize);
1698   mInResize = true;
1700   // ConstrainSize operates in device pixels, so we need to convert using
1701   // the backing scale factor here
1702   CGFloat scale = BackingScaleFactor();
1703   int32_t width = NSToIntRound(aWidth * scale);
1704   int32_t height = NSToIntRound(aHeight * scale);
1705   ConstrainSize(&width, &height);
1707   DesktopIntRect newBounds(NSToIntRound(aX), NSToIntRound(aY), NSToIntRound(width / scale),
1708                            NSToIntRound(height / scale));
1710   // constrain to the screen that contains the largest area of the new rect
1711   FitRectToVisibleAreaForScreen(newBounds, aConstrainToCurrentScreen ? [mWindow screen] : nullptr);
1713   // convert requested bounds into Cocoa coordinate system
1714   NSRect newFrame = nsCocoaUtils::GeckoRectToCocoaRect(newBounds);
1716   NSRect frame = [mWindow frame];
1717   BOOL isMoving = newFrame.origin.x != frame.origin.x || newFrame.origin.y != frame.origin.y;
1718   BOOL isResizing =
1719       newFrame.size.width != frame.size.width || newFrame.size.height != frame.size.height;
1721   if (!isMoving && !isResizing) {
1722     return;
1723   }
1725   // We ignore aRepaint -- we have to call display:YES, otherwise the
1726   // title bar doesn't immediately get repainted and is displayed in
1727   // the wrong place, leading to a visual jump.
1728   [mWindow setFrame:newFrame display:YES];
1730   NS_OBJC_END_TRY_ABORT_BLOCK;
1733 // Coordinates are desktop pixels
1734 void nsCocoaWindow::Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) {
1735   DoResize(aX, aY, aWidth, aHeight, aRepaint, false);
1738 // Coordinates are desktop pixels
1739 void nsCocoaWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
1740   double invScale = 1.0 / BackingScaleFactor();
1741   DoResize(mBounds.x * invScale, mBounds.y * invScale, aWidth, aHeight, aRepaint, true);
1744 // Return the area that the Gecko ChildView in our window should cover, as an
1745 // NSRect in screen coordinates (with 0,0 being the bottom left corner of the
1746 // primary screen).
1747 NSRect nsCocoaWindow::GetClientCocoaRect() {
1748   if (!mWindow) {
1749     return NSZeroRect;
1750   }
1752   return [mWindow childViewRectForFrameRect:[mWindow frame]];
1755 LayoutDeviceIntRect nsCocoaWindow::GetClientBounds() {
1756   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
1758   CGFloat scaleFactor = BackingScaleFactor();
1759   return nsCocoaUtils::CocoaRectToGeckoRectDevPix(GetClientCocoaRect(), scaleFactor);
1761   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntRect(0, 0, 0, 0));
1764 void nsCocoaWindow::UpdateBounds() {
1765   NSRect frame = NSZeroRect;
1766   if (mWindow) {
1767     frame = [mWindow frame];
1768   }
1769   mBounds = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
1771   if (mPopupContentView) {
1772     mPopupContentView->UpdateBoundsFromView();
1773   }
1776 LayoutDeviceIntRect nsCocoaWindow::GetScreenBounds() {
1777   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
1779 #ifdef DEBUG
1780   LayoutDeviceIntRect r =
1781       nsCocoaUtils::CocoaRectToGeckoRectDevPix([mWindow frame], BackingScaleFactor());
1782   NS_ASSERTION(mWindow && mBounds == r, "mBounds out of sync!");
1783 #endif
1785   return mBounds;
1787   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntRect(0, 0, 0, 0));
1790 double nsCocoaWindow::GetDefaultScaleInternal() { return BackingScaleFactor(); }
1792 static CGFloat GetBackingScaleFactor(NSWindow* aWindow) {
1793   NSRect frame = [aWindow frame];
1794   if (frame.size.width > 0 && frame.size.height > 0) {
1795     return nsCocoaUtils::GetBackingScaleFactor(aWindow);
1796   }
1798   // For windows with zero width or height, the backingScaleFactor method
1799   // is broken - it will always return 2 on a retina macbook, even when
1800   // the window position implies it's on a non-hidpi external display
1801   // (to the extent that a zero-area window can be said to be "on" a
1802   // display at all!)
1803   // And to make matters worse, Cocoa even fires a
1804   // windowDidChangeBackingProperties notification with the
1805   // NSBackingPropertyOldScaleFactorKey key when a window on an
1806   // external display is resized to/from zero height, even though it hasn't
1807   // really changed screens.
1809   // This causes us to handle popup window sizing incorrectly when the
1810   // popup is resized to zero height (bug 820327) - nsXULPopupManager
1811   // becomes (incorrectly) convinced the popup has been explicitly forced
1812   // to a non-default size and needs to have size attributes attached.
1814   // Workaround: instead of asking the window, we'll find the screen it is on
1815   // and ask that for *its* backing scale factor.
1817   // (See bug 853252 and additional comments in windowDidChangeScreen: below
1818   // for further complications this causes.)
1820   // First, expand the rect so that it actually has a measurable area,
1821   // for FindTargetScreenForRect to use.
1822   if (frame.size.width == 0) {
1823     frame.size.width = 1;
1824   }
1825   if (frame.size.height == 0) {
1826     frame.size.height = 1;
1827   }
1829   // Then identify the screen it belongs to, and return its scale factor.
1830   NSScreen* screen = FindTargetScreenForRect(nsCocoaUtils::CocoaRectToGeckoRect(frame));
1831   return nsCocoaUtils::GetBackingScaleFactor(screen);
1834 CGFloat nsCocoaWindow::BackingScaleFactor() {
1835   if (mBackingScaleFactor > 0.0) {
1836     return mBackingScaleFactor;
1837   }
1838   if (!mWindow) {
1839     return 1.0;
1840   }
1841   mBackingScaleFactor = GetBackingScaleFactor(mWindow);
1842   return mBackingScaleFactor;
1845 void nsCocoaWindow::BackingScaleFactorChanged() {
1846   CGFloat oldScale = mBackingScaleFactor;
1847   CGFloat newScale = GetBackingScaleFactor(mWindow);
1849   // ignore notification if it hasn't really changed (or maybe we have
1850   // disabled HiDPI mode via prefs)
1851   if (mBackingScaleFactor == newScale) {
1852     return;
1853   }
1855   if (mBackingScaleFactor > 0.0) {
1856     // convert size constraints to the new device pixel coordinate space
1857     double scaleFactor = newScale / mBackingScaleFactor;
1858     mSizeConstraints.mMinSize.width = NSToIntRound(mSizeConstraints.mMinSize.width * scaleFactor);
1859     mSizeConstraints.mMinSize.height = NSToIntRound(mSizeConstraints.mMinSize.height * scaleFactor);
1860     if (mSizeConstraints.mMaxSize.width < NS_MAXSIZE) {
1861       mSizeConstraints.mMaxSize.width =
1862           std::min(NS_MAXSIZE, NSToIntRound(mSizeConstraints.mMaxSize.width * scaleFactor));
1863     }
1864     if (mSizeConstraints.mMaxSize.height < NS_MAXSIZE) {
1865       mSizeConstraints.mMaxSize.height =
1866           std::min(NS_MAXSIZE, NSToIntRound(mSizeConstraints.mMaxSize.height * scaleFactor));
1867     }
1868   }
1870   mBackingScaleFactor = newScale;
1872   if (!mWidgetListener || mWidgetListener->GetAppWindow()) {
1873     return;
1874   }
1876   if (PresShell* presShell = mWidgetListener->GetPresShell()) {
1877     presShell->BackingScaleFactorChanged();
1878   }
1879   mWidgetListener->UIResolutionChanged();
1881   if ((mWindowType == eWindowType_popup) && (mBackingScaleFactor == 2.0)) {
1882     // Recalculate the size and y-origin for the popup now that the backing
1883     // scale factor has changed. After creating the popup window NSWindow,
1884     // setting the frame when the menu is moved into the correct location
1885     // causes the backing scale factor to change if the window is not on the
1886     // menu bar display. Update the dimensions and y-origin here so that the
1887     // frame is correct for the following ::Show(). Only do this when the
1888     // scale factor changes from 1.0 to 2.0. When the scale factor changes
1889     // from 2.0 to 1.0, the view will resize the widget before it is shown.
1890     NSRect frame = [mWindow frame];
1891     CGFloat previousYOrigin = frame.origin.y + frame.size.height;
1892     frame.size.width = mBounds.Width() * (oldScale / newScale);
1893     frame.size.height = mBounds.Height() * (oldScale / newScale);
1894     frame.origin.y = previousYOrigin - frame.size.height;
1895     [mWindow setFrame:frame display:NO animate:NO];
1896   }
1899 int32_t nsCocoaWindow::RoundsWidgetCoordinatesTo() {
1900   if (BackingScaleFactor() == 2.0) {
1901     return 2;
1902   }
1903   return 1;
1906 void nsCocoaWindow::SetCursor(nsCursor aDefaultCursor, imgIContainer* aCursorImage,
1907                               uint32_t aHotspotX, uint32_t aHotspotY) {
1908   if (mPopupContentView)
1909     mPopupContentView->SetCursor(aDefaultCursor, aCursorImage, aHotspotX, aHotspotY);
1912 nsresult nsCocoaWindow::SetTitle(const nsAString& aTitle) {
1913   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1915   if (!mWindow) {
1916     return NS_OK;
1917   }
1919   const nsString& strTitle = PromiseFlatString(aTitle);
1920   const unichar* uniTitle = reinterpret_cast<const unichar*>(strTitle.get());
1921   NSString* title = [NSString stringWithCharacters:uniTitle length:strTitle.Length()];
1922   if ([mWindow drawsContentsIntoWindowFrame] && ![mWindow wantsTitleDrawn]) {
1923     // Don't cause invalidations when the title isn't displayed.
1924     [mWindow disableSetNeedsDisplay];
1925     [mWindow setTitle:title];
1926     [mWindow enableSetNeedsDisplay];
1927   } else {
1928     [mWindow setTitle:title];
1929   }
1931   return NS_OK;
1933   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1936 void nsCocoaWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
1937   if (mPopupContentView) {
1938     mPopupContentView->Invalidate(aRect);
1939   }
1942 // Pass notification of some drag event to Gecko
1944 // The drag manager has let us know that something related to a drag has
1945 // occurred in this window. It could be any number of things, ranging from
1946 // a drop, to a drag enter/leave, or a drag over event. The actual event
1947 // is passed in |aMessage| and is passed along to our event hanlder so Gecko
1948 // knows about it.
1949 bool nsCocoaWindow::DragEvent(unsigned int aMessage, mozilla::gfx::Point aMouseGlobal,
1950                               UInt16 aKeyModifiers) {
1951   return false;
1954 NS_IMETHODIMP nsCocoaWindow::SendSetZLevelEvent() {
1955   nsWindowZ placement = nsWindowZTop;
1956   nsCOMPtr<nsIWidget> actualBelow;
1957   if (mWidgetListener)
1958     mWidgetListener->ZLevelChanged(true, &placement, nullptr, getter_AddRefs(actualBelow));
1959   return NS_OK;
1962 NS_IMETHODIMP nsCocoaWindow::GetChildSheet(bool aShown, nsIWidget** _retval) {
1963   nsIWidget* child = GetFirstChild();
1965   while (child) {
1966     if (child->WindowType() == eWindowType_sheet) {
1967       // if it's a sheet, it must be an nsCocoaWindow
1968       nsCocoaWindow* cocoaWindow = static_cast<nsCocoaWindow*>(child);
1969       if (cocoaWindow->mWindow && ((aShown && [cocoaWindow->mWindow isVisible]) ||
1970                                    (!aShown && cocoaWindow->mSheetNeedsShow))) {
1971         nsCOMPtr<nsIWidget> widget = cocoaWindow;
1972         widget.forget(_retval);
1973         return NS_OK;
1974       }
1975     }
1976     child = child->GetNextSibling();
1977   }
1979   *_retval = nullptr;
1981   return NS_OK;
1984 NS_IMETHODIMP nsCocoaWindow::GetRealParent(nsIWidget** parent) {
1985   *parent = mParent;
1986   return NS_OK;
1989 NS_IMETHODIMP nsCocoaWindow::GetIsSheet(bool* isSheet) {
1990   mWindowType == eWindowType_sheet ? * isSheet = true : * isSheet = false;
1991   return NS_OK;
1994 NS_IMETHODIMP nsCocoaWindow::GetSheetWindowParent(NSWindow** sheetWindowParent) {
1995   *sheetWindowParent = mSheetWindowParent;
1996   return NS_OK;
1999 // Invokes callback and ProcessEvent methods on Event Listener object
2000 nsresult nsCocoaWindow::DispatchEvent(WidgetGUIEvent* event, nsEventStatus& aStatus) {
2001   aStatus = nsEventStatus_eIgnore;
2003   nsCOMPtr<nsIWidget> kungFuDeathGrip(event->mWidget);
2004   mozilla::Unused << kungFuDeathGrip;  // Not used within this function
2006   if (mWidgetListener) aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents);
2008   return NS_OK;
2011 // aFullScreen should be the window's mInFullScreenMode. We don't have access to that
2012 // from here, so we need to pass it in. mInFullScreenMode should be the canonical
2013 // indicator that a window is currently full screen and it makes sense to keep
2014 // all sizemode logic here.
2015 static nsSizeMode GetWindowSizeMode(NSWindow* aWindow, bool aFullScreen) {
2016   if (aFullScreen) return nsSizeMode_Fullscreen;
2017   if ([aWindow isMiniaturized]) return nsSizeMode_Minimized;
2018   if (([aWindow styleMask] & NSWindowStyleMaskResizable) && [aWindow isZoomed])
2019     return nsSizeMode_Maximized;
2020   return nsSizeMode_Normal;
2023 void nsCocoaWindow::ReportMoveEvent() {
2024   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2026   // Prevent recursion, which can become infinite (see bug 708278).  This
2027   // can happen when the call to [NSWindow setFrameTopLeftPoint:] in
2028   // nsCocoaWindow::Move() triggers an immediate NSWindowDidMove notification
2029   // (and a call to [WindowDelegate windowDidMove:]).
2030   if (mInReportMoveEvent) {
2031     return;
2032   }
2033   mInReportMoveEvent = true;
2035   UpdateBounds();
2037   // The zoomed state can change when we're moving, in which case we need to
2038   // update our internal mSizeMode. This can happen either if we're maximized
2039   // and then moved, or if we're not maximized and moved back to zoomed state.
2040   if (mWindow && ((mSizeMode == nsSizeMode_Maximized) ^ [mWindow isZoomed])) {
2041     DispatchSizeModeEvent();
2042   }
2044   // Dispatch the move event to Gecko
2045   NotifyWindowMoved(mBounds.x, mBounds.y);
2047   mInReportMoveEvent = false;
2049   NS_OBJC_END_TRY_ABORT_BLOCK;
2052 void nsCocoaWindow::DispatchSizeModeEvent() {
2053   if (!mWindow) {
2054     return;
2055   }
2057   nsSizeMode newMode = GetWindowSizeMode(mWindow, mInFullScreenMode);
2059   // Don't dispatch a sizemode event if:
2060   // 1. the window is transitioning to fullscreen
2061   // 2. the new sizemode is the same as the current sizemode
2062   if (mInFullScreenTransition || mSizeMode == newMode) {
2063     return;
2064   }
2066   mSizeMode = newMode;
2067   if (mWidgetListener) {
2068     mWidgetListener->SizeModeChanged(newMode);
2069   }
2071   if (StaticPrefs::widget_pause_compositor_when_minimized()) {
2072     if (newMode == nsSizeMode_Minimized) {
2073       PauseCompositor();
2074     } else {
2075       ResumeCompositor();
2076     }
2077   }
2080 void nsCocoaWindow::DispatchOcclusionEvent() {
2081   if (!mWindow) {
2082     return;
2083   }
2085   bool newOcclusionState = !([mWindow occlusionState] & NSWindowOcclusionStateVisible);
2087   // Don't dispatch if the new occlustion state is the same as the current state.
2088   if (mIsFullyOccluded == newOcclusionState) {
2089     return;
2090   }
2092   mIsFullyOccluded = newOcclusionState;
2093   if (mWidgetListener) {
2094     mWidgetListener->OcclusionStateChanged(mIsFullyOccluded);
2095   }
2098 void nsCocoaWindow::ReportSizeEvent() {
2099   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2101   UpdateBounds();
2103   if (mWidgetListener) {
2104     LayoutDeviceIntRect innerBounds = GetClientBounds();
2105     mWidgetListener->WindowResized(this, innerBounds.width, innerBounds.height);
2106   }
2108   NS_OBJC_END_TRY_ABORT_BLOCK;
2111 void nsCocoaWindow::PauseCompositor() {
2112   nsIWidget* mainChildView = static_cast<nsIWidget*>([[mWindow mainChildView] widget]);
2113   if (!mainChildView) {
2114     return;
2115   }
2116   CompositorBridgeChild* remoteRenderer = mainChildView->GetRemoteRenderer();
2117   if (!remoteRenderer) {
2118     return;
2119   }
2120   remoteRenderer->SendPause();
2122   // Now that the compositor has paused, we also try to mark the browser window
2123   // docshell inactive to stop any animations. This does not affect docshells
2124   // for browsers in other processes, but browser UI code should be managing
2125   // their active state appropriately.
2126   if (!mWidgetListener) {
2127     return;
2128   }
2129   PresShell* presShell = mWidgetListener->GetPresShell();
2130   if (!presShell) {
2131     return;
2132   }
2133   nsPresContext* presContext = presShell->GetPresContext();
2134   if (!presContext) {
2135     return;
2136   }
2137   BrowsingContext* bc = presContext->Document()->GetBrowsingContext();
2138   if (!bc) {
2139     return;
2140   }
2141   Unused << bc->SetExplicitActive(ExplicitActiveStatus::Inactive);
2144 void nsCocoaWindow::ResumeCompositor() {
2145   nsIWidget* mainChildView = static_cast<nsIWidget*>([[mWindow mainChildView] widget]);
2146   if (!mainChildView) {
2147     return;
2148   }
2149   CompositorBridgeChild* remoteRenderer = mainChildView->GetRemoteRenderer();
2150   if (!remoteRenderer) {
2151     return;
2152   }
2153   remoteRenderer->SendResume();
2155   // Now that the compositor has resumed, we also try to mark the browser window
2156   // docshell active to restart any animations. This does not affect docshells
2157   // for browsers in other processes, but browser UI code should be managing
2158   // their active state appropriately.
2159   if (!mWidgetListener) {
2160     return;
2161   }
2162   PresShell* presShell = mWidgetListener->GetPresShell();
2163   if (!presShell) {
2164     return;
2165   }
2166   nsPresContext* presContext = presShell->GetPresContext();
2167   if (!presContext) {
2168     return;
2169   }
2170   BrowsingContext* bc = presContext->Document()->GetBrowsingContext();
2171   if (!bc) {
2172     return;
2173   }
2174   Unused << bc->SetExplicitActive(ExplicitActiveStatus::Active);
2177 void nsCocoaWindow::SetMenuBar(nsMenuBarX* aMenuBar) {
2178   if (mMenuBar) mMenuBar->SetParent(nullptr);
2179   if (!mWindow) {
2180     mMenuBar = nullptr;
2181     return;
2182   }
2183   mMenuBar = aMenuBar;
2185   // Only paint for active windows, or paint the hidden window menu bar if no
2186   // other menu bar has been painted yet so that some reasonable menu bar is
2187   // displayed when the app starts up.
2188   id windowDelegate = [mWindow delegate];
2189   if (mMenuBar && ((!gSomeMenuBarPainted && nsMenuUtilsX::GetHiddenWindowMenuBar() == mMenuBar) ||
2190                    (windowDelegate && [windowDelegate toplevelActiveState])))
2191     mMenuBar->Paint();
2194 void nsCocoaWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
2195   if (!mWindow) return;
2197   if (mPopupContentView) {
2198     return mPopupContentView->SetFocus(aRaise, aCallerType);
2199   }
2201   if (aRaise == Raise::Yes && ([mWindow isVisible] || [mWindow isMiniaturized])) {
2202     if ([mWindow isMiniaturized]) {
2203       [mWindow deminiaturize:nil];
2204     }
2206     [mWindow makeKeyAndOrderFront:nil];
2207     SendSetZLevelEvent();
2208   }
2211 LayoutDeviceIntPoint nsCocoaWindow::WidgetToScreenOffset() {
2212   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
2214   return nsCocoaUtils::CocoaRectToGeckoRectDevPix(GetClientCocoaRect(), BackingScaleFactor())
2215       .TopLeft();
2217   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0));
2220 LayoutDeviceIntPoint nsCocoaWindow::GetClientOffset() {
2221   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
2223   LayoutDeviceIntRect clientRect = GetClientBounds();
2225   return clientRect.TopLeft() - mBounds.TopLeft();
2227   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0));
2230 LayoutDeviceIntSize nsCocoaWindow::ClientToWindowSize(const LayoutDeviceIntSize& aClientSize) {
2231   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
2233   if (!mWindow) return LayoutDeviceIntSize(0, 0);
2235   CGFloat backingScale = BackingScaleFactor();
2236   LayoutDeviceIntRect r(0, 0, aClientSize.width, aClientSize.height);
2237   NSRect rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, backingScale);
2239   // Our caller expects the inflated rect for windows *with separate titlebars*,
2240   // i.e. for windows where [mWindow drawsContentsIntoWindowFrame] is NO.
2241   //
2242   // So we call frameRectForContentRect on NSWindow here, instead of mWindow, so
2243   // that we don't run into our override if this window is a window that draws
2244   // its content into the titlebar.
2245   //
2246   // This is the same thing the windows widget does, but we probably should fix
2247   // that, see bug 1445738.
2248   NSUInteger styleMask = [mWindow styleMask];
2249   styleMask &= ~NSWindowStyleMaskFullSizeContentView;
2250   NSRect inflatedRect = [NSWindow frameRectForContentRect:rect styleMask:styleMask];
2251   r = nsCocoaUtils::CocoaRectToGeckoRectDevPix(inflatedRect, backingScale);
2252   return r.Size();
2254   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntSize(0, 0));
2257 nsMenuBarX* nsCocoaWindow::GetMenuBar() { return mMenuBar; }
2259 void nsCocoaWindow::CaptureRollupEvents(nsIRollupListener* aListener, bool aDoCapture) {
2260   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2262   gRollupListener = nullptr;
2264   if (aDoCapture) {
2265     if (![NSApp isActive]) {
2266       // We need to capture mouse event if we aren't
2267       // the active application. We only set this up when needed
2268       // because they cause spurious mouse event after crash
2269       // and gdb sessions. See bug 699538.
2270       nsToolkit::GetToolkit()->MonitorAllProcessMouseEvents();
2271     }
2272     gRollupListener = aListener;
2274     // Sometimes more than one popup window can be visible at the same time
2275     // (e.g. nested non-native context menus, or the test case (attachment
2276     // 276885) for bmo bug 392389, which displays a non-native combo-box in a
2277     // non-native popup window).  In these cases the "active" popup window should
2278     // be the topmost -- the (nested) context menu the mouse is currently over,
2279     // or the combo-box's drop-down list (when it's displayed).  But (among
2280     // windows that have the same "level") OS X makes topmost the window that
2281     // last received a mouse-down event, which may be incorrect (in the combo-
2282     // box case, it makes topmost the window containing the combo-box).  So
2283     // here we fiddle with a non-native popup window's level to make sure the
2284     // "active" one is always above any other non-native popup windows that
2285     // may be visible.
2286     if (mWindow && (mWindowType == eWindowType_popup)) SetPopupWindowLevel();
2287   } else {
2288     nsToolkit::GetToolkit()->StopMonitoringAllProcessMouseEvents();
2290     // XXXndeakin this doesn't make sense.
2291     // Why is the new window assumed to be a modal panel?
2292     if (mWindow && (mWindowType == eWindowType_popup)) [mWindow setLevel:NSModalPanelWindowLevel];
2293   }
2295   NS_OBJC_END_TRY_ABORT_BLOCK;
2298 nsresult nsCocoaWindow::GetAttention(int32_t aCycleCount) {
2299   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2301   [NSApp requestUserAttention:NSInformationalRequest];
2302   return NS_OK;
2304   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2307 bool nsCocoaWindow::HasPendingInputEvent() { return nsChildView::DoHasPendingInputEvent(); }
2309 void nsCocoaWindow::SetWindowShadowStyle(StyleWindowShadow aStyle) {
2310   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2312   if (!mWindow) return;
2314   mShadowStyle = aStyle;
2316   // Shadowless windows are only supported on popups.
2317   if (mWindowType == eWindowType_popup) {
2318     [mWindow setHasShadow:aStyle != StyleWindowShadow::None];
2319   }
2321   [mWindow setUseMenuStyle:(aStyle == StyleWindowShadow::Menu)];
2322   AdjustWindowShadow();
2323   SetWindowBackgroundBlur();
2325   NS_OBJC_END_TRY_ABORT_BLOCK;
2328 void nsCocoaWindow::SetWindowOpacity(float aOpacity) {
2329   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2331   if (!mWindow) {
2332     return;
2333   }
2335   [mWindow setAlphaValue:(CGFloat)aOpacity];
2337   NS_OBJC_END_TRY_ABORT_BLOCK;
2340 static inline CGAffineTransform GfxMatrixToCGAffineTransform(const gfx::Matrix& m) {
2341   CGAffineTransform t;
2342   t.a = m._11;
2343   t.b = m._12;
2344   t.c = m._21;
2345   t.d = m._22;
2346   t.tx = m._31;
2347   t.ty = m._32;
2348   return t;
2351 void nsCocoaWindow::SetWindowTransform(const gfx::Matrix& aTransform) {
2352   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2354   if (!mWindow) {
2355     return;
2356   }
2358   // Calling CGSSetWindowTransform when the window is not visible results in
2359   // misplacing the window into doubled x,y coordinates (see bug 1448132).
2360   if (![mWindow isVisible] || NSIsEmptyRect([mWindow frame])) {
2361     return;
2362   }
2364   if (StaticPrefs::widget_window_transforms_disabled()) {
2365     // CGSSetWindowTransform is a private API. In case calling it causes
2366     // problems either now or in the future, we'll want to have an easy kill
2367     // switch. So we allow disabling it with a pref.
2368     return;
2369   }
2371   gfx::Matrix transform = aTransform;
2373   // aTransform is a transform that should be applied to the window relative
2374   // to its regular position: If aTransform._31 is 100, then we want the
2375   // window to be displayed 100 pixels to the right of its regular position.
2376   // The transform that CGSSetWindowTransform accepts has a different meaning:
2377   // It's used to answer the question "For the screen pixel at x,y (with the
2378   // origin at the top left), what pixel in the window's buffer (again with
2379   // origin top left) should be displayed at that position?"
2380   // In the example above, this means that we need to call
2381   // CGSSetWindowTransform with a horizontal translation of -windowPos.x - 100.
2382   // So we need to invert the transform and adjust it by the window's position.
2383   if (!transform.Invert()) {
2384     // Treat non-invertible transforms as the identity transform.
2385     transform = gfx::Matrix();
2386   }
2388   bool isIdentity = transform.IsIdentity();
2389   if (isIdentity && mWindowTransformIsIdentity) {
2390     return;
2391   }
2393   transform.PreTranslate(-mBounds.x, -mBounds.y);
2395   // Snap translations to device pixels, to match what we do for CSS transforms
2396   // and because the window server rounds down instead of to nearest.
2397   if (!transform.HasNonTranslation() && transform.HasNonIntegerTranslation()) {
2398     auto snappedTranslation = gfx::IntPoint::Round(transform.GetTranslation());
2399     transform = gfx::Matrix::Translation(snappedTranslation.x, snappedTranslation.y);
2400   }
2402   // We also need to account for the backing scale factor: aTransform is given
2403   // in device pixels, but CGSSetWindowTransform works with logical display
2404   // pixels.
2405   CGFloat backingScale = BackingScaleFactor();
2406   transform.PreScale(backingScale, backingScale);
2407   transform.PostScale(1 / backingScale, 1 / backingScale);
2409   CGSConnection cid = _CGSDefaultConnection();
2410   CGSSetWindowTransform(cid, [mWindow windowNumber], GfxMatrixToCGAffineTransform(transform));
2412   mWindowTransformIsIdentity = isIdentity;
2414   NS_OBJC_END_TRY_ABORT_BLOCK;
2417 void nsCocoaWindow::SetWindowMouseTransparent(bool aIsTransparent) {
2418   MOZ_ASSERT(mWindowType == eWindowType_popup, "This should only be called on popup windows.");
2419   if (aIsTransparent) {
2420     [mWindow setIgnoresMouseEvents:YES];
2421   } else {
2422     [mWindow setIgnoresMouseEvents:NO];
2423   }
2426 void nsCocoaWindow::SetShowsToolbarButton(bool aShow) {
2427   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2429   if (mWindow) [mWindow setShowsToolbarButton:aShow];
2431   NS_OBJC_END_TRY_ABORT_BLOCK;
2434 void nsCocoaWindow::SetSupportsNativeFullscreen(bool aSupportsNativeFullscreen) {
2435   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2437   if (mWindow) {
2438     // This determines whether we tell cocoa that the window supports native
2439     // full screen. If we do so, and another window is in native full screen,
2440     // this window will also appear in native full screen. We generally only
2441     // want to do this for primary application windows. We'll set the
2442     // relevant macnativefullscreen attribute on those, which will lead to us
2443     // being called with aSupportsNativeFullscreen set to `true` here.
2444     NSWindowCollectionBehavior newBehavior = [mWindow collectionBehavior];
2445     if (aSupportsNativeFullscreen) {
2446       newBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
2447     } else {
2448       newBehavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
2449     }
2450     [mWindow setCollectionBehavior:newBehavior];
2451   }
2453   NS_OBJC_END_TRY_ABORT_BLOCK;
2456 void nsCocoaWindow::SetWindowAnimationType(nsIWidget::WindowAnimationType aType) {
2457   mAnimationType = aType;
2460 void nsCocoaWindow::SetDrawsTitle(bool aDrawTitle) {
2461   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2463   if (![mWindow drawsContentsIntoWindowFrame]) {
2464     // If we don't draw into the window frame, we always want to display window
2465     // titles.
2466     [mWindow setWantsTitleDrawn:YES];
2467   } else {
2468     [mWindow setWantsTitleDrawn:aDrawTitle];
2469   }
2471   NS_OBJC_END_TRY_ABORT_BLOCK;
2474 void nsCocoaWindow::SetUseBrightTitlebarForeground(bool aBrightForeground) {
2475   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2477   [mWindow setUseBrightTitlebarForeground:aBrightForeground];
2479   NS_OBJC_END_TRY_ABORT_BLOCK;
2482 nsresult nsCocoaWindow::SetNonClientMargins(LayoutDeviceIntMargin& margins) {
2483   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2485   SetDrawsInTitlebar(margins.top == 0);
2487   return NS_OK;
2489   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2492 void nsCocoaWindow::SetDrawsInTitlebar(bool aState) {
2493   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2495   if (mWindow) [mWindow setDrawsContentsIntoWindowFrame:aState];
2497   NS_OBJC_END_TRY_ABORT_BLOCK;
2500 NS_IMETHODIMP nsCocoaWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
2501                                                         uint32_t aNativeMessage,
2502                                                         uint32_t aModifierFlags,
2503                                                         nsIObserver* aObserver) {
2504   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2506   AutoObserverNotifier notifier(aObserver, "mouseevent");
2507   if (mPopupContentView)
2508     return mPopupContentView->SynthesizeNativeMouseEvent(aPoint, aNativeMessage, aModifierFlags,
2509                                                          nullptr);
2511   return NS_OK;
2513   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2516 NS_IMETHODIMP nsCocoaWindow::SynthesizeNativeMouseScrollEvent(
2517     LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX, double aDeltaY,
2518     double aDeltaZ, uint32_t aModifierFlags, uint32_t aAdditionalFlags, nsIObserver* aObserver) {
2519   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2521   AutoObserverNotifier notifier(aObserver, "mousescrollevent");
2522   if (mPopupContentView) {
2523     // Pass nullptr as the observer so that the AutoObserverNotification in
2524     // nsChildView::SynthesizeNativeMouseScrollEvent will be ignored.
2525     return mPopupContentView->SynthesizeNativeMouseScrollEvent(aPoint, aNativeMessage, aDeltaX,
2526                                                                aDeltaY, aDeltaZ, aModifierFlags,
2527                                                                aAdditionalFlags, nullptr);
2528   }
2530   return NS_OK;
2532   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2535 void nsCocoaWindow::LockAspectRatio(bool aShouldLock) {
2536   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2538   if (aShouldLock) {
2539     [mWindow setContentAspectRatio:mWindow.frame.size];
2540     mAspectRatioLocked = true;
2541   } else {
2542     // According to https://developer.apple.com/documentation/appkit/nswindow/1419507-aspectratio,
2543     // aspect ratios and resize increments are mutually exclusive, and the accepted way of
2544     // cancelling an established aspect ratio is to set the resize increments to 1.0, 1.0
2545     [mWindow setResizeIncrements:NSMakeSize(1.0, 1.0)];
2546     mAspectRatioLocked = false;
2547   }
2549   NS_OBJC_END_TRY_ABORT_BLOCK;
2552 void nsCocoaWindow::UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) {
2553   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2555   if (mPopupContentView) {
2556     return mPopupContentView->UpdateThemeGeometries(aThemeGeometries);
2557   }
2559   NS_OBJC_END_TRY_ABORT_BLOCK;
2562 void nsCocoaWindow::SetPopupWindowLevel() {
2563   if (!mWindow) return;
2565   // Floating popups are at the floating level and hide when the window is
2566   // deactivated.
2567   if (mPopupLevel == ePopupLevelFloating) {
2568     [mWindow setLevel:NSFloatingWindowLevel];
2569     [mWindow setHidesOnDeactivate:YES];
2570   } else {
2571     // Otherwise, this is a top-level or parent popup. Parent popups always
2572     // appear just above their parent and essentially ignore the level.
2573     [mWindow setLevel:NSPopUpMenuWindowLevel];
2574     [mWindow setHidesOnDeactivate:NO];
2575   }
2578 void nsCocoaWindow::SetInputContext(const InputContext& aContext,
2579                                     const InputContextAction& aAction) {
2580   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2582   mInputContext = aContext;
2584   NS_OBJC_END_TRY_ABORT_BLOCK;
2587 bool nsCocoaWindow::GetEditCommands(NativeKeyBindingsType aType, const WidgetKeyboardEvent& aEvent,
2588                                     nsTArray<CommandInt>& aCommands) {
2589   // Validate the arguments.
2590   if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
2591     return false;
2592   }
2594   NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
2595   keyBindings->GetEditCommands(aEvent, aCommands);
2596   return true;
2599 already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
2600   nsCOMPtr<nsIWidget> window = new nsCocoaWindow();
2601   return window.forget();
2604 already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
2605   nsCOMPtr<nsIWidget> window = new nsChildView();
2606   return window.forget();
2609 @implementation WindowDelegate
2611 // We try to find a gecko menu bar to paint. If one does not exist, just paint
2612 // the application menu by itself so that a window doesn't have some other
2613 // window's menu bar.
2614 + (void)paintMenubarForWindow:(NSWindow*)aWindow {
2615   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2617   // make sure we only act on windows that have this kind of
2618   // object as a delegate
2619   id windowDelegate = [aWindow delegate];
2620   if ([windowDelegate class] != [self class]) return;
2622   nsCocoaWindow* geckoWidget = [windowDelegate geckoWidget];
2623   NS_ASSERTION(geckoWidget, "Window delegate not returning a gecko widget!");
2625   nsMenuBarX* geckoMenuBar = geckoWidget->GetMenuBar();
2626   if (geckoMenuBar) {
2627     geckoMenuBar->Paint();
2628   } else {
2629     // sometimes we don't have a native application menu early in launching
2630     if (!sApplicationMenu) return;
2632     NSMenu* mainMenu = [NSApp mainMenu];
2633     NS_ASSERTION([mainMenu numberOfItems] > 0,
2634                  "Main menu does not have any items, something is terribly wrong!");
2636     // Create a new menu bar.
2637     // We create a GeckoNSMenu because all menu bar NSMenu objects should use that subclass for
2638     // key handling reasons.
2639     GeckoNSMenu* newMenuBar = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar"];
2641     // move the application menu from the existing menu bar to the new one
2642     NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain];
2643     [mainMenu removeItemAtIndex:0];
2644     [newMenuBar insertItem:firstMenuItem atIndex:0];
2645     [firstMenuItem release];
2647     // set our new menu bar as the main menu
2648     [NSApp setMainMenu:newMenuBar];
2649     [newMenuBar release];
2650   }
2652   NS_OBJC_END_TRY_ABORT_BLOCK;
2655 - (id)initWithGeckoWindow:(nsCocoaWindow*)geckoWind {
2656   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
2658   [super init];
2659   mGeckoWindow = geckoWind;
2660   mToplevelActiveState = false;
2661   mHasEverBeenZoomed = false;
2662   return self;
2664   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
2667 - (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)proposedFrameSize {
2668   RollUpPopups();
2670   return proposedFrameSize;
2673 - (void)windowDidResize:(NSNotification*)aNotification {
2674   BaseWindow* window = [aNotification object];
2675   [window updateTrackingArea];
2677   if (!mGeckoWindow) return;
2679   // Resizing might have changed our zoom state.
2680   mGeckoWindow->DispatchSizeModeEvent();
2681   mGeckoWindow->ReportSizeEvent();
2684 - (void)windowDidChangeScreen:(NSNotification*)aNotification {
2685   if (!mGeckoWindow) return;
2687   // Because of Cocoa's peculiar treatment of zero-size windows (see comments
2688   // at GetBackingScaleFactor() above), we sometimes have a situation where
2689   // our concept of backing scale (based on the screen where the zero-sized
2690   // window is positioned) differs from Cocoa's idea (always based on the
2691   // Retina screen, AFAICT, even when an external non-Retina screen is the
2692   // primary display).
2693   //
2694   // As a result, if the window was created with zero size on an external
2695   // display, but then made visible on the (secondary) Retina screen, we
2696   // will *not* get a windowDidChangeBackingProperties notification for it.
2697   // This leads to an incorrect GetDefaultScale(), and widget coordinate
2698   // confusion, as per bug 853252.
2699   //
2700   // To work around this, we check for a backing scale mismatch when we
2701   // receive a windowDidChangeScreen notification, as we will receive this
2702   // even if Cocoa was already treating the zero-size window as having
2703   // Retina backing scale.
2704   NSWindow* window = (NSWindow*)[aNotification object];
2705   if ([window respondsToSelector:@selector(backingScaleFactor)]) {
2706     if (GetBackingScaleFactor(window) != mGeckoWindow->BackingScaleFactor()) {
2707       mGeckoWindow->BackingScaleFactorChanged();
2708     }
2709   }
2711   mGeckoWindow->ReportMoveEvent();
2714 - (NSArray<NSWindow*>*)customWindowsToEnterFullScreenForWindow:(NSWindow*)window {
2715   return AlwaysUsesNativeFullScreen() ? @[ window ] : nil;
2718 - (void)window:(NSWindow*)window
2719     startCustomAnimationToEnterFullScreenOnScreen:(NSScreen*)screen
2720                                      withDuration:(NSTimeInterval)duration {
2721   // Immediately switch to cover full screen, so we don't show the default
2722   // transition effect which stops video from playing.
2723   // XXX Is it possible to simulate the native transition effect without
2724   //     triggering content size change?
2725   [window setFrame:[screen frame] display:YES];
2728 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
2729   if (!mGeckoWindow) {
2730     return;
2731   }
2733   mGeckoWindow->WillEnterFullScreen(true);
2736 // Lion's full screen mode will bypass our internal fullscreen tracking, so
2737 // we need to catch it when we transition and call our own methods, which in
2738 // turn will fire "fullscreen" events.
2739 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
2740   if (!mGeckoWindow) {
2741     return;
2742   }
2744   mGeckoWindow->EnteredFullScreen(true);
2746   // On Yosemite, the NSThemeFrame class has two new properties --
2747   // titlebarView (an NSTitlebarView object) and titlebarContainerView (an
2748   // NSTitlebarContainerView object).  These are used to display the titlebar
2749   // in fullscreen mode.  In Safari they're not transparent.  But in Firefox
2750   // for some reason they are, which causes bug 1069658.  The following code
2751   // works around this Apple bug or design flaw.
2752   NSWindow* window = (NSWindow*)[notification object];
2753   NSView* frameView = [[window contentView] superview];
2754   NSView* titlebarView = nil;
2755   NSView* titlebarContainerView = nil;
2756   if ([frameView respondsToSelector:@selector(titlebarView)]) {
2757     titlebarView = [frameView titlebarView];
2758   }
2759   if ([frameView respondsToSelector:@selector(titlebarContainerView)]) {
2760     titlebarContainerView = [frameView titlebarContainerView];
2761   }
2762   if ([titlebarView respondsToSelector:@selector(setTransparent:)]) {
2763     [titlebarView setTransparent:NO];
2764   }
2765   if ([titlebarContainerView respondsToSelector:@selector(setTransparent:)]) {
2766     [titlebarContainerView setTransparent:NO];
2767   }
2770 - (void)windowWillExitFullScreen:(NSNotification*)notification {
2771   if (!mGeckoWindow) {
2772     return;
2773   }
2775   mGeckoWindow->WillEnterFullScreen(false);
2778 - (void)windowDidExitFullScreen:(NSNotification*)notification {
2779   if (!mGeckoWindow) {
2780     return;
2781   }
2783   mGeckoWindow->EnteredFullScreen(false);
2786 - (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
2787   if (!mGeckoWindow) {
2788     return;
2789   }
2791   mGeckoWindow->EnteredFullScreen(false);
2794 - (void)windowDidFailToExitFullScreen:(NSWindow*)window {
2795   if (!mGeckoWindow) {
2796     return;
2797   }
2799   mGeckoWindow->EnteredFullScreen(true);
2802 - (void)windowDidBecomeMain:(NSNotification*)aNotification {
2803   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2805   RollUpPopups();
2806   ChildViewMouseTracker::ReEvaluateMouseEnterState();
2808   // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
2809   // app modally. If one of those is up then we want it to retain its menu bar.
2810   if ([NSApp _isRunningAppModal]) return;
2811   NSWindow* window = [aNotification object];
2812   if (window) [WindowDelegate paintMenubarForWindow:window];
2814   if ([window isKindOfClass:[ToolbarWindow class]]) {
2815     [(ToolbarWindow*)window windowMainStateChanged];
2816   }
2818   NS_OBJC_END_TRY_ABORT_BLOCK;
2821 - (void)windowDidResignMain:(NSNotification*)aNotification {
2822   RollUpPopups();
2823   ChildViewMouseTracker::ReEvaluateMouseEnterState();
2825   // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
2826   // app modally. If one of those is up then we want it to retain its menu bar.
2827   if ([NSApp _isRunningAppModal]) return;
2828   RefPtr<nsMenuBarX> hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
2829   if (hiddenWindowMenuBar) {
2830     // printf("painting hidden window menu bar due to window losing main status\n");
2831     hiddenWindowMenuBar->Paint();
2832   }
2834   NSWindow* window = [aNotification object];
2835   if ([window isKindOfClass:[ToolbarWindow class]]) {
2836     [(ToolbarWindow*)window windowMainStateChanged];
2837   }
2840 - (void)windowDidBecomeKey:(NSNotification*)aNotification {
2841   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2843   RollUpPopups();
2844   ChildViewMouseTracker::ReEvaluateMouseEnterState();
2846   NSWindow* window = [aNotification object];
2847   if ([window isSheet]) [WindowDelegate paintMenubarForWindow:window];
2849   nsChildView* mainChildView =
2850       static_cast<nsChildView*>([[(BaseWindow*)window mainChildView] widget]);
2851   if (mainChildView) {
2852     if (mainChildView->GetInputContext().IsPasswordEditor()) {
2853       TextInputHandler::EnableSecureEventInput();
2854     } else {
2855       TextInputHandler::EnsureSecureEventInputDisabled();
2856     }
2857   }
2859   NS_OBJC_END_TRY_ABORT_BLOCK;
2862 - (void)windowDidResignKey:(NSNotification*)aNotification {
2863   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2865   RollUpPopups();
2866   ChildViewMouseTracker::ReEvaluateMouseEnterState();
2868   // If a sheet just resigned key then we should paint the menu bar
2869   // for whatever window is now main.
2870   NSWindow* window = [aNotification object];
2871   if ([window isSheet]) [WindowDelegate paintMenubarForWindow:[NSApp mainWindow]];
2873   TextInputHandler::EnsureSecureEventInputDisabled();
2875   NS_OBJC_END_TRY_ABORT_BLOCK;
2878 - (void)windowWillMove:(NSNotification*)aNotification {
2879   RollUpPopups();
2882 - (void)windowDidMove:(NSNotification*)aNotification {
2883   if (mGeckoWindow) mGeckoWindow->ReportMoveEvent();
2886 - (BOOL)windowShouldClose:(id)sender {
2887   nsIWidgetListener* listener = mGeckoWindow ? mGeckoWindow->GetWidgetListener() : nullptr;
2888   if (listener) listener->RequestWindowClose(mGeckoWindow);
2889   return NO;  // gecko will do it
2892 - (void)windowWillClose:(NSNotification*)aNotification {
2893   RollUpPopups();
2896 - (void)windowWillMiniaturize:(NSNotification*)aNotification {
2897   RollUpPopups();
2900 - (void)windowDidMiniaturize:(NSNotification*)aNotification {
2901   if (mGeckoWindow) mGeckoWindow->DispatchSizeModeEvent();
2904 - (void)windowDidDeminiaturize:(NSNotification*)aNotification {
2905   if (mGeckoWindow) mGeckoWindow->DispatchSizeModeEvent();
2908 - (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)proposedFrame {
2909   if (!mHasEverBeenZoomed && [window isZoomed]) return NO;  // See bug 429954.
2911   mHasEverBeenZoomed = YES;
2912   return YES;
2915 - (NSRect)window:(NSWindow*)window willPositionSheet:(NSWindow*)sheet usingRect:(NSRect)rect {
2916   if ([window isKindOfClass:[ToolbarWindow class]]) {
2917     rect.origin.y = [(ToolbarWindow*)window sheetAttachmentPosition];
2918   }
2919   return rect;
2922 - (void)didEndSheet:(NSWindow*)sheet returnCode:(int)returnCode contextInfo:(void*)contextInfo {
2923   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2925   // Note: 'contextInfo' (if it is set) is the window that is the parent of
2926   // the sheet.  The value of contextInfo is determined in
2927   // nsCocoaWindow::Show().  If it's set, 'contextInfo' is always the top-
2928   // level window, not another sheet itself.  But 'contextInfo' is nil if
2929   // our parent window is also a sheet -- in that case we shouldn't send
2930   // the top-level window any activate events (because it's our parent
2931   // window that needs to get these events, not the top-level window).
2932   [TopLevelWindowData deactivateInWindow:sheet];
2933   [sheet orderOut:self];
2934   if (contextInfo) [TopLevelWindowData activateInWindow:(NSWindow*)contextInfo];
2936   NS_OBJC_END_TRY_ABORT_BLOCK;
2939 - (void)windowDidChangeBackingProperties:(NSNotification*)aNotification {
2940   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2942   NSWindow* window = (NSWindow*)[aNotification object];
2944   if ([window respondsToSelector:@selector(backingScaleFactor)]) {
2945     CGFloat oldFactor =
2946         [[[aNotification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue];
2947     if ([window backingScaleFactor] != oldFactor) {
2948       mGeckoWindow->BackingScaleFactorChanged();
2949     }
2950   }
2952   NS_OBJC_END_TRY_ABORT_BLOCK;
2955 // This method is on NSWindowDelegate starting with 10.9
2956 - (void)windowDidChangeOcclusionState:(NSNotification*)aNotification {
2957   if (mGeckoWindow) {
2958     mGeckoWindow->DispatchOcclusionEvent();
2959   }
2962 - (nsCocoaWindow*)geckoWidget {
2963   return mGeckoWindow;
2966 - (bool)toplevelActiveState {
2967   return mToplevelActiveState;
2970 - (void)sendToplevelActivateEvents {
2971   if (!mToplevelActiveState && mGeckoWindow) {
2972     nsIWidgetListener* listener = mGeckoWindow->GetWidgetListener();
2973     if (listener) {
2974       listener->WindowActivated();
2975     }
2976     mToplevelActiveState = true;
2977   }
2980 - (void)sendToplevelDeactivateEvents {
2981   if (mToplevelActiveState && mGeckoWindow) {
2982     nsIWidgetListener* listener = mGeckoWindow->GetWidgetListener();
2983     if (listener) {
2984       listener->WindowDeactivated();
2985     }
2986     mToplevelActiveState = false;
2987   }
2990 @end
2992 @interface NSView (FrameViewMethodSwizzling)
2993 - (NSPoint)FrameView__closeButtonOrigin;
2994 - (NSPoint)FrameView__fullScreenButtonOrigin;
2995 - (CGFloat)FrameView__titlebarHeight;
2996 @end
2998 @implementation NSView (FrameViewMethodSwizzling)
3000 - (NSPoint)FrameView__closeButtonOrigin {
3001   NSPoint defaultPosition = [self FrameView__closeButtonOrigin];
3002   if ([[self window] isKindOfClass:[ToolbarWindow class]]) {
3003     return [(ToolbarWindow*)[self window] windowButtonsPositionWithDefaultPosition:defaultPosition];
3004   }
3005   return defaultPosition;
3008 - (NSPoint)FrameView__fullScreenButtonOrigin {
3009   NSPoint defaultPosition = [self FrameView__fullScreenButtonOrigin];
3010   if ([[self window] isKindOfClass:[ToolbarWindow class]]) {
3011     return
3012         [(ToolbarWindow*)[self window] fullScreenButtonPositionWithDefaultPosition:defaultPosition];
3013   }
3014   return defaultPosition;
3017 - (CGFloat)FrameView__titlebarHeight {
3018   CGFloat height = [self FrameView__titlebarHeight];
3019   if ([[self window] isKindOfClass:[ToolbarWindow class]]) {
3020     // Make sure that the titlebar height includes our shifted buttons.
3021     // The following coordinates are in window space, with the origin being at the bottom left
3022     // corner of the window.
3023     ToolbarWindow* win = (ToolbarWindow*)[self window];
3024     CGFloat frameHeight = [self frame].size.height;
3025     NSPoint pointAboveWindow = {0.0, frameHeight};
3026     CGFloat windowButtonY = [win windowButtonsPositionWithDefaultPosition:pointAboveWindow].y;
3027     CGFloat fullScreenButtonY =
3028         [win fullScreenButtonPositionWithDefaultPosition:pointAboveWindow].y;
3029     CGFloat maxDistanceFromWindowTopToButtonBottom =
3030         std::max(frameHeight - windowButtonY, frameHeight - fullScreenButtonY);
3031     height = std::max(height, maxDistanceFromWindowTopToButtonBottom);
3032   }
3033   return height;
3036 @end
3038 static NSMutableSet* gSwizzledFrameViewClasses = nil;
3040 @interface NSWindow (PrivateSetNeedsDisplayInRectMethod)
3041 - (void)_setNeedsDisplayInRect:(NSRect)aRect;
3042 @end
3044 // This method is on NSThemeFrame starting with 10.10, but since NSThemeFrame
3045 // is not a public class, we declare the method on NSView instead. We only have
3046 // this declaration in order to avoid compiler warnings.
3047 @interface NSView (PrivateAddKnownSubviewMethod)
3048 - (void)_addKnownSubview:(NSView*)aView
3049               positioned:(NSWindowOrderingMode)place
3050               relativeTo:(NSView*)otherView;
3051 @end
3053 #if !defined(MAC_OS_X_VERSION_10_10) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10
3055 @interface NSImage (CapInsets)
3056 - (void)setCapInsets:(NSEdgeInsets)capInsets;
3057 @end
3059 #endif
3061 #if !defined(MAC_OS_X_VERSION_10_8) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8
3063 @interface NSImage (ImageCreationWithDrawingHandler)
3064 + (NSImage*)imageWithSize:(NSSize)size
3065                   flipped:(BOOL)drawingHandlerShouldBeCalledWithFlippedContext
3066            drawingHandler:(BOOL (^)(NSRect dstRect))drawingHandler;
3067 @end
3069 #endif
3071 #if !defined(MAC_OS_X_VERSION_10_12_2) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12_2
3072 @interface NSView (NSTouchBarProvider)
3073 - (NSTouchBar*)makeTouchBar;
3074 @end
3075 #endif
3077 @interface NSView (NSVisualEffectViewSetMaskImage)
3078 - (void)setMaskImage:(NSImage*)image;
3079 @end
3081 @interface BaseWindow (Private)
3082 - (void)removeTrackingArea;
3083 - (void)cursorUpdated:(NSEvent*)aEvent;
3084 - (void)reflowTitlebarElements;
3085 @end
3087 @implementation BaseWindow
3089 // The frame of a window is implemented using undocumented NSView subclasses.
3090 // We offset the window buttons by overriding the methods _closeButtonOrigin
3091 // and _fullScreenButtonOrigin on these frame view classes. The class which is
3092 // used for a window is determined in the window's frameViewClassForStyleMask:
3093 // method, so this is where we make sure that we have swizzled the method on
3094 // all encountered classes.
3095 + (Class)frameViewClassForStyleMask:(NSUInteger)styleMask {
3096   Class frameViewClass = [super frameViewClassForStyleMask:styleMask];
3098   if (!gSwizzledFrameViewClasses) {
3099     gSwizzledFrameViewClasses = [[NSMutableSet setWithCapacity:3] retain];
3100     if (!gSwizzledFrameViewClasses) {
3101       return frameViewClass;
3102     }
3103   }
3105   static IMP our_closeButtonOrigin =
3106       class_getMethodImplementation([NSView class], @selector(FrameView__closeButtonOrigin));
3107   static IMP our_fullScreenButtonOrigin =
3108       class_getMethodImplementation([NSView class], @selector(FrameView__fullScreenButtonOrigin));
3109   static IMP our_titlebarHeight =
3110       class_getMethodImplementation([NSView class], @selector(FrameView__titlebarHeight));
3112   if (![gSwizzledFrameViewClasses containsObject:frameViewClass]) {
3113     // Either of these methods might be implemented in both a subclass of
3114     // NSFrameView and one of its own subclasses.  Which means that if we
3115     // aren't careful we might end up swizzling the same method twice.
3116     // Since method swizzling involves swapping pointers, this would break
3117     // things.
3118     IMP _closeButtonOrigin =
3119         class_getMethodImplementation(frameViewClass, @selector(_closeButtonOrigin));
3120     if (_closeButtonOrigin && _closeButtonOrigin != our_closeButtonOrigin) {
3121       nsToolkit::SwizzleMethods(frameViewClass, @selector(_closeButtonOrigin),
3122                                 @selector(FrameView__closeButtonOrigin));
3123     }
3124     IMP _fullScreenButtonOrigin =
3125         class_getMethodImplementation(frameViewClass, @selector(_fullScreenButtonOrigin));
3126     if (_fullScreenButtonOrigin && _fullScreenButtonOrigin != our_fullScreenButtonOrigin) {
3127       nsToolkit::SwizzleMethods(frameViewClass, @selector(_fullScreenButtonOrigin),
3128                                 @selector(FrameView__fullScreenButtonOrigin));
3129     }
3131     // Override _titlebarHeight so that the floating titlebar doesn't clip the bottom of the
3132     // window buttons which we move down with our override of _closeButtonOrigin.
3133     IMP _titlebarHeight = class_getMethodImplementation(frameViewClass, @selector(_titlebarHeight));
3134     if (_titlebarHeight && _titlebarHeight != our_titlebarHeight) {
3135       nsToolkit::SwizzleMethods(frameViewClass, @selector(_titlebarHeight),
3136                                 @selector(FrameView__titlebarHeight));
3137     }
3139     [gSwizzledFrameViewClasses addObject:frameViewClass];
3140   }
3142   return frameViewClass;
3145 - (id)initWithContentRect:(NSRect)aContentRect
3146                 styleMask:(NSUInteger)aStyle
3147                   backing:(NSBackingStoreType)aBufferingType
3148                     defer:(BOOL)aFlag {
3149   mDrawsIntoWindowFrame = NO;
3150   [super initWithContentRect:aContentRect styleMask:aStyle backing:aBufferingType defer:aFlag];
3151   mState = nil;
3152   mDisabledNeedsDisplay = NO;
3153   mTrackingArea = nil;
3154   mDirtyRect = NSZeroRect;
3155   mBeingShown = NO;
3156   mDrawTitle = NO;
3157   mBrightTitlebarForeground = NO;
3158   mUseMenuStyle = NO;
3159   mTouchBar = nil;
3160   mIsAnimationSuppressed = NO;
3161   [self updateTrackingArea];
3163   return self;
3166 // Returns an autoreleased NSImage.
3167 static NSImage* GetMenuMaskImage() {
3168   CGFloat radius = 4.0f;
3169   NSEdgeInsets insets = {5, 5, 5, 5};
3170   NSSize maskSize = {12, 12};
3171   NSImage* maskImage = [NSImage imageWithSize:maskSize
3172                                       flipped:YES
3173                                drawingHandler:^BOOL(NSRect dstRect) {
3174                                  NSBezierPath* path =
3175                                      [NSBezierPath bezierPathWithRoundedRect:dstRect
3176                                                                      xRadius:radius
3177                                                                      yRadius:radius];
3178                                  [[NSColor colorWithDeviceWhite:1.0 alpha:1.0] set];
3179                                  [path fill];
3180                                  return YES;
3181                                }];
3182   [maskImage setCapInsets:insets];
3183   return maskImage;
3186 - (void)swapOutChildViewWrapper:(NSView*)aNewWrapper {
3187   [aNewWrapper setFrame:[[self contentView] frame]];
3188   NSView* childView = [[self mainChildView] retain];
3189   [childView removeFromSuperview];
3190   [aNewWrapper addSubview:childView];
3191   [childView release];
3192   [super setContentView:aNewWrapper];
3195 - (void)setUseMenuStyle:(BOOL)aValue {
3196   if (aValue && !mUseMenuStyle) {
3197     // Turn on rounded corner masking.
3198     NSView* effectView = VibrancyManager::CreateEffectView(VibrancyType::MENU, YES);
3199     if ([effectView respondsToSelector:@selector(setMaskImage:)]) {
3200       [effectView setMaskImage:GetMenuMaskImage()];
3201     }
3202     [self swapOutChildViewWrapper:effectView];
3203     [effectView release];
3204   } else if (mUseMenuStyle && !aValue) {
3205     // Turn off rounded corner masking.
3206     NSView* wrapper = [[NSView alloc] initWithFrame:NSZeroRect];
3207     [wrapper setWantsLayer:YES];
3208     [self swapOutChildViewWrapper:wrapper];
3209     [wrapper release];
3210   }
3211   mUseMenuStyle = aValue;
3214 - (NSTouchBar*)makeTouchBar {
3215   mTouchBar = [[nsTouchBar alloc] init];
3216   if (mTouchBar) {
3217     sTouchBarIsInitialized = YES;
3218   }
3219   return mTouchBar;
3222 - (void)setBeingShown:(BOOL)aValue {
3223   mBeingShown = aValue;
3226 - (BOOL)isBeingShown {
3227   return mBeingShown;
3230 - (BOOL)isVisibleOrBeingShown {
3231   return [super isVisible] || mBeingShown;
3234 - (void)setIsAnimationSuppressed:(BOOL)aValue {
3235   mIsAnimationSuppressed = aValue;
3238 - (BOOL)isAnimationSuppressed {
3239   return mIsAnimationSuppressed;
3242 - (void)disableSetNeedsDisplay {
3243   mDisabledNeedsDisplay = YES;
3246 - (void)enableSetNeedsDisplay {
3247   mDisabledNeedsDisplay = NO;
3250 - (void)dealloc {
3251   [mTouchBar release];
3252   [self removeTrackingArea];
3253   ChildViewMouseTracker::OnDestroyWindow(self);
3254   [super dealloc];
3257 static const NSString* kStateTitleKey = @"title";
3258 static const NSString* kStateDrawsContentsIntoWindowFrameKey = @"drawsContentsIntoWindowFrame";
3259 static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
3260 static const NSString* kStateCollectionBehavior = @"collectionBehavior";
3261 static const NSString* kStateWantsTitleDrawn = @"wantsTitleDrawn";
3263 - (void)importState:(NSDictionary*)aState {
3264   if (NSString* title = [aState objectForKey:kStateTitleKey]) {
3265     [self setTitle:title];
3266   }
3267   [self setDrawsContentsIntoWindowFrame:[[aState objectForKey:kStateDrawsContentsIntoWindowFrameKey]
3268                                             boolValue]];
3269   [self setShowsToolbarButton:[[aState objectForKey:kStateShowsToolbarButton] boolValue]];
3270   [self setCollectionBehavior:[[aState objectForKey:kStateCollectionBehavior] unsignedIntValue]];
3271   [self setWantsTitleDrawn:[[aState objectForKey:kStateWantsTitleDrawn] boolValue]];
3274 - (NSMutableDictionary*)exportState {
3275   NSMutableDictionary* state = [NSMutableDictionary dictionaryWithCapacity:10];
3276   if (NSString* title = [self title]) {
3277     [state setObject:title forKey:kStateTitleKey];
3278   }
3279   [state setObject:[NSNumber numberWithBool:[self drawsContentsIntoWindowFrame]]
3280             forKey:kStateDrawsContentsIntoWindowFrameKey];
3281   [state setObject:[NSNumber numberWithBool:[self showsToolbarButton]]
3282             forKey:kStateShowsToolbarButton];
3283   [state setObject:[NSNumber numberWithUnsignedInt:[self collectionBehavior]]
3284             forKey:kStateCollectionBehavior];
3285   [state setObject:[NSNumber numberWithBool:[self wantsTitleDrawn]] forKey:kStateWantsTitleDrawn];
3286   return state;
3289 - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState {
3290   bool changed = (aState != mDrawsIntoWindowFrame);
3291   mDrawsIntoWindowFrame = aState;
3292   if (changed) {
3293     [self reflowTitlebarElements];
3294   }
3297 - (BOOL)drawsContentsIntoWindowFrame {
3298   return mDrawsIntoWindowFrame;
3301 - (NSRect)childViewRectForFrameRect:(NSRect)aFrameRect {
3302   if (mDrawsIntoWindowFrame) {
3303     return aFrameRect;
3304   }
3305   NSUInteger styleMask = [self styleMask];
3306   styleMask &= ~NSWindowStyleMaskFullSizeContentView;
3307   return [NSWindow contentRectForFrameRect:aFrameRect styleMask:styleMask];
3310 - (NSRect)frameRectForChildViewRect:(NSRect)aChildViewRect {
3311   if (mDrawsIntoWindowFrame) {
3312     return aChildViewRect;
3313   }
3314   NSUInteger styleMask = [self styleMask];
3315   styleMask &= ~NSWindowStyleMaskFullSizeContentView;
3316   return [NSWindow frameRectForContentRect:aChildViewRect styleMask:styleMask];
3319 - (NSTimeInterval)animationResizeTime:(NSRect)newFrame {
3320   if (mIsAnimationSuppressed) {
3321     // Should not animate the initial session-restore size change
3322     return 0.0;
3323   }
3325   return [super animationResizeTime:newFrame];
3328 - (void)setWantsTitleDrawn:(BOOL)aDrawTitle {
3329   mDrawTitle = aDrawTitle;
3330   if ([self respondsToSelector:@selector(setTitleVisibility:)]) {
3331     [self setTitleVisibility:mDrawTitle ? NSWindowTitleVisible : NSWindowTitleHidden];
3332   }
3335 - (BOOL)wantsTitleDrawn {
3336   return mDrawTitle;
3339 - (void)setUseBrightTitlebarForeground:(BOOL)aBrightForeground {
3340   mBrightTitlebarForeground = aBrightForeground;
3341   [[self standardWindowButton:NSWindowFullScreenButton] setNeedsDisplay:YES];
3344 - (BOOL)useBrightTitlebarForeground {
3345   return mBrightTitlebarForeground;
3348 - (NSView*)trackingAreaView {
3349   NSView* contentView = [self contentView];
3350   return [contentView superview] ? [contentView superview] : contentView;
3353 - (NSArray<NSView*>*)contentViewContents {
3354   return [[[[self contentView] subviews] copy] autorelease];
3357 - (ChildView*)mainChildView {
3358   NSView* contentView = [self contentView];
3359   NSView* lastView = [[contentView subviews] lastObject];
3360   if ([lastView isKindOfClass:[ChildView class]]) {
3361     return (ChildView*)lastView;
3362   }
3363   return nil;
3366 - (void)removeTrackingArea {
3367   if (mTrackingArea) {
3368     [[self trackingAreaView] removeTrackingArea:mTrackingArea];
3369     [mTrackingArea release];
3370     mTrackingArea = nil;
3371   }
3374 - (void)updateTrackingArea {
3375   [self removeTrackingArea];
3377   NSView* view = [self trackingAreaView];
3378   const NSTrackingAreaOptions options =
3379       NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways;
3380   mTrackingArea = [[NSTrackingArea alloc] initWithRect:[view bounds]
3381                                                options:options
3382                                                  owner:self
3383                                               userInfo:nil];
3384   [view addTrackingArea:mTrackingArea];
3387 - (void)mouseEntered:(NSEvent*)aEvent {
3388   ChildViewMouseTracker::MouseEnteredWindow(aEvent);
3391 - (void)mouseExited:(NSEvent*)aEvent {
3392   ChildViewMouseTracker::MouseExitedWindow(aEvent);
3395 - (void)mouseMoved:(NSEvent*)aEvent {
3396   ChildViewMouseTracker::MouseMoved(aEvent);
3399 - (void)cursorUpdated:(NSEvent*)aEvent {
3400   // Nothing to do here, but NSTrackingArea wants us to implement this method.
3403 - (void)_setNeedsDisplayInRect:(NSRect)aRect {
3404   // Prevent unnecessary invalidations due to moving NSViews (e.g. for plugins)
3405   if (!mDisabledNeedsDisplay) {
3406     // This method is only called by Cocoa, so when we're here, we know that
3407     // it's available and don't need to check whether our superclass responds
3408     // to the selector.
3409     [super _setNeedsDisplayInRect:aRect];
3410     mDirtyRect = NSUnionRect(mDirtyRect, aRect);
3411   }
3414 - (NSRect)getAndResetNativeDirtyRect {
3415   NSRect dirtyRect = mDirtyRect;
3416   mDirtyRect = NSZeroRect;
3417   return dirtyRect;
3420 // Possibly move the titlebar buttons.
3421 - (void)reflowTitlebarElements {
3422   NSView* frameView = [[self contentView] superview];
3423   if ([frameView respondsToSelector:@selector(_tileTitlebarAndRedisplay:)]) {
3424     [frameView _tileTitlebarAndRedisplay:NO];
3425   }
3428 - (BOOL)respondsToSelector:(SEL)aSelector {
3429   // Claim the window doesn't respond to this so that the system
3430   // doesn't steal keyboard equivalents for it. Bug 613710.
3431   if (aSelector == @selector(cancelOperation:)) {
3432     return NO;
3433   }
3435   return [super respondsToSelector:aSelector];
3438 - (void)doCommandBySelector:(SEL)aSelector {
3439   // We override this so that it won't beep if it can't act.
3440   // We want to control the beeping for missing or disabled
3441   // commands ourselves.
3442   [self tryToPerform:aSelector with:nil];
3445 - (id)accessibilityAttributeValue:(NSString*)attribute {
3446   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
3448   id retval = [super accessibilityAttributeValue:attribute];
3450   // The following works around a problem with Text-to-Speech on OS X 10.7.
3451   // See bug 674612 for more info.
3452   //
3453   // When accessibility is off, AXUIElementCopyAttributeValue(), when called
3454   // on an AXApplication object to get its AXFocusedUIElement attribute,
3455   // always returns an AXWindow object (the actual browser window -- never a
3456   // mozAccessible object).  This also happens with accessibility turned on,
3457   // if no other object in the browser window has yet been focused.  But if
3458   // the browser window has a title bar (as it currently always does), the
3459   // AXWindow object will always have four "accessible" children, one of which
3460   // is an AXStaticText object (the title bar's "title"; the other three are
3461   // the close, minimize and zoom buttons).  This means that (for complicated
3462   // reasons, for which see bug 674612) Text-to-Speech on OS X 10.7 will often
3463   // "speak" the window title, no matter what text is selected, or even if no
3464   // text at all is selected.  (This always happens when accessibility is off.
3465   // It doesn't happen in Firefox releases because Apple has (on OS X 10.7)
3466   // special-cased the handling of apps whose CFBundleIdentifier is
3467   // org.mozilla.firefox.)
3468   //
3469   // We work around this problem by only returning AXChildren that are
3470   // mozAccessible object or are one of the titlebar's buttons (which
3471   // instantiate subclasses of NSButtonCell).
3472   if ([retval isKindOfClass:[NSArray class]] && [attribute isEqualToString:@"AXChildren"]) {
3473     NSMutableArray* holder = [NSMutableArray arrayWithCapacity:10];
3474     [holder addObjectsFromArray:(NSArray*)retval];
3475     NSUInteger count = [holder count];
3476     for (NSInteger i = count - 1; i >= 0; --i) {
3477       id item = [holder objectAtIndex:i];
3478       // Remove anything from holder that isn't one of the titlebar's buttons
3479       // (which instantiate subclasses of NSButtonCell) or a mozAccessible
3480       // object (or one of its subclasses).
3481       if (![item isKindOfClass:[NSButtonCell class]] &&
3482           ![item respondsToSelector:@selector(hasRepresentedView)]) {
3483         [holder removeObjectAtIndex:i];
3484       }
3485     }
3486     retval = [NSArray arrayWithArray:holder];
3487   }
3489   return retval;
3491   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
3494 - (void)releaseJSObjects {
3495   [mTouchBar releaseJSObjects];
3498 @end
3500 @interface NSView (NSThemeFrame)
3501 - (void)_drawTitleStringInClip:(NSRect)aRect;
3502 - (void)_maskCorners:(NSUInteger)aFlags clipRect:(NSRect)aRect;
3503 @end
3505 @implementation TitlebarGradientView
3507 - (void)drawRect:(NSRect)aRect {
3508   CGContextRef ctx = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
3509   ToolbarWindow* window = (ToolbarWindow*)[self window];
3510   nsNativeThemeCocoa::DrawNativeTitlebar(ctx, NSRectToCGRect([self bounds]),
3511                                          [window unifiedToolbarHeight], [window isMainWindow], NO);
3514 - (BOOL)isOpaque {
3515   return YES;
3518 - (BOOL)mouseDownCanMoveWindow {
3519   return YES;
3522 - (void)mouseUp:(NSEvent*)event {
3523   if ([event clickCount] == 2) {
3524     // Handle titlebar double click. We don't get the window's default behavior here because the
3525     // window uses NSWindowStyleMaskFullSizeContentView, and this view (the titlebar gradient view)
3526     // is technically part of the window "contents" (it's a subview of the content view).
3527     if (nsCocoaUtils::ShouldZoomOnTitlebarDoubleClick()) {
3528       [[self window] performZoom:nil];
3529     } else if (nsCocoaUtils::ShouldMinimizeOnTitlebarDoubleClick()) {
3530       [[self window] performMiniaturize:nil];
3531     }
3532   }
3535 @end
3537 // This class allows us to exercise control over the window's title bar. It is
3538 // used for all windows with titlebars.
3540 // ToolbarWindow supports two modes:
3541 //  - drawsContentsIntoWindowFrame mode: In this mode, the Gecko ChildView is
3542 //    sized to cover the entire window frame and manages titlebar drawing.
3543 //  - separate titlebar mode, with support for unified toolbars: In this mode,
3544 //    the Gecko ChildView does not extend into the titlebar. However, this
3545 //    window's content view (which is the ChildView's superview) *does* extend
3546 //    into the titlebar. Moreover, in this mode, we place a TitlebarGradientView
3547 //    in the content view, as a sibling of the ChildView.
3549 // The "separate titlebar mode" supports the "unified toolbar" look:
3550 // If there's a toolbar right below the titlebar, the two can "connect" and
3551 // form a single gradient without a separator line in between.
3553 // The following mechanism communicates the height of the unified toolbar to
3554 // the ToolbarWindow:
3556 // 1) In the style sheet we set the toolbar's -moz-appearance to toolbar.
3557 // 2) When the toolbar is visible and we paint the application chrome
3558 //    window, the array that Gecko passes nsChildView::UpdateThemeGeometries
3559 //    will contain an entry for the widget type StyleAppearance::Toolbar.
3560 // 3) nsChildView::UpdateThemeGeometries passes the toolbar's height, plus the
3561 //    titlebar height, to -[ToolbarWindow setUnifiedToolbarHeight:].
3563 // The actual drawing of the gradient happens in two parts: The titlebar part
3564 // (i.e. the top 22 pixels of the gradient) is drawn by the TitlebarGradientView,
3565 // which is a subview of the window's content view and a sibling of the ChildView.
3566 // The rest of the gradient is drawn by Gecko into the ChildView, as part of the
3567 // -moz-appearance rendering of the toolbar.
3568 @implementation ToolbarWindow
3570 - (id)initWithContentRect:(NSRect)aChildViewRect
3571                 styleMask:(NSUInteger)aStyle
3572                   backing:(NSBackingStoreType)aBufferingType
3573                     defer:(BOOL)aFlag {
3574   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
3576   // We treat aChildViewRect as the rectangle that the window's main ChildView
3577   // should be sized to. Get the right frameRect for the requested child view
3578   // rect.
3579   NSRect frameRect = [NSWindow frameRectForContentRect:aChildViewRect styleMask:aStyle];
3581   // Always size the content view to the full frame size of the window.
3582   // We cannot use this window mask when our CoreAnimation pref is disabled: This flag forces
3583   // CoreAnimation on for the entire window, which causes glitches in combination with our
3584   // non-CoreAnimation drawing. (Specifically, on macOS versions up until at least 10.14.0,
3585   // layer-backed NSOpenGLViews have extremely glitchy resizing behavior.)
3586   aStyle |= NSWindowStyleMaskFullSizeContentView;
3588   // -[NSWindow initWithContentRect:styleMask:backing:defer:] calls
3589   // [self frameRectForContentRect:styleMask:] to convert the supplied content
3590   // rect to the window's frame rect. We've overridden that method to be a
3591   // pass-through function. So, in order to get the intended frameRect, we need
3592   // to supply frameRect itself as the "content rect".
3593   NSRect contentRect = frameRect;
3595   if ((self = [super initWithContentRect:contentRect
3596                                styleMask:aStyle
3597                                  backing:aBufferingType
3598                                    defer:aFlag])) {
3599     mTitlebarGradientView = nil;
3600     mUnifiedToolbarHeight = 22.0f;
3601     mSheetAttachmentPosition = aChildViewRect.size.height;
3602     mWindowButtonsRect = NSZeroRect;
3603     mFullScreenButtonRect = NSZeroRect;
3605     if ([self respondsToSelector:@selector(setTitlebarAppearsTransparent:)]) {
3606       [self setTitlebarAppearsTransparent:YES];
3607     }
3609     [self updateTitlebarGradientViewPresence];
3610   }
3611   return self;
3613   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
3616 - (void)dealloc {
3617   [mTitlebarGradientView release];
3618   [super dealloc];
3621 - (NSArray<NSView*>*)contentViewContents {
3622   NSMutableArray<NSView*>* contents = [[[self contentView] subviews] mutableCopy];
3623   if (mTitlebarGradientView) {
3624     // Do not include the titlebar gradient view in the returned array.
3625     [contents removeObject:mTitlebarGradientView];
3626   }
3627   return [contents autorelease];
3630 - (void)updateTitlebarGradientViewPresence {
3631   BOOL needTitlebarView = ![self drawsContentsIntoWindowFrame];
3632   if (needTitlebarView && !mTitlebarGradientView) {
3633     mTitlebarGradientView = [[TitlebarGradientView alloc] initWithFrame:[self titlebarRect]];
3634     mTitlebarGradientView.autoresizingMask = NSViewWidthSizable | NSViewMinYMargin;
3635     [self.contentView addSubview:mTitlebarGradientView positioned:NSWindowBelow relativeTo:nil];
3636   } else if (!needTitlebarView && mTitlebarGradientView) {
3637     [mTitlebarGradientView removeFromSuperview];
3638     [mTitlebarGradientView release];
3639     mTitlebarGradientView = nil;
3640   }
3643 // Override methods that translate between content rect and frame rect.
3644 // These overrides are only needed on 10.9 or when the CoreAnimation pref is
3645 // is false; otherwise we use NSFullSizeContentViewMask and get this behavior
3646 // for free.
3647 - (NSRect)contentRectForFrameRect:(NSRect)aRect {
3648   return aRect;
3651 - (NSRect)contentRectForFrameRect:(NSRect)aRect styleMask:(NSUInteger)aMask {
3652   return aRect;
3655 - (NSRect)frameRectForContentRect:(NSRect)aRect {
3656   return aRect;
3659 - (NSRect)frameRectForContentRect:(NSRect)aRect styleMask:(NSUInteger)aMask {
3660   return aRect;
3663 - (void)setContentView:(NSView*)aView {
3664   [super setContentView:aView];
3666   if (!([self styleMask] & NSWindowStyleMaskFullSizeContentView)) {
3667     // Move the contentView to the bottommost layer so that it's guaranteed
3668     // to be under the window buttons.
3669     // When the window uses the NSFullSizeContentViewMask, this manual
3670     // adjustment is not necessary.
3671     NSView* frameView = [aView superview];
3672     [aView removeFromSuperview];
3673     if ([frameView respondsToSelector:@selector(_addKnownSubview:positioned:relativeTo:)]) {
3674       // 10.10 prints a warning when we call addSubview on the frame view, so we
3675       // silence the warning by calling a private method instead.
3676       [frameView _addKnownSubview:aView positioned:NSWindowBelow relativeTo:nil];
3677     } else {
3678       [frameView addSubview:aView positioned:NSWindowBelow relativeTo:nil];
3679     }
3680   }
3683 - (void)windowMainStateChanged {
3684   [self setTitlebarNeedsDisplay];
3685   [[self mainChildView] ensureNextCompositeIsAtomicWithMainThreadPaint];
3688 - (void)setTitlebarNeedsDisplay {
3689   [mTitlebarGradientView setNeedsDisplay:YES];
3692 - (NSRect)titlebarRect {
3693   CGFloat titlebarHeight = [self titlebarHeight];
3694   return NSMakeRect(0, [self frame].size.height - titlebarHeight, [self frame].size.width,
3695                     titlebarHeight);
3698 // Returns the unified height of titlebar + toolbar.
3699 - (CGFloat)unifiedToolbarHeight {
3700   return mUnifiedToolbarHeight;
3703 - (CGFloat)titlebarHeight {
3704   // We use the original content rect here, not what we return from
3705   // [self contentRectForFrameRect:], because that would give us a
3706   // titlebarHeight of zero.
3707   NSRect frameRect = [self frame];
3708   NSUInteger styleMask = [self styleMask];
3709   styleMask &= ~NSWindowStyleMaskFullSizeContentView;
3710   NSRect originalContentRect = [NSWindow contentRectForFrameRect:frameRect styleMask:styleMask];
3711   return NSMaxY(frameRect) - NSMaxY(originalContentRect);
3714 // Stores the complete height of titlebar + toolbar.
3715 - (void)setUnifiedToolbarHeight:(CGFloat)aHeight {
3716   if (aHeight == mUnifiedToolbarHeight) return;
3718   mUnifiedToolbarHeight = aHeight;
3720   if (![self drawsContentsIntoWindowFrame]) {
3721     [self setTitlebarNeedsDisplay];
3722   }
3725 // Extending the content area into the title bar works by resizing the
3726 // mainChildView so that it covers the titlebar.
3727 - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState {
3728   BOOL stateChanged = ([self drawsContentsIntoWindowFrame] != aState);
3729   [super setDrawsContentsIntoWindowFrame:aState];
3730   if (stateChanged && [[self delegate] isKindOfClass:[WindowDelegate class]]) {
3731     // Here we extend / shrink our mainChildView. We do that by firing a resize
3732     // event which will cause the ChildView to be resized to the rect returned
3733     // by nsCocoaWindow::GetClientBounds. GetClientBounds bases its return
3734     // value on what we return from drawsContentsIntoWindowFrame.
3735     WindowDelegate* windowDelegate = (WindowDelegate*)[self delegate];
3736     nsCocoaWindow* geckoWindow = [windowDelegate geckoWidget];
3737     if (geckoWindow) {
3738       // Re-layout our contents.
3739       geckoWindow->ReportSizeEvent();
3740     }
3742     // Resizing the content area causes a reflow which would send a synthesized
3743     // mousemove event to the old mouse position relative to the top left
3744     // corner of the content area. But the mouse has shifted relative to the
3745     // content area, so that event would have wrong position information. So
3746     // we'll send a mouse move event with the correct new position.
3747     ChildViewMouseTracker::ResendLastMouseMoveEvent();
3748   }
3750   [self updateTitlebarGradientViewPresence];
3753 - (void)setWantsTitleDrawn:(BOOL)aDrawTitle {
3754   [super setWantsTitleDrawn:aDrawTitle];
3755   [self setTitlebarNeedsDisplay];
3758 - (void)setSheetAttachmentPosition:(CGFloat)aY {
3759   mSheetAttachmentPosition = aY;
3762 - (CGFloat)sheetAttachmentPosition {
3763   return mSheetAttachmentPosition;
3766 - (void)placeWindowButtons:(NSRect)aRect {
3767   if (!NSEqualRects(mWindowButtonsRect, aRect)) {
3768     mWindowButtonsRect = aRect;
3769     [self reflowTitlebarElements];
3770   }
3773 - (NSPoint)windowButtonsPositionWithDefaultPosition:(NSPoint)aDefaultPosition {
3774   NSInteger styleMask = [self styleMask];
3775   if ([self drawsContentsIntoWindowFrame] && !(styleMask & NSWindowStyleMaskFullScreen) &&
3776       (styleMask & NSWindowStyleMaskTitled)) {
3777     if (NSIsEmptyRect(mWindowButtonsRect)) {
3778       // Empty rect. Let's hide the buttons.
3779       // Position is in non-flipped window coordinates. Using frame's height
3780       // for the vertical coordinate will move the buttons above the window,
3781       // making them invisible.
3782       return NSMakePoint(0, [self frame].size.height);
3783     }
3784     return NSMakePoint(mWindowButtonsRect.origin.x, mWindowButtonsRect.origin.y);
3785   }
3786   return aDefaultPosition;
3789 - (void)placeFullScreenButton:(NSRect)aRect {
3790   if (!NSEqualRects(mFullScreenButtonRect, aRect)) {
3791     mFullScreenButtonRect = aRect;
3792     [self reflowTitlebarElements];
3793   }
3796 - (NSPoint)fullScreenButtonPositionWithDefaultPosition:(NSPoint)aDefaultPosition {
3797   if ([self drawsContentsIntoWindowFrame] && !NSIsEmptyRect(mFullScreenButtonRect)) {
3798     return NSMakePoint(std::min(mFullScreenButtonRect.origin.x, aDefaultPosition.x),
3799                        std::min(mFullScreenButtonRect.origin.y, aDefaultPosition.y));
3800   }
3801   return aDefaultPosition;
3804 // Returning YES here makes the setShowsToolbarButton method work even though
3805 // the window doesn't contain an NSToolbar.
3806 - (BOOL)_hasToolbar {
3807   return YES;
3810 // Dispatch a toolbar pill button clicked message to Gecko.
3811 - (void)_toolbarPillButtonClicked:(id)sender {
3812   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3814   RollUpPopups();
3816   if ([[self delegate] isKindOfClass:[WindowDelegate class]]) {
3817     WindowDelegate* windowDelegate = (WindowDelegate*)[self delegate];
3818     nsCocoaWindow* geckoWindow = [windowDelegate geckoWidget];
3819     if (!geckoWindow) return;
3821     nsIWidgetListener* listener = geckoWindow->GetWidgetListener();
3822     if (listener) listener->OSToolbarButtonPressed();
3823   }
3825   NS_OBJC_END_TRY_ABORT_BLOCK;
3828 // Retain and release "self" to avoid crashes when our widget (and its native
3829 // window) is closed as a result of processing a key equivalent (e.g.
3830 // Command+w or Command+q).  This workaround is only needed for a window
3831 // that can become key.
3832 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
3833   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3835   NSWindow* nativeWindow = [self retain];
3836   BOOL retval = [super performKeyEquivalent:theEvent];
3837   [nativeWindow release];
3838   return retval;
3840   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
3843 - (void)sendEvent:(NSEvent*)anEvent {
3844   NSEventType type = [anEvent type];
3846   switch (type) {
3847     case NSEventTypeScrollWheel:
3848     case NSEventTypeLeftMouseDown:
3849     case NSEventTypeLeftMouseUp:
3850     case NSEventTypeRightMouseDown:
3851     case NSEventTypeRightMouseUp:
3852     case NSEventTypeOtherMouseDown:
3853     case NSEventTypeOtherMouseUp:
3854     case NSEventTypeMouseMoved:
3855     case NSEventTypeLeftMouseDragged:
3856     case NSEventTypeRightMouseDragged:
3857     case NSEventTypeOtherMouseDragged: {
3858       // Drop all mouse events if a modal window has appeared above us.
3859       // This helps make us behave as if the OS were running a "real" modal
3860       // event loop.
3861       id delegate = [self delegate];
3862       if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
3863         nsCocoaWindow* widget = [(WindowDelegate*)delegate geckoWidget];
3864         if (widget) {
3865           if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window)) return;
3866           if (widget->HasModalDescendents()) return;
3867         }
3868       }
3869       break;
3870     }
3871     default:
3872       break;
3873   }
3875   [super sendEvent:anEvent];
3878 @end
3880 @implementation PopupWindow
3882 - (id)initWithContentRect:(NSRect)contentRect
3883                 styleMask:(NSUInteger)styleMask
3884                   backing:(NSBackingStoreType)bufferingType
3885                     defer:(BOOL)deferCreation {
3886   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
3888   mIsContextMenu = false;
3889   return [super initWithContentRect:contentRect
3890                           styleMask:styleMask
3891                             backing:bufferingType
3892                               defer:deferCreation];
3894   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
3897 // Override the private API _backdropBleedAmount. This determines how much the
3898 // desktop wallpaper contributes to the vibrancy backdrop.
3899 // Return 0 in order to match what the system does for sheet windows and
3900 // _NSPopoverWindows.
3901 - (CGFloat)_backdropBleedAmount {
3902   return 0.0;
3905 - (BOOL)isContextMenu {
3906   return mIsContextMenu;
3909 - (void)setIsContextMenu:(BOOL)flag {
3910   mIsContextMenu = flag;
3913 - (BOOL)canBecomeMainWindow {
3914   // This is overriden because the default is 'yes' when a titlebar is present.
3915   return NO;
3918 @end
3920 // According to Apple's docs on [NSWindow canBecomeKeyWindow] and [NSWindow
3921 // canBecomeMainWindow], windows without a title bar or resize bar can't (by
3922 // default) become key or main.  But if a window can't become key, it can't
3923 // accept keyboard input (bmo bug 393250).  And it should also be possible for
3924 // an otherwise "ordinary" window to become main.  We need to override these
3925 // two methods to make this happen.
3926 @implementation BorderlessWindow
3928 - (BOOL)canBecomeKeyWindow {
3929   return YES;
3932 - (void)sendEvent:(NSEvent*)anEvent {
3933   NSEventType type = [anEvent type];
3935   switch (type) {
3936     case NSEventTypeScrollWheel:
3937     case NSEventTypeLeftMouseDown:
3938     case NSEventTypeLeftMouseUp:
3939     case NSEventTypeRightMouseDown:
3940     case NSEventTypeRightMouseUp:
3941     case NSEventTypeOtherMouseDown:
3942     case NSEventTypeOtherMouseUp:
3943     case NSEventTypeMouseMoved:
3944     case NSEventTypeLeftMouseDragged:
3945     case NSEventTypeRightMouseDragged:
3946     case NSEventTypeOtherMouseDragged: {
3947       // Drop all mouse events if a modal window has appeared above us.
3948       // This helps make us behave as if the OS were running a "real" modal
3949       // event loop.
3950       id delegate = [self delegate];
3951       if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
3952         nsCocoaWindow* widget = [(WindowDelegate*)delegate geckoWidget];
3953         if (widget) {
3954           if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window)) return;
3955           if (widget->HasModalDescendents()) return;
3956         }
3957       }
3958       break;
3959     }
3960     default:
3961       break;
3962   }
3964   [super sendEvent:anEvent];
3967 // Apple's doc on this method says that the NSWindow class's default is not to
3968 // become main if the window isn't "visible" -- so we should replicate that
3969 // behavior here.  As best I can tell, the [NSWindow isVisible] method is an
3970 // accurate test of what Apple means by "visibility".
3971 - (BOOL)canBecomeMainWindow {
3972   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3974   if (![self isVisible]) return NO;
3975   return YES;
3977   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
3980 // Retain and release "self" to avoid crashes when our widget (and its native
3981 // window) is closed as a result of processing a key equivalent (e.g.
3982 // Command+w or Command+q).  This workaround is only needed for a window
3983 // that can become key.
3984 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
3985   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3987   NSWindow* nativeWindow = [self retain];
3988   BOOL retval = [super performKeyEquivalent:theEvent];
3989   [nativeWindow release];
3990   return retval;
3992   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
3995 @end