layout/generic/nsVideoFrame.cpp
author Boris Zbarsky <bzbarsky@mit.edu>
Sat, 11 May 2019 08:28:29 +0000
changeset 532359 6103006e3172f0ef258a30256b8cc7a6553e7f14
parent 531253 451701e88d92935b77385235832865dabe8c2942
child 533271 7db30b209d7e1d8784dbb1f37ffec3434d6c5676
permissions -rw-r--r--
Bug 1550937. Stop using [array] in nsIEventListenerService. r=smaug Differential Revision: https://phabricator.services.mozilla.com/D30773

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* rendering object for the HTML <video> element */

#include "nsVideoFrame.h"

#include "nsCOMPtr.h"
#include "nsGkAtoms.h"

#include "mozilla/PresShell.h"
#include "mozilla/dom/HTMLVideoElement.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/layers/RenderRootStateManager.h"
#include "nsDisplayList.h"
#include "nsGenericHTMLElement.h"
#include "nsPresContext.h"
#include "nsContentCreatorFunctions.h"
#include "nsBoxLayoutState.h"
#include "nsBoxFrame.h"
#include "nsIContentInlines.h"
#include "nsImageFrame.h"
#include "nsIImageLoadingContent.h"
#include "nsContentUtils.h"
#include "ImageContainer.h"
#include "ImageLayers.h"
#include "nsStyleUtil.h"
#include <algorithm>

using namespace mozilla;
using namespace mozilla::layers;
using namespace mozilla::dom;
using namespace mozilla::gfx;

nsIFrame* NS_NewHTMLVideoFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
  return new (aPresShell) nsVideoFrame(aStyle, aPresShell->GetPresContext());
}

NS_IMPL_FRAMEARENA_HELPERS(nsVideoFrame)

// A matrix to obtain a correct-rotated video frame.
static Matrix ComputeRotationMatrix(gfxFloat aRotatedWidth,
                                    gfxFloat aRotatedHeight,
                                    VideoInfo::Rotation aDegrees) {
  Matrix shiftVideoCenterToOrigin;
  if (aDegrees == VideoInfo::Rotation::kDegree_90 ||
      aDegrees == VideoInfo::Rotation::kDegree_270) {
    shiftVideoCenterToOrigin =
        Matrix::Translation(-aRotatedHeight / 2.0, -aRotatedWidth / 2.0);
  } else {
    shiftVideoCenterToOrigin =
        Matrix::Translation(-aRotatedWidth / 2.0, -aRotatedHeight / 2.0);
  }

  Matrix rotation = Matrix::Rotation(gfx::Float(aDegrees / 180.0 * M_PI));
  Matrix shiftLeftTopToOrigin =
      Matrix::Translation(aRotatedWidth / 2.0, aRotatedHeight / 2.0);
  return shiftVideoCenterToOrigin * rotation * shiftLeftTopToOrigin;
}

static void SwapScaleWidthHeightForRotation(IntSize& aSize,
                                            VideoInfo::Rotation aDegrees) {
  if (aDegrees == VideoInfo::Rotation::kDegree_90 ||
      aDegrees == VideoInfo::Rotation::kDegree_270) {
    int32_t tmpWidth = aSize.width;
    aSize.width = aSize.height;
    aSize.height = tmpWidth;
  }
}

nsVideoFrame::nsVideoFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
    : nsContainerFrame(aStyle, aPresContext, kClassID) {
  EnableVisibilityTracking();
}

nsVideoFrame::~nsVideoFrame() {}

NS_QUERYFRAME_HEAD(nsVideoFrame)
  NS_QUERYFRAME_ENTRY(nsVideoFrame)
  NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)

