From 005a8d24efa1c72dd4676e41e57e77c91c82a498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Dziewo=C5=84ski?= Date: Tue, 4 Jan 2022 16:09:09 +0100 Subject: [PATCH] Re-duplicate deduplicated TemplateStyles Bug: T287675 Bug: T299251 Change-Id: I7711c30131cb441f84b3e2137983f0ba2a50b46f --- .../ve-mw/init/targets/ve.init.mw.Target.js | 3 + .../preinit/ve.init.mw.ArticleTargetSaver.js | 4 ++ modules/ve-mw/preinit/ve.utils.parsoid.js | 69 +++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/modules/ve-mw/init/targets/ve.init.mw.Target.js b/modules/ve-mw/init/targets/ve.init.mw.Target.js index a1e2bdeebc..2376749f22 100644 --- a/modules/ve-mw/init/targets/ve.init.mw.Target.js +++ b/modules/ve-mw/init/targets/ve.init.mw.Target.js @@ -229,6 +229,9 @@ ve.init.mw.Target.static.parseDocument = function ( documentString, mode, sectio } // Strip legacy IDs, for example in section headings mw.libs.ve.stripParsoidFallbackIds( doc.body ); + // Re-duplicate deduplicated TemplateStyles, for correct rendering when editing a section or + // when templates are removed during the edit + mw.libs.ve.reduplicateStyles( doc.body ); // Fix relative or missing base URL if needed this.fixBase( doc ); } diff --git a/modules/ve-mw/preinit/ve.init.mw.ArticleTargetSaver.js b/modules/ve-mw/preinit/ve.init.mw.ArticleTargetSaver.js index f433cdd0bb..ee7ccf3d1b 100644 --- a/modules/ve-mw/preinit/ve.init.mw.ArticleTargetSaver.js +++ b/modules/ve-mw/preinit/ve.init.mw.ArticleTargetSaver.js @@ -91,6 +91,10 @@ // Remove these to avoid triggering selser. $( newDoc ).find( '[data-mw-section-id]:not( section )' ).removeAttr( 'data-mw-section-id' ); + // Deduplicate styles (we re-duplicated them in ve.init.mw.Target.static.parseDocument) + // to let selser recognize the nodes and avoid dirty diffs. + mw.libs.ve.deduplicateStyles( newDoc.body ); + // Add doctype manually // ve.serializeXhtml is loaded separately from utils.parsing // eslint-disable-next-line no-undef diff --git a/modules/ve-mw/preinit/ve.utils.parsoid.js b/modules/ve-mw/preinit/ve.utils.parsoid.js index 485bcabc9a..26b81a519a 100644 --- a/modules/ve-mw/preinit/ve.utils.parsoid.js +++ b/modules/ve-mw/preinit/ve.utils.parsoid.js @@ -93,6 +93,75 @@ mw.libs.ve.stripRestbaseIds = function ( doc ) { } ); }; +/** + * Re-duplicate deduplicated TemplateStyles, for correct rendering when editing a section or + * when templates are removed during the edit. + * + * @param {HTMLElement} element Parent element, e.g. document body + */ +mw.libs.ve.reduplicateStyles = function ( element ) { + Array.prototype.forEach.call( element.querySelectorAll( 'link[rel="mw-deduplicated-inline-style"]' ), function ( link ) { + var href = link.getAttribute( 'href' ); + if ( !href || href.slice( 0, 'mw-data:'.length ) !== 'mw-data:' ) { + return; + } + var key = href.slice( 'mw-data:'.length ); + var style = element.querySelector( 'style[data-mw-deduplicate="' + key + '"]' ); + if ( !style ) { + return; + } + + var newStyle = link.ownerDocument.createElement( 'style' ); + newStyle.setAttribute( 'data-mw-deduplicate', key ); + + // Copy content from the old `style` node (for rendering) + for ( var i = 0; i < style.childNodes.length; i++ ) { + newStyle.appendChild( style.childNodes[ i ].cloneNode( true ) ); + } + // Copy attributes from the old `link` node (for selser) + Array.prototype.forEach.call( link.attributes, function ( attr ) { + if ( attr.name !== 'rel' && attr.name !== 'href' ) { + newStyle.setAttribute( attr.name, attr.value ); + } + } ); + + link.parentNode.replaceChild( newStyle, link ); + } ); +}; + +/** + * De-duplicate TemplateStyles, like Parsoid does. + * + * @param {HTMLElement} element Parent element, e.g. document body + */ +mw.libs.ve.deduplicateStyles = function ( element ) { + var styleTagKeys = {}; + + Array.prototype.forEach.call( element.querySelectorAll( 'style[data-mw-deduplicate]' ), function ( style ) { + var key = style.getAttribute( 'data-mw-deduplicate' ); + + if ( !styleTagKeys[ key ] ) { + // Not a dupe + styleTagKeys[ key ] = true; + return; + } + + // Dupe - replace with a placeholder reference + var link = style.ownerDocument.createElement( 'link' ); + link.setAttribute( 'rel', 'mw-deduplicated-inline-style' ); + link.setAttribute( 'href', 'mw-data:' + key ); + + // Copy attributes from the old `link` node (for selser) + Array.prototype.forEach.call( style.attributes, function ( attr ) { + if ( attr.name !== 'rel' && attr.name !== 'data-mw-deduplicate' ) { + link.setAttribute( attr.name, attr.value ); + } + } ); + + style.parentNode.replaceChild( link, style ); + } ); +}; + /** * Fix fragment links which should be relative to the current document *