text: /**
   * Copyright (c) Facebook, Inc. and its affiliates.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @flow
   */
  
  export type Rect = {
    bottom: number,
    height: number,
    left: number,
    right: number,
    top: number,
    width: number,
    ...
  };
  
  // Get the window object for the document that a node belongs to,
  // or return null if it cannot be found (node not attached to DOM,
  // etc).
  export function getOwnerWindow(node: HTMLElement): typeof window | null {
    if (!node.ownerDocument) {
      return null;
    }
    return node.ownerDocument.defaultView;
  }
  
  // Get the iframe containing a node, or return null if it cannot
  // be found (node not within iframe, etc).
  export function getOwnerIframe(node: HTMLElement): HTMLElement | null {
    const nodeWindow = getOwnerWindow(node);
    if (nodeWindow) {
      return nodeWindow.frameElement;
    }
    return null;
  }
  
  // Get a bounding client rect for a node, with an
  // offset added to compensate for its border.
  export function getBoundingClientRectWithBorderOffset(node: HTMLElement) {
    const dimensions = getElementDimensions(node);
    return mergeRectOffsets([
      node.getBoundingClientRect(),
      {
        top: dimensions.borderTop,
        left: dimensions.borderLeft,
        bottom: dimensions.borderBottom,
        right: dimensions.borderRight,
        // This width and height won't get used by mergeRectOffsets (since this
        // is not the first rect in the array), but we set them so that this
        // object typechecks as a ClientRect.
        width: 0,
        height: 0,
      },
    ]);
  }
  
  // Add together the top, left, bottom, and right properties of
  // each ClientRect, but keep the width and height of the first one.
  export function mergeRectOffsets(rects: Array<Rect>): Rect {
    return rects.reduce((previousRect, rect) => {
      if (previousRect == null) {
        return rect;
      }
  
      return {
        top: previousRect.top + rect.top,
        left: previousRect.left + rect.left,
        width: previousRect.width,
        height: previousRect.height,
        bottom: previousRect.bottom + rect.bottom,
        right: previousRect.right + rect.right,
      };
    });
  }
  
  // Calculate a boundingClientRect for a node relative to boundaryWindow,
  // taking into account any offsets caused by intermediate iframes.
  export function getNestedBoundingClientRect(
    node: HTMLElement,
    boundaryWindow: typeof window,
  ): Rect {
    const ownerIframe = getOwnerIframe(node);
    if (ownerIframe && ownerIframe !== boundaryWindow) {
      const rects = [node.getBoundingClientRect()];
      let currentIframe = ownerIframe;
      let onlyOneMore = false;
      while (currentIframe) {
        const rect = getBoundingClientRectWithBorderOffset(currentIframe);
        rects.push(rect);
        currentIframe = getOwnerIframe(currentIframe);
  
        if (onlyOneMore) {
          break;
        }
        // We don't want to calculate iframe offsets upwards beyond
        // the iframe containing the boundaryWindow, but we
        // need to calculate the offset relative to the boundaryWindow.
        if (currentIframe && getOwnerWindow(currentIframe) === boundaryWindow) {
          onlyOneMore = true;
        }
      }
  
      return mergeRectOffsets(rects);
    } else {
      return node.getBoundingClientRect();
    }
  }
  
  export function getElementDimensions(domElement: Element) {
    const calculatedStyle = window.getComputedStyle(domElement);
    return {
      borderLeft: parseInt(calculatedStyle.borderLeftWidth, 10),
      borderRight: parseInt(calculatedStyle.borderRightWidth, 10),
      borderTop: parseInt(calculatedStyle.borderTopWidth, 10),
      borderBottom: parseInt(calculatedStyle.borderBottomWidth, 10),
      marginLeft: parseInt(calculatedStyle.marginLeft, 10),
      marginRight: parseInt(calculatedStyle.marginRight, 10),
      marginTop: parseInt(calculatedStyle.marginTop, 10),
      marginBottom: parseInt(calculatedStyle.marginBottom, 10),
      paddingLeft: parseInt(calculatedStyle.paddingLeft, 10),
      paddingRight: parseInt(calculatedStyle.paddingRight, 10),
      paddingTop: parseInt(calculatedStyle.paddingTop, 10),
      paddingBottom: parseInt(calculatedStyle.paddingBottom, 10),
    };
  }
  