Skip to content

Commit f8e1076

Browse files
committed
Support nested SVGs by keeping a counter
We allocate an array with one number, and increment it on any nested <svg>. We add a new counter to the array when we encounter a <foreignObject>. The counter lets us avoid a stack for nested <svg>s in the general case.
1 parent 50bfa83 commit f8e1076

File tree

1 file changed

+48
-2
lines changed

1 file changed

+48
-2
lines changed

src/renderers/dom/fiber/ReactDOMFiber.js

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,22 @@ let eventsEnabled : ?boolean = null;
6464
let selectionInformation : ?mixed = null;
6565
let currentNamespaceURI : null | SVG_NAMESPACE | MATH_NAMESPACE = null;
6666

67+
// How many <svg>s have we entered so far.
68+
// We increment or decrement the last array item when pushing and popping <svg>.
69+
// A new counter is appended to the end whenever we enter a <foreignObject>.
70+
let svgDepthByForeignObjectDepth : Array<number> = [0];
71+
72+
// How many <foreignObject>s we have entered so far.
73+
// We increment and decrement it when pushing and popping <foreignObject>.
74+
// We use this counter as the current index for accessing the array above.
75+
let foreignObjectDepth : number = 0;
76+
77+
// For example, given this structure, `svgDepthByForeignObjectDepth` would be:
78+
// <svg><foreignObject><svg><svg><svg><foreignObject><svg>
79+
// ^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^]
80+
// [ 1 , 3 , 1 ]
81+
// `foreignObjectDepth` would be 2.
82+
6783
function getIntrinsicNamespaceURI(type : string) {
6884
switch (type) {
6985
case 'svg':
@@ -80,14 +96,32 @@ var DOMRenderer = ReactFiberReconciler({
8096
pushHostContext(type : string) {
8197
switch (type) {
8298
case 'svg':
99+
if (currentNamespaceURI == null) {
100+
// We are entering an <svg> for the first time.
101+
currentNamespaceURI = SVG_NAMESPACE;
102+
svgDepthByForeignObjectDepth[foreignObjectDepth] = 1;
103+
} else if (currentNamespaceURI === SVG_NAMESPACE) {
104+
// We are entering an <svg> inside <svg>.
105+
// We record this fact so that when we pop this <svg>, we stay in the
106+
// SVG mode instead of switching to HTML mode.
107+
svgDepthByForeignObjectDepth[foreignObjectDepth]++;
108+
}
109+
break;
83110
case 'math':
84111
if (currentNamespaceURI == null) {
85-
currentNamespaceURI = getIntrinsicNamespaceURI(type);
112+
currentNamespaceURI = MATH_NAMESPACE;
86113
}
87114
break;
88115
case 'foreignObject':
89116
if (currentNamespaceURI === SVG_NAMESPACE) {
90117
currentNamespaceURI = null;
118+
// We are in HTML mode again, so current <svg> nesting counter needs
119+
// to be reset. However we still need to remember its value when we
120+
// pop this <foreignObject>. So instead of resetting the counter, we
121+
// advance the pointer, and start a new independent <svg> depth
122+
// counter at the next array index.
123+
foreignObjectDepth++;
124+
svgDepthByForeignObjectDepth[foreignObjectDepth] = 0;
91125
}
92126
break;
93127
}
@@ -97,7 +131,16 @@ var DOMRenderer = ReactFiberReconciler({
97131
switch (type) {
98132
case 'svg':
99133
if (currentNamespaceURI === SVG_NAMESPACE) {
100-
currentNamespaceURI = null;
134+
if (svgDepthByForeignObjectDepth[foreignObjectDepth] === 1) {
135+
// We exited all nested <svg> nodes.
136+
// We can switch to HTML mode.
137+
currentNamespaceURI = null;
138+
} else {
139+
// There is still an <svg> above so we stay in SVG mode.
140+
// We decrease the counter so that next time we leave <svg>
141+
// we will be able to switch to HTML mode.
142+
svgDepthByForeignObjectDepth[foreignObjectDepth]--;
143+
}
101144
}
102145
break;
103146
case 'math':
@@ -107,6 +150,9 @@ var DOMRenderer = ReactFiberReconciler({
107150
break;
108151
case 'foreignObject':
109152
if (currentNamespaceURI == null) {
153+
// We are exiting <foreignObject> and nested <svg>s may exist above.
154+
// Switch to the previous <svg> depth counter by decreasing the index.
155+
foreignObjectDepth--;
110156
currentNamespaceURI = SVG_NAMESPACE;
111157
}
112158
break;

0 commit comments

Comments
 (0)