nsresult nsVideoFrame::CreateAnonymousContent(
    nsTArray<ContentInfo>& aElements) {
  nsNodeInfoManager* nodeInfoManager =
      GetContent()->GetComposedDoc()->NodeInfoManager();
  RefPtr<NodeInfo> nodeInfo;

  if (HasVideoElement()) {
    // Create an anonymous image element as a child to hold the poster
    // image. We may not have a poster image now, but one could be added
    // before we load, or on a subsequent load.
    nodeInfo = nodeInfoManager->GetNodeInfo(
        nsGkAtoms::img, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
    NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
    mPosterImage = NS_NewHTMLImageElement(nodeInfo.forget());
    NS_ENSURE_TRUE(mPosterImage, NS_ERROR_OUT_OF_MEMORY);

    // Set the nsImageLoadingContent::ImageState() to 0. This means that the
    // image will always report its state as 0, so it will never be reframed
    // to show frames for loading or the broken image icon. This is important,
    // as the image is native anonymous, and so can't be reframed (currently).
    nsCOMPtr<nsIImageLoadingContent> imgContent =
        do_QueryInterface(mPosterImage);
    NS_ENSURE_TRUE(imgContent, NS_ERROR_FAILURE);

    imgContent->ForceImageState(true, 0);
    // And now have it update its internal state
    mPosterImage->UpdateState(false);

    UpdatePosterSource(false);

    if (!aElements.AppendElement(mPosterImage)) return NS_ERROR_OUT_OF_MEMORY;

    // Set up the caption overlay div for showing any TextTrack data
    nodeInfo = nodeInfoManager->GetNodeInfo(
        nsGkAtoms::div, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
    NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
    mCaptionDiv = NS_NewHTMLDivElement(nodeInfo.forget());
    NS_ENSURE_TRUE(mCaptionDiv, NS_ERROR_OUT_OF_MEMORY);
    nsGenericHTMLElement* div =
        static_cast<nsGenericHTMLElement*>(mCaptionDiv.get());
    div->SetClassName(NS_LITERAL_STRING("caption-box"));

    if (!aElements.AppendElement(mCaptionDiv)) return NS_ERROR_OUT_OF_MEMORY;
    UpdateTextTrack();
  }

  return NS_OK;
}

void nsVideoFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
                                            uint32_t aFliter) {
  if (mPosterImage) {
    aElements.AppendElement(mPosterImage);
  }

  if (mCaptionDiv) {
    aElements.AppendElement(mCaptionDiv);
  }
}

nsIContent* nsVideoFrame::GetVideoControls() {
  if (!mContent->GetShadowRoot()) {
    return nullptr;
  }

  // The video controls <div> is the only child of the UA Widget Shadow Root
  // if it is present. It is only lazily inserted into the DOM when
  // the controls attribute is set.
  MOZ_ASSERT(mContent->GetShadowRoot()->IsUAWidget());
  MOZ_ASSERT(1 >= mContent->GetShadowRoot()->GetChildCount());
  return mContent->GetShadowRoot()->GetFirstChild();
}

void nsVideoFrame::DestroyFrom(nsIFrame* aDestructRoot,
                               PostDestroyData& aPostDestroyData) {
  aPostDestroyData.AddAnonymousContent(mCaptionDiv.forget());
  aPostDestroyData.AddAnonymousContent(mPosterImage.forget());
  nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
}

already_AddRefed<Layer> nsVideoFrame::BuildLayer(
    nsDisplayListBuilder* aBuilder, LayerManager* aManager,
    nsDisplayItem* aItem,
    const ContainerLayerParameters& aContainerParameters) {
  nsRect area = GetContentRectRelativeToSelf() + aItem->ToReferenceFrame();
  HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());

  nsIntSize videoSizeInPx;
  if (NS_FAILED(element->GetVideoSize(&videoSizeInPx)) || area.IsEmpty()) {
    return nullptr;
  }

  RefPtr<ImageContainer> container = element->GetImageContainer();
  if (!container) return nullptr;

  // Retrieve the size of the decoded video frame, before being scaled
  // by pixel aspect ratio.
  mozilla::gfx::IntSize frameSize = container->GetCurrentSize();
  if (frameSize.width == 0 || frameSize.height == 0) {
    // No image, or zero-sized image. No point creating a layer.
    return nullptr;
  }

  // Convert video size from pixel units into app units, to get an aspect-ratio
  // (which has to be represented as a nsSize) and an IntrinsicSize that we
  // can pass to ComputeObjectRenderRect.
  auto aspectRatio =
      AspectRatio::FromSize(videoSizeInPx.width, videoSizeInPx.height);
  IntrinsicSize intrinsicSize(
      nsPresContext::CSSPixelsToAppUnits(videoSizeInPx.width),
      nsPresContext::CSSPixelsToAppUnits(videoSizeInPx.height));

  nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
      area, intrinsicSize, aspectRatio, StylePosition());

  gfxRect destGFXRect = PresContext()->AppUnitsToGfxUnits(dest);
  destGFXRect.Round();
  if (destGFXRect.IsEmpty()) {
    return nullptr;
  }

  VideoInfo::Rotation rotationDeg = element->RotationDegrees();
  IntSize scaleHint(static_cast<int32_t>(destGFXRect.Width()),
                    static_cast<int32_t>(destGFXRect.Height()));
  // scaleHint is set regardless of rotation, so swap w/h if needed.
  SwapScaleWidthHeightForRotation(scaleHint, rotationDeg);
  container->SetScaleHint(scaleHint);

  RefPtr<ImageLayer> layer = static_cast<ImageLayer*>(
      aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, aItem));
  if (!layer) {
    layer = aManager->CreateImageLayer();
    if (!layer) return nullptr;
  }

  layer->SetContainer(container);
  layer->SetSamplingFilter(nsLayoutUtils::GetSamplingFilterForFrame(this));
  // Set a transform on the layer to draw the video in the right place
  gfxPoint p = destGFXRect.TopLeft() + aContainerParameters.mOffset;

  Matrix preTransform = ComputeRotationMatrix(
      destGFXRect.Width(), destGFXRect.Height(), rotationDeg);

  Matrix transform = preTransform * Matrix::Translation(p.x, p.y);

  layer->SetBaseTransform(gfx::Matrix4x4::From2D(transform));
  layer->SetScaleToSize(scaleHint, ScaleMode::STRETCH);

  uint32_t flags = element->HasAlpha() ? 0 : Layer::CONTENT_OPAQUE;
  layer->SetContentFlags(flags);

  RefPtr<Layer> result = layer.forget();
  return result.forget();
}

class DispatchResizeToControls : public Runnable {
 public:
  explicit DispatchResizeToControls(nsIContent* aContent)
      : mozilla::Runnable("DispatchResizeToControls"), mContent(aContent) {}
  NS_IMETHOD Run() override {
    nsContentUtils::DispatchTrustedEvent(
        mContent->OwnerDoc(), mContent,
        NS_LITERAL_STRING("resizevideocontrols"), CanBubble::eNo,
        Cancelable::eNo);
    return NS_OK;
  }
  nsCOMPtr<nsIContent> mContent;
};

void nsVideoFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
                          const ReflowInput& aReflowInput,
                          nsReflowStatus& aStatus) {
  MarkInReflow();
  DO_GLOBAL_REFLOW_COUNT("nsVideoFrame");
  DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
  NS_FRAME_TRACE(
      NS_FRAME_TRACE_CALLS,
      ("enter nsVideoFrame::Reflow: availSize=%d,%d",
       aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));

  MOZ_ASSERT(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow");

  const WritingMode myWM = aReflowInput.GetWritingMode();
  nscoord contentBoxBSize = aReflowInput.ComputedBSize();
  const nscoord borderBoxISize =
      aReflowInput.ComputedISize() +
      aReflowInput.ComputedLogicalBorderPadding().IStartEnd(myWM);
  const bool isBSizeShrinkWrapping = (contentBoxBSize == NS_INTRINSICSIZE);

  nscoord borderBoxBSize;
  if (!isBSizeShrinkWrapping) {
    borderBoxBSize =
        contentBoxBSize +
        aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
  }

  nsMargin borderPadding = aReflowInput.ComputedPhysicalBorderPadding();

  nsIContent* videoControlsDiv = GetVideoControls();

  // Reflow the child frames. We may have up to three: an image
  // frame (for the poster image), a container frame for the controls,
  // and a container frame for the caption.
  for (nsIFrame* child : mFrames) {
    nsSize oldChildSize = child->GetSize();
    nsReflowStatus childStatus;

    if (child->GetContent() == mPosterImage) {
      // Reflow the poster frame.
      nsImageFrame* imageFrame = static_cast<nsImageFrame*>(child);
      ReflowOutput kidDesiredSize(aReflowInput);
      WritingMode wm = imageFrame->GetWritingMode();
      LogicalSize availableSize = aReflowInput.AvailableSize(wm);
      availableSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;

      LogicalSize cbSize = aMetrics.Size(aMetrics.GetWritingMode())
                               .ConvertTo(wm, aMetrics.GetWritingMode());
      ReflowInput kidReflowInput(aPresContext, aReflowInput, imageFrame,
                                 availableSize, Some(cbSize));

      nsRect posterRenderRect;
      if (ShouldDisplayPoster()) {
        posterRenderRect =
            nsRect(nsPoint(borderPadding.left, borderPadding.top),
                   nsSize(aReflowInput.ComputedWidth(),
                          aReflowInput.ComputedHeight()));
      }
      kidReflowInput.SetComputedWidth(posterRenderRect.width);
      kidReflowInput.SetComputedHeight(posterRenderRect.height);
      ReflowChild(imageFrame, aPresContext, kidDesiredSize, kidReflowInput,
                  posterRenderRect.x, posterRenderRect.y, 0, childStatus);
      MOZ_ASSERT(childStatus.IsFullyComplete(),
                 "We gave our child unconstrained available block-size, "
                 "so it should be complete!");

      FinishReflowChild(imageFrame, aPresContext, kidDesiredSize,
                        &kidReflowInput, posterRenderRect.x, posterRenderRect.y,
                        0);

    } else if (child->GetContent() == mCaptionDiv ||
               child->GetContent() == videoControlsDiv) {
      // Reflow the caption and control bar frames.
      WritingMode wm = child->GetWritingMode();
      LogicalSize availableSize = aReflowInput.ComputedSize(wm);
      availableSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;

      ReflowInput kidReflowInput(aPresContext, aReflowInput, child,
                                 availableSize);
      ReflowOutput kidDesiredSize(kidReflowInput);
      ReflowChild(child, aPresContext, kidDesiredSize, kidReflowInput,
                  borderPadding.left, borderPadding.top, 0, childStatus);
      MOZ_ASSERT(childStatus.IsFullyComplete(),
                 "We gave our child unconstrained available block-size, "
                 "so it should be complete!");

      if (child->GetContent() == videoControlsDiv && isBSizeShrinkWrapping) {
        // Resolve our own BSize based on the controls' size in the
        // same axis. Unless we're size-contained, in which case we
        // have to behave as if we have an intrinsic size of 0.
        if (aReflowInput.mStyleDisplay->IsContainSize()) {
          contentBoxBSize = 0;
        } else {
          contentBoxBSize = myWM.IsOrthogonalTo(wm) ? kidDesiredSize.ISize(wm)
                                                    : kidDesiredSize.BSize(wm);
        }
      }

      FinishReflowChild(child, aPresContext, kidDesiredSize, &kidReflowInput,
                        borderPadding.left, borderPadding.top, 0);

      if (child->GetContent() == videoControlsDiv &&
          child->GetSize() != oldChildSize) {
        RefPtr<Runnable> event =
            new DispatchResizeToControls(child->GetContent());
        nsContentUtils::AddScriptRunner(event);
      }
    } else {
      NS_ERROR("Unexpected extra child frame in nsVideoFrame; skipping");
    }
  }

  if (isBSizeShrinkWrapping) {
    if (contentBoxBSize == NS_INTRINSICSIZE) {
      // We didn't get a BSize from our intrinsic size/ratio, nor did we
      // get one from our controls. Just use BSize of 0.
      contentBoxBSize = 0;
    }
    contentBoxBSize =
        NS_CSS_MINMAX(contentBoxBSize, aReflowInput.ComputedMinBSize(),
                      aReflowInput.ComputedMaxBSize());
    borderBoxBSize =
        contentBoxBSize +
        aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
  }

  LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize);
  aMetrics.SetSize(myWM, logicalDesiredSize);

  aMetrics.SetOverflowAreasToDesiredBounds();

  FinishAndStoreOverflow(&aMetrics);

  NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("exit nsVideoFrame::Reflow: size=%d,%d",
                                        aMetrics.Width(), aMetrics.Height()));

  MOZ_ASSERT(aStatus.IsEmpty(), "This type of frame can't be split.");
  NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics);
}

