tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 9183dbde2d07549a3cea6b9f2203db1637126dfc
parent fb71d00185798af0def5c049947c2605f283e76d
Author: Emily McDonough <emcdonough@mozilla.com>
Date:   Mon, 29 Dec 2025 21:11:03 +0000

Bug 1650176 - During static clone, copy the lastest video frame when cloning HTMLMediaElement r=jwatt,pehrsons

This also adds a Mozilla-specific WPT print test.

Differential Revision: https://phabricator.services.mozilla.com/D275132

Diffstat:
Mdom/media/mediaelement/HTMLVideoElement.cpp | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdom/media/mediaelement/HTMLVideoElement.h | 12++++++++++++
Atesting/web-platform/mozilla/meta/printing/video-001-print.html.ini | 2++
Atesting/web-platform/mozilla/meta/printing/video-002-print.html.ini | 2++
Atesting/web-platform/mozilla/meta/printing/video-003-print.html.ini | 4++++
Atesting/web-platform/mozilla/meta/printing/video-004-print.html.ini | 4++++
Atesting/web-platform/mozilla/meta/printing/video-005-print.html.ini | 2++
Atesting/web-platform/mozilla/tests/printing/reference/teal-16x16-masked.html | 13+++++++++++++
Atesting/web-platform/mozilla/tests/printing/support/16x16-teal-av1.mp4 | 0
Atesting/web-platform/mozilla/tests/printing/support/16x16-teal-h264.mp4 | 0
Atesting/web-platform/mozilla/tests/printing/support/16x16-teal-hevc.mp4 | 0
Atesting/web-platform/mozilla/tests/printing/support/16x16-teal-vp8.webm | 0
Atesting/web-platform/mozilla/tests/printing/support/16x16-teal-vp9.webm | 0
Atesting/web-platform/mozilla/tests/printing/video-001-print.html | 35+++++++++++++++++++++++++++++++++++
Atesting/web-platform/mozilla/tests/printing/video-002-print.html | 35+++++++++++++++++++++++++++++++++++
Atesting/web-platform/mozilla/tests/printing/video-003-print.html | 35+++++++++++++++++++++++++++++++++++
Atesting/web-platform/mozilla/tests/printing/video-004-print.html | 35+++++++++++++++++++++++++++++++++++
Atesting/web-platform/mozilla/tests/printing/video-005-print.html | 35+++++++++++++++++++++++++++++++++++
18 files changed, 299 insertions(+), 0 deletions(-)

