diff options
Diffstat (limited to 'layout/generic/nsVideoFrame.cpp')
-rw-r--r-- | layout/generic/nsVideoFrame.cpp | 695 |
1 files changed, 695 insertions, 0 deletions
diff --git a/layout/generic/nsVideoFrame.cpp b/layout/generic/nsVideoFrame.cpp new file mode 100644 index 000000000..9f27684a7 --- /dev/null +++ b/layout/generic/nsVideoFrame.cpp @@ -0,0 +1,695 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* rendering object for the HTML <video> element */ + +#include "nsVideoFrame.h" + +#include "nsCOMPtr.h" +#include "nsGkAtoms.h" + +#include "mozilla/dom/HTMLVideoElement.h" +#include "nsIDOMHTMLImageElement.h" +#include "nsDisplayList.h" +#include "nsGenericHTMLElement.h" +#include "nsPresContext.h" +#include "nsContentCreatorFunctions.h" +#include "nsBoxLayoutState.h" +#include "nsBoxFrame.h" +#include "nsImageFrame.h" +#include "nsIImageLoadingContent.h" +#include "nsContentUtils.h" +#include "ImageContainer.h" +#include "ImageLayers.h" +#include "nsContentList.h" +#include "nsStyleUtil.h" +#include <algorithm> + +using namespace mozilla; +using namespace mozilla::layers; +using namespace mozilla::dom; +using namespace mozilla::gfx; + +nsIFrame* +NS_NewHTMLVideoFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsVideoFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsVideoFrame) + +// A matrix to obtain a correct-rotated video frame. +static Matrix +ComputeRotationMatrix(gfxFloat aRotatedWidth, + gfxFloat aRotatedHeight, + VideoInfo::Rotation aDegrees) +{ + Matrix shiftVideoCenterToOrigin; + if (aDegrees == VideoInfo::Rotation::kDegree_90 || + aDegrees == VideoInfo::Rotation::kDegree_270) { + shiftVideoCenterToOrigin = Matrix::Translation(-aRotatedHeight / 2.0, + -aRotatedWidth / 2.0); + } else { + shiftVideoCenterToOrigin = Matrix::Translation(-aRotatedWidth / 2.0, + -aRotatedHeight / 2.0); + } + + Matrix rotation = Matrix::Rotation(gfx::Float(aDegrees / 180.0 * M_PI)); + Matrix shiftLeftTopToOrigin = Matrix::Translation(aRotatedWidth / 2.0, + aRotatedHeight / 2.0); + return shiftVideoCenterToOrigin * rotation * shiftLeftTopToOrigin; +} + +static void +SwapScaleWidthHeightForRotation(IntSize& aSize, VideoInfo::Rotation aDegrees) +{ + if (aDegrees == VideoInfo::Rotation::kDegree_90 || + aDegrees == VideoInfo::Rotation::kDegree_270) { + int32_t tmpWidth = aSize.width; + aSize.width = aSize.height; + aSize.height = tmpWidth; + } +} + +nsVideoFrame::nsVideoFrame(nsStyleContext* aContext) + : nsContainerFrame(aContext) +{ + EnableVisibilityTracking(); +} + +nsVideoFrame::~nsVideoFrame() +{ +} + +NS_QUERYFRAME_HEAD(nsVideoFrame) + NS_QUERYFRAME_ENTRY(nsVideoFrame) + NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +nsresult +nsVideoFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements) +{ + nsNodeInfoManager *nodeInfoManager = GetContent()->GetComposedDoc()->NodeInfoManager(); + RefPtr<NodeInfo> nodeInfo; + Element *element; + + if (HasVideoElement()) { + // Create an anonymous image element as a child to hold the poster + // image. We may not have a poster image now, but one could be added + // before we load, or on a subsequent load. + nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::img, + nullptr, + kNameSpaceID_XHTML, + nsIDOMNode::ELEMENT_NODE); + NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY); + element = NS_NewHTMLImageElement(nodeInfo.forget()); + mPosterImage = element; + NS_ENSURE_TRUE(mPosterImage, NS_ERROR_OUT_OF_MEMORY); + + // Set the nsImageLoadingContent::ImageState() to 0. This means that the + // image will always report its state as 0, so it will never be reframed + // to show frames for loading or the broken image icon. This is important, + // as the image is native anonymous, and so can't be reframed (currently). + nsCOMPtr<nsIImageLoadingContent> imgContent = do_QueryInterface(mPosterImage); + NS_ENSURE_TRUE(imgContent, NS_ERROR_FAILURE); + + imgContent->ForceImageState(true, 0); + // And now have it update its internal state + element->UpdateState(false); + + UpdatePosterSource(false); + + if (!aElements.AppendElement(mPosterImage)) + return NS_ERROR_OUT_OF_MEMORY; + + // Set up the caption overlay div for showing any TextTrack data + nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::div, + nullptr, + kNameSpaceID_XHTML, + nsIDOMNode::ELEMENT_NODE); + NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY); + mCaptionDiv = NS_NewHTMLDivElement(nodeInfo.forget()); + NS_ENSURE_TRUE(mCaptionDiv, NS_ERROR_OUT_OF_MEMORY); + nsGenericHTMLElement* div = static_cast<nsGenericHTMLElement*>(mCaptionDiv.get()); + div->SetClassName(NS_LITERAL_STRING("caption-box")); + + if (!aElements.AppendElement(mCaptionDiv)) + return NS_ERROR_OUT_OF_MEMORY; + } + + // Set up "videocontrols" XUL element which will be XBL-bound to the + // actual controls. + nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::videocontrols, + nullptr, + kNameSpaceID_XUL, + nsIDOMNode::ELEMENT_NODE); + NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY); + + NS_TrustedNewXULElement(getter_AddRefs(mVideoControls), nodeInfo.forget()); + if (!aElements.AppendElement(mVideoControls)) + return NS_ERROR_OUT_OF_MEMORY; + + return NS_OK; +} + +void +nsVideoFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements, + uint32_t aFliter) +{ + if (mPosterImage) { + aElements.AppendElement(mPosterImage); + } + + if (mVideoControls) { + aElements.AppendElement(mVideoControls); + } + + if (mCaptionDiv) { + aElements.AppendElement(mCaptionDiv); + } +} + +void +nsVideoFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + nsContentUtils::DestroyAnonymousContent(&mCaptionDiv); + nsContentUtils::DestroyAnonymousContent(&mVideoControls); + nsContentUtils::DestroyAnonymousContent(&mPosterImage); + nsContainerFrame::DestroyFrom(aDestructRoot); +} + +bool +nsVideoFrame::IsLeaf() const +{ + return true; +} + +already_AddRefed<Layer> +nsVideoFrame::BuildLayer(nsDisplayListBuilder* aBuilder, + LayerManager* aManager, + nsDisplayItem* aItem, + const ContainerLayerParameters& aContainerParameters) +{ + nsRect area = GetContentRectRelativeToSelf() + aItem->ToReferenceFrame(); + HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent()); + + nsIntSize videoSizeInPx; + if (NS_FAILED(element->GetVideoSize(&videoSizeInPx)) || + area.IsEmpty()) { + return nullptr; + } + + RefPtr<ImageContainer> container = element->GetImageContainer(); + if (!container) + return nullptr; + + // Retrieve the size of the decoded video frame, before being scaled + // by pixel aspect ratio. + mozilla::gfx::IntSize frameSize = container->GetCurrentSize(); + if (frameSize.width == 0 || frameSize.height == 0) { + // No image, or zero-sized image. No point creating a layer. + return nullptr; + } + + // Convert video size from pixel units into app units, to get an aspect-ratio + // (which has to be represented as a nsSize) and an IntrinsicSize that we + // can pass to ComputeObjectRenderRect. + nsSize aspectRatio(nsPresContext::CSSPixelsToAppUnits(videoSizeInPx.width), + nsPresContext::CSSPixelsToAppUnits(videoSizeInPx.height)); + IntrinsicSize intrinsicSize; + intrinsicSize.width.SetCoordValue(aspectRatio.width); + intrinsicSize.height.SetCoordValue(aspectRatio.height); + + nsRect dest = nsLayoutUtils::ComputeObjectDestRect(area, + intrinsicSize, + aspectRatio, + StylePosition()); + + gfxRect destGFXRect = PresContext()->AppUnitsToGfxUnits(dest); + destGFXRect.Round(); + if (destGFXRect.IsEmpty()) { + return nullptr; + } + + VideoInfo::Rotation rotationDeg = element->RotationDegrees(); + IntSize scaleHint(static_cast<int32_t>(destGFXRect.Width()), + static_cast<int32_t>(destGFXRect.Height())); + // scaleHint is set regardless of rotation, so swap w/h if needed. + SwapScaleWidthHeightForRotation(scaleHint, rotationDeg); + container->SetScaleHint(scaleHint); + + RefPtr<ImageLayer> layer = static_cast<ImageLayer*> + (aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, aItem)); + if (!layer) { + layer = aManager->CreateImageLayer(); + if (!layer) + return nullptr; + } + + layer->SetContainer(container); + layer->SetSamplingFilter(nsLayoutUtils::GetSamplingFilterForFrame(this)); + // Set a transform on the layer to draw the video in the right place + gfxPoint p = destGFXRect.TopLeft() + aContainerParameters.mOffset; + + Matrix preTransform = ComputeRotationMatrix(destGFXRect.Width(), + destGFXRect.Height(), + rotationDeg); + + Matrix transform = preTransform * Matrix::Translation(p.x, p.y); + + layer->SetBaseTransform(gfx::Matrix4x4::From2D(transform)); + layer->SetScaleToSize(scaleHint, ScaleMode::STRETCH); + RefPtr<Layer> result = layer.forget(); + return result.forget(); +} + +class DispatchResizeToControls : public Runnable +{ +public: + explicit DispatchResizeToControls(nsIContent* aContent) + : mContent(aContent) {} + NS_IMETHOD Run() override { + nsContentUtils::DispatchTrustedEvent(mContent->OwnerDoc(), mContent, + NS_LITERAL_STRING("resizevideocontrols"), + false, false); + return NS_OK; + } + nsCOMPtr<nsIContent> mContent; +}; + +void +nsVideoFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aMetrics, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsVideoFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus); + NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, + ("enter nsVideoFrame::Reflow: availSize=%d,%d", + aReflowInput.AvailableWidth(), + aReflowInput.AvailableHeight())); + + NS_PRECONDITION(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow"); + + aStatus = NS_FRAME_COMPLETE; + + aMetrics.Width() = aReflowInput.ComputedWidth(); + aMetrics.Height() = aReflowInput.ComputedHeight(); + + // stash this away so we can compute our inner area later + mBorderPadding = aReflowInput.ComputedPhysicalBorderPadding(); + + aMetrics.Width() += mBorderPadding.left + mBorderPadding.right; + aMetrics.Height() += mBorderPadding.top + mBorderPadding.bottom; + + // Reflow the child frames. We may have up to two, an image frame + // which is the poster, and a box frame, which is the video controls. + for (nsIFrame* child : mFrames) { + if (child->GetContent() == mPosterImage) { + // Reflow the poster frame. + nsImageFrame* imageFrame = static_cast<nsImageFrame*>(child); + ReflowOutput kidDesiredSize(aReflowInput); + WritingMode wm = imageFrame->GetWritingMode(); + LogicalSize availableSize = aReflowInput.AvailableSize(wm); + LogicalSize cbSize = aMetrics.Size(aMetrics.GetWritingMode()). + ConvertTo(wm, aMetrics.GetWritingMode()); + ReflowInput kidReflowInput(aPresContext, + aReflowInput, + imageFrame, + availableSize, + &cbSize); + + nsRect posterRenderRect; + if (ShouldDisplayPoster()) { + posterRenderRect = + nsRect(nsPoint(mBorderPadding.left, mBorderPadding.top), + nsSize(aReflowInput.ComputedWidth(), + aReflowInput.ComputedHeight())); + } + kidReflowInput.SetComputedWidth(posterRenderRect.width); + kidReflowInput.SetComputedHeight(posterRenderRect.height); + ReflowChild(imageFrame, aPresContext, kidDesiredSize, kidReflowInput, + posterRenderRect.x, posterRenderRect.y, 0, aStatus); + FinishReflowChild(imageFrame, aPresContext, + kidDesiredSize, &kidReflowInput, + posterRenderRect.x, posterRenderRect.y, 0); + } else if (child->GetContent() == mVideoControls) { + // Reflow the video controls frame. + nsBoxLayoutState boxState(PresContext(), aReflowInput.mRenderingContext); + nsSize size = child->GetSize(); + nsBoxFrame::LayoutChildAt(boxState, + child, + nsRect(mBorderPadding.left, + mBorderPadding.top, + aReflowInput.ComputedWidth(), + aReflowInput.ComputedHeight())); + if (child->GetSize() != size) { + RefPtr<Runnable> event = new DispatchResizeToControls(child->GetContent()); + nsContentUtils::AddScriptRunner(event); + } + } else if (child->GetContent() == mCaptionDiv) { + // Reflow to caption div + ReflowOutput kidDesiredSize(aReflowInput); + WritingMode wm = child->GetWritingMode(); + LogicalSize availableSize = aReflowInput.AvailableSize(wm); + LogicalSize cbSize = aMetrics.Size(aMetrics.GetWritingMode()). + ConvertTo(wm, aMetrics.GetWritingMode()); + ReflowInput kidReflowInput(aPresContext, + aReflowInput, + child, + availableSize, + &cbSize); + nsSize size(aReflowInput.ComputedWidth(), aReflowInput.ComputedHeight()); + size.width -= kidReflowInput.ComputedPhysicalBorderPadding().LeftRight(); + size.height -= kidReflowInput.ComputedPhysicalBorderPadding().TopBottom(); + + kidReflowInput.SetComputedWidth(std::max(size.width, 0)); + kidReflowInput.SetComputedHeight(std::max(size.height, 0)); + + ReflowChild(child, aPresContext, kidDesiredSize, kidReflowInput, + mBorderPadding.left, mBorderPadding.top, 0, aStatus); + FinishReflowChild(child, aPresContext, + kidDesiredSize, &kidReflowInput, + mBorderPadding.left, mBorderPadding.top, 0); + } + } + aMetrics.SetOverflowAreasToDesiredBounds(); + + FinishAndStoreOverflow(&aMetrics); + + NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, + ("exit nsVideoFrame::Reflow: size=%d,%d", + aMetrics.Width(), aMetrics.Height())); + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics); +} + +class nsDisplayVideo : public nsDisplayItem { +public: + nsDisplayVideo(nsDisplayListBuilder* aBuilder, nsVideoFrame* aFrame) + : nsDisplayItem(aBuilder, aFrame) + { + MOZ_COUNT_CTOR(nsDisplayVideo); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayVideo() { + MOZ_COUNT_DTOR(nsDisplayVideo); + } +#endif + + NS_DISPLAY_DECL_NAME("Video", TYPE_VIDEO) + + // It would be great if we could override GetOpaqueRegion to return nonempty here, + // but it's probably not safe to do so in general. Video frames are + // updated asynchronously from decoder threads, and it's possible that + // we might have an opaque video frame when GetOpaqueRegion is called, but + // when we come to paint, the video frame is transparent or has gone + // away completely (e.g. because of a decoder error). The problem would + // be especially acute if we have off-main-thread rendering. + + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override + { + *aSnap = true; + nsIFrame* f = Frame(); + return f->GetContentRectRelativeToSelf() + ToReferenceFrame(); + } + + virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder, + LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) override + { + return static_cast<nsVideoFrame*>(mFrame)->BuildLayer(aBuilder, aManager, this, aContainerParameters); + } + + virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder, + LayerManager* aManager, + const ContainerLayerParameters& aParameters) override + { + if (aManager->IsCompositingCheap()) { + // Since ImageLayers don't require additional memory of the + // video frames we have to have anyway, we can't save much by + // making layers inactive. Also, for many accelerated layer + // managers calling imageContainer->GetCurrentAsSurface can be + // very expensive. So just always be active when compositing is + // cheap (i.e. hardware accelerated). + return LAYER_ACTIVE; + } + HTMLMediaElement* elem = + static_cast<HTMLMediaElement*>(mFrame->GetContent()); + return elem->IsPotentiallyPlaying() ? LAYER_ACTIVE_FORCE : LAYER_INACTIVE; + } +}; + +void +nsVideoFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + if (!IsVisibleForPainting(aBuilder)) + return; + + DO_GLOBAL_REFLOW_COUNT_DSP("nsVideoFrame"); + + DisplayBorderBackgroundOutline(aBuilder, aLists); + + const bool shouldDisplayPoster = ShouldDisplayPoster(); + + // NOTE: If we're displaying a poster image (instead of video data), we can + // trust the nsImageFrame to constrain its drawing to its content rect + // (which happens to be the same as our content rect). + uint32_t clipFlags; + if (shouldDisplayPoster || + !nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition())) { + clipFlags = + DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT; + } else { + clipFlags = 0; + } + + DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox + clip(aBuilder, this, clipFlags); + + if (HasVideoElement() && !shouldDisplayPoster) { + aLists.Content()->AppendNewToTop( + new (aBuilder) nsDisplayVideo(aBuilder, this)); + } + + // Add child frames to display list. We expect various children, + // but only want to draw mPosterImage conditionally. Others we + // always add to the display list. + for (nsIFrame* child : mFrames) { + if (child->GetContent() != mPosterImage || shouldDisplayPoster) { + child->BuildDisplayListForStackingContext(aBuilder, + aDirtyRect - child->GetOffsetTo(this), + aLists.Content()); + } else if (child->GetType() == nsGkAtoms::boxFrame) { + child->BuildDisplayListForStackingContext(aBuilder, + aDirtyRect - child->GetOffsetTo(this), + aLists.Content()); + } + } +} + +nsIAtom* +nsVideoFrame::GetType() const +{ + return nsGkAtoms::HTMLVideoFrame; +} + +#ifdef ACCESSIBILITY +a11y::AccType +nsVideoFrame::AccessibleType() +{ + return a11y::eHTMLMediaType; +} +#endif + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsVideoFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("HTMLVideo"), aResult); +} +#endif + +LogicalSize +nsVideoFrame::ComputeSize(nsRenderingContext *aRenderingContext, + WritingMode aWM, + const LogicalSize& aCBSize, + nscoord aAvailableISize, + const LogicalSize& aMargin, + const LogicalSize& aBorder, + const LogicalSize& aPadding, + ComputeSizeFlags aFlags) +{ + nsSize size = GetVideoIntrinsicSize(aRenderingContext); + + IntrinsicSize intrinsicSize; + intrinsicSize.width.SetCoordValue(size.width); + intrinsicSize.height.SetCoordValue(size.height); + + // Only video elements have an intrinsic ratio. + nsSize intrinsicRatio = HasVideoElement() ? size : nsSize(0, 0); + + return ComputeSizeWithIntrinsicDimensions(aRenderingContext, aWM, + intrinsicSize, intrinsicRatio, + aCBSize, aMargin, aBorder, aPadding, + aFlags); +} + +nscoord nsVideoFrame::GetMinISize(nsRenderingContext *aRenderingContext) +{ + nsSize size = GetVideoIntrinsicSize(aRenderingContext); + nscoord result = GetWritingMode().IsVertical() ? size.height : size.width; + DISPLAY_MIN_WIDTH(this, result); + return result; +} + +nscoord nsVideoFrame::GetPrefISize(nsRenderingContext *aRenderingContext) +{ + nsSize size = GetVideoIntrinsicSize(aRenderingContext); + nscoord result = GetWritingMode().IsVertical() ? size.height : size.width; + DISPLAY_PREF_WIDTH(this, result); + return result; +} + +nsSize nsVideoFrame::GetIntrinsicRatio() +{ + if (!HasVideoElement()) { + // Audio elements have no intrinsic ratio. + return nsSize(0, 0); + } + + return GetVideoIntrinsicSize(nullptr); +} + +bool nsVideoFrame::ShouldDisplayPoster() +{ + if (!HasVideoElement()) + return false; + + HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent()); + if (element->GetPlayedOrSeeked() && HasVideoData()) + return false; + + nsCOMPtr<nsIImageLoadingContent> imgContent = do_QueryInterface(mPosterImage); + NS_ENSURE_TRUE(imgContent, false); + + nsCOMPtr<imgIRequest> request; + nsresult res = imgContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(request)); + if (NS_FAILED(res) || !request) { + return false; + } + + uint32_t status = 0; + res = request->GetImageStatus(&status); + if (NS_FAILED(res) || (status & imgIRequest::STATUS_ERROR)) + return false; + + return true; +} + +nsSize +nsVideoFrame::GetVideoIntrinsicSize(nsRenderingContext *aRenderingContext) +{ + // Defaulting size to 300x150 if no size given. + nsIntSize size(300, 150); + + if (!HasVideoElement()) { + if (!mFrames.FirstChild()) { + return nsSize(0, 0); + } + + // Ask the controls frame what its preferred height is + nsBoxLayoutState boxState(PresContext(), aRenderingContext, 0); + nscoord prefHeight = mFrames.LastChild()->GetXULPrefSize(boxState).height; + return nsSize(nsPresContext::CSSPixelsToAppUnits(size.width), prefHeight); + } + + HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent()); + if (NS_FAILED(element->GetVideoSize(&size)) && ShouldDisplayPoster()) { + // Use the poster image frame's size. + nsIFrame *child = mPosterImage->GetPrimaryFrame(); + nsImageFrame* imageFrame = do_QueryFrame(child); + nsSize imgsize; + if (NS_SUCCEEDED(imageFrame->GetIntrinsicImageSize(imgsize))) { + return imgsize; + } + } + + return nsSize(nsPresContext::CSSPixelsToAppUnits(size.width), + nsPresContext::CSSPixelsToAppUnits(size.height)); +} + +void +nsVideoFrame::UpdatePosterSource(bool aNotify) +{ + NS_ASSERTION(HasVideoElement(), "Only call this on <video> elements."); + HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent()); + + if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::poster) && + !element->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::poster, + nsGkAtoms::_empty, + eIgnoreCase)) { + nsAutoString posterStr; + element->GetPoster(posterStr); + mPosterImage->SetAttr(kNameSpaceID_None, + nsGkAtoms::src, + posterStr, + aNotify); + } else { + mPosterImage->UnsetAttr(kNameSpaceID_None, nsGkAtoms::src, aNotify); + } +} + +nsresult +nsVideoFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aAttribute == nsGkAtoms::poster && HasVideoElement()) { + UpdatePosterSource(true); + } + return nsContainerFrame::AttributeChanged(aNameSpaceID, + aAttribute, + aModType); +} + +void +nsVideoFrame::OnVisibilityChange(Visibility aNewVisibility, + Maybe<OnNonvisible> aNonvisibleAction) +{ + if (HasVideoElement()) { + nsCOMPtr<nsIDOMHTMLMediaElement> mediaDomElement = do_QueryInterface(mContent); + mediaDomElement->OnVisibilityChange(aNewVisibility); + } + + nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mPosterImage); + if (imageLoader) { + imageLoader->OnVisibilityChange(aNewVisibility, + aNonvisibleAction); + } + + nsContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction); +} + +bool nsVideoFrame::HasVideoElement() { + nsCOMPtr<nsIDOMHTMLMediaElement> mediaDomElement = do_QueryInterface(mContent); + return mediaDomElement->IsVideo(); +} + +bool nsVideoFrame::HasVideoData() +{ + if (!HasVideoElement()) + return false; + HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent()); + nsIntSize size(0, 0); + element->GetVideoSize(&size); + return size != nsIntSize(0,0); +} |