Catrope has uploaded a new change for review.
https://gerrit.wikimedia.org/r/93934
Change subject: [WIP] Use serialization cache in MW integration
......................................................................
[WIP] Use serialization cache in MW integration
Add prepareWikitext() which submits HTML for serialization and saves
the resulting cache key, and tryWithPreparedWikitext() which uses that
cache key (if available; if pending, it waits for it) for API requests.
Implemented save(), serialize() and showChanges() in terms of
tryWithPreparedWikitext().
As a bonus, make save() retry on badtoken.
TODO:
* Call prepareWikitext() when opening the save dialog
* Test all of this stuff
* Put in new ve.track() calls (removed the one for save because it
doesn't make much sense with all the new cache key and retry stuff)
* Draw a diagram of how all the promises are chained; there are a lot
Change-Id: I1d56fe88d312e9810a57d56a285ccdf4f1facf42
---
M modules/ve-mw/init/ve.init.mw.Target.js
1 file changed, 175 insertions(+), 54 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/VisualEditor
refs/changes/34/93934/1
diff --git a/modules/ve-mw/init/ve.init.mw.Target.js
b/modules/ve-mw/init/ve.init.mw.Target.js
index 1f4ec25..5411c14 100644
--- a/modules/ve-mw/init/ve.init.mw.Target.js
+++ b/modules/ve-mw/init/ve.init.mw.Target.js
@@ -58,8 +58,10 @@
this.pluginCallbacks = [];
this.modulesReady = $.Deferred();
+ this.prepareWikitextPromise = null;
this.loading = false;
this.saving = false;
+ this.diffing = false;
this.serializing = false;
this.submitting = false;
this.baseTimeStamp = null;
@@ -346,6 +348,7 @@
*/
ve.init.mw.Target.onShowChanges = function ( response ) {
var data = response.visualeditor;
+ this.diffing = false;
if ( !data && !response.error ) {
ve.init.mw.Target.onShowChangesError.call( this, null, 'Invalid
response from server', null );
} else if ( response.error ) {
@@ -366,7 +369,7 @@
};
/**
- * Handle errors during saveChanges action.
+ * Handle errors during showChanges action.
*
* @static
* @method
@@ -377,7 +380,7 @@
* @fires showChangesError
*/
ve.init.mw.Target.onShowChangesError = function ( jqXHR, status, error ) {
- this.saving = false;
+ this.diffing = false;
this.emit( 'showChangesError', jqXHR, status, error );
};
@@ -554,6 +557,136 @@
};
/**
+ * Serialize the current document and store the result in the serialization
cache on the server.
+ *
+ * This function returns a promise that is resolved once serialization is
complete, with the
+ * cache key passed as the first parameter.
+ *
+ * If there's already a request pending for the same (reference-identical
HTMLDocument), this
+ * function will not initiate a new request but will return the promise for
the pending request.
+ * If a request for the same document has already been completed, this
function will keep returning
+ * the same promise (which will already have been resolved) until
clearPreparedWikitext() is called.
+ *
+ * @param {HTMLDocument} doc Document to serialize
+ * @returns {jQuery.Promise} Abortable promise, resolved with the cache key.
+ */
+ve.init.mw.Target.prototype.prepareWikitext = function ( doc ) {
+ var xhr, html, deferred = $.Deferred();
+
+ if ( this.prepareWikitextPromise && this.prepareWikitextPromise.doc ===
doc ) {
+ return this.prepareWikitextPromise;
+ }
+ this.clearPreparedWikitext();
+
+ html = this.getHtml( doc );
+ xhr = $.ajax( {
+ 'url': this.apiUrl,
+ 'data': {
+ 'action': 'visualeditor',
+ 'paction': 'serializeforcache',
+ 'html': html,
+ 'page': this.pageName,
+ 'oldid': this.revid,
+ 'format': 'json'
+ },
+ 'dataType': 'json',
+ 'type': 'POST',
+ // Wait up to 100 seconds before giving up
+ 'timeout': 100000,
+ 'cache': 'false'
+ } )
+ .done( function ( response ) {
+ if ( !response.visualeditor || typeof
response.visualeditor.cachekey === 'string' ) {
+ deferred.resolve(
response.visualeditor.cachekey );
+ } else {
+ deferred.reject();
+ }
+ } )
+ .fail( deferred.reject );
+
+ this.prepareWikitextPromise = deferred.promise( {
+ 'abort': xhr.abort,
+ 'html': html,
+ 'doc': doc
+ } );
+ return this.prepareWikitextPromise;
+};
+
+/**
+ * Get the prepared wikitext, if any. Same as prepareWikitext() but does not
initiate a request
+ * if one isn't already pending or finished. Instead, it returns a rejected
promise in that case.
+ *
+ * @param {HTMLDocument} doc Document to serialize
+ * @returns {jQuery.Promise} Abortable promise, resolved with the cache key.
+ */
+ve.init.mw.Target.prototype.getPreparedWikitext = function ( doc ) {
+ var deferred;
+ if ( this.prepareWikitextPromise && this.prepareWikitextPromise.doc ===
doc ) {
+ return this.prepareWikitextPromise;
+ }
+ deferred = $.Deferred();
+ deferred.reject();
+ return deferred.promise();
+};
+
+/**
+ * Clear the promise for the prepared wikitext cache key, and abort it if it's
still in progress.
+ */
+ve.init.mw.Target.prototype.clearPreparedWikitext = function () {
+ if ( this.prepareWikitextPromise ) {
+ this.prepareWikitextPromise.abort();
+ this.prepareWikitextPromise = null;
+ }
+};
+
+/**
+ * Try submitting an API request with a cache key for prepared wikitext,
falling back to submitting
+ * HTML directly if there is no cache key present or pending, or if the
request for the cache key
+ * fails, or if using the cache key fails with a badcachekey error.
+ *
+ * @param {HTMLDocument} doc Document to submit
+ * @param {Object} options POST parameters to send. Do not include 'html',
'cachekey' or 'format'.
+ * @returns {jQuery.Promise}
+ */
+ve.init.mw.Target.prototype.tryWithPreparedWikitext = function ( doc, options
) {
+ var data, preparedWikitext = this.getPreparedWikitext( doc ), target =
this;
+ data = $.extend( {}, options, { 'format': 'json' } );
+
+ function ajaxRequest( cachekey ) {
+ if ( typeof cachekey === 'string' ) {
+ data.cachekey = cachekey;
+ } else {
+ // Getting a cache key failed, fall back to sending the
HTML
+ data.html = preparedWikitext && preparedWikitext.html
|| this.getHtml( doc );
+ // If using the cache key fails, we'll come back here
with cachekey still set
+ delete data.cachekey;
+ }
+ return $.ajax( {
+ 'url': target.apiUrl,
+ 'data': data,
+ 'dataType': 'json',
+ 'type': 'POST',
+ // Wait up to 100 seconds before giving up
+ 'timeout': 100000
+ } )
+ .then( function ( response, status, jqxhr ) {
+ if ( response.error && response.error.code ===
'badcachekey' ) {
+ // This cache key is evidently bad,
clear it
+ this.clearPreparedWikitext();
+ // Try again without a cache key
+ return ajaxRequest( null );
+ }
+ return jqxhr;
+ } );
+ // TODO ve.track()
+ }
+
+ // If we successfully get prepared wikitext, then invoke ajaxRequest()
with the cache key,
+ // otherwise invoke it without.
+ return preparedWikitext.then( ajaxRequest, ajaxRequest );
+};
+
+/**
* Post DOM data to the Parsoid API.
*
* This method performs an asynchronous action and uses a callback function to
handle the result.
@@ -569,42 +702,44 @@
* @returns {boolean} Saving has been started
*/
ve.init.mw.Target.prototype.save = function ( doc, options ) {
- var data, start;
+ var data, target = this;
// Prevent duplicate requests
if ( this.saving ) {
return false;
}
data = $.extend( {}, options, {
- 'format': 'json',
'action': 'visualeditoredit',
'page': this.pageName,
'oldid': this.revid,
'basetimestamp': this.baseTimeStamp,
'starttimestamp': this.startTimeStamp,
- 'html': this.getHtml( doc ),
'token': this.editToken
} );
- // Save DOM
- start = ve.now();
-
- this.saving = $.ajax( {
- 'url': this.apiUrl,
- 'data': data,
- 'dataType': 'json',
- 'type': 'POST',
- // Wait up to 100 seconds before giving up
- 'timeout': 100000
- } )
- .then( function ( data, status, jqxhr ) {
- ve.track( 'performance.domSave', {
- 'bytes': $.byteLength( jqxhr.responseText ),
- 'duration': ve.now() - start,
- 'parsoid': jqxhr.getResponseHeader(
'X-Parsoid-Performance' )
- } );
+ this.saving = this.tryWithPreparedWikitext( doc, data )
+ .then( function ( response, status, jqxhr ) {
+ if ( response.error && response.error === 'badtoken' ) {
+ // Refetch token and try again
+ return $.ajax( {
+ 'url': target.apiUrl,
+ 'data': {
+ 'action': 'tokens',
+ 'type': 'edit'
+ },
+ 'dataType': 'json',
+ 'type': 'GET'
+ } )
+ .then( function ( response ) {
+ if ( response.tokens && typeof
response.tokens.edittoken === 'string' ) {
+ data.token =
this.editToken = response.tokens.edittoken;
+ return
this.tryWithPreparedWikitext( doc, data );
+ }
+ } );
+ }
return jqxhr;
} )
+ // TODO ve.track()
.done( ve.bind( ve.init.mw.Target.onSave, this ) )
.fail( ve.bind( ve.init.mw.Target.onSaveError, this ) );
@@ -616,25 +751,23 @@
*
* @method
* @param {HTMLDocument} doc Document to compare against (via wikitext)
+ * @returns {boolean} Diffing has been started
*/
ve.init.mw.Target.prototype.showChanges = function ( doc ) {
- $.ajax( {
- 'url': this.apiUrl,
- 'data': {
- 'format': 'json',
- 'action': 'visualeditor',
- 'paction': 'diff',
- 'page': this.pageName,
- 'oldid': this.revid,
- 'html': this.getHtml( doc )
- },
- 'dataType': 'json',
- 'type': 'POST',
- // Wait up to 100 seconds before giving up
- 'timeout': 100000
+ if ( this.diffing ) {
+ return false;
+ }
+ this.diffing = this.tryWithPreparedWikitext( doc, {
+ 'action': 'visualeditor',
+ 'paction': 'diff',
+ 'page': this.pageName,
+ 'oldid': this.revid,
} )
+ // TOOD ve.track()
.done( ve.bind( ve.init.mw.Target.onShowChanges, this ) )
.fail( ve.bind( ve.init.mw.Target.onShowChangesError, this ) );
+
+ return true;
};
/**
@@ -697,31 +830,19 @@
* @method
* @param {HTMLDocument} doc Document to serialize
* @param {Function} callback Function to call when complete, accepts error
and wikitext arguments
- * @returns {boolean} Serializing has beeen started
+ * @returns {boolean} Serializing has been started
*/
ve.init.mw.Target.prototype.serialize = function ( doc, callback ) {
// Prevent duplicate requests
if ( this.serializing ) {
return false;
}
- // Load DOM
- this.serializing = true;
this.serializeCallback = callback;
- $.ajax( {
- 'url': this.apiUrl,
- 'data': {
- 'action': 'visualeditor',
- 'paction': 'serialize',
- 'html': this.getHtml( doc ),
- 'page': this.pageName,
- 'oldid': this.revid,
- 'format': 'json'
- },
- 'dataType': 'json',
- 'type': 'POST',
- // Wait up to 100 seconds before giving up
- 'timeout': 100000,
- 'cache': 'false'
+ this.serializing = this.tryWithPreparedWikitext( doc, {
+ 'action': 'visualeditor',
+ 'paction': 'serialize',
+ 'page': this.pageName,
+ 'oldid': this.revid
} )
.done( ve.bind( ve.init.mw.Target.onSerialize, this ) )
.fail( ve.bind( ve.init.mw.Target.onSerializeError, this ) );
--
To view, visit https://gerrit.wikimedia.org/r/93934
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I1d56fe88d312e9810a57d56a285ccdf4f1facf42
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Catrope <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits