tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsVideoFrame.cpp (27132B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /* rendering object for the HTML <video> element */
      8 
      9 #include "nsVideoFrame.h"
     10 
     11 #include "ImageContainer.h"
     12 #include "mozilla/PresShell.h"
     13 #include "mozilla/dom/HTMLImageElement.h"
     14 #include "mozilla/dom/HTMLVideoElement.h"
     15 #include "mozilla/dom/ShadowRoot.h"
     16 #include "mozilla/layers/RenderRootStateManager.h"
     17 #include "nsCOMPtr.h"
     18 #include "nsContentCreatorFunctions.h"
     19 #include "nsContentUtils.h"
     20 #include "nsDisplayList.h"
     21 #include "nsGenericHTMLElement.h"
     22 #include "nsGkAtoms.h"
     23 #include "nsIContentInlines.h"
     24 #include "nsIImageLoadingContent.h"
     25 #include "nsImageFrame.h"
     26 #include "nsLayoutUtils.h"
     27 #include "nsPresContext.h"
     28 #include "nsStyleUtil.h"
     29 
     30 using namespace mozilla;
     31 using namespace mozilla::layers;
     32 using namespace mozilla::dom;
     33 using namespace mozilla::gfx;
     34 
     35 nsIFrame* NS_NewHTMLVideoFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
     36  return new (aPresShell) nsVideoFrame(aStyle, aPresShell->GetPresContext());
     37 }
     38 
     39 nsIFrame* NS_NewHTMLAudioFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
     40  return new (aPresShell) nsAudioFrame(aStyle, aPresShell->GetPresContext());
     41 }
     42 
     43 NS_IMPL_FRAMEARENA_HELPERS(nsVideoFrame)
     44 NS_QUERYFRAME_HEAD(nsVideoFrame)
     45  NS_QUERYFRAME_ENTRY(nsVideoFrame)
     46  NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
     47 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
     48 
     49 NS_IMPL_FRAMEARENA_HELPERS(nsAudioFrame)
     50 NS_QUERYFRAME_HEAD(nsAudioFrame)
     51  NS_QUERYFRAME_ENTRY(nsAudioFrame)
     52 NS_QUERYFRAME_TAIL_INHERITING(nsVideoFrame)
     53 
     54 // A matrix to obtain a correct-rotated video frame.
     55 static Matrix ComputeRotationMatrix(gfxFloat aRotatedWidth,
     56                                    gfxFloat aRotatedHeight,
     57                                    VideoRotation aDegrees) {
     58  Matrix shiftVideoCenterToOrigin;
     59  if (aDegrees == VideoRotation::kDegree_90 ||
     60      aDegrees == VideoRotation::kDegree_270) {
     61    shiftVideoCenterToOrigin =
     62        Matrix::Translation(-aRotatedHeight / 2.0, -aRotatedWidth / 2.0);
     63  } else {
     64    shiftVideoCenterToOrigin =
     65        Matrix::Translation(-aRotatedWidth / 2.0, -aRotatedHeight / 2.0);
     66  }
     67 
     68  auto angle = static_cast<double>(aDegrees) / 180.0 * M_PI;
     69  Matrix rotation = Matrix::Rotation(static_cast<gfx::Float>(angle));
     70  Matrix shiftLeftTopToOrigin =
     71      Matrix::Translation(aRotatedWidth / 2.0, aRotatedHeight / 2.0);
     72  return shiftVideoCenterToOrigin * rotation * shiftLeftTopToOrigin;
     73 }
     74 
     75 static void SwapScaleWidthHeightForRotation(IntSize& aSize,
     76                                            VideoRotation aDegrees) {
     77  if (aDegrees == VideoRotation::kDegree_90 ||
     78      aDegrees == VideoRotation::kDegree_270) {
     79    int32_t tmpWidth = aSize.width;
     80    aSize.width = aSize.height;
     81    aSize.height = tmpWidth;
     82  }
     83 }
     84 
     85 nsVideoFrame::nsVideoFrame(ComputedStyle* aStyle, nsPresContext* aPc,
     86                           ClassID aClassID)
     87    : nsContainerFrame(aStyle, aPc, aClassID),
     88      mIsAudio(aClassID == nsAudioFrame::kClassID) {
     89  EnableVisibilityTracking();
     90 }
     91 
     92 nsVideoFrame::~nsVideoFrame() = default;
     93 
     94 nsAudioFrame::nsAudioFrame(ComputedStyle* aStyle, nsPresContext* aPc)
     95    : nsVideoFrame(aStyle, aPc, kClassID) {}
     96 
     97 nsAudioFrame::~nsAudioFrame() = default;
     98 
     99 nsresult nsVideoFrame::CreateAnonymousContent(
    100    nsTArray<ContentInfo>& aElements) {
    101  nsNodeInfoManager* nodeInfoManager =
    102      GetContent()->GetComposedDoc()->NodeInfoManager();
    103  RefPtr<NodeInfo> nodeInfo;
    104 
    105  if (HasVideoElement()) {
    106    // Create an anonymous image element as a child to hold the poster
    107    // image. We may not have a poster image now, but one could be added
    108    // before we load, or on a subsequent load.
    109    nodeInfo = nodeInfoManager->GetNodeInfo(
    110        nsGkAtoms::img, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
    111    NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
    112    mPosterImage = NS_NewHTMLImageElement(nodeInfo.forget());
    113    NS_ENSURE_TRUE(mPosterImage, NS_ERROR_OUT_OF_MEMORY);
    114    UpdatePosterSource(false);
    115 
    116    // XXX(Bug 1631371) Check if this should use a fallible operation as it
    117    // pretended earlier.
    118    aElements.AppendElement(mPosterImage);
    119 
    120    // Set up the caption overlay div for showing any TextTrack data
    121    nodeInfo = nodeInfoManager->GetNodeInfo(
    122        nsGkAtoms::div, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
    123    NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
    124    mCaptionDiv = NS_NewHTMLDivElement(nodeInfo.forget());
    125    NS_ENSURE_TRUE(mCaptionDiv, NS_ERROR_OUT_OF_MEMORY);
    126    nsGenericHTMLElement* div =
    127        static_cast<nsGenericHTMLElement*>(mCaptionDiv.get());
    128    div->SetClassName(u"caption-box"_ns);
    129 
    130    // XXX(Bug 1631371) Check if this should use a fallible operation as it
    131    // pretended earlier.
    132    aElements.AppendElement(mCaptionDiv);
    133    UpdateTextTrack();
    134  }
    135 
    136  return NS_OK;
    137 }
    138 
    139 void nsVideoFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
    140                                            uint32_t aFliter) {
    141  if (mPosterImage) {
    142    aElements.AppendElement(mPosterImage);
    143  }
    144 
    145  if (mCaptionDiv) {
    146    aElements.AppendElement(mCaptionDiv);
    147  }
    148 }
    149 
    150 nsIContent* nsVideoFrame::GetVideoControls() const {
    151  if (!mContent->GetShadowRoot()) {
    152    return nullptr;
    153  }
    154 
    155  // The video controls <div> is the only child of the UA Widget Shadow Root
    156  // if it is present. It is only lazily inserted into the DOM when
    157  // the controls attribute is set.
    158  MOZ_ASSERT(mContent->GetShadowRoot()->IsUAWidget());
    159  MOZ_ASSERT(1 >= mContent->GetShadowRoot()->GetChildCount());
    160  return mContent->GetShadowRoot()->GetFirstChild();
    161 }
    162 
    163 void nsVideoFrame::Destroy(DestroyContext& aContext) {
    164  if (mReflowCallbackPosted) {
    165    PresShell()->CancelReflowCallback(this);
    166  }
    167  aContext.AddAnonymousContent(mCaptionDiv.forget());
    168  aContext.AddAnonymousContent(mPosterImage.forget());
    169  nsContainerFrame::Destroy(aContext);
    170 }
    171 
    172 class DispatchResizeEvent : public Runnable {
    173 public:
    174  explicit DispatchResizeEvent(nsIContent* aContent,
    175                               const nsLiteralString& aName)
    176      : Runnable("DispatchResizeEvent"), mContent(aContent), mName(aName) {}
    177  NS_IMETHOD Run() override {
    178    nsContentUtils::DispatchTrustedEvent(mContent->OwnerDoc(), mContent, mName,
    179                                         CanBubble::eNo, Cancelable::eNo);
    180    return NS_OK;
    181  }
    182  nsCOMPtr<nsIContent> mContent;
    183  const nsLiteralString mName;
    184 };
    185 
    186 bool nsVideoFrame::ReflowFinished() {
    187  mReflowCallbackPosted = false;
    188  auto GetSize = [&](nsIContent* aContent) -> Maybe<nsSize> {
    189    if (!aContent) {
    190      return Nothing();
    191    }
    192    nsIFrame* f = aContent->GetPrimaryFrame();
    193    if (!f) {
    194      return Nothing();
    195    }
    196    return Some(f->GetSize());
    197  };
    198 
    199  AutoTArray<nsCOMPtr<nsIRunnable>, 2> events;
    200 
    201  if (auto size = GetSize(mCaptionDiv)) {
    202    if (*size != mCaptionTrackedSize) {
    203      mCaptionTrackedSize = *size;
    204      events.AppendElement(
    205          new DispatchResizeEvent(mCaptionDiv, u"resizecaption"_ns));
    206    }
    207  }
    208  nsIContent* controls = GetVideoControls();
    209  if (auto size = GetSize(controls)) {
    210    if (*size != mControlsTrackedSize) {
    211      mControlsTrackedSize = *size;
    212      events.AppendElement(
    213          new DispatchResizeEvent(controls, u"resizevideocontrols"_ns));
    214    }
    215  }
    216  for (auto& event : events) {
    217    nsContentUtils::AddScriptRunner(event.forget());
    218  }
    219  return false;
    220 }
    221 
    222 nsRect nsVideoFrame::GetDestRect(const nsRect& aContentBox) const {
    223  return nsLayoutUtils::ComputeObjectDestRect(
    224      aContentBox, GetIntrinsicSize(/* aIgnoreContainment = */ true),
    225      GetIntrinsicRatio(/* aIgnoreContainment = */ true), StylePosition());
    226 }
    227 
    228 void nsVideoFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
    229                          const ReflowInput& aReflowInput,
    230                          nsReflowStatus& aStatus) {
    231  MarkInReflow();
    232  DO_GLOBAL_REFLOW_COUNT("nsVideoFrame");
    233  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
    234  NS_FRAME_TRACE(
    235      NS_FRAME_TRACE_CALLS,
    236      ("enter nsVideoFrame::Reflow: availSize=%d,%d",
    237       aReflowInput.AvailableISize(), aReflowInput.AvailableBSize()));
    238 
    239  MOZ_ASSERT(HasAnyStateBits(NS_FRAME_IN_REFLOW), "frame is not in reflow");
    240 
    241  const WritingMode myWM = aReflowInput.GetWritingMode();
    242  nscoord contentBoxBSize = aReflowInput.ComputedBSize();
    243  const auto logicalBP = aReflowInput.ComputedLogicalBorderPadding(myWM);
    244  const nscoord borderBoxISize =
    245      aReflowInput.ComputedISize() + logicalBP.IStartEnd(myWM);
    246  const bool isBSizeShrinkWrapping = (contentBoxBSize == NS_UNCONSTRAINEDSIZE);
    247 
    248  nscoord borderBoxBSize;
    249  if (!isBSizeShrinkWrapping) {
    250    borderBoxBSize = contentBoxBSize + logicalBP.BStartEnd(myWM);
    251  }
    252 
    253  nsIContent* videoControlsDiv = GetVideoControls();
    254  const nsSize containerSize =
    255      aReflowInput.ComputedSizeAsContainerIfConstrained();
    256 
    257  // Reflow the child frames. We may have up to three: an image
    258  // frame (for the poster image), a container frame for the controls,
    259  // and a container frame for the caption.
    260  for (nsIFrame* child : mFrames) {
    261    nsSize oldChildSize = child->GetSize();
    262    nsReflowStatus childStatus;
    263    const WritingMode childWM = child->GetWritingMode();
    264    LogicalSize availableSize = aReflowInput.ComputedSize(childWM);
    265    availableSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE;
    266    ReflowInput kidReflowInput(aPresContext, aReflowInput, child,
    267                               availableSize);
    268    ReflowOutput kidDesiredSize(myWM);
    269 
    270    if (child->GetContent() == mPosterImage) {
    271      // Reflow the poster frame.
    272      const LogicalPoint childOrigin = logicalBP.StartOffset(myWM);
    273      const LogicalSize posterRenderSize = aReflowInput.ComputedSize(childWM);
    274      kidReflowInput.SetComputedISize(posterRenderSize.ISize(childWM));
    275      kidReflowInput.SetComputedBSize(posterRenderSize.BSize(childWM));
    276 
    277      ReflowChild(child, aPresContext, kidDesiredSize, kidReflowInput, myWM,
    278                  childOrigin, containerSize, ReflowChildFlags::Default,
    279                  childStatus);
    280      MOZ_ASSERT(childStatus.IsFullyComplete(),
    281                 "We gave our child unconstrained available block-size, "
    282                 "so it should be complete!");
    283 
    284      FinishReflowChild(child, aPresContext, kidDesiredSize, &kidReflowInput,
    285                        myWM, childOrigin, containerSize,
    286                        ReflowChildFlags::Default);
    287 
    288    } else if (child->GetContent() == mCaptionDiv ||
    289               child->GetContent() == videoControlsDiv) {
    290      // Reflow the caption and control bar frames.
    291      const LogicalPoint childOrigin = logicalBP.StartOffset(myWM);
    292      ReflowChild(child, aPresContext, kidDesiredSize, kidReflowInput, myWM,
    293                  childOrigin, containerSize, ReflowChildFlags::Default,
    294                  childStatus);
    295      MOZ_ASSERT(childStatus.IsFullyComplete(),
    296                 "We gave our child unconstrained available block-size, "
    297                 "so it should be complete!");
    298 
    299      if (child->GetContent() == videoControlsDiv && isBSizeShrinkWrapping) {
    300        // Resolve our own BSize based on the controls' size in the
    301        // same axis. Unless we're size-contained, in which case we
    302        // have to behave as if we have an intrinsic size of 0.
    303        if (GetContainSizeAxes().mBContained) {
    304          contentBoxBSize = 0;
    305        } else {
    306          contentBoxBSize = kidDesiredSize.BSize(myWM);
    307        }
    308      }
    309 
    310      FinishReflowChild(child, aPresContext, kidDesiredSize, &kidReflowInput,
    311                        myWM, childOrigin, containerSize,
    312                        ReflowChildFlags::Default);
    313 
    314      if (child->GetSize() != oldChildSize) {
    315        // We might find non-primary frames in printing due to
    316        // ReplicateFixedFrames, but we don't care about that.
    317        MOZ_ASSERT(child->IsPrimaryFrame() ||
    318                       PresContext()->IsPrintingOrPrintPreview(),
    319                   "We only look at the primary frame in ReflowFinished");
    320        if (!mReflowCallbackPosted) {
    321          mReflowCallbackPosted = true;
    322          PresShell()->PostReflowCallback(this);
    323        }
    324      }
    325    } else {
    326      // We expect a placeholder for ::backdrop if we're fullscreen.
    327      MOZ_ASSERT(child->IsPlaceholderFrame());
    328    }
    329  }
    330 
    331  if (isBSizeShrinkWrapping) {
    332    if (contentBoxBSize == NS_UNCONSTRAINEDSIZE) {
    333      // We didn't get a BSize from our intrinsic size/ratio, nor did we
    334      // get one from our controls. Just use BSize of 0.
    335      contentBoxBSize = 0;
    336    }
    337    contentBoxBSize = aReflowInput.ApplyMinMaxBSize(contentBoxBSize);
    338    borderBoxBSize = contentBoxBSize + logicalBP.BStartEnd(myWM);
    339  }
    340 
    341  LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize);
    342  aMetrics.SetSize(myWM, logicalDesiredSize);
    343 
    344  aMetrics.SetOverflowAreasToDesiredBounds();
    345  if (HasVideoElement()) {
    346    aMetrics.mOverflowAreas.UnionAllWith(
    347        GetDestRect(aReflowInput.ComputedPhysicalContentBoxRelativeToSelf()));
    348  }
    349 
    350  FinishAndStoreOverflow(&aMetrics);
    351 
    352  NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("exit nsVideoFrame::Reflow: size=%d,%d",
    353                                        aMetrics.Width(), aMetrics.Height()));
    354 
    355  MOZ_ASSERT(aStatus.IsEmpty(), "This type of frame can't be split.");
    356 }
    357 
    358 #ifdef ACCESSIBILITY
    359 a11y::AccType nsVideoFrame::AccessibleType() { return a11y::eHTMLMediaType; }
    360 #endif
    361 
    362 #ifdef DEBUG_FRAME_DUMP
    363 nsresult nsVideoFrame::GetFrameName(nsAString& aResult) const {
    364  return MakeFrameName(u"HTMLVideo"_ns, aResult);
    365 }
    366 #endif
    367 
    368 nsIFrame::SizeComputationResult nsVideoFrame::ComputeSize(
    369    const SizeComputationInput& aSizingInput, WritingMode aWM,
    370    const LogicalSize& aCBSize, nscoord aAvailableISize,
    371    const LogicalSize& aMargin, const LogicalSize& aBorderPadding,
    372    const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) {
    373  if (!HasVideoElement()) {
    374    return nsContainerFrame::ComputeSize(
    375        aSizingInput, aWM, aCBSize, aAvailableISize, aMargin, aBorderPadding,
    376        aSizeOverrides, aFlags);
    377  }
    378 
    379  return {ComputeSizeWithIntrinsicDimensions(
    380              aSizingInput.mRenderingContext, aWM, GetIntrinsicSize(),
    381              GetAspectRatio(), aCBSize, aMargin, aBorderPadding,
    382              aSizeOverrides, aFlags),
    383          AspectRatioUsage::None};
    384 }
    385 
    386 nscoord nsVideoFrame::IntrinsicISize(const IntrinsicSizeInput& aInput,
    387                                     IntrinsicISizeType aType) {
    388  // <audio> / <video> has the same min / pref ISize.
    389  return GetIntrinsicSize().ISize(GetWritingMode()).valueOr(0);
    390 }
    391 
    392 Maybe<nsSize> nsVideoFrame::PosterImageSize() const {
    393  // Use the poster image frame's size.
    394  nsIFrame* child = GetPosterImage()->GetPrimaryFrame();
    395  return child->GetIntrinsicSize().ToSize();
    396 }
    397 
    398 AspectRatio nsVideoFrame::GetIntrinsicRatio() const {
    399  return GetIntrinsicRatio(/* aIgnoreContainment = */ false);
    400 }
    401 
    402 AspectRatio nsVideoFrame::GetIntrinsicRatio(bool aIgnoreContainment) const {
    403  if (!HasVideoElement()) {
    404    // Audio elements have no intrinsic ratio.
    405    return AspectRatio();
    406  }
    407 
    408  // 'contain:[inline-]size' replaced elements have no intrinsic ratio.
    409  if (!aIgnoreContainment && GetContainSizeAxes().IsAny()) {
    410    return AspectRatio();
    411  }
    412 
    413  auto* element = static_cast<HTMLVideoElement*>(GetContent());
    414  if (Maybe<CSSIntSize> size = element->GetVideoSize()) {
    415    return AspectRatio::FromSize(*size);
    416  }
    417 
    418  if (ShouldDisplayPoster()) {
    419    if (Maybe<nsSize> imgSize = PosterImageSize()) {
    420      return AspectRatio::FromSize(*imgSize);
    421    }
    422  }
    423 
    424  if (StylePosition()->mAspectRatio.HasRatio()) {
    425    return AspectRatio();
    426  }
    427 
    428  return AspectRatio::FromSize(kFallbackIntrinsicSizeInPixels);
    429 }
    430 
    431 bool nsVideoFrame::ShouldDisplayPoster() const {
    432  if (!HasVideoElement()) {
    433    return false;
    434  }
    435 
    436  auto* element = static_cast<HTMLVideoElement*>(GetContent());
    437  if (element->GetPlayedOrSeeked() && HasVideoData()) {
    438    return false;
    439  }
    440 
    441  nsCOMPtr<nsIImageLoadingContent> imgContent = do_QueryInterface(mPosterImage);
    442  NS_ENSURE_TRUE(imgContent, false);
    443 
    444  nsCOMPtr<imgIRequest> request;
    445  nsresult res = imgContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
    446                                        getter_AddRefs(request));
    447  if (NS_FAILED(res) || !request) {
    448    return false;
    449  }
    450 
    451  uint32_t status = 0;
    452  res = request->GetImageStatus(&status);
    453  if (NS_FAILED(res) || (status & imgIRequest::STATUS_ERROR)) {
    454    return false;
    455  }
    456 
    457  return true;
    458 }
    459 
    460 IntrinsicSize nsVideoFrame::GetIntrinsicSize(bool aIgnoreContainment) const {
    461  const auto containAxes =
    462      aIgnoreContainment ? ContainSizeAxes(false, false) : GetContainSizeAxes();
    463  const auto isVideo = HasVideoElement();
    464  // Intrinsic size will be given by contain-intrinsic-size if the element is
    465  // size-contained. If both axes have containment, FinishIntrinsicSize() will
    466  // ignore the fallback size argument, so we can just pass no intrinsic size,
    467  // or whatever.
    468  if (containAxes.IsBoth()) {
    469    return FinishIntrinsicSize(containAxes, {});
    470  }
    471 
    472  if (!isVideo) {
    473    // An audio element with no "controls" attribute, distinguished by the last
    474    // and only child being the control, falls back to no intrinsic size.
    475    if (!mFrames.LastChild()) {
    476      return FinishIntrinsicSize(containAxes, {});
    477    }
    478 
    479    return FinishIntrinsicSize(containAxes,
    480                               IntrinsicSize(kFallbackIntrinsicSize));
    481  }
    482 
    483  auto* element = static_cast<HTMLVideoElement*>(GetContent());
    484  if (Maybe<CSSIntSize> size = element->GetVideoSize()) {
    485    return FinishIntrinsicSize(containAxes,
    486                               IntrinsicSize(CSSPixel::ToAppUnits(*size)));
    487  }
    488 
    489  if (ShouldDisplayPoster()) {
    490    if (Maybe<nsSize> imgSize = PosterImageSize()) {
    491      return FinishIntrinsicSize(containAxes, IntrinsicSize(*imgSize));
    492    }
    493  }
    494 
    495  if (StylePosition()->mAspectRatio.HasRatio()) {
    496    return {};
    497  }
    498 
    499  return FinishIntrinsicSize(containAxes,
    500                             IntrinsicSize(kFallbackIntrinsicSize));
    501 }
    502 
    503 IntrinsicSize nsVideoFrame::GetIntrinsicSize() {
    504  return GetIntrinsicSize(/* aIgnoreContainment = */ false);
    505 }
    506 
    507 void nsVideoFrame::UpdatePosterSource(bool aNotify) {
    508  NS_ASSERTION(HasVideoElement(), "Only call this on <video> elements.");
    509  HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());
    510 
    511  if (element->HasAttr(nsGkAtoms::poster) &&
    512      !element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::poster,
    513                            nsGkAtoms::_empty, eIgnoreCase)) {
    514    nsAutoString posterStr;
    515    element->GetPoster(posterStr);
    516    mPosterImage->SetAttr(kNameSpaceID_None, nsGkAtoms::src, posterStr,
    517                          aNotify);
    518  } else {
    519    mPosterImage->UnsetAttr(kNameSpaceID_None, nsGkAtoms::src, aNotify);
    520  }
    521 }
    522 
    523 nsresult nsVideoFrame::AttributeChanged(int32_t aNameSpaceID,
    524                                        nsAtom* aAttribute,
    525                                        AttrModType aModType) {
    526  if (aAttribute == nsGkAtoms::poster && HasVideoElement()) {
    527    UpdatePosterSource(true);
    528  }
    529  return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
    530 }
    531 
    532 void nsVideoFrame::OnVisibilityChange(
    533    Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
    534  if (HasVideoElement()) {
    535    static_cast<HTMLMediaElement*>(GetContent())
    536        ->OnVisibilityChange(aNewVisibility);
    537  }
    538 
    539  nsCOMPtr<nsIImageLoadingContent> imageLoader =
    540      do_QueryInterface(mPosterImage);
    541  if (imageLoader) {
    542    imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
    543  }
    544 
    545  nsContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
    546 }
    547 
    548 bool nsVideoFrame::HasVideoData() const {
    549  if (!HasVideoElement()) {
    550    return false;
    551  }
    552  auto* element = static_cast<HTMLVideoElement*>(GetContent());
    553  return element->GetVideoSize().isSome();
    554 }
    555 
    556 void nsVideoFrame::UpdateTextTrack() {
    557  static_cast<HTMLMediaElement*>(GetContent())->NotifyCueDisplayStatesChanged();
    558 }
    559 
    560 namespace mozilla {
    561 
    562 class nsDisplayVideo final : public nsPaintedDisplayItem {
    563 public:
    564  nsDisplayVideo(nsDisplayListBuilder* aBuilder, nsVideoFrame* aFrame)
    565      : nsPaintedDisplayItem(aBuilder, aFrame) {
    566    MOZ_COUNT_CTOR(nsDisplayVideo);
    567  }
    568 
    569  MOZ_COUNTED_DTOR_FINAL(nsDisplayVideo)
    570 
    571  NS_DISPLAY_DECL_NAME("Video", TYPE_VIDEO)
    572 
    573  already_AddRefed<ImageContainer> GetImageContainer(gfxRect& aDestGFXRect) {
    574    auto* f = static_cast<nsVideoFrame*>(Frame());
    575    nsRect area = f->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
    576    auto* element = static_cast<HTMLVideoElement*>(f->GetContent());
    577 
    578    Maybe<CSSIntSize> videoSizeInPx = element->GetVideoSize();
    579    if (videoSizeInPx.isNothing() || area.IsEmpty()) {
    580      return nullptr;
    581    }
    582 
    583    RefPtr<ImageContainer> container = element->GetImageContainer();
    584    if (!container) {
    585      return nullptr;
    586    }
    587 
    588    // Retrieve the size of the decoded video frame, before being scaled
    589    // by pixel aspect ratio.
    590    mozilla::gfx::IntSize frameSize = container->GetCurrentSize();
    591    if (frameSize.width == 0 || frameSize.height == 0) {
    592      // No image, or zero-sized image. Don't render.
    593      return nullptr;
    594    }
    595 
    596    nsRect dest =
    597        f->GetDestRect(f->GetContentRectRelativeToSelf() + ToReferenceFrame());
    598    aDestGFXRect = f->PresContext()->AppUnitsToGfxUnits(dest);
    599    aDestGFXRect.Round();
    600    if (aDestGFXRect.IsEmpty()) {
    601      return nullptr;
    602    }
    603 
    604    return container.forget();
    605  }
    606 
    607  bool CreateWebRenderCommands(
    608      mozilla::wr::DisplayListBuilder& aBuilder,
    609      mozilla::wr::IpcResourceUpdateQueue& aResources,
    610      const mozilla::layers::StackingContextHelper& aSc,
    611      mozilla::layers::RenderRootStateManager* aManager,
    612      nsDisplayListBuilder* aDisplayListBuilder) override {
    613    HTMLVideoElement* element =
    614        static_cast<HTMLVideoElement*>(Frame()->GetContent());
    615    gfxRect destGFXRect;
    616    RefPtr<ImageContainer> container = GetImageContainer(destGFXRect);
    617    if (!container) {
    618      return true;
    619    }
    620 
    621    container->SetRotation(element->RotationDegrees());
    622 
    623    // If the image container is empty, we don't want to fallback. Any other
    624    // failure will be due to resource constraints and fallback is unlikely to
    625    // help us. Hence we can ignore the return value from PushImage.
    626    LayoutDeviceRect rect(destGFXRect.x, destGFXRect.y, destGFXRect.width,
    627                          destGFXRect.height);
    628    aManager->CommandBuilder().PushImage(this, container, aBuilder, aResources,
    629                                         aSc, rect, rect);
    630    return true;
    631  }
    632 
    633  // For opaque videos, we will want to override GetOpaqueRegion here.
    634  // This is tracked by bug 1545498.
    635 
    636  nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override {
    637    *aSnap = true;
    638    nsIFrame* f = Frame();
    639    return f->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
    640  }
    641 
    642  // Only report FirstContentfulPaint when the video is set
    643  bool IsContentful() const override {
    644    nsVideoFrame* f = static_cast<nsVideoFrame*>(Frame());
    645    HTMLVideoElement* video = HTMLVideoElement::FromNode(f->GetContent());
    646    return video->VideoWidth() > 0;
    647  }
    648 
    649  void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override {
    650    HTMLVideoElement* element =
    651        static_cast<HTMLVideoElement*>(Frame()->GetContent());
    652    gfxRect destGFXRect;
    653    RefPtr<ImageContainer> container = GetImageContainer(destGFXRect);
    654    if (!container) {
    655      return;
    656    }
    657 
    658    VideoRotation rotationDeg = element->RotationDegrees();
    659    Matrix preTransform = ComputeRotationMatrix(
    660        destGFXRect.Width(), destGFXRect.Height(), rotationDeg);
    661    Matrix transform =
    662        preTransform * Matrix::Translation(destGFXRect.x, destGFXRect.y);
    663 
    664    AutoLockImage autoLock(container);
    665    Image* image = autoLock.GetImage(TimeStamp::Now());
    666    if (!image) {
    667      return;
    668    }
    669    RefPtr<gfx::SourceSurface> surface = image->GetAsSourceSurface();
    670    if (!surface || !surface->IsValid()) {
    671      return;
    672    }
    673    gfx::IntSize size = surface->GetSize();
    674 
    675    IntSize scaleToSize(static_cast<int32_t>(destGFXRect.Width()),
    676                        static_cast<int32_t>(destGFXRect.Height()));
    677    // scaleHint is set regardless of rotation, so swap w/h if needed.
    678    SwapScaleWidthHeightForRotation(scaleToSize, rotationDeg);
    679    transform.PreScale(scaleToSize.width / double(size.Width()),
    680                       scaleToSize.height / double(size.Height()));
    681 
    682    gfxContextMatrixAutoSaveRestore saveMatrix(aCtx);
    683    aCtx->SetMatrix(
    684        gfxUtils::SnapTransformTranslation(aCtx->CurrentMatrix(), nullptr));
    685 
    686    transform = gfxUtils::SnapTransform(
    687        transform, gfxRect(0, 0, size.width, size.height), nullptr);
    688    aCtx->Multiply(ThebesMatrix(transform));
    689 
    690    aCtx->GetDrawTarget()->FillRect(
    691        Rect(0, 0, size.width, size.height),
    692        SurfacePattern(surface, ExtendMode::CLAMP, Matrix(),
    693                       nsLayoutUtils::GetSamplingFilterForFrame(Frame())),
    694        DrawOptions());
    695  }
    696 };
    697 
    698 }  // namespace mozilla
    699 
    700 void nsVideoFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
    701                                    const nsDisplayListSet& aLists) {
    702  if (!IsVisibleForPainting()) {
    703    return;
    704  }
    705 
    706  DO_GLOBAL_REFLOW_COUNT_DSP("nsVideoFrame");
    707 
    708  DisplayBorderBackgroundOutline(aBuilder, aLists);
    709 
    710  if (HidesContent()) {
    711    return;
    712  }
    713 
    714  const bool shouldDisplayPoster = ShouldDisplayPoster();
    715 
    716  DisplayListClipState::AutoSaveRestore clipState(aBuilder);
    717  auto clipAxes = ShouldApplyOverflowClipping(StyleDisplay());
    718  if (!clipAxes.isEmpty()) {
    719    nsRect clipRect;
    720    nsRectCornerRadii radii;
    721    const bool haveRadii =
    722        ComputeOverflowClipRectRelativeToSelf(clipAxes, clipRect, radii);
    723    // NOTE: If we're displaying a poster image (instead of video data), we can
    724    // trust the nsImageFrame to constrain its drawing to its content rect
    725    // (which happens to be the same as our content rect).
    726    const bool canOverflowWithoutRadii =
    727        !shouldDisplayPoster &&
    728        nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition());
    729    if (haveRadii || canOverflowWithoutRadii) {
    730      clipState.ClipContainingBlockDescendants(
    731          clipRect + aBuilder->ToReferenceFrame(this),
    732          haveRadii ? &radii : nullptr);
    733    }
    734  }
    735 
    736  if (HasVideoElement() && !shouldDisplayPoster) {
    737    aLists.Content()->AppendNewToTop<nsDisplayVideo>(aBuilder, this);
    738  }
    739 
    740  // Add child frames to display list. We expect various children,
    741  // but only want to draw mPosterImage conditionally. Others we
    742  // always add to the display list.
    743  for (nsIFrame* child : mFrames) {
    744    if (child->GetContent() != mPosterImage || shouldDisplayPoster) {
    745      nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
    746          aBuilder, child,
    747          aBuilder->GetVisibleRect() - child->GetOffsetTo(this),
    748          aBuilder->GetDirtyRect() - child->GetOffsetTo(this));
    749 
    750      child->BuildDisplayListForStackingContext(aBuilder, aLists.Content());
    751    }
    752  }
    753 }