Merge "Refactor CE Surface to reduce event feedback"

This commit is contained in:
jenkins-bot 2013-09-11 00:44:57 +00:00 committed by Gerrit Code Review
commit 22d9067095
3 changed files with 102 additions and 82 deletions

View file

@ -306,6 +306,7 @@ ve.ce.Surface.prototype.focus = function () {
*/
ve.ce.Surface.prototype.documentOnFocus = function () {
this.eventSequencer.attach( this.$document );
this.surfaceObserver.startTimerLoop();
};
/**
@ -315,9 +316,9 @@ ve.ce.Surface.prototype.documentOnFocus = function () {
* @param {jQuery.Event} e Element blur event
*/
ve.ce.Surface.prototype.documentOnBlur = function () {
this.$document.off( '.ve-ce-Surface' );
this.surfaceObserver.stop( true, true );
this.eventSequencer.detach();
this.surfaceObserver.stopTimerLoop();
this.surfaceObserver.pollOnce();
this.dragging = false;
};
@ -337,7 +338,9 @@ ve.ce.Surface.prototype.onDocumentMouseDown = function ( e ) {
// this.$$( e.target ).closest( '.ve-ce-documentNode' ).length === 0
if ( e.which === 1 ) {
this.surfaceObserver.stop( true, true );
this.surfaceObserver.stopTimerLoop();
// TODO: guard with incRenderLock?
this.surfaceObserver.pollOnce();
}
// Handle triple click
@ -363,7 +366,9 @@ ve.ce.Surface.prototype.onDocumentMouseDown = function ( e ) {
* @emits selectionEnd
*/
ve.ce.Surface.prototype.onDocumentMouseUp = function ( e ) {
this.surfaceObserver.start( false, true );
this.surfaceObserver.startTimerLoop();
// TODO: guard with incRenderLock?
this.surfaceObserver.pollOnce();
if ( !e.shiftKey && this.selecting ) {
this.emit( 'selectionEnd' );
this.selecting = false;
@ -481,7 +486,14 @@ ve.ce.Surface.prototype.onDocumentKeyDown = function ( e ) {
return;
}
this.surfaceObserver.stop( true, false );
this.surfaceObserver.stopTimerLoop();
this.incRenderLock();
try {
// TODO: is this correct?
this.surfaceObserver.pollOnce();
} finally {
this.decRenderLock();
}
switch ( e.keyCode ) {
case ve.Keys.LEFT:
case ve.Keys.RIGHT:
@ -516,7 +528,13 @@ ve.ce.Surface.prototype.onDocumentKeyDown = function ( e ) {
}
break;
}
this.surfaceObserver.start( false, false );
this.incRenderLock();
try {
this.surfaceObserver.pollOnce();
} finally {
this.decRenderLock();
}
this.surfaceObserver.startTimerLoop();
};
/**
@ -560,7 +578,7 @@ ve.ce.Surface.prototype.onDocumentKeyPress = function ( e ) {
* @param {jQuery.Event} ev
*/
ve.ce.Surface.prototype.afterDocumentKeyPress = function () {
this.surfaceObserver.start( false, true );
this.surfaceObserver.pollOnce();
};
/**
@ -585,7 +603,8 @@ ve.ce.Surface.prototype.onDocumentKeyUp = function ( e ) {
* @param {jQuery.Event} e Cut event
*/
ve.ce.Surface.prototype.onCut = function ( e ) {
this.surfaceObserver.stop( false, true );
// TODO: no pollOnce here: but should we add one?
this.surfaceObserver.stopTimerLoop();
this.onCopy( e );
setTimeout( ve.bind( function () {
var selection, tx;
@ -602,7 +621,8 @@ ve.ce.Surface.prototype.onCut = function ( e ) {
this.model.change( tx, new ve.Range( selection.start ) );
this.surfaceObserver.clear();
this.surfaceObserver.start( false, true );
this.surfaceObserver.startTimerLoop();
this.surfaceObserver.pollOnce();
}, this ) );
};
@ -662,7 +682,8 @@ ve.ce.Surface.prototype.onPaste = function ( e ) {
eventPasteKey = clipboardData && clipboardData.getData( 'text/xcustom' ),
eventPasteText = clipboardData && clipboardData.getData( 'text/plain' );
this.surfaceObserver.stop( false, true );
// TODO: no pollOnce here: but should we add one?
this.surfaceObserver.stopTimerLoop();
// Pasting into a range? Remove first.
if ( !rangy.getSelection( this.$document[0] ).isCollapsed ) {
@ -768,7 +789,13 @@ ve.ce.Surface.prototype.onDocumentCompositionStart = function () {
*/
ve.ce.Surface.prototype.onDocumentCompositionEnd = function () {
this.inIme = false;
this.surfaceObserver.start( false, false );
this.incRenderLock();
try {
this.surfaceObserver.pollOnce();
} finally {
this.decRenderLock();
}
this.surfaceObserver.startTimerLoop();
};
/*! Custom Events */
@ -820,7 +847,7 @@ ve.ce.Surface.prototype.onChange = function ( transaction, selection ) {
/**
* Handle selection change events.
*
* @see ve.ce.SurfaceObserver#poll
* @see ve.ce.SurfaceObserver#pollOnce
*
* @method
* @param {ve.Range} oldRange
@ -842,7 +869,7 @@ ve.ce.Surface.prototype.onSelectionChange = function ( oldRange, newRange ) {
/**
* Handle content change events.
*
* @see ve.ce.SurfaceObserver#poll
* @see ve.ce.SurfaceObserver#pollOnce
*
* @method
* @param {HTMLElement} node DOM node the change occured in
@ -1015,7 +1042,7 @@ ve.ce.Surface.prototype.onContentChange = function ( node, previous, next ) {
* @method
*/
ve.ce.Surface.prototype.onLock = function () {
this.surfaceObserver.stop( false, true );
this.surfaceObserver.locked = true;
};
/**
@ -1024,8 +1051,8 @@ ve.ce.Surface.prototype.onLock = function () {
* @method
*/
ve.ce.Surface.prototype.onUnlock = function () {
this.surfaceObserver.clear( this.model.getSelection() );
this.surfaceObserver.start( false, true );
this.surfaceObserver.locked = false;
// TODO: should we pollOnce?
};
/*! Relocation */
@ -1072,8 +1099,15 @@ ve.ce.Surface.prototype.handleLeftOrRightArrowKey = function ( e ) {
// Selection is going to be displayed programmatically so prevent default browser behaviour
e.preventDefault();
// Stop with final poll cycle so we have correct information in model
this.surfaceObserver.stop( true, false );
// TODO: onDocumentKeyDown did this already
this.surfaceObserver.stopTimerLoop();
this.incRenderLock();
try {
// TODO: onDocumentKeyDown did this already
this.surfaceObserver.pollOnce();
} finally {
this.decRenderLock();
}
selection = this.model.getSelection();
if ( this.$$( e.target ).css( 'direction' ) === 'rtl' ) {
// If the language direction is RTL, switch left/right directions:
@ -1089,7 +1123,9 @@ ve.ce.Surface.prototype.handleLeftOrRightArrowKey = function ( e ) {
e.shiftKey
);
this.model.change( null, range );
this.surfaceObserver.start( false, true );
// TODO: onDocumentKeyDown does this anyway
this.surfaceObserver.startTimerLoop();
this.surfaceObserver.pollOnce();
};
/**
@ -1106,7 +1142,11 @@ ve.ce.Surface.prototype.handleUpOrDownArrowKey = function ( e ) {
nativeSel.modify( 'extend', 'left', 'character' );
return;
}
this.surfaceObserver.stop( true, true );
// TODO: onDocumentKeyDown did this already
this.surfaceObserver.stopTimerLoop();
// TODO: onDocumentKeyDown did this already
this.surfaceObserver.pollOnce();
selection = this.model.getSelection();
rangySelection = rangy.getSelection( this.$document[0] );
// Perform programatic handling only for selection that is expanded and backwards according to
@ -1130,18 +1170,20 @@ ve.ce.Surface.prototype.handleUpOrDownArrowKey = function ( e ) {
if ( !$element.hasClass( 've-ce-branchNode-slug' ) ) {
$element.remove();
}
this.surfaceObserver.start( false, true );
this.surfaceObserver.stop( false, true );
this.surfaceObserver.pollOnce();
if ( e.shiftKey === true ) { // expanded range
range = new ve.Range( selection.from, this.model.getSelection().to );
} else { // collapsed range (just a cursor)
range = new ve.Range( this.model.getSelection().to );
}
this.model.change( null, range );
this.surfaceObserver.start( false, true );
this.surfaceObserver.pollOnce();
}, this ), 0 );
} else {
this.surfaceObserver.start( false, true );
// TODO: onDocumentKeyDown does this anyway
this.surfaceObserver.startTimerLoop();
this.surfaceObserver.pollOnce();
}
};
@ -1197,7 +1239,8 @@ ve.ce.Surface.prototype.handleInsertion = function () {
}
}
this.surfaceObserver.stop( true, true );
this.surfaceObserver.stopTimerLoop();
this.surfaceObserver.pollOnce();
};
/**

View file

@ -24,8 +24,9 @@ ve.ce.SurfaceObserver = function VeCeSurfaceObserver( documentView ) {
this.documentView = documentView;
this.domDocument = null;
this.polling = false;
this.locked = false;
this.timeoutId = null;
this.frequency = 250; //ms
this.frequency = 250; // ms
// Initialization
this.clear();
@ -80,35 +81,41 @@ ve.ce.SurfaceObserver.prototype.clear = function ( range ) {
};
/**
* Start polling.
*
* If {postpone} is false or undefined the first poll cycle will occur immediately and synchronously.
* Start the setTimeout synchronisation loop
*
* @method
* @param {boolean} postpone Add the first poll to the end of the event queue
* @param {boolean} emitContentChanges Allow contentChange to be emitted
*/
ve.ce.SurfaceObserver.prototype.start = function ( postpone, emitContentChanges ) {
ve.ce.SurfaceObserver.prototype.startTimerLoop = function () {
this.domDocument = this.documentView.getDocumentNode().getElementDocument();
this.polling = true;
this.poll( postpone, emitContentChanges );
this.timerLoop( true ); // will not sync immediately, because timeoutId should be null
};
/**
* Stop polling.
*
* If {poll} is false or undefined than no final poll cycle will occur and changes can be lost. If
* it's true then a final poll cycle will occur immediately and synchronously.
* Loop once with `setTimeout`
* @method
* @param {boolean} firstTime Wait before polling
*/
ve.ce.SurfaceObserver.prototype.timerLoop = function ( firstTime ) {
if ( this.timeoutId ) {
// in case we're not running from setTimeout
clearTimeout( this.timeoutId );
this.timeoutId = null;
}
if ( !firstTime && !this.locked ) {
this.pollOnce();
}
// only reach this point if pollOnce does not throw an exception
this.timeoutId = setTimeout( ve.bind( this.timerLoop, this ), this.frequency );
};
/**
* Stop polling
*
* @method
* @param {boolean} poll Poll one last time before stopping future polling
* @param {boolean} emitContentChanges Allow contentChange to be emitted
*/
ve.ce.SurfaceObserver.prototype.stop = function ( poll, emitContentChanges ) {
ve.ce.SurfaceObserver.prototype.stopTimerLoop = function () {
if ( this.polling === true ) {
if ( poll === true ) {
this.poll( false, emitContentChanges );
}
this.polling = false;
clearTimeout( this.timeoutId );
this.timeoutId = null;
@ -122,37 +129,16 @@ ve.ce.SurfaceObserver.prototype.stop = function ( poll, emitContentChanges ) {
*
* TODO: fixing selection in certain cases, handling selection across multiple nodes in Firefox
*
* FIXME: Does not work well (selectionChange is not emited) when cursor is placed inside a slug
* FIXME: Does not work well (selectionChange is not emitted) when cursor is placed inside a slug
* with a mouse.
*
* @method
* @param {boolean} postpone Append the poll action to the end of the event queue
* @param {boolean} emitContentChanges Allow contentChange to be emitted
* @emits contentChange
* @emits selectionChange
*/
ve.ce.SurfaceObserver.prototype.poll = function ( postpone, emitContentChanges ) {
ve.ce.SurfaceObserver.prototype.pollOnce = function () {
var $nodeOrSlug, node, text, hash, range, rangyRange;
if ( this.polling === false ) {
return;
}
if ( this.timeoutId !== null ) {
clearTimeout( this.timeoutId );
this.timeoutId = null;
}
if ( postpone === true ) {
this.timeoutId = setTimeout(
ve.bind( this.poll, this ),
0,
false,
emitContentChanges
);
return;
}
range = this.range;
node = this.node;
rangyRange = ve.ce.DomRange.newFromDomSelection( rangy.getSelection( this.domDocument ) );
@ -183,14 +169,12 @@ ve.ce.SurfaceObserver.prototype.poll = function ( postpone, emitContentChanges )
text = ve.ce.getDomText( node.$[0] );
hash = ve.ce.getDomHash( node.$[0] );
if ( this.text !== text || this.hash !== hash ) {
if ( emitContentChanges ) {
this.emit(
'contentChange',
node,
{ 'text': this.text, 'hash': this.hash, 'range': this.range },
{ 'text': text, 'hash': hash, 'range': range }
);
}
this.emit(
'contentChange',
node,
{ 'text': this.text, 'hash': this.hash, 'range': this.range },
{ 'text': text, 'hash': hash, 'range': range }
);
this.text = text;
this.hash = hash;
}
@ -205,11 +189,4 @@ ve.ce.SurfaceObserver.prototype.poll = function ( postpone, emitContentChanges )
);
this.range = range;
}
this.timeoutId = setTimeout(
ve.bind( this.poll, this ),
this.frequency,
false,
emitContentChanges
);
};

View file

@ -82,7 +82,7 @@ ve.ui.Surface.prototype.initialize = function () {
// By re-asserting the current selection and forcing a poll we force selection to be something
// reasonable - otherwise in Firefox, the initial selection is (0,0), causing bug 42277
this.model.getFragment().select();
this.view.surfaceObserver.poll( false, true );
this.view.surfaceObserver.pollOnce();
this.model.startHistoryTracking();
};