Fullscreen Video in a Floating WKWebView Panel: What WebKit Does on macOS
I clicked fullscreen on a video in Overlay and nothing happened. No error, no flicker, nothing. Overlay is a floating panel of web pages built on WKWebView, and I had honestly never tried it since rewriting the app natively. The old Tauri build could do it; it even had code to hide the panel while a page was fullscreen. That code didn't make it across, and neither did something more basic.
This is what I found over one long day, mostly by building small test harnesses and watching logs, and what I haven't solved.
requestFullscreen wasn't there
The first check was a two-line probe page. With a stock WKWebViewConfiguration, Element.prototype.requestFullscreen is undefined, document.fullscreenEnabled is undefined, and calling webkitEnterFullscreen() on a <video> throws InvalidStateError. Sites notice. Some hide their fullscreen button, some call the method anyway and nothing happens.
The switch is WKPreferences.isElementFullscreenEnabled, public since macOS 12.3, and it defaults to false. Turning it on made all three work. So the "bug" was one line. Everything after this was about what WebKit does once fullscreen actually happens.
WebKit takes the web view somewhere else
I rebuilt the panel's setup in a bare harness: an accessory app, a non-activating NSPanel at level 8 that joins all Spaces, and a WKWebView inside. Then I went fullscreen and listed every window in the process.
WebKit doesn't fullscreen your view in place. It creates its own WebCoreFullScreenWindow, takes the web view out of your view hierarchy, leaves a placeholder behind, and puts the window into a new fullscreen Space. When you exit, it puts the web view back and restores the constraints it had. I checked that last part by resizing the panel after exiting, and the web view followed.
That design collides with Overlay's panel. The panel is meant to float over fullscreen apps, so it followed the video into the new Space and sat on top of it, with an empty hole where the page used to be. So the rule became: when the first page goes fullscreen, hide the panel; when the last one comes back, show it again. WKWebView.fullscreenState is KVO-observable (macOS 13), which gives four states to react to.
Two details took a few runs.
Exiting a video's own fullscreen sometimes reports notInFullscreen twice. The first time, the web view is still inside WebKit's window; about 0.6 s later it's back in the panel. Bringing the panel back on the first report shows a panel with a hole in it for half a second. Now I only treat a page as home when its web view is actually back in one of my windows, and I re-check every 100 ms for up to 2 s before giving up.
I also needed a way to make a page leave fullscreen from native code, for when you press the global shortcut mid-video. closeAllMediaPresentations looked like the obvious API. It removed WebKit's window, but left the web view inside it, off screen, never returned, plus a constraint exception in the log. Asking the page to do it with document.exitFullscreen() goes through WebKit's normal path and puts everything back. For a video's own fullscreen that works too, because on macOS it's implemented as element fullscreen and document.fullscreenElement is the video.
The wrong display
I have three displays. With the panel on the right one, fullscreen opened on the middle one every time.
WebKit's source answered this quickly. WKFullScreenWindowController picks its screen with [NSScreen mainScreen], the screen with the current keyboard focus. Safari is the app you're using when you click fullscreen, so that's always the right screen. Overlay's panel doesn't activate when you click it (that's the point; your editor keeps focus), so mainScreen is wherever your other app is.
There's a usable gap in the order things happen. WebKit sets its window's frame to mainScreen and moves the web view in. Then the entering KVO fires. Then the web content process lays out at the new size. Then WebKit reads window.screen for the zoom animation and calls into AppKit to go fullscreen. Moving the window to the panel's screen inside that KVO callback means every later step reads the right screen. I tried it in the harness first with the panel on the right display, then in the real app with another app focused on the middle one. The log says it moved the window, and the video shows up where you clicked.
Hitting the edge twice
Overlay can dock to a screen edge and slide out when your pointer hits it. I first blocked that during fullscreen, since sliding out an empty panel over a video is pointless. Then I changed my mind: hitting the edge in fullscreen should mean what the global shortcut means, leave fullscreen and show the panel. That part was easy to wire up. What came next was a bug report: it takes two hits.
It reproduced as soon as I stopped cheating in my test. My script had been jumping the pointer to the middle of the screen and then to the edge, four seconds after entering fullscreen. A person pushes the pointer smoothly toward the edge, often a second or two after the video goes big. When I did that, the log said the mouse events were dropped.
Going fullscreen hides the menu bar and the Dock, which changes the screen's visible frame, which posts didChangeScreenParameters. The edge-hide controller treats that like a display being plugged in and ignores the mouse for a settling period so the window doesn't jump around mid-rearrangement. It lasted 2 to 3 s in my runs. Worse, it also dropped the event that says "the pointer left the edge zone", and the controller requires that before it will reveal again, so when the pause ended with the pointer already at the edge, it still didn't count.
During fullscreen the only thing a hit does is ask the page to exit, which doesn't move any windows, so there's nothing for the settling period to protect. It's exempt now. Pushing to the edge 0.8 s and 1.5 s after entering fullscreen both worked on the first try. In the 0.8 s case the enter animation hadn't finished yet; WebKit remembers the exit request and runs it right after.
Picture in picture
document.pictureInPictureEnabled reports true in a stock WKWebView on macOS, and requestPictureInPicture() then fails with NotSupportedError. The gate is a private preference, _allowsPictureInPictureMediaPlayback. iOS has a public allowsPictureInPictureMediaPlayback; macOS doesn't.
Setting it took two wrong tries. KVC can't find an underscore-prefixed setter. perform(_:with:) passes an object pointer where the method expects a BOOL, and reading the value back still gave 0. Calling the method through its IMP as a C function worked, and the system picture-in-picture window appeared. Because it's private API, it's only in the direct-download build; the App Store build compiles it out, and the App Store build check now scans the binary for that selector.
What I haven't fixed
The menu bar. In fullscreen, the menu bar often stays at the top, and it's the menu of whatever regular app you used last, Claude or Finder in my tests, not Overlay's. Overlay is an accessory app (no Dock icon) and doesn't own the menu bar, so the system has no reason to hide someone else's. Right after launch it sometimes hides, which made it look like "the first fullscreen works, then it breaks". Switching the app to a regular activation policy and activating it when fullscreen starts didn't change anything; by then it's too late. The two options I see are taking over the menu bar before the page's fullscreen request reaches WebKit, which fights the page's user-gesture timing, or doing fullscreen myself: grow the panel over the screen above the menu bar and make the element fill the web view. The second one would also keep you on your current Space, but sites' own :fullscreen styles wouldn't apply. I haven't decided.
One video site turns the whole screen white when a short clip ends in fullscreen. A bare WKWebView does the same, so I've left it alone. And once, early on, a fullscreen window got stuck white on the desktop, and every later fullscreen request in that process bounced back within 145 ms. Restarting fixed it and I haven't been able to make it happen again.
When this isn't the right tool
If what you want is to watch video, a browser or a dedicated player is better at it than Overlay. Safari gives you fullscreen and picture in picture without any of the trade-offs above. Overlay is for keeping a few web pages around your work, above fullscreen apps and across Spaces; fullscreen video is there so a video in one of those pages behaves the way you expect when you click the button.