Permalink
Browse files

Performance-related changes:

1) Make the root renderer check all its sub-renderers' isActive state, rather than all the styleInfos, to determine if the main box should be created; this prevents the css3-container from being created and then not getting anything put in it.
2) Prevent the background and border StyleInfo objects from modifying the background/border runtimeStyle if that runtimeStyle has not been modified previously; also don't worry about resetting the border-style as that never gets modified when hiding the border.
3) Wrap several of the StyleInfo methods so that their return values are cached over the course of an update; gives a decent speed bump, especially now that isActive is called more often (see #1)
4) Move the attachment of event handlers down after the first call to update(); this prevents the changes to the element that occur during rendering from triggering update checks.
5) When handling onresize/onmove events, quit early if the element's bounds have not been previously queried; this prevents the extra loops which were firing after all the elements were initted when those elements have no initial CSS3 rendering.
  • Loading branch information...
Jason Johnston
Jason Johnston committed Sep 7, 2010
1 parent cff46c3 commit 77288810b8f670615498a4d1fdee0106c83018fc
@@ -53,7 +53,6 @@ PIE.BackgroundStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
parseCss: function( css ) {
var el = this.targetElement,
cs = el.currentStyle,
- rs = el.runtimeStyle,
tokenizer, token, image,
tok_type = PIE.Tokenizer.Type,
type_operator = tok_type.OPERATOR,
@@ -246,38 +245,40 @@ PIE.BackgroundStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
rsColor = rs.backgroundColor,
ret;
- rs.backgroundImage = rs.backgroundColor = '';
+ if( rsImage ) rs.backgroundImage = '';
+ if( rsColor ) rs.backgroundColor = '';
ret = fn.call( this );
- rs.backgroundImage = rsImage;
- rs.backgroundColor = rsColor;
+ if( rsImage ) rs.backgroundImage = rsImage;
+ if( rsColor ) rs.backgroundColor = rsColor;
return ret;
},
- getCss: function() {
- var cs = this.targetElement.currentStyle;
+ getCss: PIE.StyleInfoBase.cacheWhenLocked( function() {
+ var el = this.targetElement;
return this.getCss3() ||
this.withActualBg( function() {
+ var cs = el.currentStyle;
return cs.backgroundColor + ' ' + cs.backgroundImage + ' ' + cs.backgroundRepeat + ' ' +
cs.backgroundPositionX + ' ' + cs.backgroundPositionY;
} );
- },
+ } ),
- getCss3: function() {
+ getCss3: PIE.StyleInfoBase.cacheWhenLocked( function() {
var el = this.targetElement;
return el.style[ this.styleProperty ] || el.currentStyle.getAttribute( this.cssProperty );
- },
+ } ),
/**
* The isActive logic is slightly different, because getProps() always returns an object
* even if it is just falling back to the native background properties. But we only want
* to report is as being "active" if the -pie-background override property is present and
* parses successfully.
*/
- isActive: function() {
+ isActive: PIE.StyleInfoBase.cacheWhenLocked( function() {
return this.getCss3() && !!this.getProps();
- }
+ } )
} );
View
@@ -62,7 +62,7 @@ PIE.BorderStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
} : null;
},
- getCss: function() {
+ getCss: PIE.StyleInfoBase.cacheWhenLocked( function() {
var el = this.targetElement,
cs = el.currentStyle,
css;
@@ -71,7 +71,7 @@ PIE.BorderStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
css = cs.borderWidth + '|' + cs.borderStyle + '|' + cs.borderColor;
} );
return css;
- },
+ } ),
/**
* Execute a function with the actual border styles (not overridden with runtimeStyle
@@ -81,17 +81,16 @@ PIE.BorderStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
withActualBorder: function( fn ) {
var rs = this.targetElement.runtimeStyle,
rsWidth = rs.borderWidth,
- rsStyle = rs.borderStyle,
rsColor = rs.borderColor,
ret;
- rs.borderWidth = rs.borderStyle = rs.borderColor = '';
+ if( rsWidth ) rs.borderWidth = '';
+ if( rsColor ) rs.borderColor = '';
ret = fn.call( this );
- rs.borderWidth = rsWidth;
- rs.borderStyle = rsStyle;
- rs.borderColor = rsColor;
+ if( rsWidth ) rs.borderWidth = rsWidth;
+ if( rsColor ) rs.borderColor = rsColor;
return ret;
}
View
@@ -38,13 +38,17 @@ PIE.BoundsInfo.prototype = {
this.getLiveBounds();
},
+ hasBeenQueried: function() {
+ return !!this._lastBounds;
+ },
+
lock: function() {
++this._locked;
},
unlock: function() {
if( !--this._locked ) {
- this._lastBounds = this._lockedBounds;
+ if( this._lockedBounds ) this._lastBounds = this._lockedBounds;
this._lockedBounds = null;
}
}
View
@@ -9,10 +9,13 @@ PIE.Element = (function() {
var renderers,
boundsInfo = new PIE.BoundsInfo( el ),
styleInfos,
+ styleInfosArr,
ancestors,
initializing,
initialized,
- delayed;
+ eventsAttached,
+ delayed,
+ destroyed;
/**
* Initialize PIE for this element.
@@ -30,12 +33,6 @@ PIE.Element = (function() {
initializing = 1;
el.runtimeStyle.zoom = 1;
initFirstChildPseudoClass();
-
- el.attachEvent( 'onmove', update );
- el.attachEvent( 'onresize', update );
- el.attachEvent( 'onpropertychange', propChanged );
- el.attachEvent( 'onmouseenter', mouseEntered );
- el.attachEvent( 'onmouseleave', mouseLeft );
}
boundsInfo.lock();
@@ -61,16 +58,25 @@ PIE.Element = (function() {
boxShadowInfo: new PIE.BoxShadowStyleInfo( el ),
visibilityInfo: new PIE.VisibilityStyleInfo( el )
};
+ styleInfosArr = [
+ styleInfos.backgroundInfo,
+ styleInfos.borderInfo,
+ styleInfos.borderImageInfo,
+ styleInfos.borderRadiusInfo,
+ styleInfos.boxShadowInfo,
+ styleInfos.visibilityInfo
+ ];
rootRenderer = new PIE.RootRenderer( el, boundsInfo, styleInfos );
- renderers = [
- rootRenderer,
+ var childRenderers = [
new PIE.BoxShadowOutsetRenderer( el, boundsInfo, styleInfos, rootRenderer ),
new PIE.BackgroundRenderer( el, boundsInfo, styleInfos, rootRenderer ),
new PIE.BoxShadowInsetRenderer( el, boundsInfo, styleInfos, rootRenderer ),
new PIE.BorderRenderer( el, boundsInfo, styleInfos, rootRenderer ),
new PIE.BorderImageRenderer( el, boundsInfo, styleInfos, rootRenderer )
];
+ rootRenderer.childRenderers = childRenderers; // circular reference, can't pass in constructor; TODO is there a cleaner way?
+ renderers = [ rootRenderer ].concat( childRenderers );
// Add property change listeners to ancestors if requested
initAncestorPropChangeListeners();
@@ -80,46 +86,73 @@ PIE.Element = (function() {
PIE.Heartbeat.observe( update );
}
- // Listen for window resize events
- PIE.OnResize.observe( update );
-
// Trigger rendering
update();
}
+ if( !eventsAttached ) {
+ eventsAttached = 1;
+ el.attachEvent( 'onmove', handleMoveOrResize );
+ el.attachEvent( 'onresize', handleMoveOrResize );
+ el.attachEvent( 'onpropertychange', propChanged );
+ el.attachEvent( 'onmouseenter', mouseEntered );
+ el.attachEvent( 'onmouseleave', mouseLeft );
+ PIE.OnResize.observe( handleMoveOrResize );
+ }
+
boundsInfo.unlock();
}
}
+ /**
+ * Event handler for onmove and onresize events. Invokes update() only if the element's
+ * bounds have previously been calculated, to prevent multiple runs during page load when
+ * the element has no initial CSS3 properties.
+ */
+ function handleMoveOrResize() {
+ if( boundsInfo && boundsInfo.hasBeenQueried() ) {
+ update();
+ }
+ }
+
+
/**
* Update position and/or size as necessary. Both move and resize events call
* this rather than the updatePos/Size functions because sometimes, particularly
* during page load, one will fire but the other won't.
*/
function update() {
if( initialized ) {
- var i, len;
+ if( !destroyed ) {
+ var i, len;
- boundsInfo.lock();
- if( boundsInfo.positionChanged() ) {
- /* TODO just using getBoundingClientRect (used internally by BoundsInfo) for detecting
- position changes may not always be accurate; it's possible that
- an element will actually move relative to its positioning parent, but its position
- relative to the viewport will stay the same. Need to come up with a better way to
- track movement. The most accurate would be the same logic used in RootRenderer.updatePos()
- but that is a more expensive operation since it does some DOM walking, and we want this
- check to be as fast as possible. */
- for( i = 0, len = renderers.length; i < len; i++ ) {
- renderers[i].updatePos();
+ boundsInfo.lock();
+ for( i = styleInfosArr.length; i--; ) {
+ styleInfosArr[i].lock();
}
- }
- if( boundsInfo.sizeChanged() ) {
- for( i = 0, len = renderers.length; i < len; i++ ) {
- renderers[i].updateSize();
+ if( boundsInfo.positionChanged() ) {
+ /* TODO just using getBoundingClientRect (used internally by BoundsInfo) for detecting
+ position changes may not always be accurate; it's possible that
+ an element will actually move relative to its positioning parent, but its position
+ relative to the viewport will stay the same. Need to come up with a better way to
+ track movement. The most accurate would be the same logic used in RootRenderer.updatePos()
+ but that is a more expensive operation since it does some DOM walking, and we want this
+ check to be as fast as possible. */
+ for( i = 0, len = renderers.length; i < len; i++ ) {
+ renderers[i].updatePos();
+ }
}
+ if( boundsInfo.sizeChanged() ) {
+ for( i = 0, len = renderers.length; i < len; i++ ) {
+ renderers[i].updateSize();
+ }
+ }
+ for( i = styleInfosArr.length; i--; ) {
+ styleInfosArr[i].unlock();
+ }
+ boundsInfo.unlock();
}
- boundsInfo.unlock();
}
else if( !initializing ) {
init();
@@ -206,24 +239,22 @@ PIE.Element = (function() {
function destroy() {
var i, len;
+ destroyed = 1;
+
// destroy any active renderers
if( renderers ) {
for( i = 0, len = renderers.length; i < len; i++ ) {
renderers[i].destroy();
}
- renderers = null;
}
- styleInfos = null;
-
// remove any ancestor propertychange listeners
if( ancestors ) {
for( i = 0, len = ancestors.length; i < len; i++ ) {
ancestors[i].detachEvent( 'onpropertychange', ancestorPropChanged );
ancestors[i].detachEvent( 'onmouseenter', mouseEntered );
ancestors[i].detachEvent( 'onmouseleave', mouseLeft );
}
- ancestors = null;
}
// Remove from list of polled elements in IE8
@@ -234,12 +265,15 @@ PIE.Element = (function() {
PIE.OnScroll.unobserve( update );
PIE.OnResize.unobserve( update );
+ // Remove event listeners
el.detachEvent( 'onmove', update );
el.detachEvent( 'onresize', update );
el.detachEvent( 'onpropertychange', propChanged );
el.detachEvent( 'onmouseenter', mouseEntered );
el.detachEvent( 'onmouseleave', mouseLeft );
+ // Kill objects
+ renderers = boundsInfo = styleInfos = styleInfosArr = ancestors = null;
}
View
@@ -7,9 +7,9 @@
PIE.RootRenderer = PIE.RendererBase.newRenderer( {
isActive: function() {
- var infos = this.styleInfos;
- for( var i in infos ) {
- if( infos.hasOwnProperty( i ) && infos[ i ].isActive() ) {
+ var children = this.childRenderers;
+ for( var i in children ) {
+ if( children.hasOwnProperty( i ) && children[ i ].isActive() ) {
return true;
}
}
Oops, something went wrong.

0 comments on commit 7728881

Please sign in to comment.