chromium/third_party/blink/web_tests/media/media-controls.js

var captionsButtonElement;
var captionsButtonCoordinates;

// As specified in mediaControls.css, this is how long it takes to fade out controls
const controlsFadeOutDurationMs = 300;

// The timeout for the hide-after-no-mouse-movement behavior. Defined (and
// should mirror) the value 'timeWithoutMouseMovementBeforeHidingMediaControls'
// in MediaControls.cpp.
const controlsMouseMovementTimeoutMs = 2500;

function isControlVisible(control) {
    var style = getComputedStyle(control);
    var visibility = style.getPropertyValue("visibility");
    var display = style.getPropertyValue("display");
    return (display != "none" && visibility == "visible");
}

function mediaControls(videoElement) {
  var controlID = '-webkit-media-controls';
  var element = mediaControlsElement(internals.shadowRoot(videoElement).firstChild, controlID);
  if (!element)
    throw 'Failed to find media controls';
  return element;
}

function buttonPanelElement(videoElement) {
  var controlID = '-internal-media-controls-button-panel';
  var element = mediaControlsElement(internals.shadowRoot(videoElement).firstChild, controlID);
  if (!element)
    throw 'Failed to find media controls button panel';
  return element;
}

function loadingPanelElement(videoElement) {
  var controlID = '-internal-media-controls-loading-panel';
  var element = mediaControlsElement(internals.shadowRoot(videoElement).firstChild, controlID);
  if (!element)
    throw 'Failed to find loading panel';
  return element;
}

function panelElement(videoElement) {
  var element = mediaControlsButton(videoElement, "panel");
  if (!element)
    throw 'Failed to find media controls panel';
  return element;
}

function castButton(videoElement) {
    var controlID = '-internal-media-controls-cast-button';
    var button = mediaControlsElement(internals.shadowRoot(videoElement).firstChild, controlID);
    if (!button)
        throw 'Failed to find cast button';
    return button;
}

function currentTimeElement(videoElement) {
  var controlID = '-webkit-media-controls-current-time-display';
  var element = mediaControlsElement(internals.shadowRoot(videoElement).firstChild, controlID);
  if (!element)
    throw 'Failed to find current time element';
  return element;
}

function timeRemainingElement(videoElement) {
  var controlID = '-webkit-media-controls-time-remaining-display';
  var element = mediaControlsElement(internals.shadowRoot(videoElement).firstChild, controlID);
  if (!element)
    throw 'Failed to find time remaining element';
  return element;
}

function downloadButton(videoElement) {
    var controlID = '-internal-media-controls-download-button';
    var button = mediaControlsElement(internals.shadowRoot(videoElement).firstChild, controlID);
    if (!button)
        throw 'Failed to find download button';
    return button;
}

function pictureInPictureButton(videoElement) {
  var controlID = '-internal-media-controls-picture-in-picture-button';
  var button = mediaControlsElement(internals.shadowRoot(videoElement).firstChild, controlID);
  if (!button)
    throw 'Failed to find picture-in-picture button';
  return button;
}

function fullscreenButton(videoElement) {
    var controlID = '-webkit-media-controls-fullscreen-button';
    var button = mediaControlsElement(internals.shadowRoot(videoElement).firstChild, controlID);
    if (!button)
        throw 'Failed to find fullscreen button';
    return button;
}

function overlayCastButton(videoElement)
{
    var controlID = '-internal-media-controls-overlay-cast-button';
    var button = mediaControlsElement(internals.shadowRoot(videoElement).firstChild, controlID);
    if (!button)
        throw 'Failed to find cast button';
    return button;
}

function overlayEnclosureElement(videoElement) {
    var controlID = '-webkit-media-controls-overlay-enclosure';
    var element = mediaControlsElement(internals.shadowRoot(videoElement).firstChild, controlID);
    if (!element)
        throw 'Failed to find overlay enclosure';
    return element;
}

function overflowButton(videoElement)
{
    var controlID = '-internal-media-controls-overflow-button';
    var button = mediaControlsElement(internals.shadowRoot(videoElement).firstChild, controlID);
    if (!button)
        throw 'Failed to find overflow button';
    return button;
}

