Synthesizing events inside a normal child of a shadow root.

What happens when we synthesize a click event in a child Element of a Shadow Root?

by David Iglesias

HTML

<element-with-shadow>
  <outside-shadow-root>
    Set "useShadow" to true to see the issue.
  </outside-shadow-root>
</element-with-shadow>

<!-- The below is just to show the results of the "test"
 -->
<div id="result">
  <h1>offsetX/Y</h1>
  <p>
  Expected: <span id="expected"></span>
  </p>
  <p>
  Actual: <span id="actual"></span>
  </p>
</div>

CSS

/* Reset */
* { box-sizing: border-box; }
html, body { width: 100%; height: 100%; margin: 0px; padding: 0px; font-family: sans-serif; }

/* Kinda relevant code */

element-with-shadow {
  display: block;
  height: 100%;
  background: #a00;
  color: white;
}

outside-shadow-root {
  display: block;
  height: 100%;
  background: #00a;
  /* Uncomment next line to cause an error in other UAs */
  /* margin-left: 20px; */
}

/* Some more eye candy */
#result {
  background: #3a3;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: 0px 20px;
}

#result.fail {
  background: #f00;
  color: white;
}

JavaScript

// Set this to true to see the issue.
let useShadow = false;

// Expected offsetX, Y in the synthetic event.
let expectedX = 100;
let expectedY = 200;

// The elements in the DOM...
let withShadow = document.querySelector('element-with-shadow');
let outsideShadowRoot = document.querySelector('outside-shadow-root');

// Add a shadow root to the element-with-shadow
if (useShadow) {
  let shadow = withShadow.attachShadow({
    mode: 'open',
  });
  // Add something in the shadow DOM
  let insideShadow = document.createElement('inside-shadow-dom');
  insideShadow.style.cssText = 'background: #afa; height: 100%; display: block; color: initial;';
  shadow.appendChild(insideShadow);
  insideShadow.innerText = "Firefox won't compute offsetX/Y (0, 0) for MouseEvents dispatched from 'outside-shadow-root' (regardless of its CSS) if a shadow gets attached to 'element-with-shadow'!";
}

// Synthesize an event in `outsideShadow`, and listen to it in `withShadow`:
withShadow.addEventListener('plop', _handleEvent);

outsideShadowRoot.dispatchEvent(new MouseEvent('plop', {
  bubbles: true,
  clientX: expectedX,
  clientY: expectedY
}));

// This just updates the #result with the results of the test.
function _handleEvent(event) {
  // console.log(event);
  if ((event.offsetX != expectedX)
    || (event.offsetY != expectedY)) {
    result.classList.add('fail');
  } else {
    result.classList.remove('fail');
  }
  expected.innerText = `${expectedX}, ${expectedY}`;
  actual.innerText = `${event.offsetX}, ${event.offsetY}`;
}