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 "nsCOMPtr.h"
12 #include "nsGkAtoms.h"
13 
14 #include "mozilla/PresShell.h"
15 #include "mozilla/dom/HTMLImageElement.h"
16 #include "mozilla/dom/HTMLVideoElement.h"
17 #include "mozilla/dom/ShadowRoot.h"
18 #include "mozilla/layers/RenderRootStateManager.h"
19 #include "nsDisplayList.h"
20 #include "nsGenericHTMLElement.h"
21 #include "nsPresContext.h"
22 #include "nsContentCreatorFunctions.h"
23 #include "nsBoxLayoutState.h"
24 #include "nsBoxFrame.h"
25 #include "nsIContentInlines.h"
26 #include "nsImageFrame.h"
27 #include "nsIImageLoadingContent.h"
28 #include "nsContentUtils.h"
29 #include "nsLayoutUtils.h"
30 #include "ImageContainer.h"
31 #include "nsStyleUtil.h"
32 #include <algorithm>
33 
34 using namespace mozilla;
35 using namespace mozilla::layers;
36 using namespace mozilla::dom;
37 using namespace mozilla::gfx;
38 
NS_NewHTMLVideoFrame(PresShell * aPresShell,ComputedStyle * aStyle)39 nsIFrame* NS_NewHTMLVideoFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
40   return new (aPresShell) nsVideoFrame(aStyle, aPresShell->GetPresContext());
41 }
42 
NS_IMPL_FRAMEARENA_HELPERS(nsVideoFrame)43 NS_IMPL_FRAMEARENA_HELPERS(nsVideoFrame)
44 
45 // A matrix to obtain a correct-rotated video frame.
46 static Matrix ComputeRotationMatrix(gfxFloat aRotatedWidth,
47                                     gfxFloat aRotatedHeight,
48                                     VideoInfo::Rotation aDegrees) {
49   Matrix shiftVideoCenterToOrigin;
50   if (aDegrees == VideoInfo::Rotation::kDegree_90 ||
51       aDegrees == VideoInfo::Rotation::kDegree_270) {
52     shiftVideoCenterToOrigin =
53         Matrix::Translation(-aRotatedHeight / 2.0, -aRotatedWidth / 2.0);
54   } else {
55     shiftVideoCenterToOrigin =
56         Matrix::Translation(-aRotatedWidth / 2.0, -aRotatedHeight / 2.0);
57   }
58 
59   Matrix rotation = Matrix::Rotation(gfx::Float(aDegrees / 180.0 * M_PI));
60   Matrix shiftLeftTopToOrigin =
61       Matrix::Translation(aRotatedWidth / 2.0, aRotatedHeight / 2.0);
62   return shiftVideoCenterToOrigin * rotation * shiftLeftTopToOrigin;
63 }
64 
SwapScaleWidthHeightForRotation(IntSize & aSize,VideoInfo::Rotation aDegrees)65 static void SwapScaleWidthHeightForRotation(IntSize& aSize,
66                                             VideoInfo::Rotation aDegrees) {
67   if (aDegrees == VideoInfo::Rotation::kDegree_90 ||
68       aDegrees == VideoInfo::Rotation::kDegree_270) {
69     int32_t tmpWidth = aSize.width;
70     aSize.width = aSize.height;
71     aSize.height = tmpWidth;
72   }
73 }
74 
nsVideoFrame(ComputedStyle * aStyle,nsPresContext * aPresContext)75 nsVideoFrame::nsVideoFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
76     : nsContainerFrame(aStyle, aPresContext, kClassID) {
77   EnableVisibilityTracking();
78 }
79 
80 nsVideoFrame::~nsVideoFrame() = default;
81 
82 NS_QUERYFRAME_HEAD(nsVideoFrame)
NS_QUERYFRAME_ENTRY(nsVideoFrame)83   NS_QUERYFRAME_ENTRY(nsVideoFrame)
84   NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
85 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
86 
87 nsresult nsVideoFrame::CreateAnonymousContent(
88     nsTArray<ContentInfo>& aElements) {
89   nsNodeInfoManager* nodeInfoManager =
90       GetContent()->GetComposedDoc()->NodeInfoManager();
91   RefPtr<NodeInfo> nodeInfo;
92 
93   if (HasVideoElement()) {
94     // Create an anonymous image element as a child to hold the poster
95     // image. We may not have a poster image now, but one could be added
96     // before we load, or on a subsequent load.
97     nodeInfo = nodeInfoManager->GetNodeInfo(
98         nsGkAtoms::img, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
99     NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
100     mPosterImage = NS_NewHTMLImageElement(nodeInfo.forget());
101     NS_ENSURE_TRUE(mPosterImage, NS_ERROR_OUT_OF_MEMORY);
102 
103     // Set the nsImageLoadingContent::ImageState() to 0. This means that the
104     // image will always report its state as 0, so it will never be reframed
105     // to show frames for loading or the broken image icon. This is important,
106     // as the image is native anonymous, and so can't be reframed (currently).
107     HTMLImageElement* imgContent = HTMLImageElement::FromNode(mPosterImage);
108     NS_ENSURE_TRUE(imgContent, NS_ERROR_FAILURE);
109 
110     imgContent->ForceImageState(true, 0);
111     // And now have it update its internal state
112     mPosterImage->UpdateState(false);
113 
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 
AppendAnonymousContentTo(nsTArray<nsIContent * > & aElements,uint32_t aFliter)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 
GetVideoControls() const150 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 
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)163 void nsVideoFrame::DestroyFrom(nsIFrame* aDestructRoot,
164                                PostDestroyData& aPostDestroyData) {
165   aPostDestroyData.AddAnonymousContent(mCaptionDiv.forget());
166   aPostDestroyData.AddAnonymousContent(mPosterImage.forget());
167   nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
168 }
169 
170 class DispatchResizeEvent : public Runnable {
171  public:
DispatchResizeEvent(nsIContent * aContent,const nsString & aName)172   explicit DispatchResizeEvent(nsIContent* aContent, const nsString& aName)
173       : mozilla::Runnable("DispatchResizeEvent"),
174         mContent(aContent),
175         mName(aName) {}
Run()176   NS_IMETHOD Run() override {
177     nsContentUtils::DispatchTrustedEvent(mContent->OwnerDoc(), mContent, mName,
178                                          CanBubble::eNo, Cancelable::eNo);
179     return NS_OK;
180   }
181   nsCOMPtr<nsIContent> mContent;
182   nsString mName;
183 };
184 
Reflow(nsPresContext * aPresContext,ReflowOutput & aMetrics,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)185 void nsVideoFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
186                           const ReflowInput& aReflowInput,
187                           nsReflowStatus& aStatus) {
188   MarkInReflow();
189   DO_GLOBAL_REFLOW_COUNT("nsVideoFrame");
190   DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
191   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
192   NS_FRAME_TRACE(
193       NS_FRAME_TRACE_CALLS,
194       ("enter nsVideoFrame::Reflow: availSize=%d,%d",
195        aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
196 
197   MOZ_ASSERT(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow");
198 
199   const WritingMode myWM = aReflowInput.GetWritingMode();
200   nscoord contentBoxBSize = aReflowInput.ComputedBSize();
201   const auto logicalBP = aReflowInput.ComputedLogicalBorderPadding(myWM);
202   const nscoord borderBoxISize =
203       aReflowInput.ComputedISize() + logicalBP.IStartEnd(myWM);
204   const bool isBSizeShrinkWrapping = (contentBoxBSize == NS_UNCONSTRAINEDSIZE);
205 
206   nscoord borderBoxBSize;
207   if (!isBSizeShrinkWrapping) {
208     borderBoxBSize = contentBoxBSize + logicalBP.BStartEnd(myWM);
209   }
210 
211   nsMargin borderPadding = aReflowInput.ComputedPhysicalBorderPadding();
212 
213   nsIContent* videoControlsDiv = GetVideoControls();
214 
215   // Reflow the child frames. We may have up to three: an image
216   // frame (for the poster image), a container frame for the controls,
217   // and a container frame for the caption.
218   for (nsIFrame* child : mFrames) {
219     nsSize oldChildSize = child->GetSize();
220     nsReflowStatus childStatus;
221 
222     if (child->GetContent() == mPosterImage) {
223       // Reflow the poster frame.
224       nsImageFrame* imageFrame = static_cast<nsImageFrame*>(child);
225       ReflowOutput kidDesiredSize(aReflowInput);
226       WritingMode wm = imageFrame->GetWritingMode();
227       LogicalSize availableSize = aReflowInput.AvailableSize(wm);
228       availableSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
229 
230       LogicalSize cbSize = aMetrics.Size(aMetrics.GetWritingMode())
231                                .ConvertTo(wm, aMetrics.GetWritingMode());
232       ReflowInput kidReflowInput(aPresContext, aReflowInput, imageFrame,
233                                  availableSize, Some(cbSize));
234 
235       nsRect posterRenderRect;
236       if (ShouldDisplayPoster()) {
237         posterRenderRect =
238             nsRect(nsPoint(borderPadding.left, borderPadding.top),
239                    nsSize(aReflowInput.ComputedWidth(),
240                           aReflowInput.ComputedHeight()));
241       }
242       kidReflowInput.SetComputedWidth(posterRenderRect.width);
243       kidReflowInput.SetComputedHeight(posterRenderRect.height);
244       ReflowChild(imageFrame, aPresContext, kidDesiredSize, kidReflowInput,
245                   posterRenderRect.x, posterRenderRect.y,
246                   ReflowChildFlags::Default, childStatus);
247       MOZ_ASSERT(childStatus.IsFullyComplete(),
248                  "We gave our child unconstrained available block-size, "
249                  "so it should be complete!");
250 
251       FinishReflowChild(imageFrame, aPresContext, kidDesiredSize,
252                         &kidReflowInput, posterRenderRect.x, posterRenderRect.y,
253                         ReflowChildFlags::Default);
254 
255     } else if (child->GetContent() == mCaptionDiv ||
256                child->GetContent() == videoControlsDiv) {
257       // Reflow the caption and control bar frames.
258       WritingMode wm = child->GetWritingMode();
259       LogicalSize availableSize = aReflowInput.ComputedSize(wm);
260       availableSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
261 
262       ReflowInput kidReflowInput(aPresContext, aReflowInput, child,
263                                  availableSize);
264       ReflowOutput kidDesiredSize(kidReflowInput);
265       ReflowChild(child, aPresContext, kidDesiredSize, kidReflowInput,
266                   borderPadding.left, borderPadding.top,
267                   ReflowChildFlags::Default, childStatus);
268       MOZ_ASSERT(childStatus.IsFullyComplete(),
269                  "We gave our child unconstrained available block-size, "
270                  "so it should be complete!");
271 
272       if (child->GetContent() == videoControlsDiv && isBSizeShrinkWrapping) {
273         // Resolve our own BSize based on the controls' size in the
274         // same axis. Unless we're size-contained, in which case we
275         // have to behave as if we have an intrinsic size of 0.
276         if (aReflowInput.mStyleDisplay->IsContainSize()) {
277           contentBoxBSize = 0;
278         } else {
279           contentBoxBSize = myWM.IsOrthogonalTo(wm) ? kidDesiredSize.ISize(wm)
280                                                     : kidDesiredSize.BSize(wm);
281         }
282       }
283 
284       FinishReflowChild(child, aPresContext, kidDesiredSize, &kidReflowInput,
285                         borderPadding.left, borderPadding.top,
286                         ReflowChildFlags::Default);
287 
288       if (child->GetSize() != oldChildSize) {
289         const nsString name = child->GetContent() == videoControlsDiv
290                                   ? u"resizevideocontrols"_ns
291                                   : u"resizecaption"_ns;
292         RefPtr<Runnable> event =
293             new DispatchResizeEvent(child->GetContent(), name);
294         nsContentUtils::AddScriptRunner(event);
295       }
296     } else {
297       NS_ERROR("Unexpected extra child frame in nsVideoFrame; skipping");
298     }
299   }
300 
301   if (isBSizeShrinkWrapping) {
302     if (contentBoxBSize == NS_UNCONSTRAINEDSIZE) {
303       // We didn't get a BSize from our intrinsic size/ratio, nor did we
304       // get one from our controls. Just use BSize of 0.
305       contentBoxBSize = 0;
306     }
307     contentBoxBSize =
308         NS_CSS_MINMAX(contentBoxBSize, aReflowInput.ComputedMinBSize(),
309                       aReflowInput.ComputedMaxBSize());
310     borderBoxBSize = contentBoxBSize + logicalBP.BStartEnd(myWM);
311   }
312 
313   LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize);
314   aMetrics.SetSize(myWM, logicalDesiredSize);
315 
316   aMetrics.SetOverflowAreasToDesiredBounds();
317 
318   FinishAndStoreOverflow(&aMetrics);
319 
320   NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("exit nsVideoFrame::Reflow: size=%d,%d",
321                                         aMetrics.Width(), aMetrics.Height()));
322 
323   MOZ_ASSERT(aStatus.IsEmpty(), "This type of frame can't be split.");
324   NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics);
325 }
326 
327 #ifdef ACCESSIBILITY
AccessibleType()328 a11y::AccType nsVideoFrame::AccessibleType() { return a11y::eHTMLMediaType; }
329 #endif
330 
331 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const332 nsresult nsVideoFrame::GetFrameName(nsAString& aResult) const {
333   return MakeFrameName(u"HTMLVideo"_ns, aResult);
334 }
335 #endif
336 
ComputeSize(gfxContext * aRenderingContext,WritingMode aWM,const LogicalSize & aCBSize,nscoord aAvailableISize,const LogicalSize & aMargin,const LogicalSize & aBorderPadding,const StyleSizeOverrides & aSizeOverrides,ComputeSizeFlags aFlags)337 nsIFrame::SizeComputationResult nsVideoFrame::ComputeSize(
338     gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
339     nscoord aAvailableISize, const LogicalSize& aMargin,
340     const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
341     ComputeSizeFlags aFlags) {
342   if (!HasVideoElement()) {
343     return nsContainerFrame::ComputeSize(
344         aRenderingContext, aWM, aCBSize, aAvailableISize, aMargin,
345         aBorderPadding, aSizeOverrides, aFlags);
346   }
347 
348   return {ComputeSizeWithIntrinsicDimensions(
349               aRenderingContext, aWM, GetIntrinsicSize(), GetAspectRatio(),
350               aCBSize, aMargin, aBorderPadding, aSizeOverrides, aFlags),
351           AspectRatioUsage::None};
352 }
353 
GetMinISize(gfxContext * aRenderingContext)354 nscoord nsVideoFrame::GetMinISize(gfxContext* aRenderingContext) {
355   nscoord result;
356   DISPLAY_MIN_INLINE_SIZE(this, result);
357 
358   nsSize size = kFallbackIntrinsicSize;
359   if (HasVideoElement()) {
360     size = GetVideoIntrinsicSize();
361   } else {
362     // We expect last and only child of audio elements to be control if
363     // "controls" attribute is present.
364     if (StyleDisplay()->IsContainSize() || !mFrames.LastChild()) {
365       size = nsSize();
366     }
367   }
368 
369   result = GetWritingMode().IsVertical() ? size.height : size.width;
370   return result;
371 }
372 
GetPrefISize(gfxContext * aRenderingContext)373 nscoord nsVideoFrame::GetPrefISize(gfxContext* aRenderingContext) {
374   // <audio> / <video> has the same min / pref ISize.
375   return GetMinISize(aRenderingContext);
376 }
377 
PosterImageSize() const378 Maybe<nsSize> nsVideoFrame::PosterImageSize() const {
379   // Use the poster image frame's size.
380   nsIFrame* child = GetPosterImage()->GetPrimaryFrame();
381   return child->GetIntrinsicSize().ToSize();
382 }
383 
GetIntrinsicRatio() const384 AspectRatio nsVideoFrame::GetIntrinsicRatio() const {
385   if (!HasVideoElement()) {
386     // Audio elements have no intrinsic ratio.
387     return AspectRatio();
388   }
389 
390   // 'contain:size' replaced elements have no intrinsic ratio.
391   if (StyleDisplay()->IsContainSize()) {
392     return AspectRatio();
393   }
394 
395   HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());
396   if (Maybe<CSSIntSize> size = element->GetVideoSize()) {
397     return AspectRatio::FromSize(*size);
398   }
399 
400   if (ShouldDisplayPoster()) {
401     if (Maybe<nsSize> imgSize = PosterImageSize()) {
402       return AspectRatio::FromSize(*imgSize);
403     }
404   }
405 
406   return AspectRatio::FromSize(kFallbackIntrinsicSizeInPixels);
407 }
408 
ShouldDisplayPoster() const409 bool nsVideoFrame::ShouldDisplayPoster() const {
410   if (!HasVideoElement()) return false;
411 
412   HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());
413   if (element->GetPlayedOrSeeked() && HasVideoData()) return false;
414 
415   nsCOMPtr<nsIImageLoadingContent> imgContent = do_QueryInterface(mPosterImage);
416   NS_ENSURE_TRUE(imgContent, false);
417 
418   nsCOMPtr<imgIRequest> request;
419   nsresult res = imgContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
420                                         getter_AddRefs(request));
421   if (NS_FAILED(res) || !request) {
422     return false;
423   }
424 
425   uint32_t status = 0;
426   res = request->GetImageStatus(&status);
427   if (NS_FAILED(res) || (status & imgIRequest::STATUS_ERROR)) return false;
428 
429   return true;
430 }
431 
GetVideoIntrinsicSize() const432 nsSize nsVideoFrame::GetVideoIntrinsicSize() const {
433   // 'contain:size' replaced elements have intrinsic size 0,0.
434   if (StyleDisplay()->IsContainSize()) {
435     return nsSize(0, 0);
436   }
437 
438   HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());
439   if (Maybe<CSSIntSize> size = element->GetVideoSize()) {
440     return CSSPixel::ToAppUnits(*size);
441   }
442 
443   if (ShouldDisplayPoster()) {
444     if (Maybe<nsSize> imgSize = PosterImageSize()) {
445       return *imgSize;
446     }
447   }
448 
449   return kFallbackIntrinsicSize;
450 }
451 
GetIntrinsicSize()452 IntrinsicSize nsVideoFrame::GetIntrinsicSize() {
453   return IntrinsicSize(GetVideoIntrinsicSize());
454 }
455 
UpdatePosterSource(bool aNotify)456 void nsVideoFrame::UpdatePosterSource(bool aNotify) {
457   NS_ASSERTION(HasVideoElement(), "Only call this on <video> elements.");
458   HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());
459 
460   if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::poster) &&
461       !element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::poster,
462                             nsGkAtoms::_empty, eIgnoreCase)) {
463     nsAutoString posterStr;
464     element->GetPoster(posterStr);
465     mPosterImage->SetAttr(kNameSpaceID_None, nsGkAtoms::src, posterStr,
466                           aNotify);
467   } else {
468     mPosterImage->UnsetAttr(kNameSpaceID_None, nsGkAtoms::src, aNotify);
469   }
470 }
471 
AttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType)472 nsresult nsVideoFrame::AttributeChanged(int32_t aNameSpaceID,
473                                         nsAtom* aAttribute, int32_t aModType) {
474   if (aAttribute == nsGkAtoms::poster && HasVideoElement()) {
475     UpdatePosterSource(true);
476   }
477   return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
478 }
479 
OnVisibilityChange(Visibility aNewVisibility,const Maybe<OnNonvisible> & aNonvisibleAction)480 void nsVideoFrame::OnVisibilityChange(
481     Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
482   if (HasVideoElement()) {
483     static_cast<HTMLMediaElement*>(GetContent())
484         ->OnVisibilityChange(aNewVisibility);
485   }
486 
487   nsCOMPtr<nsIImageLoadingContent> imageLoader =
488       do_QueryInterface(mPosterImage);
489   if (imageLoader) {
490     imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
491   }
492 
493   nsContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
494 }
495 
HasVideoElement() const496 bool nsVideoFrame::HasVideoElement() const {
497   return static_cast<HTMLMediaElement*>(GetContent())->IsVideo();
498 }
499 
HasVideoData() const500 bool nsVideoFrame::HasVideoData() const {
501   if (!HasVideoElement()) {
502     return false;
503   }
504   auto* element = static_cast<HTMLVideoElement*>(GetContent());
505   return element->GetVideoSize().isSome();
506 }
507 
UpdateTextTrack()508 void nsVideoFrame::UpdateTextTrack() {
509   static_cast<HTMLMediaElement*>(GetContent())->NotifyCueDisplayStatesChanged();
510 }
511 
512 namespace mozilla {
513 
514 class nsDisplayVideo : public nsPaintedDisplayItem {
515  public:
nsDisplayVideo(nsDisplayListBuilder * aBuilder,nsVideoFrame * aFrame)516   nsDisplayVideo(nsDisplayListBuilder* aBuilder, nsVideoFrame* aFrame)
517       : nsPaintedDisplayItem(aBuilder, aFrame) {
518     MOZ_COUNT_CTOR(nsDisplayVideo);
519   }
520 
521   MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayVideo)
522 
523   NS_DISPLAY_DECL_NAME("Video", TYPE_VIDEO)
524 
GetImageContainer(gfxRect & aDestGFXRect)525   already_AddRefed<ImageContainer> GetImageContainer(gfxRect& aDestGFXRect) {
526     nsRect area = Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
527     HTMLVideoElement* element =
528         static_cast<HTMLVideoElement*>(Frame()->GetContent());
529 
530     Maybe<CSSIntSize> videoSizeInPx = element->GetVideoSize();
531     if (videoSizeInPx.isNothing() || area.IsEmpty()) {
532       return nullptr;
533     }
534 
535     RefPtr<ImageContainer> container = element->GetImageContainer();
536     if (!container) {
537       return nullptr;
538     }
539 
540     // Retrieve the size of the decoded video frame, before being scaled
541     // by pixel aspect ratio.
542     mozilla::gfx::IntSize frameSize = container->GetCurrentSize();
543     if (frameSize.width == 0 || frameSize.height == 0) {
544       // No image, or zero-sized image. Don't render.
545       return nullptr;
546     }
547 
548     const auto aspectRatio = AspectRatio::FromSize(*videoSizeInPx);
549     const IntrinsicSize intrinsicSize(CSSPixel::ToAppUnits(*videoSizeInPx));
550     nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
551         area, intrinsicSize, aspectRatio, Frame()->StylePosition());
552 
553     aDestGFXRect = Frame()->PresContext()->AppUnitsToGfxUnits(dest);
554     aDestGFXRect.Round();
555     if (aDestGFXRect.IsEmpty()) {
556       return nullptr;
557     }
558 
559     return container.forget();
560   }
561 
CreateWebRenderCommands(mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const mozilla::layers::StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayListBuilder * aDisplayListBuilder)562   virtual bool CreateWebRenderCommands(
563       mozilla::wr::DisplayListBuilder& aBuilder,
564       mozilla::wr::IpcResourceUpdateQueue& aResources,
565       const mozilla::layers::StackingContextHelper& aSc,
566       mozilla::layers::RenderRootStateManager* aManager,
567       nsDisplayListBuilder* aDisplayListBuilder) override {
568     HTMLVideoElement* element =
569         static_cast<HTMLVideoElement*>(Frame()->GetContent());
570     gfxRect destGFXRect;
571     RefPtr<ImageContainer> container = GetImageContainer(destGFXRect);
572     if (!container) {
573       return true;
574     }
575 
576     container->SetRotation(element->RotationDegrees());
577 
578     // If the image container is empty, we don't want to fallback. Any other
579     // failure will be due to resource constraints and fallback is unlikely to
580     // help us. Hence we can ignore the return value from PushImage.
581     LayoutDeviceRect rect(destGFXRect.x, destGFXRect.y, destGFXRect.width,
582                           destGFXRect.height);
583     aManager->CommandBuilder().PushImage(this, container, aBuilder, aResources,
584                                          aSc, rect, rect);
585     return true;
586   }
587 
588   // For opaque videos, we will want to override GetOpaqueRegion here.
589   // This is tracked by bug 1545498.
590 
GetBounds(nsDisplayListBuilder * aBuilder,bool * aSnap) const591   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
592                            bool* aSnap) const override {
593     *aSnap = true;
594     nsIFrame* f = Frame();
595     return f->GetContentRectRelativeToSelf() + ToReferenceFrame();
596   }
597 
598   // Only report FirstContentfulPaint when the video is set
IsContentful() const599   bool IsContentful() const override {
600     nsVideoFrame* f = static_cast<nsVideoFrame*>(Frame());
601     HTMLVideoElement* video = HTMLVideoElement::FromNode(f->GetContent());
602     return video->VideoWidth() > 0;
603   }
604 
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)605   virtual void Paint(nsDisplayListBuilder* aBuilder,
606                      gfxContext* aCtx) override {
607     HTMLVideoElement* element =
608         static_cast<HTMLVideoElement*>(Frame()->GetContent());
609     gfxRect destGFXRect;
610     RefPtr<ImageContainer> container = GetImageContainer(destGFXRect);
611     if (!container) {
612       return;
613     }
614 
615     VideoInfo::Rotation rotationDeg = element->RotationDegrees();
616     Matrix preTransform = ComputeRotationMatrix(
617         destGFXRect.Width(), destGFXRect.Height(), rotationDeg);
618     Matrix transform =
619         preTransform * Matrix::Translation(destGFXRect.x, destGFXRect.y);
620 
621     AutoLockImage autoLock(container);
622     Image* image = autoLock.GetImage(TimeStamp::Now());
623     if (!image) {
624       return;
625     }
626     RefPtr<gfx::SourceSurface> surface = image->GetAsSourceSurface();
627     if (!surface || !surface->IsValid()) {
628       return;
629     }
630     gfx::IntSize size = surface->GetSize();
631 
632     IntSize scaleToSize(static_cast<int32_t>(destGFXRect.Width()),
633                         static_cast<int32_t>(destGFXRect.Height()));
634     // scaleHint is set regardless of rotation, so swap w/h if needed.
635     SwapScaleWidthHeightForRotation(scaleToSize, rotationDeg);
636     transform.PreScale(scaleToSize.width / double(size.Width()),
637                        scaleToSize.height / double(size.Height()));
638 
639     gfxContextMatrixAutoSaveRestore saveMatrix(aCtx);
640     aCtx->SetMatrix(
641         gfxUtils::SnapTransformTranslation(aCtx->CurrentMatrix(), nullptr));
642 
643     transform = gfxUtils::SnapTransform(
644         transform, gfxRect(0, 0, size.width, size.height), nullptr);
645     aCtx->Multiply(ThebesMatrix(transform));
646 
647     aCtx->GetDrawTarget()->FillRect(
648         Rect(0, 0, size.width, size.height),
649         SurfacePattern(surface, ExtendMode::CLAMP, Matrix(),
650                        nsLayoutUtils::GetSamplingFilterForFrame(Frame())),
651         DrawOptions());
652   }
653 };
654 
655 }  // namespace mozilla
656 
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)657 void nsVideoFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
658                                     const nsDisplayListSet& aLists) {
659   if (!IsVisibleForPainting()) return;
660 
661   DO_GLOBAL_REFLOW_COUNT_DSP("nsVideoFrame");
662 
663   DisplayBorderBackgroundOutline(aBuilder, aLists);
664 
665   const bool shouldDisplayPoster = ShouldDisplayPoster();
666 
667   // NOTE: If we're displaying a poster image (instead of video data), we can
668   // trust the nsImageFrame to constrain its drawing to its content rect
669   // (which happens to be the same as our content rect).
670   uint32_t clipFlags;
671   if (shouldDisplayPoster ||
672       !nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition())) {
673     clipFlags = DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
674   } else {
675     clipFlags = 0;
676   }
677 
678   DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox clip(
679       aBuilder, this, clipFlags);
680 
681   if (HasVideoElement() && !shouldDisplayPoster) {
682     aLists.Content()->AppendNewToTop<nsDisplayVideo>(aBuilder, this);
683   }
684 
685   // Add child frames to display list. We expect various children,
686   // but only want to draw mPosterImage conditionally. Others we
687   // always add to the display list.
688   for (nsIFrame* child : mFrames) {
689     if (child->GetContent() != mPosterImage || shouldDisplayPoster ||
690         child->IsBoxFrame()) {
691       nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
692           aBuilder, child,
693           aBuilder->GetVisibleRect() - child->GetOffsetTo(this),
694           aBuilder->GetDirtyRect() - child->GetOffsetTo(this));
695 
696       child->BuildDisplayListForStackingContext(aBuilder, aLists.Content());
697     }
698   }
699 }
700