w3c / webextensions

Charter and administrivia for the WebExtensions Community Group (WECG)
Other
576 stars 50 forks source link

Proposal: getLeafTarget() method #624

Open polywock opened 1 month ago

polywock commented 1 month ago

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 because event.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() or stopImmediatePropagation(), which prevent downstream event listeners from receiving the event.

Example

window.addEventListener(type, e => {
    const trueTarget = browser.dom.getLeafTarget(e)     
    // .... 
}, {capture: true})
tophf commented 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.

Rob--W commented 3 weeks ago

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?

polywock commented 3 weeks ago

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.

Use case 1

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.

Use case 2

I wanted to show an overlay to interactively change the video's brightness.

Use case 3

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.

fregante commented 2 weeks ago

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.