Divec has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/102009


Change subject: WIP: Reduce CE-DM-CE echo when text is replaced
......................................................................

WIP: Reduce CE-DM-CE echo when text is replaced

Proof of concept: don't cause needless surface refreshes on overwriting.

WHAT DOES IT FIX?
* In the demo, select a letter in the middle of the text
* Press a letter key to overwrite it
* The 350ms grey flash no longer happens: ContentBranchNode isn't echoing 
changes

demos/ve/index.php
* Temporary auto model dump (helpful for testing on tiny documents)

ve.ce.Surface
* Flag the connection between Insertion/Removal change pairs (it's really a 
replacement)
* Disable handleInsertion. TODO: we're not ready to move away from 
handleInsertion yet

ve.dm.Surface->Document->TransactionProcessor->DocumentSynchronizer->Node
* Pass the flag down to the point where an event should be emitted or not
* TODO: This is somewhat ugly: how deep must the flag go? Is there a better way?

ve.ce.ContentBranchNode
* Ignore changes which have no effect
* TODO: The test may need to be more/less careful

Change-Id: Ied06031e9f25ea6ac80c64035f04eef82ce46df0
---
M demos/ve/index.php
M modules/ve/ce/ve.ce.ContentBranchNode.js
M modules/ve/ce/ve.ce.Surface.js
M modules/ve/dm/ve.dm.Document.js
M modules/ve/dm/ve.dm.DocumentSynchronizer.js
M modules/ve/dm/ve.dm.Node.js
M modules/ve/dm/ve.dm.Surface.js
M modules/ve/dm/ve.dm.TransactionProcessor.js
8 files changed, 66 insertions(+), 30 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/VisualEditor 
refs/changes/09/102009/1

diff --git a/demos/ve/index.php b/demos/ve/index.php
index 8bf2983..06473d9 100644
--- a/demos/ve/index.php
+++ b/demos/ve/index.php
@@ -347,7 +347,7 @@
                                // TODO: Validate input
                                console.dir( 
ve.instances[0].view.documentView.model.data.slice( start, end ) );
                        } );
