Open polywock opened 1 month ago
this isn't a great workaround [...]
Particularly in case of [deeply] nested shadows, e.g. to intercept mouseover
and mousemove
event I have to recursively attach listeners to every hovered shadow and detach them on mouseleave
to avoid having thousands of listeners.
I wonder if this is something that should be solved in the web platform? I mean maybe they didn't do it initially because custom elements were expected to be simple and self-contained, but now they are used as freely as standard elements and contain complex layouts, which makes event delegation desirable to reduce the amount of listeners.
Independently of extensions, by design shadow DOM comes in two flavors, closed and open shadow DOM.
If the shadow root was created with mode: "open"
, you can use event.composedPath()
to get the information you're after, where the first element in the array may be the DOM element you're looking for.
Closed shadow DOM is supposed to represent a self-contained piece of DOM structure. In reality, web pages can of course use it for anything and embed complicated DOM trees.
What are the use cases of wanting to reach deep into closed shadow DOM?
What are the use cases of wanting to reach deep into closed shadow DOM?
Most are already served by openOrClosedShadowRoot
, but getLeafEvent()
offers a simpler approach.
If an extension implements shortcut keys using keydown/keyup, it's best practice to not trigger when the event target is an input element, textarea, etc. That way if the user is trying to type, the extension's shortcut won't be triggered. From what I've seen, most extensions do it this way.
window.addEventListener("keyup", e => {
const target = e.target
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") return
if (e.code === "Space") {
activateShortcut()
e.stopPropagation().
e.preventDefault()
}
}, {capture: true})
Replacing const target = e.target
with const target = browser.dom.getLeafEvent(e.target)
offers a more robust approach as it also takes into account the user typing into elements that are inside shadow DOMs. You could also do this using a recursive openOrClosedShadowRoot
check, but that's a more complex approach.
I wanted to show an overlay to interactively change the video's brightness.
pointerover
event wasn't being triggered on some websites who were stopping event propagation before it reached my listener.event.composedPath()
, but that resulted in more issues as many dark mode extensions inject styling on all open shadow roots. I know of ways to work around that (CSS priority hacks), but I still wanted a closed shadow for better isolation from the page. elementsFromPoint + openOrClosedShadowRoot
approach. I was satisfied with this approach, but it required a lot of trial and error, and it's not the most elegant solution. getLeafTarget()
existed, it would've taken me a tenth of the time with half the code. Translating text while hovering over it. If the text is inside a closed shadow DOM, you won't be able to detect the text target unless you recursively add listeners to all shadow DOMs (as @tophf mentioned, this approach is complicated and can have memory implications). In addition, if event propagation was stopped, there will be no way for the extension to access the text target.
What are the use cases of wanting to reach deep into closed shadow DOM?
I think this is a weird question in the context of extensions. The answer is the same as why you build extensions (and content scripts specifically): to extract, manipulate, extend content.
The page should be fully accessible to extensions regardless of the encapsulation web page authors used — unless it's protected in the name of security. I don’t think that's the case here.
This is similar to openOrClosedShadowRoot as will only be exposed for content scripts.
getLeafTarget(event: Event): Element
will take an Event as an argument and return the leaf target. This is helpful becauseevent.target
doesn't expose the actual event target if the target was inside a shadow DOM. For composed events,event.target
only points to the first shadow host.If you need access to the actual target, you can add an event listener to the shadow root(s) itself, but this isn't a great workaround because those event listeners are susceptible to
stopPropagation()
orstopImmediatePropagation()
, which prevent downstream event listeners from receiving the event.Example