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