1/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2/* vim: set ts=2 et sw=2 tw=80: */
3/* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7#include "nsCocoaWindow.h"
8
9#include "AppearanceOverride.h"
10#include "NativeKeyBindings.h"
11#include "ScreenHelperCocoa.h"
12#include "TextInputHandler.h"
13#include "nsCocoaUtils.h"
14#include "nsObjCExceptions.h"
15#include "nsCOMPtr.h"
16#include "nsWidgetsCID.h"
17#include "nsIRollupListener.h"
18#include "nsChildView.h"
19#include "nsWindowMap.h"
20#include "nsAppShell.h"
21#include "nsIAppShellService.h"
22#include "nsIBaseWindow.h"
23#include "nsIInterfaceRequestorUtils.h"
24#include "nsIAppWindow.h"
25#include "nsToolkit.h"
26#include "nsTouchBarNativeAPIDefines.h"
27#include "nsPIDOMWindow.h"
28#include "nsThreadUtils.h"
29#include "nsMenuBarX.h"
30#include "nsMenuUtilsX.h"
31#include "nsStyleConsts.h"
32#include "nsNativeThemeColors.h"
33#include "nsNativeThemeCocoa.h"
34#include "nsChildView.h"
35#include "nsCocoaFeatures.h"
36#include "nsIScreenManager.h"
37#include "nsIWidgetListener.h"
38#include "SDKDeclarations.h"
39#include "VibrancyManager.h"
40#include "nsPresContext.h"
41#include "nsDocShell.h"
42
43#include "gfxPlatform.h"
44#include "qcms.h"
45
46#include "mozilla/AutoRestore.h"
47#include "mozilla/BasicEvents.h"
48#include "mozilla/dom/Document.h"
49#include "mozilla/Maybe.h"
50#include "mozilla/NativeKeyBindingsType.h"
51#include "mozilla/Preferences.h"
52#include "mozilla/PresShell.h"
53#include "mozilla/ScopeExit.h"
54#include "mozilla/StaticPrefs_gfx.h"
55#include "mozilla/StaticPrefs_widget.h"
56#include "mozilla/WritingModes.h"
57#include "mozilla/layers/CompositorBridgeChild.h"
58#include <algorithm>
59
60namespace mozilla {
61namespace layers {
62class LayerManager;
63}  // namespace layers
64}  // namespace mozilla
65using namespace mozilla::layers;
66using namespace mozilla::widget;
67using namespace mozilla;
68
69int32_t gXULModalLevel = 0;
70
71// In principle there should be only one app-modal window at any given time.
72// But sometimes, despite our best efforts, another window appears above the
73// current app-modal window.  So we need to keep a linked list of app-modal
74// windows.  (A non-sheet window that appears above an app-modal window is
75// also made app-modal.)  See nsCocoaWindow::SetModal().
76nsCocoaWindowList* gGeckoAppModalWindowList = NULL;
77
78BOOL sTouchBarIsInitialized = NO;
79
80// defined in nsMenuBarX.mm
81extern NSMenu* sApplicationMenu;  // Application menu shared by all menubars
82
83// defined in nsChildView.mm
84extern BOOL gSomeMenuBarPainted;
85
86extern "C" {
87// CGSPrivate.h
88typedef NSInteger CGSConnection;
89typedef NSUInteger CGSSpaceID;
90typedef NSInteger CGSWindow;
91typedef enum {
92  kCGSSpaceIncludesCurrent = 1 << 0,
93  kCGSSpaceIncludesOthers = 1 << 1,
94  kCGSSpaceIncludesUser = 1 << 2,
95
96  kCGSAllSpacesMask = kCGSSpaceIncludesCurrent | kCGSSpaceIncludesOthers | kCGSSpaceIncludesUser
97} CGSSpaceMask;
98static NSString* const CGSSpaceIDKey = @"ManagedSpaceID";
99static NSString* const CGSSpacesKey = @"Spaces";
100extern CGSConnection _CGSDefaultConnection(void);
101extern CGError CGSSetWindowTransform(CGSConnection cid, CGSWindow wid, CGAffineTransform transform);
102}
103
104#define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1"
105
106NS_IMPL_ISUPPORTS_INHERITED(nsCocoaWindow, Inherited, nsPIWidgetCocoa)
107
108// A note on testing to see if your object is a sheet...
109// |mWindowType == eWindowType_sheet| is true if your gecko nsIWidget is a sheet
110// widget - whether or not the sheet is showing. |[mWindow isSheet]| will return
111// true *only when the sheet is actually showing*. Choose your test wisely.
112
113static void RollUpPopups() {
114  nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
115  NS_ENSURE_TRUE_VOID(rollupListener);
116
117  if (rollupListener->RollupNativeMenu()) {
118    return;
119  }
120
121  nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
122  if (!rollupWidget) return;
123  rollupListener->Rollup(0, true, nullptr, nullptr);
124}
125
126nsCocoaWindow::nsCocoaWindow()
127    : mParent(nullptr),
128      mAncestorLink(nullptr),
129      mWindow(nil),
130      mDelegate(nil),
131      mSheetWindowParent(nil),
132      mPopupContentView(nil),
133      mFullscreenTransitionAnimation(nil),
134      mShadowStyle(StyleWindowShadow::Default),
135      mBackingScaleFactor(0.0),
136      mAnimationType(nsIWidget::eGenericWindowAnimation),
137      mWindowMadeHere(false),
138      mSheetNeedsShow(false),
139      mInFullScreenMode(false),
140      mInFullScreenTransition(false),
141      mIgnoreOcclusionCount(0),
142      mModal(false),
143      mFakeModal(false),
144      mInNativeFullScreenMode(false),
145      mIsAnimationSuppressed(false),
146      mInReportMoveEvent(false),
147      mInResize(false),
148      mWindowTransformIsIdentity(true),
149      mAlwaysOnTop(false),
150      mAspectRatioLocked(false),
151      mNumModalDescendents(0),
152      mWindowAnimationBehavior(NSWindowAnimationBehaviorDefault),
153      mWasShown(false) {
154  // Disable automatic tabbing. We need to do this before we
155  // orderFront any of our windows.
156  [NSWindow setAllowsAutomaticWindowTabbing:NO];
157}
158
159void nsCocoaWindow::DestroyNativeWindow() {
160  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
161
162  if (!mWindow) return;
163
164  [mWindow releaseJSObjects];
165  // We want to unhook the delegate here because we don't want events
166  // sent to it after this object has been destroyed.
167  [mWindow setDelegate:nil];
168  [mWindow close];
169  mWindow = nil;
170  [mDelegate autorelease];
171
172  NS_OBJC_END_TRY_IGNORE_BLOCK;
173}
174
175nsCocoaWindow::~nsCocoaWindow() {
176  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
177
178  // Notify the children that we're gone.  Popup windows (e.g. tooltips) can
179  // have nsChildView children.  'kid' is an nsChildView object if and only if
180  // its 'type' is 'eWindowType_child'.
181  // childView->ResetParent() can change our list of children while it's
182  // being iterated, so the way we iterate the list must allow for this.
183  for (nsIWidget* kid = mLastChild; kid;) {
184    nsWindowType kidType = kid->WindowType();
185    if (kidType == eWindowType_child) {
186      nsChildView* childView = static_cast<nsChildView*>(kid);
187      kid = kid->GetPrevSibling();
188      childView->ResetParent();
189    } else {
190      nsCocoaWindow* childWindow = static_cast<nsCocoaWindow*>(kid);
191      childWindow->mParent = nullptr;
192      childWindow->mAncestorLink = mAncestorLink;
193      kid = kid->GetPrevSibling();
194    }
195  }
196
197  if (mWindow && mWindowMadeHere) {
198    DestroyNativeWindow();
199  }
200
201  NS_IF_RELEASE(mPopupContentView);
202
203  // Deal with the possiblity that we're being destroyed while running modal.
204  if (mModal) {
205    NS_WARNING("Widget destroyed while running modal!");
206    --gXULModalLevel;
207    NS_ASSERTION(gXULModalLevel >= 0, "Weirdness setting modality!");
208  }
209
210  NS_OBJC_END_TRY_IGNORE_BLOCK;
211}
212
213// Find the screen that overlaps aRect the most,
214// if none are found default to the mainScreen.
215static NSScreen* FindTargetScreenForRect(const DesktopIntRect& aRect) {
216  NSScreen* targetScreen = [NSScreen mainScreen];
217  NSEnumerator* screenEnum = [[NSScreen screens] objectEnumerator];
218  int largestIntersectArea = 0;
219  while (NSScreen* screen = [screenEnum nextObject]) {
220    DesktopIntRect screenRect = nsCocoaUtils::CocoaRectToGeckoRect([screen visibleFrame]);
221    screenRect = screenRect.Intersect(aRect);
222    int area = screenRect.width * screenRect.height;
223    if (area > largestIntersectArea) {
224      largestIntersectArea = area;
225      targetScreen = screen;
226    }
227  }
228  return targetScreen;
229}
230
231// fits the rect to the screen that contains the largest area of it,
232// or to aScreen if a screen is passed in
233// NB: this operates with aRect in desktop pixels
234static void FitRectToVisibleAreaForScreen(DesktopIntRect& aRect, NSScreen* aScreen) {
235  if (!aScreen) {
236    aScreen = FindTargetScreenForRect(aRect);
237  }
238
239  DesktopIntRect screenBounds = nsCocoaUtils::CocoaRectToGeckoRect([aScreen visibleFrame]);
240
241  if (aRect.width > screenBounds.width) {
242    aRect.width = screenBounds.width;
243  }
244  if (aRect.height > screenBounds.height) {
245    aRect.height = screenBounds.height;
246  }
247
248  if (aRect.x - screenBounds.x + aRect.width > screenBounds.width) {
249    aRect.x += screenBounds.width - (aRect.x - screenBounds.x + aRect.width);
250  }
251  if (aRect.y - screenBounds.y + aRect.height > screenBounds.height) {
252    aRect.y += screenBounds.height - (aRect.y - screenBounds.y + aRect.height);
253  }
254
255  // If the left/top edge of the window is off the screen in either direction,
256  // then set the window to start at the left/top edge of the screen.
257  if (aRect.x < screenBounds.x || aRect.x > (screenBounds.x + screenBounds.width)) {
258    aRect.x = screenBounds.x;
259  }
260  if (aRect.y < screenBounds.y || aRect.y > (screenBounds.y + screenBounds.height)) {
261    aRect.y = screenBounds.y;
262  }
263}
264
265DesktopToLayoutDeviceScale ParentBackingScaleFactor(nsIWidget* aParent, NSView* aParentView) {
266  if (aParent) {
267    return aParent->GetDesktopToDeviceScale();
268  }
269  NSWindow* parentWindow = [aParentView window];
270  if (parentWindow) {
271    return DesktopToLayoutDeviceScale([parentWindow backingScaleFactor]);
272  }
273  return DesktopToLayoutDeviceScale(1.0);
274}
275
276// Returns the screen rectangle for the given widget.
277// Child widgets are positioned relative to this rectangle.
278// Exactly one of the arguments must be non-null.
279static DesktopRect GetWidgetScreenRectForChildren(nsIWidget* aWidget, NSView* aView) {
280  if (aWidget) {
281    mozilla::DesktopToLayoutDeviceScale scale = aWidget->GetDesktopToDeviceScale();
282    if (aWidget->WindowType() == eWindowType_child) {
283      return aWidget->GetScreenBounds() / scale;
284    }
285    return aWidget->GetClientBounds() / scale;
286  }
287
288  MOZ_RELEASE_ASSERT(aView);
289
290  // 1. Transform the view rect into window coords.
291  // The returned rect is in "origin bottom-left" coordinates.
292  NSRect rectInWindowCoordinatesOBL = [aView convertRect:[aView bounds] toView:nil];
293
294  // 2. Turn the window-coord rect into screen coords, still origin bottom-left.
295  NSRect rectInScreenCoordinatesOBL =
296      [[aView window] convertRectToScreen:rectInWindowCoordinatesOBL];
297
298  // 3. Convert the NSRect to a DesktopRect. This will convert to coordinates
299  // with the origin in the top left corner of the primary screen.
300  return DesktopRect(nsCocoaUtils::CocoaRectToGeckoRect(rectInScreenCoordinatesOBL));
301}
302
303// aRect here is specified in desktop pixels
304//
305// For child windows (where either aParent or aNativeParent is non-null),
306// aRect.{x,y} are offsets from the origin of the parent window and not an
307// absolute position.
308nsresult nsCocoaWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
309                               const DesktopIntRect& aRect, nsWidgetInitData* aInitData) {
310  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
311
312  // Because the hidden window is created outside of an event loop,
313  // we have to provide an autorelease pool (see bug 559075).
314  nsAutoreleasePool localPool;
315
316  DesktopIntRect newBounds = aRect;
317  FitRectToVisibleAreaForScreen(newBounds, nullptr);
318
319  // Set defaults which can be overriden from aInitData in BaseCreate
320  mWindowType = eWindowType_toplevel;
321  mBorderStyle = eBorderStyle_default;
322
323  // Ensure that the toolkit is created.
324  nsToolkit::GetToolkit();
325
326  Inherited::BaseCreate(aParent, aInitData);
327
328  mParent = aParent;
329  mAncestorLink = aParent;
330  mAlwaysOnTop = aInitData->mAlwaysOnTop;
331
332  // If we have a parent widget, the new widget will be offset from the
333  // parent widget by aRect.{x,y}. Otherwise, we'll use aRect for the
334  // new widget coordinates.
335  DesktopIntPoint parentOrigin;
336
337  // Do we have a parent widget?
338  if (aParent || aNativeParent) {
339    DesktopRect parentDesktopRect = GetWidgetScreenRectForChildren(aParent, (NSView*)aNativeParent);
340    parentOrigin = gfx::RoundedToInt(parentDesktopRect.TopLeft());
341  }
342
343  DesktopIntRect widgetRect = aRect + parentOrigin;
344
345  nsresult rv =
346      CreateNativeWindow(nsCocoaUtils::GeckoRectToCocoaRect(widgetRect), mBorderStyle, false);
347  NS_ENSURE_SUCCESS(rv, rv);
348
349  if (mWindowType == eWindowType_popup) {
350    SetWindowMouseTransparent(aInitData->mMouseTransparent);
351    // now we can convert widgetRect to device pixels for the window we created,
352    // as the child view expects a rect expressed in the dev pix of its parent
353    LayoutDeviceIntRect devRect = RoundedToInt(newBounds * GetDesktopToDeviceScale());
354    return CreatePopupContentView(devRect, aInitData);
355  }
356
357  mIsAnimationSuppressed = aInitData->mIsAnimationSuppressed;
358
359  return NS_OK;
360
361  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
362}
363
364nsresult nsCocoaWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
365                               const LayoutDeviceIntRect& aRect, nsWidgetInitData* aInitData) {
366  DesktopIntRect desktopRect =
367      RoundedToInt(aRect / ParentBackingScaleFactor(aParent, (NSView*)aNativeParent));
368  return Create(aParent, aNativeParent, desktopRect, aInitData);
369}
370
371static unsigned int WindowMaskForBorderStyle(nsBorderStyle aBorderStyle) {
372  bool allOrDefault = (aBorderStyle == eBorderStyle_all || aBorderStyle == eBorderStyle_default);
373
374  /* Apple's docs on NSWindow styles say that "a window's style mask should
375   * include NSWindowStyleMaskTitled if it includes any of the others [besides
376   * NSWindowStyleMaskBorderless]".  This implies that a borderless window
377   * shouldn't have any other styles than NSWindowStyleMaskBorderless.
378   */
379  if (!allOrDefault && !(aBorderStyle & eBorderStyle_title)) return NSWindowStyleMaskBorderless;
380
381  unsigned int mask = NSWindowStyleMaskTitled;
382  if (allOrDefault || aBorderStyle & eBorderStyle_close) mask |= NSWindowStyleMaskClosable;
383  if (allOrDefault || aBorderStyle & eBorderStyle_minimize) mask |= NSWindowStyleMaskMiniaturizable;
384  if (allOrDefault || aBorderStyle & eBorderStyle_resizeh) mask |= NSWindowStyleMaskResizable;
385
386  return mask;
387}
388
389// If aRectIsFrameRect, aRect specifies the frame rect of the new window.
390// Otherwise, aRect.x/y specify the position of the window's frame relative to
391// the bottom of the menubar and aRect.width/height specify the size of the
392// content rect.
393nsresult nsCocoaWindow::CreateNativeWindow(const NSRect& aRect, nsBorderStyle aBorderStyle,
394                                           bool aRectIsFrameRect) {
395  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
396
397  // We default to NSWindowStyleMaskBorderless, add features if needed.
398  unsigned int features = NSWindowStyleMaskBorderless;
399
400  // Configure the window we will create based on the window type.
401  switch (mWindowType) {
402    case eWindowType_invisible:
403    case eWindowType_child:
404      break;
405    case eWindowType_popup:
406      if (aBorderStyle != eBorderStyle_default && mBorderStyle & eBorderStyle_title) {
407        features |= NSWindowStyleMaskTitled;
408        if (aBorderStyle & eBorderStyle_close) {
409          features |= NSWindowStyleMaskClosable;
410        }
411      }
412      if (mPopupType != ePopupTypeTooltip && aBorderStyle & eBorderStyle_minimize) {
413        features |= NSWindowStyleMaskMiniaturizable;
414      }
415      break;
416    case eWindowType_toplevel:
417    case eWindowType_dialog:
418      features = WindowMaskForBorderStyle(aBorderStyle);
419      break;
420    case eWindowType_sheet:
421      if (mParent->WindowType() != eWindowType_invisible && aBorderStyle & eBorderStyle_resizeh) {
422        features = NSWindowStyleMaskResizable;
423      } else {
424        features = NSWindowStyleMaskMiniaturizable;
425      }
426      features |= NSWindowStyleMaskTitled;
427      break;
428    default:
429      NS_ERROR("Unhandled window type!");
430      return NS_ERROR_FAILURE;
431  }
432
433  NSRect contentRect;
434
435  if (aRectIsFrameRect) {
436    contentRect = [NSWindow contentRectForFrameRect:aRect styleMask:features];
437  } else {
438    /*
439     * We pass a content area rect to initialize the native Cocoa window. The
440     * content rect we give is the same size as the size we're given by gecko.
441     * The origin we're given for non-popup windows is moved down by the height
442     * of the menu bar so that an origin of (0,100) from gecko puts the window
443     * 100 pixels below the top of the available desktop area. We also move the
444     * origin down by the height of a title bar if it exists. This is so the
445     * origin that gecko gives us for the top-left of  the window turns out to
446     * be the top-left of the window we create. This is how it was done in
447     * Carbon. If it ought to be different we'll probably need to look at all
448     * the callers.
449     *
450     * Note: This means that if you put a secondary screen on top of your main
451     * screen and open a window in the top screen, it'll be incorrectly shifted
452     * down by the height of the menu bar. Same thing would happen in Carbon.
453     *
454     * Note: If you pass a rect with 0,0 for an origin, the window ends up in a
455     * weird place for some reason. This stops that without breaking popups.
456     */
457    // Compensate for difference between frame and content area height (e.g. title bar).
458    NSRect newWindowFrame = [NSWindow frameRectForContentRect:aRect styleMask:features];
459
460    contentRect = aRect;
461    contentRect.origin.y -= (newWindowFrame.size.height - aRect.size.height);
462
463    if (mWindowType != eWindowType_popup) contentRect.origin.y -= [[NSApp mainMenu] menuBarHeight];
464  }
465
466  // NSLog(@"Top-level window being created at Cocoa rect: %f, %f, %f, %f\n",
467  //       rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
468
469  Class windowClass = [BaseWindow class];
470  // If we have a titlebar on a top-level window, we want to be able to control the
471  // titlebar color (for unified windows), so use the special ToolbarWindow class.
472  // Note that we need to check the window type because we mark sheets as
473  // having titlebars.
474  if ((mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) &&
475      (features & NSWindowStyleMaskTitled))
476    windowClass = [ToolbarWindow class];
477  // If we're a popup window we need to use the PopupWindow class.
478  else if (mWindowType == eWindowType_popup)
479    windowClass = [PopupWindow class];
480  // If we're a non-popup borderless window we need to use the
481  // BorderlessWindow class.
482  else if (features == NSWindowStyleMaskBorderless)
483    windowClass = [BorderlessWindow class];
484
485  // Create the window
486  mWindow = [[windowClass alloc] initWithContentRect:contentRect
487                                           styleMask:features
488                                             backing:NSBackingStoreBuffered
489                                               defer:YES];
490
491  // Make sure that window titles don't leak to disk in private browsing mode
492  // due to macOS' resume feature.
493  [mWindow setRestorable:NO];
494  [mWindow disableSnapshotRestoration];
495
496  // setup our notification delegate. Note that setDelegate: does NOT retain.
497  mDelegate = [[WindowDelegate alloc] initWithGeckoWindow:this];
498  [mWindow setDelegate:mDelegate];
499
500  // Make sure that the content rect we gave has been honored.
501  NSRect wantedFrame = [mWindow frameRectForChildViewRect:contentRect];
502  if (!NSEqualRects([mWindow frame], wantedFrame)) {
503    // This can happen when the window is not on the primary screen.
504    [mWindow setFrame:wantedFrame display:NO];
505  }
506  UpdateBounds();
507
508  if (mWindowType == eWindowType_invisible) {
509    [mWindow setLevel:kCGDesktopWindowLevelKey];
510  }
511
512  if (mWindowType == eWindowType_popup) {
513    SetPopupWindowLevel();
514    [mWindow setBackgroundColor:[NSColor clearColor]];
515    [mWindow setOpaque:NO];
516
517    // When multiple spaces are in use and the browser is assigned to a
518    // particular space, override the "Assign To" space and display popups on
519    // the active space. Does not work with multiple displays. See
520    // NeedsRecreateToReshow() for multi-display with multi-space workaround.
521    if (!mAlwaysOnTop) {
522      NSWindowCollectionBehavior behavior = [mWindow collectionBehavior];
523      behavior |= NSWindowCollectionBehaviorMoveToActiveSpace;
524      [mWindow setCollectionBehavior:behavior];
525    }
526  } else {
527    // Non-popup windows are always opaque.
528    [mWindow setOpaque:YES];
529  }
530
531  NSWindowCollectionBehavior newBehavior = [mWindow collectionBehavior];
532  if (mAlwaysOnTop) {
533    [mWindow setLevel:NSFloatingWindowLevel];
534    newBehavior |= NSWindowCollectionBehaviorCanJoinAllSpaces;
535  }
536  [mWindow setCollectionBehavior:newBehavior];
537
538  [mWindow setContentMinSize:NSMakeSize(60, 60)];
539  [mWindow disableCursorRects];
540
541  // Make the window use CoreAnimation from the start, so that we don't
542  // switch from a non-CA window to a CA-window in the middle.
543  [[mWindow contentView] setWantsLayer:YES];
544
545  // Make sure the window starts out not draggable by the background.
546  // We will turn it on as necessary.
547  [mWindow setMovableByWindowBackground:NO];
548
549  [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow];
550  mWindowMadeHere = true;
551
552  if (@available(macOS 10.14, *)) {
553    // Make the window respect the global appearance, which follows the browser.theme.toolbar-theme
554    // pref.
555    mWindow.appearanceSource = MOZGlobalAppearance.sharedInstance;
556  }
557
558  return NS_OK;
559
560  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
561}
562
563nsresult nsCocoaWindow::CreatePopupContentView(const LayoutDeviceIntRect& aRect,
564                                               nsWidgetInitData* aInitData) {
565  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
566
567  // We need to make our content view a ChildView.
568  mPopupContentView = new nsChildView();
569  if (!mPopupContentView) return NS_ERROR_FAILURE;
570
571  NS_ADDREF(mPopupContentView);
572
573  nsIWidget* thisAsWidget = static_cast<nsIWidget*>(this);
574  nsresult rv = mPopupContentView->Create(thisAsWidget, nullptr, aRect, aInitData);
575  if (NS_WARN_IF(NS_FAILED(rv))) {
576    return rv;
577  }
578
579  NSView* contentView = [mWindow contentView];
580  ChildView* childView = (ChildView*)mPopupContentView->GetNativeData(NS_NATIVE_WIDGET);
581  [childView setFrame:[contentView bounds]];
582  [childView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
583  [contentView addSubview:childView];
584
585  return NS_OK;
586
587  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
588}
589
590void nsCocoaWindow::Destroy() {
591  if (mOnDestroyCalled) return;
592  mOnDestroyCalled = true;
593
594  // SetFakeModal(true) is called for non-modal window opened by modal window.
595  // On Cocoa, it needs corresponding SetFakeModal(false) on destroy to restore
596  // ancestor windows' state.
597  if (mFakeModal) {
598    SetFakeModal(false);
599  }
600
601  // If we don't hide here we run into problems with panels, this is not ideal.
602  // (Bug 891424)
603  Show(false);
604
605  if (mPopupContentView) mPopupContentView->Destroy();
606
607  if (mFullscreenTransitionAnimation) {
608    [mFullscreenTransitionAnimation stopAnimation];
609    ReleaseFullscreenTransitionAnimation();
610  }
611
612  nsBaseWidget::Destroy();
613  // nsBaseWidget::Destroy() calls GetParent()->RemoveChild(this). But we
614  // don't implement GetParent(), so we need to do the equivalent here.
615  if (mParent) {
616    mParent->RemoveChild(this);
617  }
618  nsBaseWidget::OnDestroy();
619
620  if (mInFullScreenMode) {
621    // On Lion we don't have to mess with the OS chrome when in Full Screen
622    // mode.  But we do have to destroy the native window here (and not wait
623    // for that to happen in our destructor).  We don't switch away from the
624    // native window's space until the window is destroyed, and otherwise this
625    // might not happen for several seconds (because at least one object
626    // holding a reference to ourselves is usually waiting to be garbage-
627    // collected).  See bug 757618.
628    if (mInNativeFullScreenMode) {
629      DestroyNativeWindow();
630    } else if (mWindow) {
631      nsCocoaUtils::HideOSChromeOnScreen(false);
632    }
633  }
634}
635
636nsIWidget* nsCocoaWindow::GetSheetWindowParent(void) {
637  if (mWindowType != eWindowType_sheet) return nullptr;
638  nsCocoaWindow* parent = static_cast<nsCocoaWindow*>(mParent);
639  while (parent && (parent->mWindowType == eWindowType_sheet))
640    parent = static_cast<nsCocoaWindow*>(parent->mParent);
641  return parent;
642}
643
644void* nsCocoaWindow::GetNativeData(uint32_t aDataType) {
645  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
646
647  void* retVal = nullptr;
648
649  switch (aDataType) {
650    // to emulate how windows works, we always have to return a NSView
651    // for NS_NATIVE_WIDGET
652    case NS_NATIVE_WIDGET:
653    case NS_NATIVE_DISPLAY:
654      retVal = [mWindow contentView];
655      break;
656
657    case NS_NATIVE_WINDOW:
658      retVal = mWindow;
659      break;
660
661    case NS_NATIVE_GRAPHIC:
662      // There isn't anything that makes sense to return here,
663      // and it doesn't matter so just return nullptr.
664      NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a top-level window!");
665      break;
666    case NS_RAW_NATIVE_IME_CONTEXT: {
667      retVal = GetPseudoIMEContext();
668      if (retVal) {
669        break;
670      }
671      NSView* view = mWindow ? [mWindow contentView] : nil;
672      if (view) {
673        retVal = [view inputContext];
674      }
675      // If inputContext isn't available on this window, return this window's
676      // pointer instead of nullptr since if this returns nullptr,
677      // IMEStateManager cannot manage composition with TextComposition
678      // instance.  Although, this case shouldn't occur.
679      if (NS_WARN_IF(!retVal)) {
680        retVal = this;
681      }
682      break;
683    }
684  }
685
686  return retVal;
687
688  NS_OBJC_END_TRY_BLOCK_RETURN(nullptr);
689}
690
691bool nsCocoaWindow::IsVisible() const {
692  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
693
694  return (mWindow && ([mWindow isVisibleOrBeingShown] || mSheetNeedsShow));
695
696  NS_OBJC_END_TRY_BLOCK_RETURN(false);
697}
698
699void nsCocoaWindow::SetModal(bool aState) {
700  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
701
702  if (!mWindow) return;
703
704  // This is used during startup (outside the event loop) when creating
705  // the add-ons compatibility checking dialog and the profile manager UI;
706  // therefore, it needs to provide an autorelease pool to avoid cocoa
707  // objects leaking.
708  nsAutoreleasePool localPool;
709
710  mModal = aState;
711  nsCocoaWindow* ancestor = static_cast<nsCocoaWindow*>(mAncestorLink);
712  if (aState) {
713    ++gXULModalLevel;
714    // When a non-sheet window gets "set modal", make the window(s) that it
715    // appears over behave as they should.  We can't rely on native methods to
716    // do this, for the following reason:  The OS runs modal non-sheet windows
717    // in an event loop (using [NSApplication runModalForWindow:] or similar
718    // methods) that's incompatible with the modal event loop in AppWindow::
719    // ShowModal() (each of these event loops is "exclusive", and can't run at
720    // the same time as other (similar) event loops).
721    if (mWindowType != eWindowType_sheet) {
722      while (ancestor) {
723        if (ancestor->mNumModalDescendents++ == 0) {
724          NSWindow* aWindow = ancestor->GetCocoaWindow();
725          if (ancestor->mWindowType != eWindowType_invisible) {
726            [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:NO];
727            [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:NO];
728            [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:NO];
729          }
730        }
731        ancestor = static_cast<nsCocoaWindow*>(ancestor->mParent);
732      }
733      [mWindow setLevel:NSModalPanelWindowLevel];
734      nsCocoaWindowList* windowList = new nsCocoaWindowList;
735      if (windowList) {
736        windowList->window = this;  // Don't ADDREF
737        windowList->prev = gGeckoAppModalWindowList;
738        gGeckoAppModalWindowList = windowList;
739      }
740    }
741  } else {
742    --gXULModalLevel;
743    NS_ASSERTION(gXULModalLevel >= 0, "Mismatched call to nsCocoaWindow::SetModal(false)!");
744    if (mWindowType != eWindowType_sheet) {
745      while (ancestor) {
746        if (--ancestor->mNumModalDescendents == 0) {
747          NSWindow* aWindow = ancestor->GetCocoaWindow();
748          if (ancestor->mWindowType != eWindowType_invisible) {
749            [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:YES];
750            [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:YES];
751            [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:YES];
752          }
753        }
754        NS_ASSERTION(ancestor->mNumModalDescendents >= 0, "Widget hierarchy changed while modal!");
755        ancestor = static_cast<nsCocoaWindow*>(ancestor->mParent);
756      }
757      if (gGeckoAppModalWindowList) {
758        NS_ASSERTION(gGeckoAppModalWindowList->window == this,
759                     "Widget hierarchy changed while modal!");
760        nsCocoaWindowList* saved = gGeckoAppModalWindowList;
761        gGeckoAppModalWindowList = gGeckoAppModalWindowList->prev;
762        delete saved;  // "window" not ADDREFed
763      }
764      if (mWindowType == eWindowType_popup)
765        SetPopupWindowLevel();
766      else
767        [mWindow setLevel:NSNormalWindowLevel];
768    }
769  }
770
771  NS_OBJC_END_TRY_IGNORE_BLOCK;
772}
773
774void nsCocoaWindow::SetFakeModal(bool aState) {
775  mFakeModal = aState;
776  SetModal(aState);
777}
778
779bool nsCocoaWindow::IsRunningAppModal() { return [NSApp _isRunningAppModal]; }
780
781// Hide or show this window
782void nsCocoaWindow::Show(bool bState) {
783  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
784
785  if (!mWindow) return;
786
787  if (!mSheetNeedsShow) {
788    // Early exit if our current visibility state is already the requested state.
789    if (bState == ([mWindow isVisible] || [mWindow isBeingShown])) {
790      return;
791    }
792  }
793
794  [mWindow setBeingShown:bState];
795  if (bState && !mWasShown) {
796    mWasShown = true;
797  }
798
799  nsIWidget* parentWidget = mParent;
800  nsCOMPtr<nsPIWidgetCocoa> piParentWidget(do_QueryInterface(parentWidget));
801  NSWindow* nativeParentWindow =
802      (parentWidget) ? (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW) : nil;
803
804  if (bState && !mBounds.IsEmpty()) {
805    // If we had set the activationPolicy to accessory, then right now we won't
806    // have a dock icon. Make sure that we undo that and show a dock icon now that
807    // we're going to show a window.
808    if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular) {
809      [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
810      PR_SetEnv("MOZ_APP_NO_DOCK=");
811    }
812
813    // Don't try to show a popup when the parent isn't visible or is minimized.
814    if (mWindowType == eWindowType_popup && nativeParentWindow) {
815      if (![nativeParentWindow isVisible] || [nativeParentWindow isMiniaturized]) {
816        return;
817      }
818    }
819
820    if (mPopupContentView) {
821      // Ensure our content view is visible. We never need to hide it.
822      mPopupContentView->Show(true);
823    }
824
825    if (mWindowType == eWindowType_sheet) {
826      // bail if no parent window (its basically what we do in Carbon)
827      if (!nativeParentWindow || !piParentWidget) return;
828
829      NSWindow* topNonSheetWindow = nativeParentWindow;
830
831      // If this sheet is the child of another sheet, hide the parent so that
832      // this sheet can be displayed. Leave the parent mSheetNeedsShow alone,
833      // that is only used to handle sibling sheet contention. The parent will
834      // return once there are no more child sheets.
835      bool parentIsSheet = false;
836      if (NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) && parentIsSheet) {
837        piParentWidget->GetSheetWindowParent(&topNonSheetWindow);
838#ifdef MOZ_THUNDERBIRD
839        [NSApp endSheet:nativeParentWindow];
840#else
841        [nativeParentWindow.sheetParent endSheet:nativeParentWindow];
842#endif
843      }
844
845      nsCOMPtr<nsIWidget> sheetShown;
846      if (NS_SUCCEEDED(piParentWidget->GetChildSheet(true, getter_AddRefs(sheetShown))) &&
847          (!sheetShown || sheetShown == this)) {
848        // If this sheet is already the sheet actually being shown, don't
849        // tell it to show again. Otherwise the number of calls to
850#ifdef MOZ_THUNDERBIRD
851        // [NSApp beginSheet...] won't match up with [NSApp endSheet...].
852#else
853        // [NSWindow beginSheet...] won't match up with [NSWindow endSheet...].
854#endif
855        if (![mWindow isVisible]) {
856          mSheetNeedsShow = false;
857          mSheetWindowParent = topNonSheetWindow;
858#ifdef MOZ_THUNDERBIRD
859          // Only set contextInfo if our parent isn't a sheet.
860          NSWindow* contextInfo = parentIsSheet ? nil : mSheetWindowParent;
861          [TopLevelWindowData deactivateInWindow:mSheetWindowParent];
862          [NSApp beginSheet:mWindow
863              modalForWindow:mSheetWindowParent
864               modalDelegate:mDelegate
865              didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
866                 contextInfo:contextInfo];
867#else
868          NSWindow* sheet = mWindow;
869          NSWindow* nonSheetParent = parentIsSheet ? nil : mSheetWindowParent;
870          [TopLevelWindowData deactivateInWindow:mSheetWindowParent];
871          [mSheetWindowParent beginSheet:sheet
872                       completionHandler:^(NSModalResponse returnCode) {
873                         // Note: 'nonSheetParent' (if it is set) is the window that is the parent
874                         // of the sheet. If it's set, 'nonSheetParent' is always the top- level
875                         // window, not another sheet itself.  But 'nonSheetParent' is nil if our
876                         // parent window is also a sheet -- in that case we shouldn't send the
877                         // top-level window any activate events (because it's our parent window
878                         // that needs to get these events, not the top-level window).
879                         [TopLevelWindowData deactivateInWindow:sheet];
880                         [sheet orderOut:nil];
881                         if (nonSheetParent) {
882                           [TopLevelWindowData activateInWindow:nonSheetParent];
883                         }
884                       }];
885#endif
886          [TopLevelWindowData activateInWindow:mWindow];
887          SendSetZLevelEvent();
888        }
889      } else {
890        // A sibling of this sheet is active, don't show this sheet yet.
891        // When the active sheet hides, its brothers and sisters that have
892        // mSheetNeedsShow set will have their opportunities to display.
893        mSheetNeedsShow = true;
894      }
895    } else if (mWindowType == eWindowType_popup) {
896      // For reasons that aren't yet clear, calls to [NSWindow orderFront:] or
897      // [NSWindow makeKeyAndOrderFront:] can sometimes trigger "Error (1000)
898      // creating CGSWindow", which in turn triggers an internal inconsistency
899      // NSException.  These errors shouldn't be fatal.  So we need to wrap
900      // calls to ...orderFront: in TRY blocks.  See bmo bug 470864.
901      NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
902      [[mWindow contentView] setNeedsDisplay:YES];
903      [mWindow orderFront:nil];
904      NS_OBJC_END_TRY_IGNORE_BLOCK;
905      SendSetZLevelEvent();
906      // If our popup window is a non-native context menu, tell the OS (and
907      // other programs) that a menu has opened.  This is how the OS knows to
908      // close other programs' context menus when ours open.
909      if ([mWindow isKindOfClass:[PopupWindow class]] && [(PopupWindow*)mWindow isContextMenu]) {
910        [[NSDistributedNotificationCenter defaultCenter]
911            postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification"
912                          object:@"org.mozilla.gecko.PopupWindow"];
913      }
914
915      // If a parent window was supplied and this is a popup at the parent
916      // level, set its child window. This will cause the child window to
917      // appear above the parent and move when the parent does. Setting this
918      // needs to happen after the _setWindowNumber calls above, otherwise the
919      // window doesn't focus properly.
920      if (nativeParentWindow && mPopupLevel == ePopupLevelParent)
921        [nativeParentWindow addChildWindow:mWindow ordered:NSWindowAbove];
922    } else {
923      NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
924      if (mWindowType == eWindowType_toplevel &&
925          [mWindow respondsToSelector:@selector(setAnimationBehavior:)]) {
926        NSWindowAnimationBehavior behavior;
927        if (mIsAnimationSuppressed) {
928          behavior = NSWindowAnimationBehaviorNone;
929        } else {
930          switch (mAnimationType) {
931            case nsIWidget::eDocumentWindowAnimation:
932              behavior = NSWindowAnimationBehaviorDocumentWindow;
933              break;
934            default:
935              MOZ_FALLTHROUGH_ASSERT("unexpected mAnimationType value");
936            case nsIWidget::eGenericWindowAnimation:
937              behavior = NSWindowAnimationBehaviorDefault;
938              break;
939          }
940        }
941        [mWindow setAnimationBehavior:behavior];
942        mWindowAnimationBehavior = behavior;
943      }
944
945      // We don't want alwaysontop windows to pull focus when they're opened,
946      // as these tend to be for peripheral indicators and displays.
947      if (mAlwaysOnTop) {
948        [mWindow orderFront:nil];
949      } else {
950        [mWindow makeKeyAndOrderFront:nil];
951      }
952      NS_OBJC_END_TRY_IGNORE_BLOCK;
953      SendSetZLevelEvent();
954    }
955  } else {
956    // roll up any popups if a top-level window is going away
957    if (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) RollUpPopups();
958
959    // now get rid of the window/sheet
960    if (mWindowType == eWindowType_sheet) {
961      if (mSheetNeedsShow) {
962        // This is an attempt to hide a sheet that never had a chance to
963        // be shown. There's nothing to do other than make sure that it
964        // won't show.
965        mSheetNeedsShow = false;
966      } else {
967        // get sheet's parent *before* hiding the sheet (which breaks the linkage)
968        NSWindow* sheetParent = mSheetWindowParent;
969
970        // hide the sheet
971#ifdef MOZ_THUNDERBIRD
972        [NSApp endSheet:mWindow];
973#else
974        [mSheetWindowParent endSheet:mWindow];
975#endif
976        [TopLevelWindowData deactivateInWindow:mWindow];
977
978        nsCOMPtr<nsIWidget> siblingSheetToShow;
979        bool parentIsSheet = false;
980
981        if (nativeParentWindow && piParentWidget &&
982            NS_SUCCEEDED(
983                piParentWidget->GetChildSheet(false, getter_AddRefs(siblingSheetToShow))) &&
984            siblingSheetToShow) {
985          // First, give sibling sheets an opportunity to show.
986          siblingSheetToShow->Show(true);
987        } else if (nativeParentWindow && piParentWidget &&
988                   NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) && parentIsSheet) {
989#ifdef MOZ_THUNDERBIRD
990          // Only set contextInfo if the parent of the parent sheet we're about
991          // to restore isn't itself a sheet.
992          NSWindow* contextInfo = sheetParent;
993#else
994          // Only set nonSheetGrandparent if the parent of the parent sheet we're about
995          // to restore isn't itself a sheet.
996          NSWindow* nonSheetGrandparent = sheetParent;
997#endif
998          nsIWidget* grandparentWidget = nil;
999          if (NS_SUCCEEDED(piParentWidget->GetRealParent(&grandparentWidget)) &&
1000              grandparentWidget) {
1001            nsCOMPtr<nsPIWidgetCocoa> piGrandparentWidget(do_QueryInterface(grandparentWidget));
1002            bool grandparentIsSheet = false;
1003            if (piGrandparentWidget &&
1004                NS_SUCCEEDED(piGrandparentWidget->GetIsSheet(&grandparentIsSheet)) &&
1005                grandparentIsSheet) {
1006#ifdef MOZ_THUNDERBIRD
1007              contextInfo = nil;
1008#else
1009              nonSheetGrandparent = nil;
1010#endif
1011            }
1012          }
1013          // If there are no sibling sheets, but the parent is a sheet, restore
1014          // it.  It wasn't sent any deactivate events when it was hidden, so
1015          // don't call through Show, just let the OS put it back up.
1016#ifdef MOZ_THUNDERBIRD
1017          [NSApp beginSheet:nativeParentWindow
1018              modalForWindow:sheetParent
1019               modalDelegate:[nativeParentWindow delegate]
1020              didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
1021                 contextInfo:contextInfo];
1022#else
1023          [nativeParentWindow beginSheet:sheetParent
1024                       completionHandler:^(NSModalResponse returnCode) {
1025                         // Note: 'nonSheetGrandparent' (if it is set) is the window that is the
1026                         // parent of sheetParent. If it's set, 'nonSheetGrandparent' is always the
1027                         // top-level window, not another sheet itself.  But 'nonSheetGrandparent'
1028                         // is nil if our parent window is also a sheet -- in that case we shouldn't
1029                         // send the top-level window any activate events (because it's our parent
1030                         // window that needs to get these events, not the top-level window).
1031                         [TopLevelWindowData deactivateInWindow:sheetParent];
1032                         [sheetParent orderOut:nil];
1033                         if (nonSheetGrandparent) {
1034                           [TopLevelWindowData activateInWindow:nonSheetGrandparent];
1035                         }
1036                       }];
1037#endif
1038        } else {
1039          // Sheet, that was hard.  No more siblings or parents, going back
1040          // to a real window.
1041          NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1042          [sheetParent makeKeyAndOrderFront:nil];
1043          NS_OBJC_END_TRY_IGNORE_BLOCK;
1044        }
1045        SendSetZLevelEvent();
1046      }
1047    } else {
1048      // If the window is a popup window with a parent window we need to
1049      // unhook it here before ordering it out. When you order out the child
1050      // of a window it hides the parent window.
1051      if (mWindowType == eWindowType_popup && nativeParentWindow)
1052        [nativeParentWindow removeChildWindow:mWindow];
1053
1054      [mWindow orderOut:nil];
1055
1056      // If our popup window is a non-native context menu, tell the OS (and
1057      // other programs) that a menu has closed.
1058      if ([mWindow isKindOfClass:[PopupWindow class]] && [(PopupWindow*)mWindow isContextMenu]) {
1059        [[NSDistributedNotificationCenter defaultCenter]
1060            postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification"
1061                          object:@"org.mozilla.gecko.PopupWindow"];
1062      }
1063    }
1064  }
1065
1066  [mWindow setBeingShown:NO];
1067
1068  NS_OBJC_END_TRY_IGNORE_BLOCK;
1069}
1070
1071// Work around a problem where with multiple displays and multiple spaces
1072// enabled, where the browser is assigned to a single display or space, popup
1073// windows that are reshown after being hidden with [NSWindow orderOut] show on
1074// the assigned space even when opened from another display. Apply the
1075// workaround whenever more than one display is enabled.
1076bool nsCocoaWindow::NeedsRecreateToReshow() {
1077  // Limit the workaround to popup windows because only they need to override
1078  // the "Assign To" setting. i.e., to display where the parent window is.
1079  return (mWindowType == eWindowType_popup) && mWasShown && ([[NSScreen screens] count] > 1);
1080}
1081
1082WindowRenderer* nsCocoaWindow::GetWindowRenderer() {
1083  if (mPopupContentView) {
1084    return mPopupContentView->GetWindowRenderer();
1085  }
1086  return nullptr;
1087}
1088
1089nsTransparencyMode nsCocoaWindow::GetTransparencyMode() {
1090  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1091
1092  return (!mWindow || [mWindow isOpaque]) ? eTransparencyOpaque : eTransparencyTransparent;
1093
1094  NS_OBJC_END_TRY_BLOCK_RETURN(eTransparencyOpaque);
1095}
1096
1097// This is called from nsMenuPopupFrame when making a popup transparent.
1098void nsCocoaWindow::SetTransparencyMode(nsTransparencyMode aMode) {
1099  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1100
1101  // Only respect calls for popup windows.
1102  if (!mWindow || mWindowType != eWindowType_popup) {
1103    return;
1104  }
1105
1106  BOOL isTransparent = aMode == eTransparencyTransparent;
1107  BOOL currentTransparency = ![mWindow isOpaque];
1108  if (isTransparent != currentTransparency) {
1109    [mWindow setOpaque:!isTransparent];
1110    [mWindow setBackgroundColor:(isTransparent ? [NSColor clearColor] : [NSColor whiteColor])];
1111  }
1112
1113  NS_OBJC_END_TRY_IGNORE_BLOCK;
1114}
1115
1116void nsCocoaWindow::Enable(bool aState) {}
1117
1118bool nsCocoaWindow::IsEnabled() const { return true; }
1119
1120#define kWindowPositionSlop 20
1121
1122void nsCocoaWindow::ConstrainPosition(bool aAllowSlop, int32_t* aX, int32_t* aY) {
1123  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1124
1125  if (!mWindow || ![mWindow screen]) {
1126    return;
1127  }
1128
1129  nsIntRect screenBounds;
1130
1131  int32_t width, height;
1132
1133  NSRect frame = [mWindow frame];
1134
1135  // zero size rects confuse the screen manager
1136  width = std::max<int32_t>(frame.size.width, 1);
1137  height = std::max<int32_t>(frame.size.height, 1);
1138
1139  nsCOMPtr<nsIScreenManager> screenMgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
1140  if (screenMgr) {
1141    nsCOMPtr<nsIScreen> screen;
1142    screenMgr->ScreenForRect(*aX, *aY, width, height, getter_AddRefs(screen));
1143
1144    if (screen) {
1145      screen->GetRectDisplayPix(&(screenBounds.x), &(screenBounds.y), &(screenBounds.width),
1146                                &(screenBounds.height));
1147    }
1148  }
1149
1150  if (aAllowSlop) {
1151    if (*aX < screenBounds.x - width + kWindowPositionSlop) {
1152      *aX = screenBounds.x - width + kWindowPositionSlop;
1153    } else if (*aX >= screenBounds.x + screenBounds.width - kWindowPositionSlop) {
1154      *aX = screenBounds.x + screenBounds.width - kWindowPositionSlop;
1155    }
1156
1157    if (*aY < screenBounds.y - height + kWindowPositionSlop) {
1158      *aY = screenBounds.y - height + kWindowPositionSlop;
1159    } else if (*aY >= screenBounds.y + screenBounds.height - kWindowPositionSlop) {
1160      *aY = screenBounds.y + screenBounds.height - kWindowPositionSlop;
1161    }
1162  } else {
1163    if (*aX < screenBounds.x) {
1164      *aX = screenBounds.x;
1165    } else if (*aX >= screenBounds.x + screenBounds.width - width) {
1166      *aX = screenBounds.x + screenBounds.width - width;
1167    }
1168
1169    if (*aY < screenBounds.y) {
1170      *aY = screenBounds.y;
1171    } else if (*aY >= screenBounds.y + screenBounds.height - height) {
1172      *aY = screenBounds.y + screenBounds.height - height;
1173    }
1174  }
1175
1176  NS_OBJC_END_TRY_IGNORE_BLOCK;
1177}
1178
1179void nsCocoaWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
1180  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1181
1182  // Popups can be smaller than (32, 32)
1183  NSRect rect = (mWindowType == eWindowType_popup) ? NSZeroRect : NSMakeRect(0.0, 0.0, 32, 32);
1184  rect = [mWindow frameRectForChildViewRect:rect];
1185
1186  SizeConstraints c = aConstraints;
1187
1188  if (c.mScale.scale == MOZ_WIDGET_INVALID_SCALE) {
1189    c.mScale.scale = BackingScaleFactor();
1190  }
1191
1192  c.mMinSize.width = std::max(nsCocoaUtils::CocoaPointsToDevPixels(rect.size.width, c.mScale.scale),
1193                              c.mMinSize.width);
1194  c.mMinSize.height = std::max(
1195      nsCocoaUtils::CocoaPointsToDevPixels(rect.size.height, c.mScale.scale), c.mMinSize.height);
1196
1197  NSSize minSize = {nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.width, c.mScale.scale),
1198                    nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.height, c.mScale.scale)};
1199  [mWindow setMinSize:minSize];
1200
1201  c.mMaxSize.width = std::max(
1202      nsCocoaUtils::CocoaPointsToDevPixels(c.mMaxSize.width, c.mScale.scale), c.mMaxSize.width);
1203  c.mMaxSize.height = std::max(
1204      nsCocoaUtils::CocoaPointsToDevPixels(c.mMaxSize.height, c.mScale.scale), c.mMaxSize.height);
1205
1206  NSSize maxSize = {c.mMaxSize.width == NS_MAXSIZE
1207                        ? FLT_MAX
1208                        : nsCocoaUtils::DevPixelsToCocoaPoints(c.mMaxSize.width, c.mScale.scale),
1209                    c.mMaxSize.height == NS_MAXSIZE
1210                        ? FLT_MAX
1211                        : nsCocoaUtils::DevPixelsToCocoaPoints(c.mMaxSize.height, c.mScale.scale)};
1212  [mWindow setMaxSize:maxSize];
1213
1214  nsBaseWidget::SetSizeConstraints(c);
1215
1216  NS_OBJC_END_TRY_IGNORE_BLOCK;
1217}
1218
1219// Coordinates are desktop pixels
1220void nsCocoaWindow::Move(double aX, double aY) {
1221  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1222
1223  if (!mWindow) {
1224    return;
1225  }
1226
1227  // The point we have is in Gecko coordinates (origin top-left). Convert
1228  // it to Cocoa ones (origin bottom-left).
1229  NSPoint coord = {static_cast<float>(aX),
1230                   static_cast<float>(nsCocoaUtils::FlippedScreenY(NSToIntRound(aY)))};
1231
1232  NSRect frame = [mWindow frame];
1233  if (frame.origin.x != coord.x || frame.origin.y + frame.size.height != coord.y) {
1234    [mWindow setFrameTopLeftPoint:coord];
1235  }
1236
1237  NS_OBJC_END_TRY_IGNORE_BLOCK;
1238}
1239
1240void nsCocoaWindow::SetSizeMode(nsSizeMode aMode) {
1241  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1242
1243  if (!mWindow) return;
1244
1245  // mSizeMode will be updated in DispatchSizeModeEvent, which will be called
1246  // from a delegate method that handles the state change during one of the
1247  // calls below.
1248  nsSizeMode previousMode = mSizeMode;
1249
1250  if (aMode == nsSizeMode_Normal) {
1251    if ([mWindow isMiniaturized])
1252      [mWindow deminiaturize:nil];
1253    else if (previousMode == nsSizeMode_Maximized && [mWindow isZoomed])
1254      [mWindow zoom:nil];
1255    else if (previousMode == nsSizeMode_Fullscreen)
1256      MakeFullScreen(false);
1257  } else if (aMode == nsSizeMode_Minimized) {
1258    if (![mWindow isMiniaturized]) [mWindow miniaturize:nil];
1259  } else if (aMode == nsSizeMode_Maximized) {
1260    if ([mWindow isMiniaturized]) [mWindow deminiaturize:nil];
1261    if (![mWindow isZoomed]) [mWindow zoom:nil];
1262  } else if (aMode == nsSizeMode_Fullscreen) {
1263    if (!mInFullScreenMode) MakeFullScreen(true);
1264  }
1265
1266  NS_OBJC_END_TRY_IGNORE_BLOCK;
1267}
1268
1269// The (work)space switching implementation below was inspired by Phoenix:
1270// https://github.com/kasper/phoenix/tree/d6c877f62b30a060dff119d8416b0934f76af534
1271// License: MIT.
1272
1273// Runtime `CGSGetActiveSpace` library function feature detection.
1274typedef CGSSpaceID (*CGSGetActiveSpaceFunc)(CGSConnection cid);
1275static CGSGetActiveSpaceFunc GetCGSGetActiveSpaceFunc() {
1276  static CGSGetActiveSpaceFunc func = nullptr;
1277  static bool lookedUpFunc = false;
1278  if (!lookedUpFunc) {
1279    func = (CGSGetActiveSpaceFunc)dlsym(RTLD_DEFAULT, "CGSGetActiveSpace");
1280    lookedUpFunc = true;
1281  }
1282  return func;
1283}
1284// Runtime `CGSCopyManagedDisplaySpaces` library function feature detection.
1285typedef CFArrayRef (*CGSCopyManagedDisplaySpacesFunc)(CGSConnection cid);
1286static CGSCopyManagedDisplaySpacesFunc GetCGSCopyManagedDisplaySpacesFunc() {
1287  static CGSCopyManagedDisplaySpacesFunc func = nullptr;
1288  static bool lookedUpFunc = false;
1289  if (!lookedUpFunc) {
1290    func = (CGSCopyManagedDisplaySpacesFunc)dlsym(RTLD_DEFAULT, "CGSCopyManagedDisplaySpaces");
1291    lookedUpFunc = true;
1292  }
1293  return func;
1294}
1295// Runtime `CGSCopySpacesForWindows` library function feature detection.
1296typedef CFArrayRef (*CGSCopySpacesForWindowsFunc)(CGSConnection cid, CGSSpaceMask mask,
1297                                                  CFArrayRef windowIDs);
1298static CGSCopySpacesForWindowsFunc GetCGSCopySpacesForWindowsFunc() {
1299  static CGSCopySpacesForWindowsFunc func = nullptr;
1300  static bool lookedUpFunc = false;
1301  if (!lookedUpFunc) {
1302    func = (CGSCopySpacesForWindowsFunc)dlsym(RTLD_DEFAULT, "CGSCopySpacesForWindows");
1303    lookedUpFunc = true;
1304  }
1305  return func;
1306}
1307// Runtime `CGSAddWindowsToSpaces` library function feature detection.
1308typedef void (*CGSAddWindowsToSpacesFunc)(CGSConnection cid, CFArrayRef windowIDs,
1309                                          CFArrayRef spaceIDs);
1310static CGSAddWindowsToSpacesFunc GetCGSAddWindowsToSpacesFunc() {
1311  static CGSAddWindowsToSpacesFunc func = nullptr;
1312  static bool lookedUpFunc = false;
1313  if (!lookedUpFunc) {
1314    func = (CGSAddWindowsToSpacesFunc)dlsym(RTLD_DEFAULT, "CGSAddWindowsToSpaces");
1315    lookedUpFunc = true;
1316  }
1317  return func;
1318}
1319// Runtime `CGSRemoveWindowsFromSpaces` library function feature detection.
1320typedef void (*CGSRemoveWindowsFromSpacesFunc)(CGSConnection cid, CFArrayRef windowIDs,
1321                                               CFArrayRef spaceIDs);
1322static CGSRemoveWindowsFromSpacesFunc GetCGSRemoveWindowsFromSpacesFunc() {
1323  static CGSRemoveWindowsFromSpacesFunc func = nullptr;
1324  static bool lookedUpFunc = false;
1325  if (!lookedUpFunc) {
1326    func = (CGSRemoveWindowsFromSpacesFunc)dlsym(RTLD_DEFAULT, "CGSRemoveWindowsFromSpaces");
1327    lookedUpFunc = true;
1328  }
1329  return func;
1330}
1331
1332void nsCocoaWindow::GetWorkspaceID(nsAString& workspaceID) {
1333  workspaceID.Truncate();
1334  int32_t sid = GetWorkspaceID();
1335  if (sid != 0) {
1336    workspaceID.AppendInt(sid);
1337  }
1338}
1339
1340int32_t nsCocoaWindow::GetWorkspaceID() {
1341  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1342
1343  // Mac OSX space IDs start at '1' (default space), so '0' means 'unknown',
1344  // effectively.
1345  CGSSpaceID sid = 0;
1346
1347  CGSCopySpacesForWindowsFunc CopySpacesForWindows = GetCGSCopySpacesForWindowsFunc();
1348  if (!CopySpacesForWindows) {
1349    return sid;
1350  }
1351
1352  CGSConnection cid = _CGSDefaultConnection();
1353  // Fetch all spaces that this window belongs to (in order).
1354  NSArray<NSNumber*>* spaceIDs = CFBridgingRelease(CopySpacesForWindows(
1355      cid, kCGSAllSpacesMask, (__bridge CFArrayRef) @[ @([mWindow windowNumber]) ]));
1356  if ([spaceIDs count]) {
1357    // When spaces are found, return the first one.
1358    // We don't support a single window painted across multiple places for now.
1359    sid = [spaceIDs[0] integerValue];
1360  } else {
1361    // Fall back to the workspace that's currently active, which is '1' in the
1362    // common case.
1363    CGSGetActiveSpaceFunc GetActiveSpace = GetCGSGetActiveSpaceFunc();
1364    if (GetActiveSpace) {
1365      sid = GetActiveSpace(cid);
1366    }
1367  }
1368
1369  return sid;
1370
1371  NS_OBJC_END_TRY_IGNORE_BLOCK;
1372}
1373
1374void nsCocoaWindow::MoveToWorkspace(const nsAString& workspaceIDStr) {
1375  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1376
1377  if ([NSScreen screensHaveSeparateSpaces] && [[NSScreen screens] count] > 1) {
1378    // We don't support moving to a workspace when the user has this option
1379    // enabled in Mission Control.
1380    return;
1381  }
1382
1383  nsresult rv = NS_OK;
1384  int32_t workspaceID = workspaceIDStr.ToInteger(&rv);
1385  if (NS_FAILED(rv)) {
1386    return;
1387  }
1388
1389  CGSConnection cid = _CGSDefaultConnection();
1390  int32_t currentSpace = GetWorkspaceID();
1391  // If an empty workspace ID is passed in (not valid on OSX), or when the
1392  // window is already on this workspace, we don't need to do anything.
1393  if (!workspaceID || workspaceID == currentSpace) {
1394    return;
1395  }
1396
1397  CGSCopyManagedDisplaySpacesFunc CopyManagedDisplaySpaces = GetCGSCopyManagedDisplaySpacesFunc();
1398  CGSAddWindowsToSpacesFunc AddWindowsToSpaces = GetCGSAddWindowsToSpacesFunc();
1399  CGSRemoveWindowsFromSpacesFunc RemoveWindowsFromSpaces = GetCGSRemoveWindowsFromSpacesFunc();
1400  if (!CopyManagedDisplaySpaces || !AddWindowsToSpaces || !RemoveWindowsFromSpaces) {
1401    return;
1402  }
1403
1404  // Fetch an ordered list of all known spaces.
1405  NSArray* displaySpacesInfo = CFBridgingRelease(CopyManagedDisplaySpaces(cid));
1406  // When we found the space we're looking for, we can bail out of the loop
1407  // early, which this local variable is used for.
1408  BOOL found = false;
1409  for (NSDictionary<NSString*, id>* spacesInfo in displaySpacesInfo) {
1410    NSArray<NSNumber*>* sids = [spacesInfo[CGSSpacesKey] valueForKey:CGSSpaceIDKey];
1411    for (NSNumber* sid in sids) {
1412      // If we found our space in the list, we're good to go and can jump out of
1413      // this loop.
1414      if ((int)[sid integerValue] == workspaceID) {
1415        found = true;
1416        break;
1417      }
1418    }
1419    if (found) {
1420      break;
1421    }
1422  }
1423
1424  // We were unable to find the space to correspond with the workspaceID as
1425  // requested, so let's bail out.
1426  if (!found) {
1427    return;
1428  }
1429
1430  // First we add the window to the appropriate space.
1431  AddWindowsToSpaces(cid, (__bridge CFArrayRef) @[ @([mWindow windowNumber]) ],
1432                     (__bridge CFArrayRef) @[ @(workspaceID) ]);
1433  // Then we remove the window from the active space.
1434  RemoveWindowsFromSpaces(cid, (__bridge CFArrayRef) @[ @([mWindow windowNumber]) ],
1435                          (__bridge CFArrayRef) @[ @(currentSpace) ]);
1436
1437  NS_OBJC_END_TRY_IGNORE_BLOCK;
1438}
1439
1440void nsCocoaWindow::SuppressAnimation(bool aSuppress) {
1441  if ([mWindow respondsToSelector:@selector(setAnimationBehavior:)]) {
1442    if (aSuppress) {
1443      [mWindow setIsAnimationSuppressed:YES];
1444      [mWindow setAnimationBehavior:NSWindowAnimationBehaviorNone];
1445    } else {
1446      [mWindow setIsAnimationSuppressed:NO];
1447      [mWindow setAnimationBehavior:mWindowAnimationBehavior];
1448    }
1449  }
1450}
1451
1452// This has to preserve the window's frame bounds.
1453// This method requires (as does the Windows impl.) that you call Resize shortly
1454// after calling HideWindowChrome. See bug 498835 for fixing this.
1455void nsCocoaWindow::HideWindowChrome(bool aShouldHide) {
1456  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1457
1458  if (!mWindow || !mWindowMadeHere ||
1459      (mWindowType != eWindowType_toplevel && mWindowType != eWindowType_dialog))
1460    return;
1461
1462  BOOL isVisible = [mWindow isVisible];
1463
1464  // Remove child windows.
1465  NSArray* childWindows = [mWindow childWindows];
1466  NSEnumerator* enumerator = [childWindows objectEnumerator];
1467  NSWindow* child = nil;
1468  while ((child = [enumerator nextObject])) {
1469    [mWindow removeChildWindow:child];
1470  }
1471
1472  // Remove the views in the old window's content view.
1473  // The NSArray is autoreleased and retains its NSViews.
1474  NSArray<NSView*>* contentViewContents = [mWindow contentViewContents];
1475  for (NSView* view in contentViewContents) {
1476    [view removeFromSuperviewWithoutNeedingDisplay];
1477  }
1478
1479  // Save state (like window title).
1480  NSMutableDictionary* state = [mWindow exportState];
1481
1482  // Recreate the window with the right border style.
1483  NSRect frameRect = [mWindow frame];
1484  DestroyNativeWindow();
1485  nsresult rv = CreateNativeWindow(frameRect, aShouldHide ? eBorderStyle_none : mBorderStyle, true);
1486  NS_ENSURE_SUCCESS_VOID(rv);
1487
1488  // Re-import state.
1489  [mWindow importState:state];
1490
1491  // Add the old content view subviews to the new window's content view.
1492  for (NSView* view in contentViewContents) {
1493    [[mWindow contentView] addSubview:view];
1494  }
1495
1496  // Reparent child windows.
1497  enumerator = [childWindows objectEnumerator];
1498  while ((child = [enumerator nextObject])) {
1499    [mWindow addChildWindow:child ordered:NSWindowAbove];
1500  }
1501
1502  // Show the new window.
1503  if (isVisible) {
1504    bool wasAnimationSuppressed = mIsAnimationSuppressed;
1505    mIsAnimationSuppressed = true;
1506    Show(true);
1507    mIsAnimationSuppressed = wasAnimationSuppressed;
1508  }
1509
1510  NS_OBJC_END_TRY_IGNORE_BLOCK;
1511}
1512
1513class FullscreenTransitionData : public nsISupports {
1514 public:
1515  NS_DECL_ISUPPORTS
1516
1517  explicit FullscreenTransitionData(NSWindow* aWindow) : mTransitionWindow(aWindow) {}
1518
1519  NSWindow* mTransitionWindow;
1520
1521 private:
1522  virtual ~FullscreenTransitionData() { [mTransitionWindow close]; }
1523};
1524
1525NS_IMPL_ISUPPORTS0(FullscreenTransitionData)
1526
1527@interface FullscreenTransitionDelegate : NSObject <NSAnimationDelegate> {
1528 @public
1529  nsCocoaWindow* mWindow;
1530  nsIRunnable* mCallback;
1531}
1532@end
1533
1534@implementation FullscreenTransitionDelegate
1535- (void)cleanupAndDispatch:(NSAnimation*)animation {
1536  [animation setDelegate:nil];
1537  [self autorelease];
1538  // The caller should have added ref for us.
1539  NS_DispatchToMainThread(already_AddRefed<nsIRunnable>(mCallback));
1540}
1541
1542- (void)animationDidEnd:(NSAnimation*)animation {
1543  MOZ_ASSERT(animation == mWindow->FullscreenTransitionAnimation(),
1544             "Should be handling the only animation on the window");
1545  mWindow->ReleaseFullscreenTransitionAnimation();
1546  [self cleanupAndDispatch:animation];
1547}
1548
1549- (void)animationDidStop:(NSAnimation*)animation {
1550  [self cleanupAndDispatch:animation];
1551}
1552@end
1553
1554static bool AlwaysUsesNativeFullScreen() {
1555  return Preferences::GetBool("full-screen-api.macos-native-full-screen", false);
1556}
1557
1558/* virtual */ bool nsCocoaWindow::PrepareForFullscreenTransition(nsISupports** aData) {
1559  if (AlwaysUsesNativeFullScreen()) {
1560    return false;
1561  }
1562
1563  // Our fullscreen transition creates a new window occluding this window.
1564  // That triggers an occlusion event which can cause DOM fullscreen requests
1565  // to fail due to the context not being focused at the time the focus check
1566  // is performed in the child process. Until the transition is cleaned up in
1567  // CleanupFullscreenTransition(), ignore occlusion events for this window.
1568  // If this method is changed to return false, the transition will not be
1569  // performed and mIgnoreOcclusionCount should not be incremented.
1570  MOZ_ASSERT(mIgnoreOcclusionCount >= 0);
1571  mIgnoreOcclusionCount++;
1572
1573  nsCOMPtr<nsIScreen> widgetScreen = GetWidgetScreen();
1574  NSScreen* cocoaScreen = ScreenHelperCocoa::CocoaScreenForScreen(widgetScreen);
1575
1576  NSWindow* win = [[NSWindow alloc] initWithContentRect:[cocoaScreen frame]
1577                                              styleMask:NSWindowStyleMaskBorderless
1578                                                backing:NSBackingStoreBuffered
1579                                                  defer:YES];
1580  [win setBackgroundColor:[NSColor blackColor]];
1581  [win setAlphaValue:0];
1582  [win setIgnoresMouseEvents:YES];
1583  [win setLevel:NSScreenSaverWindowLevel];
1584  [win makeKeyAndOrderFront:nil];
1585
1586  auto data = new FullscreenTransitionData(win);
1587  *aData = data;
1588  NS_ADDREF(data);
1589  return true;
1590}
1591
1592/* virtual */ void nsCocoaWindow::CleanupFullscreenTransition() {
1593  MOZ_ASSERT(mIgnoreOcclusionCount > 0);
1594  mIgnoreOcclusionCount--;
1595}
1596
1597/* virtual */ void nsCocoaWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
1598                                                              uint16_t aDuration,
1599                                                              nsISupports* aData,
1600                                                              nsIRunnable* aCallback) {
1601  auto data = static_cast<FullscreenTransitionData*>(aData);
1602  FullscreenTransitionDelegate* delegate = [[FullscreenTransitionDelegate alloc] init];
1603  delegate->mWindow = this;
1604  // Storing already_AddRefed directly could cause static checking fail.
1605  delegate->mCallback = nsCOMPtr<nsIRunnable>(aCallback).forget().take();
1606
1607  if (mFullscreenTransitionAnimation) {
1608    [mFullscreenTransitionAnimation stopAnimation];
1609    ReleaseFullscreenTransitionAnimation();
1610  }
1611
1612  NSDictionary* dict = @{
1613    NSViewAnimationTargetKey : data->mTransitionWindow,
1614    NSViewAnimationEffectKey : aStage == eBeforeFullscreenToggle ? NSViewAnimationFadeInEffect
1615                                                                 : NSViewAnimationFadeOutEffect
1616  };
1617  mFullscreenTransitionAnimation = [[NSViewAnimation alloc] initWithViewAnimations:@[ dict ]];
1618  [mFullscreenTransitionAnimation setDelegate:delegate];
1619  [mFullscreenTransitionAnimation setDuration:aDuration / 1000.0];
1620  [mFullscreenTransitionAnimation startAnimation];
1621}
1622
1623void nsCocoaWindow::WillEnterFullScreen(bool aFullScreen) {
1624  if (mWidgetListener) {
1625    mWidgetListener->FullscreenWillChange(aFullScreen);
1626  }
1627  // Update the state to full screen when we are entering, so that we switch to
1628  // full screen view as soon as possible.
1629  UpdateFullscreenState(aFullScreen, true);
1630}
1631
1632void nsCocoaWindow::EnteredFullScreen(bool aFullScreen, bool aNativeMode) {
1633  mInFullScreenTransition = false;
1634  UpdateFullscreenState(aFullScreen, aNativeMode);
1635}
1636
1637void nsCocoaWindow::UpdateFullscreenState(bool aFullScreen, bool aNativeMode) {
1638  bool wasInFullscreen = mInFullScreenMode;
1639  mInFullScreenMode = aFullScreen;
1640  if (aNativeMode || mInNativeFullScreenMode) {
1641    mInNativeFullScreenMode = aFullScreen;
1642  }
1643  DispatchSizeModeEvent();
1644  if (mWidgetListener && wasInFullscreen != aFullScreen) {
1645    mWidgetListener->FullscreenChanged(aFullScreen);
1646  }
1647
1648  // Notify the mainChildView with our new fullscreen state.
1649  nsChildView* mainChildView = static_cast<nsChildView*>([[mWindow mainChildView] widget]);
1650  if (mainChildView) {
1651    mainChildView->UpdateFullscreen(aFullScreen);
1652  }
1653}
1654
1655inline bool nsCocoaWindow::ShouldToggleNativeFullscreen(bool aFullScreen,
1656                                                        bool aUseSystemTransition) {
1657  // First check if this window supports entering native fullscreen.
1658  // This is set based on the macnativefullscreen attribute on the window's
1659  // document element.
1660  NSWindowCollectionBehavior colBehavior = [mWindow collectionBehavior];
1661  if (!(colBehavior & NSWindowCollectionBehaviorFullScreenPrimary)) {
1662    return false;
1663  }
1664
1665  if (mInNativeFullScreenMode) {
1666    // If we are using native fullscreen, go ahead to exit it.
1667    return true;
1668  }
1669  if (!aUseSystemTransition) {
1670    // If we do not want the system fullscreen transition,
1671    // don't use the native fullscreen.
1672    return false;
1673  }
1674  // If we are using native fullscreen, we should have returned earlier.
1675  return aFullScreen;
1676}
1677
1678nsresult nsCocoaWindow::MakeFullScreen(bool aFullScreen) {
1679  return DoMakeFullScreen(aFullScreen, AlwaysUsesNativeFullScreen());
1680}
1681
1682nsresult nsCocoaWindow::MakeFullScreenWithNativeTransition(bool aFullScreen) {
1683  return DoMakeFullScreen(aFullScreen, true);
1684}
1685
1686nsresult nsCocoaWindow::DoMakeFullScreen(bool aFullScreen, bool aUseSystemTransition) {
1687  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1688
1689  if (!mWindow) {
1690    return NS_OK;
1691  }
1692
1693  // We will call into MakeFullScreen redundantly when entering/exiting
1694  // fullscreen mode via OS X controls. When that happens we should just handle
1695  // it gracefully - no need to ASSERT.
1696  if (mInFullScreenMode == aFullScreen) {
1697    return NS_OK;
1698  }
1699
1700  mInFullScreenTransition = true;
1701
1702  if (ShouldToggleNativeFullscreen(aFullScreen, aUseSystemTransition)) {
1703    MOZ_ASSERT(mInNativeFullScreenMode != aFullScreen,
1704               "We shouldn't have been in native fullscreen.");
1705    // Calling toggleFullScreen will result in windowDid(FailTo)?(Enter|Exit)FullScreen
1706    // to be called from the OS. We will call EnteredFullScreen from those methods,
1707    // where mInFullScreenMode will be set and a sizemode event will be dispatched.
1708    [mWindow toggleFullScreen:nil];
1709  } else {
1710    if (mWidgetListener) {
1711      mWidgetListener->FullscreenWillChange(aFullScreen);
1712    }
1713    NSDisableScreenUpdates();
1714    // The order here matters. When we exit full screen mode, we need to show the
1715    // Dock first, otherwise the newly-created window won't have its minimize
1716    // button enabled. See bug 526282.
1717    nsCocoaUtils::HideOSChromeOnScreen(aFullScreen);
1718    nsBaseWidget::InfallibleMakeFullScreen(aFullScreen);
1719    NSEnableScreenUpdates();
1720    EnteredFullScreen(aFullScreen, /* aNativeMode */ false);
1721  }
1722
1723  return NS_OK;
1724
1725  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
1726}
1727
1728// Coordinates are desktop pixels
1729void nsCocoaWindow::DoResize(double aX, double aY, double aWidth, double aHeight, bool aRepaint,
1730                             bool aConstrainToCurrentScreen) {
1731  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1732
1733  if (!mWindow || mInResize) {
1734    return;
1735  }
1736
1737  // We are able to resize a window outside of any aspect ratio contraints
1738  // applied to it, but in order to "update" the aspect ratio contraint to the
1739  // new window dimensions, we must re-lock the aspect ratio.
1740  auto relockAspectRatio = MakeScopeExit([&]() {
1741    if (mAspectRatioLocked) {
1742      LockAspectRatio(true);
1743    }
1744  });
1745
1746  AutoRestore<bool> reentrantResizeGuard(mInResize);
1747  mInResize = true;
1748
1749  CGFloat scale = mSizeConstraints.mScale.scale;
1750  if (scale == MOZ_WIDGET_INVALID_SCALE) {
1751    scale = BackingScaleFactor();
1752  }
1753
1754  // mSizeConstraints is in device pixels.
1755  int32_t width = NSToIntRound(aWidth * scale);
1756  int32_t height = NSToIntRound(aHeight * scale);
1757
1758  width =
1759      std::max(mSizeConstraints.mMinSize.width, std::min(mSizeConstraints.mMaxSize.width, width));
1760  height = std::max(mSizeConstraints.mMinSize.height,
1761                    std::min(mSizeConstraints.mMaxSize.height, height));
1762
1763  DesktopIntRect newBounds(NSToIntRound(aX), NSToIntRound(aY), NSToIntRound(width / scale),
1764                           NSToIntRound(height / scale));
1765
1766  // constrain to the screen that contains the largest area of the new rect
1767  FitRectToVisibleAreaForScreen(newBounds, aConstrainToCurrentScreen ? [mWindow screen] : nullptr);
1768
1769  // convert requested bounds into Cocoa coordinate system
1770  NSRect newFrame = nsCocoaUtils::GeckoRectToCocoaRect(newBounds);
1771
1772  NSRect frame = [mWindow frame];
1773  BOOL isMoving = newFrame.origin.x != frame.origin.x || newFrame.origin.y != frame.origin.y;
1774  BOOL isResizing =
1775      newFrame.size.width != frame.size.width || newFrame.size.height != frame.size.height;
1776
1777  if (!isMoving && !isResizing) {
1778    return;
1779  }
1780
1781  // We ignore aRepaint -- we have to call display:YES, otherwise the
1782  // title bar doesn't immediately get repainted and is displayed in
1783  // the wrong place, leading to a visual jump.
1784  [mWindow setFrame:newFrame display:YES];
1785
1786  NS_OBJC_END_TRY_IGNORE_BLOCK;
1787}
1788
1789// Coordinates are desktop pixels
1790void nsCocoaWindow::Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) {
1791  DoResize(aX, aY, aWidth, aHeight, aRepaint, false);
1792}
1793
1794// Coordinates are desktop pixels
1795void nsCocoaWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
1796  double invScale = 1.0 / BackingScaleFactor();
1797  DoResize(mBounds.x * invScale, mBounds.y * invScale, aWidth, aHeight, aRepaint, true);
1798}
1799
1800// Return the area that the Gecko ChildView in our window should cover, as an
1801// NSRect in screen coordinates (with 0,0 being the bottom left corner of the
1802// primary screen).
1803NSRect nsCocoaWindow::GetClientCocoaRect() {
1804  if (!mWindow) {
1805    return NSZeroRect;
1806  }
1807
1808  return [mWindow childViewRectForFrameRect:[mWindow frame]];
1809}
1810
1811LayoutDeviceIntRect nsCocoaWindow::GetClientBounds() {
1812  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1813
1814  CGFloat scaleFactor = BackingScaleFactor();
1815  return nsCocoaUtils::CocoaRectToGeckoRectDevPix(GetClientCocoaRect(), scaleFactor);
1816
1817  NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntRect(0, 0, 0, 0));
1818}
1819
1820void nsCocoaWindow::UpdateBounds() {
1821  NSRect frame = NSZeroRect;
1822  if (mWindow) {
1823    frame = [mWindow frame];
1824  }
1825  mBounds = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
1826
1827  if (mPopupContentView) {
1828    mPopupContentView->UpdateBoundsFromView();
1829  }
1830}
1831
1832LayoutDeviceIntRect nsCocoaWindow::GetScreenBounds() {
1833  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1834
1835#ifdef DEBUG
1836  LayoutDeviceIntRect r =
1837      nsCocoaUtils::CocoaRectToGeckoRectDevPix([mWindow frame], BackingScaleFactor());
1838  NS_ASSERTION(mWindow && mBounds == r, "mBounds out of sync!");
1839#endif
1840
1841  return mBounds;
1842
1843  NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntRect(0, 0, 0, 0));
1844}
1845
1846double nsCocoaWindow::GetDefaultScaleInternal() { return BackingScaleFactor(); }
1847
1848static CGFloat GetBackingScaleFactor(NSWindow* aWindow) {
1849  NSRect frame = [aWindow frame];
1850  if (frame.size.width > 0 && frame.size.height > 0) {
1851    return nsCocoaUtils::GetBackingScaleFactor(aWindow);
1852  }
1853
1854  // For windows with zero width or height, the backingScaleFactor method
1855  // is broken - it will always return 2 on a retina macbook, even when
1856  // the window position implies it's on a non-hidpi external display
1857  // (to the extent that a zero-area window can be said to be "on" a
1858  // display at all!)
1859  // And to make matters worse, Cocoa even fires a
1860  // windowDidChangeBackingProperties notification with the
1861  // NSBackingPropertyOldScaleFactorKey key when a window on an
1862  // external display is resized to/from zero height, even though it hasn't
1863  // really changed screens.
1864
1865  // This causes us to handle popup window sizing incorrectly when the
1866  // popup is resized to zero height (bug 820327) - nsXULPopupManager
1867  // becomes (incorrectly) convinced the popup has been explicitly forced
1868  // to a non-default size and needs to have size attributes attached.
1869
1870  // Workaround: instead of asking the window, we'll find the screen it is on
1871  // and ask that for *its* backing scale factor.
1872
1873  // (See bug 853252 and additional comments in windowDidChangeScreen: below
1874  // for further complications this causes.)
1875
1876  // First, expand the rect so that it actually has a measurable area,
1877  // for FindTargetScreenForRect to use.
1878  if (frame.size.width == 0) {
1879    frame.size.width = 1;
1880  }
1881  if (frame.size.height == 0) {
1882    frame.size.height = 1;
1883  }
1884
1885  // Then identify the screen it belongs to, and return its scale factor.
1886  NSScreen* screen = FindTargetScreenForRect(nsCocoaUtils::CocoaRectToGeckoRect(frame));
1887  return nsCocoaUtils::GetBackingScaleFactor(screen);
1888}
1889
1890CGFloat nsCocoaWindow::BackingScaleFactor() {
1891  if (mBackingScaleFactor > 0.0) {
1892    return mBackingScaleFactor;
1893  }
1894  if (!mWindow) {
1895    return 1.0;
1896  }
1897  mBackingScaleFactor = GetBackingScaleFactor(mWindow);
1898  return mBackingScaleFactor;
1899}
1900
1901void nsCocoaWindow::BackingScaleFactorChanged() {
1902  CGFloat newScale = GetBackingScaleFactor(mWindow);
1903
1904  // ignore notification if it hasn't really changed (or maybe we have
1905  // disabled HiDPI mode via prefs)
1906  if (mBackingScaleFactor == newScale) {
1907    return;
1908  }
1909
1910  mBackingScaleFactor = newScale;
1911
1912  if (!mWidgetListener || mWidgetListener->GetAppWindow()) {
1913    return;
1914  }
1915
1916  if (PresShell* presShell = mWidgetListener->GetPresShell()) {
1917    presShell->BackingScaleFactorChanged();
1918  }
1919  mWidgetListener->UIResolutionChanged();
1920}
1921
1922int32_t nsCocoaWindow::RoundsWidgetCoordinatesTo() {
1923  if (BackingScaleFactor() == 2.0) {
1924    return 2;
1925  }
1926  return 1;
1927}
1928
1929void nsCocoaWindow::SetCursor(const Cursor& aCursor) {
1930  if (mPopupContentView) {
1931    mPopupContentView->SetCursor(aCursor);
1932  }
1933}
1934
1935nsresult nsCocoaWindow::SetTitle(const nsAString& aTitle) {
1936  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1937
1938  if (!mWindow) {
1939    return NS_OK;
1940  }
1941
1942  const nsString& strTitle = PromiseFlatString(aTitle);
1943  const unichar* uniTitle = reinterpret_cast<const unichar*>(strTitle.get());
1944  NSString* title = [NSString stringWithCharacters:uniTitle length:strTitle.Length()];
1945  if ([mWindow drawsContentsIntoWindowFrame] && ![mWindow wantsTitleDrawn]) {
1946    // Don't cause invalidations when the title isn't displayed.
1947    [mWindow disableSetNeedsDisplay];
1948    [mWindow setTitle:title];
1949    [mWindow enableSetNeedsDisplay];
1950  } else {
1951    [mWindow setTitle:title];
1952  }
1953
1954  return NS_OK;
1955
1956  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
1957}
1958
1959void nsCocoaWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
1960  if (mPopupContentView) {
1961    mPopupContentView->Invalidate(aRect);
1962  }
1963}
1964
1965// Pass notification of some drag event to Gecko
1966//
1967// The drag manager has let us know that something related to a drag has
1968// occurred in this window. It could be any number of things, ranging from
1969// a drop, to a drag enter/leave, or a drag over event. The actual event
1970// is passed in |aMessage| and is passed along to our event hanlder so Gecko
1971// knows about it.
1972bool nsCocoaWindow::DragEvent(unsigned int aMessage, mozilla::gfx::Point aMouseGlobal,
1973                              UInt16 aKeyModifiers) {
1974  return false;
1975}
1976
1977NS_IMETHODIMP nsCocoaWindow::SendSetZLevelEvent() {
1978  nsWindowZ placement = nsWindowZTop;
1979  nsCOMPtr<nsIWidget> actualBelow;
1980  if (mWidgetListener)
1981    mWidgetListener->ZLevelChanged(true, &placement, nullptr, getter_AddRefs(actualBelow));
1982  return NS_OK;
1983}
1984
1985NS_IMETHODIMP nsCocoaWindow::GetChildSheet(bool aShown, nsIWidget** _retval) {
1986  nsIWidget* child = GetFirstChild();
1987
1988  while (child) {
1989    if (child->WindowType() == eWindowType_sheet) {
1990      // if it's a sheet, it must be an nsCocoaWindow
1991      nsCocoaWindow* cocoaWindow = static_cast<nsCocoaWindow*>(child);
1992      if (cocoaWindow->mWindow && ((aShown && [cocoaWindow->mWindow isVisible]) ||
1993                                   (!aShown && cocoaWindow->mSheetNeedsShow))) {
1994        nsCOMPtr<nsIWidget> widget = cocoaWindow;
1995        widget.forget(_retval);
1996        return NS_OK;
1997      }
1998    }
1999    child = child->GetNextSibling();
2000  }
2001
2002  *_retval = nullptr;
2003
2004  return NS_OK;
2005}
2006
2007NS_IMETHODIMP nsCocoaWindow::GetRealParent(nsIWidget** parent) {
2008  *parent = mParent;
2009  return NS_OK;
2010}
2011
2012NS_IMETHODIMP nsCocoaWindow::GetIsSheet(bool* isSheet) {
2013  mWindowType == eWindowType_sheet ? * isSheet = true : * isSheet = false;
2014  return NS_OK;
2015}
2016
2017NS_IMETHODIMP nsCocoaWindow::GetSheetWindowParent(NSWindow** sheetWindowParent) {
2018  *sheetWindowParent = mSheetWindowParent;
2019  return NS_OK;
2020}
2021
2022// Invokes callback and ProcessEvent methods on Event Listener object
2023nsresult nsCocoaWindow::DispatchEvent(WidgetGUIEvent* event, nsEventStatus& aStatus) {
2024  aStatus = nsEventStatus_eIgnore;
2025
2026  nsCOMPtr<nsIWidget> kungFuDeathGrip(event->mWidget);
2027  mozilla::Unused << kungFuDeathGrip;  // Not used within this function
2028
2029  if (mWidgetListener) aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents);
2030
2031  return NS_OK;
2032}
2033
2034// aFullScreen should be the window's mInFullScreenMode. We don't have access to that
2035// from here, so we need to pass it in. mInFullScreenMode should be the canonical
2036// indicator that a window is currently full screen and it makes sense to keep
2037// all sizemode logic here.
2038static nsSizeMode GetWindowSizeMode(NSWindow* aWindow, bool aFullScreen) {
2039  if (aFullScreen) return nsSizeMode_Fullscreen;
2040  if ([aWindow isMiniaturized]) return nsSizeMode_Minimized;
2041  if (([aWindow styleMask] & NSWindowStyleMaskResizable) && [aWindow isZoomed])
2042    return nsSizeMode_Maximized;
2043  return nsSizeMode_Normal;
2044}
2045
2046void nsCocoaWindow::ReportMoveEvent() {
2047  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2048
2049  // Prevent recursion, which can become infinite (see bug 708278).  This
2050  // can happen when the call to [NSWindow setFrameTopLeftPoint:] in
2051  // nsCocoaWindow::Move() triggers an immediate NSWindowDidMove notification
2052  // (and a call to [WindowDelegate windowDidMove:]).
2053  if (mInReportMoveEvent) {
2054    return;
2055  }
2056  mInReportMoveEvent = true;
2057
2058  UpdateBounds();
2059
2060  // The zoomed state can change when we're moving, in which case we need to
2061  // update our internal mSizeMode. This can happen either if we're maximized
2062  // and then moved, or if we're not maximized and moved back to zoomed state.
2063  if (mWindow && ((mSizeMode == nsSizeMode_Maximized) ^ [mWindow isZoomed])) {
2064    DispatchSizeModeEvent();
2065  }
2066
2067  // Dispatch the move event to Gecko
2068  NotifyWindowMoved(mBounds.x, mBounds.y);
2069
2070  mInReportMoveEvent = false;
2071
2072  NS_OBJC_END_TRY_IGNORE_BLOCK;
2073}
2074
2075void nsCocoaWindow::DispatchSizeModeEvent() {
2076  if (!mWindow) {
2077    return;
2078  }
2079
2080  nsSizeMode newMode = GetWindowSizeMode(mWindow, mInFullScreenMode);
2081
2082  // Don't dispatch a sizemode event if:
2083  // 1. the window is transitioning to fullscreen
2084  // 2. the new sizemode is the same as the current sizemode
2085  if (mInFullScreenTransition || mSizeMode == newMode) {
2086    return;
2087  }
2088
2089  mSizeMode = newMode;
2090  if (mWidgetListener) {
2091    mWidgetListener->SizeModeChanged(newMode);
2092  }
2093
2094  if (StaticPrefs::widget_pause_compositor_when_minimized()) {
2095    if (newMode == nsSizeMode_Minimized) {
2096      PauseCompositor();
2097    } else {
2098      ResumeCompositor();
2099    }
2100  }
2101}
2102
2103void nsCocoaWindow::DispatchOcclusionEvent() {
2104  if (!mWindow) {
2105    return;
2106  }
2107
2108  bool newOcclusionState = !([mWindow occlusionState] & NSWindowOcclusionStateVisible);
2109
2110  // Don't dispatch if the new occlustion state is the same as the current state.
2111  if (mIsFullyOccluded == newOcclusionState) {
2112    return;
2113  }
2114
2115  MOZ_ASSERT(mIgnoreOcclusionCount >= 0);
2116  if (newOcclusionState && mIgnoreOcclusionCount > 0) {
2117    return;
2118  }
2119
2120  mIsFullyOccluded = newOcclusionState;
2121  if (mWidgetListener) {
2122    mWidgetListener->OcclusionStateChanged(mIsFullyOccluded);
2123  }
2124}
2125
2126void nsCocoaWindow::ReportSizeEvent() {
2127  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2128
2129  UpdateBounds();
2130
2131  if (mWidgetListener) {
2132    LayoutDeviceIntRect innerBounds = GetClientBounds();
2133    mWidgetListener->WindowResized(this, innerBounds.width, innerBounds.height);
2134  }
2135
2136  NS_OBJC_END_TRY_IGNORE_BLOCK;
2137}
2138
2139void nsCocoaWindow::PauseCompositor() {
2140  nsIWidget* mainChildView = static_cast<nsIWidget*>([[mWindow mainChildView] widget]);
2141  if (!mainChildView) {
2142    return;
2143  }
2144  CompositorBridgeChild* remoteRenderer = mainChildView->GetRemoteRenderer();
2145  if (!remoteRenderer) {
2146    return;
2147  }
2148  remoteRenderer->SendPause();
2149
2150  // Now that the compositor has paused, we also try to mark the browser window
2151  // docshell inactive to stop any animations. This does not affect docshells
2152  // for browsers in other processes, but browser UI code should be managing
2153  // their active state appropriately.
2154  if (!mWidgetListener) {
2155    return;
2156  }
2157  PresShell* presShell = mWidgetListener->GetPresShell();
2158  if (!presShell) {
2159    return;
2160  }
2161  nsPresContext* presContext = presShell->GetPresContext();
2162  if (!presContext) {
2163    return;
2164  }
2165  BrowsingContext* bc = presContext->Document()->GetBrowsingContext();
2166  if (!bc) {
2167    return;
2168  }
2169  Unused << bc->SetExplicitActive(ExplicitActiveStatus::Inactive);
2170}
2171
2172void nsCocoaWindow::ResumeCompositor() {
2173  nsIWidget* mainChildView = static_cast<nsIWidget*>([[mWindow mainChildView] widget]);
2174  if (!mainChildView) {
2175    return;
2176  }
2177  CompositorBridgeChild* remoteRenderer = mainChildView->GetRemoteRenderer();
2178  if (!remoteRenderer) {
2179    return;
2180  }
2181  remoteRenderer->SendResume();
2182
2183  // Now that the compositor has resumed, we also try to mark the browser window
2184  // docshell active to restart any animations. This does not affect docshells
2185  // for browsers in other processes, but browser UI code should be managing
2186  // their active state appropriately.
2187  if (!mWidgetListener) {
2188    return;
2189  }
2190  PresShell* presShell = mWidgetListener->GetPresShell();
2191  if (!presShell) {
2192    return;
2193  }
2194  nsPresContext* presContext = presShell->GetPresContext();
2195  if (!presContext) {
2196    return;
2197  }
2198  BrowsingContext* bc = presContext->Document()->GetBrowsingContext();
2199  if (!bc) {
2200    return;
2201  }
2202  Unused << bc->SetExplicitActive(ExplicitActiveStatus::Active);
2203}
2204
2205void nsCocoaWindow::SetMenuBar(RefPtr<nsMenuBarX>&& aMenuBar) {
2206  if (!mWindow) {
2207    mMenuBar = nullptr;
2208    return;
2209  }
2210  mMenuBar = std::move(aMenuBar);
2211
2212  // Only paint for active windows, or paint the hidden window menu bar if no
2213  // other menu bar has been painted yet so that some reasonable menu bar is
2214  // displayed when the app starts up.
2215  if (mMenuBar && ((!gSomeMenuBarPainted && nsMenuUtilsX::GetHiddenWindowMenuBar() == mMenuBar) ||
2216                   [mWindow isMainWindow]))
2217    mMenuBar->Paint();
2218}
2219
2220void nsCocoaWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
2221  if (!mWindow) return;
2222
2223  if (mPopupContentView) {
2224    return mPopupContentView->SetFocus(aRaise, aCallerType);
2225  }
2226
2227  if (aRaise == Raise::Yes && ([mWindow isVisible] || [mWindow isMiniaturized])) {
2228    if ([mWindow isMiniaturized]) {
2229      [mWindow deminiaturize:nil];
2230    }
2231
2232    [mWindow makeKeyAndOrderFront:nil];
2233    SendSetZLevelEvent();
2234  }
2235}
2236
2237LayoutDeviceIntPoint nsCocoaWindow::WidgetToScreenOffset() {
2238  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2239
2240  return nsCocoaUtils::CocoaRectToGeckoRectDevPix(GetClientCocoaRect(), BackingScaleFactor())
2241      .TopLeft();
2242
2243  NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0));
2244}
2245
2246LayoutDeviceIntPoint nsCocoaWindow::GetClientOffset() {
2247  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2248
2249  LayoutDeviceIntRect clientRect = GetClientBounds();
2250
2251  return clientRect.TopLeft() - mBounds.TopLeft();
2252
2253  NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0));
2254}
2255
2256LayoutDeviceIntSize nsCocoaWindow::ClientToWindowSize(const LayoutDeviceIntSize& aClientSize) {
2257  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2258
2259  if (!mWindow) return LayoutDeviceIntSize(0, 0);
2260
2261  CGFloat backingScale = BackingScaleFactor();
2262  LayoutDeviceIntRect r(0, 0, aClientSize.width, aClientSize.height);
2263  NSRect rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, backingScale);
2264
2265  // Our caller expects the inflated rect for windows *with separate titlebars*,
2266  // i.e. for windows where [mWindow drawsContentsIntoWindowFrame] is NO.
2267  //
2268  // So we call frameRectForContentRect on NSWindow here, instead of mWindow, so
2269  // that we don't run into our override if this window is a window that draws
2270  // its content into the titlebar.
2271  //
2272  // This is the same thing the windows widget does, but we probably should fix
2273  // that, see bug 1445738.
2274  NSUInteger styleMask = [mWindow styleMask];
2275  styleMask &= ~NSWindowStyleMaskFullSizeContentView;
2276  NSRect inflatedRect = [NSWindow frameRectForContentRect:rect styleMask:styleMask];
2277  r = nsCocoaUtils::CocoaRectToGeckoRectDevPix(inflatedRect, backingScale);
2278  return r.Size();
2279
2280  NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntSize(0, 0));
2281}
2282
2283nsMenuBarX* nsCocoaWindow::GetMenuBar() { return mMenuBar; }
2284
2285void nsCocoaWindow::CaptureRollupEvents(nsIRollupListener* aListener, bool aDoCapture) {
2286  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2287
2288  gRollupListener = nullptr;
2289
2290  if (aDoCapture) {
2291    if (![NSApp isActive]) {
2292      // We need to capture mouse event if we aren't
2293      // the active application. We only set this up when needed
2294      // because they cause spurious mouse event after crash
2295      // and gdb sessions. See bug 699538.
2296      nsToolkit::GetToolkit()->MonitorAllProcessMouseEvents();
2297    }
2298    gRollupListener = aListener;
2299
2300    // Sometimes more than one popup window can be visible at the same time
2301    // (e.g. nested non-native context menus, or the test case (attachment
2302    // 276885) for bmo bug 392389, which displays a non-native combo-box in a
2303    // non-native popup window).  In these cases the "active" popup window should
2304    // be the topmost -- the (nested) context menu the mouse is currently over,
2305    // or the combo-box's drop-down list (when it's displayed).  But (among
2306    // windows that have the same "level") OS X makes topmost the window that
2307    // last received a mouse-down event, which may be incorrect (in the combo-
2308    // box case, it makes topmost the window containing the combo-box).  So
2309    // here we fiddle with a non-native popup window's level to make sure the
2310    // "active" one is always above any other non-native popup windows that
2311    // may be visible.
2312    if (mWindow && (mWindowType == eWindowType_popup)) SetPopupWindowLevel();
2313  } else {
2314    nsToolkit::GetToolkit()->StopMonitoringAllProcessMouseEvents();
2315
2316    // XXXndeakin this doesn't make sense.
2317    // Why is the new window assumed to be a modal panel?
2318    if (mWindow && (mWindowType == eWindowType_popup)) [mWindow setLevel:NSModalPanelWindowLevel];
2319  }
2320
2321  NS_OBJC_END_TRY_IGNORE_BLOCK;
2322}
2323
2324nsresult nsCocoaWindow::GetAttention(int32_t aCycleCount) {
2325  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2326
2327  [NSApp requestUserAttention:NSInformationalRequest];
2328  return NS_OK;
2329
2330  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2331}
2332
2333bool nsCocoaWindow::HasPendingInputEvent() { return nsChildView::DoHasPendingInputEvent(); }
2334
2335void nsCocoaWindow::SetWindowShadowStyle(StyleWindowShadow aStyle) {
2336  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2337
2338  mShadowStyle = aStyle;
2339
2340  if (!mWindow || mWindowType != eWindowType_popup) {
2341    return;
2342  }
2343
2344  mWindow.shadowStyle = mShadowStyle;
2345  [mWindow setUseMenuStyle:mShadowStyle == StyleWindowShadow::Menu];
2346  [mWindow setHasShadow:aStyle != StyleWindowShadow::None];
2347
2348  NS_OBJC_END_TRY_IGNORE_BLOCK;
2349}
2350
2351void nsCocoaWindow::SetWindowOpacity(float aOpacity) {
2352  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2353
2354  if (!mWindow) {
2355    return;
2356  }
2357
2358  [mWindow setAlphaValue:(CGFloat)aOpacity];
2359
2360  NS_OBJC_END_TRY_IGNORE_BLOCK;
2361}
2362
2363void nsCocoaWindow::SetColorScheme(const Maybe<ColorScheme>& aScheme) {
2364  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2365
2366  if (!mWindow) {
2367    return;
2368  }
2369
2370  mWindow.appearance = aScheme ? NSAppearanceForColorScheme(*aScheme) : nil;
2371
2372  NS_OBJC_END_TRY_IGNORE_BLOCK;
2373}
2374
2375static inline CGAffineTransform GfxMatrixToCGAffineTransform(const gfx::Matrix& m) {
2376  CGAffineTransform t;
2377  t.a = m._11;
2378  t.b = m._12;
2379  t.c = m._21;
2380  t.d = m._22;
2381  t.tx = m._31;
2382  t.ty = m._32;
2383  return t;
2384}
2385
2386void nsCocoaWindow::SetWindowTransform(const gfx::Matrix& aTransform) {
2387  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2388
2389  if (!mWindow) {
2390    return;
2391  }
2392
2393  // Calling CGSSetWindowTransform when the window is not visible results in
2394  // misplacing the window into doubled x,y coordinates (see bug 1448132).
2395  if (![mWindow isVisible] || NSIsEmptyRect([mWindow frame])) {
2396    return;
2397  }
2398
2399  if (StaticPrefs::widget_window_transforms_disabled()) {
2400    // CGSSetWindowTransform is a private API. In case calling it causes
2401    // problems either now or in the future, we'll want to have an easy kill
2402    // switch. So we allow disabling it with a pref.
2403    return;
2404  }
2405
2406  gfx::Matrix transform = aTransform;
2407
2408  // aTransform is a transform that should be applied to the window relative
2409  // to its regular position: If aTransform._31 is 100, then we want the
2410  // window to be displayed 100 pixels to the right of its regular position.
2411  // The transform that CGSSetWindowTransform accepts has a different meaning:
2412  // It's used to answer the question "For the screen pixel at x,y (with the
2413  // origin at the top left), what pixel in the window's buffer (again with
2414  // origin top left) should be displayed at that position?"
2415  // In the example above, this means that we need to call
2416  // CGSSetWindowTransform with a horizontal translation of -windowPos.x - 100.
2417  // So we need to invert the transform and adjust it by the window's position.
2418  if (!transform.Invert()) {
2419    // Treat non-invertible transforms as the identity transform.
2420    transform = gfx::Matrix();
2421  }
2422
2423  bool isIdentity = transform.IsIdentity();
2424  if (isIdentity && mWindowTransformIsIdentity) {
2425    return;
2426  }
2427
2428  transform.PreTranslate(-mBounds.x, -mBounds.y);
2429
2430  // Snap translations to device pixels, to match what we do for CSS transforms
2431  // and because the window server rounds down instead of to nearest.
2432  if (!transform.HasNonTranslation() && transform.HasNonIntegerTranslation()) {
2433    auto snappedTranslation = gfx::IntPoint::Round(transform.GetTranslation());
2434    transform = gfx::Matrix::Translation(snappedTranslation.x, snappedTranslation.y);
2435  }
2436
2437  // We also need to account for the backing scale factor: aTransform is given
2438  // in device pixels, but CGSSetWindowTransform works with logical display
2439  // pixels.
2440  CGFloat backingScale = BackingScaleFactor();
2441  transform.PreScale(backingScale, backingScale);
2442  transform.PostScale(1 / backingScale, 1 / backingScale);
2443
2444  CGSConnection cid = _CGSDefaultConnection();
2445  CGSSetWindowTransform(cid, [mWindow windowNumber], GfxMatrixToCGAffineTransform(transform));
2446
2447  mWindowTransformIsIdentity = isIdentity;
2448
2449  NS_OBJC_END_TRY_IGNORE_BLOCK;
2450}
2451
2452void nsCocoaWindow::SetWindowMouseTransparent(bool aIsTransparent) {
2453  MOZ_ASSERT(mWindowType == eWindowType_popup, "This should only be called on popup windows.");
2454  if (aIsTransparent) {
2455    [mWindow setIgnoresMouseEvents:YES];
2456  } else {
2457    [mWindow setIgnoresMouseEvents:NO];
2458  }
2459}
2460
2461void nsCocoaWindow::SetShowsToolbarButton(bool aShow) {
2462  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2463
2464  if (mWindow) [mWindow setShowsToolbarButton:aShow];
2465
2466  NS_OBJC_END_TRY_IGNORE_BLOCK;
2467}
2468
2469void nsCocoaWindow::SetSupportsNativeFullscreen(bool aSupportsNativeFullscreen) {
2470  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2471
2472  if (mWindow) {
2473    // This determines whether we tell cocoa that the window supports native
2474    // full screen. If we do so, and another window is in native full screen,
2475    // this window will also appear in native full screen. We generally only
2476    // want to do this for primary application windows. We'll set the
2477    // relevant macnativefullscreen attribute on those, which will lead to us
2478    // being called with aSupportsNativeFullscreen set to `true` here.
2479    NSWindowCollectionBehavior newBehavior = [mWindow collectionBehavior];
2480    if (aSupportsNativeFullscreen) {
2481      newBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
2482    } else {
2483      newBehavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
2484    }
2485    [mWindow setCollectionBehavior:newBehavior];
2486  }
2487
2488  NS_OBJC_END_TRY_IGNORE_BLOCK;
2489}
2490
2491void nsCocoaWindow::SetWindowAnimationType(nsIWidget::WindowAnimationType aType) {
2492  mAnimationType = aType;
2493}
2494
2495void nsCocoaWindow::SetDrawsTitle(bool aDrawTitle) {
2496  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2497
2498  if (![mWindow drawsContentsIntoWindowFrame]) {
2499    // If we don't draw into the window frame, we always want to display window
2500    // titles.
2501    [mWindow setWantsTitleDrawn:YES];
2502  } else {
2503    [mWindow setWantsTitleDrawn:aDrawTitle];
2504  }
2505
2506  NS_OBJC_END_TRY_IGNORE_BLOCK;
2507}
2508
2509nsresult nsCocoaWindow::SetNonClientMargins(LayoutDeviceIntMargin& margins) {
2510  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2511
2512  SetDrawsInTitlebar(margins.top == 0);
2513
2514  return NS_OK;
2515
2516  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2517}
2518
2519void nsCocoaWindow::SetDrawsInTitlebar(bool aState) {
2520  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2521
2522  if (mWindow) [mWindow setDrawsContentsIntoWindowFrame:aState];
2523
2524  NS_OBJC_END_TRY_IGNORE_BLOCK;
2525}
2526
2527NS_IMETHODIMP nsCocoaWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
2528                                                        NativeMouseMessage aNativeMessage,
2529                                                        MouseButton aButton,
2530                                                        nsIWidget::Modifiers aModifierFlags,
2531                                                        nsIObserver* aObserver) {
2532  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2533
2534  AutoObserverNotifier notifier(aObserver, "mouseevent");
2535  if (mPopupContentView) {
2536    return mPopupContentView->SynthesizeNativeMouseEvent(aPoint, aNativeMessage, aButton,
2537                                                         aModifierFlags, nullptr);
2538  }
2539
2540  return NS_OK;
2541
2542  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2543}
2544
2545NS_IMETHODIMP nsCocoaWindow::SynthesizeNativeMouseScrollEvent(
2546    LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX, double aDeltaY,
2547    double aDeltaZ, uint32_t aModifierFlags, uint32_t aAdditionalFlags, nsIObserver* aObserver) {
2548  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2549
2550  AutoObserverNotifier notifier(aObserver, "mousescrollevent");
2551  if (mPopupContentView) {
2552    // Pass nullptr as the observer so that the AutoObserverNotification in
2553    // nsChildView::SynthesizeNativeMouseScrollEvent will be ignored.
2554    return mPopupContentView->SynthesizeNativeMouseScrollEvent(aPoint, aNativeMessage, aDeltaX,
2555                                                               aDeltaY, aDeltaZ, aModifierFlags,
2556                                                               aAdditionalFlags, nullptr);
2557  }
2558
2559  return NS_OK;
2560
2561  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2562}
2563
2564void nsCocoaWindow::LockAspectRatio(bool aShouldLock) {
2565  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2566
2567  if (aShouldLock) {
2568    [mWindow setContentAspectRatio:mWindow.frame.size];
2569    mAspectRatioLocked = true;
2570  } else {
2571    // According to https://developer.apple.com/documentation/appkit/nswindow/1419507-aspectratio,
2572    // aspect ratios and resize increments are mutually exclusive, and the accepted way of
2573    // cancelling an established aspect ratio is to set the resize increments to 1.0, 1.0
2574    [mWindow setResizeIncrements:NSMakeSize(1.0, 1.0)];
2575    mAspectRatioLocked = false;
2576  }
2577
2578  NS_OBJC_END_TRY_IGNORE_BLOCK;
2579}
2580
2581void nsCocoaWindow::UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) {
2582  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2583
2584  if (mPopupContentView) {
2585    return mPopupContentView->UpdateThemeGeometries(aThemeGeometries);
2586  }
2587
2588  NS_OBJC_END_TRY_IGNORE_BLOCK;
2589}
2590
2591void nsCocoaWindow::SetPopupWindowLevel() {
2592  if (!mWindow) return;
2593
2594  // Floating popups are at the floating level and hide when the window is
2595  // deactivated.
2596  if (mPopupLevel == ePopupLevelFloating) {
2597    [mWindow setLevel:NSFloatingWindowLevel];
2598    [mWindow setHidesOnDeactivate:YES];
2599  } else {
2600    // Otherwise, this is a top-level or parent popup. Parent popups always
2601    // appear just above their parent and essentially ignore the level.
2602    [mWindow setLevel:NSPopUpMenuWindowLevel];
2603    [mWindow setHidesOnDeactivate:NO];
2604  }
2605}
2606
2607void nsCocoaWindow::SetInputContext(const InputContext& aContext,
2608                                    const InputContextAction& aAction) {
2609  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2610
2611  mInputContext = aContext;
2612
2613  NS_OBJC_END_TRY_IGNORE_BLOCK;
2614}
2615
2616bool nsCocoaWindow::GetEditCommands(NativeKeyBindingsType aType, const WidgetKeyboardEvent& aEvent,
2617                                    nsTArray<CommandInt>& aCommands) {
2618  // Validate the arguments.
2619  if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
2620    return false;
2621  }
2622
2623  NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
2624  // When the keyboard event is fired from this widget, it must mean that no web content has focus
2625  // because any web contents should be on `nsChildView`.  And in any locales, the system UI is
2626  // always horizontal layout.  So, let's pass `Nothing()` for the writing mode here, it won't be
2627  // treated as in a vertical content.
2628  keyBindings->GetEditCommands(aEvent, Nothing(), aCommands);
2629  return true;
2630}
2631
2632bool nsCocoaWindow::AsyncPanZoomEnabled() const {
2633  if (mPopupContentView) {
2634    return mPopupContentView->AsyncPanZoomEnabled();
2635  }
2636  return nsBaseWidget::AsyncPanZoomEnabled();
2637}
2638
2639bool nsCocoaWindow::StartAsyncAutoscroll(const ScreenPoint& aAnchorLocation,
2640                                         const ScrollableLayerGuid& aGuid) {
2641  if (mPopupContentView) {
2642    return mPopupContentView->StartAsyncAutoscroll(aAnchorLocation, aGuid);
2643  }
2644  return nsBaseWidget::StartAsyncAutoscroll(aAnchorLocation, aGuid);
2645}
2646
2647void nsCocoaWindow::StopAsyncAutoscroll(const ScrollableLayerGuid& aGuid) {
2648  if (mPopupContentView) {
2649    mPopupContentView->StopAsyncAutoscroll(aGuid);
2650    return;
2651  }
2652  nsBaseWidget::StopAsyncAutoscroll(aGuid);
2653}
2654
2655already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
2656  nsCOMPtr<nsIWidget> window = new nsCocoaWindow();
2657  return window.forget();
2658}
2659
2660already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
2661  nsCOMPtr<nsIWidget> window = new nsChildView();
2662  return window.forget();
2663}
2664
2665@implementation WindowDelegate
2666
2667// We try to find a gecko menu bar to paint. If one does not exist, just paint
2668// the application menu by itself so that a window doesn't have some other
2669// window's menu bar.
2670+ (void)paintMenubarForWindow:(NSWindow*)aWindow {
2671  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2672
2673  // make sure we only act on windows that have this kind of
2674  // object as a delegate
2675  id windowDelegate = [aWindow delegate];
2676  if ([windowDelegate class] != [self class]) return;
2677
2678  nsCocoaWindow* geckoWidget = [windowDelegate geckoWidget];
2679  NS_ASSERTION(geckoWidget, "Window delegate not returning a gecko widget!");
2680
2681  nsMenuBarX* geckoMenuBar = geckoWidget->GetMenuBar();
2682  if (geckoMenuBar) {
2683    geckoMenuBar->Paint();
2684  } else {
2685    // sometimes we don't have a native application menu early in launching
2686    if (!sApplicationMenu) return;
2687
2688    NSMenu* mainMenu = [NSApp mainMenu];
2689    NS_ASSERTION([mainMenu numberOfItems] > 0,
2690                 "Main menu does not have any items, something is terribly wrong!");
2691
2692    // Create a new menu bar.
2693    // We create a GeckoNSMenu because all menu bar NSMenu objects should use that subclass for
2694    // key handling reasons.
2695    GeckoNSMenu* newMenuBar = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar"];
2696
2697    // move the application menu from the existing menu bar to the new one
2698    NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain];
2699    [mainMenu removeItemAtIndex:0];
2700    [newMenuBar insertItem:firstMenuItem atIndex:0];
2701    [firstMenuItem release];
2702
2703    // set our new menu bar as the main menu
2704    [NSApp setMainMenu:newMenuBar];
2705    [newMenuBar release];
2706  }
2707
2708  NS_OBJC_END_TRY_IGNORE_BLOCK;
2709}
2710
2711- (id)initWithGeckoWindow:(nsCocoaWindow*)geckoWind {
2712  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2713
2714  [super init];
2715  mGeckoWindow = geckoWind;
2716  mToplevelActiveState = false;
2717  mHasEverBeenZoomed = false;
2718  return self;
2719
2720  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
2721}
2722
2723- (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)proposedFrameSize {
2724  RollUpPopups();
2725
2726  return proposedFrameSize;
2727}
2728
2729- (void)windowDidResize:(NSNotification*)aNotification {
2730  BaseWindow* window = [aNotification object];
2731  [window updateTrackingArea];
2732
2733  if (!mGeckoWindow) return;
2734
2735  // Resizing might have changed our zoom state.
2736  mGeckoWindow->DispatchSizeModeEvent();
2737  mGeckoWindow->ReportSizeEvent();
2738}
2739
2740- (void)windowDidChangeScreen:(NSNotification*)aNotification {
2741  if (!mGeckoWindow) return;
2742
2743  // Because of Cocoa's peculiar treatment of zero-size windows (see comments
2744  // at GetBackingScaleFactor() above), we sometimes have a situation where
2745  // our concept of backing scale (based on the screen where the zero-sized
2746  // window is positioned) differs from Cocoa's idea (always based on the
2747  // Retina screen, AFAICT, even when an external non-Retina screen is the
2748  // primary display).
2749  //
2750  // As a result, if the window was created with zero size on an external
2751  // display, but then made visible on the (secondary) Retina screen, we
2752  // will *not* get a windowDidChangeBackingProperties notification for it.
2753  // This leads to an incorrect GetDefaultScale(), and widget coordinate
2754  // confusion, as per bug 853252.
2755  //
2756  // To work around this, we check for a backing scale mismatch when we
2757  // receive a windowDidChangeScreen notification, as we will receive this
2758  // even if Cocoa was already treating the zero-size window as having
2759  // Retina backing scale.
2760  NSWindow* window = (NSWindow*)[aNotification object];
2761  if ([window respondsToSelector:@selector(backingScaleFactor)]) {
2762    if (GetBackingScaleFactor(window) != mGeckoWindow->BackingScaleFactor()) {
2763      mGeckoWindow->BackingScaleFactorChanged();
2764    }
2765  }
2766
2767  mGeckoWindow->ReportMoveEvent();
2768}
2769
2770- (void)windowWillEnterFullScreen:(NSNotification*)notification {
2771  if (!mGeckoWindow) {
2772    return;
2773  }
2774
2775  mGeckoWindow->WillEnterFullScreen(true);
2776}
2777
2778// Lion's full screen mode will bypass our internal fullscreen tracking, so
2779// we need to catch it when we transition and call our own methods, which in
2780// turn will fire "fullscreen" events.
2781- (void)windowDidEnterFullScreen:(NSNotification*)notification {
2782  if (!mGeckoWindow) {
2783    return;
2784  }
2785
2786  mGeckoWindow->EnteredFullScreen(true);
2787
2788  // On Yosemite, the NSThemeFrame class has two new properties --
2789  // titlebarView (an NSTitlebarView object) and titlebarContainerView (an
2790  // NSTitlebarContainerView object).  These are used to display the titlebar
2791  // in fullscreen mode.  In Safari they're not transparent.  But in Firefox
2792  // for some reason they are, which causes bug 1069658.  The following code
2793  // works around this Apple bug or design flaw.
2794  NSWindow* window = (NSWindow*)[notification object];
2795  NSView* frameView = [[window contentView] superview];
2796  NSView* titlebarView = nil;
2797  NSView* titlebarContainerView = nil;
2798  if ([frameView respondsToSelector:@selector(titlebarView)]) {
2799    titlebarView = [frameView titlebarView];
2800  }
2801  if ([frameView respondsToSelector:@selector(titlebarContainerView)]) {
2802    titlebarContainerView = [frameView titlebarContainerView];
2803  }
2804  if ([titlebarView respondsToSelector:@selector(setTransparent:)]) {
2805    [titlebarView setTransparent:NO];
2806  }
2807  if ([titlebarContainerView respondsToSelector:@selector(setTransparent:)]) {
2808    [titlebarContainerView setTransparent:NO];
2809  }
2810}
2811
2812- (void)windowWillExitFullScreen:(NSNotification*)notification {
2813  if (!mGeckoWindow) {
2814    return;
2815  }
2816
2817  mGeckoWindow->WillEnterFullScreen(false);
2818}
2819
2820- (void)windowDidExitFullScreen:(NSNotification*)notification {
2821  if (!mGeckoWindow) {
2822    return;
2823  }
2824
2825  mGeckoWindow->EnteredFullScreen(false);
2826}
2827
2828- (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
2829  if (!mGeckoWindow) {
2830    return;
2831  }
2832
2833  mGeckoWindow->EnteredFullScreen(false);
2834}
2835
2836- (void)windowDidFailToExitFullScreen:(NSWindow*)window {
2837  if (!mGeckoWindow) {
2838    return;
2839  }
2840
2841  mGeckoWindow->EnteredFullScreen(true);
2842}
2843
2844- (void)windowDidBecomeMain:(NSNotification*)aNotification {
2845  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2846
2847  RollUpPopups();
2848  ChildViewMouseTracker::ReEvaluateMouseEnterState();
2849
2850  // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
2851  // app modally. If one of those is up then we want it to retain its menu bar.
2852  if ([NSApp _isRunningAppModal]) return;
2853  NSWindow* window = [aNotification object];
2854  if (window) [WindowDelegate paintMenubarForWindow:window];
2855
2856  if ([window isKindOfClass:[ToolbarWindow class]]) {
2857    [(ToolbarWindow*)window windowMainStateChanged];
2858  }
2859
2860  NS_OBJC_END_TRY_IGNORE_BLOCK;
2861}
2862
2863- (void)windowDidResignMain:(NSNotification*)aNotification {
2864  RollUpPopups();
2865  ChildViewMouseTracker::ReEvaluateMouseEnterState();
2866
2867  // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
2868  // app modally. If one of those is up then we want it to retain its menu bar.
2869  if ([NSApp _isRunningAppModal]) return;
2870  RefPtr<nsMenuBarX> hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
2871  if (hiddenWindowMenuBar) {
2872    // printf("painting hidden window menu bar due to window losing main status\n");
2873    hiddenWindowMenuBar->Paint();
2874  }
2875
2876  NSWindow* window = [aNotification object];
2877  if ([window isKindOfClass:[ToolbarWindow class]]) {
2878    [(ToolbarWindow*)window windowMainStateChanged];
2879  }
2880}
2881
2882- (void)windowDidBecomeKey:(NSNotification*)aNotification {
2883  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2884
2885  RollUpPopups();
2886  ChildViewMouseTracker::ReEvaluateMouseEnterState();
2887
2888  NSWindow* window = [aNotification object];
2889  if ([window isSheet]) [WindowDelegate paintMenubarForWindow:window];
2890
2891  nsChildView* mainChildView =
2892      static_cast<nsChildView*>([[(BaseWindow*)window mainChildView] widget]);
2893  if (mainChildView) {
2894    if (mainChildView->GetInputContext().IsPasswordEditor()) {
2895      TextInputHandler::EnableSecureEventInput();
2896    } else {
2897      TextInputHandler::EnsureSecureEventInputDisabled();
2898    }
2899  }
2900
2901  NS_OBJC_END_TRY_IGNORE_BLOCK;
2902}
2903
2904- (void)windowDidResignKey:(NSNotification*)aNotification {
2905  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2906
2907  RollUpPopups();
2908  ChildViewMouseTracker::ReEvaluateMouseEnterState();
2909
2910  // If a sheet just resigned key then we should paint the menu bar
2911  // for whatever window is now main.
2912  NSWindow* window = [aNotification object];
2913  if ([window isSheet]) [WindowDelegate paintMenubarForWindow:[NSApp mainWindow]];
2914
2915  TextInputHandler::EnsureSecureEventInputDisabled();
2916
2917  NS_OBJC_END_TRY_IGNORE_BLOCK;
2918}
2919
2920- (void)windowWillMove:(NSNotification*)aNotification {
2921  RollUpPopups();
2922}
2923
2924- (void)windowDidMove:(NSNotification*)aNotification {
2925  if (mGeckoWindow) mGeckoWindow->ReportMoveEvent();
2926}
2927
2928- (BOOL)windowShouldClose:(id)sender {
2929  nsIWidgetListener* listener = mGeckoWindow ? mGeckoWindow->GetWidgetListener() : nullptr;
2930  if (listener) listener->RequestWindowClose(mGeckoWindow);
2931  return NO;  // gecko will do it
2932}
2933
2934- (void)windowWillClose:(NSNotification*)aNotification {
2935  RollUpPopups();
2936}
2937
2938- (void)windowWillMiniaturize:(NSNotification*)aNotification {
2939  RollUpPopups();
2940}
2941
2942- (void)windowDidMiniaturize:(NSNotification*)aNotification {
2943  if (mGeckoWindow) mGeckoWindow->DispatchSizeModeEvent();
2944}
2945
2946- (void)windowDidDeminiaturize:(NSNotification*)aNotification {
2947  if (mGeckoWindow) mGeckoWindow->DispatchSizeModeEvent();
2948}
2949
2950- (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)proposedFrame {
2951  if (!mHasEverBeenZoomed && [window isZoomed]) return NO;  // See bug 429954.
2952
2953  mHasEverBeenZoomed = YES;
2954  return YES;
2955}
2956
2957- (NSRect)window:(NSWindow*)window willPositionSheet:(NSWindow*)sheet usingRect:(NSRect)rect {
2958  if ([window isKindOfClass:[ToolbarWindow class]]) {
2959    rect.origin.y = [(ToolbarWindow*)window sheetAttachmentPosition];
2960  }
2961  return rect;
2962}
2963
2964#ifdef MOZ_THUNDERBIRD
2965- (void)didEndSheet:(NSWindow*)sheet returnCode:(int)returnCode contextInfo:(void*)contextInfo {
2966  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2967
2968  // Note: 'contextInfo' (if it is set) is the window that is the parent of
2969  // the sheet.  The value of contextInfo is determined in
2970  // nsCocoaWindow::Show().  If it's set, 'contextInfo' is always the top-
2971  // level window, not another sheet itself.  But 'contextInfo' is nil if
2972  // our parent window is also a sheet -- in that case we shouldn't send
2973  // the top-level window any activate events (because it's our parent
2974  // window that needs to get these events, not the top-level window).
2975  [TopLevelWindowData deactivateInWindow:sheet];
2976  [sheet orderOut:self];
2977  if (contextInfo) [TopLevelWindowData activateInWindow:(NSWindow*)contextInfo];
2978
2979  NS_OBJC_END_TRY_ABORT_BLOCK;
2980}
2981#endif
2982
2983- (void)windowDidChangeBackingProperties:(NSNotification*)aNotification {
2984  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2985
2986  NSWindow* window = (NSWindow*)[aNotification object];
2987
2988  if ([window respondsToSelector:@selector(backingScaleFactor)]) {
2989    CGFloat oldFactor =
2990        [[[aNotification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue];
2991    if ([window backingScaleFactor] != oldFactor) {
2992      mGeckoWindow->BackingScaleFactorChanged();
2993    }
2994  }
2995
2996  NS_OBJC_END_TRY_IGNORE_BLOCK;
2997}
2998
2999// This method is on NSWindowDelegate starting with 10.9
3000- (void)windowDidChangeOcclusionState:(NSNotification*)aNotification {
3001  if (mGeckoWindow) {
3002    mGeckoWindow->DispatchOcclusionEvent();
3003  }
3004}
3005
3006- (nsCocoaWindow*)geckoWidget {
3007  return mGeckoWindow;
3008}
3009
3010- (bool)toplevelActiveState {
3011  return mToplevelActiveState;
3012}
3013
3014- (void)sendToplevelActivateEvents {
3015  if (!mToplevelActiveState && mGeckoWindow) {
3016    nsIWidgetListener* listener = mGeckoWindow->GetWidgetListener();
3017    if (listener) {
3018      listener->WindowActivated();
3019    }
3020    mToplevelActiveState = true;
3021  }
3022}
3023
3024- (void)sendToplevelDeactivateEvents {
3025  if (mToplevelActiveState && mGeckoWindow) {
3026    nsIWidgetListener* listener = mGeckoWindow->GetWidgetListener();
3027    if (listener) {
3028      listener->WindowDeactivated();
3029    }
3030    mToplevelActiveState = false;
3031  }
3032}
3033
3034@end
3035
3036@interface NSView (FrameViewMethodSwizzling)
3037- (NSPoint)FrameView__closeButtonOrigin;
3038- (CGFloat)FrameView__titlebarHeight;
3039@end
3040
3041@implementation NSView (FrameViewMethodSwizzling)
3042
3043- (NSPoint)FrameView__closeButtonOrigin {
3044  if (![self.window isKindOfClass:[ToolbarWindow class]]) {
3045    return self.FrameView__closeButtonOrigin;
3046  }
3047  ToolbarWindow* win = (ToolbarWindow*)[self window];
3048  if (win.drawsContentsIntoWindowFrame && !(win.styleMask & NSWindowStyleMaskFullScreen) &&
3049      (win.styleMask & NSWindowStyleMaskTitled)) {
3050    const NSRect buttonsRect = win.windowButtonsRect;
3051    if (NSIsEmptyRect(buttonsRect)) {
3052      // Empty rect. Let's hide the buttons.
3053      // Position is in non-flipped window coordinates. Using frame's height
3054      // for the vertical coordinate will move the buttons above the window,
3055      // making them invisible.
3056      return NSMakePoint(buttonsRect.origin.x, win.frame.size.height);
3057    } else if (win.windowTitlebarLayoutDirection == NSUserInterfaceLayoutDirectionRightToLeft) {
3058      // We're in RTL mode, which means that the close button is the rightmost
3059      // button of the three window buttons. and buttonsRect.origin is the
3060      // bottom left corner of the green (zoom) button. The close button is 40px
3061      // to the right of the zoom button. This is confirmed to be the same on
3062      // all macOS versions between 10.12 - 12.0.
3063      return NSMakePoint(buttonsRect.origin.x + 40.0f, buttonsRect.origin.y);
3064    }
3065    return buttonsRect.origin;
3066  }
3067  return self.FrameView__closeButtonOrigin;
3068}
3069
3070- (CGFloat)FrameView__titlebarHeight {
3071  CGFloat height = [self FrameView__titlebarHeight];
3072  if ([[self window] isKindOfClass:[ToolbarWindow class]]) {
3073    // Make sure that the titlebar height includes our shifted buttons.
3074    // The following coordinates are in window space, with the origin being at the bottom left
3075    // corner of the window.
3076    ToolbarWindow* win = (ToolbarWindow*)[self window];
3077    CGFloat frameHeight = [self frame].size.height;
3078    CGFloat windowButtonY = frameHeight;
3079    if (!NSIsEmptyRect(win.windowButtonsRect) && win.drawsContentsIntoWindowFrame &&
3080        !(win.styleMask & NSWindowStyleMaskFullScreen) &&
3081        (win.styleMask & NSWindowStyleMaskTitled)) {
3082      windowButtonY = win.windowButtonsRect.origin.y;
3083    }
3084    height = std::max(height, frameHeight - windowButtonY);
3085  }
3086  return height;
3087}
3088
3089@end
3090
3091static NSMutableSet* gSwizzledFrameViewClasses = nil;
3092
3093@interface NSWindow (PrivateSetNeedsDisplayInRectMethod)
3094- (void)_setNeedsDisplayInRect:(NSRect)aRect;
3095@end
3096
3097@interface NSView (NSVisualEffectViewSetMaskImage)
3098- (void)setMaskImage:(NSImage*)image;
3099@end
3100
3101@interface BaseWindow (Private)
3102- (void)removeTrackingArea;
3103- (void)cursorUpdated:(NSEvent*)aEvent;
3104- (void)reflowTitlebarElements;
3105@end
3106
3107@implementation BaseWindow
3108
3109// The frame of a window is implemented using undocumented NSView subclasses.
3110// We offset the window buttons by overriding the method _closeButtonOrigin on
3111// these frame view classes. The class which is
3112// used for a window is determined in the window's frameViewClassForStyleMask:
3113// method, so this is where we make sure that we have swizzled the method on
3114// all encountered classes.
3115+ (Class)frameViewClassForStyleMask:(NSUInteger)styleMask {
3116  Class frameViewClass = [super frameViewClassForStyleMask:styleMask];
3117
3118  if (!gSwizzledFrameViewClasses) {
3119    gSwizzledFrameViewClasses = [[NSMutableSet setWithCapacity:3] retain];
3120    if (!gSwizzledFrameViewClasses) {
3121      return frameViewClass;
3122    }
3123  }
3124
3125  static IMP our_closeButtonOrigin =
3126      class_getMethodImplementation([NSView class], @selector(FrameView__closeButtonOrigin));
3127  static IMP our_titlebarHeight =
3128      class_getMethodImplementation([NSView class], @selector(FrameView__titlebarHeight));
3129
3130  if (![gSwizzledFrameViewClasses containsObject:frameViewClass]) {
3131    // Either of these methods might be implemented in both a subclass of
3132    // NSFrameView and one of its own subclasses.  Which means that if we
3133    // aren't careful we might end up swizzling the same method twice.
3134    // Since method swizzling involves swapping pointers, this would break
3135    // things.
3136    IMP _closeButtonOrigin =
3137        class_getMethodImplementation(frameViewClass, @selector(_closeButtonOrigin));
3138    if (_closeButtonOrigin && _closeButtonOrigin != our_closeButtonOrigin) {
3139      nsToolkit::SwizzleMethods(frameViewClass, @selector(_closeButtonOrigin),
3140                                @selector(FrameView__closeButtonOrigin));
3141    }
3142
3143    // Override _titlebarHeight so that the floating titlebar doesn't clip the bottom of the
3144    // window buttons which we move down with our override of _closeButtonOrigin.
3145    IMP _titlebarHeight = class_getMethodImplementation(frameViewClass, @selector(_titlebarHeight));
3146    if (_titlebarHeight && _titlebarHeight != our_titlebarHeight) {
3147      nsToolkit::SwizzleMethods(frameViewClass, @selector(_titlebarHeight),
3148                                @selector(FrameView__titlebarHeight));
3149    }
3150
3151    [gSwizzledFrameViewClasses addObject:frameViewClass];
3152  }
3153
3154  return frameViewClass;
3155}
3156
3157- (id)initWithContentRect:(NSRect)aContentRect
3158                styleMask:(NSUInteger)aStyle
3159                  backing:(NSBackingStoreType)aBufferingType
3160                    defer:(BOOL)aFlag {
3161  mDrawsIntoWindowFrame = NO;
3162  [super initWithContentRect:aContentRect styleMask:aStyle backing:aBufferingType defer:aFlag];
3163  mState = nil;
3164  mDisabledNeedsDisplay = NO;
3165  mTrackingArea = nil;
3166  mDirtyRect = NSZeroRect;
3167  mBeingShown = NO;
3168  mDrawTitle = NO;
3169  mUseMenuStyle = NO;
3170  mTouchBar = nil;
3171  mIsAnimationSuppressed = NO;
3172  [self updateTrackingArea];
3173
3174  return self;
3175}
3176
3177// Returns an autoreleased NSImage.
3178static NSImage* GetMenuMaskImage() {
3179  CGFloat radius = 4.0f;
3180  NSEdgeInsets insets = {5, 5, 5, 5};
3181  NSSize maskSize = {12, 12};
3182  NSImage* maskImage = [NSImage imageWithSize:maskSize
3183                                      flipped:YES
3184                               drawingHandler:^BOOL(NSRect dstRect) {
3185                                 NSBezierPath* path =
3186                                     [NSBezierPath bezierPathWithRoundedRect:dstRect
3187                                                                     xRadius:radius
3188                                                                     yRadius:radius];
3189                                 [[NSColor colorWithDeviceWhite:1.0 alpha:1.0] set];
3190                                 [path fill];
3191                                 return YES;
3192                               }];
3193  [maskImage setCapInsets:insets];
3194  return maskImage;
3195}
3196
3197- (void)swapOutChildViewWrapper:(NSView*)aNewWrapper {
3198  [aNewWrapper setFrame:[[self contentView] frame]];
3199  NSView* childView = [[self mainChildView] retain];
3200  [childView removeFromSuperview];
3201  [aNewWrapper addSubview:childView];
3202  [childView release];
3203  [super setContentView:aNewWrapper];
3204}
3205
3206- (void)setUseMenuStyle:(BOOL)aValue {
3207  if (aValue && !mUseMenuStyle) {
3208    // Turn on rounded corner masking.
3209    NSView* effectView = VibrancyManager::CreateEffectView(VibrancyType::MENU, YES);
3210    [effectView setMaskImage:GetMenuMaskImage()];
3211    [self swapOutChildViewWrapper:effectView];
3212    [effectView release];
3213  } else if (mUseMenuStyle && !aValue) {
3214    // Turn off rounded corner masking.
3215    NSView* wrapper = [[NSView alloc] initWithFrame:NSZeroRect];
3216    [wrapper setWantsLayer:YES];
3217    [self swapOutChildViewWrapper:wrapper];
3218    [wrapper release];
3219  }
3220  mUseMenuStyle = aValue;
3221}
3222
3223- (NSTouchBar*)makeTouchBar {
3224  mTouchBar = [[nsTouchBar alloc] init];
3225  if (mTouchBar) {
3226    sTouchBarIsInitialized = YES;
3227  }
3228  return mTouchBar;
3229}
3230
3231- (void)setBeingShown:(BOOL)aValue {
3232  mBeingShown = aValue;
3233}
3234
3235- (BOOL)isBeingShown {
3236  return mBeingShown;
3237}
3238
3239- (BOOL)isVisibleOrBeingShown {
3240  return [super isVisible] || mBeingShown;
3241}
3242
3243- (void)setIsAnimationSuppressed:(BOOL)aValue {
3244  mIsAnimationSuppressed = aValue;
3245}
3246
3247- (BOOL)isAnimationSuppressed {
3248  return mIsAnimationSuppressed;
3249}
3250
3251- (void)disableSetNeedsDisplay {
3252  mDisabledNeedsDisplay = YES;
3253}
3254
3255- (void)enableSetNeedsDisplay {
3256  mDisabledNeedsDisplay = NO;
3257}
3258
3259- (void)dealloc {
3260  [mTouchBar release];
3261  [self removeTrackingArea];
3262  ChildViewMouseTracker::OnDestroyWindow(self);
3263  [super dealloc];
3264}
3265
3266static const NSString* kStateTitleKey = @"title";
3267static const NSString* kStateDrawsContentsIntoWindowFrameKey = @"drawsContentsIntoWindowFrame";
3268static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
3269static const NSString* kStateCollectionBehavior = @"collectionBehavior";
3270static const NSString* kStateWantsTitleDrawn = @"wantsTitleDrawn";
3271
3272- (void)importState:(NSDictionary*)aState {
3273  if (NSString* title = [aState objectForKey:kStateTitleKey]) {
3274    [self setTitle:title];
3275  }
3276  [self setDrawsContentsIntoWindowFrame:[[aState objectForKey:kStateDrawsContentsIntoWindowFrameKey]
3277                                            boolValue]];
3278  [self setShowsToolbarButton:[[aState objectForKey:kStateShowsToolbarButton] boolValue]];
3279  [self setCollectionBehavior:[[aState objectForKey:kStateCollectionBehavior] unsignedIntValue]];
3280  [self setWantsTitleDrawn:[[aState objectForKey:kStateWantsTitleDrawn] boolValue]];
3281}
3282
3283- (NSMutableDictionary*)exportState {
3284  NSMutableDictionary* state = [NSMutableDictionary dictionaryWithCapacity:10];
3285  if (NSString* title = [self title]) {
3286    [state setObject:title forKey:kStateTitleKey];
3287  }
3288  [state setObject:[NSNumber numberWithBool:[self drawsContentsIntoWindowFrame]]
3289            forKey:kStateDrawsContentsIntoWindowFrameKey];
3290  [state setObject:[NSNumber numberWithBool:[self showsToolbarButton]]
3291            forKey:kStateShowsToolbarButton];
3292  [state setObject:[NSNumber numberWithUnsignedInt:[self collectionBehavior]]
3293            forKey:kStateCollectionBehavior];
3294  [state setObject:[NSNumber numberWithBool:[self wantsTitleDrawn]] forKey:kStateWantsTitleDrawn];
3295  return state;
3296}
3297
3298- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState {
3299  bool changed = (aState != mDrawsIntoWindowFrame);
3300  mDrawsIntoWindowFrame = aState;
3301  if (changed) {
3302    [self reflowTitlebarElements];
3303  }
3304}
3305
3306- (BOOL)drawsContentsIntoWindowFrame {
3307  return mDrawsIntoWindowFrame;
3308}
3309
3310- (NSRect)childViewRectForFrameRect:(NSRect)aFrameRect {
3311  if (mDrawsIntoWindowFrame) {
3312    return aFrameRect;
3313  }
3314  NSUInteger styleMask = [self styleMask];
3315  styleMask &= ~NSWindowStyleMaskFullSizeContentView;
3316  return [NSWindow contentRectForFrameRect:aFrameRect styleMask:styleMask];
3317}
3318
3319- (NSRect)frameRectForChildViewRect:(NSRect)aChildViewRect {
3320  if (mDrawsIntoWindowFrame) {
3321    return aChildViewRect;
3322  }
3323  NSUInteger styleMask = [self styleMask];
3324  styleMask &= ~NSWindowStyleMaskFullSizeContentView;
3325  return [NSWindow frameRectForContentRect:aChildViewRect styleMask:styleMask];
3326}
3327
3328- (NSTimeInterval)animationResizeTime:(NSRect)newFrame {
3329  if (mIsAnimationSuppressed) {
3330    // Should not animate the initial session-restore size change
3331    return 0.0;
3332  }
3333
3334  return [super animationResizeTime:newFrame];
3335}
3336
3337- (void)setWantsTitleDrawn:(BOOL)aDrawTitle {
3338  mDrawTitle = aDrawTitle;
3339  [self setTitleVisibility:mDrawTitle ? NSWindowTitleVisible : NSWindowTitleHidden];
3340}
3341
3342- (BOOL)wantsTitleDrawn {
3343  return mDrawTitle;
3344}
3345
3346- (NSView*)trackingAreaView {
3347  NSView* contentView = [self contentView];
3348  return [contentView superview] ? [contentView superview] : contentView;
3349}
3350
3351- (NSArray<NSView*>*)contentViewContents {
3352  return [[[[self contentView] subviews] copy] autorelease];
3353}
3354
3355- (ChildView*)mainChildView {
3356  NSView* contentView = [self contentView];
3357  NSView* lastView = [[contentView subviews] lastObject];
3358  if ([lastView isKindOfClass:[ChildView class]]) {
3359    return (ChildView*)lastView;
3360  }
3361  return nil;
3362}
3363
3364- (void)removeTrackingArea {
3365  if (mTrackingArea) {
3366    [[self trackingAreaView] removeTrackingArea:mTrackingArea];
3367    [mTrackingArea release];
3368    mTrackingArea = nil;
3369  }
3370}
3371
3372- (void)updateTrackingArea {
3373  [self removeTrackingArea];
3374
3375  NSView* view = [self trackingAreaView];
3376  const NSTrackingAreaOptions options =
3377      NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways;
3378  mTrackingArea = [[NSTrackingArea alloc] initWithRect:[view bounds]
3379                                               options:options
3380                                                 owner:self
3381                                              userInfo:nil];
3382  [view addTrackingArea:mTrackingArea];
3383}
3384
3385- (void)mouseEntered:(NSEvent*)aEvent {
3386  ChildViewMouseTracker::MouseEnteredWindow(aEvent);
3387}
3388
3389- (void)mouseExited:(NSEvent*)aEvent {
3390  ChildViewMouseTracker::MouseExitedWindow(aEvent);
3391}
3392
3393- (void)mouseMoved:(NSEvent*)aEvent {
3394  ChildViewMouseTracker::MouseMoved(aEvent);
3395}
3396
3397- (void)cursorUpdated:(NSEvent*)aEvent {
3398  // Nothing to do here, but NSTrackingArea wants us to implement this method.
3399}
3400
3401- (void)_setNeedsDisplayInRect:(NSRect)aRect {
3402  // Prevent unnecessary invalidations due to moving NSViews (e.g. for plugins)
3403  if (!mDisabledNeedsDisplay) {
3404    // This method is only called by Cocoa, so when we're here, we know that
3405    // it's available and don't need to check whether our superclass responds
3406    // to the selector.
3407    [super _setNeedsDisplayInRect:aRect];
3408    mDirtyRect = NSUnionRect(mDirtyRect, aRect);
3409  }
3410}
3411
3412- (NSRect)getAndResetNativeDirtyRect {
3413  NSRect dirtyRect = mDirtyRect;
3414  mDirtyRect = NSZeroRect;
3415  return dirtyRect;
3416}
3417
3418// Possibly move the titlebar buttons.
3419- (void)reflowTitlebarElements {
3420  NSView* frameView = [[self contentView] superview];
3421  if ([frameView respondsToSelector:@selector(_tileTitlebarAndRedisplay:)]) {
3422    [frameView _tileTitlebarAndRedisplay:NO];
3423  }
3424}
3425
3426- (BOOL)respondsToSelector:(SEL)aSelector {
3427  // Claim the window doesn't respond to this so that the system
3428  // doesn't steal keyboard equivalents for it. Bug 613710.
3429  if (aSelector == @selector(cancelOperation:)) {
3430    return NO;
3431  }
3432
3433  return [super respondsToSelector:aSelector];
3434}
3435
3436- (void)doCommandBySelector:(SEL)aSelector {
3437  // We override this so that it won't beep if it can't act.
3438  // We want to control the beeping for missing or disabled
3439  // commands ourselves.
3440  [self tryToPerform:aSelector with:nil];
3441}
3442
3443- (id)accessibilityAttributeValue:(NSString*)attribute {
3444  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3445
3446  id retval = [super accessibilityAttributeValue:attribute];
3447
3448  // The following works around a problem with Text-to-Speech on OS X 10.7.
3449  // See bug 674612 for more info.
3450  //
3451  // When accessibility is off, AXUIElementCopyAttributeValue(), when called
3452  // on an AXApplication object to get its AXFocusedUIElement attribute,
3453  // always returns an AXWindow object (the actual browser window -- never a
3454  // mozAccessible object).  This also happens with accessibility turned on,
3455  // if no other object in the browser window has yet been focused.  But if
3456  // the browser window has a title bar (as it currently always does), the
3457  // AXWindow object will always have four "accessible" children, one of which
3458  // is an AXStaticText object (the title bar's "title"; the other three are
3459  // the close, minimize and zoom buttons).  This means that (for complicated
3460  // reasons, for which see bug 674612) Text-to-Speech on OS X 10.7 will often
3461  // "speak" the window title, no matter what text is selected, or even if no
3462  // text at all is selected.  (This always happens when accessibility is off.
3463  // It doesn't happen in Firefox releases because Apple has (on OS X 10.7)
3464  // special-cased the handling of apps whose CFBundleIdentifier is
3465  // org.mozilla.firefox.)
3466  //
3467  // We work around this problem by only returning AXChildren that are
3468  // mozAccessible object or are one of the titlebar's buttons (which
3469  // instantiate subclasses of NSButtonCell).
3470  if ([retval isKindOfClass:[NSArray class]] && [attribute isEqualToString:@"AXChildren"]) {
3471    NSMutableArray* holder = [NSMutableArray arrayWithCapacity:10];
3472    [holder addObjectsFromArray:(NSArray*)retval];
3473    NSUInteger count = [holder count];
3474    for (NSInteger i = count - 1; i >= 0; --i) {
3475      id item = [holder objectAtIndex:i];
3476      // Remove anything from holder that isn't one of the titlebar's buttons
3477      // (which instantiate subclasses of NSButtonCell) or a mozAccessible
3478      // object (or one of its subclasses).
3479      if (![item isKindOfClass:[NSButtonCell class]] &&
3480          ![item respondsToSelector:@selector(hasRepresentedView)]) {
3481        [holder removeObjectAtIndex:i];
3482      }
3483    }
3484    retval = [NSArray arrayWithArray:holder];
3485  }
3486
3487  return retval;
3488
3489  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
3490}
3491
3492- (void)releaseJSObjects {
3493  [mTouchBar releaseJSObjects];
3494}
3495
3496@end
3497
3498@interface NSView (NSThemeFrame)
3499- (void)_drawTitleStringInClip:(NSRect)aRect;
3500- (void)_maskCorners:(NSUInteger)aFlags clipRect:(NSRect)aRect;
3501@end
3502
3503@implementation MOZTitlebarView
3504
3505- (instancetype)initWithFrame:(NSRect)aFrame {
3506  self = [super initWithFrame:aFrame];
3507
3508  self.material = NSVisualEffectMaterialTitlebar;
3509  self.blendingMode = NSVisualEffectBlendingModeWithinWindow;
3510
3511  // Add a separator line at the bottom of the titlebar. NSBoxSeparator isn't a perfect match for
3512  // a native titlebar separator, but it's better than nothing.
3513  // We really want the appearance that _NSTitlebarDecorationView creates with the help of CoreUI,
3514  // but there's no public API for that.
3515  NSBox* separatorLine = [[NSBox alloc] initWithFrame:NSMakeRect(0, 0, aFrame.size.width, 1)];
3516  separatorLine.autoresizingMask = NSViewWidthSizable | NSViewMaxYMargin;
3517  separatorLine.boxType = NSBoxSeparator;
3518  [self addSubview:separatorLine];
3519  [separatorLine release];
3520
3521  return self;
3522}
3523
3524- (BOOL)mouseDownCanMoveWindow {
3525  return YES;
3526}
3527
3528- (void)mouseUp:(NSEvent*)event {
3529  if ([event clickCount] == 2) {
3530    // Handle titlebar double click. We don't get the window's default behavior here because the
3531    // window uses NSWindowStyleMaskFullSizeContentView, and this view (the titlebar gradient view)
3532    // is technically part of the window "contents" (it's a subview of the content view).
3533    if (nsCocoaUtils::ShouldZoomOnTitlebarDoubleClick()) {
3534      [[self window] performZoom:nil];
3535    } else if (nsCocoaUtils::ShouldMinimizeOnTitlebarDoubleClick()) {
3536      [[self window] performMiniaturize:nil];
3537    }
3538  }
3539}
3540
3541@end
3542
3543@interface MOZTitlebarAccessoryView : NSView
3544@end
3545
3546@implementation MOZTitlebarAccessoryView : NSView
3547- (void)viewWillMoveToWindow:(NSWindow*)aWindow {
3548  if (aWindow) {
3549    // When entering full screen mode, titlebar accessory views are inserted
3550    // into a floating NSWindow which houses the window titlebar and toolbars.
3551    // In order to work around a drawing bug with titlebarAppearsTransparent
3552    // windows in full screen mode, disable titlebar separators for all
3553    // NSWindows that this view is used in, including the floating full screen
3554    // toolbar window. The drawing bug was filed as FB9056136. See bug 1700211
3555    // for more details.
3556#if !defined(MAC_OS_VERSION_11_0) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_VERSION_11_0
3557    if (nsCocoaFeatures::OnBigSurOrLater()) {
3558#else
3559    if (@available(macOS 11.0, *)) {
3560#endif
3561      aWindow.titlebarSeparatorStyle = NSTitlebarSeparatorStyleNone;
3562    }
3563  }
3564}
3565@end
3566
3567@implementation FullscreenTitlebarTracker
3568- (FullscreenTitlebarTracker*)init {
3569  [super init];
3570  self.view = [[[MOZTitlebarAccessoryView alloc] initWithFrame:NSZeroRect] autorelease];
3571  self.hidden = YES;
3572  return self;
3573}
3574@end
3575
3576// This class allows us to exercise control over the window's title bar. It is
3577// used for all windows with titlebars.
3578//
3579// ToolbarWindow supports two modes:
3580//  - drawsContentsIntoWindowFrame mode: In this mode, the Gecko ChildView is
3581//    sized to cover the entire window frame and manages titlebar drawing.
3582//  - separate titlebar mode, with support for unified toolbars: In this mode,
3583//    the Gecko ChildView does not extend into the titlebar. However, this
3584//    window's content view (which is the ChildView's superview) *does* extend
3585//    into the titlebar. Moreover, in this mode, we place a MOZTitlebarView
3586//    in the content view, as a sibling of the ChildView.
3587//
3588// The "separate titlebar mode" supports the "unified toolbar" look:
3589// If there's a toolbar right below the titlebar, the two can "connect" and
3590// form a single gradient without a separator line in between.
3591//
3592// The following mechanism communicates the height of the unified toolbar to
3593// the ToolbarWindow:
3594//
3595// 1) In the style sheet we set the toolbar's -moz-appearance to toolbar.
3596// 2) When the toolbar is visible and we paint the application chrome
3597//    window, the array that Gecko passes nsChildView::UpdateThemeGeometries
3598//    will contain an entry for the widget type StyleAppearance::Toolbar.
3599// 3) nsChildView::UpdateThemeGeometries passes the toolbar's height, plus the
3600//    titlebar height, to -[ToolbarWindow setUnifiedToolbarHeight:].
3601//
3602// The actual drawing of the gradient happens in two parts: The titlebar part
3603// (i.e. the top 22 pixels of the gradient) is drawn by the MOZTitlebarView,
3604// which is a subview of the window's content view and a sibling of the ChildView.
3605// The rest of the gradient is drawn by Gecko into the ChildView, as part of the
3606// -moz-appearance rendering of the toolbar.
3607@implementation ToolbarWindow
3608
3609- (id)initWithContentRect:(NSRect)aChildViewRect
3610                styleMask:(NSUInteger)aStyle
3611                  backing:(NSBackingStoreType)aBufferingType
3612                    defer:(BOOL)aFlag {
3613  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3614
3615  // We treat aChildViewRect as the rectangle that the window's main ChildView
3616  // should be sized to. Get the right frameRect for the requested child view
3617  // rect.
3618  NSRect frameRect = [NSWindow frameRectForContentRect:aChildViewRect styleMask:aStyle];
3619
3620  // Always size the content view to the full frame size of the window.
3621  // We do this even if we want this window to have a titlebar; in that case, the window's content
3622  // view covers the entire window but the ChildView inside it will only cover the content area. We
3623  // do this so that we can render the titlebar gradient manually, with a subview of our content
3624  // view that's positioned in the titlebar area. This lets us have a smooth connection between
3625  // titlebar and toolbar gradient in case the window has a "unified toolbar + titlebar" look.
3626  // Moreover, always using a full size content view lets us toggle the titlebar on and off without
3627  // changing the window's style mask (which would have other subtle effects, for example on
3628  // keyboard focus).
3629  aStyle |= NSWindowStyleMaskFullSizeContentView;
3630
3631  // -[NSWindow initWithContentRect:styleMask:backing:defer:] calls
3632  // [self frameRectForContentRect:styleMask:] to convert the supplied content
3633  // rect to the window's frame rect. We've overridden that method to be a
3634  // pass-through function. So, in order to get the intended frameRect, we need
3635  // to supply frameRect itself as the "content rect".
3636  NSRect contentRect = frameRect;
3637
3638  if ((self = [super initWithContentRect:contentRect
3639                               styleMask:aStyle
3640                                 backing:aBufferingType
3641                                   defer:aFlag])) {
3642    mTitlebarView = nil;
3643    mUnifiedToolbarHeight = 22.0f;
3644    mSheetAttachmentPosition = aChildViewRect.size.height;
3645    mWindowButtonsRect = NSZeroRect;
3646    mInitialTitlebarHeight = [self titlebarHeight];
3647
3648    [self setTitlebarAppearsTransparent:YES];
3649#if !defined(MAC_OS_VERSION_11_0) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_VERSION_11_0
3650    if (nsCocoaFeatures::OnBigSurOrLater()) {
3651#else
3652    if (@available(macOS 11.0, *)) {
3653#endif
3654      self.titlebarSeparatorStyle = NSTitlebarSeparatorStyleNone;
3655    }
3656    [self updateTitlebarView];
3657
3658    mFullscreenTitlebarTracker = [[FullscreenTitlebarTracker alloc] init];
3659    // revealAmount is an undocumented property of
3660    // NSTitlebarAccessoryViewController that updates whenever the menubar
3661    // slides down in fullscreen mode.
3662    [mFullscreenTitlebarTracker addObserver:self
3663                                 forKeyPath:@"revealAmount"
3664                                    options:NSKeyValueObservingOptionNew
3665                                    context:nil];
3666    // Adding this accessory view controller allows us to shift the toolbar down
3667    // when the user mouses to the top of the screen in fullscreen.
3668    [(NSWindow*)self addTitlebarAccessoryViewController:mFullscreenTitlebarTracker];
3669  }
3670  return self;
3671
3672  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
3673}
3674
3675- (void)observeValueForKeyPath:(NSString*)keyPath
3676                      ofObject:(id)object
3677                        change:(NSDictionary<NSKeyValueChangeKey, id>*)change
3678                       context:(void*)context {
3679  if ([keyPath isEqualToString:@"revealAmount"]) {
3680    [[self mainChildView] ensureNextCompositeIsAtomicWithMainThreadPaint];
3681    NSNumber* revealAmount = (change[NSKeyValueChangeNewKey]);
3682    [self updateTitlebarShownAmount:[revealAmount doubleValue]];
3683  } else {
3684    [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
3685  }
3686}
3687
3688static bool ScreenHasNotch(nsCocoaWindow* aGeckoWindow) {
3689  if (@available(macOS 12.0, *)) {
3690    nsCOMPtr<nsIScreen> widgetScreen = aGeckoWindow->GetWidgetScreen();
3691    NSScreen* cocoaScreen = ScreenHelperCocoa::CocoaScreenForScreen(widgetScreen);
3692    return cocoaScreen.safeAreaInsets.top != 0.0f;
3693  }
3694  return false;
3695}
3696
3697static bool ShouldShiftByMenubarHeightInFullscreen(nsCocoaWindow* aWindow) {
3698  switch (StaticPrefs::widget_macos_shift_by_menubar_on_fullscreen()) {
3699    case 0:
3700      return false;
3701    case 1:
3702      return true;
3703    default:
3704      break;
3705  }
3706  // TODO: On notch-less macbooks, this creates extra space when the
3707  // "automatically show and hide the menubar on fullscreen" option is unchecked
3708  // (default checked). We tried to detect that in bug 1737831 but it wasn't
3709  // reliable enough, see the regressions from that bug. For now, stick to the
3710  // good behavior for default configurations (that is, shift by menubar height
3711  // on notch-less macbooks, and don't for devices that have a notch). This will
3712  // need refinement in the future.
3713  return !ScreenHasNotch(aWindow);
3714}
3715
3716- (void)updateTitlebarShownAmount:(CGFloat)aShownAmount {
3717  NSInteger styleMask = [self styleMask];
3718  if (!(styleMask & NSWindowStyleMaskFullScreen)) {
3719    // We are not interested in the size of the titlebar unless we are in
3720    // fullscreen.
3721    return;
3722  }
3723
3724  // [NSApp mainMenu] menuBarHeight] returns one of two values: the full height
3725  // if the menubar is shown or is in the process of being shown, and 0
3726  // otherwise. Since we are multiplying the menubar height by aShownAmount, we
3727  // always want the full height.
3728  CGFloat menuBarHeight = NSApp.mainMenu.menuBarHeight;
3729  if (menuBarHeight > 0.0f) {
3730    mMenuBarHeight = menuBarHeight;
3731  }
3732  if ([[self delegate] isKindOfClass:[WindowDelegate class]]) {
3733    WindowDelegate* windowDelegate = (WindowDelegate*)[self delegate];
3734    nsCocoaWindow* geckoWindow = [windowDelegate geckoWidget];
3735    if (!geckoWindow) {
3736      return;
3737    }
3738
3739    if (nsIWidgetListener* listener = geckoWindow->GetWidgetListener()) {
3740      // Use the titlebar height cached in our frame rather than
3741      // [ToolbarWindow titlebarHeight]. titlebarHeight returns 0 when we're in
3742      // fullscreen.
3743      CGFloat shiftByPixels = mInitialTitlebarHeight * aShownAmount;
3744      if (ShouldShiftByMenubarHeightInFullscreen(geckoWindow)) {
3745        shiftByPixels += mMenuBarHeight * aShownAmount;
3746      }
3747      // Use mozilla::DesktopToLayoutDeviceScale rather than the
3748      // DesktopToLayoutDeviceScale in nsCocoaWindow. The latter accounts for
3749      // screen DPI. We don't want that because the revealAmount property
3750      // already accounts for it, so we'd be compounding DPI scales > 1.
3751      mozilla::DesktopCoord coord =
3752          LayoutDeviceCoord(shiftByPixels) / mozilla::DesktopToLayoutDeviceScale();
3753
3754      listener->MacFullscreenMenubarOverlapChanged(coord);
3755    }
3756  }
3757}
3758
3759- (void)dealloc {
3760  [mTitlebarView release];
3761  [mFullscreenTitlebarTracker removeObserver:self forKeyPath:@"revealAmount"];
3762  [mFullscreenTitlebarTracker removeFromParentViewController];
3763  [mFullscreenTitlebarTracker release];
3764
3765  [super dealloc];
3766}
3767
3768- (NSArray<NSView*>*)contentViewContents {
3769  NSMutableArray<NSView*>* contents = [[[self contentView] subviews] mutableCopy];
3770  if (mTitlebarView) {
3771    // Do not include the titlebar gradient view in the returned array.
3772    [contents removeObject:mTitlebarView];
3773  }
3774  return [contents autorelease];
3775}
3776
3777- (void)updateTitlebarView {
3778  BOOL needTitlebarView = ![self drawsContentsIntoWindowFrame] || mUnifiedToolbarHeight > 0;
3779  if (needTitlebarView && !mTitlebarView) {
3780    mTitlebarView = [[MOZTitlebarView alloc] initWithFrame:[self unifiedToolbarRect]];
3781    mTitlebarView.autoresizingMask = NSViewWidthSizable | NSViewMinYMargin;
3782    [self.contentView addSubview:mTitlebarView positioned:NSWindowBelow relativeTo:nil];
3783  } else if (needTitlebarView && mTitlebarView) {
3784    mTitlebarView.frame = [self unifiedToolbarRect];
3785  } else if (!needTitlebarView && mTitlebarView) {
3786    [mTitlebarView removeFromSuperview];
3787    [mTitlebarView release];
3788    mTitlebarView = nil;
3789  }
3790}
3791
3792- (void)windowMainStateChanged {
3793  [self setTitlebarNeedsDisplay];
3794  [[self mainChildView] ensureNextCompositeIsAtomicWithMainThreadPaint];
3795}
3796
3797- (void)setTitlebarNeedsDisplay {
3798  [mTitlebarView setNeedsDisplay:YES];
3799}
3800
3801- (NSRect)titlebarRect {
3802  CGFloat titlebarHeight = [self titlebarHeight];
3803  return NSMakeRect(0, [self frame].size.height - titlebarHeight, [self frame].size.width,
3804                    titlebarHeight);
3805}
3806
3807// In window contentView coordinates (origin bottom left)
3808- (NSRect)unifiedToolbarRect {
3809  return NSMakeRect(0, [self frame].size.height - mUnifiedToolbarHeight, [self frame].size.width,
3810                    mUnifiedToolbarHeight);
3811}
3812
3813// Returns the unified height of titlebar + toolbar.
3814- (CGFloat)unifiedToolbarHeight {
3815  return mUnifiedToolbarHeight;
3816}
3817
3818- (CGFloat)titlebarHeight {
3819  // We use the original content rect here, not what we return from
3820  // [self contentRectForFrameRect:], because that would give us a
3821  // titlebarHeight of zero.
3822  NSRect frameRect = [self frame];
3823  NSUInteger styleMask = [self styleMask];
3824  styleMask &= ~NSWindowStyleMaskFullSizeContentView;
3825  NSRect originalContentRect = [NSWindow contentRectForFrameRect:frameRect styleMask:styleMask];
3826  return NSMaxY(frameRect) - NSMaxY(originalContentRect);
3827}
3828
3829// Stores the complete height of titlebar + toolbar.
3830- (void)setUnifiedToolbarHeight:(CGFloat)aHeight {
3831  if (aHeight == mUnifiedToolbarHeight) return;
3832
3833  mUnifiedToolbarHeight = aHeight;
3834
3835  [self updateTitlebarView];
3836}
3837
3838// Extending the content area into the title bar works by resizing the
3839// mainChildView so that it covers the titlebar.
3840- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState {
3841  BOOL stateChanged = ([self drawsContentsIntoWindowFrame] != aState);
3842  [super setDrawsContentsIntoWindowFrame:aState];
3843  if (stateChanged && [[self delegate] isKindOfClass:[WindowDelegate class]]) {
3844    // Here we extend / shrink our mainChildView. We do that by firing a resize
3845    // event which will cause the ChildView to be resized to the rect returned
3846    // by nsCocoaWindow::GetClientBounds. GetClientBounds bases its return
3847    // value on what we return from drawsContentsIntoWindowFrame.
3848    WindowDelegate* windowDelegate = (WindowDelegate*)[self delegate];
3849    nsCocoaWindow* geckoWindow = [windowDelegate geckoWidget];
3850    if (geckoWindow) {
3851      // Re-layout our contents.
3852      geckoWindow->ReportSizeEvent();
3853    }
3854
3855    // Resizing the content area causes a reflow which would send a synthesized
3856    // mousemove event to the old mouse position relative to the top left
3857    // corner of the content area. But the mouse has shifted relative to the
3858    // content area, so that event would have wrong position information. So
3859    // we'll send a mouse move event with the correct new position.
3860    ChildViewMouseTracker::ResendLastMouseMoveEvent();
3861  }
3862
3863  [self updateTitlebarView];
3864}
3865
3866- (void)setWantsTitleDrawn:(BOOL)aDrawTitle {
3867  [super setWantsTitleDrawn:aDrawTitle];
3868  [self setTitlebarNeedsDisplay];
3869}
3870
3871- (void)setSheetAttachmentPosition:(CGFloat)aY {
3872  mSheetAttachmentPosition = aY;
3873}
3874
3875- (CGFloat)sheetAttachmentPosition {
3876  return mSheetAttachmentPosition;
3877}
3878
3879- (void)placeWindowButtons:(NSRect)aRect {
3880  if (!NSEqualRects(mWindowButtonsRect, aRect)) {
3881    mWindowButtonsRect = aRect;
3882    [self reflowTitlebarElements];
3883  }
3884}
3885
3886- (NSRect)windowButtonsRect {
3887  return mWindowButtonsRect;
3888}
3889
3890// Returning YES here makes the setShowsToolbarButton method work even though
3891// the window doesn't contain an NSToolbar.
3892- (BOOL)_hasToolbar {
3893  return YES;
3894}
3895
3896// Dispatch a toolbar pill button clicked message to Gecko.
3897- (void)_toolbarPillButtonClicked:(id)sender {
3898  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3899
3900  RollUpPopups();
3901
3902  if ([[self delegate] isKindOfClass:[WindowDelegate class]]) {
3903    WindowDelegate* windowDelegate = (WindowDelegate*)[self delegate];
3904    nsCocoaWindow* geckoWindow = [windowDelegate geckoWidget];
3905    if (!geckoWindow) return;
3906
3907    nsIWidgetListener* listener = geckoWindow->GetWidgetListener();
3908    if (listener) listener->OSToolbarButtonPressed();
3909  }
3910
3911  NS_OBJC_END_TRY_IGNORE_BLOCK;
3912}
3913
3914// Retain and release "self" to avoid crashes when our widget (and its native
3915// window) is closed as a result of processing a key equivalent (e.g.
3916// Command+w or Command+q).  This workaround is only needed for a window
3917// that can become key.
3918- (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
3919  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3920
3921  NSWindow* nativeWindow = [self retain];
3922  BOOL retval = [super performKeyEquivalent:theEvent];
3923  [nativeWindow release];
3924  return retval;
3925
3926  NS_OBJC_END_TRY_BLOCK_RETURN(NO);
3927}
3928
3929- (void)sendEvent:(NSEvent*)anEvent {
3930  NSEventType type = [anEvent type];
3931
3932  switch (type) {
3933    case NSEventTypeScrollWheel:
3934    case NSEventTypeLeftMouseDown:
3935    case NSEventTypeLeftMouseUp:
3936    case NSEventTypeRightMouseDown:
3937    case NSEventTypeRightMouseUp:
3938    case NSEventTypeOtherMouseDown:
3939    case NSEventTypeOtherMouseUp:
3940    case NSEventTypeMouseMoved:
3941    case NSEventTypeLeftMouseDragged:
3942    case NSEventTypeRightMouseDragged:
3943    case NSEventTypeOtherMouseDragged: {
3944      // Drop all mouse events if a modal window has appeared above us.
3945      // This helps make us behave as if the OS were running a "real" modal
3946      // event loop.
3947      id delegate = [self delegate];
3948      if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
3949        nsCocoaWindow* widget = [(WindowDelegate*)delegate geckoWidget];
3950        if (widget) {
3951          if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window)) return;
3952          if (widget->HasModalDescendents()) return;
3953        }
3954      }
3955      break;
3956    }
3957    default:
3958      break;
3959  }
3960
3961  [super sendEvent:anEvent];
3962}
3963
3964@end
3965
3966@implementation PopupWindow
3967
3968- (id)initWithContentRect:(NSRect)contentRect
3969                styleMask:(NSUInteger)styleMask
3970                  backing:(NSBackingStoreType)bufferingType
3971                    defer:(BOOL)deferCreation {
3972  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3973
3974  mIsContextMenu = false;
3975  return [super initWithContentRect:contentRect
3976                          styleMask:styleMask
3977                            backing:bufferingType
3978                              defer:deferCreation];
3979
3980  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
3981}
3982
3983// Override the private API _backdropBleedAmount. This determines how much the
3984// desktop wallpaper contributes to the vibrancy backdrop.
3985// Return 0 in order to match what the system does for sheet windows and
3986// _NSPopoverWindows.
3987- (CGFloat)_backdropBleedAmount {
3988  return 0.0;
3989}
3990
3991// Override the private API shadowOptions.
3992// The constants below were found in AppKit's implementations of the
3993// shadowOptions method on the various window types.
3994static const NSUInteger kWindowShadowOptionsNoShadow = 0;
3995static const NSUInteger kWindowShadowOptionsMenu = 2;
3996static const NSUInteger kWindowShadowOptionsTooltipMojaveOrLater = 4;
3997- (NSUInteger)shadowOptions {
3998  if (!self.hasShadow) {
3999    return kWindowShadowOptionsNoShadow;
4000  }
4001
4002  switch (self.shadowStyle) {
4003    case StyleWindowShadow::None:
4004      return kWindowShadowOptionsNoShadow;
4005
4006    case StyleWindowShadow::Default:  // we treat "default" as "default panel"
4007    case StyleWindowShadow::Menu:
4008    case StyleWindowShadow::Sheet:
4009    case StyleWindowShadow::Cliprounded:  // this is a Windows-only value.
4010      return kWindowShadowOptionsMenu;
4011
4012    case StyleWindowShadow::Tooltip:
4013      if (nsCocoaFeatures::OnMojaveOrLater()) {
4014        return kWindowShadowOptionsTooltipMojaveOrLater;
4015      }
4016      return kWindowShadowOptionsMenu;
4017  }
4018}
4019
4020- (BOOL)isContextMenu {
4021  return mIsContextMenu;
4022}
4023
4024- (void)setIsContextMenu:(BOOL)flag {
4025  mIsContextMenu = flag;
4026}
4027
4028- (BOOL)canBecomeMainWindow {
4029  // This is overriden because the default is 'yes' when a titlebar is present.
4030  return NO;
4031}
4032
4033@end
4034
4035// According to Apple's docs on [NSWindow canBecomeKeyWindow] and [NSWindow
4036// canBecomeMainWindow], windows without a title bar or resize bar can't (by
4037// default) become key or main.  But if a window can't become key, it can't
4038// accept keyboard input (bmo bug 393250).  And it should also be possible for
4039// an otherwise "ordinary" window to become main.  We need to override these
4040// two methods to make this happen.
4041@implementation BorderlessWindow
4042
4043- (BOOL)canBecomeKeyWindow {
4044  return YES;
4045}
4046
4047- (void)sendEvent:(NSEvent*)anEvent {
4048  NSEventType type = [anEvent type];
4049
4050  switch (type) {
4051    case NSEventTypeScrollWheel:
4052    case NSEventTypeLeftMouseDown:
4053    case NSEventTypeLeftMouseUp:
4054    case NSEventTypeRightMouseDown:
4055    case NSEventTypeRightMouseUp:
4056    case NSEventTypeOtherMouseDown:
4057    case NSEventTypeOtherMouseUp:
4058    case NSEventTypeMouseMoved:
4059    case NSEventTypeLeftMouseDragged:
4060    case NSEventTypeRightMouseDragged:
4061    case NSEventTypeOtherMouseDragged: {
4062      // Drop all mouse events if a modal window has appeared above us.
4063      // This helps make us behave as if the OS were running a "real" modal
4064      // event loop.
4065      id delegate = [self delegate];
4066      if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
4067        nsCocoaWindow* widget = [(WindowDelegate*)delegate geckoWidget];
4068        if (widget) {
4069          if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window)) return;
4070          if (widget->HasModalDescendents()) return;
4071        }
4072      }
4073      break;
4074    }
4075    default:
4076      break;
4077  }
4078
4079  [super sendEvent:anEvent];
4080}
4081
4082// Apple's doc on this method says that the NSWindow class's default is not to
4083// become main if the window isn't "visible" -- so we should replicate that
4084// behavior here.  As best I can tell, the [NSWindow isVisible] method is an
4085// accurate test of what Apple means by "visibility".
4086- (BOOL)canBecomeMainWindow {
4087  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4088
4089  if (![self isVisible]) return NO;
4090  return YES;
4091
4092  NS_OBJC_END_TRY_BLOCK_RETURN(NO);
4093}
4094
4095// Retain and release "self" to avoid crashes when our widget (and its native
4096// window) is closed as a result of processing a key equivalent (e.g.
4097// Command+w or Command+q).  This workaround is only needed for a window
4098// that can become key.
4099- (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
4100  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4101
4102  NSWindow* nativeWindow = [self retain];
4103  BOOL retval = [super performKeyEquivalent:theEvent];
4104  [nativeWindow release];
4105  return retval;
4106
4107  NS_OBJC_END_TRY_BLOCK_RETURN(NO);
4108}
4109
4110@end
4111