function textTrackMenu(video)
{
  var controlID = '-internal-media-controls-text-track-list';
  var element = mediaControlsElement(internals.shadowRoot(video).firstChild, controlID);
  if (!element)
    throw 'Failed to find the overflow menu';
  return element;
}

function textTrackListHeader(video)
{
  var element = textTrackMenu(video).childNodes[0];
  if (!element)
    throw 'Failed to find the track list header'
  return element;
}

function playbackSpeedMenu(video)
{
  var controlID = '-internal-media-controls-playback-speed-list';
  var element = mediaControlsElement(internals.shadowRoot(video).firstChild, controlID);
  if (!element)
    throw 'Failed to find the playback speed menu';
  return element;
}

function playbackSpeedListHeader(video)
{
  var element = playbackSpeedMenu(video).childNodes[0];
  if (!element)
    throw 'Failed to find the playback speed header'
  return element;
}

function overflowMenu(video)
{
  var controlID = '-internal-media-controls-overflow-menu-list';
  var element = mediaControlsElement(internals.shadowRoot(video).firstChild, controlID);
  if (!element)
    throw 'Failed to find the overflow menu';
  return element;
}

function overflowItem(video, controlID) {
  var element = mediaControlsElement(overflowMenu(video).firstChild, controlID);
  if (!element)
    throw 'Failed to find overflow item: ' + controlID;
  return element;
}

function fullscreenOverflowItem(video) {
  return overflowItem(video, '-webkit-media-controls-fullscreen-button');
}

function muteOverflowItem(video) {
  return overflowItem(video, '-webkit-media-controls-mute-button');
}

function captionsOverflowItem(video) {
  return overflowItem(video, '-webkit-media-controls-toggle-closed-captions-button');
}

function playbackSpeedOverflowItem(video) {
  return overflowItem(video, '-internal-media-controls-playback-speed-button');
}

function castOverflowItem(video) {
  return overflowItem(video, '-internal-media-controls-cast-button');
}

function downloadsOverflowItem(video) {
  return overflowItem(video, '-internal-media-controls-download-button');
}

function pictureInPictureOverflowItem(video) {
  return overflowItem(video, '-internal-media-controls-picture-in-picture-button');
}

function mediaControlsElement(first, id)
{
    for (var element = first; element; element = element.nextSibling) {
        // Not every element in the media controls has a shadow pseudo ID, eg. the
        // text nodes for the time values, so guard against exceptions.
        try {
            if (internals.shadowPseudoId(element) == id)
                return element;
        } catch (exception) { }

        if (element.firstChild) {
            var childElement = mediaControlsElement(element.firstChild, id);
            if (childElement)
                return childElement;
        }
    }

    return null;
}

function getFocusedElement(video) {
  return internals.shadowRoot(video).activeElement;
}

function mediaControlsButton(element, id)
{
    var controlID = "-webkit-media-controls-" + id;
    var button = mediaControlsElement(internals.shadowRoot(element).firstChild, controlID);
    if (!button)
        throw "Failed to find media control element ID '" + id + "'";
    return button;
}

function elementCoordinates(element)
{
    var elementBoundingRect = element.getBoundingClientRect();
    var x = elementBoundingRect.left + elementBoundingRect.width / 2;
    var y = elementBoundingRect.top + elementBoundingRect.height / 2;
    return new Array(x, y);
}

function coordinatesOutsideElement(element)
{
    var elementBoundingRect = element.getBoundingClientRect();
    var x = elementBoundingRect.left - 1;
    var y = elementBoundingRect.top - 1;
    return new Array(x, y);
}

function videoLeftEdgeCoordinates(element) {
    const rect = element.getBoundingClientRect();
    return [rect.left + 5, rect.top + rect.height / 2];
}

function videoRightEdgeCoordinates(element) {
    const rect = element.getBoundingClientRect();
    return [rect.right - 5, rect.top + rect.height / 2];
}

function mediaControlsButtonCoordinates(element, id)
{
    var button = mediaControlsButton(element, id);
    return elementCoordinates(button);
}

function mediaControlsButtonDimensions(element, id)
{
    var button = mediaControlsButton(element, id);
    var buttonBoundingRect = button.getBoundingClientRect();
    return new Array(buttonBoundingRect.width, buttonBoundingRect.height);
}

