@@ -94,6 +94,40 @@ private function convertDataValueToString( $value ) {
9494 return json_encode ( $ value );
9595 }
9696
97+ private function safeModifyChildren ( DOMNode $ parent , DOMNode $ oldNode , array $ newNodes , 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 = array_merge ( $ children , $ newNodes );
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 , array $ newNodes ) {
119+ $ this ->safeModifyChildren ( $ parent , $ oldNode , $ newNodes , false );
120+ }
121+
122+ private function safeInsertBefore ( DOMNode $ parent , DOMNode $ oldNode , array $ newNodes ) {
123+ $ this ->safeModifyChildren ( $ parent , $ oldNode , $ newNodes , true );
124+ }
125+
126+ private function replaceNodeWithChildren ( DOMNode $ node ) {
127+ $ children = iterator_to_array ( $ node ->childNodes );
128+ $ this ->safeReplaceNode ( $ node ->parentNode , $ node , $ children );
129+ }
130+
97131 /**
98132 * @param DOMNode $node
99133 * @param array $data
@@ -145,24 +179,7 @@ private function handleComponent( DOMElement $node, array $data ): bool {
145179 if ( $ node != $ importNode ) {
146180 $ node ->replaceWith ( $ importNode );
147181 } 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- }
182+ $ this ->safeReplaceNode ( $ node ->parentNode , $ node , [ $ importNode ] );
166183 }
167184 return true ;
168185 }
@@ -286,14 +303,18 @@ private function handleFor( DOMNode $node, array $data ) {
286303
287304 /** @var DOMElement $node */
288305 if ( $ node ->hasAttribute ( 'v-for ' ) ) {
306+ $ parentNode = $ node ->parentNode ;
289307 list ( $ itemName , $ listName ) = explode ( ' in ' , $ node ->getAttribute ( 'v-for ' ) );
290308 $ node ->removeAttribute ( 'v-for ' );
291309 $ node ->removeAttribute ( ':key ' );
292310
293311 foreach ( $ this ->app ->evaluateExpression ( $ listName , $ data ) as $ item ) {
294312 $ newNode = $ node ->cloneNode ( true );
295- $ node -> parentNode -> insertBefore ( $ newNode , $ node );
313+ $ this -> safeInsertBefore ( $ parentNode , $ node, [ $ newNode ] );
296314 $ this ->handleNode ( $ newNode , array_merge ( $ data , [ $ itemName => $ item ] ) );
315+ if ( $ newNode ->tagName === 'template ' ) {
316+ $ this ->replaceNodeWithChildren ( $ newNode );
317+ }
297318 }
298319
299320 $ this ->removeNode ( $ node );
@@ -324,7 +345,9 @@ private function handleRawHtml( DOMNode $node, array $data ) {
324345 }
325346
326347 private function removeNode ( DOMElement $ node ) {
327- $ node ->parentNode ->removeChild ( $ node );
348+ if ( $ node ->parentNode ) {
349+ $ node ->parentNode ->removeChild ( $ node );
350+ }
328351 }
329352
330353 /**
0 commit comments