Skip to content

Instantly share code, notes, and snippets.

@trevorparscal
Last active August 29, 2015 14:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trevorparscal/c0bf02b02c96d98cfc56 to your computer and use it in GitHub Desktop.
Save trevorparscal/c0bf02b02c96d98cfc56 to your computer and use it in GitHub Desktop.
Transplant the CSS styles from as parent document to a frame's document
/**
* 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, resolves the promise.
*
* If the styles still haven't loaded after a long time (5 seconds by default), we give up waiting
* and resolve the promise anyway. This protects against cases like a display: none; iframe in
* Firefox, where the styles won't load until the iframe becomes visible.
*
* For details of how we arrived at the strategy used in this function, see #load.
*
* @license MIT
*
* @static
* @inheritable
* @param {HTMLDocument} parentDoc Document to transplant styles from
* @param {HTMLDocument} frameDoc Document to transplant styles to
* @param {number} [timeout=5000] How long to wait before giving up (in ms). If 0, never give up.
* @return {jQuery.Promise} Promise resolved when styles have loaded
*/
function transplantStyles( parentDoc, frameDoc, timeout ) {
var i, numSheets, styleNode, styleText, newNode, timeoutID, pollNodeId, $pendingPollNodes,
$pollNodes = $( [] ),
// Fake font-family value
fontFamily = 'oo-ui-frame-transplantStyles-loaded',
nextIndex = parentDoc.oouiFrameTransplantStylesNextIndex || 0,
deferred = $.Deferred();
for ( i = 0, numSheets = parentDoc.styleSheets.length; i < numSheets; i++ ) {
styleNode = parentDoc.styleSheets[ i ].ownerNode;
if ( styleNode.disabled ) {
continue;
}
if ( styleNode.nodeName.toLowerCase() === 'link' ) {
// External stylesheet; use @import
styleText = '@import url(' + styleNode.href + ');';
} else {
// Internal stylesheet; just copy the text
// For IE10 we need to fall back to .cssText, BUT that's undefined in
// other browsers, so fall back to '' rather than 'undefined'
styleText = styleNode.textContent || parentDoc.styleSheets[ i ].cssText || '';
}
// Create a node with a unique ID that we're going to monitor to see when the CSS
// has loaded
if ( styleNode.oouiFrameTransplantStylesId ) {
// If we're nesting transplantStyles operations and this node already has
// a CSS rule to wait for loading, reuse it
pollNodeId = styleNode.oouiFrameTransplantStylesId;
} else {
// Otherwise, create a new ID
pollNodeId = 'oo-ui-frame-transplantStyles-loaded-' + nextIndex;
nextIndex++;
// Add #pollNodeId { font-family: ... } to the end of the stylesheet / after the @import
// The font-family rule will only take effect once the @import finishes
styleText += '\n' + '#' + pollNodeId + ' { font-family: ' + fontFamily + '; }';
}
// Create a node with id=pollNodeId
$pollNodes = $pollNodes.add( $( '<div>', frameDoc )
.attr( 'id', pollNodeId )
.appendTo( frameDoc.body )
);
// Add our modified CSS as a <style> tag
newNode = frameDoc.createElement( 'style' );
newNode.textContent = styleText;
newNode.oouiFrameTransplantStylesId = pollNodeId;
frameDoc.head.appendChild( newNode );
}
frameDoc.oouiFrameTransplantStylesNextIndex = nextIndex;
// Poll every 100ms until all external stylesheets have loaded
$pendingPollNodes = $pollNodes;
timeoutID = 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!
if ( timeoutID !== null ) {
timeoutID = null;
$pollNodes.remove();
deferred.resolve();
}
} else {
timeoutID = setTimeout( pollExternalStylesheets, 100 );
}
}, 100 );
// ...but give up after a while
if ( timeout !== 0 ) {
setTimeout( function () {
if ( timeoutID ) {
clearTimeout( timeoutID );
timeoutID = null;
$pollNodes.remove();
deferred.reject();
}
}, timeout || 5000 );
}
return deferred.promise();
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment