Firefox · DOM
CVE-2026-117948
Logic Error in DOM
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
dom/svg/SVGSwitchElement.cpptesting/web-platform/tests/svg/struct/scripted/switch_reentrant_removal.html
Patch
diff --git a/dom/svg/SVGSwitchElement.cpp b/dom/svg/SVGSwitchElement.cpp
index 1fe6c032ea2..d273e3c16d2 100644
--- a/dom/svg/SVGSwitchElement.cpp
+++ b/dom/svg/SVGSwitchElement.cpp
@@ -4,6 +4,7 @@
#include "mozilla/dom/SVGSwitchElement.h"
+#include "mozAutoDocUpdate.h"
#include "mozilla/SVGUtils.h"
#include "mozilla/dom/SVGSwitchElementBinding.h"
#include "nsLayoutUtils.h"
@@ -69,6 +70,7 @@ NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGSwitchElement)
void SVGSwitchElement::InsertChildBefore(
nsIContent* aKid, nsIContent* aBeforeThis, bool aNotify, ErrorResult& aRv,
nsINode* aOldParent, MutationEffectOnScript aMutationEffectOnScript) {
+ mozAutoDocUpdate updateBatch(GetComposedDoc(), aNotify);
SVGSwitchElementBase::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv,
aOldParent, aMutationEffectOnScript);
if (aRv.Failed()) {
@@ -81,6 +83,7 @@ void SVGSwitchElement::InsertChildBefore(
void SVGSwitchElement::RemoveChildNode(
nsIContent* aKid, bool aNotify, const BatchRemovalState* aState,
nsINode* aNewParent, MutationEffectOnScript aMutationEffectOnScript) {
+ mozAutoDocUpdate updateBatch(GetComposedDoc(), aNotify);
SVGSwitchElementBase::RemoveChildNode(aKid, aNotify, aState, aNewParent,
aMutationEffectOnScript);
MaybeInvalidate();
diff --git a/testing/web-platform/tests/svg/struct/scripted/switch_reentrant_removal.html b/testing/web-platform/tests/svg/struct/scripted/switch_reentrant_removal.html
new file mode 100644
index 00000000000..338c30977c5
--- /dev/null
+++ b/testing/web-platform/tests/svg/struct/scripted/switch_reentrant_removal.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<title>SVG switch active child during reentrant removal</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<svg width="300" height="300">
+ <switch id="switch">
+ <foreignObject id="child" width="100" height="100">
+ <iframe id="trigger" srcdoc="<!doctype html><body>unload"></iframe>
+ </foreignObject>
+ <rect width="50" height="50"/>
+ </switch>
+</svg>
+<iframe id="destination" srcdoc="<!doctype html>
+ <svg width='300' height='300'>
+ <g id='target' transform='translate(1 1)'></g>
+ </svg>"></iframe>
+
+<script>
+async_test(t => {
+ window.addEventListener("load", () => {
+ const sw = document.getElementById("switch");
+ const child = document.getElementById("child");
+ const trigger = document.getElementById("trigger");
+ const destination = document.getElementById("destination").contentDocument;
+ const target = destination.getElementById("target");
+
+ assert_equals(sw.getBBox().width, 100, "The foreignObject is initially active");
+
+ let didUnload = false;
+ trigger.contentWindow.addEventListener("unload", () => {
+ didUnload = true;
+ assert_equals(child.parentNode, null, "The active child has been removed");
+ trigger.remove();
+ target.appendChild(child);
+ assert_equals(child.getBBox().width, 100, "The adopted child has a frame");
+
+ // Invalidate the old switch so its bounds have to be recomputed while
+ // removeChild is still on the stack.
+ sw.setAttribute("transform", "translate(2 2)");
+ assert_equals(sw.getBBox().width, 50, "Only the remaining child contributes bounds");
+ child.remove();
+ }, { once: true });
+
+ sw.removeChild(child);
+ assert_true(didUnload, "The unload handler ran during removeChild");
+ assert_equals(sw.getBBox().width, 50, "The remaining child is still active");
+ t.done();
+ }, { once: true });
+}, "SVG switch active child during reentrant removal");
+</script>
+
+</script>
+</html>
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/testing/web-platform/tests/svg/struct/scripted/switch_reentrant_removal.html b/testing/web-platform/tests/svg/struct/scripted/switch_reentrant_removal.html
new file mode 100644
index 00000000000..338c30977c5
--- /dev/null
+++ b/testing/web-platform/tests/svg/struct/scripted/switch_reentrant_removal.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<title>SVG switch active child during reentrant removal</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<svg width="300" height="300">
+ <switch id="switch">
+ <foreignObject id="child" width="100" height="100">
+ <iframe id="trigger" srcdoc="<!doctype html><body>unload"></iframe>
+ </foreignObject>
+ <rect width="50" height="50"/>
+ </switch>
+</svg>
+<iframe id="destination" srcdoc="<!doctype html>
+ <svg width='300' height='300'>
+ <g id='target' transform='translate(1 1)'></g>
+ </svg>"></iframe>
+
+<script>
+async_test(t => {
+ window.addEventListener("load", () => {
+ const sw = document.getElementById("switch");
+ const child = document.getElementById("child");
+ const trigger = document.getElementById("trigger");
+ const destination = document.getElementById("destination").contentDocument;
+ const target = destination.getElementById("target");
+
+ assert_equals(sw.getBBox().width, 100, "The foreignObject is initially active");
+
+ let didUnload = false;
+ trigger.contentWindow.addEventListener("unload", () => {
+ didUnload = true;
+ assert_equals(child.parentNode, null, "The active child has been removed");
+ trigger.remove();
+ target.appendChild(child);
+ assert_equals(child.getBBox().width, 100, "The adopted child has a frame");
+
+ // Invalidate the old switch so its bounds have to be recomputed while
+ // removeChild is still on the stack.
+ sw.setAttribute("transform", "translate(2 2)");
+ assert_equals(sw.getBBox().width, 50, "Only the remaining child contributes bounds");
+ child.remove();
+ }, { once: true });
+
+ sw.removeChild(child);
+ assert_true(didUnload, "The unload handler ran during removeChild");
+ assert_equals(sw.getBBox().width, 50, "The remaining child is still active");
+ t.done();
+ }, { once: true });
+}, "SVG switch active child during reentrant removal");
+</script>
+
+</script>
+</html>
Loading diff…
References
On This Page