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