Catrope has uploaded a new change for review.

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

Change subject: [WIP] Factor out loading code into TargetLoader
......................................................................

[WIP] Factor out loading code into TargetLoader

TODO:
* Write commit summary
* Refactor activate concept into something sensible
* Deal with errors
* Merge onLoad/onReady/onNoticesReady
* Keep CSS transitions etc working
* Find a place to call generateCitationFeatures() from

Change-Id: Ibfa6abbeaf872ae2aadc6ed9d5beba7473ea441a
---
M VisualEditor.hooks.php
M VisualEditor.php
M modules/ve-mw/init/targets/ve.init.mw.ViewPageTarget.init.js
M modules/ve-mw/init/targets/ve.init.mw.ViewPageTarget.js
M modules/ve-mw/init/ve.init.mw.Target.js
A modules/ve-mw/init/ve.init.mw.TargetLoader.js
6 files changed, 234 insertions(+), 193 deletions(-)


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

diff --git a/VisualEditor.hooks.php b/VisualEditor.hooks.php
index 13c09ab..2c4b69b 100644
--- a/VisualEditor.hooks.php
+++ b/VisualEditor.hooks.php
@@ -46,7 +46,7 @@
         * @return boolean
         */
        public static function onBeforePageDisplay( OutputPage &$output, Skin 
&$skin ) {
-               $output->addModules( array( 
'ext.visualEditor.viewPageTarget.init' ) );
+               $output->addModules( array( 
'ext.visualEditor.viewPageTarget.init', 'ext.visualEditor.targetLoader' ) );
                $output->addModuleStyles( array( 
'ext.visualEditor.viewPageTarget.noscript' ) );
                return true;
        }
diff --git a/VisualEditor.php b/VisualEditor.php
index f2ce216..25b25c4 100644
--- a/VisualEditor.php
+++ b/VisualEditor.php
@@ -183,6 +183,17 @@
                'styles' => 
'modules/ve-mw/init/styles/ve.init.mw.ViewPageTarget.noscript.css',
        ),
 
+       'ext.visualEditor.targetLoader' => $wgVisualEditorResourceTemplate + 
array(
+               'scripts' => 'modules/ve-mw/init/ve.init.mw.TargetLoader.js',
+               'dependencies' => array(
+                       'mediawiki.util',
+                       'mediawiki.api',
+                       'ext.visualEditor.track',
+                       'ext.visualEditor.veinitmw',
+               ),
+               'targets' => array( 'desktop', 'mobile' ),
+       ),
+
        'ext.visualEditor.viewPageTarget' => $wgVisualEditorResourceTemplate + 
array(
                'scripts' => array(
                        
'modules/ve-mw/init/targets/ve.init.mw.ViewPageTarget.js',
@@ -257,6 +268,18 @@
                'targets' => array( 'desktop', 'mobile' ),
        ),
 
+       'ext.visualEditor.veinit' => $wgVisualEditorResourceTemplate + array(
+               'scripts' => 'lib/ve/src/init/ve.init.js',
+               'dependencies' => 'ext.visualEditor.ve',
+               'targets' => array( 'desktop', 'mobile' ),
+       ),
+
+       'ext.visualEditor.veinitmw' => $wgVisualEditorResourceTemplate + array(
+               'scripts' => 'modules/ve-mw/init/ve.init.mw.js',
+               'dependencies' => 'ext.visualEditor.veinit',
+               'targets' => array( 'desktop', 'mobile' ),
+       ),
+
        'ext.visualEditor.base' => $wgVisualEditorResourceTemplate + array(
                'scripts' => array(
                        // ve
@@ -264,7 +287,6 @@
                        'lib/ve/src/ve.TriggerListener.js',
 
                        // init
-                       'lib/ve/src/init/ve.init.js',
                        'lib/ve/src/init/ve.init.Platform.js',
                        'lib/ve/src/init/ve.init.Target.js',
                ),
@@ -275,7 +297,7 @@
                        'oojs',
                        'oojs-ui',
                        'unicodejs',
-                       'ext.visualEditor.ve',
+                       'ext.visualEditor.veinit',
                ),
                'targets' => array( 'desktop', 'mobile' ),
        ),
