commit 0ab5fc76883d63d41db23ae49916382fe81f1313
parent 747e38c2bb65bd0908f645822041f5ffc419868b
Author: longsonr <longsonr@gmail.com>
Date: Wed, 19 Nov 2025 13:51:33 +0000
Bug 1999989 Part 3 - SVG media fragment support r=birtles
From https://svgwg.org/svg2-draft/linking.html#SVGFragmentIdentifiers
- If the SVG fragment identifier addresses a space segment (e.g., MyDrawing.svg#xywh=0,0,100,100),then the initial view into the SVG document is established using the view specification attributes on the outermost svg element where the 'viewBox' is overriden by the x, y, width and height values provided by the fragment identifier.
- If the SVG fragment identifier addresses a time segment (e.g., MyDrawing.svg#t=10),then the initial view into the SVG document is established as if no fragment identifier was provided. The rendering of the SVG Document shall be as if the setCurrentTime method on the SVG Document element had been called with the begin time value from the fragment identifier. Additionally, if an end time value is provided in the fragment identifier, the effect is equivalent to calling the pauseAnimations method on the SVG Document when the document time reaches the end time of the fragment identifier.
Differential Revision: https://phabricator.services.mozilla.com/D272474
Diffstat:
9 files changed, 141 insertions(+), 11 deletions(-)
diff --git a/dom/smil/SMILTimeContainer.cpp b/dom/smil/SMILTimeContainer.cpp
@@ -85,6 +85,10 @@ void SMILTimeContainer::Pause(uint32_t aType) {
}
}
+void SMILTimeContainer::PauseAt(SMILTime aTime) {
+ mPauseTime = Some(std::max<SMILTime>(0, aTime));
+}
+
void SMILTimeContainer::Resume(uint32_t aType) {
if (!mPauseState) return;
@@ -162,8 +166,11 @@ void SMILTimeContainer::Sample() {
UpdateCurrentTime();
DoSample();
-
mNeedsPauseSample = false;
+
+ if (mPauseTime && mCurrentTime >= mPauseTime.value()) {
+ Pause(PAUSE_SCRIPT);
+ }
}
nsresult SMILTimeContainer::SetParent(SMILTimeContainer* aParent) {
diff --git a/dom/smil/SMILTimeContainer.h b/dom/smil/SMILTimeContainer.h
@@ -54,6 +54,12 @@ class SMILTimeContainer {
virtual void Pause(uint32_t aType);
/*
+ * Pause this time container when it reaches the specified time.
+ *
+ */
+ void PauseAt(SMILTime aTime);
+
+ /*
* Resume this time container
*
* param @aType The source of the resume request. Clears the pause flag for
@@ -256,6 +262,9 @@ class SMILTimeContainer {
//
SMILTime mParentOffset;
+ // The time the time container will pause when it reaches this point.
+ Maybe<SMILTime> mPauseTime;
+
// The timestamp in parent time when the container was paused
SMILTime mPauseStart;
diff --git a/dom/svg/SVGFragmentIdentifier.cpp b/dom/svg/SVGFragmentIdentifier.cpp
@@ -7,6 +7,7 @@
#include "SVGFragmentIdentifier.h"
#include "SVGAnimatedTransformList.h"
+#include "mozilla/MediaFragmentURIParser.h"
#include "mozilla/SVGOuterSVGFrame.h"
#include "mozilla/dom/SVGSVGElement.h"
#include "mozilla/dom/SVGViewElement.h"
@@ -54,6 +55,12 @@ class MOZ_RAII AutoSVGViewHandler {
mSVGView = MakeUnique<SVGView>();
}
+ void SetViewBox(const gfx::Rect& aRect) {
+ SVGViewBox viewBox(aRect.x, aRect.y, aRect.width, aRect.height);
+ mSVGView->mViewBox.SetBaseValue(viewBox, mRoot, true);
+ mValid = true;
+ }
+
bool ProcessAttr(const nsAString& aToken, const nsAString& aParams) {
MOZ_ASSERT(mSVGView, "CreateSVGView should have been called");
@@ -147,6 +154,48 @@ bool SVGFragmentIdentifier::ProcessSVGViewSpec(const nsAString& aViewSpec,
return true;
}
+static float PxLengthOrFallback(const LengthPercentage& aLenPct,
+ CSSIntCoord aFallback) {
+ if (!aLenPct.IsLength()) {
+ return aFallback;
+ }
+ return aLenPct.AsLength().ToCSSPixels();
+}
+
+bool SVGFragmentIdentifier::ProcessMediaFragment(
+ const nsAString& aMediaFragment, SVGSVGElement* aRoot) {
+ NS_ConvertUTF16toUTF8 mediaFragment(aMediaFragment);
+ MediaFragmentURIParser parser(mediaFragment);
+
+ bool foundMediaFragment = false;
+
+ if (parser.HasStartTime()) {
+ aRoot->SetCurrentTime(parser.GetStartTime());
+ foundMediaFragment = true;
+ }
+ if (parser.HasEndTime()) {
+ // pause animations at end time.
+ aRoot->PauseAnimationsAt(parser.GetEndTime());
+ foundMediaFragment = true;
+ }
+ if (parser.HasClip()) {
+ gfx::Rect rect = IntRectToRect(parser.GetClip());
+ if (parser.GetClipUnit() == eClipUnit_Percent) {
+ float width = PxLengthOrFallback(aRoot->GetIntrinsicWidth(),
+ kFallbackIntrinsicWidthInPixels);
+ float height = PxLengthOrFallback(aRoot->GetIntrinsicHeight(),
+ kFallbackIntrinsicHeightInPixels);
+ rect.Scale(width / 100.0f, height / 100.0f);
+ }
+ AutoSVGViewHandler viewHandler(aRoot);
+ viewHandler.CreateSVGView();
+ viewHandler.SetViewBox(rect);
+ foundMediaFragment = true;
+ }
+
+ return foundMediaFragment;
+}
+
bool SVGFragmentIdentifier::ProcessFragmentIdentifier(
Document* aDocument, const nsAString& aAnchorName) {
MOZ_ASSERT(aDocument->GetRootElement()->IsSVGElement(nsGkAtoms::svg),
@@ -154,10 +203,7 @@ bool SVGFragmentIdentifier::ProcessFragmentIdentifier(
auto* rootElement = SVGSVGElement::FromNode(aDocument->GetRootElement());
- const auto* viewElement =
- SVGViewElement::FromNodeOrNull(aDocument->GetElementById(aAnchorName));
-
- if (viewElement) {
+ if (SVGViewElement::FromNodeOrNull(aDocument->GetElementById(aAnchorName))) {
if (!rootElement->mCurrentViewID) {
rootElement->mCurrentViewID = MakeUnique<nsString>();
}
@@ -174,7 +220,13 @@ bool SVGFragmentIdentifier::ProcessFragmentIdentifier(
return false;
}
- return ProcessSVGViewSpec(aAnchorName, rootElement);
+ if (ProcessSVGViewSpec(aAnchorName, rootElement)) {
+ return true;
+ }
+ if (ProcessMediaFragment(aAnchorName, rootElement)) {
+ return true;
+ }
+ return false;
}
} // namespace mozilla
diff --git a/dom/svg/SVGFragmentIdentifier.h b/dom/svg/SVGFragmentIdentifier.h
@@ -41,6 +41,13 @@ class SVGFragmentIdentifier {
*/
static bool ProcessSVGViewSpec(const nsAString& aViewSpec,
dom::SVGSVGElement* root);
+
+ /**
+ * Parse a media fragment
+ * @return true if there is a valid media fragment.
+ */
+ static bool ProcessMediaFragment(const nsAString& aMediaFragment,
+ dom::SVGSVGElement* root);
};
} // namespace mozilla
diff --git a/dom/svg/SVGSVGElement.cpp b/dom/svg/SVGSVGElement.cpp
@@ -171,6 +171,20 @@ void SVGSVGElement::PauseAnimations() {
// else we're not the outermost <svg> or not bound to a tree, so silently fail
}
+static SMILTime SecondsToSMILTime(float aSeconds) {
+ double milliseconds = double(aSeconds) * PR_MSEC_PER_SEC;
+ // Round to nearest whole number before converting, to avoid precision
+ // errors
+ return SVGUtils::ClampToInt64(NS_round(milliseconds));
+}
+
+void SVGSVGElement::PauseAnimationsAt(float aSeconds) {
+ if (mTimedDocumentRoot) {
+ mTimedDocumentRoot->PauseAt(SecondsToSMILTime(aSeconds));
+ }
+ // else we're not the outermost <svg> or not bound to a tree, so silently fail
+}
+
void SVGSVGElement::UnpauseAnimations() {
if (mTimedDocumentRoot) {
mTimedDocumentRoot->Resume(SMILTimeContainer::PAUSE_SCRIPT);
@@ -205,11 +219,7 @@ void SVGSVGElement::SetCurrentTime(float seconds) {
return;
}
FlushAnimations();
- double fMilliseconds = double(seconds) * PR_MSEC_PER_SEC;
- // Round to nearest whole number before converting, to avoid precision
- // errors
- SMILTime lMilliseconds = SVGUtils::ClampToInt64(NS_round(fMilliseconds));
- mTimedDocumentRoot->SetCurrentTime(lMilliseconds);
+ mTimedDocumentRoot->SetCurrentTime(SecondsToSMILTime(seconds));
AnimationNeedsResample();
// Trigger synchronous sample now, to:
// - Make sure we get an up-to-date paint after this method
diff --git a/dom/svg/SVGSVGElement.h b/dom/svg/SVGSVGElement.h
@@ -104,6 +104,7 @@ class SVGSVGElement final : public SVGSVGElementBase {
void UnsuspendRedrawAll();
void ForceRedraw();
void PauseAnimations();
+ void PauseAnimationsAt(float seconds);
void UnpauseAnimations();
bool AnimationsPaused();
float GetCurrentTimeAsFloat();
diff --git a/testing/web-platform/tests/svg/animations/media-fragment-override-animation.html b/testing/web-platform/tests/svg/animations/media-fragment-override-animation.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>animated background-image at a particular time</title>
+<link rel="help" href="https://svgwg.org/svg2-draft/linking.html#LinksIntoSVG">
+<link rel="match" href="../struct/reftests/reference/green-100x100.html">
+<style>
+ .one {
+ width: 100px;
+ height: 100px;
+ background-image: url("support/green-at-3s.svg#t=npt:3,3.01");
+ }
+</style>
+<div style="position: relative">
+ <div class="one"></div>
+</div>
diff --git a/testing/web-platform/tests/svg/animations/support/green-at-3s.svg b/testing/web-platform/tests/svg/animations/support/green-at-3s.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
+ <rect width="100" height="100" fill="red">
+ <set attributeName="fill" to="green" begin="3s" dur="0.5s" />
+ </rect>
+</svg>
diff --git a/testing/web-platform/tests/svg/linking/reftests/media-fragment-override-multiple.html b/testing/web-platform/tests/svg/linking/reftests/media-fragment-override-multiple.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>background-image with multiple layers using different views of the same image</title>
+<link rel="help" href="https://svgwg.org/svg2-draft/linking.html#LinksIntoSVG">
+<link rel="match" href="../../struct/reftests/reference/green-100x100.html">
+<style>
+ .one {
+ width: 20px;
+ height: 100px;
+ background-repeat: no-repeat;
+ background-image: url("support/green-random-rects.svg#xywh=0,900,20,100");
+ }
+ .two {
+ width: 80px;
+ height: 100px;
+ background-repeat: no-repeat;
+ background-image: url("support/green-random-rects.svg#xywh=pixel:0,400,80,100");
+ position: absolute;
+ left: 20px;
+ top: 0;
+ }
+</style>
+<div style="position: relative">
+ <div class="one"></div>
+ <div class="two"></div>
+</div>