Bug 1631735 Part 1: Make nsCocoaWindow animated transitions asynchronous and atomic. r=mstange

This patch attempts to make nsCocoaWindow transitions (fullscreen,
windowed, minimize, zoom) atomic operations that can't be disrupted by
user action or by programmatic triggers. To accomplish this, it defines a
transition queue which ensures that when a transition is submitted, it is
always run to completion before another transition is started. Native
transitions execute asynchronously, and emulated ones execute
synchronously.

Additionally, this patch adds intermediary transitions to handle
programmatic transitions which aren't allowed by macOS, which include:

1) Attempting to minimize a fullscreen window will first transition to
windowed state.
2) Any transition on a minimized window will first transition to windowed
state.

A later part of this patch stack tests this behavior.

Differential Revision: https://phabricator.services.mozilla.com/D166450
This commit is contained in:
Brad Werth 2023-03-06 19:38:10 +00:00
Родитель 80cc721b82
Коммит c01f7ba390
2 изменённых файлов: 334 добавлений и 131 удалений

Просмотреть файл

@ -16,6 +16,7 @@
#include "nsCocoaUtils.h"
#include "nsTouchBar.h"
#include <dlfcn.h>
#include <queue>
class nsCocoaWindow;
class nsChildView;
@ -262,8 +263,6 @@ class nsCocoaWindow final : public nsBaseWidget, public nsPIWidgetCocoa {
virtual void SuppressAnimation(bool aSuppress) override;
virtual void HideWindowChrome(bool aShouldHide) override;
void WillEnterFullScreen(bool aFullScreen);
void EnteredFullScreen(bool aFullScreen, bool aNativeMode = true);
virtual bool PrepareForFullscreenTransition(nsISupports** aData) override;
virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage, uint16_t aDuration,
nsISupports* aData, nsIRunnable* aCallback) override;
@ -362,6 +361,25 @@ class nsCocoaWindow final : public nsBaseWidget, public nsPIWidgetCocoa {
const ScrollableLayerGuid& aGuid) override;
void StopAsyncAutoscroll(const ScrollableLayerGuid& aGuid) override;
// Class method versions of NSWindow/Delegate callbacks which need to
// access object state.
void CocoaWindowWillEnterFullscreen(bool aFullscreen);
void CocoaWindowDidFailFullscreen(bool aAttemptedFullscreen);
void CocoaWindowDidResize();
void CocoaSendToplevelActivateEvents();
void CocoaSendToplevelDeactivateEvents();
enum class TransitionType {
Windowed,
Fullscreen,
EmulatedFullscreen,
Miniaturize,
Deminiaturize,
Zoom,
};
void FinishCurrentTransition();
void FinishCurrentTransitionIfMatching(const TransitionType& aTransition);
protected:
virtual ~nsCocoaWindow();
@ -375,7 +393,6 @@ class nsCocoaWindow final : public nsBaseWidget, public nsPIWidgetCocoa {
void DoResize(double aX, double aY, double aWidth, double aHeight, bool aRepaint,
bool aConstrainToCurrentScreen);
inline bool ShouldToggleNativeFullscreen(bool aFullScreen, bool aUseSystemTransition);
void UpdateFullscreenState(bool aFullScreen, bool aNativeMode);
nsresult DoMakeFullScreen(bool aFullScreen, bool aUseSystemTransition);
@ -405,8 +422,33 @@ class nsCocoaWindow final : public nsBaseWidget, public nsPIWidgetCocoa {
// this is used for sibling sheet contention only
nsSizeMode mSizeMode;
bool mInFullScreenMode;
bool mInFullScreenTransition; // true from the request to enter/exit fullscreen
// (MakeFullScreen() call) to EnteredFullScreen()
// Whether we are currently using native fullscreen. It could be false because
// we are in the emulated fullscreen where we do not use the native fullscreen.
bool mInNativeFullScreenMode;
mozilla::Maybe<TransitionType> mTransitionCurrent;
std::queue<TransitionType> mTransitionsPending;
// Sometimes we add a transition that wasn't requested by a caller. We do this
// to manage transitions between states that otherwise would be rejected by
// Cocoa. When we do this, it's useful to know when we are handling an added
// transition because we don't want to send size mode events when they execute.
bool mIsTransitionCurrentAdded = false;
// Whether we are treating the next resize as the start of a fullscreen transition.
// If we are, which direction are we going: Fullscreen or Windowed.
mozilla::Maybe<TransitionType> mUpdateFullscreenOnResize;
bool IsInTransition() { return mTransitionCurrent.isSome(); }
void QueueTransition(const TransitionType& aTransition);
void ProcessTransitions();
bool mInProcessTransitions = false;
// While running an emulated fullscreen transition, we want to suppress sending
// size mode events due to window resizing. We fix it up at the end when the
// transition is complete.
bool mSuppressSizeModeEvents = false;
// Ignore occlusion events caused by displaying the temporary fullscreen
// window during the fullscreen transition animation because only focused
@ -416,10 +458,6 @@ class nsCocoaWindow final : public nsBaseWidget, public nsPIWidgetCocoa {
bool mModal;
bool mFakeModal;
// Whether we are currently using native fullscreen. It could be false because
// we are in the DOM fullscreen where we do not use the native fullscreen.
bool mInNativeFullScreenMode;
bool mIsAnimationSuppressed;
bool mInReportMoveEvent; // true if in a call to ReportMoveEvent().

Просмотреть файл

@ -139,11 +139,10 @@ nsCocoaWindow::nsCocoaWindow()
mSheetNeedsShow(false),
mSizeMode(nsSizeMode_Normal),
mInFullScreenMode(false),
mInFullScreenTransition(false),
mInNativeFullScreenMode(false),
mIgnoreOcclusionCount(0),
mModal(false),
mFakeModal(false),
mInNativeFullScreenMode(false),
mIsAnimationSuppressed(false),
mInReportMoveEvent(false),
mInResize(false),
@ -1256,32 +1255,15 @@ void nsCocoaWindow::Move(double aX, double aY) {
}
void nsCocoaWindow::SetSizeMode(nsSizeMode aMode) {
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
if (!mWindow) return;
// mSizeMode will be updated in DispatchSizeModeEvent, which will be called
// from a delegate method that handles the state change during one of the
// calls below.
nsSizeMode previousMode = mSizeMode;
if (aMode == nsSizeMode_Normal) {
if ([mWindow isMiniaturized])
[mWindow deminiaturize:nil];
else if (previousMode == nsSizeMode_Maximized && [mWindow isZoomed])
[mWindow zoom:nil];
else if (previousMode == nsSizeMode_Fullscreen)
MakeFullScreen(false);
QueueTransition(TransitionType::Windowed);
} else if (aMode == nsSizeMode_Minimized) {
if (![mWindow isMiniaturized]) [mWindow miniaturize:nil];
QueueTransition(TransitionType::Miniaturize);
} else if (aMode == nsSizeMode_Maximized) {
if ([mWindow isMiniaturized]) [mWindow deminiaturize:nil];
if (![mWindow isZoomed]) [mWindow zoom:nil];
QueueTransition(TransitionType::Zoom);
} else if (aMode == nsSizeMode_Fullscreen) {
if (!mInFullScreenMode) MakeFullScreen(true);
MakeFullScreen(true);
}
NS_OBJC_END_TRY_IGNORE_BLOCK;
}
// The (work)space switching implementation below was inspired by Phoenix:
@ -1639,18 +1621,33 @@ static bool AlwaysUsesNativeFullScreen() {
[mFullscreenTransitionAnimation startAnimation];
}
void nsCocoaWindow::WillEnterFullScreen(bool aFullScreen) {
void nsCocoaWindow::CocoaWindowWillEnterFullscreen(bool aFullscreen) {
MOZ_ASSERT(mUpdateFullscreenOnResize.isNothing());
// Ensure that we update our fullscreen state as early as possible, when the resize
// happens.
mUpdateFullscreenOnResize =
Some(aFullscreen ? TransitionType::Fullscreen : TransitionType::Windowed);
if (mWidgetListener) {
mWidgetListener->FullscreenWillChange(aFullScreen);
mWidgetListener->FullscreenWillChange(aFullscreen);
}
// Update the state to full screen when we are entering, so that we switch to
// full screen view as soon as possible.
UpdateFullscreenState(aFullScreen, true);
}
void nsCocoaWindow::EnteredFullScreen(bool aFullScreen, bool aNativeMode) {
mInFullScreenTransition = false;
UpdateFullscreenState(aFullScreen, aNativeMode);
void nsCocoaWindow::CocoaWindowDidFailFullscreen(bool aAttemptedFullscreen) {
if (mWidgetListener) {
mWidgetListener->FullscreenWillChange(!aAttemptedFullscreen);
}
// If we already updated our fullscreen state due to a resize, we need to update it again.
if (mUpdateFullscreenOnResize.isNothing()) {
UpdateFullscreenState(!aAttemptedFullscreen, true);
ReportSizeEvent();
}
TransitionType transition =
aAttemptedFullscreen ? TransitionType::Fullscreen : TransitionType::Windowed;
FinishCurrentTransitionIfMatching(transition);
}
void nsCocoaWindow::UpdateFullscreenState(bool aFullScreen, bool aNativeMode) {
@ -1659,8 +1656,14 @@ void nsCocoaWindow::UpdateFullscreenState(bool aFullScreen, bool aNativeMode) {
if (aNativeMode || mInNativeFullScreenMode) {
mInNativeFullScreenMode = aFullScreen;
}
if (aFullScreen == wasInFullscreen) {
return;
}
DispatchSizeModeEvent();
if (mWidgetListener && wasInFullscreen != aFullScreen) {
if (mWidgetListener) {
mWidgetListener->FullscreenChanged(aFullScreen);
}
@ -1671,29 +1674,6 @@ void nsCocoaWindow::UpdateFullscreenState(bool aFullScreen, bool aNativeMode) {
}
}
inline bool nsCocoaWindow::ShouldToggleNativeFullscreen(bool aFullScreen,
bool aUseSystemTransition) {
// First check if this window supports entering native fullscreen.
// This is set based on the macnativefullscreen attribute on the window's
// document element.
NSWindowCollectionBehavior colBehavior = [mWindow collectionBehavior];
if (!(colBehavior & NSWindowCollectionBehaviorFullScreenPrimary)) {
return false;
}
if (mInNativeFullScreenMode) {
// If we are using native fullscreen, go ahead to exit it.
return true;
}
if (!aUseSystemTransition) {
// If we do not want the system fullscreen transition,
// don't use the native fullscreen.
return false;
}
// If we are using native fullscreen, we should have returned earlier.
return aFullScreen;
}
nsresult nsCocoaWindow::MakeFullScreen(bool aFullScreen) {
return DoMakeFullScreen(aFullScreen, AlwaysUsesNativeFullScreen());
}
@ -1703,45 +1683,204 @@ nsresult nsCocoaWindow::MakeFullScreenWithNativeTransition(bool aFullScreen) {
}
nsresult nsCocoaWindow::DoMakeFullScreen(bool aFullScreen, bool aUseSystemTransition) {
NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
if (!mWindow) {
return NS_OK;
}
// We will call into MakeFullScreen redundantly when entering/exiting
// fullscreen mode via OS X controls. When that happens we should just handle
// it gracefully - no need to ASSERT.
if (mInFullScreenMode == aFullScreen) {
return NS_OK;
}
mInFullScreenTransition = true;
if (ShouldToggleNativeFullscreen(aFullScreen, aUseSystemTransition)) {
MOZ_ASSERT(mInNativeFullScreenMode != aFullScreen,
"We shouldn't have been in native fullscreen.");
// Calling toggleFullScreen will result in windowDid(FailTo)?(Enter|Exit)FullScreen
// to be called from the OS. We will call EnteredFullScreen from those methods,
// where mInFullScreenMode will be set and a sizemode event will be dispatched.
[mWindow toggleFullScreen:nil];
} else {
if (mWidgetListener) {
mWidgetListener->FullscreenWillChange(aFullScreen);
}
NSDisableScreenUpdates();
// The order here matters. When we exit full screen mode, we need to show the
// Dock first, otherwise the newly-created window won't have its minimize
// button enabled. See bug 526282.
nsCocoaUtils::HideOSChromeOnScreen(aFullScreen);
nsBaseWidget::InfallibleMakeFullScreen(aFullScreen);
NSEnableScreenUpdates();
EnteredFullScreen(aFullScreen, /* aNativeMode */ false);
// Figure out what type of transition is being requested.
TransitionType transition = TransitionType::Windowed;
if (aFullScreen) {
// Decide whether to use fullscreen or emulated fullscreen.
transition = (aUseSystemTransition &&
(mWindow.collectionBehavior & NSWindowCollectionBehaviorFullScreenPrimary))
? TransitionType::Fullscreen
: TransitionType::EmulatedFullscreen;
}
QueueTransition(transition);
return NS_OK;
}
NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
void nsCocoaWindow::QueueTransition(const TransitionType& aTransition) {
mTransitionsPending.push(aTransition);
ProcessTransitions();
}
void nsCocoaWindow::ProcessTransitions() {
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK
if (mInProcessTransitions) {
return;
}
mInProcessTransitions = true;
// Start a loop that will continue as long as we have transitions to process
// and we aren't waiting on an asynchronous transition to complete. Any
// transition that starts something async will `continue` this loop to exit.
while (!mTransitionsPending.empty() && !IsInTransition()) {
TransitionType nextTransition = mTransitionsPending.front();
// We have to check for some incompatible transition states, and if we find one,
// instead perform an alternative transition and leave the queue untouched. If
// we add one of these transitions, we set mIsTransitionCurrentAdded because we
// don't want to confuse listeners who are expecting to receive exactly one
// event when the requested transition has completed.
switch (nextTransition) {
case TransitionType::Fullscreen:
case TransitionType::EmulatedFullscreen:
case TransitionType::Windowed:
case TransitionType::Zoom:
// These can't handle miniaturized windows, so deminiaturize first.
if (mWindow.miniaturized) {
mTransitionCurrent = Some(TransitionType::Deminiaturize);
mIsTransitionCurrentAdded = true;
}
break;
case TransitionType::Miniaturize:
// This can't handle fullscreen, so go to windowed first.
if (mInFullScreenMode) {
mTransitionCurrent = Some(TransitionType::Windowed);
mIsTransitionCurrentAdded = true;
}
break;
default:
break;
}
// If mTransitionCurrent is still empty, then we use the nextTransition and pop
// the queue.
if (mTransitionCurrent.isNothing()) {
mTransitionCurrent = Some(nextTransition);
mTransitionsPending.pop();
}
switch (*mTransitionCurrent) {
case TransitionType::Fullscreen: {
if (!mInFullScreenMode) {
// This triggers an async animation, so continue.
[mWindow toggleFullScreen:nil];
continue;
}
break;
}
case TransitionType::EmulatedFullscreen: {
if (!mInFullScreenMode) {
// This can be done synchronously.
if (mWidgetListener) {
mWidgetListener->FullscreenWillChange(true);
}
NSDisableScreenUpdates();
mSuppressSizeModeEvents = true;
// The order here matters. When we exit full screen mode, we need to show the
// Dock first, otherwise the newly-created window won't have its minimize
// button enabled. See bug 526282.
nsCocoaUtils::HideOSChromeOnScreen(true);
nsBaseWidget::InfallibleMakeFullScreen(true);
mSuppressSizeModeEvents = false;
NSEnableScreenUpdates();
UpdateFullscreenState(true, false);
}
break;
}
case TransitionType::Windowed: {
if (mInFullScreenMode) {
if (mInNativeFullScreenMode) {
// This triggers an async animation, so continue.
[mWindow toggleFullScreen:nil];
continue;
} else {
// This can be done synchronously.
if (mWidgetListener) {
mWidgetListener->FullscreenWillChange(false);
}
NSDisableScreenUpdates();
mSuppressSizeModeEvents = true;
// The order here matters. When we exit full screen mode, we need to show the
// Dock first, otherwise the newly-created window won't have its minimize
// button enabled. See bug 526282.
nsCocoaUtils::HideOSChromeOnScreen(false);
nsBaseWidget::InfallibleMakeFullScreen(false);
mSuppressSizeModeEvents = false;
NSEnableScreenUpdates();
UpdateFullscreenState(false, false);
}
} else if (mWindow.zoomed) {
[mWindow zoom:nil];
}
break;
}
case TransitionType::Miniaturize:
if (!mWindow.miniaturized) {
// This triggers an async animation, so continue.
[mWindow miniaturize:nil];
continue;
}
break;
case TransitionType::Deminiaturize:
if (mWindow.miniaturized) {
// This triggers an async animation, so continue.
[mWindow deminiaturize:nil];
continue;
}
break;
case TransitionType::Zoom:
if (!mWindow.zoomed) {
[mWindow zoom:nil];
}
break;
default:
break;
}
mTransitionCurrent.reset();
mIsTransitionCurrentAdded = false;
}
mInProcessTransitions = false;
// When we finish processing transitions, dispatch a size mode event to cover the
// cases where an inserted transition suppressed one, and the original transition
// never sent one because it detected it was at the desired state when it ran. If
// we've already sent a size mode event, then this will be a no-op.
if (!IsInTransition()) {
DispatchSizeModeEvent();
}
NS_OBJC_END_TRY_IGNORE_BLOCK;
}
void nsCocoaWindow::FinishCurrentTransition() {
mTransitionCurrent.reset();
mIsTransitionCurrentAdded = false;
ProcessTransitions();
}
void nsCocoaWindow::FinishCurrentTransitionIfMatching(const TransitionType& aTransition) {
// We've just finished some transition activity, and we're not sure whether it was
// triggered programmatically, or by the user. If it matches our current transition,
// then assume it was triggered programmatically and we can clean up that transition
// and start processing transitions again.
// Whether programmatic or user-initiated, we send out a size mode event.
DispatchSizeModeEvent();
if (mTransitionCurrent.isSome() && (*mTransitionCurrent == aTransition)) {
// This matches our current transition. Since this function is called from
// nsWindowDelegate transition callbacks, we want to make sure those callbacks are
// all the way done before we start processing more transitions. To accomplish this,
// we dispatch our cleanup to happen on the next event loop. Doing this will ensure
// that any async native transition methods we call (like toggleFullscreen) will
// succeed.
NS_DispatchToCurrentThread(NewRunnableMethod("FinishCurrentTransition", this,
&nsCocoaWindow::FinishCurrentTransition));
}
}
// Coordinates are desktop pixels
@ -2096,12 +2235,12 @@ void nsCocoaWindow::DispatchSizeModeEvent() {
return;
}
nsSizeMode newMode = GetWindowSizeMode(mWindow, mInFullScreenMode);
if (mSuppressSizeModeEvents || mIsTransitionCurrentAdded) {
return;
}
// Don't dispatch a sizemode event if:
// 1. the window is transitioning to fullscreen
// 2. the new sizemode is the same as the current sizemode
if (mInFullScreenTransition || mSizeMode == newMode) {
nsSizeMode newMode = GetWindowSizeMode(mWindow, mInFullScreenMode);
if (mSizeMode == newMode) {
return;
}
@ -2138,7 +2277,6 @@ void nsCocoaWindow::ReportSizeEvent() {
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
UpdateBounds();
if (mWidgetListener) {
LayoutDeviceIntRect innerBounds = GetClientBounds();
mWidgetListener->WindowResized(this, innerBounds.width, innerBounds.height);
@ -2665,19 +2803,50 @@ already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
- (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)proposedFrameSize {
RollUpPopups();
return proposedFrameSize;
}
void nsCocoaWindow::CocoaSendToplevelActivateEvents() {
if (mWidgetListener) {
mWidgetListener->WindowActivated();
}
}
void nsCocoaWindow::CocoaSendToplevelDeactivateEvents() {
if (mWidgetListener) {
mWidgetListener->WindowDeactivated();
}
}
void nsCocoaWindow::CocoaWindowDidResize() {
// It's important to update our bounds before we trigger any listeners. This
// ensures that our bounds are correct when GetScreenBounds is called.
UpdateBounds();
if (mUpdateFullscreenOnResize.isSome()) {
// Act as if the native fullscreen transition is complete, doing everything other
// than actually clearing the transition state, which will happen when one of the
// appropriate windowDid delegate methods is called.
bool toFullscreen = (*mUpdateFullscreenOnResize == TransitionType::Fullscreen);
mUpdateFullscreenOnResize.reset();
UpdateFullscreenState(toFullscreen, true);
ReportSizeEvent();
return;
}
// Resizing might have changed our zoom state.
DispatchSizeModeEvent();
ReportSizeEvent();
}
- (void)windowDidResize:(NSNotification*)aNotification {
BaseWindow* window = [aNotification object];
[window updateTrackingArea];
if (!mGeckoWindow) return;
// Resizing might have changed our zoom state.
mGeckoWindow->DispatchSizeModeEvent();
mGeckoWindow->ReportSizeEvent();
mGeckoWindow->CocoaWindowDidResize();
}
- (void)windowDidChangeScreen:(NSNotification*)aNotification {
@ -2714,20 +2883,13 @@ already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
if (!mGeckoWindow) {
return;
}
mGeckoWindow->WillEnterFullScreen(true);
mGeckoWindow->CocoaWindowWillEnterFullscreen(true);
}
// Lion's full screen mode will bypass our internal fullscreen tracking, so
// we need to catch it when we transition and call our own methods, which in
// turn will fire "fullscreen" events.
- (void)windowDidEnterFullScreen:(NSNotification*)notification {
if (!mGeckoWindow) {
return;
}
mGeckoWindow->EnteredFullScreen(true);
// On Yosemite, the NSThemeFrame class has two new properties --
// titlebarView (an NSTitlebarView object) and titlebarContainerView (an
// NSTitlebarContainerView object). These are used to display the titlebar
@ -2750,38 +2912,40 @@ already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
if ([titlebarContainerView respondsToSelector:@selector(setTransparent:)]) {
[titlebarContainerView setTransparent:NO];
}
if (!mGeckoWindow) {
return;
}
mGeckoWindow->FinishCurrentTransitionIfMatching(nsCocoaWindow::TransitionType::Fullscreen);
}
- (void)windowWillExitFullScreen:(NSNotification*)notification {
if (!mGeckoWindow) {
return;
}
mGeckoWindow->WillEnterFullScreen(false);
mGeckoWindow->CocoaWindowWillEnterFullscreen(false);
}
- (void)windowDidExitFullScreen:(NSNotification*)notification {
if (!mGeckoWindow) {
return;
}
mGeckoWindow->EnteredFullScreen(false);
mGeckoWindow->FinishCurrentTransitionIfMatching(nsCocoaWindow::TransitionType::Windowed);
}
- (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
if (!mGeckoWindow) {
return;
}
mGeckoWindow->EnteredFullScreen(false);
mGeckoWindow->CocoaWindowDidFailFullscreen(true);
}
- (void)windowDidFailToExitFullScreen:(NSWindow*)window {
if (!mGeckoWindow) {
return;
}
mGeckoWindow->EnteredFullScreen(true);
mGeckoWindow->CocoaWindowDidFailFullscreen(false);
}
- (void)windowDidBecomeMain:(NSNotification*)aNotification {
@ -2883,11 +3047,17 @@ already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
}
- (void)windowDidMiniaturize:(NSNotification*)aNotification {
if (mGeckoWindow) mGeckoWindow->DispatchSizeModeEvent();
if (!mGeckoWindow) {
return;
}
mGeckoWindow->FinishCurrentTransitionIfMatching(nsCocoaWindow::TransitionType::Miniaturize);
}
- (void)windowDidDeminiaturize:(NSNotification*)aNotification {
if (mGeckoWindow) mGeckoWindow->DispatchSizeModeEvent();
if (!mGeckoWindow) {
return;
}
mGeckoWindow->FinishCurrentTransitionIfMatching(nsCocoaWindow::TransitionType::Deminiaturize);
}
- (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)proposedFrame {
@ -2956,20 +3126,15 @@ already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
- (void)sendToplevelActivateEvents {
if (!mToplevelActiveState && mGeckoWindow) {
nsIWidgetListener* listener = mGeckoWindow->GetWidgetListener();
if (listener) {
listener->WindowActivated();
}
mGeckoWindow->CocoaSendToplevelActivateEvents();
mToplevelActiveState = true;
}
}
- (void)sendToplevelDeactivateEvents {
if (mToplevelActiveState && mGeckoWindow) {
nsIWidgetListener* listener = mGeckoWindow->GetWidgetListener();
if (listener) {
listener->WindowDeactivated();
}
mGeckoWindow->CocoaSendToplevelDeactivateEvents();
mToplevelActiveState = false;
}
}