nsVideoFrame.cpp (27132B)
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 "ImageContainer.h" 12 #include "mozilla/PresShell.h" 13 #include "mozilla/dom/HTMLImageElement.h" 14 #include "mozilla/dom/HTMLVideoElement.h" 15 #include "mozilla/dom/ShadowRoot.h" 16 #include "mozilla/layers/RenderRootStateManager.h" 17 #include "nsCOMPtr.h" 18 #include "nsContentCreatorFunctions.h" 19 #include "nsContentUtils.h" 20 #include "nsDisplayList.h" 21 #include "nsGenericHTMLElement.h" 22 #include "nsGkAtoms.h" 23 #include "nsIContentInlines.h" 24 #include "nsIImageLoadingContent.h" 25 #include "nsImageFrame.h" 26 #include "nsLayoutUtils.h" 27 #include "nsPresContext.h" 28 #include "nsStyleUtil.h" 29 30 using namespace mozilla; 31 using namespace mozilla::layers; 32 using namespace mozilla::dom; 33 using namespace mozilla::gfx; 34 35 nsIFrame* NS_NewHTMLVideoFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 36 return new (aPresShell) nsVideoFrame(aStyle, aPresShell->GetPresContext()); 37 } 38 39 nsIFrame* NS_NewHTMLAudioFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 40 return new (aPresShell) nsAudioFrame(aStyle, aPresShell->GetPresContext()); 41 } 42 43 NS_IMPL_FRAMEARENA_HELPERS(nsVideoFrame) 44 NS_QUERYFRAME_HEAD(nsVideoFrame) 45 NS_QUERYFRAME_ENTRY(nsVideoFrame) 46 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) 47 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 48 49 NS_IMPL_FRAMEARENA_HELPERS(nsAudioFrame) 50 NS_QUERYFRAME_HEAD(nsAudioFrame) 51 NS_QUERYFRAME_ENTRY(nsAudioFrame) 52 NS_QUERYFRAME_TAIL_INHERITING(nsVideoFrame) 53 54 // A matrix to obtain a correct-rotated video frame. 55 static Matrix ComputeRotationMatrix(gfxFloat aRotatedWidth, 56 gfxFloat aRotatedHeight, 57 VideoRotation aDegrees) { 58 Matrix shiftVideoCenterToOrigin; 59 if (aDegrees == VideoRotation::kDegree_90 || 60 aDegrees == VideoRotation::kDegree_270) { 61 shiftVideoCenterToOrigin = 62 Matrix::Translation(-aRotatedHeight / 2.0, -aRotatedWidth / 2.0); 63 } else { 64 shiftVideoCenterToOrigin = 65 Matrix::Translation(-aRotatedWidth / 2.0, -aRotatedHeight / 2.0); 66 } 67 68 auto angle = static_cast<double>(aDegrees) / 180.0 * M_PI; 69 Matrix rotation = Matrix::Rotation(static_cast<gfx::Float>(angle)); 70 Matrix shiftLeftTopToOrigin = 71 Matrix::Translation(aRotatedWidth / 2.0, aRotatedHeight / 2.0); 72 return shiftVideoCenterToOrigin * rotation * shiftLeftTopToOrigin; 73 } 74 75 static void SwapScaleWidthHeightForRotation(IntSize& aSize, 76 VideoRotation aDegrees) { 77 if (aDegrees == VideoRotation::kDegree_90 || 78 aDegrees == VideoRotation::kDegree_270) { 79 int32_t tmpWidth = aSize.width; 80 aSize.width = aSize.height; 81 aSize.height = tmpWidth; 82 } 83 } 84 85 nsVideoFrame::nsVideoFrame(ComputedStyle* aStyle, nsPresContext* aPc, 86 ClassID aClassID) 87 : nsContainerFrame(aStyle, aPc, aClassID), 88 mIsAudio(aClassID == nsAudioFrame::kClassID) { 89 EnableVisibilityTracking(); 90 } 91 92 nsVideoFrame::~nsVideoFrame() = default; 93 94 nsAudioFrame::nsAudioFrame(ComputedStyle* aStyle, nsPresContext* aPc) 95 : nsVideoFrame(aStyle, aPc, kClassID) {} 96 97 nsAudioFrame::~nsAudioFrame() = default; 98 99 nsresult nsVideoFrame::CreateAnonymousContent( 100 nsTArray<ContentInfo>& aElements) { 101 nsNodeInfoManager* nodeInfoManager = 102 GetContent()->GetComposedDoc()->NodeInfoManager(); 103 RefPtr<NodeInfo> nodeInfo; 104 105 if (HasVideoElement()) { 106 // Create an anonymous image element as a child to hold the poster 107 // image. We may not have a poster image now, but one could be added 108 // before we load, or on a subsequent load. 109 nodeInfo = nodeInfoManager->GetNodeInfo( 110 nsGkAtoms::img, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE); 111 NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY); 112 mPosterImage = NS_NewHTMLImageElement(nodeInfo.forget()); 113 NS_ENSURE_TRUE(mPosterImage, NS_ERROR_OUT_OF_MEMORY); 114 UpdatePosterSource(false); 115 116 // XXX(Bug 1631371) Check if this should use a fallible operation as it 117 // pretended earlier. 118 aElements.AppendElement(mPosterImage); 119 120 // Set up the caption overlay div for showing any TextTrack data 121 nodeInfo = nodeInfoManager->GetNodeInfo( 122 nsGkAtoms::div, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE); 123 NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY); 124 mCaptionDiv = NS_NewHTMLDivElement(nodeInfo.forget()); 125 NS_ENSURE_TRUE(mCaptionDiv, NS_ERROR_OUT_OF_MEMORY); 126 nsGenericHTMLElement* div = 127 static_cast<nsGenericHTMLElement*>(mCaptionDiv.get()); 128 div->SetClassName(u"caption-box"_ns); 129 130 // XXX(Bug 1631371) Check if this should use a fallible operation as it 131 // pretended earlier. 132 aElements.AppendElement(mCaptionDiv); 133 UpdateTextTrack(); 134 } 135 136 return NS_OK; 137 } 138 139 void nsVideoFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements, 140 uint32_t aFliter) { 141 if (mPosterImage) { 142 aElements.AppendElement(mPosterImage); 143 } 144 145 if (mCaptionDiv) { 146 aElements.AppendElement(mCaptionDiv); 147 } 148 } 149 150 nsIContent* nsVideoFrame::GetVideoControls() const { 151 if (!mContent->GetShadowRoot()) { 152 return nullptr; 153 } 154 155 // The video controls <div> is the only child of the UA Widget Shadow Root 156 // if it is present. It is only lazily inserted into the DOM when 157 // the controls attribute is set. 158 MOZ_ASSERT(mContent->GetShadowRoot()->IsUAWidget()); 159 MOZ_ASSERT(1 >= mContent->GetShadowRoot()->GetChildCount()); 160 return mContent->GetShadowRoot()->GetFirstChild(); 161 } 162 163 void nsVideoFrame::Destroy(DestroyContext& aContext) { 164 if (mReflowCallbackPosted) { 165 PresShell()->CancelReflowCallback(this); 166 } 167 aContext.AddAnonymousContent(mCaptionDiv.forget()); 168 aContext.AddAnonymousContent(mPosterImage.forget()); 169 nsContainerFrame::Destroy(aContext); 170 } 171 172 class DispatchResizeEvent : public Runnable { 173 public: 174 explicit DispatchResizeEvent(nsIContent* aContent, 175 const nsLiteralString& aName) 176 : Runnable("DispatchResizeEvent"), mContent(aContent), mName(aName) {} 177 NS_IMETHOD Run() override { 178 nsContentUtils::DispatchTrustedEvent(mContent->OwnerDoc(), mContent, mName, 179 CanBubble::eNo, Cancelable::eNo); 180 return NS_OK; 181 } 182 nsCOMPtr<nsIContent> mContent; 183 const nsLiteralString mName; 184 }; 185 186 bool nsVideoFrame::ReflowFinished() { 187 mReflowCallbackPosted = false; 188 auto GetSize = [&](nsIContent* aContent) -> Maybe<nsSize> { 189 if (!aContent) { 190 return Nothing(); 191 } 192 nsIFrame* f = aContent->GetPrimaryFrame(); 193 if (!f) { 194 return Nothing(); 195 } 196 return Some(f->GetSize()); 197 }; 198 199 AutoTArray<nsCOMPtr<nsIRunnable>, 2> events; 200 201 if (auto size = GetSize(mCaptionDiv)) { 202 if (*size != mCaptionTrackedSize) { 203 mCaptionTrackedSize = *size; 204 events.AppendElement( 205 new DispatchResizeEvent(mCaptionDiv, u"resizecaption"_ns)); 206 } 207 } 208 nsIContent* controls = GetVideoControls(); 209 if (auto size = GetSize(controls)) { 210 if (*size != mControlsTrackedSize) { 211 mControlsTrackedSize = *size; 212 events.AppendElement( 213 new DispatchResizeEvent(controls, u"resizevideocontrols"_ns)); 214 } 215 } 216 for (auto& event : events) { 217 nsContentUtils::AddScriptRunner(event.forget()); 218 } 219 return false; 220 } 221 222 nsRect nsVideoFrame::GetDestRect(const nsRect& aContentBox) const { 223 return nsLayoutUtils::ComputeObjectDestRect( 224 aContentBox, GetIntrinsicSize(/* aIgnoreContainment = */ true), 225 GetIntrinsicRatio(/* aIgnoreContainment = */ true), StylePosition()); 226 } 227 228 void nsVideoFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics, 229 const ReflowInput& aReflowInput, 230 nsReflowStatus& aStatus) { 231 MarkInReflow(); 232 DO_GLOBAL_REFLOW_COUNT("nsVideoFrame"); 233 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 234 NS_FRAME_TRACE( 235 NS_FRAME_TRACE_CALLS, 236 ("enter nsVideoFrame::Reflow: availSize=%d,%d", 237 aReflowInput.AvailableISize(), aReflowInput.AvailableBSize())); 238 239 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_IN_REFLOW), "frame is not in reflow"); 240 241 const WritingMode myWM = aReflowInput.GetWritingMode(); 242 nscoord contentBoxBSize = aReflowInput.ComputedBSize(); 243 const auto logicalBP = aReflowInput.ComputedLogicalBorderPadding(myWM); 244 const nscoord borderBoxISize = 245 aReflowInput.ComputedISize() + logicalBP.IStartEnd(myWM); 246 const bool isBSizeShrinkWrapping = (contentBoxBSize == NS_UNCONSTRAINEDSIZE); 247 248 nscoord borderBoxBSize; 249 if (!isBSizeShrinkWrapping) { 250 borderBoxBSize = contentBoxBSize + logicalBP.BStartEnd(myWM); 251 } 252 253 nsIContent* videoControlsDiv = GetVideoControls(); 254 const nsSize containerSize = 255 aReflowInput.ComputedSizeAsContainerIfConstrained(); 256 257 // Reflow the child frames. We may have up to three: an image 258 // frame (for the poster image), a container frame for the controls, 259 // and a container frame for the caption. 260 for (nsIFrame* child : mFrames) { 261 nsSize oldChildSize = child->GetSize(); 262 nsReflowStatus childStatus; 263 const WritingMode childWM = child->GetWritingMode(); 264 LogicalSize availableSize = aReflowInput.ComputedSize(childWM); 265 availableSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE; 266 ReflowInput kidReflowInput(aPresContext, aReflowInput, child, 267 availableSize); 268 ReflowOutput kidDesiredSize(myWM); 269 270 if (child->GetContent() == mPosterImage) { 271 // Reflow the poster frame. 272 const LogicalPoint childOrigin = logicalBP.StartOffset(myWM); 273 const LogicalSize posterRenderSize = aReflowInput.ComputedSize(childWM); 274 kidReflowInput.SetComputedISize(posterRenderSize.ISize(childWM)); 275 kidReflowInput.SetComputedBSize(posterRenderSize.BSize(childWM)); 276 277 ReflowChild(child, aPresContext, kidDesiredSize, kidReflowInput, myWM, 278 childOrigin, containerSize, ReflowChildFlags::Default, 279 childStatus); 280 MOZ_ASSERT(childStatus.IsFullyComplete(), 281 "We gave our child unconstrained available block-size, " 282 "so it should be complete!"); 283 284 FinishReflowChild(child, aPresContext, kidDesiredSize, &kidReflowInput, 285 myWM, childOrigin, containerSize, 286 ReflowChildFlags::Default); 287 288 } else if (child->GetContent() == mCaptionDiv || 289 child->GetContent() == videoControlsDiv) { 290 // Reflow the caption and control bar frames. 291 const LogicalPoint childOrigin = logicalBP.StartOffset(myWM); 292 ReflowChild(child, aPresContext, kidDesiredSize, kidReflowInput, myWM, 293 childOrigin, containerSize, ReflowChildFlags::Default, 294 childStatus); 295 MOZ_ASSERT(childStatus.IsFullyComplete(), 296 "We gave our child unconstrained available block-size, " 297 "so it should be complete!"); 298 299 if (child->GetContent() == videoControlsDiv && isBSizeShrinkWrapping) { 300 // Resolve our own BSize based on the controls' size in the 301 // same axis. Unless we're size-contained, in which case we 302 // have to behave as if we have an intrinsic size of 0. 303 if (GetContainSizeAxes().mBContained) { 304 contentBoxBSize = 0; 305 } else { 306 contentBoxBSize = kidDesiredSize.BSize(myWM); 307 } 308 } 309 310 FinishReflowChild(child, aPresContext, kidDesiredSize, &kidReflowInput, 311 myWM, childOrigin, containerSize, 312 ReflowChildFlags::Default); 313 314 if (child->GetSize() != oldChildSize) { 315 // We might find non-primary frames in printing due to 316 // ReplicateFixedFrames, but we don't care about that. 317 MOZ_ASSERT(child->IsPrimaryFrame() || 318 PresContext()->IsPrintingOrPrintPreview(), 319 "We only look at the primary frame in ReflowFinished"); 320 if (!mReflowCallbackPosted) { 321 mReflowCallbackPosted = true; 322 PresShell()->PostReflowCallback(this); 323 } 324 } 325 } else { 326 // We expect a placeholder for ::backdrop if we're fullscreen. 327 MOZ_ASSERT(child->IsPlaceholderFrame()); 328 } 329 } 330 331 if (isBSizeShrinkWrapping) { 332 if (contentBoxBSize == NS_UNCONSTRAINEDSIZE) { 333 // We didn't get a BSize from our intrinsic size/ratio, nor did we 334 // get one from our controls. Just use BSize of 0. 335 contentBoxBSize = 0; 336 } 337 contentBoxBSize = aReflowInput.ApplyMinMaxBSize(contentBoxBSize); 338 borderBoxBSize = contentBoxBSize + logicalBP.BStartEnd(myWM); 339 } 340 341 LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize); 342 aMetrics.SetSize(myWM, logicalDesiredSize); 343 344 aMetrics.SetOverflowAreasToDesiredBounds(); 345 if (HasVideoElement()) { 346 aMetrics.mOverflowAreas.UnionAllWith( 347 GetDestRect(aReflowInput.ComputedPhysicalContentBoxRelativeToSelf())); 348 } 349 350 FinishAndStoreOverflow(&aMetrics); 351 352 NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("exit nsVideoFrame::Reflow: size=%d,%d", 353 aMetrics.Width(), aMetrics.Height())); 354 355 MOZ_ASSERT(aStatus.IsEmpty(), "This type of frame can't be split."); 356 } 357 358 #ifdef ACCESSIBILITY 359 a11y::AccType nsVideoFrame::AccessibleType() { return a11y::eHTMLMediaType; } 360 #endif 361 362 #ifdef DEBUG_FRAME_DUMP 363 nsresult nsVideoFrame::GetFrameName(nsAString& aResult) const { 364 return MakeFrameName(u"HTMLVideo"_ns, aResult); 365 } 366 #endif 367 368 nsIFrame::SizeComputationResult nsVideoFrame::ComputeSize( 369 const SizeComputationInput& aSizingInput, WritingMode aWM, 370 const LogicalSize& aCBSize, nscoord aAvailableISize, 371 const LogicalSize& aMargin, const LogicalSize& aBorderPadding, 372 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { 373 if (!HasVideoElement()) { 374 return nsContainerFrame::ComputeSize( 375 aSizingInput, aWM, aCBSize, aAvailableISize, aMargin, aBorderPadding, 376 aSizeOverrides, aFlags); 377 } 378 379 return {ComputeSizeWithIntrinsicDimensions( 380 aSizingInput.mRenderingContext, aWM, GetIntrinsicSize(), 381 GetAspectRatio(), aCBSize, aMargin, aBorderPadding, 382 aSizeOverrides, aFlags), 383 AspectRatioUsage::None}; 384 } 385 386 nscoord nsVideoFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, 387 IntrinsicISizeType aType) { 388 // <audio> / <video> has the same min / pref ISize. 389 return GetIntrinsicSize().ISize(GetWritingMode()).valueOr(0); 390 } 391 392 Maybe<nsSize> nsVideoFrame::PosterImageSize() const { 393 // Use the poster image frame's size. 394 nsIFrame* child = GetPosterImage()->GetPrimaryFrame(); 395 return child->GetIntrinsicSize().ToSize(); 396 } 397 398 AspectRatio nsVideoFrame::GetIntrinsicRatio() const { 399 return GetIntrinsicRatio(/* aIgnoreContainment = */ false); 400 } 401 402 AspectRatio nsVideoFrame::GetIntrinsicRatio(bool aIgnoreContainment) const { 403 if (!HasVideoElement()) { 404 // Audio elements have no intrinsic ratio. 405 return AspectRatio(); 406 } 407 408 // 'contain:[inline-]size' replaced elements have no intrinsic ratio. 409 if (!aIgnoreContainment && GetContainSizeAxes().IsAny()) { 410 return AspectRatio(); 411 } 412 413 auto* element = static_cast<HTMLVideoElement*>(GetContent()); 414 if (Maybe<CSSIntSize> size = element->GetVideoSize()) { 415 return AspectRatio::FromSize(*size); 416 } 417 418 if (ShouldDisplayPoster()) { 419 if (Maybe<nsSize> imgSize = PosterImageSize()) { 420 return AspectRatio::FromSize(*imgSize); 421 } 422 } 423 424 if (StylePosition()->mAspectRatio.HasRatio()) { 425 return AspectRatio(); 426 } 427 428 return AspectRatio::FromSize(kFallbackIntrinsicSizeInPixels); 429 } 430 431 bool nsVideoFrame::ShouldDisplayPoster() const { 432 if (!HasVideoElement()) { 433 return false; 434 } 435 436 auto* element = static_cast<HTMLVideoElement*>(GetContent()); 437 if (element->GetPlayedOrSeeked() && HasVideoData()) { 438 return false; 439 } 440 441 nsCOMPtr<nsIImageLoadingContent> imgContent = do_QueryInterface(mPosterImage); 442 NS_ENSURE_TRUE(imgContent, false); 443 444 nsCOMPtr<imgIRequest> request; 445 nsresult res = imgContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, 446 getter_AddRefs(request)); 447 if (NS_FAILED(res) || !request) { 448 return false; 449 } 450 451 uint32_t status = 0; 452 res = request->GetImageStatus(&status); 453 if (NS_FAILED(res) || (status & imgIRequest::STATUS_ERROR)) { 454 return false; 455 } 456 457 return true; 458 } 459 460 IntrinsicSize nsVideoFrame::GetIntrinsicSize(bool aIgnoreContainment) const { 461 const auto containAxes = 462 aIgnoreContainment ? ContainSizeAxes(false, false) : GetContainSizeAxes(); 463 const auto isVideo = HasVideoElement(); 464 // Intrinsic size will be given by contain-intrinsic-size if the element is 465 // size-contained. If both axes have containment, FinishIntrinsicSize() will 466 // ignore the fallback size argument, so we can just pass no intrinsic size, 467 // or whatever. 468 if (containAxes.IsBoth()) { 469 return FinishIntrinsicSize(containAxes, {}); 470 } 471 472 if (!isVideo) { 473 // An audio element with no "controls" attribute, distinguished by the last 474 // and only child being the control, falls back to no intrinsic size. 475 if (!mFrames.LastChild()) { 476 return FinishIntrinsicSize(containAxes, {}); 477 } 478 479 return FinishIntrinsicSize(containAxes, 480 IntrinsicSize(kFallbackIntrinsicSize)); 481 } 482 483 auto* element = static_cast<HTMLVideoElement*>(GetContent()); 484 if (Maybe<CSSIntSize> size = element->GetVideoSize()) { 485 return FinishIntrinsicSize(containAxes, 486 IntrinsicSize(CSSPixel::ToAppUnits(*size))); 487 } 488 489 if (ShouldDisplayPoster()) { 490 if (Maybe<nsSize> imgSize = PosterImageSize()) { 491 return FinishIntrinsicSize(containAxes, IntrinsicSize(*imgSize)); 492 } 493 } 494 495 if (StylePosition()->mAspectRatio.HasRatio()) { 496 return {}; 497 } 498 499 return FinishIntrinsicSize(containAxes, 500 IntrinsicSize(kFallbackIntrinsicSize)); 501 } 502 503 IntrinsicSize nsVideoFrame::GetIntrinsicSize() { 504 return GetIntrinsicSize(/* aIgnoreContainment = */ false); 505 } 506 507 void nsVideoFrame::UpdatePosterSource(bool aNotify) { 508 NS_ASSERTION(HasVideoElement(), "Only call this on <video> elements."); 509 HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent()); 510 511 if (element->HasAttr(nsGkAtoms::poster) && 512 !element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::poster, 513 nsGkAtoms::_empty, eIgnoreCase)) { 514 nsAutoString posterStr; 515 element->GetPoster(posterStr); 516 mPosterImage->SetAttr(kNameSpaceID_None, nsGkAtoms::src, posterStr, 517 aNotify); 518 } else { 519 mPosterImage->UnsetAttr(kNameSpaceID_None, nsGkAtoms::src, aNotify); 520 } 521 } 522 523 nsresult nsVideoFrame::AttributeChanged(int32_t aNameSpaceID, 524 nsAtom* aAttribute, 525 AttrModType aModType) { 526 if (aAttribute == nsGkAtoms::poster && HasVideoElement()) { 527 UpdatePosterSource(true); 528 } 529 return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); 530 } 531 532 void nsVideoFrame::OnVisibilityChange( 533 Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) { 534 if (HasVideoElement()) { 535 static_cast<HTMLMediaElement*>(GetContent()) 536 ->OnVisibilityChange(aNewVisibility); 537 } 538 539 nsCOMPtr<nsIImageLoadingContent> imageLoader = 540 do_QueryInterface(mPosterImage); 541 if (imageLoader) { 542 imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction); 543 } 544 545 nsContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction); 546 } 547 548 bool nsVideoFrame::HasVideoData() const { 549 if (!HasVideoElement()) { 550 return false; 551 } 552 auto* element = static_cast<HTMLVideoElement*>(GetContent()); 553 return element->GetVideoSize().isSome(); 554 } 555 556 void nsVideoFrame::UpdateTextTrack() { 557 static_cast<HTMLMediaElement*>(GetContent())->NotifyCueDisplayStatesChanged(); 558 } 559 560 namespace mozilla { 561 562 class nsDisplayVideo final : public nsPaintedDisplayItem { 563 public: 564 nsDisplayVideo(nsDisplayListBuilder* aBuilder, nsVideoFrame* aFrame) 565 : nsPaintedDisplayItem(aBuilder, aFrame) { 566 MOZ_COUNT_CTOR(nsDisplayVideo); 567 } 568 569 MOZ_COUNTED_DTOR_FINAL(nsDisplayVideo) 570 571 NS_DISPLAY_DECL_NAME("Video", TYPE_VIDEO) 572 573 already_AddRefed<ImageContainer> GetImageContainer(gfxRect& aDestGFXRect) { 574 auto* f = static_cast<nsVideoFrame*>(Frame()); 575 nsRect area = f->InkOverflowRectRelativeToSelf() + ToReferenceFrame(); 576 auto* element = static_cast<HTMLVideoElement*>(f->GetContent()); 577 578 Maybe<CSSIntSize> videoSizeInPx = element->GetVideoSize(); 579 if (videoSizeInPx.isNothing() || area.IsEmpty()) { 580 return nullptr; 581 } 582 583 RefPtr<ImageContainer> container = element->GetImageContainer(); 584 if (!container) { 585 return nullptr; 586 } 587 588 // Retrieve the size of the decoded video frame, before being scaled 589 // by pixel aspect ratio. 590 mozilla::gfx::IntSize frameSize = container->GetCurrentSize(); 591 if (frameSize.width == 0 || frameSize.height == 0) { 592 // No image, or zero-sized image. Don't render. 593 return nullptr; 594 } 595 596 nsRect dest = 597 f->GetDestRect(f->GetContentRectRelativeToSelf() + ToReferenceFrame()); 598 aDestGFXRect = f->PresContext()->AppUnitsToGfxUnits(dest); 599 aDestGFXRect.Round(); 600 if (aDestGFXRect.IsEmpty()) { 601 return nullptr; 602 } 603 604 return container.forget(); 605 } 606 607 bool CreateWebRenderCommands( 608 mozilla::wr::DisplayListBuilder& aBuilder, 609 mozilla::wr::IpcResourceUpdateQueue& aResources, 610 const mozilla::layers::StackingContextHelper& aSc, 611 mozilla::layers::RenderRootStateManager* aManager, 612 nsDisplayListBuilder* aDisplayListBuilder) override { 613 HTMLVideoElement* element = 614 static_cast<HTMLVideoElement*>(Frame()->GetContent()); 615 gfxRect destGFXRect; 616 RefPtr<ImageContainer> container = GetImageContainer(destGFXRect); 617 if (!container) { 618 return true; 619 } 620 621 container->SetRotation(element->RotationDegrees()); 622 623 // If the image container is empty, we don't want to fallback. Any other 624 // failure will be due to resource constraints and fallback is unlikely to 625 // help us. Hence we can ignore the return value from PushImage. 626 LayoutDeviceRect rect(destGFXRect.x, destGFXRect.y, destGFXRect.width, 627 destGFXRect.height); 628 aManager->CommandBuilder().PushImage(this, container, aBuilder, aResources, 629 aSc, rect, rect); 630 return true; 631 } 632 633 // For opaque videos, we will want to override GetOpaqueRegion here. 634 // This is tracked by bug 1545498. 635 636 nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override { 637 *aSnap = true; 638 nsIFrame* f = Frame(); 639 return f->InkOverflowRectRelativeToSelf() + ToReferenceFrame(); 640 } 641 642 // Only report FirstContentfulPaint when the video is set 643 bool IsContentful() const override { 644 nsVideoFrame* f = static_cast<nsVideoFrame*>(Frame()); 645 HTMLVideoElement* video = HTMLVideoElement::FromNode(f->GetContent()); 646 return video->VideoWidth() > 0; 647 } 648 649 void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override { 650 HTMLVideoElement* element = 651 static_cast<HTMLVideoElement*>(Frame()->GetContent()); 652 gfxRect destGFXRect; 653 RefPtr<ImageContainer> container = GetImageContainer(destGFXRect); 654 if (!container) { 655 return; 656 } 657 658 VideoRotation rotationDeg = element->RotationDegrees(); 659 Matrix preTransform = ComputeRotationMatrix( 660 destGFXRect.Width(), destGFXRect.Height(), rotationDeg); 661 Matrix transform = 662 preTransform * Matrix::Translation(destGFXRect.x, destGFXRect.y); 663 664 AutoLockImage autoLock(container); 665 Image* image = autoLock.GetImage(TimeStamp::Now()); 666 if (!image) { 667 return; 668 } 669 RefPtr<gfx::SourceSurface> surface = image->GetAsSourceSurface(); 670 if (!surface || !surface->IsValid()) { 671 return; 672 } 673 gfx::IntSize size = surface->GetSize(); 674 675 IntSize scaleToSize(static_cast<int32_t>(destGFXRect.Width()), 676 static_cast<int32_t>(destGFXRect.Height())); 677 // scaleHint is set regardless of rotation, so swap w/h if needed. 678 SwapScaleWidthHeightForRotation(scaleToSize, rotationDeg); 679 transform.PreScale(scaleToSize.width / double(size.Width()), 680 scaleToSize.height / double(size.Height())); 681 682 gfxContextMatrixAutoSaveRestore saveMatrix(aCtx); 683 aCtx->SetMatrix( 684 gfxUtils::SnapTransformTranslation(aCtx->CurrentMatrix(), nullptr)); 685 686 transform = gfxUtils::SnapTransform( 687 transform, gfxRect(0, 0, size.width, size.height), nullptr); 688 aCtx->Multiply(ThebesMatrix(transform)); 689 690 aCtx->GetDrawTarget()->FillRect( 691 Rect(0, 0, size.width, size.height), 692 SurfacePattern(surface, ExtendMode::CLAMP, Matrix(), 693 nsLayoutUtils::GetSamplingFilterForFrame(Frame())), 694 DrawOptions()); 695 } 696 }; 697 698 } // namespace mozilla 699 700 void nsVideoFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 701 const nsDisplayListSet& aLists) { 702 if (!IsVisibleForPainting()) { 703 return; 704 } 705 706 DO_GLOBAL_REFLOW_COUNT_DSP("nsVideoFrame"); 707 708 DisplayBorderBackgroundOutline(aBuilder, aLists); 709 710 if (HidesContent()) { 711 return; 712 } 713 714 const bool shouldDisplayPoster = ShouldDisplayPoster(); 715 716 DisplayListClipState::AutoSaveRestore clipState(aBuilder); 717 auto clipAxes = ShouldApplyOverflowClipping(StyleDisplay()); 718 if (!clipAxes.isEmpty()) { 719 nsRect clipRect; 720 nsRectCornerRadii radii; 721 const bool haveRadii = 722 ComputeOverflowClipRectRelativeToSelf(clipAxes, clipRect, radii); 723 // NOTE: If we're displaying a poster image (instead of video data), we can 724 // trust the nsImageFrame to constrain its drawing to its content rect 725 // (which happens to be the same as our content rect). 726 const bool canOverflowWithoutRadii = 727 !shouldDisplayPoster && 728 nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition()); 729 if (haveRadii || canOverflowWithoutRadii) { 730 clipState.ClipContainingBlockDescendants( 731 clipRect + aBuilder->ToReferenceFrame(this), 732 haveRadii ? &radii : nullptr); 733 } 734 } 735 736 if (HasVideoElement() && !shouldDisplayPoster) { 737 aLists.Content()->AppendNewToTop<nsDisplayVideo>(aBuilder, this); 738 } 739 740 // Add child frames to display list. We expect various children, 741 // but only want to draw mPosterImage conditionally. Others we 742 // always add to the display list. 743 for (nsIFrame* child : mFrames) { 744 if (child->GetContent() != mPosterImage || shouldDisplayPoster) { 745 nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild( 746 aBuilder, child, 747 aBuilder->GetVisibleRect() - child->GetOffsetTo(this), 748 aBuilder->GetDirtyRect() - child->GetOffsetTo(this)); 749 750 child->BuildDisplayListForStackingContext(aBuilder, aLists.Content()); 751 } 752 } 753 }