[MediaWiki-commits] [Gerrit] Make CSS transplantation in OO.ui.Frame actually wait for CSS - change (oojs/ui)
jenkins-bot has submitted this change and it was merged. Change subject: Make CSS transplantation in OO.ui.Frame actually wait for CSS .. Make CSS transplantation in OO.ui.Frame actually wait for CSS Before e9ca44c86, there was code in place that waited for the CSS to load and fired 'initialize' asynchronously. It was initially removed because it was deemed unnecessary, but with later changes it has become necessary again. The original approach also didn't work in Chrome. This commit completely rewrites the CSS transplantation code to correctly wait for CSS to be loaded before emitting 'initialize', and to unbreak image URL references by no longer inlining same-origin styles. The new code transplants style tags directly, while link tags are rewritten as style@import url(..); #foo { ... }/style. We then insert a div id=foo into the document and wait for it to get styled, which will only happen after the @import has finished. For the styling of #foo we use font-family, because it allows us to set arbitrary string values. Additionally, we have to use visiblity: hidden; to hide windows initially, because display: none; causes the iframe to not load in Firefox. We reapply display: none; after the iframe initializes. Bug: 56630 Change-Id: I76688a5ad7bd144c5e46064119f607060e76f0d9 --- M src/OO.ui.Frame.js M src/OO.ui.Window.js M src/styles/OO.ui.Window.css 3 files changed, 104 insertions(+), 61 deletions(-) Approvals: Krinkle: Looks good to me, approved jenkins-bot: Verified diff --git a/src/OO.ui.Frame.js b/src/OO.ui.Frame.js index 3c6fb32..6a761fe 100644 --- a/src/OO.ui.Frame.js +++ b/src/OO.ui.Frame.js @@ -42,6 +42,76 @@ * @event initialize */ +/* Static Methods */ + +/** + * Transplant the CSS styles from as parent document to a frame's document. + * + * This loops over the style sheets in the parent document, and copies their nodes to the + * frame's document. It then polls the document to see when all styles have loaded, and once they + * have, invokes the callback. + * + * For details of how we arrived at the strategy used in this function, see #load. + * + * @static + * @method + * @inheritable + * @param {HTMLDocument} parentDoc Document to transplant styles from + * @param {HTMLDocument} frameDoc Document to transplant styles to + * @param {Function} [callback] Callback to execute once styles have loaded + */ +OO.ui.Frame.static.transplantStyles = function ( parentDoc, frameDoc, callback ) { + var i, numSheets, styleNode, newNode, timeout, pollNodeId, $pendingPollNodes, + $pollNodes = $( [] ), + // Fake font-family value + fontFamily = 'oo-ui-frame-transplantStyles-loaded'; + + for ( i = 0, numSheets = parentDoc.styleSheets.length; i numSheets; i++ ) { + styleNode = parentDoc.styleSheets[i].ownerNode; + if ( callback styleNode.nodeName.toLowerCase() === 'link' ) { + // External stylesheet + // Create a node with a unique ID that we're going to monitor to see when the CSS + // has loaded + pollNodeId = 'oo-ui-frame-transplantStyles-loaded-' + i; + $pollNodes = $pollNodes.add( $( 'div', frameDoc ) + .attr( 'id', pollNodeId ) + .appendTo( frameDoc.body ) + ); + + // Add style@import url(...); #pollNodeId { font-family: ... }/style + // The font-family rule will only take effect once the @import finishes + newNode = frameDoc.createElement( 'style' ); + newNode.textContent = '@import url(' + styleNode.href + ');\n' + + '#' + pollNodeId + ' { font-family: ' + fontFamily + '; }'; + } else { + // Not an external stylesheet, or no polling required; just copy the node over + newNode = frameDoc.importNode( styleNode, true ); + } + frameDoc.head.appendChild( newNode ); + } + + if ( callback ) { + // Poll every 100ms until all external stylesheets have loaded + $pendingPollNodes = $pollNodes; + timeout = setTimeout( function pollExternalStylesheets() { + while ( + $pendingPollNodes.length 0 + $pendingPollNodes.eq( 0 ).css( 'font-family' ) === fontFamily + ) { + $pendingPollNodes = $pendingPollNodes.slice( 1 ); + } + + if ( $pendingPollNodes.length === 0 ) { + // We're done! + $pollNodes.remove(); + callback(); +
[MediaWiki-commits] [Gerrit] Make CSS transplantation in OO.ui.Frame actually wait for CSS - change (oojs/ui)
Catrope has uploaded a new change for review. https://gerrit.wikimedia.org/r/96431 Change subject: Make CSS transplantation in OO.ui.Frame actually wait for CSS .. Make CSS transplantation in OO.ui.Frame actually wait for CSS Before e9ca44c86, there was code in place that waited for the CSS to load and fired 'initialize' asynchronously. It was initially removed because it was deemed unnecessary, but with later changes it has become necessary again. The original approach also didn't work in Chrome. This commit completely rewrites the CSS transplantation code to correctly wait for CSS to be loaded before emitting 'initialize', and to unbreak image URL references by no longer inlining same-origin styles. The new code transplants style tags directly, while link tags are rewritten as style@import url(..); #foo { ... }/style. We then insert a div id=foo into the document and wait for it to get styled, which will only happen after the @import has finished. For the styling of #foo we use font-family, because it allows us to set arbitrary string values. Additionally, we have to use visiblity: hidden; to hide windows initially, because display: none; causes the iframe to not load in Firefox. We reapply display: none; after the iframe initializes. Bug: 56630 Change-Id: I76688a5ad7bd144c5e46064119f607060e76f0d9 --- M src/OO.ui.Frame.js M src/OO.ui.Window.js M src/styles/OO.ui.Window.css 3 files changed, 103 insertions(+), 61 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/oojs/ui refs/changes/31/96431/1 diff --git a/src/OO.ui.Frame.js b/src/OO.ui.Frame.js index 3c6fb32..78146d4 100644 --- a/src/OO.ui.Frame.js +++ b/src/OO.ui.Frame.js @@ -42,6 +42,76 @@ * @event initialize */ +/* Static Methods */ + +/** + * Transplant the CSS styles from as parent document to a frame's document. + * + * This loops over the style sheets in the parent document, and copies their nodes to the + * frame's document. It then polls the document to see when all styles have loaded, and once they + * have, invokes the callback. + * + * For details of how we arrived at the strategy used in this function, see #load. + * + * @static + * @method + * @inheritable + * @param {HTMLDocument} parentDoc Document to transplant styles from + * @param {HTMLDocument} frameDoc Document to transplant styles to + * @param {Function} [callback] Callback to execute once styles have loaded + */ +OO.ui.Frame.static.transplantStyles = function ( parentDoc, frameDoc, callback ) { + var i, numSheets, styleNode, newNode, timeout, pollNodeId, + $pollNodes = $( [] ), + // Fake font-family value + fontFamily = 'oo-ui-frame-transplantStyles-loaded'; + + for ( i = 0, numSheets = parentDoc.styleSheets.length; i numSheets; i++ ) { + styleNode = parentDoc.styleSheets[i].ownerNode; + if ( callback styleNode.nodeName.toLowerCase() === 'link' ) { + // External stylesheet + // Create a node with a unique ID that we're going to monitor to see when the CSS + // has loaded + pollNodeId = 'oo-ui-frame-transplantStyles-loaded-' + i; + $pollNodes = $pollNodes.add( $( 'div', frameDoc ) + .attr( 'id', pollNodeId ) + .appendTo( frameDoc.body ) + ); + + // Add style@import url(...); #pollNodeId { font-family: ... }/style + // The font-family rule will only take effect once the @import finishes + newNode = frameDoc.createElement( 'style' ); + newNode.textContent = '@import url(' + styleNode.href + ');\n' + + '#' + pollNodeId + ' { font-family: ' + fontFamily + '; }'; + } else { + // Not an external stylesheet, or no polling required; just copy the node over + newNode = frameDoc.importNode( styleNode, true ); + } + frameDoc.head.appendChild( newNode ); + } + + if ( callback ) { + // Poll every 100ms until all external stylesheets have loaded + timeout = setTimeout( function pollExternalStylesheets() { + var i, len, doneLoading = true; + for ( i = 0, len = $pollNodes.length; i len; i++ ) { + if ( $pollNodes.eq( i ).css( 'font-family' ) !== fontFamily ) { + // This one hasn't loaded yet + doneLoading = false; + break; + } + } + + if ( doneLoading ) { + $pollNodes.remove(); +