Skip to content

Commit

Permalink
batch updating of css transforms for AccessibleContent, see #41
Browse files Browse the repository at this point in the history
  • Loading branch information
jessegreenberg committed Jul 17, 2018
1 parent 74bb785 commit 63e0e7e
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 8 deletions.
43 changes: 43 additions & 0 deletions js/accessibility/AccessibilityTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,49 @@ define( function( require ) {
}
},

/**
* Debugging - after the all AccessibleInstances have had CSS transforms updated, make sure that none of the
* instances are still marked dirty
*
* @param {AccessibleInstance} rootInstance
*/
ensureNoDirtyInstances: function( rootInstance ) {
assert && assert( !rootInstance.hasDirtyTransform, 'dirty transform for node ' + rootInstance.node );

for ( var i = 0; i < rootInstance.children.length; i++ ) {
var instance = rootInstance.children[ i ];
AccessibilityTree.ensureNoDirtyInstances( instance );
}
},

/**
* Update CSS transforms on DOM elements in the PDOM. Does a depth first search for all descendants on the rootNode
* marked with descendantHasDirtyTransform until it finds the descendant marked with hasDirtyTransform.
* Once it finds this, it will update the CSS transforms on that AccessibleInstance and all descendants (in that
* order because parents need to be updated before children do to the computation of the matrix that transforms
* DOM to the local node coordinate frame).
*
* @private (scenery-internal)
*/
updateCSSTransforms: function( rootAccessibleInstance ) {

// now that we have completed the search, we don't have to search down this subtree next time
rootAccessibleInstance.descendantHasDirtyTransform = false;

if ( rootAccessibleInstance.hasDirtyTransform ) {
console.log( rootAccessibleInstance.node );
AccessibleInstance.updateCSSTransformsForSubTree( rootAccessibleInstance );
}
else {
for ( var i = 0; i < rootAccessibleInstance.children.length; i++ ) {
var child = rootAccessibleInstance.children[ i ];
if ( child.hasDirtyTransform || child.descendantHasDirtyTransform ) {
AccessibilityTree.updateCSSTransforms( rootAccessibleInstance.children[ i ] );
}
}
}
},