function textTrackContainerElement(parentElement) {
    return mediaControlsElement(internals.shadowRoot(parentElement).firstChild,
        "-webkit-media-text-track-container");
}

function textTrackCueElementByIndex(parentElement, cueIndex) {
    var displayElement = textTrackDisplayElement(parentElement);
    if (displayElement) {
        for (i = 0; i < cueIndex; i++)
            displayElement = displayElement.nextSibling;
    }

    return displayElement;
}

function textTrackRegionElement(parentElement)
{
    var containerElement = textTrackContainerElement(parentElement);
    return mediaControlsElement(containerElement.firstChild, "-webkit-media-text-track-region");
}

function textTrackRegionContainerElement(parentElement)
{
    var containerElement = textTrackContainerElement(parentElement);
    return mediaControlsElement(containerElement.firstChild, "-webkit-media-text-track-region-container");
}

function textTrackDisplayElement(parentElement)
{
    var containerElement = textTrackContainerElement(parentElement);
    return mediaControlsElement(containerElement.firstChild, "-webkit-media-text-track-display");
}

function isCastButtonEnabled(video) {
  var button = castOverflowItem(video);
  return !button.disabled && button.style.display != "none";
}

function isClosedCaptionsButtonEnabled(video) {
  var button = captionsOverflowItem(video);
  return !button.disabled && button.style.display != "none";
}

function isPlaybackSpeedButtonEnabled(video) {
  var button = playbackSpeedOverflowItem(video);
  return !button.disabled && button.style.display != "none";
}

function isDownloadsButtonEnabled(video) {
  var button = downloadsOverflowItem(video);
  return !button.disabled && button.style.display != "none";
}

function isFullscreenButtonEnabled(video) {
  var button = fullscreenButton(video);
  var overflowButton = fullscreenOverflowItem(video);
  return (!button.disabled && button.style.display != "none") ||
      (!overflowButton.disabled && overflowButton.style.display != "none");
}

function isClosedCaptionsButtonVisible(currentMediaElement)
{
    var captionsButtonElement = mediaControlsButton(currentMediaElement, "toggle-closed-captions-button");
    var captionsButtonCoordinates = mediaControlsButtonCoordinates(currentMediaElement, "toggle-closed-captions-button");

    if (!captionsButtonElement.disabled
        && captionsButtonCoordinates[0] > 0
        && captionsButtonCoordinates[1] > 0) {
        return true;
    }

    return false;
}

function toggleClosedCaptionsButton(videoElement) {
    return mediaControlsButton(videoElement, 'toggle-closed-captions-button');
}

function playbackSpeedButton(videoElement) {
  var controlID = '-internal-media-controls-playback-speed-button';
  var button = mediaControlsElement(internals.shadowRoot(videoElement).firstChild, controlID);
  if (!button)
    throw 'Failed to find playback speed button';
  return button;
}

function playButton(videoElement) {
    return mediaControlsButton(videoElement, 'play-button');
}

function enabledPlayButton(videoElement) {
  if (internals.runtimeFlags.mediaControlsOverlayPlayButtonEnabled) {
    return mediaControlsOverlayPlayButton(videoElement);
  }
  return playButton(videoElement);
}

function muteButton(videoElement) {
    return mediaControlsButton(videoElement, 'mute-button');
}

function volumeSliderElement(videoElement) {
  return mediaControlsButton(videoElement, 'volume-slider');
}

function isVolumeSliderOpen(videoElement) {
  return !volumeSliderElement(videoElement).classList.contains('closed');
}

function runAfterVolumeSliderAnimationEnds(func) {
  // 300ms timer plus 200ms slack.
  const volumeSliderTimeoutMs = 300 + 200;
  setTimeout(func, volumeSliderTimeoutMs);
}

function timelineElement(videoElement) {
    return mediaControlsButton(videoElement, 'timeline');
}

function timelineThumb(videoElement) {
    const timeline = timelineElement(videoElement);
    const thumb = internals.shadowRoot(timeline).getElementById('thumb');
    if (!thumb)
        throw 'Failed to find timeline thumb';
    return thumb;
}

