Hammerspoon / hammerspoon

Staggeringly powerful macOS desktop automation with Lua
http://www.hammerspoon.org
MIT License
11.9k stars 578 forks source link

hs.window.filter has outdated Safari exclusions list #2943

Open lilyball opened 3 years ago

lilyball commented 3 years ago

hs.window.filter keeps a list of apps to always skip. Both SKIP_APPS_NO_PID and SKIP_APPS_NO_WINDOWS contain 'Safari Web Content'.

Unfortunately, at least with the Safari 15 public beta on macOS Big Sur, there are additional web content processes named 'Safari Web Content (Prewarmed)' and 'Safari Web Content (Cached)'. These should be skipped as well. There's also SafariQuickLookPreview (Spotlight), which is showing up as a GUI app, but I'm not sure if it ever has windows we care about.

There's actually a whole mess of other Safari processes too, but I'm calling these out specifically because, on my machine at the moment, hs.window._timed_allWindows() reports that com.apple.WebKit.WebContent and com.apple.Safari.SafariQuickLookPreview both are taking approximately 6 seconds to query. And yesterday, prior to me quitting and relaunching Safari, com.apple.WebKit.WebContent was taking upwards of a minute to query (probably 6 seconds per process). I don't know which specific processes are involved as hs.window._timed_allWindows() merges processes with the same bundle ID, but hs.window.filter.default:start() is taking ~13 seconds right now, which suggests that one of the two unfiltered Safari Web Content processes plus the SafariQuickLookPreview process are the cause.

I'm actually rather surprised this filtering is even done via app name instead of bundle ID. hs.window.allWindows() filters out a set of apps via bundle ID (though far fewer than hs.window.filter does). I would think that teaching hs.window.filter to filter via bundleID would be more stable (though not entirely foolproof; Messages changed its bundle ID when it switched to a Catalyst app). Barring that, it could filter by string pattern instead of exact match, allowing e.g. 'Safari Web Content.*' to be used as a filter. Though matching by bundleID pattern would be even cleaner, allowing 'com%.apple%.Safari%..+' to filter out all Safari helper processes (I'm assuming none of them are useful, though I don't know for certain).

(side note: Looks like matching by title actually should filter out '.* Web Content.*' as other apps that show webviews change the name here, e.g. I have a lot of "Dash Web Content" and "Dash Web Content (Prewarmed)" processes; they all use the same com.apple.WebKit.WebContent bundle ID though, so it's quite plausible that they're involved in the querying delays too)

In the meantime, just adding ['Safari Web Content (Prewarmed)', 'Safari Web Content (Cached)', 'SafariQuickLookPreview (Spotlight)'] to the default skip list would be very helpful. Incidentally, hs.window.filter._showCandidates() has a lot of other suggestions too, so it's worth running that on Big Sur and updating the list.

jasonvarga commented 2 years ago

I don't understand most of what was said here 🤣 , except that I'm running Safari 15 and using hs.window.filter and it takes ages to load. I didn't know why. Thanks for working this out! Excited for a fix here. Waiting ~6 seconds every time I save a lua file is a bummer.

hopefulwerewolf commented 2 years ago

Also seeing issues with com.apple.WebKit.WebContent as well as com.apple.PassKit.PaymentAuthorizationUIExtension (application:name() is PaymentAuthorizationUIExtension (Safari)) in macOS Monterey.

Is there a workaround for this? I've tried using the code below but it has no effect.

wf = hs.window.filter
wf.ignoreAlways["Safari Web Content"] = true
wf.ignoreAlways["com.apple.WebKit.WebContent"] = true
wf.ignoreAlways["PaymentAuthorizationUIExtension (Safari)"] = true
wf.ignoreAlways["com.apple.PassKit.PaymentAuthorizationUIExtension"] = true
SilverEzhik commented 4 months ago

hs.window._timed_allWindows() reports bundle IDs, but the documentation to hs.window.filter.ignoreAlways says the table uses hs.application:name().

Excluding apps by name seemed to work for me - I had to make my own version of hs.window._timed_allWindows() for this:

function _wf_timed_allWindows()
    local r = {}
    for _, app in ipairs(hs.application.runningApplications()) do
        local starttime = hs.timer.secondsSinceEpoch()
        local _, name = app:allWindows(), string.format("%s (%s)", app:name() or 'N/A', app:bundleID() or 'N/A')
        r[name] = (r[name] or 0) + hs.timer.secondsSinceEpoch() - starttime
    end
    for app, time in pairs(r) do
        if time > 0.05 then print(string.format('took %.2fs for %s', time, app)) end
    end
    return r
end

Excluding Web Content windows actually fixed the issue for me. I added extra filters to my config to make the process automatic:

function _wf_ignoreWebContent()
    for _, app in pairs(hs.application.runningApplications()) do
        local name = app:name()
        if name and (name:match(" Web Content$") or app:bundleID() == "com.apple.WebKit.WebContent") then
            hs.window.filter.ignoreAlways[name] = true
        end
    end
end

hs.timer.doEvery(15, _wf_ignoreWebContent)
_wf_ignoreWebContent()

It might also be beneficial to filter some apps out explicitly (so window filters will ignore these even if they aren't open when that function runs) - for me Dash Web Content was the most troublesome one.