/**
* Prepares for an a11y-tree-changing operation (saving some state).
* @private
Expand Down
65 changes: 57 additions & 8 deletions js/accessibility/AccessibleInstance.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ define( function( require ) {
// node, identity until siblings have client bounds
this.domToLocalMatrix = Matrix3.IDENTITY;

// @private - flag that indicates that this AccessibleInstance has a peer with accessible content
// that is out of date and needs to be updated in the next animation frame
this.hasDirtyTransform = false;

// @private - flag that indicates this AccessibleInstance has a some descendant with a dirty transform
// that needs to be updated
this.descendantHasDirtyTransform = false;

// PURELY FOR DEBUGGING
display.rootNode.setOpacity( 0.2 );

Expand Down Expand Up @@ -207,7 +215,8 @@ define( function( require ) {

this.primaryObserver = new MutationObserver( function( mutations ) {
for ( var i = 0; i < mutations.length; i++ ) {
self.updateCSSTransforms( self.peer.primarySibling );
self.invalidateCSSTransforms();
// self.updateCSSTransforms( self.peer.primarySibling );
}
} );
this.primaryObserver.observe( this.peer.primarySibling, config );
Expand All @@ -216,7 +225,7 @@ define( function( require ) {
if ( this.peer.descriptionSibling ) {
this.descriptionObserver = new MutationObserver( function( mutations ) {
for ( var i = 0; i < mutations.length; i++ ) {
self.updateCSSTransforms( self.peer.descriptionSibling );
self.invalidateCSSTransforms();
}
} );
this.descriptionObserver.observe( this.peer.descriptionSibling, config );
Expand All @@ -225,20 +234,18 @@ define( function( require ) {
if ( this.peer.labelSibling ) {
this.labelObserver = new MutationObserver( function( mutations ) {
for ( var i = 0; i < mutations.length; i++ ) {
self.updateCSSTransforms( self.peer.labelSibling );
self.invalidateCSSTransforms();
}
} );
this.labelObserver.observe( this.peer.labelSibling, config );
}

// @private {TransformTracker} - also update all css bounds when transform of this node changes
this.transformTracker = new TransformTracker( this.trail );
this.transformTracker = new TransformTracker( this.trail, { isStatic: true } );

// @private {function} - the listener that updates css transforms, to be disposed
this.transformListener = function() {
self.updateCSSTransforms( self.peer.primarySibling );
self.peer.descriptionSibling && self.updateCSSTransforms( self.peer.descriptionSibling );
self.peer.labelSibling && self.updateCSSTransforms( self.peer.labelSibling );
self.invalidateCSSTransforms();
}
this.transformTracker.addListener( this.transformListener );
}
Expand Down Expand Up @@ -679,6 +686,21 @@ define( function( require ) {
audit( AccessibleInstance.createFakeAccessibleTree( rootNode ), this );
},

invalidateCSSTransforms: function() {
if ( !this.hasDirtyTransform ) {

// mark that this instance needs to be updated
this.hasDirtyTransform = true;

// mark all ancestors to indicate that this instance will require redrawing of CSS transforms
var parent = this.parent;
while ( parent ) {
parent.descendantHasDirtyTransform = true;
parent = parent.parent;
}
}
},

/**
* Update the CSS transform of the elements of the AccessiblePeer. Correct CSS transformation is required by
* assistive technology to handle DOM events such as click and pointer down.
Expand All @@ -694,7 +716,6 @@ define( function( require ) {
var nodeBoundsFinite = localBounds.isFinite();

// if leaf is empty and there are still no dimensions, we will just use the identity matrix
// var localNodeTranslation = Matrix3.translation( localBounds.minX, localBounds.minY );
if ( clientWidth > 0 && clientHeight > 0 ) {

// inefficient version, can potentially combine later to not create extra matrices that aren't used
Expand Down Expand Up @@ -731,6 +752,34 @@ define( function( require ) {
}
}
}, {

/**
* TODO: Perhaps handle invalidation of individual Sibling elements, rather than all of them. Or maybe this is OK
* because we can mark dirty on a sibling by sibling basis and if node transform changes, then all of them are
* dirty anyway.
*
* @param {AccessibleInstance} rootInstance - root of the subtree whose transforms we are going to update
* @private (scenery-internal)
*/
updateCSSTransformsForSubTree: function( rootInstance ) {

// no longer dirty for next time
rootInstance.hasDirtyTransform = false;

// we are going to update the entire subtree, no instance down this sub tree need to be marked as having
// dirty descendants
rootInstance.descendantHasDirtyTransform = false;

rootInstance.updateCSSTransforms( rootInstance.peer.primarySibling );
rootInstance.peer.labelSibling && rootInstance.updateCSSTransforms( rootInstance.peer.labelSibling );
rootInstance.peer.descriptionSibling && rootInstance.updateCSSTransforms( rootInstance.peer.descriptionSibling );

// now apply to all instance in the subtree (depth-first)
for ( var i = 0; i < rootInstance.children.length; i++ ) {
AccessibleInstance.updateCSSTransformsForSubTree( rootInstance.children[ i ] );
}
},

/**
* Creates a fake AccessibleInstance-like tree structure (with the equivalent nodes and children structure).
* For debugging.
Expand Down
7 changes: 7 additions & 0 deletions js/display/Display.js
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,13 @@ define( function( require ) {
}
}

if ( this._accessible ) {

// make sure that DOM elements are correctly positioned with CSS transforms
AccessibilityTree.updateCSSTransforms( this._rootAccessibleInstance );
AccessibilityTree.ensureNoDirtyInstances( this._rootAccessibleInstance );
}

this._frameId++;

if ( sceneryLog && scenery.isLoggingPerformance() ) {
Expand Down

0 comments on commit 63e0e7e

Please sign in to comment.