class nsDisplayVideo : public nsDisplayItem {
 public:
  nsDisplayVideo(nsDisplayListBuilder* aBuilder, nsVideoFrame* aFrame)
      : nsDisplayItem(aBuilder, aFrame) {
    MOZ_COUNT_CTOR(nsDisplayVideo);
  }
#ifdef NS_BUILD_REFCNT_LOGGING
  virtual ~nsDisplayVideo() { MOZ_COUNT_DTOR(nsDisplayVideo); }
#endif

  NS_DISPLAY_DECL_NAME("Video", TYPE_VIDEO)

  virtual bool CreateWebRenderCommands(
      mozilla::wr::DisplayListBuilder& aBuilder,
      mozilla::wr::IpcResourceUpdateQueue& aResources,
      const mozilla::layers::StackingContextHelper& aSc,
      mozilla::layers::RenderRootStateManager* aManager,
      nsDisplayListBuilder* aDisplayListBuilder) override {
    nsRect area = Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
    HTMLVideoElement* element =
        static_cast<HTMLVideoElement*>(Frame()->GetContent());

    nsIntSize videoSizeInPx;
    if (NS_FAILED(element->GetVideoSize(&videoSizeInPx)) || area.IsEmpty()) {
      return true;
    }

    RefPtr<ImageContainer> container = element->GetImageContainer();
    if (!container) {
      return true;
    }

    // Retrieve the size of the decoded video frame, before being scaled
    // by pixel aspect ratio.
    mozilla::gfx::IntSize frameSize = container->GetCurrentSize();
    if (frameSize.width == 0 || frameSize.height == 0) {
      // No image, or zero-sized image. Don't render.
      return true;
    }

    // Convert video size from pixel units into app units, to get an
    // aspect-ratio (which has to be represented as a nsSize) and an
    // IntrinsicSize that we can pass to ComputeObjectRenderRect.
    IntrinsicSize intrinsicSize(
        nsPresContext::CSSPixelsToAppUnits(videoSizeInPx.width),
        nsPresContext::CSSPixelsToAppUnits(videoSizeInPx.height));
    auto aspectRatio =
        AspectRatio::FromSize(videoSizeInPx.width, videoSizeInPx.height);

    nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
        area, intrinsicSize, aspectRatio, Frame()->StylePosition());