diff --git a/dom/media/mediaelement/HTMLVideoElement.cpp b/dom/media/mediaelement/HTMLVideoElement.cpp @@ -30,6 +30,7 @@ #include "mozilla/dom/VideoStreamTrack.h" #include "mozilla/dom/WakeLock.h" #include "mozilla/dom/power/PowerManagerService.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" #include "nsError.h" #include "nsGenericHTMLElement.h" #include "nsGkAtoms.h" @@ -74,6 +75,64 @@ nsresult HTMLVideoElement::Clone(mozilla::dom::NodeInfo* aNodeInfo, return rv; } +nsresult HTMLVideoElement::CopyInnerTo(Element* aDest) { + nsresult rv = HTMLMediaElement::CopyInnerTo(aDest); + NS_ENSURE_SUCCESS(rv, rv); + HTMLVideoElement* dest = static_cast<HTMLVideoElement*>(aDest); + + // Cloning into a static document indicates we are creating a clone for + // printing purposes only. + // + // If we are making a clone for printing, also clone a frame of video. + // Avoid using GetVideoFrameContainer on this object, because that may + // create a new video container for no reason. + if (aDest->OwnerDoc()->IsStaticDocument() && mVideoFrameContainer) { + // We can expect the source video to have frames, unless the decoder + // has been suspended. When that happens, all frames are cleared. + // Otherwise, even if the media is not seekable, once a frame is + // decoded there should always be images available. + // + // When the decoder is suspended, we will only get fake frames. + // This situation is similar to when JS needs a frame to use in, + // eg., nsLayoutUtils::SurfaceFromElement() via drawImage(). + // + // TODO: As an alternative, we could asynchronously resume decoding + // and dispatch an event to copy an image when that succeeds. + // + // See bug 1295921 for synchronous decoding support: + // https://bugzilla.mozilla.org/show_bug.cgi?id=1295921#c208 + AutoTArray<ImageContainer::OwningImage, 10> images; + mVideoFrameContainer->GetImageContainer()->GetCurrentImages(&images); + if (images.IsEmpty()) { + LOG("no video images, printing with a suspended video decoder?"); + return rv; + } + + // Ask for the video frame container only after we know we have a current + // image from the source. + // GetVideoFrameContainer might create a new video frame container, + // which would be pointless if we won't be able to create any video data. + VideoFrameContainer* const dstVideo = dest->GetVideoFrameContainer(); + NS_ENSURE_TRUE(dstVideo, rv); + + // Make a copy of the first image. This ensures we don't hold onto any + // output buffers from a decoder while print preview is open. + RefPtr<gfx::DataSourceSurface> dstSurface(CopyImage(images[0].mImage)); + if (!dstSurface) { + MOZ_LOG(gMediaElementLog, LogLevel::Error, + ("failed to copy video image")); + return rv; + } + RefPtr<layers::SourceSurfaceImage> dstImage = + MakeAndAddRef<layers::SourceSurfaceImage>(dstSurface.get()); + + dstVideo->SetCurrentFrame(dstImage->GetSize(), dstImage, TimeStamp(), + media::TimeUnit::Invalid(), + media::TimeUnit::Invalid()); + } + return rv; +} + NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLVideoElement, HTMLMediaElement) @@ -493,6 +552,32 @@ bool HTMLVideoElement::SetVisualCloneSource( } /* static */ +already_AddRefed<gfx::DataSourceSurface> HTMLVideoElement::CopyImage( + layers::Image* aImage) { + RefPtr<gfx::SourceSurface> surface = aImage->GetAsSourceSurface(); + if (!surface) { + return nullptr; + } + + RefPtr<gfx::DataSourceSurface> data = surface->GetDataSurface(); + if (!data) { + return nullptr; + } + + gfx::DataSourceSurface::ScopedMap read(data, gfx::DataSourceSurface::READ); + if (!read.IsMapped()) { + return nullptr; + } + + RefPtr<gfx::DataSourceSurface> copy = gfx::CreateDataSourceSurfaceFromData( + data->GetSize(), data->GetFormat(), read.GetData(), read.GetStride()); + + MOZ_ASSERT_IF(copy, data->GetSize() == copy->GetSize()); + MOZ_ASSERT_IF(copy, data->GetFormat() == copy->GetFormat()); + return copy.forget(); +} + +/* static */ bool HTMLVideoElement::IsVideoStatsEnabled() { return StaticPrefs::media_video_stats_enabled(); } diff --git a/dom/media/mediaelement/HTMLVideoElement.h b/dom/media/mediaelement/HTMLVideoElement.h @@ -18,6 +18,10 @@ namespace mozilla { class FrameStatistics; +namespace gfx { +class DataSourceSurface; +} + namespace dom { class WakeLock; @@ -56,6 +60,8 @@ class HTMLVideoElement final : public HTMLMediaElement { nsresult Clone(NodeInfo*, nsINode** aResult) const override; + nsresult CopyInnerTo(Element* aDest); + void UnbindFromTree(UnbindContext&) override; mozilla::Maybe<mozilla::CSSIntSize> GetVideoSize() const; @@ -167,6 +173,12 @@ class HTMLVideoElement final : public HTMLMediaElement { RefPtr<Promise> aVisualCloneTargetPromise = nullptr); bool SetVisualCloneSource(RefPtr<HTMLVideoElement> aVisualCloneSource); + // Creates a new DataSourceSurface with data copied from aImage. + // This is used when creating a static clone of this element, to ensure we + // do not hold onto an output buffer from a decoder. + static already_AddRefed<gfx::DataSourceSurface> CopyImage( + layers::Image* aImage); + // For video elements, we can clone the frames being played to // a secondary video element. If we're doing that, we hold a // reference to the video element we're cloning to in diff --git a/testing/web-platform/mozilla/meta/printing/video-001-print.html.ini b/testing/web-platform/mozilla/meta/printing/video-001-print.html.ini @@ -0,0 +1,2 @@ +[video-001-print.html] + fuzzy: maxDifference=0-20;totalPixels=0-200 diff --git a/testing/web-platform/mozilla/meta/printing/video-002-print.html.ini b/testing/web-platform/mozilla/meta/printing/video-002-print.html.ini @@ -0,0 +1,2 @@ +[video-002-print.html] + fuzzy: maxDifference=0-20;totalPixels=0-200 diff --git a/testing/web-platform/mozilla/meta/printing/video-003-print.html.ini b/testing/web-platform/mozilla/meta/printing/video-003-print.html.ini @@ -0,0 +1,4 @@ +[video-003-print.html] + fuzzy: maxDifference=0-20;totalPixels=0-200 + expected: + if (os == "win"): [PASS, FAIL] diff --git a/testing/web-platform/mozilla/meta/printing/video-004-print.html.ini b/testing/web-platform/mozilla/meta/printing/video-004-print.html.ini @@ -0,0 +1,4 @@ +[video-004-print.html] + fuzzy: maxDifference=0-20;totalPixels=0-200 + expected: + if (os == "win"): [PASS, FAIL] diff --git a/testing/web-platform/mozilla/meta/printing/video-005-print.html.ini b/testing/web-platform/mozilla/meta/printing/video-005-print.html.ini @@ -0,0 +1,2 @@ +[video-005-print.html] + fuzzy: maxDifference=0-20;totalPixels=0-200 diff --git a/testing/web-platform/mozilla/tests/printing/reference/teal-16x16-masked.html b/testing/web-platform/mozilla/tests/printing/reference/teal-16x16-masked.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<link rel="author" title="Emily McDonough" href="mailto:emcdonough@mozilla.com"> +<style> +*{margin: 0; padding: 0;} +div{ + margin: 4px; + width: 14px; + height: 14px; + border: teal 2px solid; + background: teal; +} +</style> +<div></div> diff --git a/testing/web-platform/mozilla/tests/printing/support/16x16-teal-av1.mp4 b/testing/web-platform/mozilla/tests/printing/support/16x16-teal-av1.mp4 Binary files differ. diff --git a/testing/web-platform/mozilla/tests/printing/support/16x16-teal-h264.mp4 b/testing/web-platform/mozilla/tests/printing/support/16x16-teal-h264.mp4 Binary files differ. diff --git a/testing/web-platform/mozilla/tests/printing/support/16x16-teal-hevc.mp4 b/testing/web-platform/mozilla/tests/printing/support/16x16-teal-hevc.mp4 Binary files differ. diff --git a/testing/web-platform/mozilla/tests/printing/support/16x16-teal-vp8.webm b/testing/web-platform/mozilla/tests/printing/support/16x16-teal-vp8.webm Binary files differ. diff --git a/testing/web-platform/mozilla/tests/printing/support/16x16-teal-vp9.webm b/testing/web-platform/mozilla/tests/printing/support/16x16-teal-vp9.webm Binary files differ. diff --git a/testing/web-platform/mozilla/tests/printing/video-001-print.html b/testing/web-platform/mozilla/tests/printing/video-001-print.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<link rel="author" title="Emily McDonough" href="mailto:emcdonough@mozilla.com"> +<link rel="match" href="reference/teal-16x16-masked.html"> +<style> +*{margin: 0; padding: 0;} +div{ + margin: 5px; + width: 16px; + height: 16px; + background: hotpink; +} +#mask{ + margin: 0; + position: absolute; + top: 4px; + left: 4px; + width: 14px; + height: 14px; + border: teal 2px solid; + background: initial; +} +</style> +<script> +function ready(){ + document.documentElement.removeAttribute("class"); +} +</script> +<!-- Mask to hide the edges of the video and try to reduce differences + caused by blurriness in PDF output. --> +<div id="mask"></div> +<div> + <video preload="auto" onloadeddata="ready()" onerror="ready()" src="support/16x16-teal-vp8.webm"></video> +</div> +</html> diff --git a/testing/web-platform/mozilla/tests/printing/video-002-print.html b/testing/web-platform/mozilla/tests/printing/video-002-print.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<link rel="author" title="Emily McDonough" href="mailto:emcdonough@mozilla.com"> +<link rel="match" href="reference/teal-16x16-masked.html"> +<style> +*{margin: 0; padding: 0;} +div{ + margin: 5px; + width: 16px; + height: 16px; + background: hotpink; +} +#mask{ + margin: 0; + position: absolute; + top: 4px; + left: 4px; + width: 14px; + height: 14px; + border: teal 2px solid; + background: initial; +} +</style> +<script> +function ready(){ + document.documentElement.removeAttribute("class"); +} +</script> +<!-- Mask to hide the edges of the video and try to reduce differences + caused by blurriness in PDF output. --> +<div id="mask"></div> +<div> + <video preload="auto" onloadeddata="ready()" onerror="ready()" src="support/16x16-teal-vp9.webm"></video> +</div> +</html> diff --git a/testing/web-platform/mozilla/tests/printing/video-003-print.html b/testing/web-platform/mozilla/tests/printing/video-003-print.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<link rel="author" title="Emily McDonough" href="mailto:emcdonough@mozilla.com"> +<link rel="match" href="reference/teal-16x16-masked.html"> +<style> +*{margin: 0; padding: 0;} +div{ + margin: 5px; + width: 16px; + height: 16px; + background: hotpink; +} +#mask{ + margin: 0; + position: absolute; + top: 4px; + left: 4px; + width: 14px; + height: 14px; + border: teal 2px solid; + background: initial; +} +</style> +<script> +function ready(){ + document.documentElement.removeAttribute("class"); +} +</script> +<!-- Mask to hide the edges of the video and try to reduce differences + caused by blurriness in PDF output. --> +<div id="mask"></div> +<div> + <video preload="auto" onloadeddata="ready()" onerror="ready()" src="support/16x16-teal-h264.mp4"></video> +</div> +</html> diff --git a/testing/web-platform/mozilla/tests/printing/video-004-print.html b/testing/web-platform/mozilla/tests/printing/video-004-print.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<link rel="author" title="Emily McDonough" href="mailto:emcdonough@mozilla.com"> +<link rel="match" href="reference/teal-16x16-masked.html"> +<style> +*{margin: 0; padding: 0;} +div{ + margin: 5px; + width: 16px; + height: 16px; + background: hotpink; +} +#mask{ + margin: 0; + position: absolute; + top: 4px; + left: 4px; + width: 14px; + height: 14px; + border: teal 2px solid; + background: initial; +} +</style> +<script> +function ready(){ + document.documentElement.removeAttribute("class"); +} +</script> +<!-- Mask to hide the edges of the video and try to reduce differences + caused by blurriness in PDF output. --> +<div id="mask"></div> +<div> + <video preload="auto" onloadeddata="ready()" onerror="ready()" src="support/16x16-teal-hevc.mp4"></video> +</div> +</html> diff --git a/testing/web-platform/mozilla/tests/printing/video-005-print.html b/testing/web-platform/mozilla/tests/printing/video-005-print.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<link rel="author" title="Emily McDonough" href="mailto:emcdonough@mozilla.com"> +<link rel="match" href="reference/teal-16x16-masked.html"> +<style> +*{margin: 0; padding: 0;} +div{ + margin: 5px; + width: 16px; + height: 16px; + background: hotpink; +} +#mask{ + margin: 0; + position: absolute; + top: 4px; + left: 4px; + width: 14px; + height: 14px; + border: teal 2px solid; + background: initial; +} +</style> +<script> +function ready(){ + document.documentElement.removeAttribute("class"); +} +</script> +<!-- Mask to hide the edges of the video and try to reduce differences + caused by blurriness in PDF output. --> +<div id="mask"></div> +<div> + <video preload="auto" onloadeddata="ready()" onerror="ready()" src="support/16x16-teal-av1.mp4"></video> +</div> +</html>