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