    gfxRect destGFXRect = Frame()->PresContext()->AppUnitsToGfxUnits(dest);
    destGFXRect.Round();
    if (destGFXRect.IsEmpty()) {
      return true;
    }

    VideoInfo::Rotation rotationDeg = element->RotationDegrees();
    IntSize scaleHint(static_cast<int32_t>(destGFXRect.Width()),
                      static_cast<int32_t>(destGFXRect.Height()));
    // scaleHint is set regardless of rotation, so swap w/h if needed.
    SwapScaleWidthHeightForRotation(scaleHint, rotationDeg);
    container->SetScaleHint(scaleHint);

    Matrix transformHint;
    if (rotationDeg != VideoInfo::Rotation::kDegree_0) {
      transformHint = ComputeRotationMatrix(destGFXRect.Width(),
                                            destGFXRect.Height(), rotationDeg);
    }
    container->SetTransformHint(transformHint);

    // If the image container is empty, we don't want to fallback. Any other
    // failure will be due to resource constraints and fallback is unlikely to
    // help us. Hence we can ignore the return value from PushImage.
    LayoutDeviceRect rect(destGFXRect.x, destGFXRect.y, destGFXRect.width,
                          destGFXRect.height);
    aManager->CommandBuilder().PushImage(this, container, aBuilder, aResources,
                                         aSc, rect, rect);
    return true;
  }

  // For opaque videos, we will want to override GetOpaqueRegion here.
  // This is tracked by bug 1545498.

  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
                           bool* aSnap) const override {
    *aSnap = true;
    nsIFrame* f = Frame();
    return f->GetContentRectRelativeToSelf() + ToReferenceFrame();
  }

  virtual already_AddRefed<Layer> BuildLayer(
      nsDisplayListBuilder* aBuilder, LayerManager* aManager,
      const ContainerLayerParameters& aContainerParameters) override {
    return static_cast<nsVideoFrame*>(mFrame)->BuildLayer(
        aBuilder, aManager, this, aContainerParameters);
  }

  virtual LayerState GetLayerState(
      nsDisplayListBuilder* aBuilder, LayerManager* aManager,
      const ContainerLayerParameters& aParameters) override {
    if (aManager->IsCompositingCheap()) {
      // Since ImageLayers don't require additional memory of the
      // video frames we have to have anyway, we can't save much by
      // making layers inactive. Also, for many accelerated layer
      // managers calling imageContainer->GetCurrentAsSurface can be
      // very expensive. So just always be active when compositing is
      // cheap (i.e. hardware accelerated).
      return LAYER_ACTIVE;
    }
    HTMLMediaElement* elem =
        static_cast<HTMLMediaElement*>(mFrame->GetContent());
    return elem->IsPotentiallyPlaying() ? LAYER_ACTIVE_FORCE : LAYER_INACTIVE;
  }
};

void nsVideoFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                    const nsDisplayListSet& aLists) {
  if (!IsVisibleForPainting()) return;

  DO_GLOBAL_REFLOW_COUNT_DSP("nsVideoFrame");

  DisplayBorderBackgroundOutline(aBuilder, aLists);

  const bool shouldDisplayPoster = ShouldDisplayPoster();

  // NOTE: If we're displaying a poster image (instead of video data), we can
  // trust the nsImageFrame to constrain its drawing to its content rect
  // (which happens to be the same as our content rect).
  uint32_t clipFlags;
  if (shouldDisplayPoster ||
      !nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition())) {
    clipFlags = DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
  } else {
    clipFlags = 0;
  }

  DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox clip(
      aBuilder, this, clipFlags);

  if (HasVideoElement() && !shouldDisplayPoster) {
    aLists.Content()->AppendNewToTop<nsDisplayVideo>(aBuilder, this);
  }

  // Add child frames to display list. We expect various children,
  // but only want to draw mPosterImage conditionally. Others we
  // always add to the display list.
  for (nsIFrame* child : mFrames) {
    if (child->GetContent() != mPosterImage || shouldDisplayPoster ||
        child->IsBoxFrame()) {
      nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
          aBuilder, child,
          aBuilder->GetVisibleRect() - child->GetOffsetTo(this),
          aBuilder->GetDirtyRect() - child->GetOffsetTo(this));

      child->BuildDisplayListForStackingContext(aBuilder, aLists.Content());
    }
  }
}

#ifdef ACCESSIBILITY
a11y::AccType nsVideoFrame::AccessibleType() { return a11y::eHTMLMediaType; }
#endif

#ifdef DEBUG_FRAME_DUMP
nsresult nsVideoFrame::GetFrameName(nsAString& aResult) const {
  return MakeFrameName(NS_LITERAL_STRING("HTMLVideo"), aResult);
}
#endif

LogicalSize nsVideoFrame::ComputeSize(
    gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
    nscoord aAvailableISize, const LogicalSize& aMargin,
    const LogicalSize& aBorder, const LogicalSize& aPadding,
    ComputeSizeFlags aFlags) {
  if (!HasVideoElement()) {
    return nsContainerFrame::ComputeSize(aRenderingContext, aWM, aCBSize,
                                         aAvailableISize, aMargin, aBorder,
                                         aPadding, aFlags);
  }

  nsSize size = GetVideoIntrinsicSize(aRenderingContext);
  IntrinsicSize intrinsicSize(size.width, size.height);

  // Only video elements have an intrinsic ratio.
  auto intrinsicRatio = HasVideoElement()
                            ? AspectRatio::FromSize(size.width, size.height)
                            : AspectRatio();

  return ComputeSizeWithIntrinsicDimensions(
      aRenderingContext, aWM, intrinsicSize, intrinsicRatio, aCBSize, aMargin,
      aBorder, aPadding, aFlags);
}

nscoord nsVideoFrame::GetMinISize(gfxContext* aRenderingContext) {
  nscoord result;
  DISPLAY_MIN_INLINE_SIZE(this, result);

  if (HasVideoElement()) {
    nsSize size = GetVideoIntrinsicSize(aRenderingContext);
    result = GetWritingMode().IsVertical() ? size.height : size.width;
  } else {
    // We expect last and only child of audio elements to be control if
    // "controls" attribute is present.
    nsIFrame* kid = mFrames.LastChild();
    if (!StyleDisplay()->IsContainSize() && kid) {
      result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, kid,
                                                    nsLayoutUtils::MIN_ISIZE);
    } else {
      result = 0;
    }
  }

  return result;
}

