<!DOCTYPE html>
<html>
  <head>
    <title>CSSOM View - scrollIntoView from position:fixed</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,minimum-scale=1">
    <link rel="author" title="David Bokan" href="mailto:bokan@chromium.org">
    <link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-scrollintoview">
    <script src="/resources/testharness.js"></script>
    <script src="/resources/testharnessreport.js"></script>
    <style>
      body {
        width: 1000vw;
        height: 1000vh;
        /* stripes so we can see scroll offset more easily */
        background: repeating-linear-gradient(
          45deg,
          #A2CFD9,
          #A2CFD9 100px,
          #C3F3FF 100px,
          #C3F3FF 200px
        );
      }

      .fixedContainer {
        position: fixed;
        bottom: 10px;
        left: 10px;
        width:  150px;
        height: 150px;
        background-color: coral;
      }

      .fixedContainer.scrollable {
        overflow: auto;
        left: unset;
        right: 10px;
      }

      button {
        position: absolute;
        margin: 5px;
      }

      .target {
        position: absolute;
        width: 10px;
        height: 10px;
        background-color: blue;
        left: 50%;
        top: 50%;
      }

      .scrollable .target {
        left: 200%;
        top: 200%;
      }

      iframe {
        width: 96vw;
        height: 300px;
        position: absolute;
        left: 2vw;
        top: 100px;
      }
    </style>
    <script>
    </script>
  </head>
  <body>
    <div style="width:90vw">
      <p>
        The orange boxes are position: fixed. Clicking ScrollIntoView in each box
        will attempt to scroll into view the blue target element inside that fixed
        container to block/inline: start (i.e. aligned with top left corner in RTL).
      </p>
      <p>
        scrollIntoView from a position:fixed element must not scroll its
        containing frame; however, it must scroll further ancestor scrollers as
        the element isn't fixed in relation to them.
      </p>
    </div>
    <iframe></iframe>
    <div class="fixedContainer">
      Box A
      <button id="fixedContainerBtn">ScrollIntoView</button>
      <div class="target"></div>
    </div>
    <div class="fixedContainer scrollable">
      Box C
      <button id="scrollableFixedContainerBtn">ScrollIntoView</button>
      <div class="target"></div>
    </div>
    <script>
      if (typeof setup != 'undefined') {
        setup({ explicit_done: true });
        window.addEventListener("load", runTests);
      }

      function scrollIntoView(evt) {
        const container = evt.target.closest('.fixedContainer');
        const target = container.querySelector('.target');
        target.scrollIntoView({block: 'start', inline: 'start'});
      }

      document.querySelectorAll('button').forEach((btn) => {
        btn.addEventListener('click', scrollIntoView);
      });

      const iframe = document.querySelector('iframe');
      iframe.onload = () => {
        frames[0].document.querySelectorAll('button').forEach((btn) => {
          btn.addEventListener('click', scrollIntoView);
        });
      }
      iframe.srcdoc = `
        <!DOCTYPE html>
        <style>
          body {
            height: 200vh;
            width: 200vw;
            /* stripes so we can see scroll offset more easily */
            background: repeating-linear-gradient(
              45deg,
              #C3A2D9,
              #C3A2D9 50px,
              #E5C0FF 50px,
              #E5C0FF 100px
            );
          }
          .fixedContainer {
            position: fixed;
            bottom: 10px;
            left: 10px;
            width:  150px;
            height: 150px;
            background-color: coral;
          }

          .fixedContainer.scrollable {
            overflow: auto;
            left: unset;
            right: 10px;
          }

          button {
            position: absolute;
            margin: 5px;
          }

          .target {
            position: absolute;
            width: 10px;
            height: 10px;
            background-color: blue;
            left: 50%;
            top: 50%;
          }

          .scrollable .target {
            left: 200%;
            top: 200%;
          }
        </style>
        IFRAME
        <div class="fixedContainer">
          Box B
          <button id="fixedContainerBtn">ScrollIntoView</button>
          <div class="target"></div>
        </div>
        <div class="fixedContainer scrollable">
          Box D
          <button id="scrollableFixedContainerBtn">ScrollIntoView</button>
          <div class="target"></div>
        </div>
      `;

      function reset() {
        [document, frames[0].document].forEach((doc) => {
          doc.scrollingElement.scrollLeft = 0;
          doc.scrollingElement.scrollTop = 0;
          doc.querySelectorAll('.fixedContainer').forEach((e) => {
            e.scrollLeft = 0;
            e.scrollTop = 0;
          });
        });
      }

      function runTests() {
        // Test scrollIntoView from a plain, unscrollable position:fixed element.
        // Nothing should scroll.
        test(() => {
          reset()
          const container = document.querySelector('.fixedContainer:not(.scrollable)');
          const target = container.querySelector('.target');
          target.scrollIntoView({block: 'start', inline: 'start'});
          assert_equals(window.scrollX, 0, 'must not scroll window [scrollX]');
          assert_equals(window.scrollY, 0, 'must not scroll window [scrollY]');
        }, `[Box A] scrollIntoView from unscrollable position:fixed`);

        // Same as above but from inside an iframe. Since the container is fixed
        // only to the iframe, we should scroll the outer window.
        test(() => {
          reset()
          const container = frames[0].document.querySelector('.fixedContainer:not(.scrollable)');
          const target = container.querySelector('.target');
          target.scrollIntoView({block: 'start', inline: 'start'});

          // Large approx to account for differences like scrollbars
          assert_approx_equals(window.scrollX, 100, 20, 'must scroll outer window [scrollX]');
          assert_approx_equals(window.scrollY, 300, 20, 'must scroll outer window [scrollY]');
          assert_equals(frames[0].window.scrollX, 0, 'must not scroll iframe [scrollX]');
          assert_equals(frames[0].window.scrollY, 0, 'must not scroll iframe [scrollY]');
        }, `[Box B] scrollIntoView from unscrollable position:fixed in iframe`);

        // Test scrollIntoView from a scroller that's position fixed. The
        // scroller should be scrolled but shouldn't bubble the scroll as the
        // window scrolling won't change the target's position.
        test(() => {
          reset()
          const container = document.querySelector('.fixedContainer.scrollable');
          const target = container.querySelector('.target');
          target.scrollIntoView({block: 'start', inline: 'start'});
          // Large approx to account for differences like scrollbars
          assert_equals(window.scrollX, 0, 'must not scroll window [scrollX]');
          assert_equals(window.scrollY, 0, 'must not scroll window [scrollY]');
          assert_approx_equals(container.scrollLeft, 145, 20,
              'scrollIntoView in container [scrollLeft]');
          assert_approx_equals(container.scrollTop, 145, 20,
              'scrollIntoView in container [scrollTop]');
        }, `[Box C] scrollIntoView from scrollable position:fixed`);

        // Same as above but from inside an iframe. In this case, the scroller
        // should bubble the scroll but skip its own frame (as it's fixed with
        // respect to its own frame's scroll offset) and bubble to the outer
        // window.
        test(() => {
          reset()
          const container = frames[0].document.querySelector('.fixedContainer.scrollable');
          const target = container.querySelector('.target');
          target.scrollIntoView({block: 'start', inline: 'start'});
          // Large approx to account for differences like scrollbars
          assert_approx_equals(window.scrollX, 740, 20, 'must scroll outer window [scrollX]');
          assert_approx_equals(window.scrollY, 360, 20, 'must scroll outer window [scrollY]');
          assert_approx_equals(container.scrollLeft, 145, 20,
              'scrollIntoView in container [scrollLeft]');
          assert_approx_equals(container.scrollTop, 145, 20,
              'scrollIntoView in container [scrollTop]');
          assert_equals(frames[0].window.scrollX, 0, 'must not scroll iframe [scrollX]');
          assert_equals(frames[0].window.scrollY, 0, 'must not scroll iframe [scrollY]');
        }, `[Box D] scrollIntoView from scrollable position:fixed in iframe`);

        done();
      }

    </script>
  </body>
</html>
