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