How are Shadow DOM events from under <content> targeted?
Asked Answered
P

1

8

I'm trying to understand what events that originate in light DOM look like when received in shadow DOM via a <content> element. I'm reading the Shadow DOM W3C Draft, and I don't entirely understand it but it sounds like events are to be "retargeted" from the point of view of the EventListener attachment.

In the cases where event path is across multiple node trees, the event's information about the target of the event is adjusted in order to maintain encapsulation. Event retargeting is a process of computing relative targets for each ancestor of the node at which the event is dispatched. A relative target is a node that most accurately represents the target of a dispatched event at a given ancestor while maintaining the encapsulation.

and

At the time of event dispatch:

  • The Event target and currentTarget attributes must return the relative target for the node on which event listeners are invoked

So here's a simple Polymer custom element that just puts its children into a container, and adds a click EventListener to the container (in the shadow DOM). In this case the child is a button.

<!DOCTYPE html>
<html>
  <head>
    <script src="bower_components/platform/platform.js"></script>
    <link rel="import" href="bower_components/polymer/polymer.html">
  </head>
  <body unresolved>
    <polymer-element name="foo-bar">
      <template>
        <div id="internal-container" style="background-color:red; width:100%;">
          <content></content>
        </div>
      </template>
      <script>
       Polymer("foo-bar", {
         clickHandler: function(event) {
           console.log(event);
           var element = event.target;
           while (element) {
             console.log(element.tagName, element.id);
             element = element.parentElement;
           }
         },

         ready: function() {
           this.shadowRoot.querySelector('#internal-container').addEventListener('click', this.clickHandler);
         }
       });
      </script>
    </polymer-element>

    <foo-bar id="custom-element">
      <button>Click me</button>
    </foo-bar>
  </body>
</html>

When I run this on Chrome 38.0.2075.0 canary, when I click on the button I get:

MouseEvent {dataTransfer: null, toElement: button, fromElement: null, y: 19, x: 53…}altKey: falsebubbles: truebutton: 0cancelBubble: falsecancelable: truecharCode: 0clientX: 53clientY: 19clipboardData: undefinedctrlKey: falsecurrentTarget: nulldataTransfer: nulldefaultPrevented: falsedetail: 1eventPhase: 0fromElement: nullkeyCode: 0layerX: 53layerY: 19metaKey: falsemovementX: 0movementY: 0offsetX: 45offsetY: 10pageX: 53pageY: 19path: NodeList[0]relatedTarget: nullreturnValue: truescreenX: 472screenY: 113shiftKey: falsesrcElement: buttontarget: buttontimeStamp: 1404078533176toElement: buttontype: "click"view: WindowwebkitMovementX: 0webkitMovementY: 0which: 1x: 53y: 19__proto__: MouseEvent test.html:17
BUTTON  test.html:20
FOO-BAR custom-element test.html:20
BODY  test.html:20
HTML  test.html:20

and when I click on the container I get:

MouseEvent {dataTransfer: null, toElement: div#internal-container, fromElement: null, y: 15, x: 82…} test.html:17
DIV internal-container test.html:20

So I get an event target in either the light or shadow DOM, depending on which DOM the source element was in. I was expecting to get a target from the shadow DOM in both cases because that's where the EventListener is attached. My questions are:

  1. Is this the way it is supposed to work, and
  2. If so, is there an alternative way to get events that bubble up from the light DOM retargeted to the shadow DOM?

In case someone wants to ask, "What are you trying to do?", I'm not trying to do anything specifically other than understand the behavior.

Pardue answered 29/6, 2014 at 22:22 Comment(0)
I
14

Events with shadow dom are tricky. I try to capture a braindump below.

  1. Is this the way it is supposed to work

Yep. If you're testing in Chrome, you get native shadow dom.


I wrote a section on event retargeting in the HTML5Rocks - Shadow DOM 301 article. Basically, retargeting means that events that originate in the shadow dom look like they come from the element itself.

In your example, you're logging the event internal to the shadow dom, so it's still seen there. If you also add a 'click' listener outside of the element, the target will look as if it came from the element:

<script>
  var el = document.querySelector('#custom-element');
  el.addEventListener('click', function(e) {
    console.log(e.target.tagName); // logs FOO-Bar
  });
</script>

http://jsbin.com/womususe/1/edit

The 'click' event bubbles. This is why you see BUTTON in your top example. Why do you see it at all? You see it because the button is not part of your element's shadow dom. It's in the light dom and the target of the element. It's important to remember that light DOM nodes are still logically in the main document. They're not moved into the shadow dom, merely rendered at <content> insertion points.


BTW, there are a couple of Polymerized fixes to your examples:

  1. this.shadowRoot.querySelector('#internalcontainer') -> this.$.internalcontainer. this.$.ID is Polymer's "automatic node finding" feature.
  2. You don't need to use addEventListener() at all. Instead , use <div id="internalcontainer" on-click="{{clickHandler}}">. This is a declarative event handler.
Interdepartmental answered 30/6, 2014 at 3:24 Comment(3)
Thanks for the response (and all the other info you put out in the world)! I understand that the button is not part of the shadow DOM; that's why I'm surprised to see it on a shadow DOM listener. The spec says, "The Event target and currentTarget attributes must return the relative target for the node on which event listeners are invoked." so I expected events to be retargeted when passed through a <content> element. Just to be redundantly clear, are you saying that it complies with the spec for a shadow DOM listener to receive event targets from its host's DOM as well as its own DOM?Pardue
Yes, this is normal. The event retargetting goes the other direction: preventing outer DOM from seeing nodes inside the ShadowDOM.Pinckney
@ScottMiles Let's say an element at level -10 in the shadow DOM fires a "response" event. Thru retargeting it will be propagated on all 10 levels (hosts) above. At the same time, an element at level -5 fires an event named "response" as well, and retargeting occurs for this one too. If on level -4 you want to attach a handler for the "response" event on level -5 (unaware of the one fired on level -10), it will unwillingly also handle the event coming from -10. You cannot possibly know all events coming from deep "underwater" in a medium or large application. What's you opinion on this? Thanks.Norwich

© 2022 - 2024 — McMap. All rights reserved.