@@ -94,6 +94,35 @@ private function convertDataValueToString( $value ) {
9494 return json_encode ( $ value );
9595 }
9696
97+ private function safeModifyChildren ( DOMNode $ parent , DOMNode $ oldNode , DOMNode $ newNode , bool $ insert = false ) {
98+ // TODO To work around the double-free, we detach all the children of the parent node and
99+ // re-attach them in the correct sequence, replacing the target node with our newly-imported
100+ // node. Once `mwcli` has moved off this outdated version of PHP (T388411) we should be able
101+ // to remove this workaround. T398821
102+ $ children = [];
103+ foreach ( iterator_to_array ( $ parent ->childNodes ) as $ child ) {
104+ if ( $ child === $ oldNode ) {
105+ $ children [] = $ newNode ;
106+ }
107+ if ( $ insert || $ child !== $ oldNode ) {
108+ $ children [] = $ child ;
109+ }
110+ $ child ->remove ();
111+ }
112+
113+ foreach ( $ children as $ child ) {
114+ $ parent ->appendChild ( $ child );
115+ }
116+ }
117+
118+ private function safeReplaceNode ( DOMNode $ parent , DOMNode $ oldNode , DOMNode $ newNode ) {
119+ $ this ->safeModifyChildren ( $ parent , $ oldNode , $ newNode , false );
120+ }
121+
122+ private function safeInsertBefore ( DOMNode $ parent , DOMNode $ oldNode , DOMNode $ newNode ) {
123+ $ this ->safeModifyChildren ( $ parent , $ oldNode , $ newNode , true );
124+ }
125+
97126 /**
98127 * @param DOMNode $node
99128 * @param array $data
@@ -145,24 +174,7 @@ private function handleComponent( DOMElement $node, array $data ): bool {
145174 if ( $ node != $ importNode ) {
146175 $ node ->replaceWith ( $ importNode );
147176 } else {
148- // TODO To work around the double-free, we detach all the children of the parent node and
149- // re-attach them in the correct sequence, replacing the target node with our newly-imported
150- // node. Once `mwcli` has moved off this outdated version of PHP (T388411) we should be able
151- // to remove this workaround. T398821
152- $ parent = $ node ->parentNode ;
153- $ children = [];
154- foreach ( iterator_to_array ( $ parent ->childNodes ) as $ child ) {
155- if ( $ child !== $ node ) {
156- $ children [] = $ child ;
157- } else {
158- $ children [] = $ importNode ;
159- }
160- $ child ->remove ();
161- }
162-
163- foreach ( $ children as $ child ) {
164- $ parent ->appendChild ( $ child );
165- }
177+ $ this ->safeReplaceNode ( $ node ->parentNode , $ node , $ importNode );
166178 }
167179 return true ;
168180 }
@@ -286,13 +298,17 @@ private function handleFor( DOMNode $node, array $data ) {
286298
287299 /** @var DOMElement $node */
288300 if ( $ node ->hasAttribute ( 'v-for ' ) ) {
301+ $ parentNode = $ node ->parentNode ;
289302 list ( $ itemName , $ listName ) = explode ( ' in ' , $ node ->getAttribute ( 'v-for ' ) );
290303 $ node ->removeAttribute ( 'v-for ' );
291304 $ node ->removeAttribute ( ':key ' );
292305
293306 foreach ( $ this ->app ->evaluateExpression ( $ listName , $ data ) as $ item ) {
294307 $ newNode = $ node ->cloneNode ( true );
295- $ node ->parentNode ->insertBefore ( $ newNode , $ node );
308+ if ( $ newNode ->tagName === 'template ' ) {
309+ $ newNode = $ newNode ->firstChild ->cloneNode ( true );
310+ }
311+ $ this ->safeInsertBefore ( $ parentNode , $ node , $ newNode );
296312 $ this ->handleNode ( $ newNode , array_merge ( $ data , [ $ itemName => $ item ] ) );
297313 }
298314
@@ -324,7 +340,9 @@ private function handleRawHtml( DOMNode $node, array $data ) {
324340 }
325341
326342 private function removeNode ( DOMElement $ node ) {
327- $ node ->parentNode ->removeChild ( $ node );
343+ if ( $ node ->parentNode ) {
344+ $ node ->parentNode ->removeChild ( $ node );
345+ }
328346 }
329347
330348 /**
0 commit comments