function scrubbingMessageElement(videoElement) {
    var controlID = '-internal-media-controls-scrubbing-message';
    var button = mediaControlsElement(internals.shadowRoot(videoElement).firstChild, controlID);
    if (!button)
        throw 'Failed to find scrubbing message element';
    return button;
}

function clickAtCoordinates(x, y) {
    eventSender.mouseMoveTo(x, y);
    eventSender.mouseDown();
    eventSender.mouseUp();
}

function openOverflowAndClickButton(video, button, callback) {
  singleTapOnControl(overflowButton(video), function () {
    singleTapOnControl(button, callback);
  });
}

function clickDownloadButton(video, callback) {
  openOverflowAndClickButton(video, downloadsOverflowItem(video), callback);
}

function textTrackListItemAtIndex(video, index) {
    var trackListItems = textTrackMenu(video).childNodes;
    for (var i = 0; i < trackListItems.length; i++) {
        var trackListItem = trackListItems[i];
        var innerCheckbox = textTrackListItemInnerCheckbox(trackListItem);
        if (innerCheckbox && innerCheckbox.getAttribute("data-track-index") == index)
            return trackListItem;
    }
}

function textTrackListItemInnerCheckbox(trackListItem) {
  const children = trackListItem.children;
  for (var i = 0; i < children.length; i++) {
    const child = children[i];
    if (internals.shadowPseudoId(child) == "-internal-media-controls-text-track-list-item-input")
      return child;
  }
  return null;
}

function textTrackListItemInnerKindIndicator(trackListItem) {
  const children = trackListItem.children;
  for (var i = 0; i < children.length; i++) {
    const child = children[i];
    const pseudoId = internals.shadowPseudoId(child);
    if (pseudoId == "-internal-media-controls-text-track-list-kind-captions" ||
        pseudoId == "-internal-media-controls-text-track-list-kind-subtitles") {
      return child;
    }
  }
  return null;
}

function clickCaptionButton(video, callback) {
  openOverflowAndClickButton(video, captionsOverflowItem(video), callback);
}

function clickTextTrackAtIndex(video, index, callback) {
    clickCaptionButton(video, function () {
      var track = textTrackListItemAtIndex(video, index);
      track.scrollIntoView();
      singleTapOnControl(track, callback);
    });
}

function turnClosedCaptionsOff(video, callback)
{
    clickTextTrackAtIndex(video, -1, callback);
}

function checkCaptionsVisible(video, captions)
{
    for (var i = 0; i < captions.length; i++) {
      assert_equals(textTrackCueElementByIndex(video, i).innerText, captions[i]);
    }
}

function checkCaptionsHidden(video)
{
    assert_equals(textTrackDisplayElement(video), null);
}

function playbackSpeedListItemAtPlaybackRate(video, playbackRate) {
  var playbackSpeedItems = playbackSpeedMenu(video).childNodes;
  for (var i = 0; i < playbackSpeedItems.length; i++) {
      var playbackSpeedItem = playbackSpeedItems[i];
      var innerCheckbox = playbackSpeedListItemInnerCheckbox(playbackSpeedItem);
      if (innerCheckbox && innerCheckbox.getAttribute("data-playback-rate") == playbackRate)
          return playbackSpeedItem;
  }
}

function clickPlaybackSpeedButton(video, callback) {
  openOverflowAndClickButton(video, playbackSpeedOverflowItem(video), function() {
    var playbackSpeed = playbackSpeedListItemAtPlaybackRate(video, video.playbackRate);
    var playbackSpeedsList = playbackSpeedMenu(video);
    assert_between_inclusive(playbackSpeedsList.scrollTop,
        playbackSpeed.offsetTop + playbackSpeed.offsetHeight - playbackSpeedsList.offsetHeight,
        playbackSpeed.offsetTop);
    callback();
  });
}

function clickPlaybackSpeedAtPlaybackRate(video, playbackRate, callback) {
  clickPlaybackSpeedButton(video, function () {
    var playbackSpeed = playbackSpeedListItemAtPlaybackRate(video, playbackRate);
    playbackSpeed.scrollIntoView();
    singleTapOnControl(playbackSpeed, callback);
  });
}