nscoord nsVideoFrame::GetPrefISize(gfxContext* aRenderingContext) {
  nscoord result;
  DISPLAY_PREF_INLINE_SIZE(this, result);

  if (HasVideoElement()) {
    nsSize size = GetVideoIntrinsicSize(aRenderingContext);
    result = GetWritingMode().IsVertical() ? size.height : size.width;
  } else {
    // We expect last and only child of audio elements to be control if
    // "controls" attribute is present.
    nsIFrame* kid = mFrames.LastChild();
    if (!StyleDisplay()->IsContainSize() && kid) {
      result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, kid,
                                                    nsLayoutUtils::PREF_ISIZE);
    } else {
      result = 0;
    }
  }

  return result;
}

AspectRatio nsVideoFrame::GetIntrinsicRatio() {
  if (!HasVideoElement()) {
    // Audio elements have no intrinsic ratio.
    return AspectRatio();
  }

  nsSize size = GetVideoIntrinsicSize(nullptr);
  return AspectRatio::FromSize(size.width, size.height);
}

bool nsVideoFrame::ShouldDisplayPoster() {
  if (!HasVideoElement()) return false;

  HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());
  if (element->GetPlayedOrSeeked() && HasVideoData()) return false;

  nsCOMPtr<nsIImageLoadingContent> imgContent = do_QueryInterface(mPosterImage);
  NS_ENSURE_TRUE(imgContent, false);

  nsCOMPtr<imgIRequest> request;
  nsresult res = imgContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
                                        getter_AddRefs(request));
  if (NS_FAILED(res) || !request) {
    return false;
  }

  uint32_t status = 0;
  res = request->GetImageStatus(&status);
  if (NS_FAILED(res) || (status & imgIRequest::STATUS_ERROR)) return false;

  return true;
}

nsSize nsVideoFrame::GetVideoIntrinsicSize(gfxContext* aRenderingContext) {
  // 'contain:size' replaced elements have intrinsic size 0,0.
  if (StyleDisplay()->IsContainSize()) {
    return nsSize(0, 0);
  }

  // Defaulting size to 300x150 if no size given.
  nsIntSize size(300, 150);

  HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());
  if (NS_FAILED(element->GetVideoSize(&size)) && ShouldDisplayPoster()) {
    // Use the poster image frame's size.
    nsIFrame* child = mPosterImage->GetPrimaryFrame();
    nsImageFrame* imageFrame = do_QueryFrame(child);
    nsSize imgsize;
    if (NS_SUCCEEDED(imageFrame->GetIntrinsicImageSize(imgsize))) {
      return imgsize;
    }
  }

  return nsSize(nsPresContext::CSSPixelsToAppUnits(size.width),
                nsPresContext::CSSPixelsToAppUnits(size.height));
}

void nsVideoFrame::UpdatePosterSource(bool aNotify) {
  NS_ASSERTION(HasVideoElement(), "Only call this on <video> elements.");
  HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());

  if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::poster) &&
      !element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::poster,
                            nsGkAtoms::_empty, eIgnoreCase)) {
    nsAutoString posterStr;
    element->GetPoster(posterStr);
    mPosterImage->SetAttr(kNameSpaceID_None, nsGkAtoms::src, posterStr,
                          aNotify);
  } else {
    mPosterImage->UnsetAttr(kNameSpaceID_None, nsGkAtoms::src, aNotify);
  }
}

nsresult nsVideoFrame::AttributeChanged(int32_t aNameSpaceID,
                                        nsAtom* aAttribute, int32_t aModType) {
  if (aAttribute == nsGkAtoms::poster && HasVideoElement()) {
    UpdatePosterSource(true);
  }
  return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
}

void nsVideoFrame::OnVisibilityChange(
    Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
  if (HasVideoElement()) {
    static_cast<HTMLMediaElement*>(GetContent())
        ->OnVisibilityChange(aNewVisibility);
  }

  nsCOMPtr<nsIImageLoadingContent> imageLoader =
      do_QueryInterface(mPosterImage);
  if (imageLoader) {
    imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
  }

  nsContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
}

bool nsVideoFrame::HasVideoElement() {
  return static_cast<HTMLMediaElement*>(GetContent())->IsVideo();
}

bool nsVideoFrame::HasVideoData() {
  if (!HasVideoElement()) return false;
  HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());
  nsIntSize size(0, 0);
  element->GetVideoSize(&size);
  return size != nsIntSize(0, 0);
}

void nsVideoFrame::UpdateTextTrack() {
  static_cast<HTMLMediaElement*>(GetContent())->NotifyCueDisplayStatesChanged();
}