-                       dumpModelButton.on( 'click', function () {
+                       function dumpTheModel () {
                                // linear model dump
                                var i, $li, element, html, annotations,
                                        $ol = $( '<ol start="0"></ol>' );
@@ -420,7 +420,9 @@
                                        getKids( 
ve.instances[0].view.documentView.getDocumentNode() )
                                );
                                $( '#ve-dump' ).show();
-                       } );
+                       }
+                       dumpModelButton.on( 'click', dumpTheModel );
+                       setInterval( dumpTheModel, 500 );
                        validateButton.on( 'click', function () {
                                var failed = false;
                                $( '.ve-ce-branchNode' ).each( function ( 
index, element ) {
diff --git a/modules/ve/ce/ve.ce.ContentBranchNode.js 
b/modules/ve/ce/ve.ce.ContentBranchNode.js
index 10d365c..6a1383d 100644
--- a/modules/ve/ce/ve.ce.ContentBranchNode.js
+++ b/modules/ve/ce/ve.ce.ContentBranchNode.js
@@ -160,7 +160,7 @@
  * @method
  */
 ve.ce.ContentBranchNode.prototype.renderContents = function () {
-       var i, len, node, rendered;
+       var i, len, node, rendered, oldContent, newContent;
        if (
                this.root instanceof ve.ce.DocumentNode &&
                this.root.getSurface().isRenderingLocked()
@@ -172,6 +172,22 @@
                this.root.getSurface().setContentBranchNodeChanged( true );
        }
 
+       // Test for changes before wiping
+       oldContent = [];
+       newContent = [];
+       for ( i = 0, len = this.$element.length; i < len; i++ ) {
+               node = this.$element[i];
+               oldContent.push( node.textContent );
+       }
+       rendered = this.getRenderedContents();
+       for ( i = 0, len = rendered.length; i < len; i++ ) {
+               node = rendered[i];
+               newContent.push( node.textContent );
+       }
+       if ( oldContent.join( '' ) === newContent.join( '' ) ) {
+               return;
+       }
+
        // Detach all child nodes from this.$element
        for ( i = 0, len = this.$element.length; i < len; i++ ) {
                node = this.$element[i];
@@ -181,7 +197,6 @@
        }
 
        // Reattach child nodes with the right annotations
-       rendered = this.getRenderedContents();
        for ( i = 0, len = rendered.length; i < len; i++ ) {
                this.$element[0].appendChild( rendered[i] );
        }
diff --git a/modules/ve/ce/ve.ce.Surface.js b/modules/ve/ce/ve.ce.Surface.js
index b7d8cb5..83aaa19 100644
--- a/modules/ve/ce/ve.ce.Surface.js
+++ b/modules/ve/ce/ve.ce.Surface.js
@@ -1243,7 +1243,7 @@
                previousStart, nextStart, newRange,
                previousData, nextData,
                i, length, annotation, annotationIndex, dataString,
-               annotationsLeft, annotationsRight,
+               annotationsLeft, annotationsRight, incomplete,
                fromLeft = 0,
                fromRight = 0,
                nodeOffset = node.getModel().getOffset();
@@ -1370,13 +1370,15 @@
        }
 
        if ( data.length > 0 ) {
-                       this.changeModel(
-                               ve.dm.Transaction.newFromInsertion(
-                                       this.documentView.model, nodeOffset + 1 
+ fromLeft,
-                                       data
-                               ),
-                               newRange
-                       );
+               incomplete = fromLeft + fromRight < previousData.length;
+               this.changeModel(
+                       ve.dm.Transaction.newFromInsertion(
+                               this.documentView.model, nodeOffset + 1 + 
fromLeft,
+                               data
+                       ),
+                       newRange,
+                       incomplete
+               );
        }
        if ( fromLeft + fromRight < previousData.length ) {
                this.changeModel(
@@ -1531,6 +1533,9 @@
  * @method
  */
 ve.ce.Surface.prototype.handleInsertion = function () {
+       if ( true ) {
+               return; // TODO: We're not ready for this line yet!
+       }
        var slug, data, range, annotations, insertionAnnotations, placeholder,
                selection = this.model.getSelection(), documentModel = 
this.model.getDocument();
 
@@ -2105,15 +2110,17 @@
  * @param {ve.dm.Transaction|ve.dm.Transaction[]|null} transactions One or 
more transactions to
  * process, or null to process none
  * @param {ve.Range} new selection
+ * @param {boolean} [incomplete] Whether a following change is pending
  * @throws {Error} If calls to this method are nested
  */
-ve.ce.Surface.prototype.changeModel = function ( transaction, range ) {
+ve.ce.Surface.prototype.changeModel = function ( transaction, range, 
incomplete ) {
+       incomplete = !!incomplete;
        if ( this.newModelSelection !== null ) {
                throw new Error( 'Nested change of newModelSelection' );
        }
        this.newModelSelection = range;
        try {
-               this.model.change( transaction, range );
+               this.model.change( transaction, range, incomplete );
        } finally {
                this.newModelSelection = null;
        }
diff --git a/modules/ve/dm/ve.dm.Document.js b/modules/ve/dm/ve.dm.Document.js
index 3b7541c..93e0075 100644
--- a/modules/ve/dm/ve.dm.Document.js
+++ b/modules/ve/dm/ve.dm.Document.js
@@ -290,14 +290,15 @@
  *
  * @method
  * @param {ve.dm.Transaction} transaction Transaction to apply
+ * @param {boolean} incomplete Whether a subsequent commit is pending
  * @fires transact
  * @throws {Error} Cannot commit a transaction that has already been committed
  */
-ve.dm.Document.prototype.commit = function ( transaction ) {
+ve.dm.Document.prototype.commit = function ( transaction, incomplete ) {
        if ( transaction.hasBeenApplied() ) {
                throw new Error( 'Cannot commit a transaction that has already 
been committed' );
        }
-       new ve.dm.TransactionProcessor( this, transaction ).process();
+       new ve.dm.TransactionProcessor( this, transaction ).process( incomplete 
);
        this.completeHistory.push( transaction );
        this.emit( 'transact', transaction );
 };
diff --git a/modules/ve/dm/ve.dm.DocumentSynchronizer.js 
b/modules/ve/dm/ve.dm.DocumentSynchronizer.js
index 3f770a4..653985b 100644
--- a/modules/ve/dm/ve.dm.DocumentSynchronizer.js
+++ b/modules/ve/dm/ve.dm.DocumentSynchronizer.js
@@ -93,8 +93,9 @@
  * @static
  * @method
  * @param {Object} action
+ * @param {boolean} incomplete Whether a subsequent synchronization is pending
  */
-ve.dm.DocumentSynchronizer.synchronizers.resize = function ( action ) {
+ve.dm.DocumentSynchronizer.synchronizers.resize = function ( action, 
incomplete ) {
        var node = action.node,
                parent = node.getParent();
 
@@ -105,7 +106,7 @@
                // Apply length change to tree
                // No update event needed, adjustLength causes an update event 
on its own
                // FIXME however, any queued update event will still be 
emitted, resulting in a duplicate
-               node.adjustLength( action.adjustment );
+               node.adjustLength( action.adjustment, incomplete );
        }
        // Update adjustment
        this.adjustment += action.adjustment;
@@ -275,8 +276,9 @@
  * This method also clears both action and event queues.
  *
  * @method
+ * @param {boolean} incomplete Whether a subsequent synchronization is pending
  */
-ve.dm.DocumentSynchronizer.prototype.synchronize = function () {
+ve.dm.DocumentSynchronizer.prototype.synchronize = function ( incomplete ) {
        var action,
                event,
                i;
@@ -284,7 +286,8 @@
        for ( i = 0; i < this.actionQueue.length; i++ ) {
                action = this.actionQueue[i];
                if ( action.type in ve.dm.DocumentSynchronizer.synchronizers ) {
-                       
ve.dm.DocumentSynchronizer.synchronizers[action.type].call( this, action );
+                       
ve.dm.DocumentSynchronizer.synchronizers[action.type].call( this,
+                               action, incomplete );
                } else {
                        throw new Error( 'Invalid action type ' + action.type );
                }
diff --git a/modules/ve/dm/ve.dm.Node.js b/modules/ve/dm/ve.dm.Node.js
index 59b720c..a5c405e 100644
--- a/modules/ve/dm/ve.dm.Node.js
+++ b/modules/ve/dm/ve.dm.Node.js
@@ -463,11 +463,12 @@
  *
  * @method
  * @param {number} length Length of content
+ * @param {boolean} incomplete Whether called by synchronize with a subsequent 
synchronize pending
  * @fires lengthChange
  * @fires update
  * @throws {Error} Invalid content length error if length is less than 0
  */
-ve.dm.Node.prototype.setLength = function ( length ) {
+ve.dm.Node.prototype.setLength = function ( length, incomplete ) {
        if ( length < 0 ) {
                throw new Error( 'Length cannot be negative' );
        }
@@ -481,7 +482,9 @@
        }
        // Emit events
        this.emit( 'lengthChange', diff );
-       this.emit( 'update' );
+       if ( !incomplete ) {
+               this.emit( 'update' );
+       }
 };
 
 /**
@@ -492,12 +495,13 @@
  *
  * @method
  * @param {number} adjustment Amount to adjust length by
+ * @param {boolean} incomplete Whether called by synchronize with a subsequent 
synchronize pending
  * @fires lengthChange
  * @fires update
  * @throws {Error} Invalid adjustment error if resulting length is less than 0
  */
-ve.dm.Node.prototype.adjustLength = function ( adjustment ) {
-       this.setLength( this.length + adjustment );
+ve.dm.Node.prototype.adjustLength = function ( adjustment, incomplete ) {
+       this.setLength( this.length + adjustment, incomplete );
 };
 
 /**
diff --git a/modules/ve/dm/ve.dm.Surface.js b/modules/ve/dm/ve.dm.Surface.js
index e21eda4..0cbc2cb 100644
--- a/modules/ve/dm/ve.dm.Surface.js
+++ b/modules/ve/dm/ve.dm.Surface.js
@@ -443,10 +443,11 @@
  * @param {ve.dm.Transaction|ve.dm.Transaction[]|null} transactions One or 
more transactions to
  *  process, or null to process none
  * @param {ve.Range} [selection] Selection to apply
+ * @param {boolean} incomplete Whether a subsequent connected change is pending
  * @fires contextChange
  */
-ve.dm.Surface.prototype.change = function ( transactions, selection ) {
-       this.changeInternal( transactions, selection, false );
+ve.dm.Surface.prototype.change = function ( transactions, selection, 
incomplete ) {
+       this.changeInternal( transactions, selection, false, incomplete );
 };
 
 /**
@@ -457,9 +458,11 @@
  * @param {ve.dm.Transaction|ve.dm.Transaction[]|null} transactions
  * @param {ve.Range} [selection] [selection]
  * @param {boolean} [skipUndoStack=false] If true, do not modify the undo 
stack. Used by undo/redo
+ * @param {boolean} incomplete Whether a subsequent synchronization is pending
  * @fires contextChange
  */
-ve.dm.Surface.prototype.changeInternal = function ( transactions, selection, 
skipUndoStack ) {
+ve.dm.Surface.prototype.changeInternal = function ( transactions, selection, 
skipUndoStack,
+               incomplete ) {
        var i, len, selectionAfter, selectionBefore = this.selection, 
contextChange = false;
 
        if ( !this.enabled ) {
@@ -481,7 +484,7 @@
                                        this.newTransactions.push( 
transactions[i] );
                                }
                                // The .commit() call below indirectly invokes 
setSelection()
-                               this.documentModel.commit( transactions[i] );
+                               this.documentModel.commit( transactions[i], 
incomplete );
                                if ( 
transactions[i].hasElementAttributeOperations() ) {
                                        contextChange = true;
                                }
diff --git a/modules/ve/dm/ve.dm.TransactionProcessor.js 
b/modules/ve/dm/ve.dm.TransactionProcessor.js
index 80ff62f..fe9db9d 100644
--- a/modules/ve/dm/ve.dm.TransactionProcessor.js
+++ b/modules/ve/dm/ve.dm.TransactionProcessor.js
@@ -85,8 +85,9 @@
  * When all operations are done being processed, the document will be 
synchronized.
  *
  * @method
+ * @param {boolean} incomplete Whether a subsequent call to process is pending
  */
-ve.dm.TransactionProcessor.prototype.process = function () {
+ve.dm.TransactionProcessor.prototype.process = function ( incomplete ) {
        var op;
 
        // This loop is factored this way to allow operations to be skipped 
over or executed
@@ -95,7 +96,7 @@
        while ( ( op = this.nextOperation() ) ) {
                this.executeOperation( op );
        }
-       this.synchronizer.synchronize();
+       this.synchronizer.synchronize( incomplete );
 
        // Mark the transaction as committed or rolled back, as appropriate
        this.transaction.markAsApplied();

-- 
To view, visit https://gerrit.wikimedia.org/r/102009
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ied06031e9f25ea6ac80c64035f04eef82ce46df0
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Divec <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to