chromium/third_party/blink/web_tests/external/wpt/css/filter-effects/support/simulate-backdrop-blur.js

/**
 * Simulates a backdrop-filter: blur with the given radius by creating copies
 * of the blurred content mirrored at edges.
 * @param {Element} div          The element to display the blurred content in.
 * @param {Element[]} blurNodes  The elements to include in the blur.
 * @param {number} blurRadius    The radius of the backdrop blur.
 */
function simulateBackdropBlur(div, blurNodes, blurRadius) {
  // The amount to assume blur may oversample by beyond the blur radius.
  const BLUR_OVERSAMPLE = 2;

  const isAncestor = function(ancestor, div) {
    while (div && div != ancestor) {
      div = div.offsetParent;
    }
    return !!div;
  }

  const commonAncestor = function(a, b) {
    while (!isAncestor(a, b))
      a = a.offsetParent;
    return a;
  }

  const computeOffset = function(from, to) {
    const ancestor = commonAncestor(from, to);
    let offset = {left: 0, top: 0};
    while (from != ancestor) {
      offset.left += from.offsetLeft;
      offset.top += from.offsetTop;
      from = from.offsetParent;
    }
    while (to != ancestor) {
      offset.left -= to.offsetLeft;
      offset.top -= to.offsetTop;
      to = to.offsetParent;
    }
    return offset;
  }

  // Compute the number of copies needed in each direction.
  const w = div.offsetWidth;
  const h = div.offsetHeight;
  const copiesY = Math.ceil(BLUR_OVERSAMPLE * blurRadius / h);
  const copiesX = Math.ceil(BLUR_OVERSAMPLE * blurRadius / w);

  let clipNode = document.createElement('div');
  clipNode.style.backgroundColor = 'white';
  clipNode.style.position = 'absolute';
  clipNode.style.overflow = 'clip';
  clipNode.style.width = `${w}px`;
  clipNode.style.height = `${h}px`;
  clipNode.style.left = `${div.offsetLeft}px`;
  clipNode.style.top = `${div.offsetTop}px`;
  clipNode.style.opacity = getComputedStyle(div).opacity;
  let filterNode = document.createElement('div');
  clipNode.appendChild(filterNode);
  filterNode.style.width = `${w * (2*copiesX+1)}px`;
  filterNode.style.height = `${h * (2*copiesY+1)}px`;
  filterNode.style.position = 'absolute';
  filterNode.style.lineHeight = '0';
  filterNode.style.top = `${-copiesY * h}px`;
  filterNode.style.left = `${-copiesX * w}px`;
  filterNode.style.filter = `blur(${blurRadius}px)`;

  // Helper to clone everything except the node blurring the content in case
  // the "backdrop" content to blur is an ancestor.
  const cloneExcept = function(div, exclude) {
    let cloned = div.cloneNode(false);
    for (let child of div.children) {
      if (child != exclude) {
        cloned.appendChild(child.cloneNode(true));
      }
    }
    return cloned;
  }

  for (let y = -copiesY; y <= copiesY; y++) {
    for (let x = -copiesX; x <= copiesX; x++) {
      let copy = document.createElement('div');
      copy.style.position = 'relative';
      copy.style.width = `${w}px`;
      copy.style.height = `${h}px`;
      copy.style.display = 'inline-block';
      copy.style.overflow = 'clip';
      copy.setAttribute('data-x', x);
      copy.setAttribute('data-y', y);
      copy.style.transform = `scale(${Math.abs(x)%2 == 0 ? 1 : -1}, ${Math.abs(y)%2 == 0 ? 1 : -1})`
      for (let child of blurNodes) {
        const cloned = cloneExcept(child, div);
        cloned.style.position = 'absolute';
        const offset = computeOffset(child, div);
        cloned.style.top = `${offset.top}px`;
        cloned.style.left = `${offset.left}px`;
        copy.appendChild(cloned);
      }
      filterNode.appendChild(copy);
    }
  }
  div.parentElement.insertBefore(clipNode, div);
}