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