Skip to content

Commit

Permalink
Fix Mission Control detection; Cleanup WKFullScreenWindowController hack
Browse files Browse the repository at this point in the history
  • Loading branch information
mallexxx committed May 13, 2024
1 parent c982ab3 commit 43c0067
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 68 deletions.
43 changes: 30 additions & 13 deletions DuckDuckGo/Common/Extensions/NSWorkspaceExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,25 @@ extension NSWorkspace {
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 +77,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
44 changes: 10 additions & 34 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 @@ -122,43 +121,20 @@ final class WebViewContainerView: NSView {
// (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, *) {
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 {}
} else {
webView.closeAllMediaPresentations()
}

}
}
.store(in: &cancellables)

// 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))
}
}
}
.store(in: &cancellables)
}

}

override func removeFromSuperview() {
Expand Down

0 comments on commit 43c0067

Please sign in to comment.