function playbackSpeedListItemInnerCheckbox(playbackSpeedListItem) {
  const children = playbackSpeedListItem.children;
  for (var i = 0; i < children.length; i++) {
    const child = children[i];
    if (internals.shadowPseudoId(child) == "-internal-media-controls-playback-speed-list-item-input")
      return child;
  }
  return null;
}

function runAfterHideMediaControlsTimerFired(func, mediaElement)
{
    if (mediaElement.paused)
        throw "The media element is not playing";

    // Compute the time it'll take until the controls will be invisible -
    // assuming playback has been started prior to invoking this
    // function. Allow 500ms slack.
    var hideTimeoutMs = controlsMouseMovementTimeoutMs + controlsFadeOutDurationMs + 500;

    if (!mediaElement.loop && hideTimeoutMs >= 1000 * (mediaElement.duration - mediaElement.currentTime))
        throw "The media will end before the controls have been hidden";

    setTimeout(func, hideTimeoutMs);
}

function runAfterDoubleTapTimerFired(func) {
  // 300ms timer plus 500ms slack.
  const doubleTapTimeoutMs = 300 + 500;
  setTimeout(func, doubleTapTimeoutMs);
}

// Requests an animation frame.
function waitForHoverEffectUpdate(func) {
  // The hover effect is updated at the next animation frame after the layout
  // changes.
  return new Promise((resolve) => {
    requestAnimationFrame(() => {
      resolve();
    });
  });
}

function hoverMuteButton(video, func) {
  // Wait for hover timer fires
  const delayedCallback = function() { setTimeout(func, 1); };
  const muteBtn = muteButton(video);
  hoverOverControl(muteBtn, delayedCallback);
}

function hasEnabledFullscreenButton(element) {
  var button = fullscreenButton(element);
  return !button.disabled && button.style.display != "none";
}

function isControlsPanelVisible(element)
{
    return getComputedStyle(mediaControlsButton(element, "panel")).opacity == "1";
}

function isVisible(button) {
    var computedStyle = getComputedStyle(button);
    return computedStyle.display !== "none" &&
           computedStyle.visibility === "visible";
}

function checkButtonHasClass(button, className) {
  assert_true(button.classList.contains(className));
}

function checkButtonNotHasClass(button, className) {
  assert_false(button.classList.contains(className));
}

function checkControlsClassName(videoElement, className) {
  assert_equals(internals.shadowRoot(videoElement).firstChild.className, className);
}

function checkControlsHasClass(videoElement, className) {
  assert_true(mediaControls(videoElement).classList.contains(className),
      'Controls should have class: ' + className);
}

function checkControlsDoesNotHaveClass(videoElement, className) {
  assert_false(mediaControls(videoElement).classList.contains(className),
      'Controls should not have class: ' + className);
}

function checkControlsHasClasses(videoElement, classes) {
  classes.forEach(className => {
    checkControlsHasClass(videoElement, className);
  });
}

function checkControlsDoesNotHaveClasses(videoElement, classes) {
  classes.forEach(className => {
    checkControlsDoesNotHaveClass(videoElement, className);
  });
}

function mediaControlsOverlayPlayButton(videoElement) {
  return mediaControlsButton(videoElement, 'overlay-play-button');
}

function mediaControlsOverlayPlayButtonInternal(videoElement) {
  var controlID = '-internal-media-controls-overlay-play-button-internal';
  var element = mediaControlsElement(
      internals.shadowRoot(
          mediaControlsOverlayPlayButton(videoElement)).firstChild, controlID);
  if (!element)
    throw 'Failed to find the internal overlay play button';
  return element;
}

function pictureInPictureInterstitial(videoElement) {
  var controlID = '-internal-picture-in-picture-interstitial-message';

  var interstitial = getElementByPseudoId(internals.shadowRoot(videoElement).firstChild, controlID);
  if (!interstitial)
      throw 'Failed to find picture in picture interstitial';
  return interstitial;
}

function checkPictureInPictureInterstitialDoesNotExist(videoElement) {
  var controlID = '-internal-picture-in-picture-interstitial-message';

  var interstitial = getElementByPseudoId(internals.shadowRoot(videoElement), controlID);
  if (interstitial)
      throw 'Should not have a picture in picture interstitial';
}

