Skip to content

Commit

Permalink
Fix Mission Control detection; Cleanup WKFullScreenWindowController h…
Browse files Browse the repository at this point in the history
  • Loading branch information
mallexxx authored May 14, 2024
1 parent eb439a7 commit 26d379a
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 72 deletions.
44 changes: 31 additions & 13 deletions DuckDuckGo/Common/Extensions/NSWorkspaceExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,33 +29,32 @@ extension NSWorkspace {
return bundle.displayName
}

/// Detect if macOS Mission Control (three-finger swipe up to show the Spaces) is currently active
static func isMissionControlActive() -> Bool {
guard let visibleWindows = CGWindowListCopyWindowInfo(.optionOnScreenOnly, CGWindowID(0)) as? [[CFString: Any]] else {
assertionFailure("CGWindowListCopyWindowInfo doesn‘t work anymore")
return false
}

let allScreenSizes = NSScreen.screens.map(\.frame.size)

// Here‘s the trick: normally the Dock App only displays full-screen overlay windows drawing the Dock.
// When the Mission Control is activated, the Dock presents multiple window tiles for each visible window
// so here we filter out all the screen-sized windows and if the resulting list is not empty it may
// mean that Mission Control is active.
let missionControlWindows = visibleWindows.filter { window in
windowName(window) == "Dock" && !allScreenSizes.contains(windowSize(window))
let dockAppWindows = visibleWindows.filter { window in
window.ownerName == "Dock"
}

func windowName(_ dict: [CFString: Any]) -> String? {
dict[kCGWindowOwnerName] as? String
// filter out wallpaper windows
var missionControlWindows = dockAppWindows.filter { window in
window.name?.hasPrefix("Wallpaper") != true
}
func windowSize(_ dict: [CFString: Any]) -> NSSize {
guard let bounds = dict[kCGWindowBounds] as? [String: NSNumber],
let width = bounds["Width"]?.intValue,
let height = bounds["Height"]?.intValue else { return .zero }
return NSSize(width: width, height: height)
// filter out the Dock drawing windows
for screen in NSScreen.screens {
if let idx = missionControlWindows.firstIndex(where: { window in window.size == screen.frame.size }) {
missionControlWindows.remove(at: idx)
}
}

return missionControlWindows.count > allScreenSizes.count
return missionControlWindows.count > 0
}

@available(macOS, obsoleted: 14.0, message: "This needs to be removed as it‘s no longer necessary.")
Expand All @@ -79,3 +78,22 @@ extension NSWorkspace.OpenConfiguration {
}

}

private extension [CFString: Any] {

var name: String? {
self[kCGWindowName] as? String
}

var ownerName: String? {
self[kCGWindowOwnerName] as? String
}

var size: NSSize {
guard let bounds = self[kCGWindowBounds] as? [String: NSNumber],
let width = bounds["Width"]?.intValue,
let height = bounds["Height"]?.intValue else { return .zero }
return NSSize(width: width, height: height)
}

}
24 changes: 3 additions & 21 deletions DuckDuckGo/Tab/Model/Tab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -333,36 +333,18 @@ protocol NewWindowPolicyDecisionMaker {
}

deinit {
cleanUpBeforeClosing(onDeinit: true, webView: webView, userContentController: userContentController)
}

func cleanUpBeforeClosing() {
cleanUpBeforeClosing(onDeinit: false, webView: webView, userContentController: userContentController)
}

@MainActor(unsafe)
private func cleanUpBeforeClosing(onDeinit: Bool, webView: WebView, userContentController: UserContentController?) {
let job = { [webView, userContentController] in
DispatchQueue.main.asyncOrNow { [webView, userContentController] in
// WebKit objects must be deallocated on the main thread
webView.stopAllMedia(shouldStopLoading: true)

userContentController?.cleanUpBeforeClosing()

#if DEBUG
if case .normal = NSApp.runType {
webView.assertObjectDeallocated(after: 4.0)
}
#endif
}
#if DEBUG
if !onDeinit, case .normal = NSApp.runType {
// Tab should be deallocated shortly after burning
self.assertObjectDeallocated(after: 4.0)
}
#endif
guard Thread.isMainThread else {
DispatchQueue.main.async { job() }
return
}
job()
}

func stopAllMediaAndLoading() {
Expand Down
64 changes: 26 additions & 38 deletions DuckDuckGo/Tab/View/WebViewContainerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ final class WebViewContainerView: NSView {
}

private var blurViewIsHiddenCancellable: AnyCancellable?
private var fullScreenWindowWillCloseCancellable: AnyCancellable?
private var cancellables = Set<AnyCancellable>()

override func didAddSubview(_ subview: NSView) {
Expand Down Expand Up @@ -117,48 +116,37 @@ final class WebViewContainerView: NSView {
.store(in: &cancellables)
}

// fix a glitch scaling down Full Screen layer on next Full Screen activation
// after exiting Full Screen by dragging the window out in Mission Control
// (three-fingers-up swipe)
// see https://app.asana.com/0/1177771139624306/1204370242122745/f
private func observeFullScreenWindowWillExitFullScreen(_ fullScreenWindow: NSWindow) {
NotificationCenter.default.publisher(for: NSWindow.willExitFullScreenNotification, object: fullScreenWindow)
.sink { [weak self] _ in
guard let self else { return }
self.cancellables.removeAll()

if NSWorkspace.isMissionControlActive() {
// closeAllMediaPresentations causes all Full Screen windows to be closed and removed from their WebViews
// (and reinstantiated the next time Full Screen is requested)
// this would slightly break UX in case multiple Full Screen windows are open but it fixes the bug
if #available(macOS 12.0, *) {
webView.closeAllMediaPresentations {}
} else {
webView.closeAllMediaPresentations()
}
/**

}
}
.store(in: &cancellables)
Fix a glitch breaking the Full Screen presentation on a repeated
Full Screen mode activation after dragging out of Mission Control Spaces.

// https://app.asana.com/0/72649045549333/1206959015087322/f
if #unavailable(macOS 14.4) {
fullScreenWindowWillCloseCancellable = NotificationCenter.default.publisher(for: NSWindow.willCloseNotification, object: fullScreenWindow)
.sink { [weak self] notification in
self?.fullScreenWindowWillCloseCancellable = nil
let fullScreenWindowController = (notification.object as? NSWindow)?.windowController
DispatchQueue.main.async { [weak fullScreenWindowController] in
guard let fullScreenWindowController else { return }
// just in case.
// if WKFullScreenWindowController receives `close()` the next time it‘s open it will crash because its _webView is nil
// https://errors.duckduckgo.com/organizations/ddg/issues/3411/?project=6&referrer=release-issue-stream
NSException.try {
fullScreenWindowController.setValue(NSView(), forKeyPath: #keyPath(webView))
}
**Steps to reproduce:**
1. Enter full screen video
2. Open Mission Control (swipe three fingers up)
3. Drag the full screen video out of the top panel in the Mission Control
4. Enter full screen again - validate video opens in full screen
- The video would open in a shrinked (thumbnail) state without the fix

- Note: The bug is actual for macOS 12 and above

https://app.asana.com/0/1177771139624306/1204370242122745/f
*/
private func observeFullScreenWindowWillExitFullScreen(_ fullScreenWindow: NSWindow) {
if #available(macOS 12.0, *) { // works fine on Big Sur
NotificationCenter.default.publisher(for: NSWindow.willExitFullScreenNotification, object: fullScreenWindow)
.sink { [weak self] _ in
guard let self else { return }
self.cancellables.removeAll()

if NSWorkspace.isMissionControlActive() {
// closeAllMediaPresentations causes all Full Screen windows to be closed and removed from their WebViews
// (and reinstantiated the next time Full Screen is requested)
webView.closeAllMediaPresentations {}
}
}
.store(in: &cancellables)
}

}

override func removeFromSuperview() {
Expand Down

0 comments on commit 26d379a

Please sign in to comment.