@@ -283,7 +305,6 @@
        'ext.visualEditor.mediawiki' => $wgVisualEditorResourceTemplate + array(
                'scripts' => array(
                        // init
-                       'modules/ve-mw/init/ve.init.mw.js',
                        'modules/ve-mw/init/ve.init.mw.ApiResponseCache.js',
                        'modules/ve-mw/init/ve.init.mw.LinkCache.js',
                        'modules/ve-mw/init/ve.init.mw.ImageInfoCache.js',
@@ -296,6 +317,7 @@
                        'modules/ve-mw/init/styles/ve.init.mw.Target.css',
                ),
                'dependencies' => array(
+                       'ext.visualEditor.veinitmw',
                        'jquery.visibleText',
                        'jquery.byteLength',
                        'jquery.client',
diff --git a/modules/ve-mw/init/targets/ve.init.mw.ViewPageTarget.init.js 
b/modules/ve-mw/init/targets/ve.init.mw.ViewPageTarget.init.js
index 262cdbc..f298030 100644
--- a/modules/ve-mw/init/targets/ve.init.mw.ViewPageTarget.init.js
+++ b/modules/ve-mw/init/targets/ve.init.mw.ViewPageTarget.init.js
@@ -41,16 +41,31 @@
        function getTarget() {
                showLoading();
                if ( !targetPromise ) {
-                       targetPromise = mw.loader.using( 
'ext.visualEditor.viewPageTarget' )
+                       // The TargetLoader module is loaded in the bottom 
queue, so it should have been
+                       // requested already but it might not have loaded yet
+                       targetPromise = mw.loader.using( 
'ext.visualEditor.targetLoader' )
                                .then( function () {
-                                       var target = new 
ve.init.mw.ViewPageTarget();
-                                       $( '#content' ).append( target.$element 
);
-
+                                       // Request modules specific to desktop 
(modules shared between desktop
+                                       // and mobile are already requested by 
TargetLoader)
+                                       ve.init.mw.targetLoader.addPlugins( [
+                                               
'ext.visualEditor.viewPageTarget',
+                                               'ext.visualEditor.mwformatting',
+                                               'ext.visualEditor.mwgallery',
+                                               'ext.visualEditor.mwimage',
+                                               'ext.visualEditor.mwmeta'
+                                       ] );
+                                       return ve.init.mw.targetLoader.load(
+                                               mw.config.get( 
'wgRelevantPageName' ),
+                                               uri.query.oldid
+                                       );
+                               } )
+                               .then( function ( data ) {
                                        // Transfer methods
                                        
ve.init.mw.ViewPageTarget.prototype.setupSectionEditLinks = 
init.setupSectionLinks;
 
-                                       // Add plugins
-                                       target.addPlugins( plugins );
+                                       var target = new 
ve.init.mw.ViewPageTarget();
+                                       $( '#content' ).append( target.$element 
);
+                                       target.handleResponse( data );
 
                                        return target;
                                }, function ( e ) {
@@ -136,8 +151,7 @@
                 *
                 * If it's a function, it will be invoked once the VisualEditor 
core modules and any
                 * plugin modules registered through this function have been 
loaded, but before the editor
-                * is intialized. The function takes one parameter, which is 
the ve.init.mw.Target instance
-                * that's initializing, and can optionally return a 
jQuery.Promise . VisualEditor will
+                * is intialized. The function can optionally return a 
jQuery.Promise . VisualEditor will
                 * only be initialized once all promises returned by plugin 
functions have been resolved.
                 *
                 *     @example
diff --git a/modules/ve-mw/init/targets/ve.init.mw.ViewPageTarget.js 
b/modules/ve-mw/init/targets/ve.init.mw.ViewPageTarget.js
index 392298e..d81c0f3 100644
--- a/modules/ve-mw/init/targets/ve.init.mw.ViewPageTarget.js
+++ b/modules/ve-mw/init/targets/ve.init.mw.ViewPageTarget.js
@@ -62,23 +62,6 @@
        this.originalDocumentTitle = document.title;
        this.tabLayout = mw.config.get( 'wgVisualEditorConfig' ).tabLayout;
 
-       // Add modules specific to desktop (modules shared with mobile go in 
MWTarget)
-       this.modules.push(
-               'ext.visualEditor.mwformatting',
-               'ext.visualEditor.mwgallery',
-               'ext.visualEditor.mwimage',
-               'ext.visualEditor.mwmeta'
-       );
-
-       // Load preference modules
-       for ( prefName in conf.preferenceModules ) {
-               prefValue = mw.user.options.get( prefName );
-               // Check "0" (T89513)
-               if ( prefValue && prefValue !== '0' ) {
-                       this.modules.push( conf.preferenceModules[prefName] );
-               }
-       }
-
        // Events
        this.connect( this, {
                save: 'onSave',
@@ -299,8 +282,6 @@
                this.setupLocalNoticeMessages();
 
                this.saveScrollPosition();
-
-               this.load( [ 'site', 'user' ] );
        }
        return this.activatingDeferred.promise();
 };
diff --git a/modules/ve-mw/init/ve.init.mw.Target.js 
b/modules/ve-mw/init/ve.init.mw.Target.js
index c96cea9..382dc18 100644
--- a/modules/ve-mw/init/ve.init.mw.Target.js
+++ b/modules/ve-mw/init/ve.init.mw.Target.js
@@ -22,8 +22,6 @@
        // Parent constructor
        ve.init.Target.call( this, { shadow: true, actions: true, floatable: 
true } );
 
-       var conf = mw.config.get( 'wgVisualEditorConfig' );
-
        // Properties
        this.pageName = pageName;
        this.pageExists = mw.config.get( 'wgArticleId', 0 ) !== 0;
@@ -39,20 +37,6 @@
                .extend( { action: 'submit' } );
        this.events = new ve.init.mw.TargetEvents( this );
 
-       this.modules = [
-               'ext.visualEditor.mwcore',
-               'ext.visualEditor.mwlink',
-               'ext.visualEditor.data',
-               'ext.visualEditor.mwreference.core',
-               'ext.visualEditor.mwtransclusion.core',
-               'ext.visualEditor.mwreference',
-               'ext.visualEditor.mwtransclusion'
-       ]
-               .concat( this.constructor.static.iconModuleStyles )
-               .concat( conf.pluginModules || [] );
-
-       this.pluginCallbacks = [];
-       this.modulesReady = $.Deferred();
        this.preparedCacheKeyPromise = null;
        this.clearState();
 };
@@ -256,17 +240,6 @@
 };
 
 /**
- * Defines modules needed to style icons.
- *
- * @static
- * @inheritable
- * @property {string[]} iconModuleStyles Modules that should be loaded to 
provide the icons
- */
-ve.init.mw.Target.static.iconModuleStyles = [
-       'ext.visualEditor.icons'
-];
-
-/**
  * Name of target class. Used by TargetEvents to identify which target we are 
tracking.
  *
  * @static
@@ -308,44 +281,14 @@
 };
 
 /**
- * Handle the RL modules for VE and registered plugin modules being loaded.
- *
- * This method is called within the context of a target instance. It executes 
all registered
- * plugin callbacks, gathers any promises returned and resolves 
this.modulesReady when all of
- * the gathered promises are resolved.
- */
-ve.init.mw.Target.onModulesReady = function () {
-       var i, len, callbackResult, promises = [];
-       for ( i = 0, len = this.pluginCallbacks.length; i < len; i++ ) {
-               callbackResult = this.pluginCallbacks[i]( this );
-               if ( callbackResult && callbackResult.then ) { // duck-type 
jQuery.Promise using .then
-                       promises.push( callbackResult );
-               }
-       }
-       this.generateCitationFeatures();
-       // Dereference the callbacks
-       this.pluginCallbacks = [];
-       // Add the platform promise to the list
-       promises.push( ve.init.platform.getInitializedPromise() );
-       // Create a master promise tracking all the promises we got, and wait 
for it
-       // to be resolved
-       $.when.apply( $, promises ).done( this.modulesReady.resolve ).fail( 
this.modulesReady.reject );
-};
-
-/**
  * Handle response to a successful load request.
  *
- * This method is called within the context of a target instance. If 
successful the DOM from the
- * server will be parsed, stored in {this.doc} and then {this.onReady} will be 
called once modules
- * are ready.
- *
- * @static
  * @method
  * @param {Object} response API response data
  * @param {string} status Text status message
  * @fires loadError
  */
-ve.init.mw.Target.onLoad = function ( response ) {
+ve.init.mw.Target.prototype.handleResponse = function ( response ) {
        var i, len, linkData,
                data = response ? response.visualeditor : null;
 
@@ -400,8 +343,8 @@
                }
 
                ve.track( 'trace.parseResponse.exit' );
-               // Everything worked, the page was loaded, continue as soon as 
the modules are loaded
-               this.modulesReady.done( this.onReady.bind( this ) );
+               // TODO consolidate
+               this.onReady();
        }
 };
 
@@ -860,32 +803,6 @@
 };
 
 /**
- * Add a plugin module or callback.
- *
- * @param {string|Function} plugin Plugin module or callback
- */
-ve.init.mw.Target.prototype.addPlugin = function ( plugin ) {
-       if ( typeof plugin === 'string' ) {
-               this.modules.push( plugin );
-       } else if ( $.isFunction( plugin ) ) {
-               this.pluginCallbacks.push( plugin );
-       }
-};
-
-/**
- * Add an array of plugins.
- *
- * @see #addPlugin
- * @param {Array} plugins
- */
-ve.init.mw.Target.prototype.addPlugins = function ( plugins ) {
-       var i, len;
-       for ( i = 0, len = plugins.length; i < len; i++ ) {
-               this.addPlugin( plugins[i] );
-       }
-};
-
-/**
  * Get HTML to send to Parsoid. This takes a document generated by the 
converter and
  * transplants the head tag from the old document into it, as well as the 
attributes on the
  * html and body tags.
@@ -927,82 +844,6 @@
                .remove();
        // Add doctype manually
        return '<!doctype html>' + ve.serializeXhtml( newDoc );
-};
-
-/**
- * Load the editor.
- *
- * This method performs an asynchronous action and uses a callback function to 
handle the result.
- *
- * A side-effect of calling this method is that it requests {this.modules} be 
loaded.
- *
- * @method
- * @param {string[]} [additionalModules=[]] Resource loader modules
- * @returns {boolean} Loading has been started
-*/
-ve.init.mw.Target.prototype.load = function ( additionalModules ) {
-       var modulesPromise, additionalModulesPromise, target = this;
-
-       // Prevent duplicate requests
-       if ( this.loading ) {
-               return false;
-       }
-       this.events.timings.activationStart = ve.now();
-       // Start loading the module immediately
-       modulesPromise = mw.loader.using( this.modules );
-       additionalModulesPromise = mw.loader.using( additionalModules || [] );
-
-       modulesPromise.done( function () {
-               // Wait for site and user JS before running plugins.
-               // These modules could fail to load, proceed even if they do.
-               additionalModulesPromise.always( function () {
-                       ve.init.mw.Target.onModulesReady.call( target );
-               } );
-       } );
-
-       this.loading = this.requestPageData();
-       this.loading
-               .done( ve.init.mw.Target.onLoad.bind( this ) )
-               .fail( ve.init.mw.Target.onLoadError.bind( this ) );
-
-       return true;
-};
-
-/**
- * Request the page HTML and various metadata from the MediaWiki API and 
Parsoid.
- * @return {jQuery.Promise} Abortable promise resolved with a JSON object
- */
-ve.init.mw.Target.prototype.requestPageData = function () {
-       var start, xhr,
-               target = this,
-               data = {
-                       action: 'visualeditor',
-                       paction: 'parse',
-                       page: this.pageName
-               };
-
-       // Only request the API to explicitly load the currently visible 
revision if we're restoring
-       // from oldid. Otherwise we should load the latest version. This 
prevents us from editing an
-       // old version if an edit was made while the user was viewing the page 
and/or the user is
-       // seeing (slightly) stale cache.
-       if ( this.restoring ) {
-               data.oldid = this.revid;
-       }
-       // Load DOM
-       start = ve.now();
-       ve.track( 'trace.domLoad.enter' );
-
-       xhr = new mw.Api().get( data );
-       return xhr.then( function ( data, jqxhr ) {
-               target.events.track( 'performance.system.domLoad', {
-                       bytes: $.byteLength( jqxhr.responseText ),
-                       duration: ve.now() - start,
-                       cacheHit: /hit/i.test( jqxhr.getResponseHeader( 
'X-Cache' ) ),
-                       parsoid: jqxhr.getResponseHeader( 
'X-Parsoid-Performance' )
-               } );
-               ve.track( 'trace.domLoad.exit' );
-               return jqxhr;
-       } ).promise( { abort: xhr.abort } );
 };
 
 /**
diff --git a/modules/ve-mw/init/ve.init.mw.TargetLoader.js 
b/modules/ve-mw/init/ve.init.mw.TargetLoader.js
new file mode 100644
index 0000000..2aee25d
--- /dev/null
+++ b/modules/ve-mw/init/ve.init.mw.TargetLoader.js
@@ -0,0 +1,183 @@
+/*!
+ * VisualEditor MediaWiki Initialization TargetLoader class.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * Target loader.
+ *
+ * Light-weight loader that loads ResourceLoader modules for VisualEditor
+ * and HTML and page data from the API. Also handles plugin registration.
+ *
+ * @class
+ *
+ * @constructor
+ */
+ve.init.mw.TargetLoader = function VeInitMwTargetLoader() {
+       var prefName, prefValue, conf = mw.config.get( 'wgVisualEditorConfig' );
+
+       this.pluginCallbacks = [];
+       this.modules = [
+               'ext.visualEditor.mwcore',
+               'ext.visualEditor.mwlink',
+               'ext.visualEditor.data',
+               'ext.visualEditor.mwreference',
+               'ext.visualEditor.mwtransclusion',
+               'ext.visualEditor.icons'
+       ]
+               // Add modules from $wgVisualEditorPluginModules
+               .concat( conf.pluginModules );
+
+       // Add preference modules
+       for ( prefName in conf.preferenceModules ) {
+               prefValue = mw.user.options.get( prefName );
+               // Check "0" (T89513)
+               if ( prefValue && prefValue !== '0' ) {
+                       this.modules.push( conf.preferenceModules[prefName] );
+               }
+       }
+};
+
+// Not using OO.initClass so that TargetLoader doesn't depend on OOjs
+ve.init.mw.TargetLoader.static = {};
+
+/* Static properties */
+
+/**
+ * URL to use for MediaWiki API requests.
+ *
+ * @property {string}
+ * @static
+ * @inheritable
+ */
+ve.init.mw.TargetLoader.static.apiUrl = mw.util.wikiScript( 'api' );
+
+/* Static methods */
+
+/* Methods */
+
+/**
+ * Add a plugin module or callback.
+ *
+ * If a module name is passed, that module will be loaded alongside the other 
modules.
+ *
+ * If a callback is passed, it will be executed after the modules have loaded. 
The callback
+ * may optionally return a jQuery.Promise; if it does, loading won't be 
complete until
+ * that promise is resolved.
+ *
+ * @param {string|Function} plugin Plugin module name or callback
+ */
+ve.init.mw.TargetLoader.prototype.addPlugin = function ( plugin ) {
+       if ( typeof plugin === 'string' ) {
+               this.modules.push( plugin );
+       } else if ( $.isFunction( plugin ) ) {
+               this.pluginCallbacks.push( plugin );
+       }
+};
+
+/**
+ * Add an array of plugins.
+ *
+ * @see #addPlugin
+ * @param {Array} plugins
+ */
+ve.init.mw.TargetLoader.prototype.addPlugins = function ( plugins ) {
+       var i, len;
+       for ( i = 0, len = plugins.length; i < len; i++ ) {
+               this.addPlugin( plugins[i] );
+       }
+};
+
+/**
+ * Load the editor.
+ *
+ * This loads the required ResourceLoader modules and the page HTML+metadata
+ * in parallel, executes plugins, then resolves the returned promise with the
+ * data from requestPageData().
+ *
+ * @param {string} pageName Page title to load data for
+ * @param {number} [oldid] Revision ID to load data for
+ * @return {jQuery.Promise} Abortable promise resolved with a JSON object.
+ */
+ve.init.mw.TargetLoader.prototype.load = function ( pageName, oldid ) {
+       var callbacks = this.pluginCallbacks,
+               dataPromise = this.requestPageData( pageName, oldid ),
+               modulesPromise = mw.loader.using( this.modules );
+
+       // Clear plugin callbacks
+       this.pluginCallbacks = [];
+
+       ve.track( 'trace.moduleLoad.enter' );
+       return mw.loader.using( [ 'user', 'site' ] )
+               // If the user and site modules fail, we still want to continue 
loading,
+               // so convert failure to success
+               .then( null, function () {
+                       return $.Deferred().resolve();
+               } )
+               .then( function () {
+                       return modulesPromise;
+               } )
+               .then( function () {
+                       ve.track( 'trace.moduleLoad.exit' );
+                       var i, len, result, promises = [
+                               ve.init.platform.getInitializedPromise()
+                       ];
+                       // Execute plugin callbacks and collect promises
+                       for ( i = 0, len = callbacks.length; i < len; i++ ) {
+                               result = callbacks[i]();
+                               if ( result && result.then ) { // duck-type 
jQuery.Promise
+                                       promises.push( result );
+                               }
+                       }
+                       return $.when.apply( $, promises );
+               } )
+               .then( function () {
+                       return dataPromise;
+               } )
+               .promise( { abort: dataPromise.abort } );
+};
+
+/**
+ * Request the page HTML and various metadata from the MediaWiki API and 
Parsoid.
+ * @param {string} pageName Page title to load data for
+ * @param {number} [oldid] Revision ID to load data for
+ * @return {jQuery.Promise} Abortable promise resolved with a JSON object
+ */
+ve.init.mw.TargetLoader.prototype.requestPageData = function ( pageName, oldid 
) {
+       var start, xhr,
+               data = {
+                       action: 'visualeditor',
+                       paction: 'parse',
+                       page: pageName
+               };
+
+       // Only request the API to explicitly load the currently visible 
revision if we're restoring
+       // from oldid. Otherwise we should load the latest version. This 
prevents us from editing an
+       // old version if an edit was made while the user was viewing the page 
and/or the user is
+       // seeing a (slightly) stale cache.
+       if ( oldid !== undefined ) {
+               data.oldid = oldid;
+       }
+       // Load DOM
+       start = ve.now();
+       ve.track( 'trace.domLoad.enter' );
+
+       xhr = new mw.Api().get( data );
+       return xhr.then(
+               function ( data, jqxhr ) {
+                       ve.track( 'mwtiming.performance.system.domLoad', {
+                               bytes: $.byteLength( jqxhr.responseText ),
+                               duration: ve.now() - start,
+                               cacheHit: /hit/i.test( jqxhr.getResponseHeader( 
'X-Cache' ) ),
+                               parsoid: jqxhr.getResponseHeader( 
'X-Parsoid-Performance' ),
+                               targetName: 'loader'
+                       } );
+                       ve.track( 'trace.domLoad.exit' );
+                       return data;
+               }
+       ).promise( { abort: xhr.abort } );
+};
+
+ve.init.mw.targetLoader = new ve.init.mw.TargetLoader();

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ibfa6abbeaf872ae2aadc6ed9d5beba7473ea441a
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