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

Reply via email to