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