Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix WebView hanging input #2616

Merged
merged 11 commits into from
Apr 12, 2024
Merged
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions DuckDuckGo/Tab/View/WebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@

weak var contextMenuDelegate: WebViewContextMenuDelegate?
weak var interactionEventsDelegate: WebViewInteractionEventsDelegate?
private var isLoadingObserver: Any?

override init(frame: CGRect, configuration: WKWebViewConfiguration) {
/// swizzle NSTrackingArea.init method to insert TrackingAreaSuppressor proxy owner
/// it will disable mouseEntered/mouseMoved/mouseExited events passing to Web View while it‘s loading
/// see https://app.asana.com/0/1177771139624306/1206990108527681/f
_=NSTrackingArea.swizzleInitWithRectOptionsOwnerUserInfoOnce
super.init(frame: frame, configuration: configuration)

// suppress Tracking Area events while loading
let suppressor = suppressor=trackingAreas.first?.trackingAreaSuppressor

Check failure on line 47 in DuckDuckGo/Tab/View/WebView.swift

View workflow job for this annotation

GitHub Actions / Make Release Build (DuckDuckGo Privacy Browser)

use of local variable 'suppressor' before its declaration

Check failure on line 47 in DuckDuckGo/Tab/View/WebView.swift

View workflow job for this annotation

GitHub Actions / Make Release Build (DuckDuckGo Privacy Pro)

use of local variable 'suppressor' before its declaration

Check failure on line 47 in DuckDuckGo/Tab/View/WebView.swift

View workflow job for this annotation

GitHub Actions / Test (Non-Sandbox)

use of local variable 'suppressor' before its declaration
isLoadingObserver = self.observe(\.isLoading, options: [.new]) { [weak suppressor] _, c in
suppressor?.isSuppressingMouseEvents = c.newValue /* isLoading */ ?? false
}
}

required init?(coder: NSCoder) {
fatalError("\(Self.self): Bad initializer")
}

override var isInFullScreenMode: Bool {
if #available(macOS 13.0, *) {
Expand Down Expand Up @@ -325,3 +344,71 @@
}

}

/// used to suppress mouseEntered/mouseMoved/mouseExited events while Web View is loading
final private class TrackingAreaSuppressor: NSObject {

private weak var owner: AnyObject?

var isSuppressingMouseEvents = false

init(owner: AnyObject? = nil) {
self.owner = owner
}

@objc(mouseEntered:)
func mouseEntered(with event: NSEvent) {
guard !isSuppressingMouseEvents else { return }
_=owner?.perform(#selector(mouseEntered), with: event)
}

@objc(mouseExited:)
func mouseExited(with event: NSEvent) {
guard !isSuppressingMouseEvents else { return }
_=owner?.perform(#selector(mouseExited), with: event)
}

@objc(mouseMoved:)
func mouseMoved(with event: NSEvent) {
guard !isSuppressingMouseEvents else { return }
_=owner?.perform(#selector(mouseMoved), with: event)
}

}

/// adding the TrackingAreaSuppressor proxy object as a WebView‘s Tracking Area owner
extension NSTrackingArea {

private static let originalInitWithRectOptionsOwnerUserInfo = {
class_getInstanceMethod(NSTrackingArea.self, #selector(NSTrackingArea.init(rect:options:owner:userInfo:)))!
}()
private static let swizzledInitWithRectOptionsOwnerUserInfo = {
class_getInstanceMethod(NSTrackingArea.self, #selector(NSTrackingArea.swizzled_init(rect:options:owner:userInfo:)))!
}()

fileprivate static let swizzleInitWithRectOptionsOwnerUserInfoOnce: Void = {
method_exchangeImplementations(originalInitWithRectOptionsOwnerUserInfo, swizzledInitWithRectOptionsOwnerUserInfo)
}()

@objc private dynamic func swizzled_init(rect: NSRect, options: NSTrackingArea.Options, owner: AnyObject?, userInfo: NSDictionary) -> AnyObject? {
var owner = owner
if owner?.className == "WKMouseTrackingObserver" {
let helper = TrackingAreaSuppressor(owner: owner)
self.trackingAreaSuppressor = helper
owner = helper
}

return self.swizzled_init(rect: rect, options: options, owner: owner, userInfo: userInfo) /* call original */
}

private static let trackingAreaSuppressorKey = UnsafeRawPointer(bitPattern: "trackingAreaSuppressorKey".hashValue)!
fileprivate private(set) var trackingAreaSuppressor: TrackingAreaSuppressor? {
get {
objc_getAssociatedObject(self, Self.trackingAreaSuppressorKey) as? TrackingAreaSuppressor
}
set {
objc_setAssociatedObject(self, Self.trackingAreaSuppressorKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}

}
Loading