function doubleTapAtCoordinates(x, y, timeout, callback) {
  timeout = timeout == undefined ? 100 : timeout;

  chrome.gpuBenchmarking.pointerActionSequence([
    {
      source: 'mouse',
      actions: [
        { name: 'pointerDown', x: x, y: y },
        { name: 'pointerUp' },
        { name: 'pause', duration: timeout },
        { name: 'pointerDown', x: x, y: y },
        { name: 'pointerUp' }
      ]
    }
  ], callback);
}

function singleTapAtCoordinates(xPos, yPos, callback) {
  chrome.gpuBenchmarking.pointerActionSequence([
    {
      source: 'mouse',
      actions: [
        { name: 'pointerDown', x: xPos, y: yPos },
        { name: 'pointerUp' }
      ]
    }
  ], callback);
}

function singleTapOutsideControl(control, callback) {
  const coordinates = coordinatesOutsideElement(control);
  singleTapAtCoordinates(coordinates[0], coordinates[1], callback);
}

function singleTapOnControl(control, callback) {
  const coordinates = elementCoordinates(control);
  singleTapAtCoordinates(coordinates[0], coordinates[1], callback);
}

function hoverOverControl(control, callback) {
  const coordinates = elementCoordinates(control);
  chrome.gpuBenchmarking.pointerActionSequence([
    {
      source: 'mouse',
      actions: [
        { name: 'pointerMove', x: coordinates[0], y: coordinates[1] }
      ]
    }
  ], callback);
}

// This function does not work on Mac due to crbug.com/613672. When using this
// function, add an entry into TestExpectations to skip on Mac.
function singleTouchAtCoordinates(xPos, yPos, callback) {
  chrome.gpuBenchmarking.pointerActionSequence([
    {
      source: 'touch',
      actions: [
        { name: 'pointerDown', x: xPos, y: yPos },
        { name: 'pointerUp' }
      ]
    }
  ], callback);
}

function doubleTouchAtCoordinates(x, y, timeout, callback) {
  timeout = timeout == undefined ? 100 : timeout;

  chrome.gpuBenchmarking.pointerActionSequence([
    {
      source: 'touch',
      actions: [
        { name: 'pointerDown', x: x, y: y },
        { name: 'pointerUp' },
        { name: 'pause', duration: timeout },
        { name: 'pointerDown', x: x, y: y },
        { name: 'pointerUp' }
      ]
    }
  ], callback);
}

function singleTouchOnControl(control, callback) {
  const coordinates = elementCoordinates(control);
  singleTouchAtCoordinates(coordinates[0], coordinates[1], callback);
}

function traverseNextNode(node, stayWithin) {
    var nextNode = node.firstChild;
    if (nextNode)
        return nextNode;

    if (stayWithin && node === stayWithin)
        return null;

    nextNode = node.nextSibling;
    if (nextNode)
        return nextNode;

    nextNode = node;
    while (nextNode && !nextNode.nextSibling && (!stayWithin || !nextNode.parentNode || nextNode.parentNode !== stayWithin))
        nextNode = nextNode.parentNode;
    if (!nextNode)
        return null;

    return nextNode.nextSibling;
}

function getElementByPseudoId(root, pseudoId) {
    if (!window.internals)
        return null;
    var node = root;
    while (node) {
        if (node.nodeType === Node.ELEMENT_NODE && internals.shadowPseudoId(node) === pseudoId)
            return node;
        node = traverseNextNode(node, root);
    }
    return null;
}

function enableTestMode(video) {
  if (window.internals)
    internals.setMediaControlsTestMode(video, true);
}

function enableImmersiveMode(t) {
  if (!window.internals)
    return;

  const oldImmersive = internals.settings.immersiveModeEnabled;
  internals.settings.setImmersiveModeEnabled(true);
  t.add_cleanup(() => {
    internals.settings.setImmersiveModeEnabled(oldImmersive);
  });
}

function setPreferHiddenVolumeControlsForTest(t, preferHidden) {
  if (!window.internals)
    return;

  const oldSetting = internals.settings.preferHiddenVolumeControls;
  internals.settings.setPreferHiddenVolumeControls(preferHidden);
  t.add_cleanup(() => {
    internals.settings.setPreferHiddenVolumeControls(oldSetting);
  });
}