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