Coverage Report

Created: 2018-09-25 14:53

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