Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
1 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsCocoaWindow.h"
8 #include "NativeKeyBindings.h"
9 #include "TextInputHandler.h"
10 #include "nsObjCExceptions.h"
11 #include "nsCOMPtr.h"
12 #include "nsWidgetsCID.h"
13 #include "nsIRollupListener.h"
14 #include "nsChildView.h"
15 #include "nsWindowMap.h"
16 #include "nsAppShell.h"
17 #include "nsIAppShellService.h"
18 #include "nsIBaseWindow.h"
19 #include "nsIInterfaceRequestorUtils.h"
20 #include "nsIXULWindow.h"
21 #include "nsToolkit.h"
22 #include "nsIDOMWindow.h"
23 #include "nsPIDOMWindow.h"
24 #include "nsIDOMElement.h"
25 #include "nsThreadUtils.h"
26 #include "nsMenuBarX.h"
27 #include "nsMenuUtilsX.h"
28 #include "nsStyleConsts.h"
29 #include "nsNativeThemeColors.h"
30 #include "nsChildView.h"
31 #include "nsCocoaFeatures.h"
32 #include "nsIScreenManager.h"
33 #include "nsIWidgetListener.h"
34 #include "nsIPresShell.h"
36 #include "gfxPlatform.h"
37 #include "qcms.h"
39 #include "mozilla/AutoRestore.h"
40 #include "mozilla/BasicEvents.h"
41 #include "mozilla/Preferences.h"
42 #include <algorithm>
44 namespace mozilla {
45 namespace layers {
46 class LayerManager;
47 }
48 }
49 using namespace mozilla::layers;
50 using namespace mozilla::widget;
51 using namespace mozilla;
53 // defined in nsAppShell.mm
54 extern nsCocoaAppModalWindowList *gCocoaAppModalWindowList;
56 int32_t gXULModalLevel = 0;
58 // In principle there should be only one app-modal window at any given time.
59 // But sometimes, despite our best efforts, another window appears above the
60 // current app-modal window. So we need to keep a linked list of app-modal
61 // windows. (A non-sheet window that appears above an app-modal window is
62 // also made app-modal.) See nsCocoaWindow::SetModal().
63 nsCocoaWindowList *gGeckoAppModalWindowList = NULL;
65 // defined in nsMenuBarX.mm
66 extern NSMenu* sApplicationMenu; // Application menu shared by all menubars
68 // defined in nsChildView.mm
69 extern BOOL gSomeMenuBarPainted;
71 extern "C" {
72 // CGSPrivate.h
73 typedef NSInteger CGSConnection;
74 typedef NSInteger CGSWindow;
75 typedef NSUInteger CGSWindowFilterRef;
76 extern CGSConnection _CGSDefaultConnection(void);
77 extern CGError CGSSetWindowShadowAndRimParameters(const CGSConnection cid, CGSWindow wid, float standardDeviation, float density, int offsetX, int offsetY, unsigned int flags);
78 extern CGError CGSSetWindowBackgroundBlurRadius(CGSConnection cid, CGSWindow wid, NSUInteger blur);
79 }
81 #define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1"
83 NS_IMPL_ISUPPORTS_INHERITED(nsCocoaWindow, Inherited, nsPIWidgetCocoa)
85 // A note on testing to see if your object is a sheet...
86 // |mWindowType == eWindowType_sheet| is true if your gecko nsIWidget is a sheet
87 // widget - whether or not the sheet is showing. |[mWindow isSheet]| will return
88 // true *only when the sheet is actually showing*. Choose your test wisely.
90 static void RollUpPopups()
91 {
92 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
93 NS_ENSURE_TRUE_VOID(rollupListener);
94 nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
95 if (!rollupWidget)
96 return;
97 rollupListener->Rollup(0, nullptr, nullptr);
98 }
100 nsCocoaWindow::nsCocoaWindow()
101 : mParent(nullptr)
102 , mWindow(nil)
103 , mDelegate(nil)
104 , mSheetWindowParent(nil)
105 , mPopupContentView(nil)
106 , mShadowStyle(NS_STYLE_WINDOW_SHADOW_DEFAULT)
107 , mBackingScaleFactor(0.0)
108 , mAnimationType(nsIWidget::eGenericWindowAnimation)
109 , mWindowMadeHere(false)
110 , mSheetNeedsShow(false)
111 , mFullScreen(false)
112 , mInFullScreenTransition(false)
113 , mModal(false)
114 , mUsesNativeFullScreen(false)
115 , mIsAnimationSuppressed(false)
116 , mInReportMoveEvent(false)
117 , mInResize(false)
118 , mNumModalDescendents(0)
119 {
121 }
123 void nsCocoaWindow::DestroyNativeWindow()
124 {
125 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
127 if (!mWindow)
128 return;
130 // We want to unhook the delegate here because we don't want events
131 // sent to it after this object has been destroyed.
132 [mWindow setDelegate:nil];
133 [mWindow close];
134 mWindow = nil;
135 [mDelegate autorelease];
137 NS_OBJC_END_TRY_ABORT_BLOCK;
138 }
140 nsCocoaWindow::~nsCocoaWindow()
141 {
142 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
144 // Notify the children that we're gone. Popup windows (e.g. tooltips) can
145 // have nsChildView children. 'kid' is an nsChildView object if and only if
146 // its 'type' is 'eWindowType_child' or 'eWindowType_plugin'.
147 // childView->ResetParent() can change our list of children while it's
148 // being iterated, so the way we iterate the list must allow for this.
149 for (nsIWidget* kid = mLastChild; kid;) {
150 nsWindowType kidType = kid->WindowType();
151 if (kidType == eWindowType_child || kidType == eWindowType_plugin) {
152 nsChildView* childView = static_cast<nsChildView*>(kid);
153 kid = kid->GetPrevSibling();
154 childView->ResetParent();
155 } else {
156 nsCocoaWindow* childWindow = static_cast<nsCocoaWindow*>(kid);
157 childWindow->mParent = nullptr;
158 kid = kid->GetPrevSibling();
159 }
160 }
162 if (mWindow && mWindowMadeHere) {
163 DestroyNativeWindow();
164 }
166 NS_IF_RELEASE(mPopupContentView);
168 // Deal with the possiblity that we're being destroyed while running modal.
169 if (mModal) {
170 NS_WARNING("Widget destroyed while running modal!");
171 --gXULModalLevel;
172 NS_ASSERTION(gXULModalLevel >= 0, "Wierdness setting modality!");
173 }
175 NS_OBJC_END_TRY_ABORT_BLOCK;
176 }
178 // Find the screen that overlaps aRect the most,
179 // if none are found default to the mainScreen.
180 static NSScreen *FindTargetScreenForRect(const nsIntRect& aRect)
181 {
182 NSScreen *targetScreen = [NSScreen mainScreen];
183 NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator];
184 int largestIntersectArea = 0;
185 while (NSScreen *screen = [screenEnum nextObject]) {
186 nsIntRect screenRect(nsCocoaUtils::CocoaRectToGeckoRect([screen visibleFrame]));
187 screenRect = screenRect.Intersect(aRect);
188 int area = screenRect.width * screenRect.height;
189 if (area > largestIntersectArea) {
190 largestIntersectArea = area;
191 targetScreen = screen;
192 }
193 }
194 return targetScreen;
195 }
197 // fits the rect to the screen that contains the largest area of it,
198 // or to aScreen if a screen is passed in
199 // NB: this operates with aRect in global display pixels
200 static void FitRectToVisibleAreaForScreen(nsIntRect &aRect, NSScreen *aScreen,
201 bool aUsesNativeFullScreen)
202 {
203 if (!aScreen) {
204 aScreen = FindTargetScreenForRect(aRect);
205 }
207 nsIntRect screenBounds(nsCocoaUtils::CocoaRectToGeckoRect([aScreen visibleFrame]));
209 if (aRect.width > screenBounds.width) {
210 aRect.width = screenBounds.width;
211 }
212 if (aRect.height > screenBounds.height) {
213 aRect.height = screenBounds.height;
214 }
216 if (aRect.x - screenBounds.x + aRect.width > screenBounds.width) {
217 aRect.x += screenBounds.width - (aRect.x - screenBounds.x + aRect.width);
218 }
219 if (aRect.y - screenBounds.y + aRect.height > screenBounds.height) {
220 aRect.y += screenBounds.height - (aRect.y - screenBounds.y + aRect.height);
221 }
223 // If the left/top edge of the window is off the screen in either direction,
224 // then set the window to start at the left/top edge of the screen.
225 if (aRect.x < screenBounds.x || aRect.x > (screenBounds.x + screenBounds.width)) {
226 aRect.x = screenBounds.x;
227 }
228 if (aRect.y < screenBounds.y || aRect.y > (screenBounds.y + screenBounds.height)) {
229 aRect.y = screenBounds.y;
230 }
232 // If aRect is filling the screen and the window supports native (Lion-style)
233 // fullscreen mode, reduce aRect's height and shift it down by 22 pixels
234 // (nominally the height of the menu bar or of a window's title bar). For
235 // some reason this works around bug 740923. Yes, it's a bodacious hack.
236 // But until we know more it will have to do.
237 if (aUsesNativeFullScreen && aRect.y == 0 && aRect.height == screenBounds.height) {
238 aRect.y = 22;
239 aRect.height -= 22;
240 }
241 }
243 // Some applications like Camino use native popup windows
244 // (native context menus, native tooltips)
245 static bool UseNativePopupWindows()
246 {
247 #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
248 return true;
249 #else
250 return false;
251 #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
252 }
254 // aRect here is specified in global display pixels
255 nsresult nsCocoaWindow::Create(nsIWidget *aParent,
256 nsNativeWidget aNativeParent,
257 const nsIntRect &aRect,
258 nsDeviceContext *aContext,
259 nsWidgetInitData *aInitData)
260 {
261 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
263 // Because the hidden window is created outside of an event loop,
264 // we have to provide an autorelease pool (see bug 559075).
265 nsAutoreleasePool localPool;
267 nsIntRect newBounds = aRect;
268 FitRectToVisibleAreaForScreen(newBounds, nullptr, mUsesNativeFullScreen);
270 // Set defaults which can be overriden from aInitData in BaseCreate
271 mWindowType = eWindowType_toplevel;
272 mBorderStyle = eBorderStyle_default;
274 // Ensure that the toolkit is created.
275 nsToolkit::GetToolkit();
277 // newBounds is still display (global screen) pixels at this point;
278 // fortunately, BaseCreate doesn't actually use it so we don't
279 // need to worry about trying to convert it to device pixels
280 // when we don't have a window (or dev context, perhaps) yet
281 Inherited::BaseCreate(aParent, newBounds, aContext, aInitData);
283 mParent = aParent;
285 // Applications that use native popups don't want us to create popup windows.
286 if ((mWindowType == eWindowType_popup) && UseNativePopupWindows())
287 return NS_OK;
289 nsresult rv =
290 CreateNativeWindow(nsCocoaUtils::GeckoRectToCocoaRect(newBounds),
291 mBorderStyle, false);
292 NS_ENSURE_SUCCESS(rv, rv);
294 if (mWindowType == eWindowType_popup) {
295 if (aInitData->mMouseTransparent) {
296 [mWindow setIgnoresMouseEvents:YES];
297 }
298 // now we can convert newBounds to device pixels for the window we created,
299 // as the child view expects a rect expressed in the dev pix of its parent
300 double scale = BackingScaleFactor();
301 newBounds.x *= scale;
302 newBounds.y *= scale;
303 newBounds.width *= scale;
304 newBounds.height *= scale;
305 return CreatePopupContentView(newBounds, aContext);
306 }
308 mIsAnimationSuppressed = aInitData->mIsAnimationSuppressed;
310 return NS_OK;
312 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
313 }
315 static unsigned int WindowMaskForBorderStyle(nsBorderStyle aBorderStyle)
316 {
317 bool allOrDefault = (aBorderStyle == eBorderStyle_all ||
318 aBorderStyle == eBorderStyle_default);
320 /* Apple's docs on NSWindow styles say that "a window's style mask should
321 * include NSTitledWindowMask if it includes any of the others [besides
322 * NSBorderlessWindowMask]". This implies that a borderless window
323 * shouldn't have any other styles than NSBorderlessWindowMask.
324 */
325 if (!allOrDefault && !(aBorderStyle & eBorderStyle_title))
326 return NSBorderlessWindowMask;
328 unsigned int mask = NSTitledWindowMask;
329 if (allOrDefault || aBorderStyle & eBorderStyle_close)
330 mask |= NSClosableWindowMask;
331 if (allOrDefault || aBorderStyle & eBorderStyle_minimize)
332 mask |= NSMiniaturizableWindowMask;
333 if (allOrDefault || aBorderStyle & eBorderStyle_resizeh)
334 mask |= NSResizableWindowMask;
336 return mask;
337 }
339 NS_IMETHODIMP nsCocoaWindow::ReparentNativeWidget(nsIWidget* aNewParent)
340 {
341 return NS_ERROR_NOT_IMPLEMENTED;
342 }
344 // If aRectIsFrameRect, aRect specifies the frame rect of the new window.
345 // Otherwise, aRect.x/y specify the position of the window's frame relative to
346 // the bottom of the menubar and aRect.width/height specify the size of the
347 // content rect.
348 nsresult nsCocoaWindow::CreateNativeWindow(const NSRect &aRect,
349 nsBorderStyle aBorderStyle,
350 bool aRectIsFrameRect)
351 {
352 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
354 // We default to NSBorderlessWindowMask, add features if needed.
355 unsigned int features = NSBorderlessWindowMask;
357 // Configure the window we will create based on the window type.
358 switch (mWindowType)
359 {
360 case eWindowType_invisible:
361 case eWindowType_child:
362 case eWindowType_plugin:
363 break;
364 case eWindowType_popup:
365 if (aBorderStyle != eBorderStyle_default && mBorderStyle & eBorderStyle_title) {
366 features |= NSTitledWindowMask;
367 if (aBorderStyle & eBorderStyle_close) {
368 features |= NSClosableWindowMask;
369 }
370 }
371 break;
372 case eWindowType_toplevel:
373 case eWindowType_dialog:
374 features = WindowMaskForBorderStyle(aBorderStyle);
375 break;
376 case eWindowType_sheet:
377 if (mParent->WindowType() != eWindowType_invisible &&
378 aBorderStyle & eBorderStyle_resizeh) {
379 features = NSResizableWindowMask;
380 }
381 else {
382 features = NSMiniaturizableWindowMask;
383 }
384 features |= NSTitledWindowMask;
385 break;
386 default:
387 NS_ERROR("Unhandled window type!");
388 return NS_ERROR_FAILURE;
389 }
391 NSRect contentRect;
393 if (aRectIsFrameRect) {
394 contentRect = [NSWindow contentRectForFrameRect:aRect styleMask:features];
395 } else {
396 /*
397 * We pass a content area rect to initialize the native Cocoa window. The
398 * content rect we give is the same size as the size we're given by gecko.
399 * The origin we're given for non-popup windows is moved down by the height
400 * of the menu bar so that an origin of (0,100) from gecko puts the window
401 * 100 pixels below the top of the available desktop area. We also move the
402 * origin down by the height of a title bar if it exists. This is so the
403 * origin that gecko gives us for the top-left of the window turns out to
404 * be the top-left of the window we create. This is how it was done in
405 * Carbon. If it ought to be different we'll probably need to look at all
406 * the callers.
407 *
408 * Note: This means that if you put a secondary screen on top of your main
409 * screen and open a window in the top screen, it'll be incorrectly shifted
410 * down by the height of the menu bar. Same thing would happen in Carbon.
411 *
412 * Note: If you pass a rect with 0,0 for an origin, the window ends up in a
413 * weird place for some reason. This stops that without breaking popups.
414 */
415 // Compensate for difference between frame and content area height (e.g. title bar).
416 NSRect newWindowFrame = [NSWindow frameRectForContentRect:aRect styleMask:features];
418 contentRect = aRect;
419 contentRect.origin.y -= (newWindowFrame.size.height - aRect.size.height);
421 if (mWindowType != eWindowType_popup)
422 contentRect.origin.y -= [[NSApp mainMenu] menuBarHeight];
423 }
425 // NSLog(@"Top-level window being created at Cocoa rect: %f, %f, %f, %f\n",
426 // rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
428 Class windowClass = [BaseWindow class];
429 // If we have a titlebar on a top-level window, we want to be able to control the
430 // titlebar color (for unified windows), so use the special ToolbarWindow class.
431 // Note that we need to check the window type because we mark sheets as
432 // having titlebars.
433 if ((mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) &&
434 (features & NSTitledWindowMask))
435 windowClass = [ToolbarWindow class];
436 // If we're a popup window we need to use the PopupWindow class.
437 else if (mWindowType == eWindowType_popup)
438 windowClass = [PopupWindow class];
439 // If we're a non-popup borderless window we need to use the
440 // BorderlessWindow class.
441 else if (features == NSBorderlessWindowMask)
442 windowClass = [BorderlessWindow class];
444 // Create the window
445 mWindow = [[windowClass alloc] initWithContentRect:contentRect styleMask:features
446 backing:NSBackingStoreBuffered defer:YES];
448 // setup our notification delegate. Note that setDelegate: does NOT retain.
449 mDelegate = [[WindowDelegate alloc] initWithGeckoWindow:this];
450 [mWindow setDelegate:mDelegate];
452 // Make sure that the content rect we gave has been honored.
453 NSRect wantedFrame = [mWindow frameRectForContentRect:contentRect];
454 if (!NSEqualRects([mWindow frame], wantedFrame)) {
455 // This can happen when the window is not on the primary screen.
456 [mWindow setFrame:wantedFrame display:NO];
457 }
458 UpdateBounds();
460 if (mWindowType == eWindowType_invisible) {
461 [mWindow setLevel:kCGDesktopWindowLevelKey];
462 } else if (mWindowType == eWindowType_popup) {
463 SetPopupWindowLevel();
464 [mWindow setHasShadow:YES];
465 }
467 [mWindow setBackgroundColor:[NSColor clearColor]];
468 #ifdef MOZ_B2G
469 // In B2G, we don't create popups and we need OMTC to work (because out of
470 // process compositing depends on it). Therefore, we don't need our windows
471 // to be transparent.
472 [mWindow setOpaque:YES];
473 #else
474 [mWindow setOpaque:NO];
475 #endif
476 [mWindow setContentMinSize:NSMakeSize(60, 60)];
477 [mWindow disableCursorRects];
479 // Make sure the window starts out not draggable by the background.
480 // We will turn it on as necessary.
481 [mWindow setMovableByWindowBackground:NO];
483 [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow];
484 mWindowMadeHere = true;
486 return NS_OK;
488 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
489 }
491 NS_IMETHODIMP nsCocoaWindow::CreatePopupContentView(const nsIntRect &aRect,
492 nsDeviceContext *aContext)
493 {
494 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
496 // We need to make our content view a ChildView.
497 mPopupContentView = new nsChildView();
498 if (!mPopupContentView)
499 return NS_ERROR_FAILURE;
501 NS_ADDREF(mPopupContentView);
503 nsIWidget* thisAsWidget = static_cast<nsIWidget*>(this);
504 mPopupContentView->Create(thisAsWidget, nullptr, aRect, aContext, nullptr);
506 ChildView* newContentView = (ChildView*)mPopupContentView->GetNativeData(NS_NATIVE_WIDGET);
507 [mWindow setContentView:newContentView];
509 return NS_OK;
511 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
512 }
514 NS_IMETHODIMP nsCocoaWindow::Destroy()
515 {
516 // If we don't hide here we run into problems with panels, this is not ideal.
517 // (Bug 891424)
518 Show(false);
520 if (mPopupContentView)
521 mPopupContentView->Destroy();
523 nsBaseWidget::Destroy();
524 // nsBaseWidget::Destroy() calls GetParent()->RemoveChild(this). But we
525 // don't implement GetParent(), so we need to do the equivalent here.
526 if (mParent) {
527 mParent->RemoveChild(this);
528 }
529 nsBaseWidget::OnDestroy();
531 if (mFullScreen) {
532 // On Lion we don't have to mess with the OS chrome when in Full Screen
533 // mode. But we do have to destroy the native window here (and not wait
534 // for that to happen in our destructor). We don't switch away from the
535 // native window's space until the window is destroyed, and otherwise this
536 // might not happen for several seconds (because at least one object
537 // holding a reference to ourselves is usually waiting to be garbage-
538 // collected). See bug 757618.
539 if (mUsesNativeFullScreen) {
540 DestroyNativeWindow();
541 } else if (mWindow) {
542 nsCocoaUtils::HideOSChromeOnScreen(false, [mWindow screen]);
543 }
544 }
546 return NS_OK;
547 }
549 nsIWidget* nsCocoaWindow::GetSheetWindowParent(void)
550 {
551 if (mWindowType != eWindowType_sheet)
552 return nullptr;
553 nsCocoaWindow *parent = static_cast<nsCocoaWindow*>(mParent);
554 while (parent && (parent->mWindowType == eWindowType_sheet))
555 parent = static_cast<nsCocoaWindow*>(parent->mParent);
556 return parent;
557 }
559 void* nsCocoaWindow::GetNativeData(uint32_t aDataType)
560 {
561 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL;
563 void* retVal = nullptr;
565 switch (aDataType) {
566 // to emulate how windows works, we always have to return a NSView
567 // for NS_NATIVE_WIDGET
568 case NS_NATIVE_WIDGET:
569 case NS_NATIVE_DISPLAY:
570 retVal = [mWindow contentView];
571 break;
573 case NS_NATIVE_WINDOW:
574 retVal = mWindow;
575 break;
577 case NS_NATIVE_GRAPHIC:
578 // There isn't anything that makes sense to return here,
579 // and it doesn't matter so just return nullptr.
580 NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a top-level window!");
581 break;
582 }
584 return retVal;
586 NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
587 }
589 bool nsCocoaWindow::IsVisible() const
590 {
591 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
593 return (mWindow && ([mWindow isVisibleOrBeingShown] || mSheetNeedsShow));
595 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
596 }
598 NS_IMETHODIMP nsCocoaWindow::SetModal(bool aState)
599 {
600 if (!mWindow)
601 return NS_OK;
603 // This is used during startup (outside the event loop) when creating
604 // the add-ons compatibility checking dialog and the profile manager UI;
605 // therefore, it needs to provide an autorelease pool to avoid cocoa
606 // objects leaking.
607 nsAutoreleasePool localPool;
609 mModal = aState;
610 nsCocoaWindow *aParent = static_cast<nsCocoaWindow*>(mParent);
611 if (aState) {
612 ++gXULModalLevel;
613 if (gCocoaAppModalWindowList)
614 gCocoaAppModalWindowList->PushGecko(mWindow, this);
615 // When a non-sheet window gets "set modal", make the window(s) that it
616 // appears over behave as they should. We can't rely on native methods to
617 // do this, for the following reason: The OS runs modal non-sheet windows
618 // in an event loop (using [NSApplication runModalForWindow:] or similar
619 // methods) that's incompatible with the modal event loop in nsXULWindow::
620 // ShowModal() (each of these event loops is "exclusive", and can't run at
621 // the same time as other (similar) event loops).
622 if (mWindowType != eWindowType_sheet) {
623 while (aParent) {
624 if (aParent->mNumModalDescendents++ == 0) {
625 NSWindow *aWindow = aParent->GetCocoaWindow();
626 if (aParent->mWindowType != eWindowType_invisible) {
627 [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:NO];
628 [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:NO];
629 [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:NO];
630 }
631 }
632 aParent = static_cast<nsCocoaWindow*>(aParent->mParent);
633 }
634 [mWindow setLevel:NSModalPanelWindowLevel];
635 nsCocoaWindowList *windowList = new nsCocoaWindowList;
636 if (windowList) {
637 windowList->window = this; // Don't ADDREF
638 windowList->prev = gGeckoAppModalWindowList;
639 gGeckoAppModalWindowList = windowList;
640 }
641 }
642 }
643 else {
644 --gXULModalLevel;
645 NS_ASSERTION(gXULModalLevel >= 0, "Mismatched call to nsCocoaWindow::SetModal(false)!");
646 if (gCocoaAppModalWindowList)
647 gCocoaAppModalWindowList->PopGecko(mWindow, this);
648 if (mWindowType != eWindowType_sheet) {
649 while (aParent) {
650 if (--aParent->mNumModalDescendents == 0) {
651 NSWindow *aWindow = aParent->GetCocoaWindow();
652 if (aParent->mWindowType != eWindowType_invisible) {
653 [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:YES];
654 [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:YES];
655 [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:YES];
656 }
657 }
658 NS_ASSERTION(aParent->mNumModalDescendents >= 0, "Widget hierarchy changed while modal!");
659 aParent = static_cast<nsCocoaWindow*>(aParent->mParent);
660 }
661 if (gGeckoAppModalWindowList) {
662 NS_ASSERTION(gGeckoAppModalWindowList->window == this, "Widget hierarchy changed while modal!");
663 nsCocoaWindowList *saved = gGeckoAppModalWindowList;
664 gGeckoAppModalWindowList = gGeckoAppModalWindowList->prev;
665 delete saved; // "window" not ADDREFed
666 }
667 if (mWindowType == eWindowType_popup)
668 SetPopupWindowLevel();
669 else
670 [mWindow setLevel:NSNormalWindowLevel];
671 }
672 }
673 return NS_OK;
674 }
676 // Hide or show this window
677 NS_IMETHODIMP nsCocoaWindow::Show(bool bState)
678 {
679 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
681 if (!mWindow)
682 return NS_OK;
684 // We need to re-execute sometimes in order to bring already-visible
685 // windows forward.
686 if (!mSheetNeedsShow && !bState && ![mWindow isVisible])
687 return NS_OK;
689 // Protect against re-entering.
690 if (bState && [mWindow isBeingShown])
691 return NS_OK;
693 [mWindow setBeingShown:bState];
695 nsIWidget* parentWidget = mParent;
696 nsCOMPtr<nsPIWidgetCocoa> piParentWidget(do_QueryInterface(parentWidget));
697 NSWindow* nativeParentWindow = (parentWidget) ?
698 (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW) : nil;
700 if (bState && !mBounds.IsEmpty()) {
701 if (mPopupContentView) {
702 // Ensure our content view is visible. We never need to hide it.
703 mPopupContentView->Show(true);
704 }
706 if (mWindowType == eWindowType_sheet) {
707 // bail if no parent window (its basically what we do in Carbon)
708 if (!nativeParentWindow || !piParentWidget)
709 return NS_ERROR_FAILURE;
711 NSWindow* topNonSheetWindow = nativeParentWindow;
713 // If this sheet is the child of another sheet, hide the parent so that
714 // this sheet can be displayed. Leave the parent mSheetNeedsShow alone,
715 // that is only used to handle sibling sheet contention. The parent will
716 // return once there are no more child sheets.
717 bool parentIsSheet = false;
718 if (NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) &&
719 parentIsSheet) {
720 piParentWidget->GetSheetWindowParent(&topNonSheetWindow);
721 [NSApp endSheet:nativeParentWindow];
722 }
724 nsCocoaWindow* sheetShown = nullptr;
725 if (NS_SUCCEEDED(piParentWidget->GetChildSheet(true, &sheetShown)) &&
726 (!sheetShown || sheetShown == this)) {
727 // If this sheet is already the sheet actually being shown, don't
728 // tell it to show again. Otherwise the number of calls to
729 // [NSApp beginSheet...] won't match up with [NSApp endSheet...].
730 if (![mWindow isVisible]) {
731 mSheetNeedsShow = false;
732 mSheetWindowParent = topNonSheetWindow;
733 // Only set contextInfo if our parent isn't a sheet.
734 NSWindow* contextInfo = parentIsSheet ? nil : mSheetWindowParent;
735 [TopLevelWindowData deactivateInWindow:mSheetWindowParent];
736 [NSApp beginSheet:mWindow
737 modalForWindow:mSheetWindowParent
738 modalDelegate:mDelegate
739 didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
740 contextInfo:contextInfo];
741 [TopLevelWindowData activateInWindow:mWindow];
742 SendSetZLevelEvent();
743 }
744 }
745 else {
746 // A sibling of this sheet is active, don't show this sheet yet.
747 // When the active sheet hides, its brothers and sisters that have
748 // mSheetNeedsShow set will have their opportunities to display.
749 mSheetNeedsShow = true;
750 }
751 }
752 else if (mWindowType == eWindowType_popup) {
753 // If a popup window is shown after being hidden, it needs to be "reset"
754 // for it to receive any mouse events aside from mouse-moved events
755 // (because it was removed from the "window cache" when it was hidden
756 // -- see below). Setting the window number to -1 and then back to its
757 // original value seems to accomplish this. The idea was "borrowed"
758 // from the Java Embedding Plugin.
759 NSInteger windowNumber = [mWindow windowNumber];
760 [mWindow _setWindowNumber:-1];
761 [mWindow _setWindowNumber:windowNumber];
762 // For reasons that aren't yet clear, calls to [NSWindow orderFront:] or
763 // [NSWindow makeKeyAndOrderFront:] can sometimes trigger "Error (1000)
764 // creating CGSWindow", which in turn triggers an internal inconsistency
765 // NSException. These errors shouldn't be fatal. So we need to wrap
766 // calls to ...orderFront: in LOGONLY blocks. See bmo bug 470864.
767 NS_OBJC_BEGIN_TRY_LOGONLY_BLOCK;
768 [[mWindow contentView] setNeedsDisplay:YES];
769 [mWindow orderFront:nil];
770 NS_OBJC_END_TRY_LOGONLY_BLOCK;
771 SendSetZLevelEvent();
772 AdjustWindowShadow();
773 SetWindowBackgroundBlur();
774 // If our popup window is a non-native context menu, tell the OS (and
775 // other programs) that a menu has opened. This is how the OS knows to
776 // close other programs' context menus when ours open.
777 if ([mWindow isKindOfClass:[PopupWindow class]] &&
778 [(PopupWindow*) mWindow isContextMenu]) {
779 [[NSDistributedNotificationCenter defaultCenter]
780 postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification"
781 object:@"org.mozilla.gecko.PopupWindow"];
782 }
784 // If a parent window was supplied and this is a popup at the parent
785 // level, set its child window. This will cause the child window to
786 // appear above the parent and move when the parent does. Setting this
787 // needs to happen after the _setWindowNumber calls above, otherwise the
788 // window doesn't focus properly.
789 if (nativeParentWindow && mPopupLevel == ePopupLevelParent)
790 [nativeParentWindow addChildWindow:mWindow
791 ordered:NSWindowAbove];
792 }
793 else {
794 NS_OBJC_BEGIN_TRY_LOGONLY_BLOCK;
795 if (mWindowType == eWindowType_toplevel &&
796 [mWindow respondsToSelector:@selector(setAnimationBehavior:)]) {
797 NSWindowAnimationBehavior behavior;
798 if (mIsAnimationSuppressed) {
799 behavior = NSWindowAnimationBehaviorNone;
800 } else {
801 switch (mAnimationType) {
802 case nsIWidget::eDocumentWindowAnimation:
803 behavior = NSWindowAnimationBehaviorDocumentWindow;
804 break;
805 default:
806 NS_NOTREACHED("unexpected mAnimationType value");
807 // fall through
808 case nsIWidget::eGenericWindowAnimation:
809 behavior = NSWindowAnimationBehaviorDefault;
810 break;
811 }
812 }
813 [mWindow setAnimationBehavior:behavior];
814 }
815 [mWindow makeKeyAndOrderFront:nil];
816 NS_OBJC_END_TRY_LOGONLY_BLOCK;
817 SendSetZLevelEvent();
818 }
819 }
820 else {
821 // roll up any popups if a top-level window is going away
822 if (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog)
823 RollUpPopups();
825 // now get rid of the window/sheet
826 if (mWindowType == eWindowType_sheet) {
827 if (mSheetNeedsShow) {
828 // This is an attempt to hide a sheet that never had a chance to
829 // be shown. There's nothing to do other than make sure that it
830 // won't show.
831 mSheetNeedsShow = false;
832 }
833 else {
834 // get sheet's parent *before* hiding the sheet (which breaks the linkage)
835 NSWindow* sheetParent = mSheetWindowParent;
837 // hide the sheet
838 [NSApp endSheet:mWindow];
840 [TopLevelWindowData deactivateInWindow:mWindow];
842 nsCocoaWindow* siblingSheetToShow = nullptr;
843 bool parentIsSheet = false;
845 if (nativeParentWindow && piParentWidget &&
846 NS_SUCCEEDED(piParentWidget->GetChildSheet(false, &siblingSheetToShow)) &&
847 siblingSheetToShow) {
848 // First, give sibling sheets an opportunity to show.
849 siblingSheetToShow->Show(true);
850 }
851 else if (nativeParentWindow && piParentWidget &&
852 NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) &&
853 parentIsSheet) {
854 // Only set contextInfo if the parent of the parent sheet we're about
855 // to restore isn't itself a sheet.
856 NSWindow* contextInfo = sheetParent;
857 nsIWidget* grandparentWidget = nil;
858 if (NS_SUCCEEDED(piParentWidget->GetRealParent(&grandparentWidget)) && grandparentWidget) {
859 nsCOMPtr<nsPIWidgetCocoa> piGrandparentWidget(do_QueryInterface(grandparentWidget));
860 bool grandparentIsSheet = false;
861 if (piGrandparentWidget && NS_SUCCEEDED(piGrandparentWidget->GetIsSheet(&grandparentIsSheet)) &&
862 grandparentIsSheet) {
863 contextInfo = nil;
864 }
865 }
866 // If there are no sibling sheets, but the parent is a sheet, restore
867 // it. It wasn't sent any deactivate events when it was hidden, so
868 // don't call through Show, just let the OS put it back up.
869 [NSApp beginSheet:nativeParentWindow
870 modalForWindow:sheetParent
871 modalDelegate:[nativeParentWindow delegate]
872 didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
873 contextInfo:contextInfo];
874 }
875 else {
876 // Sheet, that was hard. No more siblings or parents, going back
877 // to a real window.
878 NS_OBJC_BEGIN_TRY_LOGONLY_BLOCK;
879 [sheetParent makeKeyAndOrderFront:nil];
880 NS_OBJC_END_TRY_LOGONLY_BLOCK;
881 }
882 SendSetZLevelEvent();
883 }
884 }
885 else {
886 // If the window is a popup window with a parent window we need to
887 // unhook it here before ordering it out. When you order out the child
888 // of a window it hides the parent window.
889 if (mWindowType == eWindowType_popup && nativeParentWindow)
890 [nativeParentWindow removeChildWindow:mWindow];
892 [mWindow orderOut:nil];
893 // Unless it's explicitly removed from NSApp's "window cache", a popup
894 // window will keep receiving mouse-moved events even after it's been
895 // "ordered out" (instead of the browser window that was underneath it,
896 // until you click on that window). This is bmo bug 378645, but it's
897 // surely an Apple bug. The "window cache" is an undocumented subsystem,
898 // all of whose methods are included in the NSWindowCache category of
899 // the NSApplication class (in header files generated using class-dump).
900 // This workaround was "borrowed" from the Java Embedding Plugin (which
901 // uses it for a different purpose).
902 if (mWindowType == eWindowType_popup)
903 [NSApp _removeWindowFromCache:mWindow];
905 // If our popup window is a non-native context menu, tell the OS (and
906 // other programs) that a menu has closed.
907 if ([mWindow isKindOfClass:[PopupWindow class]] &&
908 [(PopupWindow*) mWindow isContextMenu]) {
909 [[NSDistributedNotificationCenter defaultCenter]
910 postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification"
911 object:@"org.mozilla.gecko.PopupWindow"];
912 }
913 }
914 }
916 [mWindow setBeingShown:NO];
918 return NS_OK;
920 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
921 }
923 struct ShadowParams {
924 float standardDeviation;
925 float density;
926 int offsetX;
927 int offsetY;
928 unsigned int flags;
929 };
931 // These numbers have been determined by looking at the results of
932 // CGSGetWindowShadowAndRimParameters for native window types.
933 static const ShadowParams kWindowShadowParameters[] = {
934 { 0.0f, 0.0f, 0, 0, 0 }, // none
935 { 8.0f, 0.5f, 0, 6, 1 }, // default
936 { 10.0f, 0.44f, 0, 10, 512 }, // menu
937 { 8.0f, 0.5f, 0, 6, 1 }, // tooltip
938 { 4.0f, 0.6f, 0, 4, 512 } // sheet
939 };
941 // This method will adjust the window shadow style for popup windows after
942 // they have been made visible. Before they're visible, their window number
943 // might be -1, which is not useful.
944 // We won't attempt to change the shadow for windows that can acquire key state
945 // since OS X will reset the shadow whenever that happens.
946 void
947 nsCocoaWindow::AdjustWindowShadow()
948 {
949 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
951 if (!mWindow || ![mWindow isVisible] || ![mWindow hasShadow] ||
952 [mWindow canBecomeKeyWindow] || [mWindow windowNumber] == -1)
953 return;
955 const ShadowParams& params = kWindowShadowParameters[mShadowStyle];
956 CGSConnection cid = _CGSDefaultConnection();
957 CGSSetWindowShadowAndRimParameters(cid, [mWindow windowNumber],
958 params.standardDeviation, params.density,
959 params.offsetX, params.offsetY,
960 params.flags);
962 NS_OBJC_END_TRY_ABORT_BLOCK;
963 }
965 static const NSUInteger kWindowBackgroundBlurRadius = 4;
967 void
968 nsCocoaWindow::SetWindowBackgroundBlur()
969 {
970 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
972 if (!mWindow || ![mWindow isVisible] || [mWindow windowNumber] == -1)
973 return;
975 // Only blur the background of menus and fake sheets.
976 if (mShadowStyle != NS_STYLE_WINDOW_SHADOW_MENU &&
977 mShadowStyle != NS_STYLE_WINDOW_SHADOW_SHEET)
978 return;
980 CGSConnection cid = _CGSDefaultConnection();
981 CGSSetWindowBackgroundBlurRadius(cid, [mWindow windowNumber], kWindowBackgroundBlurRadius);
983 NS_OBJC_END_TRY_ABORT_BLOCK;
984 }
986 nsresult
987 nsCocoaWindow::ConfigureChildren(const nsTArray<Configuration>& aConfigurations)
988 {
989 if (mPopupContentView) {
990 mPopupContentView->ConfigureChildren(aConfigurations);
991 }
992 return NS_OK;
993 }
995 LayerManager*
996 nsCocoaWindow::GetLayerManager(PLayerTransactionChild* aShadowManager,
997 LayersBackend aBackendHint,
998 LayerManagerPersistence aPersistence,
999 bool* aAllowRetaining)
1000 {
1001 if (mPopupContentView) {
1002 return mPopupContentView->GetLayerManager(aShadowManager,
1003 aBackendHint,
1004 aPersistence,
1005 aAllowRetaining);
1006 }
1007 return nullptr;
1008 }
1010 nsTransparencyMode nsCocoaWindow::GetTransparencyMode()
1011 {
1012 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
1014 return (!mWindow || [mWindow isOpaque]) ? eTransparencyOpaque : eTransparencyTransparent;
1016 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(eTransparencyOpaque);
1017 }
1019 // This is called from nsMenuPopupFrame when making a popup transparent.
1020 // For other window types, nsChildView::SetTransparencyMode is used.
1021 void nsCocoaWindow::SetTransparencyMode(nsTransparencyMode aMode)
1022 {
1023 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1025 if (!mWindow)
1026 return;
1028 BOOL isTransparent = aMode == eTransparencyTransparent;
1029 BOOL currentTransparency = ![mWindow isOpaque];
1030 if (isTransparent != currentTransparency) {
1031 [mWindow setOpaque:!isTransparent];
1032 [mWindow setBackgroundColor:(isTransparent ? [NSColor clearColor] : [NSColor whiteColor])];
1033 }
1035 NS_OBJC_END_TRY_ABORT_BLOCK;
1036 }
1038 NS_IMETHODIMP nsCocoaWindow::Enable(bool aState)
1039 {
1040 return NS_OK;
1041 }
1043 bool nsCocoaWindow::IsEnabled() const
1044 {
1045 return true;
1046 }
1048 #define kWindowPositionSlop 20
1050 NS_IMETHODIMP nsCocoaWindow::ConstrainPosition(bool aAllowSlop,
1051 int32_t *aX, int32_t *aY)
1052 {
1053 if (!mWindow || ![mWindow screen]) {
1054 return NS_OK;
1055 }
1057 nsIntRect screenBounds;
1059 int32_t width, height;
1061 NSRect frame = [mWindow frame];
1063 // zero size rects confuse the screen manager
1064 width = std::max<int32_t>(frame.size.width, 1);
1065 height = std::max<int32_t>(frame.size.height, 1);
1067 nsCOMPtr<nsIScreenManager> screenMgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
1068 if (screenMgr) {
1069 nsCOMPtr<nsIScreen> screen;
1070 screenMgr->ScreenForRect(*aX, *aY, width, height, getter_AddRefs(screen));
1072 if (screen) {
1073 screen->GetRectDisplayPix(&(screenBounds.x), &(screenBounds.y),
1074 &(screenBounds.width), &(screenBounds.height));
1075 }
1076 }
1078 if (aAllowSlop) {
1079 if (*aX < screenBounds.x - width + kWindowPositionSlop) {
1080 *aX = screenBounds.x - width + kWindowPositionSlop;
1081 } else if (*aX >= screenBounds.x + screenBounds.width - kWindowPositionSlop) {
1082 *aX = screenBounds.x + screenBounds.width - kWindowPositionSlop;
1083 }
1085 if (*aY < screenBounds.y - height + kWindowPositionSlop) {
1086 *aY = screenBounds.y - height + kWindowPositionSlop;
1087 } else if (*aY >= screenBounds.y + screenBounds.height - kWindowPositionSlop) {
1088 *aY = screenBounds.y + screenBounds.height - kWindowPositionSlop;
1089 }
1090 } else {
1091 if (*aX < screenBounds.x) {
1092 *aX = screenBounds.x;
1093 } else if (*aX >= screenBounds.x + screenBounds.width - width) {
1094 *aX = screenBounds.x + screenBounds.width - width;
1095 }
1097 if (*aY < screenBounds.y) {
1098 *aY = screenBounds.y;
1099 } else if (*aY >= screenBounds.y + screenBounds.height - height) {
1100 *aY = screenBounds.y + screenBounds.height - height;
1101 }
1102 }
1104 return NS_OK;
1105 }
1107 void nsCocoaWindow::SetSizeConstraints(const SizeConstraints& aConstraints)
1108 {
1109 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1111 // Popups can be smaller than (60, 60)
1112 NSRect rect =
1113 (mWindowType == eWindowType_popup) ? NSZeroRect : NSMakeRect(0.0, 0.0, 60, 60);
1114 rect = [mWindow frameRectForContentRect:rect];
1116 CGFloat scaleFactor = BackingScaleFactor();
1118 SizeConstraints c = aConstraints;
1119 c.mMinSize.width =
1120 std::max(nsCocoaUtils::CocoaPointsToDevPixels(rect.size.width, scaleFactor),
1121 c.mMinSize.width);
1122 c.mMinSize.height =
1123 std::max(nsCocoaUtils::CocoaPointsToDevPixels(rect.size.height, scaleFactor),
1124 c.mMinSize.height);
1126 NSSize minSize = {
1127 nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.width, scaleFactor),
1128 nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.height, scaleFactor)
1129 };
1130 [mWindow setMinSize:minSize];
1132 NSSize maxSize = {
1133 c.mMaxSize.width == NS_MAXSIZE ?
1134 FLT_MAX : nsCocoaUtils::DevPixelsToCocoaPoints(c.mMaxSize.width, scaleFactor),
1135 c.mMaxSize.height == NS_MAXSIZE ?
1136 FLT_MAX : nsCocoaUtils::DevPixelsToCocoaPoints(c.mMaxSize.height, scaleFactor)
1137 };
1138 [mWindow setMaxSize:maxSize];
1140 nsBaseWidget::SetSizeConstraints(c);
1142 NS_OBJC_END_TRY_ABORT_BLOCK;
1143 }
1145 // Coordinates are global display pixels
1146 NS_IMETHODIMP nsCocoaWindow::Move(double aX, double aY)
1147 {
1148 if (!mWindow) {
1149 return NS_OK;
1150 }
1152 // The point we have is in Gecko coordinates (origin top-left). Convert
1153 // it to Cocoa ones (origin bottom-left).
1154 NSPoint coord = {
1155 static_cast<float>(aX),
1156 static_cast<float>(nsCocoaUtils::FlippedScreenY(NSToIntRound(aY)))
1157 };
1159 NSRect frame = [mWindow frame];
1160 if (frame.origin.x != coord.x ||
1161 frame.origin.y + frame.size.height != coord.y) {
1162 [mWindow setFrameTopLeftPoint:coord];
1163 }
1165 return NS_OK;
1166 }
1168 // Position the window behind the given window
1169 NS_METHOD nsCocoaWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
1170 nsIWidget *aWidget, bool aActivate)
1171 {
1172 return NS_OK;
1173 }
1175 NS_METHOD nsCocoaWindow::SetSizeMode(int32_t aMode)
1176 {
1177 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1179 if (!mWindow)
1180 return NS_OK;
1182 // mSizeMode will be updated in DispatchSizeModeEvent, which will be called
1183 // from a delegate method that handles the state change during one of the
1184 // calls below.
1185 nsSizeMode previousMode = mSizeMode;
1187 if (aMode == nsSizeMode_Normal) {
1188 if ([mWindow isMiniaturized])
1189 [mWindow deminiaturize:nil];
1190 else if (previousMode == nsSizeMode_Maximized && [mWindow isZoomed])
1191 [mWindow zoom:nil];
1192 }
1193 else if (aMode == nsSizeMode_Minimized) {
1194 if (![mWindow isMiniaturized])
1195 [mWindow miniaturize:nil];
1196 }
1197 else if (aMode == nsSizeMode_Maximized) {
1198 if ([mWindow isMiniaturized])
1199 [mWindow deminiaturize:nil];
1200 if (![mWindow isZoomed])
1201 [mWindow zoom:nil];
1202 }
1203 else if (aMode == nsSizeMode_Fullscreen) {
1204 if (!mFullScreen)
1205 MakeFullScreen(true);
1206 }
1208 return NS_OK;
1210 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1211 }
1213 // This has to preserve the window's frame bounds.
1214 // This method requires (as does the Windows impl.) that you call Resize shortly
1215 // after calling HideWindowChrome. See bug 498835 for fixing this.
1216 NS_IMETHODIMP nsCocoaWindow::HideWindowChrome(bool aShouldHide)
1217 {
1218 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1220 if (!mWindow || !mWindowMadeHere ||
1221 (mWindowType != eWindowType_toplevel && mWindowType != eWindowType_dialog))
1222 return NS_ERROR_FAILURE;
1224 BOOL isVisible = [mWindow isVisible];
1226 // Remove child windows.
1227 NSArray* childWindows = [mWindow childWindows];
1228 NSEnumerator* enumerator = [childWindows objectEnumerator];
1229 NSWindow* child = nil;
1230 while ((child = [enumerator nextObject])) {
1231 [mWindow removeChildWindow:child];
1232 }
1234 // Remove the content view.
1235 NSView* contentView = [mWindow contentView];
1236 [contentView retain];
1237 [contentView removeFromSuperviewWithoutNeedingDisplay];
1239 // Save state (like window title).
1240 NSMutableDictionary* state = [mWindow exportState];
1242 // Recreate the window with the right border style.
1243 NSRect frameRect = [mWindow frame];
1244 DestroyNativeWindow();
1245 nsresult rv = CreateNativeWindow(frameRect, aShouldHide ? eBorderStyle_none : mBorderStyle, true);
1246 NS_ENSURE_SUCCESS(rv, rv);
1248 // Re-import state.
1249 [mWindow importState:state];
1251 // Reparent the content view.
1252 [mWindow setContentView:contentView];
1253 [contentView release];
1255 // Reparent child windows.
1256 enumerator = [childWindows objectEnumerator];
1257 while ((child = [enumerator nextObject])) {
1258 [mWindow addChildWindow:child ordered:NSWindowAbove];
1259 }
1261 // Show the new window.
1262 if (isVisible) {
1263 rv = Show(true);
1264 NS_ENSURE_SUCCESS(rv, rv);
1265 }
1267 return NS_OK;
1269 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1270 }
1272 void nsCocoaWindow::EnteredFullScreen(bool aFullScreen)
1273 {
1274 mInFullScreenTransition = false;
1275 mFullScreen = aFullScreen;
1276 DispatchSizeModeEvent();
1277 }
1279 NS_METHOD nsCocoaWindow::MakeFullScreen(bool aFullScreen)
1280 {
1281 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1283 if (!mWindow) {
1284 return NS_OK;
1285 }
1287 // We will call into MakeFullScreen redundantly when entering/exiting
1288 // fullscreen mode via OS X controls. When that happens we should just handle
1289 // it gracefully - no need to ASSERT.
1290 if (mFullScreen == aFullScreen) {
1291 return NS_OK;
1292 }
1294 // If we're using native fullscreen mode and our native window is invisible,
1295 // our attempt to go into fullscreen mode will fail with an assertion in
1296 // system code, without [WindowDelegate windowDidFailToEnterFullScreen:]
1297 // ever getting called. To pre-empt this we bail here. See bug 752294.
1298 if (mUsesNativeFullScreen && aFullScreen && ![mWindow isVisible]) {
1299 EnteredFullScreen(false);
1300 return NS_OK;
1301 }
1303 mInFullScreenTransition = true;
1305 if (mUsesNativeFullScreen) {
1306 // Calling toggleFullScreen will result in windowDid(FailTo)?(Enter|Exit)FullScreen
1307 // to be called from the OS. We will call EnteredFullScreen from those methods,
1308 // where mFullScreen will be set and a sizemode event will be dispatched.
1309 [mWindow toggleFullScreen:nil];
1310 } else {
1311 NSDisableScreenUpdates();
1312 // The order here matters. When we exit full screen mode, we need to show the
1313 // Dock first, otherwise the newly-created window won't have its minimize
1314 // button enabled. See bug 526282.
1315 nsCocoaUtils::HideOSChromeOnScreen(aFullScreen, [mWindow screen]);
1316 nsresult rv = nsBaseWidget::MakeFullScreen(aFullScreen);
1317 NSEnableScreenUpdates();
1318 NS_ENSURE_SUCCESS(rv, rv);
1320 EnteredFullScreen(aFullScreen);
1321 }
1323 return NS_OK;
1325 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1326 }
1328 // Coordinates are global display pixels
1329 nsresult nsCocoaWindow::DoResize(double aX, double aY,
1330 double aWidth, double aHeight,
1331 bool aRepaint,
1332 bool aConstrainToCurrentScreen)
1333 {
1334 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1336 if (!mWindow || mInResize) {
1337 return NS_OK;
1338 }
1340 AutoRestore<bool> reentrantResizeGuard(mInResize);
1341 mInResize = true;
1343 // ConstrainSize operates in device pixels, so we need to convert using
1344 // the backing scale factor here
1345 CGFloat scale = BackingScaleFactor();
1346 int32_t width = NSToIntRound(aWidth * scale);
1347 int32_t height = NSToIntRound(aHeight * scale);
1348 ConstrainSize(&width, &height);
1350 nsIntRect newBounds(NSToIntRound(aX), NSToIntRound(aY),
1351 NSToIntRound(width / scale),
1352 NSToIntRound(height / scale));
1354 // constrain to the screen that contains the largest area of the new rect
1355 FitRectToVisibleAreaForScreen(newBounds,
1356 aConstrainToCurrentScreen ?
1357 [mWindow screen] : nullptr,
1358 mUsesNativeFullScreen);
1360 // convert requested bounds into Cocoa coordinate system
1361 NSRect newFrame = nsCocoaUtils::GeckoRectToCocoaRect(newBounds);
1363 NSRect frame = [mWindow frame];
1364 BOOL isMoving = newFrame.origin.x != frame.origin.x ||
1365 newFrame.origin.y != frame.origin.y;
1366 BOOL isResizing = newFrame.size.width != frame.size.width ||
1367 newFrame.size.height != frame.size.height;
1369 if (!isMoving && !isResizing) {
1370 return NS_OK;
1371 }
1373 // We ignore aRepaint -- we have to call display:YES, otherwise the
1374 // title bar doesn't immediately get repainted and is displayed in
1375 // the wrong place, leading to a visual jump.
1376 [mWindow setFrame:newFrame display:YES];
1378 return NS_OK;
1380 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1381 }
1383 // Coordinates are global display pixels
1384 NS_IMETHODIMP nsCocoaWindow::Resize(double aX, double aY,
1385 double aWidth, double aHeight,
1386 bool aRepaint)
1387 {
1388 return DoResize(aX, aY, aWidth, aHeight, aRepaint, false);
1389 }
1391 // Coordinates are global display pixels
1392 NS_IMETHODIMP nsCocoaWindow::Resize(double aWidth, double aHeight, bool aRepaint)
1393 {
1394 double invScale = 1.0 / GetDefaultScale().scale;
1395 return DoResize(mBounds.x * invScale, mBounds.y * invScale,
1396 aWidth, aHeight, aRepaint, true);
1397 }
1399 NS_IMETHODIMP nsCocoaWindow::GetClientBounds(nsIntRect &aRect)
1400 {
1401 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1403 CGFloat scaleFactor = BackingScaleFactor();
1404 if (!mWindow) {
1405 aRect = nsCocoaUtils::CocoaRectToGeckoRectDevPix(NSZeroRect, scaleFactor);
1406 return NS_OK;
1407 }
1409 NSRect r;
1410 if ([mWindow isKindOfClass:[ToolbarWindow class]] &&
1411 [(ToolbarWindow*)mWindow drawsContentsIntoWindowFrame]) {
1412 r = [mWindow frame];
1413 } else {
1414 r = [mWindow contentRectForFrameRect:[mWindow frame]];
1415 }
1417 aRect = nsCocoaUtils::CocoaRectToGeckoRectDevPix(r, scaleFactor);
1419 return NS_OK;
1421 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1422 }
1424 void
1425 nsCocoaWindow::UpdateBounds()
1426 {
1427 NSRect frame = NSZeroRect;
1428 if (mWindow) {
1429 frame = [mWindow frame];
1430 }
1431 mBounds = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
1432 }
1434 NS_IMETHODIMP nsCocoaWindow::GetScreenBounds(nsIntRect &aRect)
1435 {
1436 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1438 #ifdef DEBUG
1439 nsIntRect r = nsCocoaUtils::CocoaRectToGeckoRectDevPix([mWindow frame], BackingScaleFactor());
1440 NS_ASSERTION(mWindow && mBounds == r, "mBounds out of sync!");
1441 #endif
1443 aRect = mBounds;
1444 return NS_OK;
1446 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1447 }
1449 double
1450 nsCocoaWindow::GetDefaultScaleInternal()
1451 {
1452 return BackingScaleFactor();
1453 }
1455 static CGFloat
1456 GetBackingScaleFactor(NSWindow* aWindow)
1457 {
1458 NSRect frame = [aWindow frame];
1459 if (frame.size.width > 0 && frame.size.height > 0) {
1460 return nsCocoaUtils::GetBackingScaleFactor(aWindow);
1461 }
1463 // For windows with zero width or height, the backingScaleFactor method
1464 // is broken - it will always return 2 on a retina macbook, even when
1465 // the window position implies it's on a non-hidpi external display
1466 // (to the extent that a zero-area window can be said to be "on" a
1467 // display at all!)
1468 // And to make matters worse, Cocoa even fires a
1469 // windowDidChangeBackingProperties notification with the
1470 // NSBackingPropertyOldScaleFactorKey key when a window on an
1471 // external display is resized to/from zero height, even though it hasn't
1472 // really changed screens.
1474 // This causes us to handle popup window sizing incorrectly when the
1475 // popup is resized to zero height (bug 820327) - nsXULPopupManager
1476 // becomes (incorrectly) convinced the popup has been explicitly forced
1477 // to a non-default size and needs to have size attributes attached.
1479 // Workaround: instead of asking the window, we'll find the screen it is on
1480 // and ask that for *its* backing scale factor.
1482 // (See bug 853252 and additional comments in windowDidChangeScreen: below
1483 // for further complications this causes.)
1485 // First, expand the rect so that it actually has a measurable area,
1486 // for FindTargetScreenForRect to use.
1487 if (frame.size.width == 0) {
1488 frame.size.width = 1;
1489 }
1490 if (frame.size.height == 0) {
1491 frame.size.height = 1;
1492 }
1494 // Then identify the screen it belongs to, and return its scale factor.
1495 NSScreen *screen =
1496 FindTargetScreenForRect(nsCocoaUtils::CocoaRectToGeckoRect(frame));
1497 return nsCocoaUtils::GetBackingScaleFactor(screen);
1498 }
1500 CGFloat
1501 nsCocoaWindow::BackingScaleFactor()
1502 {
1503 if (mBackingScaleFactor > 0.0) {
1504 return mBackingScaleFactor;
1505 }
1506 if (!mWindow) {
1507 return 1.0;
1508 }
1509 mBackingScaleFactor = GetBackingScaleFactor(mWindow);
1510 return mBackingScaleFactor;
1511 }
1513 void
1514 nsCocoaWindow::BackingScaleFactorChanged()
1515 {
1516 CGFloat newScale = GetBackingScaleFactor(mWindow);
1518 // ignore notification if it hasn't really changed (or maybe we have
1519 // disabled HiDPI mode via prefs)
1520 if (mBackingScaleFactor == newScale) {
1521 return;
1522 }
1524 if (mBackingScaleFactor > 0.0) {
1525 // convert size constraints to the new device pixel coordinate space
1526 double scaleFactor = newScale / mBackingScaleFactor;
1527 mSizeConstraints.mMinSize.width =
1528 NSToIntRound(mSizeConstraints.mMinSize.width * scaleFactor);
1529 mSizeConstraints.mMinSize.height =
1530 NSToIntRound(mSizeConstraints.mMinSize.height * scaleFactor);
1531 if (mSizeConstraints.mMaxSize.width < NS_MAXSIZE) {
1532 mSizeConstraints.mMaxSize.width =
1533 std::min(NS_MAXSIZE,
1534 NSToIntRound(mSizeConstraints.mMaxSize.width * scaleFactor));
1535 }
1536 if (mSizeConstraints.mMaxSize.height < NS_MAXSIZE) {
1537 mSizeConstraints.mMaxSize.height =
1538 std::min(NS_MAXSIZE,
1539 NSToIntRound(mSizeConstraints.mMaxSize.height * scaleFactor));
1540 }
1541 }
1543 mBackingScaleFactor = newScale;
1545 if (!mWidgetListener || mWidgetListener->GetXULWindow()) {
1546 return;
1547 }
1549 nsIPresShell* presShell = mWidgetListener->GetPresShell();
1550 if (presShell) {
1551 presShell->BackingScaleFactorChanged();
1552 }
1553 }
1555 int32_t
1556 nsCocoaWindow::RoundsWidgetCoordinatesTo()
1557 {
1558 if (BackingScaleFactor() == 2.0) {
1559 return 2;
1560 }
1561 return 1;
1562 }
1564 NS_IMETHODIMP nsCocoaWindow::SetCursor(nsCursor aCursor)
1565 {
1566 if (mPopupContentView)
1567 return mPopupContentView->SetCursor(aCursor);
1569 return NS_OK;
1570 }
1572 NS_IMETHODIMP nsCocoaWindow::SetCursor(imgIContainer* aCursor,
1573 uint32_t aHotspotX, uint32_t aHotspotY)
1574 {
1575 if (mPopupContentView)
1576 return mPopupContentView->SetCursor(aCursor, aHotspotX, aHotspotY);
1578 return NS_OK;
1579 }
1581 NS_IMETHODIMP nsCocoaWindow::SetTitle(const nsAString& aTitle)
1582 {
1583 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1585 if (!mWindow)
1586 return NS_OK;
1588 const nsString& strTitle = PromiseFlatString(aTitle);
1589 NSString* title = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(strTitle.get())
1590 length:strTitle.Length()];
1592 if ([mWindow drawsContentsIntoWindowFrame] && ![mWindow wantsTitleDrawn]) {
1593 // Don't cause invalidations.
1594 [mWindow disableSetNeedsDisplay];
1595 [mWindow setTitle:title];
1596 [mWindow enableSetNeedsDisplay];
1597 } else {
1598 [mWindow setTitle:title];
1599 }
1601 return NS_OK;
1603 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1604 }
1606 NS_IMETHODIMP nsCocoaWindow::Invalidate(const nsIntRect & aRect)
1607 {
1608 if (mPopupContentView) {
1609 return mPopupContentView->Invalidate(aRect);
1610 }
1612 return NS_OK;
1613 }
1615 // Pass notification of some drag event to Gecko
1616 //
1617 // The drag manager has let us know that something related to a drag has
1618 // occurred in this window. It could be any number of things, ranging from
1619 // a drop, to a drag enter/leave, or a drag over event. The actual event
1620 // is passed in |aMessage| and is passed along to our event hanlder so Gecko
1621 // knows about it.
1622 bool nsCocoaWindow::DragEvent(unsigned int aMessage, Point aMouseGlobal, UInt16 aKeyModifiers)
1623 {
1624 return false;
1625 }
1627 NS_IMETHODIMP nsCocoaWindow::SendSetZLevelEvent()
1628 {
1629 nsWindowZ placement = nsWindowZTop;
1630 nsIWidget* actualBelow;
1631 if (mWidgetListener)
1632 mWidgetListener->ZLevelChanged(true, &placement, nullptr, &actualBelow);
1633 return NS_OK;
1634 }
1636 NS_IMETHODIMP nsCocoaWindow::GetChildSheet(bool aShown, nsCocoaWindow** _retval)
1637 {
1638 nsIWidget* child = GetFirstChild();
1640 while (child) {
1641 if (child->WindowType() == eWindowType_sheet) {
1642 // if it's a sheet, it must be an nsCocoaWindow
1643 nsCocoaWindow* cocoaWindow = static_cast<nsCocoaWindow*>(child);
1644 if (cocoaWindow->mWindow &&
1645 ((aShown && [cocoaWindow->mWindow isVisible]) ||
1646 (!aShown && cocoaWindow->mSheetNeedsShow))) {
1647 *_retval = cocoaWindow;
1648 return NS_OK;
1649 }
1650 }
1651 child = child->GetNextSibling();
1652 }
1654 *_retval = nullptr;
1656 return NS_OK;
1657 }
1659 NS_IMETHODIMP nsCocoaWindow::GetRealParent(nsIWidget** parent)
1660 {
1661 *parent = mParent;
1662 return NS_OK;
1663 }
1665 NS_IMETHODIMP nsCocoaWindow::GetIsSheet(bool* isSheet)
1666 {
1667 mWindowType == eWindowType_sheet ? *isSheet = true : *isSheet = false;
1668 return NS_OK;
1669 }
1671 NS_IMETHODIMP nsCocoaWindow::GetSheetWindowParent(NSWindow** sheetWindowParent)
1672 {
1673 *sheetWindowParent = mSheetWindowParent;
1674 return NS_OK;
1675 }
1677 // Invokes callback and ProcessEvent methods on Event Listener object
1678 NS_IMETHODIMP
1679 nsCocoaWindow::DispatchEvent(WidgetGUIEvent* event, nsEventStatus& aStatus)
1680 {
1681 aStatus = nsEventStatus_eIgnore;
1683 nsIWidget* aWidget = event->widget;
1684 NS_IF_ADDREF(aWidget);
1686 if (mWidgetListener)
1687 aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents);
1689 NS_IF_RELEASE(aWidget);
1691 return NS_OK;
1692 }
1694 // aFullScreen should be the window's mFullScreen. We don't have access to that
1695 // from here, so we need to pass it in. mFullScreen should be the canonical
1696 // indicator that a window is currently full screen and it makes sense to keep
1697 // all sizemode logic here.
1698 static nsSizeMode
1699 GetWindowSizeMode(NSWindow* aWindow, bool aFullScreen) {
1700 if (aFullScreen)
1701 return nsSizeMode_Fullscreen;
1702 if ([aWindow isMiniaturized])
1703 return nsSizeMode_Minimized;
1704 if (([aWindow styleMask] & NSResizableWindowMask) && [aWindow isZoomed])
1705 return nsSizeMode_Maximized;
1706 return nsSizeMode_Normal;
1707 }
1709 void
1710 nsCocoaWindow::ReportMoveEvent()
1711 {
1712 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1714 // Prevent recursion, which can become infinite (see bug 708278). This
1715 // can happen when the call to [NSWindow setFrameTopLeftPoint:] in
1716 // nsCocoaWindow::Move() triggers an immediate NSWindowDidMove notification
1717 // (and a call to [WindowDelegate windowDidMove:]).
1718 if (mInReportMoveEvent) {
1719 return;
1720 }
1721 mInReportMoveEvent = true;
1723 UpdateBounds();
1725 // Dispatch the move event to Gecko
1726 NotifyWindowMoved(mBounds.x, mBounds.y);
1728 mInReportMoveEvent = false;
1730 NS_OBJC_END_TRY_ABORT_BLOCK;
1731 }
1733 void
1734 nsCocoaWindow::DispatchSizeModeEvent()
1735 {
1736 if (!mWindow) {
1737 return;
1738 }
1740 nsSizeMode newMode = GetWindowSizeMode(mWindow, mFullScreen);
1742 // Don't dispatch a sizemode event if:
1743 // 1. the window is transitioning to fullscreen
1744 // 2. the new sizemode is the same as the current sizemode
1745 if (mInFullScreenTransition || mSizeMode == newMode) {
1746 return;
1747 }
1749 mSizeMode = newMode;
1750 if (mWidgetListener) {
1751 mWidgetListener->SizeModeChanged(newMode);
1752 }
1753 }
1755 void
1756 nsCocoaWindow::ReportSizeEvent()
1757 {
1758 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1760 UpdateBounds();
1762 if (mWidgetListener) {
1763 nsIntRect innerBounds;
1764 GetClientBounds(innerBounds);
1765 mWidgetListener->WindowResized(this, innerBounds.width, innerBounds.height);
1766 }
1768 NS_OBJC_END_TRY_ABORT_BLOCK;
1769 }
1771 void nsCocoaWindow::SetMenuBar(nsMenuBarX *aMenuBar)
1772 {
1773 if (mMenuBar)
1774 mMenuBar->SetParent(nullptr);
1775 if (!mWindow) {
1776 mMenuBar = nullptr;
1777 return;
1778 }
1779 mMenuBar = aMenuBar;
1781 // Only paint for active windows, or paint the hidden window menu bar if no
1782 // other menu bar has been painted yet so that some reasonable menu bar is
1783 // displayed when the app starts up.
1784 id windowDelegate = [mWindow delegate];
1785 if (mMenuBar &&
1786 ((!gSomeMenuBarPainted && nsMenuUtilsX::GetHiddenWindowMenuBar() == mMenuBar) ||
1787 (windowDelegate && [windowDelegate toplevelActiveState])))
1788 mMenuBar->Paint();
1789 }
1791 NS_IMETHODIMP nsCocoaWindow::SetFocus(bool aState)
1792 {
1793 if (!mWindow)
1794 return NS_OK;
1796 if (mPopupContentView) {
1797 mPopupContentView->SetFocus(aState);
1798 }
1799 else if (aState && ([mWindow isVisible] || [mWindow isMiniaturized])) {
1800 [mWindow makeKeyAndOrderFront:nil];
1801 SendSetZLevelEvent();
1802 }
1804 return NS_OK;
1805 }
1807 nsIntPoint nsCocoaWindow::WidgetToScreenOffset()
1808 {
1809 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
1811 NSRect rect = NSZeroRect;
1812 nsIntRect r;
1813 if (mWindow) {
1814 rect = [mWindow contentRectForFrameRect:[mWindow frame]];
1815 }
1816 r = nsCocoaUtils::CocoaRectToGeckoRectDevPix(rect, BackingScaleFactor());
1818 return r.TopLeft();
1820 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nsIntPoint(0,0));
1821 }
1823 nsIntPoint nsCocoaWindow::GetClientOffset()
1824 {
1825 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
1827 nsIntRect clientRect;
1828 GetClientBounds(clientRect);
1830 return clientRect.TopLeft() - mBounds.TopLeft();
1832 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nsIntPoint(0, 0));
1833 }
1835 nsIntSize nsCocoaWindow::ClientToWindowSize(const nsIntSize& aClientSize)
1836 {
1837 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
1839 if (!mWindow)
1840 return nsIntSize(0, 0);
1842 CGFloat backingScale = BackingScaleFactor();
1843 nsIntRect r(0, 0, aClientSize.width, aClientSize.height);
1844 NSRect rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, backingScale);
1846 NSRect inflatedRect = [mWindow frameRectForContentRect:rect];
1847 return nsCocoaUtils::CocoaRectToGeckoRectDevPix(inflatedRect, backingScale).Size();
1849 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nsIntSize(0,0));
1850 }
1852 nsMenuBarX* nsCocoaWindow::GetMenuBar()
1853 {
1854 return mMenuBar;
1855 }
1857 NS_IMETHODIMP nsCocoaWindow::CaptureRollupEvents(nsIRollupListener* aListener, bool aDoCapture)
1858 {
1859 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1861 gRollupListener = nullptr;
1863 if (aDoCapture) {
1864 if (![NSApp isActive]) {
1865 // We need to capture mouse event if we aren't
1866 // the active application. We only set this up when needed
1867 // because they cause spurious mouse event after crash
1868 // and gdb sessions. See bug 699538.
1869 nsToolkit::GetToolkit()->RegisterForAllProcessMouseEvents();
1870 }
1871 gRollupListener = aListener;
1873 // Sometimes more than one popup window can be visible at the same time
1874 // (e.g. nested non-native context menus, or the test case (attachment
1875 // 276885) for bmo bug 392389, which displays a non-native combo-box in a
1876 // non-native popup window). In these cases the "active" popup window should
1877 // be the topmost -- the (nested) context menu the mouse is currently over,
1878 // or the combo-box's drop-down list (when it's displayed). But (among
1879 // windows that have the same "level") OS X makes topmost the window that
1880 // last received a mouse-down event, which may be incorrect (in the combo-
1881 // box case, it makes topmost the window containing the combo-box). So
1882 // here we fiddle with a non-native popup window's level to make sure the
1883 // "active" one is always above any other non-native popup windows that
1884 // may be visible.
1885 if (mWindow && (mWindowType == eWindowType_popup))
1886 SetPopupWindowLevel();
1887 } else {
1888 nsToolkit::GetToolkit()->UnregisterAllProcessMouseEventHandlers();
1890 // XXXndeakin this doesn't make sense.
1891 // Why is the new window assumed to be a modal panel?
1892 if (mWindow && (mWindowType == eWindowType_popup))
1893 [mWindow setLevel:NSModalPanelWindowLevel];
1894 }
1896 return NS_OK;
1898 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1899 }
1901 NS_IMETHODIMP nsCocoaWindow::GetAttention(int32_t aCycleCount)
1902 {
1903 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1905 [NSApp requestUserAttention:NSInformationalRequest];
1906 return NS_OK;
1908 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1909 }
1911 bool
1912 nsCocoaWindow::HasPendingInputEvent()
1913 {
1914 return nsChildView::DoHasPendingInputEvent();
1915 }
1917 NS_IMETHODIMP nsCocoaWindow::SetWindowShadowStyle(int32_t aStyle)
1918 {
1919 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1921 if (!mWindow)
1922 return NS_OK;
1924 mShadowStyle = aStyle;
1925 [mWindow setHasShadow:(aStyle != NS_STYLE_WINDOW_SHADOW_NONE)];
1926 AdjustWindowShadow();
1927 SetWindowBackgroundBlur();
1929 return NS_OK;
1931 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1932 }
1934 void nsCocoaWindow::SetShowsToolbarButton(bool aShow)
1935 {
1936 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1938 if (mWindow)
1939 [mWindow setShowsToolbarButton:aShow];
1941 NS_OBJC_END_TRY_ABORT_BLOCK;
1942 }
1944 void nsCocoaWindow::SetShowsFullScreenButton(bool aShow)
1945 {
1946 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1948 if (!mWindow || ![mWindow respondsToSelector:@selector(toggleFullScreen:)] ||
1949 mUsesNativeFullScreen == aShow) {
1950 return;
1951 }
1953 // If the window is currently in fullscreen mode, then we're going to
1954 // transition out first, then set the collection behavior & toggle
1955 // mUsesNativeFullScreen, then transtion back into fullscreen mode. This
1956 // prevents us from getting into a conflicting state with MakeFullScreen
1957 // where mUsesNativeFullScreen would lead us down the wrong path.
1958 bool wasFullScreen = mFullScreen;
1960 if (wasFullScreen) {
1961 MakeFullScreen(false);
1962 }
1964 NSWindowCollectionBehavior newBehavior = [mWindow collectionBehavior];
1965 if (aShow) {
1966 newBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
1967 } else {
1968 newBehavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
1969 }
1970 [mWindow setCollectionBehavior:newBehavior];
1971 mUsesNativeFullScreen = aShow;
1973 if (wasFullScreen) {
1974 MakeFullScreen(true);
1975 }
1977 NS_OBJC_END_TRY_ABORT_BLOCK;
1978 }
1980 void nsCocoaWindow::SetWindowAnimationType(nsIWidget::WindowAnimationType aType)
1981 {
1982 mAnimationType = aType;
1983 }
1985 void
1986 nsCocoaWindow::SetDrawsTitle(bool aDrawTitle)
1987 {
1988 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1990 [mWindow setWantsTitleDrawn:aDrawTitle];
1992 NS_OBJC_END_TRY_ABORT_BLOCK;
1993 }
1995 NS_IMETHODIMP nsCocoaWindow::SetNonClientMargins(nsIntMargin &margins)
1996 {
1997 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1999 SetDrawsInTitlebar(margins.top == 0);
2001 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2002 }
2004 NS_IMETHODIMP nsCocoaWindow::SetWindowTitlebarColor(nscolor aColor, bool aActive)
2005 {
2006 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2008 if (!mWindow)
2009 return NS_OK;
2011 // If they pass a color with a complete transparent alpha component, use the
2012 // native titlebar appearance.
2013 if (NS_GET_A(aColor) == 0) {
2014 [mWindow setTitlebarColor:nil forActiveWindow:(BOOL)aActive];
2015 } else {
2016 // Transform from sRGBA to monitor RGBA. This seems like it would make trying
2017 // to match the system appearance lame, so probably we just shouldn't color
2018 // correct chrome.
2019 if (gfxPlatform::GetCMSMode() == eCMSMode_All) {
2020 qcms_transform *transform = gfxPlatform::GetCMSRGBATransform();
2021 if (transform) {
2022 uint8_t color[3];
2023 color[0] = NS_GET_R(aColor);
2024 color[1] = NS_GET_G(aColor);
2025 color[2] = NS_GET_B(aColor);
2026 qcms_transform_data(transform, color, color, 1);
2027 aColor = NS_RGB(color[0], color[1], color[2]);
2028 }
2029 }
2031 [mWindow setTitlebarColor:[NSColor colorWithDeviceRed:NS_GET_R(aColor)/255.0
2032 green:NS_GET_G(aColor)/255.0
2033 blue:NS_GET_B(aColor)/255.0
2034 alpha:NS_GET_A(aColor)/255.0]
2035 forActiveWindow:(BOOL)aActive];
2036 }
2037 return NS_OK;
2039 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2040 }
2042 void nsCocoaWindow::SetDrawsInTitlebar(bool aState)
2043 {
2044 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2046 if (mWindow)
2047 [mWindow setDrawsContentsIntoWindowFrame:aState];
2049 NS_OBJC_END_TRY_ABORT_BLOCK;
2050 }
2052 NS_IMETHODIMP nsCocoaWindow::SynthesizeNativeMouseEvent(nsIntPoint aPoint,
2053 uint32_t aNativeMessage,
2054 uint32_t aModifierFlags)
2055 {
2056 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2058 if (mPopupContentView)
2059 return mPopupContentView->SynthesizeNativeMouseEvent(aPoint, aNativeMessage,
2060 aModifierFlags);
2062 return NS_OK;
2064 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2065 }
2067 gfxASurface* nsCocoaWindow::GetThebesSurface()
2068 {
2069 if (mPopupContentView)
2070 return mPopupContentView->GetThebesSurface();
2071 return nullptr;
2072 }
2074 void nsCocoaWindow::SetPopupWindowLevel()
2075 {
2076 if (!mWindow)
2077 return;
2079 // Floating popups are at the floating level and hide when the window is
2080 // deactivated.
2081 if (mPopupLevel == ePopupLevelFloating) {
2082 [mWindow setLevel:NSFloatingWindowLevel];
2083 [mWindow setHidesOnDeactivate:YES];
2084 }
2085 else {
2086 // Otherwise, this is a top-level or parent popup. Parent popups always
2087 // appear just above their parent and essentially ignore the level.
2088 [mWindow setLevel:NSPopUpMenuWindowLevel];
2089 [mWindow setHidesOnDeactivate:NO];
2090 }
2091 }
2093 NS_IMETHODIMP
2094 nsCocoaWindow::NotifyIME(const IMENotification& aIMENotification)
2095 {
2096 switch (aIMENotification.mMessage) {
2097 case NOTIFY_IME_OF_FOCUS:
2098 if (mInputContext.IsPasswordEditor()) {
2099 TextInputHandler::EnableSecureEventInput();
2100 }
2101 return NS_OK;
2102 case NOTIFY_IME_OF_BLUR:
2103 // When we're going to be deactive, we must disable the secure event input
2104 // mode, see the Carbon Event Manager Reference.
2105 TextInputHandler::EnsureSecureEventInputDisabled();
2106 return NS_OK;
2107 default:
2108 return NS_ERROR_NOT_IMPLEMENTED;
2109 }
2110 }
2112 NS_IMETHODIMP_(void)
2113 nsCocoaWindow::SetInputContext(const InputContext& aContext,
2114 const InputContextAction& aAction)
2115 {
2116 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2118 if (mWindow &&
2119 [mWindow firstResponder] == mWindow &&
2120 [mWindow isKeyWindow] &&
2121 [[NSApplication sharedApplication] isActive]) {
2122 if (aContext.IsPasswordEditor()) {
2123 TextInputHandler::EnableSecureEventInput();
2124 } else {
2125 TextInputHandler::EnsureSecureEventInputDisabled();
2126 }
2127 }
2128 mInputContext = aContext;
2130 NS_OBJC_END_TRY_ABORT_BLOCK;
2131 }
2133 NS_IMETHODIMP_(bool)
2134 nsCocoaWindow::ExecuteNativeKeyBinding(NativeKeyBindingsType aType,
2135 const WidgetKeyboardEvent& aEvent,
2136 DoCommandCallback aCallback,
2137 void* aCallbackData)
2138 {
2139 NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
2140 return keyBindings->Execute(aEvent, aCallback, aCallbackData);
2141 }
2144 @implementation WindowDelegate
2146 // We try to find a gecko menu bar to paint. If one does not exist, just paint
2147 // the application menu by itself so that a window doesn't have some other
2148 // window's menu bar.
2149 + (void)paintMenubarForWindow:(NSWindow*)aWindow
2150 {
2151 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2153 // make sure we only act on windows that have this kind of
2154 // object as a delegate
2155 id windowDelegate = [aWindow delegate];
2156 if ([windowDelegate class] != [self class])
2157 return;
2159 nsCocoaWindow* geckoWidget = [windowDelegate geckoWidget];
2160 NS_ASSERTION(geckoWidget, "Window delegate not returning a gecko widget!");
2162 nsMenuBarX* geckoMenuBar = geckoWidget->GetMenuBar();
2163 if (geckoMenuBar) {
2164 geckoMenuBar->Paint();
2165 }
2166 else {
2167 // sometimes we don't have a native application menu early in launching
2168 if (!sApplicationMenu)
2169 return;
2171 NSMenu* mainMenu = [NSApp mainMenu];
2172 NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
2174 // Create a new menu bar.
2175 // We create a GeckoNSMenu because all menu bar NSMenu objects should use that subclass for
2176 // key handling reasons.
2177 GeckoNSMenu* newMenuBar = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar"];
2179 // move the application menu from the existing menu bar to the new one
2180 NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain];
2181 [mainMenu removeItemAtIndex:0];
2182 [newMenuBar insertItem:firstMenuItem atIndex:0];
2183 [firstMenuItem release];
2185 // set our new menu bar as the main menu
2186 [NSApp setMainMenu:newMenuBar];
2187 [newMenuBar release];
2188 }
2190 NS_OBJC_END_TRY_ABORT_BLOCK;
2191 }
2193 - (id)initWithGeckoWindow:(nsCocoaWindow*)geckoWind
2194 {
2195 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
2197 [super init];
2198 mGeckoWindow = geckoWind;
2199 mToplevelActiveState = false;
2200 mHasEverBeenZoomed = false;
2201 return self;
2203 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
2204 }
2206 - (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)proposedFrameSize
2207 {
2208 RollUpPopups();
2210 return proposedFrameSize;
2211 }
2213 - (void)windowDidResize:(NSNotification *)aNotification
2214 {
2215 BaseWindow* window = [aNotification object];
2216 [window updateTrackingArea];
2218 if (!mGeckoWindow)
2219 return;
2221 // Resizing might have changed our zoom state.
2222 mGeckoWindow->DispatchSizeModeEvent();
2223 mGeckoWindow->ReportSizeEvent();
2224 }
2226 - (void)windowDidChangeScreen:(NSNotification *)aNotification
2227 {
2228 if (!mGeckoWindow)
2229 return;
2231 // Because of Cocoa's peculiar treatment of zero-size windows (see comments
2232 // at GetBackingScaleFactor() above), we sometimes have a situation where
2233 // our concept of backing scale (based on the screen where the zero-sized
2234 // window is positioned) differs from Cocoa's idea (always based on the
2235 // Retina screen, AFAICT, even when an external non-Retina screen is the
2236 // primary display).
2237 //
2238 // As a result, if the window was created with zero size on an external
2239 // display, but then made visible on the (secondary) Retina screen, we
2240 // will *not* get a windowDidChangeBackingProperties notification for it.
2241 // This leads to an incorrect GetDefaultScale(), and widget coordinate
2242 // confusion, as per bug 853252.
2243 //
2244 // To work around this, we check for a backing scale mismatch when we
2245 // receive a windowDidChangeScreen notification, as we will receive this
2246 // even if Cocoa was already treating the zero-size window as having
2247 // Retina backing scale.
2248 NSWindow *window = (NSWindow *)[aNotification object];
2249 if ([window respondsToSelector:@selector(backingScaleFactor)]) {
2250 if (GetBackingScaleFactor(window) != mGeckoWindow->BackingScaleFactor()) {
2251 mGeckoWindow->BackingScaleFactorChanged();
2252 }
2253 }
2255 mGeckoWindow->ReportMoveEvent();
2256 }
2258 // Lion's full screen mode will bypass our internal fullscreen tracking, so
2259 // we need to catch it when we transition and call our own methods, which in
2260 // turn will fire "fullscreen" events.
2261 - (void)windowDidEnterFullScreen:(NSNotification *)notification
2262 {
2263 if (!mGeckoWindow) {
2264 return;
2265 }
2267 mGeckoWindow->EnteredFullScreen(true);
2268 }
2270 - (void)windowDidExitFullScreen:(NSNotification *)notification
2271 {
2272 if (!mGeckoWindow) {
2273 return;
2274 }
2276 mGeckoWindow->EnteredFullScreen(false);
2277 }
2279 - (void)windowDidFailToEnterFullScreen:(NSWindow *)window
2280 {
2281 if (!mGeckoWindow) {
2282 return;
2283 }
2285 mGeckoWindow->EnteredFullScreen(false);
2286 }
2288 - (void)windowDidFailToExitFullScreen:(NSWindow *)window
2289 {
2290 if (!mGeckoWindow) {
2291 return;
2292 }
2294 mGeckoWindow->EnteredFullScreen(true);
2295 }
2297 - (void)windowDidBecomeMain:(NSNotification *)aNotification
2298 {
2299 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2301 RollUpPopups();
2302 ChildViewMouseTracker::ReEvaluateMouseEnterState();
2304 // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
2305 // app modally. If one of those is up then we want it to retain its menu bar.
2306 if ([NSApp _isRunningAppModal])
2307 return;
2308 NSWindow* window = [aNotification object];
2309 if (window)
2310 [WindowDelegate paintMenubarForWindow:window];
2312 NS_OBJC_END_TRY_ABORT_BLOCK;
2313 }
2315 - (void)windowDidResignMain:(NSNotification *)aNotification
2316 {
2317 RollUpPopups();
2318 ChildViewMouseTracker::ReEvaluateMouseEnterState();
2320 // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
2321 // app modally. If one of those is up then we want it to retain its menu bar.
2322 if ([NSApp _isRunningAppModal])
2323 return;
2324 nsRefPtr<nsMenuBarX> hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
2325 if (hiddenWindowMenuBar) {
2326 // printf("painting hidden window menu bar due to window losing main status\n");
2327 hiddenWindowMenuBar->Paint();
2328 }
2329 }
2331 - (void)windowDidBecomeKey:(NSNotification *)aNotification
2332 {
2333 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2335 RollUpPopups();
2336 ChildViewMouseTracker::ReEvaluateMouseEnterState();
2338 NSWindow* window = [aNotification object];
2339 if ([window isSheet])
2340 [WindowDelegate paintMenubarForWindow:window];
2342 NS_OBJC_END_TRY_ABORT_BLOCK;
2343 }
2345 - (void)windowDidResignKey:(NSNotification *)aNotification
2346 {
2347 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2349 RollUpPopups();
2350 ChildViewMouseTracker::ReEvaluateMouseEnterState();
2352 // If a sheet just resigned key then we should paint the menu bar
2353 // for whatever window is now main.
2354 NSWindow* window = [aNotification object];
2355 if ([window isSheet])
2356 [WindowDelegate paintMenubarForWindow:[NSApp mainWindow]];
2358 NS_OBJC_END_TRY_ABORT_BLOCK;
2359 }
2361 - (void)windowWillMove:(NSNotification *)aNotification
2362 {
2363 RollUpPopups();
2364 }
2366 - (void)windowDidMove:(NSNotification *)aNotification
2367 {
2368 if (mGeckoWindow)
2369 mGeckoWindow->ReportMoveEvent();
2370 }
2372 - (BOOL)windowShouldClose:(id)sender
2373 {
2374 nsIWidgetListener* listener = mGeckoWindow ? mGeckoWindow->GetWidgetListener() : nullptr;
2375 if (listener)
2376 listener->RequestWindowClose(mGeckoWindow);
2377 return NO; // gecko will do it
2378 }
2380 - (void)windowWillClose:(NSNotification *)aNotification
2381 {
2382 RollUpPopups();
2383 }
2385 - (void)windowWillMiniaturize:(NSNotification *)aNotification
2386 {
2387 RollUpPopups();
2388 }
2390 - (void)windowDidMiniaturize:(NSNotification *)aNotification
2391 {
2392 if (mGeckoWindow)
2393 mGeckoWindow->DispatchSizeModeEvent();
2394 }
2396 - (void)windowDidDeminiaturize:(NSNotification *)aNotification
2397 {
2398 if (mGeckoWindow)
2399 mGeckoWindow->DispatchSizeModeEvent();
2400 }
2402 - (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)proposedFrame
2403 {
2404 if (!mHasEverBeenZoomed && [window isZoomed])
2405 return NO; // See bug 429954.
2407 mHasEverBeenZoomed = YES;
2408 return YES;
2409 }
2411 - (void)didEndSheet:(NSWindow*)sheet returnCode:(int)returnCode contextInfo:(void*)contextInfo
2412 {
2413 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2415 // Note: 'contextInfo' (if it is set) is the window that is the parent of
2416 // the sheet. The value of contextInfo is determined in
2417 // nsCocoaWindow::Show(). If it's set, 'contextInfo' is always the top-
2418 // level window, not another sheet itself. But 'contextInfo' is nil if
2419 // our parent window is also a sheet -- in that case we shouldn't send
2420 // the top-level window any activate events (because it's our parent
2421 // window that needs to get these events, not the top-level window).
2422 [TopLevelWindowData deactivateInWindow:sheet];
2423 [sheet orderOut:self];
2424 if (contextInfo)
2425 [TopLevelWindowData activateInWindow:(NSWindow*)contextInfo];
2427 NS_OBJC_END_TRY_ABORT_BLOCK;
2428 }
2430 - (void)windowDidChangeBackingProperties:(NSNotification *)aNotification
2431 {
2432 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2434 NSWindow *window = (NSWindow *)[aNotification object];
2436 if ([window respondsToSelector:@selector(backingScaleFactor)]) {
2437 CGFloat oldFactor =
2438 [[[aNotification userInfo]
2439 objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue];
2440 if ([window backingScaleFactor] != oldFactor) {
2441 mGeckoWindow->BackingScaleFactorChanged();
2442 }
2443 }
2445 NS_OBJC_END_TRY_ABORT_BLOCK;
2446 }
2448 - (nsCocoaWindow*)geckoWidget
2449 {
2450 return mGeckoWindow;
2451 }
2453 - (bool)toplevelActiveState
2454 {
2455 return mToplevelActiveState;
2456 }
2458 - (void)sendToplevelActivateEvents
2459 {
2460 if (!mToplevelActiveState && mGeckoWindow) {
2461 nsIWidgetListener* listener = mGeckoWindow->GetWidgetListener();
2462 if (listener)
2463 listener->WindowActivated();
2464 mToplevelActiveState = true;
2465 }
2466 }
2468 - (void)sendToplevelDeactivateEvents
2469 {
2470 if (mToplevelActiveState && mGeckoWindow) {
2471 nsIWidgetListener* listener = mGeckoWindow->GetWidgetListener();
2472 if (listener)
2473 listener->WindowDeactivated();
2474 mToplevelActiveState = false;
2475 }
2476 }
2478 @end
2480 static float
2481 GetDPI(NSWindow* aWindow)
2482 {
2483 NSScreen* screen = [aWindow screen];
2484 if (!screen)
2485 return 96.0f;
2487 CGDirectDisplayID displayID =
2488 [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] intValue];
2489 CGFloat heightMM = ::CGDisplayScreenSize(displayID).height;
2490 size_t heightPx = ::CGDisplayPixelsHigh(displayID);
2491 CGFloat scaleFactor = [aWindow userSpaceScaleFactor];
2492 if (scaleFactor < 0.01 || heightMM < 1 || heightPx < 1) {
2493 // Something extremely bogus is going on
2494 return 96.0f;
2495 }
2497 // Currently we don't do our own scaling to take account
2498 // of userSpaceScaleFactor, so every "pixel" we draw is actually
2499 // userSpaceScaleFactor screen pixels. So divide the screen height
2500 // by userSpaceScaleFactor to get the number of "device pixels"
2501 // available.
2502 float dpi = (heightPx / scaleFactor) / (heightMM / MM_PER_INCH_FLOAT);
2504 // Account for HiDPI mode where Cocoa's "points" do not correspond to real
2505 // device pixels
2506 CGFloat backingScale = GetBackingScaleFactor(aWindow);
2508 return dpi * backingScale;
2509 }
2511 @interface NSView(FrameViewMethodSwizzling)
2512 - (NSPoint)FrameView__closeButtonOrigin;
2513 - (NSPoint)FrameView__fullScreenButtonOrigin;
2514 @end
2516 @implementation NSView(FrameViewMethodSwizzling)
2518 - (NSPoint)FrameView__closeButtonOrigin
2519 {
2520 NSPoint defaultPosition = [self FrameView__closeButtonOrigin];
2521 if ([[self window] isKindOfClass:[ToolbarWindow class]]) {
2522 return [(ToolbarWindow*)[self window] windowButtonsPositionWithDefaultPosition:defaultPosition];
2523 }
2524 return defaultPosition;
2525 }
2527 - (NSPoint)FrameView__fullScreenButtonOrigin
2528 {
2529 NSPoint defaultPosition = [self FrameView__fullScreenButtonOrigin];
2530 if ([[self window] isKindOfClass:[ToolbarWindow class]]) {
2531 return [(ToolbarWindow*)[self window] fullScreenButtonPositionWithDefaultPosition:defaultPosition];
2532 }
2533 return defaultPosition;
2534 }
2536 @end
2538 static NSMutableSet *gSwizzledFrameViewClasses = nil;
2540 @interface NSWindow(PrivateSetNeedsDisplayInRectMethod)
2541 - (void)_setNeedsDisplayInRect:(NSRect)aRect;
2542 @end
2544 @interface BaseWindow(Private)
2545 - (void)removeTrackingArea;
2546 - (void)cursorUpdated:(NSEvent*)aEvent;
2547 - (void)updateContentViewSize;
2548 - (void)reflowTitlebarElements;
2549 @end
2551 @implementation BaseWindow
2553 // The frame of a window is implemented using undocumented NSView subclasses.
2554 // We offset the window buttons by overriding the methods _closeButtonOrigin
2555 // and _fullScreenButtonOrigin on these frame view classes. The class which is
2556 // used for a window is determined in the window's frameViewClassForStyleMask:
2557 // method, so this is where we make sure that we have swizzled the method on
2558 // all encountered classes.
2559 + (Class)frameViewClassForStyleMask:(NSUInteger)styleMask
2560 {
2561 Class frameViewClass = [super frameViewClassForStyleMask:styleMask];
2563 if (!gSwizzledFrameViewClasses) {
2564 gSwizzledFrameViewClasses = [[NSMutableSet setWithCapacity:3] retain];
2565 if (!gSwizzledFrameViewClasses) {
2566 return frameViewClass;
2567 }
2568 }
2570 static IMP our_closeButtonOrigin =
2571 class_getMethodImplementation([NSView class],
2572 @selector(FrameView__closeButtonOrigin));
2573 static IMP our_fullScreenButtonOrigin =
2574 class_getMethodImplementation([NSView class],
2575 @selector(FrameView__fullScreenButtonOrigin));
2577 if (![gSwizzledFrameViewClasses containsObject:frameViewClass]) {
2578 // Either of these methods might be implemented in both a subclass of
2579 // NSFrameView and one of its own subclasses. Which means that if we
2580 // aren't careful we might end up swizzling the same method twice.
2581 // Since method swizzling involves swapping pointers, this would break
2582 // things.
2583 IMP _closeButtonOrigin =
2584 class_getMethodImplementation(frameViewClass,
2585 @selector(_closeButtonOrigin));
2586 if (_closeButtonOrigin && _closeButtonOrigin != our_closeButtonOrigin) {
2587 nsToolkit::SwizzleMethods(frameViewClass, @selector(_closeButtonOrigin),
2588 @selector(FrameView__closeButtonOrigin));
2589 }
2590 IMP _fullScreenButtonOrigin =
2591 class_getMethodImplementation(frameViewClass,
2592 @selector(_fullScreenButtonOrigin));
2593 if (_fullScreenButtonOrigin &&
2594 _fullScreenButtonOrigin != our_fullScreenButtonOrigin) {
2595 nsToolkit::SwizzleMethods(frameViewClass, @selector(_fullScreenButtonOrigin),
2596 @selector(FrameView__fullScreenButtonOrigin));
2597 }
2598 [gSwizzledFrameViewClasses addObject:frameViewClass];
2599 }
2601 return frameViewClass;
2602 }
2604 - (id)initWithContentRect:(NSRect)aContentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)aBufferingType defer:(BOOL)aFlag
2605 {
2606 mDrawsIntoWindowFrame = NO;
2607 [super initWithContentRect:aContentRect styleMask:aStyle backing:aBufferingType defer:aFlag];
2608 mState = nil;
2609 mActiveTitlebarColor = nil;
2610 mInactiveTitlebarColor = nil;
2611 mScheduledShadowInvalidation = NO;
2612 mDisabledNeedsDisplay = NO;
2613 mDPI = GetDPI(self);
2614 mTrackingArea = nil;
2615 mBeingShown = NO;
2616 mDrawTitle = NO;
2617 [self updateTrackingArea];
2619 return self;
2620 }
2622 - (void)setBeingShown:(BOOL)aValue
2623 {
2624 mBeingShown = aValue;
2625 }
2627 - (BOOL)isBeingShown
2628 {
2629 return mBeingShown;
2630 }
2632 - (BOOL)isVisibleOrBeingShown
2633 {
2634 return [super isVisible] || mBeingShown;
2635 }
2637 - (void)disableSetNeedsDisplay
2638 {
2639 mDisabledNeedsDisplay = YES;
2640 }
2642 - (void)enableSetNeedsDisplay
2643 {
2644 mDisabledNeedsDisplay = NO;
2645 }
2647 - (void)dealloc
2648 {
2649 [mActiveTitlebarColor release];
2650 [mInactiveTitlebarColor release];
2651 [self removeTrackingArea];
2652 ChildViewMouseTracker::OnDestroyWindow(self);
2653 [super dealloc];
2654 }
2656 static const NSString* kStateTitleKey = @"title";
2657 static const NSString* kStateDrawsContentsIntoWindowFrameKey = @"drawsContentsIntoWindowFrame";
2658 static const NSString* kStateActiveTitlebarColorKey = @"activeTitlebarColor";
2659 static const NSString* kStateInactiveTitlebarColorKey = @"inactiveTitlebarColor";
2660 static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
2662 - (void)importState:(NSDictionary*)aState
2663 {
2664 [self setTitle:[aState objectForKey:kStateTitleKey]];
2665 [self setDrawsContentsIntoWindowFrame:[[aState objectForKey:kStateDrawsContentsIntoWindowFrameKey] boolValue]];
2666 [self setTitlebarColor:[aState objectForKey:kStateActiveTitlebarColorKey] forActiveWindow:YES];
2667 [self setTitlebarColor:[aState objectForKey:kStateInactiveTitlebarColorKey] forActiveWindow:NO];
2668 [self setShowsToolbarButton:[[aState objectForKey:kStateShowsToolbarButton] boolValue]];
2669 }
2671 - (NSMutableDictionary*)exportState
2672 {
2673 NSMutableDictionary* state = [NSMutableDictionary dictionaryWithCapacity:10];
2674 [state setObject:[self title] forKey:kStateTitleKey];
2675 [state setObject:[NSNumber numberWithBool:[self drawsContentsIntoWindowFrame]]
2676 forKey:kStateDrawsContentsIntoWindowFrameKey];
2677 NSColor* activeTitlebarColor = [self titlebarColorForActiveWindow:YES];
2678 if (activeTitlebarColor) {
2679 [state setObject:activeTitlebarColor forKey:kStateActiveTitlebarColorKey];
2680 }
2681 NSColor* inactiveTitlebarColor = [self titlebarColorForActiveWindow:NO];
2682 if (inactiveTitlebarColor) {
2683 [state setObject:inactiveTitlebarColor forKey:kStateInactiveTitlebarColorKey];
2684 }
2685 [state setObject:[NSNumber numberWithBool:[self showsToolbarButton]]
2686 forKey:kStateShowsToolbarButton];
2687 return state;
2688 }
2690 - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState
2691 {
2692 bool changed = (aState != mDrawsIntoWindowFrame);
2693 mDrawsIntoWindowFrame = aState;
2694 if (changed) {
2695 [self updateContentViewSize];
2696 [self reflowTitlebarElements];
2697 }
2698 }
2700 - (BOOL)drawsContentsIntoWindowFrame
2701 {
2702 return mDrawsIntoWindowFrame;
2703 }
2705 - (void)setWantsTitleDrawn:(BOOL)aDrawTitle
2706 {
2707 mDrawTitle = aDrawTitle;
2708 }
2710 - (BOOL)wantsTitleDrawn
2711 {
2712 return mDrawTitle;
2713 }
2715 // Pass nil here to get the default appearance.
2716 - (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive
2717 {
2718 [aColor retain];
2719 if (aActive) {
2720 [mActiveTitlebarColor release];
2721 mActiveTitlebarColor = aColor;
2722 } else {
2723 [mInactiveTitlebarColor release];
2724 mInactiveTitlebarColor = aColor;
2725 }
2726 }
2728 - (NSColor*)titlebarColorForActiveWindow:(BOOL)aActive
2729 {
2730 return aActive ? mActiveTitlebarColor : mInactiveTitlebarColor;
2731 }
2733 - (void)deferredInvalidateShadow
2734 {
2735 if (mScheduledShadowInvalidation || [self isOpaque] || ![self hasShadow])
2736 return;
2738 [self performSelector:@selector(invalidateShadow) withObject:nil afterDelay:0];
2739 mScheduledShadowInvalidation = YES;
2740 }
2742 - (void)invalidateShadow
2743 {
2744 [super invalidateShadow];
2745 mScheduledShadowInvalidation = NO;
2746 }
2748 - (float)getDPI
2749 {
2750 return mDPI;
2751 }
2753 - (NSView*)trackingAreaView
2754 {
2755 NSView* contentView = [self contentView];
2756 return [contentView superview] ? [contentView superview] : contentView;
2757 }
2759 - (ChildView*)mainChildView
2760 {
2761 NSView *contentView = [self contentView];
2762 // A PopupWindow's contentView is a ChildView object.
2763 if ([contentView isKindOfClass:[ChildView class]]) {
2764 return (ChildView*)contentView;
2765 }
2766 NSView* lastView = [[contentView subviews] lastObject];
2767 if ([lastView isKindOfClass:[ChildView class]]) {
2768 return (ChildView*)lastView;
2769 }
2770 return nil;
2771 }
2773 - (void)removeTrackingArea
2774 {
2775 if (mTrackingArea) {
2776 [[self trackingAreaView] removeTrackingArea:mTrackingArea];
2777 [mTrackingArea release];
2778 mTrackingArea = nil;
2779 }
2780 }
2782 - (void)updateTrackingArea
2783 {
2784 [self removeTrackingArea];
2786 NSView* view = [self trackingAreaView];
2787 const NSTrackingAreaOptions options =
2788 NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways;
2789 mTrackingArea = [[NSTrackingArea alloc] initWithRect:[view bounds]
2790 options:options
2791 owner:self
2792 userInfo:nil];
2793 [view addTrackingArea:mTrackingArea];
2794 }
2796 - (void)mouseEntered:(NSEvent*)aEvent
2797 {
2798 ChildViewMouseTracker::MouseEnteredWindow(aEvent);
2799 }
2801 - (void)mouseExited:(NSEvent*)aEvent
2802 {
2803 ChildViewMouseTracker::MouseExitedWindow(aEvent);
2804 }
2806 - (void)mouseMoved:(NSEvent*)aEvent
2807 {
2808 ChildViewMouseTracker::MouseMoved(aEvent);
2809 }
2811 - (void)cursorUpdated:(NSEvent*)aEvent
2812 {
2813 // Nothing to do here, but NSTrackingArea wants us to implement this method.
2814 }
2816 - (void)_setNeedsDisplayInRect:(NSRect)aRect
2817 {
2818 // Prevent unnecessary invalidations due to moving NSViews (e.g. for plugins)
2819 if (!mDisabledNeedsDisplay) {
2820 // This method is only called by Cocoa, so when we're here, we know that
2821 // it's available and don't need to check whether our superclass responds
2822 // to the selector.
2823 [super _setNeedsDisplayInRect:aRect];
2824 }
2825 }
2827 - (void)updateContentViewSize
2828 {
2829 NSRect rect = [self contentRectForFrameRect:[self frame]];
2830 [[self contentView] setFrameSize:rect.size];
2831 }
2833 // Possibly move the titlebar buttons.
2834 - (void)reflowTitlebarElements
2835 {
2836 NSView *frameView = [[self contentView] superview];
2837 if ([frameView respondsToSelector:@selector(_tileTitlebarAndRedisplay:)]) {
2838 [frameView _tileTitlebarAndRedisplay:NO];
2839 }
2840 }
2842 // Override methods that translate between content rect and frame rect.
2843 - (NSRect)contentRectForFrameRect:(NSRect)aRect
2844 {
2845 if ([self drawsContentsIntoWindowFrame]) {
2846 return aRect;
2847 }
2848 return [super contentRectForFrameRect:aRect];
2849 }
2851 - (NSRect)contentRectForFrameRect:(NSRect)aRect styleMask:(NSUInteger)aMask
2852 {
2853 if ([self drawsContentsIntoWindowFrame]) {
2854 return aRect;
2855 }
2856 if ([super respondsToSelector:@selector(contentRectForFrameRect:styleMask:)]) {
2857 return [super contentRectForFrameRect:aRect styleMask:aMask];
2858 } else {
2859 return [NSWindow contentRectForFrameRect:aRect styleMask:aMask];
2860 }
2861 }
2863 - (NSRect)frameRectForContentRect:(NSRect)aRect
2864 {
2865 if ([self drawsContentsIntoWindowFrame]) {
2866 return aRect;
2867 }
2868 return [super frameRectForContentRect:aRect];
2869 }
2871 - (NSRect)frameRectForContentRect:(NSRect)aRect styleMask:(NSUInteger)aMask
2872 {
2873 if ([self drawsContentsIntoWindowFrame]) {
2874 return aRect;
2875 }
2876 if ([super respondsToSelector:@selector(frameRectForContentRect:styleMask:)]) {
2877 return [super frameRectForContentRect:aRect styleMask:aMask];
2878 } else {
2879 return [NSWindow frameRectForContentRect:aRect styleMask:aMask];
2880 }
2881 }
2883 - (void)setContentView:(NSView*)aView
2884 {
2885 [super setContentView:aView];
2887 // Now move the contentView to the bottommost layer so that it's guaranteed
2888 // to be under the window buttons.
2889 NSView* frameView = [aView superview];
2890 [aView removeFromSuperview];
2891 [frameView addSubview:aView positioned:NSWindowBelow relativeTo:nil];
2892 }
2894 - (NSArray*)titlebarControls
2895 {
2896 // Return all subviews of the frameView which are not the content view.
2897 NSView* frameView = [[self contentView] superview];
2898 NSMutableArray* array = [[[frameView subviews] mutableCopy] autorelease];
2899 [array removeObject:[self contentView]];
2900 return array;
2901 }
2903 - (BOOL)respondsToSelector:(SEL)aSelector
2904 {
2905 // Claim the window doesn't respond to this so that the system
2906 // doesn't steal keyboard equivalents for it. Bug 613710.
2907 if (aSelector == @selector(cancelOperation:)) {
2908 return NO;
2909 }
2911 return [super respondsToSelector:aSelector];
2912 }
2914 - (void) doCommandBySelector:(SEL)aSelector
2915 {
2916 // We override this so that it won't beep if it can't act.
2917 // We want to control the beeping for missing or disabled
2918 // commands ourselves.
2919 [self tryToPerform:aSelector with:nil];
2920 }
2922 - (id)accessibilityAttributeValue:(NSString *)attribute
2923 {
2924 id retval = [super accessibilityAttributeValue:attribute];
2926 // The following works around a problem with Text-to-Speech on OS X 10.7.
2927 // See bug 674612 for more info.
2928 //
2929 // When accessibility is off, AXUIElementCopyAttributeValue(), when called
2930 // on an AXApplication object to get its AXFocusedUIElement attribute,
2931 // always returns an AXWindow object (the actual browser window -- never a
2932 // mozAccessible object). This also happens with accessibility turned on,
2933 // if no other object in the browser window has yet been focused. But if
2934 // the browser window has a title bar (as it currently always does), the
2935 // AXWindow object will always have four "accessible" children, one of which
2936 // is an AXStaticText object (the title bar's "title"; the other three are
2937 // the close, minimize and zoom buttons). This means that (for complicated
2938 // reasons, for which see bug 674612) Text-to-Speech on OS X 10.7 will often
2939 // "speak" the window title, no matter what text is selected, or even if no
2940 // text at all is selected. (This always happens when accessibility is off.
2941 // It doesn't happen in Firefox releases because Apple has (on OS X 10.7)
2942 // special-cased the handling of apps whose CFBundleIdentifier is
2943 // org.mozilla.firefox.)
2944 //
2945 // We work around this problem by only returning AXChildren that are
2946 // mozAccessible object or are one of the titlebar's buttons (which
2947 // instantiate subclasses of NSButtonCell).
2948 if (nsCocoaFeatures::OnLionOrLater() && [retval isKindOfClass:[NSArray class]] &&
2949 [attribute isEqualToString:@"AXChildren"]) {
2950 NSMutableArray *holder = [NSMutableArray arrayWithCapacity:10];
2951 [holder addObjectsFromArray:(NSArray *)retval];
2952 NSUInteger count = [holder count];
2953 for (NSInteger i = count - 1; i >= 0; --i) {
2954 id item = [holder objectAtIndex:i];
2955 // Remove anything from holder that isn't one of the titlebar's buttons
2956 // (which instantiate subclasses of NSButtonCell) or a mozAccessible
2957 // object (or one of its subclasses).
2958 if (![item isKindOfClass:[NSButtonCell class]] &&
2959 ![item respondsToSelector:@selector(hasRepresentedView)]) {
2960 [holder removeObjectAtIndex:i];
2961 }
2962 }
2963 retval = [NSArray arrayWithArray:holder];
2964 }
2966 return retval;
2967 }
2969 // If we were built on OS X 10.6 or with the 10.6 SDK and are running on Lion,
2970 // the OS (specifically -[NSWindow sendEvent:]) won't send NSEventTypeGesture
2971 // events to -[ChildView magnifyWithEvent:] as it should. The following code
2972 // gets around this. See bug 863841.
2973 #if !defined( MAC_OS_X_VERSION_10_7 ) || \
2974 ( MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 )
2975 - (void)sendEvent:(NSEvent *)anEvent
2976 {
2977 if ([ChildView isLionSmartMagnifyEvent: anEvent]) {
2978 [[self mainChildView] magnifyWithEvent:anEvent];
2979 return;
2980 }
2981 [super sendEvent:anEvent];
2982 }
2983 #endif
2985 @end
2987 // This class allows us to exercise control over the window's title bar. This
2988 // allows for a "unified toolbar" look without having to extend the content
2989 // area into the title bar. It works like this:
2990 // 1) We set the window's style to textured.
2991 // 2) Because of this, the background color applies to the entire window, including
2992 // the titlebar area. For normal textured windows, the default pattern is a
2993 // "brushed metal" image on Tiger and a unified gradient on Leopard.
2994 // 3) We set the background color to a custom NSColor subclass that knows how tall the window is.
2995 // When -set is called on it, it sets a pattern (with a draw callback) as the fill. In that callback,
2996 // it paints the the titlebar and background colors in the correct areas of the context it's given,
2997 // which will fill the entire window (CG will tile it horizontally for us).
2998 // 4) Whenever the window's main state changes and when [window display] is called,
2999 // Cocoa redraws the titlebar using the patternDraw callback function.
3000 //
3001 // This class also provides us with a pill button to show/hide the toolbar up to 10.6.
3002 //
3003 // Drawing the unified gradient in the titlebar and the toolbar works like this:
3004 // 1) In the style sheet we set the toolbar's -moz-appearance to toolbar or
3005 // -moz-mac-unified-toolbar.
3006 // 2) When the toolbar is visible and we paint the application chrome
3007 // window, the array that Gecko passes nsChildView::UpdateThemeGeometries
3008 // will contain an entry for the widget type NS_THEME_TOOLBAR or
3009 // NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR.
3010 // 3) nsChildView::UpdateThemeGeometries finds the toolbar frame's ToolbarWindow
3011 // and passes the toolbar frame's height to setUnifiedToolbarHeight.
3012 // 4) If the toolbar height has changed, a titlebar redraw is triggered and the
3013 // upper part of the unified gradient is drawn in the titlebar.
3014 // 5) The lower part of the unified gradient in the toolbar is drawn during
3015 // normal window content painting in nsNativeThemeCocoa::DrawUnifiedToolbar.
3016 //
3017 // Whenever the unified gradient is drawn in the titlebar or the toolbar, both
3018 // titlebar height and toolbar height must be known in order to construct the
3019 // correct gradient. But you can only get from the toolbar frame
3020 // to the containing window - the other direction doesn't work. That's why the
3021 // toolbar height is cached in the ToolbarWindow but nsNativeThemeCocoa can simply
3022 // query the window for its titlebar height when drawing the toolbar.
3023 //
3024 // Note that in drawsContentsIntoWindowFrame mode, titlebar drawing works in a
3025 // completely different way: In that mode, the window's mainChildView will
3026 // cover the titlebar completely and nothing that happens in the window
3027 // background will reach the screen.
3028 @implementation ToolbarWindow
3030 - (id)initWithContentRect:(NSRect)aContentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)aBufferingType defer:(BOOL)aFlag
3031 {
3032 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
3034 aStyle = aStyle | NSTexturedBackgroundWindowMask;
3035 if ((self = [super initWithContentRect:aContentRect styleMask:aStyle backing:aBufferingType defer:aFlag])) {
3036 mColor = [[TitlebarAndBackgroundColor alloc] initWithWindow:self];
3037 // Bypass our guard method below.
3038 [super setBackgroundColor:mColor];
3039 mBackgroundColor = [[NSColor whiteColor] retain];
3041 mUnifiedToolbarHeight = 22.0f;
3042 mWindowButtonsRect = NSZeroRect;
3043 mFullScreenButtonRect = NSZeroRect;
3045 // setBottomCornerRounded: is a private API call, so we check to make sure
3046 // we respond to it just in case.
3047 if ([self respondsToSelector:@selector(setBottomCornerRounded:)])
3048 [self setBottomCornerRounded:nsCocoaFeatures::OnLionOrLater()];
3050 [self setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
3051 [self setContentBorderThickness:0.0f forEdge:NSMaxYEdge];
3052 }
3053 return self;
3055 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
3056 }
3058 - (void)dealloc
3059 {
3060 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3062 [mColor release];
3063 [mBackgroundColor release];
3064 [mTitlebarView release];
3065 [super dealloc];
3067 NS_OBJC_END_TRY_ABORT_BLOCK;
3068 }
3070 - (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive
3071 {
3072 [super setTitlebarColor:aColor forActiveWindow:aActive];
3073 [self setTitlebarNeedsDisplayInRect:[self titlebarRect]];
3074 }
3076 - (void)setBackgroundColor:(NSColor*)aColor
3077 {
3078 [aColor retain];
3079 [mBackgroundColor release];
3080 mBackgroundColor = aColor;
3081 }
3083 - (NSColor*)windowBackgroundColor
3084 {
3085 return mBackgroundColor;
3086 }
3088 - (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect
3089 {
3090 [self setTitlebarNeedsDisplayInRect:aRect sync:NO];
3091 }
3093 - (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect sync:(BOOL)aSync
3094 {
3095 NSRect titlebarRect = [self titlebarRect];
3096 NSRect rect = NSIntersectionRect(titlebarRect, aRect);
3097 if (NSIsEmptyRect(rect))
3098 return;
3100 NSView* borderView = [[self contentView] superview];
3101 if (!borderView)
3102 return;
3104 if (aSync) {
3105 [borderView displayRect:rect];
3106 } else {
3107 [borderView setNeedsDisplayInRect:rect];
3108 }
3109 }
3111 - (NSRect)titlebarRect
3112 {
3113 CGFloat titlebarHeight = [self titlebarHeight];
3114 return NSMakeRect(0, [self frame].size.height - titlebarHeight,
3115 [self frame].size.width, titlebarHeight);
3116 }
3118 // Returns the unified height of titlebar + toolbar.
3119 - (CGFloat)unifiedToolbarHeight
3120 {
3121 return mUnifiedToolbarHeight;
3122 }
3124 - (CGFloat)titlebarHeight
3125 {
3126 // We use the original content rect here, not what we return from
3127 // [self contentRectForFrameRect:], because that would give us a
3128 // titlebarHeight of zero in drawsContentsIntoWindowFrame mode.
3129 NSRect frameRect = [self frame];
3130 NSRect originalContentRect = [NSWindow contentRectForFrameRect:frameRect styleMask:[self styleMask]];
3131 return NSMaxY(frameRect) - NSMaxY(originalContentRect);
3132 }
3134 // Stores the complete height of titlebar + toolbar.
3135 - (void)setUnifiedToolbarHeight:(CGFloat)aHeight
3136 {
3137 if (aHeight == mUnifiedToolbarHeight)
3138 return;
3140 mUnifiedToolbarHeight = aHeight;
3142 // Update sheet positioning hint
3143 CGFloat topMargin = mUnifiedToolbarHeight - [self titlebarHeight];
3144 [self setContentBorderThickness:topMargin forEdge:NSMaxYEdge];
3146 // Redraw the title bar. If we're inside painting, we'll do it right now,
3147 // otherwise we'll just invalidate it.
3148 BOOL needSyncRedraw = ([NSView focusView] != nil);
3149 [self setTitlebarNeedsDisplayInRect:[self titlebarRect] sync:needSyncRedraw];
3150 }
3152 // Extending the content area into the title bar works by resizing the
3153 // mainChildView so that it covers the titlebar.
3154 - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState
3155 {
3156 BOOL stateChanged = ([self drawsContentsIntoWindowFrame] != aState);
3157 [super setDrawsContentsIntoWindowFrame:aState];
3158 if (stateChanged && [[self delegate] isKindOfClass:[WindowDelegate class]]) {
3159 // Here we extend / shrink our mainChildView. We do that by firing a resize
3160 // event which will cause the ChildView to be resized to the rect returned
3161 // by nsCocoaWindow::GetClientBounds. GetClientBounds bases its return
3162 // value on what we return from drawsContentsIntoWindowFrame.
3163 WindowDelegate *windowDelegate = (WindowDelegate *)[self delegate];
3164 nsCocoaWindow *geckoWindow = [windowDelegate geckoWidget];
3165 if (geckoWindow) {
3166 // Re-layout our contents.
3167 geckoWindow->ReportSizeEvent();
3168 }
3170 // Resizing the content area causes a reflow which would send a synthesized
3171 // mousemove event to the old mouse position relative to the top left
3172 // corner of the content area. But the mouse has shifted relative to the
3173 // content area, so that event would have wrong position information. So
3174 // we'll send a mouse move event with the correct new position.
3175 ChildViewMouseTracker::ResendLastMouseMoveEvent();
3176 }
3177 }
3179 - (void)setWantsTitleDrawn:(BOOL)aDrawTitle
3180 {
3181 [super setWantsTitleDrawn:aDrawTitle];
3182 [self setTitlebarNeedsDisplayInRect:[self titlebarRect]];
3183 }
3185 - (void)placeWindowButtons:(NSRect)aRect
3186 {
3187 if (!NSEqualRects(mWindowButtonsRect, aRect)) {
3188 mWindowButtonsRect = aRect;
3189 [self reflowTitlebarElements];
3190 }
3191 }
3193 - (NSPoint)windowButtonsPositionWithDefaultPosition:(NSPoint)aDefaultPosition
3194 {
3195 if ([self drawsContentsIntoWindowFrame] && !NSIsEmptyRect(mWindowButtonsRect)) {
3196 return NSMakePoint(std::max(mWindowButtonsRect.origin.x, aDefaultPosition.x),
3197 std::min(mWindowButtonsRect.origin.y, aDefaultPosition.y));
3198 }
3199 return aDefaultPosition;
3200 }
3202 - (void)placeFullScreenButton:(NSRect)aRect
3203 {
3204 if (!NSEqualRects(mFullScreenButtonRect, aRect)) {
3205 mFullScreenButtonRect = aRect;
3206 [self reflowTitlebarElements];
3207 }
3208 }
3210 - (NSPoint)fullScreenButtonPositionWithDefaultPosition:(NSPoint)aDefaultPosition;
3211 {
3212 if ([self drawsContentsIntoWindowFrame] && !NSIsEmptyRect(mFullScreenButtonRect)) {
3213 return NSMakePoint(std::min(mFullScreenButtonRect.origin.x, aDefaultPosition.x),
3214 std::min(mFullScreenButtonRect.origin.y, aDefaultPosition.y));
3215 }
3216 return aDefaultPosition;
3217 }
3219 // Returning YES here makes the setShowsToolbarButton method work even though
3220 // the window doesn't contain an NSToolbar.
3221 - (BOOL)_hasToolbar
3222 {
3223 return YES;
3224 }
3226 // Dispatch a toolbar pill button clicked message to Gecko.
3227 - (void)_toolbarPillButtonClicked:(id)sender
3228 {
3229 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3231 RollUpPopups();
3233 if ([[self delegate] isKindOfClass:[WindowDelegate class]]) {
3234 WindowDelegate *windowDelegate = (WindowDelegate *)[self delegate];
3235 nsCocoaWindow *geckoWindow = [windowDelegate geckoWidget];
3236 if (!geckoWindow)
3237 return;
3239 nsIWidgetListener* listener = geckoWindow->GetWidgetListener();
3240 if (listener)
3241 listener->OSToolbarButtonPressed();
3242 }
3244 NS_OBJC_END_TRY_ABORT_BLOCK;
3245 }
3247 // Retain and release "self" to avoid crashes when our widget (and its native
3248 // window) is closed as a result of processing a key equivalent (e.g.
3249 // Command+w or Command+q). This workaround is only needed for a window
3250 // that can become key.
3251 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent
3252 {
3253 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3255 NSWindow *nativeWindow = [self retain];
3256 BOOL retval = [super performKeyEquivalent:theEvent];
3257 [nativeWindow release];
3258 return retval;
3260 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
3261 }
3263 - (void)sendEvent:(NSEvent *)anEvent
3264 {
3265 NSEventType type = [anEvent type];
3267 switch (type) {
3268 case NSScrollWheel:
3269 case NSLeftMouseDown:
3270 case NSLeftMouseUp:
3271 case NSRightMouseDown:
3272 case NSRightMouseUp:
3273 case NSOtherMouseDown:
3274 case NSOtherMouseUp:
3275 case NSMouseMoved:
3276 case NSLeftMouseDragged:
3277 case NSRightMouseDragged:
3278 case NSOtherMouseDragged:
3279 {
3280 // Drop all mouse events if a modal window has appeared above us.
3281 // This helps make us behave as if the OS were running a "real" modal
3282 // event loop.
3283 id delegate = [self delegate];
3284 if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
3285 nsCocoaWindow *widget = [(WindowDelegate *)delegate geckoWidget];
3286 if (widget) {
3287 if (type == NSMouseMoved) {
3288 [[self mainChildView] updateWindowDraggableStateOnMouseMove:anEvent];
3289 }
3290 if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window))
3291 return;
3292 if (widget->HasModalDescendents())
3293 return;
3294 }
3295 }
3296 break;
3297 }
3298 default:
3299 break;
3300 }
3302 [super sendEvent:anEvent];
3303 }
3305 @end
3307 // Custom NSColor subclass where most of the work takes place for drawing in
3308 // the titlebar area. Not used in drawsContentsIntoWindowFrame mode.
3309 @implementation TitlebarAndBackgroundColor
3311 - (id)initWithWindow:(ToolbarWindow*)aWindow
3312 {
3313 if ((self = [super init])) {
3314 mWindow = aWindow; // weak ref to avoid a cycle
3315 }
3316 return self;
3317 }
3319 static void
3320 DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
3321 CGFloat aUnifiedToolbarHeight, BOOL aIsMain)
3322 {
3323 if (aTitlebarRect.size.width * aTitlebarRect.size.height > CUIDRAW_MAX_AREA) {
3324 return;
3325 }
3327 CUIDraw([NSWindow coreUIRenderer], aTitlebarRect, aContext,
3328 (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
3329 @"kCUIWidgetWindowFrame", @"widget",
3330 @"regularwin", @"windowtype",
3331 (aIsMain ? @"normal" : @"inactive"), @"state",
3332 [NSNumber numberWithDouble:aUnifiedToolbarHeight], @"kCUIWindowFrameUnifiedTitleBarHeightKey",
3333 [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawTitleSeparatorKey",
3334 nil],
3335 nil);
3337 if (nsCocoaFeatures::OnLionOrLater()) {
3338 // On Lion the call to CUIDraw doesn't draw the top pixel strip at some
3339 // window widths. We don't want to have a flickering transparent line, so
3340 // we overdraw it.
3341 CGContextSetRGBFillColor(aContext, 0.95, 0.95, 0.95, 1);
3342 CGContextFillRect(aContext, CGRectMake(0, CGRectGetMaxY(aTitlebarRect) - 1,
3343 aTitlebarRect.size.width, 1));
3344 }
3345 }
3347 // Pattern draw callback for standard titlebar gradients and solid titlebar colors
3348 static void
3349 TitlebarDrawCallback(void* aInfo, CGContextRef aContext)
3350 {
3351 ToolbarWindow *window = (ToolbarWindow*)aInfo;
3352 if (![window drawsContentsIntoWindowFrame]) {
3353 NSRect titlebarRect = [window titlebarRect];
3354 BOOL isMain = [window isMainWindow];
3355 NSColor *titlebarColor = [window titlebarColorForActiveWindow:isMain];
3356 if (!titlebarColor) {
3357 // If the titlebar color is nil, draw the default titlebar shading.
3358 DrawNativeTitlebar(aContext, NSRectToCGRect(titlebarRect),
3359 [window unifiedToolbarHeight], isMain);
3360 } else {
3361 // If the titlebar color is not nil, just set and draw it normally.
3362 [NSGraphicsContext saveGraphicsState];
3363 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:aContext flipped:NO]];
3364 [titlebarColor set];
3365 NSRectFill(titlebarRect);
3366 [NSGraphicsContext restoreGraphicsState];
3367 }
3368 }
3369 }
3371 - (void)setFill
3372 {
3373 float patternWidth = [mWindow frame].size.width;
3375 CGPatternCallbacks callbacks = {0, &TitlebarDrawCallback, NULL};
3376 CGPatternRef pattern = CGPatternCreate(mWindow, CGRectMake(0.0f, 0.0f, patternWidth, [mWindow frame].size.height),
3377 CGAffineTransformIdentity, patternWidth, [mWindow frame].size.height,
3378 kCGPatternTilingConstantSpacing, true, &callbacks);
3380 // Set the pattern as the fill, which is what we were asked to do. All our
3381 // drawing will take place in the patternDraw callback.
3382 CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
3383 CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
3384 CGContextSetFillColorSpace(context, patternSpace);
3385 CGColorSpaceRelease(patternSpace);
3386 CGFloat component = 1.0f;
3387 CGContextSetFillPattern(context, pattern, &component);
3388 CGPatternRelease(pattern);
3389 }
3391 - (void)set
3392 {
3393 [self setFill];
3394 }
3396 - (NSString*)colorSpaceName
3397 {
3398 return NSDeviceRGBColorSpace;
3399 }
3401 @end
3403 @implementation PopupWindow
3405 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
3406 backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation
3407 {
3408 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
3410 mIsContextMenu = false;
3411 return [super initWithContentRect:contentRect styleMask:styleMask
3412 backing:bufferingType defer:deferCreation];
3414 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
3415 }
3417 - (BOOL)isContextMenu
3418 {
3419 return mIsContextMenu;
3420 }
3422 - (void)setIsContextMenu:(BOOL)flag
3423 {
3424 mIsContextMenu = flag;
3425 }
3427 - (BOOL)canBecomeMainWindow
3428 {
3429 // This is overriden because the default is 'yes' when a titlebar is present.
3430 return NO;
3431 }
3433 @end
3435 // According to Apple's docs on [NSWindow canBecomeKeyWindow] and [NSWindow
3436 // canBecomeMainWindow], windows without a title bar or resize bar can't (by
3437 // default) become key or main. But if a window can't become key, it can't
3438 // accept keyboard input (bmo bug 393250). And it should also be possible for
3439 // an otherwise "ordinary" window to become main. We need to override these
3440 // two methods to make this happen.
3441 @implementation BorderlessWindow
3443 - (BOOL)canBecomeKeyWindow
3444 {
3445 return YES;
3446 }
3448 - (void)sendEvent:(NSEvent *)anEvent
3449 {
3450 NSEventType type = [anEvent type];
3452 switch (type) {
3453 case NSScrollWheel:
3454 case NSLeftMouseDown:
3455 case NSLeftMouseUp:
3456 case NSRightMouseDown:
3457 case NSRightMouseUp:
3458 case NSOtherMouseDown:
3459 case NSOtherMouseUp:
3460 case NSMouseMoved:
3461 case NSLeftMouseDragged:
3462 case NSRightMouseDragged:
3463 case NSOtherMouseDragged:
3464 {
3465 // Drop all mouse events if a modal window has appeared above us.
3466 // This helps make us behave as if the OS were running a "real" modal
3467 // event loop.
3468 id delegate = [self delegate];
3469 if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
3470 nsCocoaWindow *widget = [(WindowDelegate *)delegate geckoWidget];
3471 if (widget) {
3472 if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window))
3473 return;
3474 if (widget->HasModalDescendents())
3475 return;
3476 }
3477 }
3478 break;
3479 }
3480 default:
3481 break;
3482 }
3484 [super sendEvent:anEvent];
3485 }
3487 // Apple's doc on this method says that the NSWindow class's default is not to
3488 // become main if the window isn't "visible" -- so we should replicate that
3489 // behavior here. As best I can tell, the [NSWindow isVisible] method is an
3490 // accurate test of what Apple means by "visibility".
3491 - (BOOL)canBecomeMainWindow
3492 {
3493 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3495 if (![self isVisible])
3496 return NO;
3497 return YES;
3499 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
3500 }
3502 // Retain and release "self" to avoid crashes when our widget (and its native
3503 // window) is closed as a result of processing a key equivalent (e.g.
3504 // Command+w or Command+q). This workaround is only needed for a window
3505 // that can become key.
3506 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent
3507 {
3508 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3510 NSWindow *nativeWindow = [self retain];
3511 BOOL retval = [super performKeyEquivalent:theEvent];
3512 [nativeWindow release];
3513 return retval;
3515 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
3516 }
3518 @end