Source code

Revision control

Other Tools

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