resourceloader: spin base module code out as a proper module
authorAaron Schulz <aschulz@wikimedia.org>
Wed, 20 Jun 2018 15:14:28 +0000 (16:14 +0100)
committerAaron Schulz <aschulz@wikimedia.org>
Mon, 25 Jun 2018 15:05:29 +0000 (16:05 +0100)
Bug: T192623
Change-Id: I6f7dc40488a990d0f8a25e84ebc9eb25ad4c2975

includes/resourceloader/ResourceLoaderStartUpModule.php
maintenance/jsduck/eg-iframe.html
resources/Resources.php
resources/src/mediawiki.base/mediawiki.base.js [new file with mode: 0644]
resources/src/mediawiki.base/mediawiki.errorLogger.js [new file with mode: 0644]
resources/src/mediawiki/mediawiki.base.js [deleted file]
resources/src/mediawiki/mediawiki.errorLogger.js [deleted file]

index 0416c85..4777545 100644 (file)
@@ -336,7 +336,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
         * @return array
         */
        public static function getStartupModules() {
-               return [ 'jquery', 'mediawiki' ];
+               return [ 'jquery', 'mediawiki', 'mediawiki.base' ];
        }
 
        public static function getLegacyModules() {
index f19a69b..8475dca 100644 (file)
@@ -43,8 +43,8 @@
        </script>
        <script src="modules/lib/jquery/jquery.js"></script>
        <script src="modules/src/mediawiki/mediawiki.js"></script>
-       <script src="modules/src/mediawiki/mediawiki.base.js"></script>
-       <script src="modules/src/mediawiki/mediawiki.errorLogger.js"></script>
+       <script src="modules/src/mediawiki.base/mediawiki.base.js"></script>
+       <script src="modules/src/mediawiki.base/mediawiki.errorLogger.js"></script>
        <script src="modules/lib/oojs/oojs.jquery.js"></script>
        <script src="modules/lib/oojs-ui/oojs-ui-core.js"></script>
        <script src="modules/lib/oojs-ui/oojs-ui-widgets.js"></script>
index b7ecd10..258434e 100644 (file)
@@ -848,12 +848,18 @@ return [
                'scripts' => [
                        'resources/src/mediawiki/mediawiki.js',
                        'resources/src/mediawiki/mediawiki.requestIdleCallback.js',
-                       'resources/src/mediawiki/mediawiki.errorLogger.js',
-                       'resources/src/mediawiki/mediawiki.base.js',
                ],
                'debugScripts' => 'resources/src/mediawiki/mediawiki.log.js',
                'targets' => [ 'desktop', 'mobile' ],
        ],
+       'mediawiki.base' => [
+               // Keep in sync with maintenance/jsduck/eg-iframe.html
+               'scripts' => [
+                       'resources/src/mediawiki.base/mediawiki.errorLogger.js',
+                       'resources/src/mediawiki.base/mediawiki.base.js',
+               ],
+               'targets' => [ 'desktop', 'mobile' ],
+       ],
        'mediawiki.apihelp' => [
                'styles' => 'resources/src/mediawiki.apihelp.css',
                'targets' => [ 'desktop' ],
diff --git a/resources/src/mediawiki.base/mediawiki.base.js b/resources/src/mediawiki.base/mediawiki.base.js
new file mode 100644 (file)
index 0000000..ed24af3
--- /dev/null
@@ -0,0 +1,644 @@
+/*!
+ * This file is currently loaded as part of the 'mediawiki' module and therefore
+ * concatenated to mediawiki.js and executed at the same time. This file exists
+ * to help prepare for splitting up the 'mediawiki' module.
+ * This effort is tracked at https://phabricator.wikimedia.org/T192623
+ *
+ * In short:
+ *
+ * - mediawiki.js will be reduced to the minimum needed to define mw.loader and
+ *   mw.config, and then moved to its own private "mediawiki.loader" module that
+ *   can be embedded within the StartupModule response.
+ *
+ * - mediawiki.base.js and other files in this directory will remain part of the
+ *   "mediawiki" module, and will remain a default/implicit dependency for all
+ *   regular modules, just like jquery and wikibits already are.
+ */
+/* globals mw */
+( function () {
+       'use strict';
+
+       var slice = Array.prototype.slice,
+               mwLoaderTrack = mw.track,
+               trackCallbacks = $.Callbacks( 'memory' ),
+               trackHandlers = [],
+               hasOwn = Object.prototype.hasOwnProperty;
+
+       /**
+        * Object constructor for messages.
+        *
+        * Similar to the Message class in MediaWiki PHP.
+        *
+        * Format defaults to 'text'.
+        *
+        *     @example
+        *
+        *     var obj, str;
+        *     mw.messages.set( {
+        *         'hello': 'Hello world',
+        *         'hello-user': 'Hello, $1!',
+        *         'welcome-user': 'Welcome back to $2, $1! Last visit by $1: $3'
+        *     } );
+        *
+        *     obj = new mw.Message( mw.messages, 'hello' );
+        *     mw.log( obj.text() );
+        *     // Hello world
+        *
+        *     obj = new mw.Message( mw.messages, 'hello-user', [ 'John Doe' ] );
+        *     mw.log( obj.text() );
+        *     // Hello, John Doe!
+        *
+        *     obj = new mw.Message( mw.messages, 'welcome-user', [ 'John Doe', 'Wikipedia', '2 hours ago' ] );
+        *     mw.log( obj.text() );
+        *     // Welcome back to Wikipedia, John Doe! Last visit by John Doe: 2 hours ago
+        *
+        *     // Using mw.message shortcut
+        *     obj = mw.message( 'hello-user', 'John Doe' );
+        *     mw.log( obj.text() );
+        *     // Hello, John Doe!
+        *
+        *     // Using mw.msg shortcut
+        *     str = mw.msg( 'hello-user', 'John Doe' );
+        *     mw.log( str );
+        *     // Hello, John Doe!
+        *
+        *     // Different formats
+        *     obj = new mw.Message( mw.messages, 'hello-user', [ 'John "Wiki" <3 Doe' ] );
+        *
+        *     obj.format = 'text';
+        *     str = obj.toString();
+        *     // Same as:
+        *     str = obj.text();
+        *
+        *     mw.log( str );
+        *     // Hello, John "Wiki" <3 Doe!
+        *
+        *     mw.log( obj.escaped() );
+        *     // Hello, John &quot;Wiki&quot; &lt;3 Doe!
+        *
+        * @class mw.Message
+        *
+        * @constructor
+        * @param {mw.Map} map Message store
+        * @param {string} key
+        * @param {Array} [parameters]
+        */
+       function Message( map, key, parameters ) {
+               this.format = 'text';
+               this.map = map;
+               this.key = key;
+               this.parameters = parameters === undefined ? [] : slice.call( parameters );
+               return this;
+       }
+
+       Message.prototype = {
+               /**
+                * Get parsed contents of the message.
+                *
+                * The default parser does simple $N replacements and nothing else.
+                * This may be overridden to provide a more complex message parser.
+                * The primary override is in the mediawiki.jqueryMsg module.
+                *
+                * This function will not be called for nonexistent messages.
+                *
+                * @return {string} Parsed message
+                */
+               parser: function () {
+                       return mw.format.apply( null, [ this.map.get( this.key ) ].concat( this.parameters ) );
+               },
+
+               /**
+                * Add (does not replace) parameters for `$N` placeholder values.
+                *
+                * @param {Array} parameters
+                * @return {mw.Message}
+                * @chainable
+                */
+               params: function ( parameters ) {
+                       var i;
+                       for ( i = 0; i < parameters.length; i++ ) {
+                               this.parameters.push( parameters[ i ] );
+                       }
+                       return this;
+               },
+
+               /**
+                * Convert message object to its string form based on current format.
+                *
+                * @return {string} Message as a string in the current form, or `<key>` if key
+                *  does not exist.
+                */
+               toString: function () {
+                       var text;
+
+                       if ( !this.exists() ) {
+                               // Use ⧼key⧽ as text if key does not exist
+                               // Err on the side of safety, ensure that the output
+                               // is always html safe in the event the message key is
+                               // missing, since in that case its highly likely the
+                               // message key is user-controlled.
+                               // '⧼' is used instead of '<' to side-step any
+                               // double-escaping issues.
+                               // (Keep synchronised with Message::toString() in PHP.)
+                               return '⧼' + mw.html.escape( this.key ) + '⧽';
+                       }
+
+                       if ( this.format === 'plain' || this.format === 'text' || this.format === 'parse' ) {
+                               text = this.parser();
+                       }
+
+                       if ( this.format === 'escaped' ) {
+                               text = this.parser();
+                               text = mw.html.escape( text );
+                       }
+
+                       return text;
+               },
+
+               /**
+                * Change format to 'parse' and convert message to string
+                *
+                * If jqueryMsg is loaded, this parses the message text from wikitext
+                * (where supported) to HTML
+                *
+                * Otherwise, it is equivalent to plain.
+                *
+                * @return {string} String form of parsed message
+                */
+               parse: function () {
+                       this.format = 'parse';
+                       return this.toString();
+               },
+
+               /**
+                * Change format to 'plain' and convert message to string
+                *
+                * This substitutes parameters, but otherwise does not change the
+                * message text.
+                *
+                * @return {string} String form of plain message
+                */
+               plain: function () {
+                       this.format = 'plain';
+                       return this.toString();
+               },
+
+               /**
+                * Change format to 'text' and convert message to string
+                *
+                * If jqueryMsg is loaded, {{-transformation is done where supported
+                * (such as {{plural:}}, {{gender:}}, {{int:}}).
+                *
+                * Otherwise, it is equivalent to plain
+                *
+                * @return {string} String form of text message
+                */
+               text: function () {
+                       this.format = 'text';
+                       return this.toString();
+               },
+
+               /**
+                * Change the format to 'escaped' and convert message to string
+                *
+                * This is equivalent to using the 'text' format (see #text), then
+                * HTML-escaping the output.
+                *
+                * @return {string} String form of html escaped message
+                */
+               escaped: function () {
+                       this.format = 'escaped';
+                       return this.toString();
+               },
+
+               /**
+                * Check if a message exists
+                *
+                * @see mw.Map#exists
+                * @return {boolean}
+                */
+               exists: function () {
+                       return this.map.exists( this.key );
+               }
+       };
+
+       /**
+        * @class mw
+        * @singleton
+        */
+
+       /**
+        * @inheritdoc mw.inspect#runReports
+        * @method
+        */
+       mw.inspect = function () {
+               var args = arguments;
+               mw.loader.using( 'mediawiki.inspect', function () {
+                       mw.inspect.runReports.apply( mw.inspect, args );
+               } );
+       };
+
+       /**
+        * Format a string. Replace $1, $2 ... $N with positional arguments.
+        *
+        * Used by Message#parser().
+        *
+        * @since 1.25
+        * @param {string} formatString Format string
+        * @param {...Mixed} parameters Values for $N replacements
+        * @return {string} Formatted string
+        */
+       mw.format = function ( formatString ) {
+               var parameters = slice.call( arguments, 1 );
+               return formatString.replace( /\$(\d+)/g, function ( str, match ) {
+                       var index = parseInt( match, 10 ) - 1;
+                       return parameters[ index ] !== undefined ? parameters[ index ] : '$' + match;
+               } );
+       };
+
+       // Expose Message constructor
+       mw.Message = Message;
+
+       /**
+        * Get a message object.
+        *
+        * Shortcut for `new mw.Message( mw.messages, key, parameters )`.
+        *
+        * @see mw.Message
+        * @param {string} key Key of message to get
+        * @param {...Mixed} parameters Values for $N replacements
+        * @return {mw.Message}
+        */
+       mw.message = function ( key ) {
+               var parameters = slice.call( arguments, 1 );
+               return new Message( mw.messages, key, parameters );
+       };
+
+       /**
+        * Get a message string using the (default) 'text' format.
+        *
+        * Shortcut for `mw.message( key, parameters... ).text()`.
+        *
+        * @see mw.Message
+        * @param {string} key Key of message to get
+        * @param {...Mixed} parameters Values for $N replacements
+        * @return {string}
+        */
+       mw.msg = function () {
+               return mw.message.apply( mw.message, arguments ).toString();
+       };
+
+       /**
+        * Track an analytic event.
+        *
+        * This method provides a generic means for MediaWiki JavaScript code to capture state
+        * information for analysis. Each logged event specifies a string topic name that describes
+        * the kind of event that it is. Topic names consist of dot-separated path components,
+        * arranged from most general to most specific. Each path component should have a clear and
+        * well-defined purpose.
+        *
+        * Data handlers are registered via `mw.trackSubscribe`, and receive the full set of
+        * events that match their subcription, including those that fired before the handler was
+        * bound.
+        *
+        * @param {string} topic Topic name
+        * @param {Object} [data] Data describing the event, encoded as an object
+        */
+       mw.track = function ( topic, data ) {
+               mwLoaderTrack( topic, data );
+               trackCallbacks.fire( mw.trackQueue );
+       };
+
+       /**
+        * Register a handler for subset of analytic events, specified by topic.
+        *
+        * Handlers will be called once for each tracked event, including any events that fired before the
+        * handler was registered; 'this' is set to a plain object with a 'timeStamp' property indicating
+        * the exact time at which the event fired, a string 'topic' property naming the event, and a
+        * 'data' property which is an object of event-specific data. The event topic and event data are
+        * also passed to the callback as the first and second arguments, respectively.
+        *
+        * @param {string} topic Handle events whose name starts with this string prefix
+        * @param {Function} callback Handler to call for each matching tracked event
+        * @param {string} callback.topic
+        * @param {Object} [callback.data]
+        */
+       mw.trackSubscribe = function ( topic, callback ) {
+               var seen = 0;
+               function handler( trackQueue ) {
+                       var event;
+                       for ( ; seen < trackQueue.length; seen++ ) {
+                               event = trackQueue[ seen ];
+                               if ( event.topic.indexOf( topic ) === 0 ) {
+                                       callback.call( event, event.topic, event.data );
+                               }
+                       }
+               }
+
+               trackHandlers.push( [ handler, callback ] );
+
+               trackCallbacks.add( handler );
+       };
+
+       /**
+        * Stop handling events for a particular handler
+        *
+        * @param {Function} callback
+        */
+       mw.trackUnsubscribe = function ( callback ) {
+               trackHandlers = trackHandlers.filter( function ( fns ) {
+                       if ( fns[ 1 ] === callback ) {
+                               trackCallbacks.remove( fns[ 0 ] );
+                               // Ensure the tuple is removed to avoid holding on to closures
+                               return false;
+                       }
+                       return true;
+               } );
+       };
+
+       // Fire events from before track() triggred fire()
+       trackCallbacks.fire( mw.trackQueue );
+
+       /**
+        * Registry and firing of events.
+        *
+        * MediaWiki has various interface components that are extended, enhanced
+        * or manipulated in some other way by extensions, gadgets and even
+        * in core itself.
+        *
+        * This framework helps streamlining the timing of when these other
+        * code paths fire their plugins (instead of using document-ready,
+        * which can and should be limited to firing only once).
+        *
+        * Features like navigating to other wiki pages, previewing an edit
+        * and editing itself – without a refresh – can then retrigger these
+        * hooks accordingly to ensure everything still works as expected.
+        *
+        * Example usage:
+        *
+        *     mw.hook( 'wikipage.content' ).add( fn ).remove( fn );
+        *     mw.hook( 'wikipage.content' ).fire( $content );
+        *
+        * Handlers can be added and fired for arbitrary event names at any time. The same
+        * event can be fired multiple times. The last run of an event is memorized
+        * (similar to `$(document).ready` and `$.Deferred().done`).
+        * This means if an event is fired, and a handler added afterwards, the added
+        * function will be fired right away with the last given event data.
+        *
+        * Like Deferreds and Promises, the mw.hook object is both detachable and chainable.
+        * Thus allowing flexible use and optimal maintainability and authority control.
+        * You can pass around the `add` and/or `fire` method to another piece of code
+        * without it having to know the event name (or `mw.hook` for that matter).
+        *
+        *     var h = mw.hook( 'bar.ready' );
+        *     new mw.Foo( .. ).fetch( { callback: h.fire } );
+        *
+        * Note: Events are documented with an underscore instead of a dot in the event
+        * name due to jsduck not supporting dots in that position.
+        *
+        * @class mw.hook
+        */
+       mw.hook = ( function () {
+               var lists = {};
+
+               /**
+                * Create an instance of mw.hook.
+                *
+                * @method hook
+                * @member mw
+                * @param {string} name Name of hook.
+                * @return {mw.hook}
+                */
+               return function ( name ) {
+                       var list = hasOwn.call( lists, name ) ?
+                               lists[ name ] :
+                               lists[ name ] = $.Callbacks( 'memory' );
+
+                       return {
+                               /**
+                                * Register a hook handler
+                                *
+                                * @param {...Function} handler Function to bind.
+                                * @chainable
+                                */
+                               add: list.add,
+
+                               /**
+                                * Unregister a hook handler
+                                *
+                                * @param {...Function} handler Function to unbind.
+                                * @chainable
+                                */
+                               remove: list.remove,
+
+                               /**
+                                * Run a hook.
+                                *
+                                * @param {...Mixed} data
+                                * @return {mw.hook}
+                                * @chainable
+                                */
+                               fire: function () {
+                                       return list.fireWith.call( this, null, slice.call( arguments ) );
+                               }
+                       };
+               };
+       }() );
+
+       /**
+        * HTML construction helper functions
+        *
+        *     @example
+        *
+        *     var Html, output;
+        *
+        *     Html = mw.html;
+        *     output = Html.element( 'div', {}, new Html.Raw(
+        *         Html.element( 'img', { src: '<' } )
+        *     ) );
+        *     mw.log( output ); // <div><img src="&lt;"/></div>
+        *
+        * @class mw.html
+        * @singleton
+        */
+       mw.html = ( function () {
+               function escapeCallback( s ) {
+                       switch ( s ) {
+                               case '\'':
+                                       return '&#039;';
+                               case '"':
+                                       return '&quot;';
+                               case '<':
+                                       return '&lt;';
+                               case '>':
+                                       return '&gt;';
+                               case '&':
+                                       return '&amp;';
+                       }
+               }
+
+               return {
+                       /**
+                        * Escape a string for HTML.
+                        *
+                        * Converts special characters to HTML entities.
+                        *
+                        *     mw.html.escape( '< > \' & "' );
+                        *     // Returns &lt; &gt; &#039; &amp; &quot;
+                        *
+                        * @param {string} s The string to escape
+                        * @return {string} HTML
+                        */
+                       escape: function ( s ) {
+                               return s.replace( /['"<>&]/g, escapeCallback );
+                       },
+
+                       /**
+                        * Create an HTML element string, with safe escaping.
+                        *
+                        * @param {string} name The tag name.
+                        * @param {Object} [attrs] An object with members mapping element names to values
+                        * @param {string|mw.html.Raw|mw.html.Cdata|null} [contents=null] The contents of the element.
+                        *
+                        *  - string: Text to be escaped.
+                        *  - null: The element is treated as void with short closing form, e.g. `<br/>`.
+                        *  - this.Raw: The raw value is directly included.
+                        *  - this.Cdata: The raw value is directly included. An exception is
+                        *    thrown if it contains any illegal ETAGO delimiter.
+                        *    See <https://www.w3.org/TR/html401/appendix/notes.html#h-B.3.2>.
+                        * @return {string} HTML
+                        */
+                       element: function ( name, attrs, contents ) {
+                               var v, attrName, s = '<' + name;
+
+                               if ( attrs ) {
+                                       for ( attrName in attrs ) {
+                                               v = attrs[ attrName ];
+                                               // Convert name=true, to name=name
+                                               if ( v === true ) {
+                                                       v = attrName;
+                                                       // Skip name=false
+                                               } else if ( v === false ) {
+                                                       continue;
+                                               }
+                                               s += ' ' + attrName + '="' + this.escape( String( v ) ) + '"';
+                                       }
+                               }
+                               if ( contents === undefined || contents === null ) {
+                                       // Self close tag
+                                       s += '/>';
+                                       return s;
+                               }
+                               // Regular open tag
+                               s += '>';
+                               switch ( typeof contents ) {
+                                       case 'string':
+                                               // Escaped
+                                               s += this.escape( contents );
+                                               break;
+                                       case 'number':
+                                       case 'boolean':
+                                               // Convert to string
+                                               s += String( contents );
+                                               break;
+                                       default:
+                                               if ( contents instanceof this.Raw ) {
+                                                       // Raw HTML inclusion
+                                                       s += contents.value;
+                                               } else if ( contents instanceof this.Cdata ) {
+                                                       // CDATA
+                                                       if ( /<\/[a-zA-z]/.test( contents.value ) ) {
+                                                               throw new Error( 'mw.html.element: Illegal end tag found in CDATA' );
+                                                       }
+                                                       s += contents.value;
+                                               } else {
+                                                       throw new Error( 'mw.html.element: Invalid type of contents' );
+                                               }
+                               }
+                               s += '</' + name + '>';
+                               return s;
+                       },
+
+                       /**
+                        * Wrapper object for raw HTML passed to mw.html.element().
+                        *
+                        * @class mw.html.Raw
+                        * @constructor
+                        * @param {string} value
+                        */
+                       Raw: function ( value ) {
+                               this.value = value;
+                       },
+
+                       /**
+                        * Wrapper object for CDATA element contents passed to mw.html.element()
+                        *
+                        * @class mw.html.Cdata
+                        * @constructor
+                        * @param {string} value
+                        */
+                       Cdata: function ( value ) {
+                               this.value = value;
+                       }
+               };
+       }() );
+
+       /**
+        * Execute a function as soon as one or more required modules are ready.
+        *
+        * Example of inline dependency on OOjs:
+        *
+        *     mw.loader.using( 'oojs', function () {
+        *         OO.compare( [ 1 ], [ 1 ] );
+        *     } );
+        *
+        * Example of inline dependency obtained via `require()`:
+        *
+        *     mw.loader.using( [ 'mediawiki.util' ], function ( require ) {
+        *         var util = require( 'mediawiki.util' );
+        *     } );
+        *
+        * Since MediaWiki 1.23 this also returns a promise.
+        *
+        * Since MediaWiki 1.28 the promise is resolved with a `require` function.
+        *
+        * @member mw.loader
+        * @param {string|Array} dependencies Module name or array of modules names the
+        *  callback depends on to be ready before executing
+        * @param {Function} [ready] Callback to execute when all dependencies are ready
+        * @param {Function} [error] Callback to execute if one or more dependencies failed
+        * @return {jQuery.Promise} With a `require` function
+        */
+       mw.loader.using = function ( dependencies, ready, error ) {
+               var deferred = $.Deferred();
+
+               // Allow calling with a single dependency as a string
+               if ( typeof dependencies === 'string' ) {
+                       dependencies = [ dependencies ];
+               }
+
+               if ( ready ) {
+                       deferred.done( ready );
+               }
+               if ( error ) {
+                       deferred.fail( error );
+               }
+
+               try {
+                       // Resolve entire dependency map
+                       dependencies = mw.loader.resolve( dependencies );
+               } catch ( e ) {
+                       return deferred.reject( e ).promise();
+               }
+
+               mw.loader.enqueue( dependencies, function () {
+                       deferred.resolve( mw.loader.require );
+               }, deferred.reject );
+
+               return deferred.promise();
+       };
+
+       // Alias $j to jQuery for backwards compatibility
+       // @deprecated since 1.23 Use $ or jQuery instead
+       mw.log.deprecate( window, '$j', $, 'Use $ or jQuery instead.' );
+}() );
diff --git a/resources/src/mediawiki.base/mediawiki.errorLogger.js b/resources/src/mediawiki.base/mediawiki.errorLogger.js
new file mode 100644 (file)
index 0000000..e86aff6
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * Try to catch errors in modules which don't do their own error handling.
+ *
+ * @class mw.errorLogger
+ * @singleton
+ */
+( function ( mw ) {
+       'use strict';
+
+       mw.errorLogger = {
+               /**
+                * Fired via mw.track when an error is not handled by local code and is caught by the
+                * window.onerror handler.
+                *
+                * @event global_error
+                * @param {string} errorMessage Error errorMessage.
+                * @param {string} url URL where error was raised.
+                * @param {number} lineNumber Line number where error was raised.
+                * @param {number} [columnNumber] Line number where error was raised. Not all browsers
+                *   support this.
+                * @param {Error|Mixed} [errorObject] The error object. Typically an instance of Error, but anything
+                *   (even a primitive value) passed to a throw clause will end up here.
+                */
+
+               /**
+                * Install a window.onerror handler that will report via mw.track, while preserving
+                * any previous handler.
+                *
+                * @param {Object} window
+                */
+               installGlobalHandler: function ( window ) {
+                       // We will preserve the return value of the previous handler. window.onerror works the
+                       // opposite way than normal event handlers (returning true will prevent the default
+                       // action, returning false will let the browser handle the error normally, by e.g.
+                       // logging to the console), so our fallback old handler needs to return false.
+                       var oldHandler = window.onerror || function () { return false; };
+
+                       /**
+                        * Dumb window.onerror handler which forwards the errors via mw.track.
+                        *
+                        * @param {string} errorMessage
+                        * @param {string} url
+                        * @param {number} lineNumber
+                        * @param {number} [columnNumber]
+                        * @param {Error|Mixed} [errorObject]
+                        * @return {boolean} True to prevent the default action
+                        * @fires global_error
+                        */
+                       window.onerror = function ( errorMessage, url, lineNumber, columnNumber, errorObject ) {
+                               mw.track( 'global.error', { errorMessage: errorMessage, url: url,
+                                       lineNumber: lineNumber, columnNumber: columnNumber, errorObject: errorObject } );
+                               return oldHandler.apply( this, arguments );
+                       };
+               }
+       };
+
+       mw.errorLogger.installGlobalHandler( window );
+}( mediaWiki ) );
diff --git a/resources/src/mediawiki/mediawiki.base.js b/resources/src/mediawiki/mediawiki.base.js
deleted file mode 100644 (file)
index ed24af3..0000000
+++ /dev/null
@@ -1,644 +0,0 @@
-/*!
- * This file is currently loaded as part of the 'mediawiki' module and therefore
- * concatenated to mediawiki.js and executed at the same time. This file exists
- * to help prepare for splitting up the 'mediawiki' module.
- * This effort is tracked at https://phabricator.wikimedia.org/T192623
- *
- * In short:
- *
- * - mediawiki.js will be reduced to the minimum needed to define mw.loader and
- *   mw.config, and then moved to its own private "mediawiki.loader" module that
- *   can be embedded within the StartupModule response.
- *
- * - mediawiki.base.js and other files in this directory will remain part of the
- *   "mediawiki" module, and will remain a default/implicit dependency for all
- *   regular modules, just like jquery and wikibits already are.
- */
-/* globals mw */
-( function () {
-       'use strict';
-
-       var slice = Array.prototype.slice,
-               mwLoaderTrack = mw.track,
-               trackCallbacks = $.Callbacks( 'memory' ),
-               trackHandlers = [],
-               hasOwn = Object.prototype.hasOwnProperty;
-
-       /**
-        * Object constructor for messages.
-        *
-        * Similar to the Message class in MediaWiki PHP.
-        *
-        * Format defaults to 'text'.
-        *
-        *     @example
-        *
-        *     var obj, str;
-        *     mw.messages.set( {
-        *         'hello': 'Hello world',
-        *         'hello-user': 'Hello, $1!',
-        *         'welcome-user': 'Welcome back to $2, $1! Last visit by $1: $3'
-        *     } );
-        *
-        *     obj = new mw.Message( mw.messages, 'hello' );
-        *     mw.log( obj.text() );
-        *     // Hello world
-        *
-        *     obj = new mw.Message( mw.messages, 'hello-user', [ 'John Doe' ] );
-        *     mw.log( obj.text() );
-        *     // Hello, John Doe!
-        *
-        *     obj = new mw.Message( mw.messages, 'welcome-user', [ 'John Doe', 'Wikipedia', '2 hours ago' ] );
-        *     mw.log( obj.text() );
-        *     // Welcome back to Wikipedia, John Doe! Last visit by John Doe: 2 hours ago
-        *
-        *     // Using mw.message shortcut
-        *     obj = mw.message( 'hello-user', 'John Doe' );
-        *     mw.log( obj.text() );
-        *     // Hello, John Doe!
-        *
-        *     // Using mw.msg shortcut
-        *     str = mw.msg( 'hello-user', 'John Doe' );
-        *     mw.log( str );
-        *     // Hello, John Doe!
-        *
-        *     // Different formats
-        *     obj = new mw.Message( mw.messages, 'hello-user', [ 'John "Wiki" <3 Doe' ] );
-        *
-        *     obj.format = 'text';
-        *     str = obj.toString();
-        *     // Same as:
-        *     str = obj.text();
-        *
-        *     mw.log( str );
-        *     // Hello, John "Wiki" <3 Doe!
-        *
-        *     mw.log( obj.escaped() );
-        *     // Hello, John &quot;Wiki&quot; &lt;3 Doe!
-        *
-        * @class mw.Message
-        *
-        * @constructor
-        * @param {mw.Map} map Message store
-        * @param {string} key
-        * @param {Array} [parameters]
-        */
-       function Message( map, key, parameters ) {
-               this.format = 'text';
-               this.map = map;
-               this.key = key;
-               this.parameters = parameters === undefined ? [] : slice.call( parameters );
-               return this;
-       }
-
-       Message.prototype = {
-               /**
-                * Get parsed contents of the message.
-                *
-                * The default parser does simple $N replacements and nothing else.
-                * This may be overridden to provide a more complex message parser.
-                * The primary override is in the mediawiki.jqueryMsg module.
-                *
-                * This function will not be called for nonexistent messages.
-                *
-                * @return {string} Parsed message
-                */
-               parser: function () {
-                       return mw.format.apply( null, [ this.map.get( this.key ) ].concat( this.parameters ) );
-               },
-
-               /**
-                * Add (does not replace) parameters for `$N` placeholder values.
-                *
-                * @param {Array} parameters
-                * @return {mw.Message}
-                * @chainable
-                */
-               params: function ( parameters ) {
-                       var i;
-                       for ( i = 0; i < parameters.length; i++ ) {
-                               this.parameters.push( parameters[ i ] );
-                       }
-                       return this;
-               },
-
-               /**
-                * Convert message object to its string form based on current format.
-                *
-                * @return {string} Message as a string in the current form, or `<key>` if key
-                *  does not exist.
-                */
-               toString: function () {
-                       var text;
-
-                       if ( !this.exists() ) {
-                               // Use ⧼key⧽ as text if key does not exist
-                               // Err on the side of safety, ensure that the output
-                               // is always html safe in the event the message key is
-                               // missing, since in that case its highly likely the
-                               // message key is user-controlled.
-                               // '⧼' is used instead of '<' to side-step any
-                               // double-escaping issues.
-                               // (Keep synchronised with Message::toString() in PHP.)
-                               return '⧼' + mw.html.escape( this.key ) + '⧽';
-                       }
-
-                       if ( this.format === 'plain' || this.format === 'text' || this.format === 'parse' ) {
-                               text = this.parser();
-                       }
-
-                       if ( this.format === 'escaped' ) {
-                               text = this.parser();
-                               text = mw.html.escape( text );
-                       }
-
-                       return text;
-               },
-
-               /**
-                * Change format to 'parse' and convert message to string
-                *
-                * If jqueryMsg is loaded, this parses the message text from wikitext
-                * (where supported) to HTML
-                *
-                * Otherwise, it is equivalent to plain.
-                *
-                * @return {string} String form of parsed message
-                */
-               parse: function () {
-                       this.format = 'parse';
-                       return this.toString();
-               },
-
-               /**
-                * Change format to 'plain' and convert message to string
-                *
-                * This substitutes parameters, but otherwise does not change the
-                * message text.
-                *
-                * @return {string} String form of plain message
-                */
-               plain: function () {
-                       this.format = 'plain';
-                       return this.toString();
-               },
-
-               /**
-                * Change format to 'text' and convert message to string
-                *
-                * If jqueryMsg is loaded, {{-transformation is done where supported
-                * (such as {{plural:}}, {{gender:}}, {{int:}}).
-                *
-                * Otherwise, it is equivalent to plain
-                *
-                * @return {string} String form of text message
-                */
-               text: function () {
-                       this.format = 'text';
-                       return this.toString();
-               },
-
-               /**
-                * Change the format to 'escaped' and convert message to string
-                *
-                * This is equivalent to using the 'text' format (see #text), then
-                * HTML-escaping the output.
-                *
-                * @return {string} String form of html escaped message
-                */
-               escaped: function () {
-                       this.format = 'escaped';
-                       return this.toString();
-               },
-
-               /**
-                * Check if a message exists
-                *
-                * @see mw.Map#exists
-                * @return {boolean}
-                */
-               exists: function () {
-                       return this.map.exists( this.key );
-               }
-       };
-
-       /**
-        * @class mw
-        * @singleton
-        */
-
-       /**
-        * @inheritdoc mw.inspect#runReports
-        * @method
-        */
-       mw.inspect = function () {
-               var args = arguments;
-               mw.loader.using( 'mediawiki.inspect', function () {
-                       mw.inspect.runReports.apply( mw.inspect, args );
-               } );
-       };
-
-       /**
-        * Format a string. Replace $1, $2 ... $N with positional arguments.
-        *
-        * Used by Message#parser().
-        *
-        * @since 1.25
-        * @param {string} formatString Format string
-        * @param {...Mixed} parameters Values for $N replacements
-        * @return {string} Formatted string
-        */
-       mw.format = function ( formatString ) {
-               var parameters = slice.call( arguments, 1 );
-               return formatString.replace( /\$(\d+)/g, function ( str, match ) {
-                       var index = parseInt( match, 10 ) - 1;
-                       return parameters[ index ] !== undefined ? parameters[ index ] : '$' + match;
-               } );
-       };
-
-       // Expose Message constructor
-       mw.Message = Message;
-
-       /**
-        * Get a message object.
-        *
-        * Shortcut for `new mw.Message( mw.messages, key, parameters )`.
-        *
-        * @see mw.Message
-        * @param {string} key Key of message to get
-        * @param {...Mixed} parameters Values for $N replacements
-        * @return {mw.Message}
-        */
-       mw.message = function ( key ) {
-               var parameters = slice.call( arguments, 1 );
-               return new Message( mw.messages, key, parameters );
-       };
-
-       /**
-        * Get a message string using the (default) 'text' format.
-        *
-        * Shortcut for `mw.message( key, parameters... ).text()`.
-        *
-        * @see mw.Message
-        * @param {string} key Key of message to get
-        * @param {...Mixed} parameters Values for $N replacements
-        * @return {string}
-        */
-       mw.msg = function () {
-               return mw.message.apply( mw.message, arguments ).toString();
-       };
-
-       /**
-        * Track an analytic event.
-        *
-        * This method provides a generic means for MediaWiki JavaScript code to capture state
-        * information for analysis. Each logged event specifies a string topic name that describes
-        * the kind of event that it is. Topic names consist of dot-separated path components,
-        * arranged from most general to most specific. Each path component should have a clear and
-        * well-defined purpose.
-        *
-        * Data handlers are registered via `mw.trackSubscribe`, and receive the full set of
-        * events that match their subcription, including those that fired before the handler was
-        * bound.
-        *
-        * @param {string} topic Topic name
-        * @param {Object} [data] Data describing the event, encoded as an object
-        */
-       mw.track = function ( topic, data ) {
-               mwLoaderTrack( topic, data );
-               trackCallbacks.fire( mw.trackQueue );
-       };
-
-       /**
-        * Register a handler for subset of analytic events, specified by topic.
-        *
-        * Handlers will be called once for each tracked event, including any events that fired before the
-        * handler was registered; 'this' is set to a plain object with a 'timeStamp' property indicating
-        * the exact time at which the event fired, a string 'topic' property naming the event, and a
-        * 'data' property which is an object of event-specific data. The event topic and event data are
-        * also passed to the callback as the first and second arguments, respectively.
-        *
-        * @param {string} topic Handle events whose name starts with this string prefix
-        * @param {Function} callback Handler to call for each matching tracked event
-        * @param {string} callback.topic
-        * @param {Object} [callback.data]
-        */
-       mw.trackSubscribe = function ( topic, callback ) {
-               var seen = 0;
-               function handler( trackQueue ) {
-                       var event;
-                       for ( ; seen < trackQueue.length; seen++ ) {
-                               event = trackQueue[ seen ];
-                               if ( event.topic.indexOf( topic ) === 0 ) {
-                                       callback.call( event, event.topic, event.data );
-                               }
-                       }
-               }
-
-               trackHandlers.push( [ handler, callback ] );
-
-               trackCallbacks.add( handler );
-       };
-
-       /**
-        * Stop handling events for a particular handler
-        *
-        * @param {Function} callback
-        */
-       mw.trackUnsubscribe = function ( callback ) {
-               trackHandlers = trackHandlers.filter( function ( fns ) {
-                       if ( fns[ 1 ] === callback ) {
-                               trackCallbacks.remove( fns[ 0 ] );
-                               // Ensure the tuple is removed to avoid holding on to closures
-                               return false;
-                       }
-                       return true;
-               } );
-       };
-
-       // Fire events from before track() triggred fire()
-       trackCallbacks.fire( mw.trackQueue );
-
-       /**
-        * Registry and firing of events.
-        *
-        * MediaWiki has various interface components that are extended, enhanced
-        * or manipulated in some other way by extensions, gadgets and even
-        * in core itself.
-        *
-        * This framework helps streamlining the timing of when these other
-        * code paths fire their plugins (instead of using document-ready,
-        * which can and should be limited to firing only once).
-        *
-        * Features like navigating to other wiki pages, previewing an edit
-        * and editing itself – without a refresh – can then retrigger these
-        * hooks accordingly to ensure everything still works as expected.
-        *
-        * Example usage:
-        *
-        *     mw.hook( 'wikipage.content' ).add( fn ).remove( fn );
-        *     mw.hook( 'wikipage.content' ).fire( $content );
-        *
-        * Handlers can be added and fired for arbitrary event names at any time. The same
-        * event can be fired multiple times. The last run of an event is memorized
-        * (similar to `$(document).ready` and `$.Deferred().done`).
-        * This means if an event is fired, and a handler added afterwards, the added
-        * function will be fired right away with the last given event data.
-        *
-        * Like Deferreds and Promises, the mw.hook object is both detachable and chainable.
-        * Thus allowing flexible use and optimal maintainability and authority control.
-        * You can pass around the `add` and/or `fire` method to another piece of code
-        * without it having to know the event name (or `mw.hook` for that matter).
-        *
-        *     var h = mw.hook( 'bar.ready' );
-        *     new mw.Foo( .. ).fetch( { callback: h.fire } );
-        *
-        * Note: Events are documented with an underscore instead of a dot in the event
-        * name due to jsduck not supporting dots in that position.
-        *
-        * @class mw.hook
-        */
-       mw.hook = ( function () {
-               var lists = {};
-
-               /**
-                * Create an instance of mw.hook.
-                *
-                * @method hook
-                * @member mw
-                * @param {string} name Name of hook.
-                * @return {mw.hook}
-                */
-               return function ( name ) {
-                       var list = hasOwn.call( lists, name ) ?
-                               lists[ name ] :
-                               lists[ name ] = $.Callbacks( 'memory' );
-
-                       return {
-                               /**
-                                * Register a hook handler
-                                *
-                                * @param {...Function} handler Function to bind.
-                                * @chainable
-                                */
-                               add: list.add,
-
-                               /**
-                                * Unregister a hook handler
-                                *
-                                * @param {...Function} handler Function to unbind.
-                                * @chainable
-                                */
-                               remove: list.remove,
-
-                               /**
-                                * Run a hook.
-                                *
-                                * @param {...Mixed} data
-                                * @return {mw.hook}
-                                * @chainable
-                                */
-                               fire: function () {
-                                       return list.fireWith.call( this, null, slice.call( arguments ) );
-                               }
-                       };
-               };
-       }() );
-
-       /**
-        * HTML construction helper functions
-        *
-        *     @example
-        *
-        *     var Html, output;
-        *
-        *     Html = mw.html;
-        *     output = Html.element( 'div', {}, new Html.Raw(
-        *         Html.element( 'img', { src: '<' } )
-        *     ) );
-        *     mw.log( output ); // <div><img src="&lt;"/></div>
-        *
-        * @class mw.html
-        * @singleton
-        */
-       mw.html = ( function () {
-               function escapeCallback( s ) {
-                       switch ( s ) {
-                               case '\'':
-                                       return '&#039;';
-                               case '"':
-                                       return '&quot;';
-                               case '<':
-                                       return '&lt;';
-                               case '>':
-                                       return '&gt;';
-                               case '&':
-                                       return '&amp;';
-                       }
-               }
-
-               return {
-                       /**
-                        * Escape a string for HTML.
-                        *
-                        * Converts special characters to HTML entities.
-                        *
-                        *     mw.html.escape( '< > \' & "' );
-                        *     // Returns &lt; &gt; &#039; &amp; &quot;
-                        *
-                        * @param {string} s The string to escape
-                        * @return {string} HTML
-                        */
-                       escape: function ( s ) {
-                               return s.replace( /['"<>&]/g, escapeCallback );
-                       },
-
-                       /**
-                        * Create an HTML element string, with safe escaping.
-                        *
-                        * @param {string} name The tag name.
-                        * @param {Object} [attrs] An object with members mapping element names to values
-                        * @param {string|mw.html.Raw|mw.html.Cdata|null} [contents=null] The contents of the element.
-                        *
-                        *  - string: Text to be escaped.
-                        *  - null: The element is treated as void with short closing form, e.g. `<br/>`.
-                        *  - this.Raw: The raw value is directly included.
-                        *  - this.Cdata: The raw value is directly included. An exception is
-                        *    thrown if it contains any illegal ETAGO delimiter.
-                        *    See <https://www.w3.org/TR/html401/appendix/notes.html#h-B.3.2>.
-                        * @return {string} HTML
-                        */
-                       element: function ( name, attrs, contents ) {
-                               var v, attrName, s = '<' + name;
-
-                               if ( attrs ) {
-                                       for ( attrName in attrs ) {
-                                               v = attrs[ attrName ];
-                                               // Convert name=true, to name=name
-                                               if ( v === true ) {
-                                                       v = attrName;
-                                                       // Skip name=false
-                                               } else if ( v === false ) {
-                                                       continue;
-                                               }
-                                               s += ' ' + attrName + '="' + this.escape( String( v ) ) + '"';
-                                       }
-                               }
-                               if ( contents === undefined || contents === null ) {
-                                       // Self close tag
-                                       s += '/>';
-                                       return s;
-                               }
-                               // Regular open tag
-                               s += '>';
-                               switch ( typeof contents ) {
-                                       case 'string':
-                                               // Escaped
-                                               s += this.escape( contents );
-                                               break;
-                                       case 'number':
-                                       case 'boolean':
-                                               // Convert to string
-                                               s += String( contents );
-                                               break;
-                                       default:
-                                               if ( contents instanceof this.Raw ) {
-                                                       // Raw HTML inclusion
-                                                       s += contents.value;
-                                               } else if ( contents instanceof this.Cdata ) {
-                                                       // CDATA
-                                                       if ( /<\/[a-zA-z]/.test( contents.value ) ) {
-                                                               throw new Error( 'mw.html.element: Illegal end tag found in CDATA' );
-                                                       }
-                                                       s += contents.value;
-                                               } else {
-                                                       throw new Error( 'mw.html.element: Invalid type of contents' );
-                                               }
-                               }
-                               s += '</' + name + '>';
-                               return s;
-                       },
-
-                       /**
-                        * Wrapper object for raw HTML passed to mw.html.element().
-                        *
-                        * @class mw.html.Raw
-                        * @constructor
-                        * @param {string} value
-                        */
-                       Raw: function ( value ) {
-                               this.value = value;
-                       },
-
-                       /**
-                        * Wrapper object for CDATA element contents passed to mw.html.element()
-                        *
-                        * @class mw.html.Cdata
-                        * @constructor
-                        * @param {string} value
-                        */
-                       Cdata: function ( value ) {
-                               this.value = value;
-                       }
-               };
-       }() );
-
-       /**
-        * Execute a function as soon as one or more required modules are ready.
-        *
-        * Example of inline dependency on OOjs:
-        *
-        *     mw.loader.using( 'oojs', function () {
-        *         OO.compare( [ 1 ], [ 1 ] );
-        *     } );
-        *
-        * Example of inline dependency obtained via `require()`:
-        *
-        *     mw.loader.using( [ 'mediawiki.util' ], function ( require ) {
-        *         var util = require( 'mediawiki.util' );
-        *     } );
-        *
-        * Since MediaWiki 1.23 this also returns a promise.
-        *
-        * Since MediaWiki 1.28 the promise is resolved with a `require` function.
-        *
-        * @member mw.loader
-        * @param {string|Array} dependencies Module name or array of modules names the
-        *  callback depends on to be ready before executing
-        * @param {Function} [ready] Callback to execute when all dependencies are ready
-        * @param {Function} [error] Callback to execute if one or more dependencies failed
-        * @return {jQuery.Promise} With a `require` function
-        */
-       mw.loader.using = function ( dependencies, ready, error ) {
-               var deferred = $.Deferred();
-
-               // Allow calling with a single dependency as a string
-               if ( typeof dependencies === 'string' ) {
-                       dependencies = [ dependencies ];
-               }
-
-               if ( ready ) {
-                       deferred.done( ready );
-               }
-               if ( error ) {
-                       deferred.fail( error );
-               }
-
-               try {
-                       // Resolve entire dependency map
-                       dependencies = mw.loader.resolve( dependencies );
-               } catch ( e ) {
-                       return deferred.reject( e ).promise();
-               }
-
-               mw.loader.enqueue( dependencies, function () {
-                       deferred.resolve( mw.loader.require );
-               }, deferred.reject );
-
-               return deferred.promise();
-       };
-
-       // Alias $j to jQuery for backwards compatibility
-       // @deprecated since 1.23 Use $ or jQuery instead
-       mw.log.deprecate( window, '$j', $, 'Use $ or jQuery instead.' );
-}() );
diff --git a/resources/src/mediawiki/mediawiki.errorLogger.js b/resources/src/mediawiki/mediawiki.errorLogger.js
deleted file mode 100644 (file)
index e86aff6..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * Try to catch errors in modules which don't do their own error handling.
- *
- * @class mw.errorLogger
- * @singleton
- */
-( function ( mw ) {
-       'use strict';
-
-       mw.errorLogger = {
-               /**
-                * Fired via mw.track when an error is not handled by local code and is caught by the
-                * window.onerror handler.
-                *
-                * @event global_error
-                * @param {string} errorMessage Error errorMessage.
-                * @param {string} url URL where error was raised.
-                * @param {number} lineNumber Line number where error was raised.
-                * @param {number} [columnNumber] Line number where error was raised. Not all browsers
-                *   support this.
-                * @param {Error|Mixed} [errorObject] The error object. Typically an instance of Error, but anything
-                *   (even a primitive value) passed to a throw clause will end up here.
-                */
-
-               /**
-                * Install a window.onerror handler that will report via mw.track, while preserving
-                * any previous handler.
-                *
-                * @param {Object} window
-                */
-               installGlobalHandler: function ( window ) {
-                       // We will preserve the return value of the previous handler. window.onerror works the
-                       // opposite way than normal event handlers (returning true will prevent the default
-                       // action, returning false will let the browser handle the error normally, by e.g.
-                       // logging to the console), so our fallback old handler needs to return false.
-                       var oldHandler = window.onerror || function () { return false; };
-
-                       /**
-                        * Dumb window.onerror handler which forwards the errors via mw.track.
-                        *
-                        * @param {string} errorMessage
-                        * @param {string} url
-                        * @param {number} lineNumber
-                        * @param {number} [columnNumber]
-                        * @param {Error|Mixed} [errorObject]
-                        * @return {boolean} True to prevent the default action
-                        * @fires global_error
-                        */
-                       window.onerror = function ( errorMessage, url, lineNumber, columnNumber, errorObject ) {
-                               mw.track( 'global.error', { errorMessage: errorMessage, url: url,
-                                       lineNumber: lineNumber, columnNumber: columnNumber, errorObject: errorObject } );
-                               return oldHandler.apply( this, arguments );
-                       };
-               }
-       };
-
-       mw.errorLogger.installGlobalHandler( window );
-}( mediaWiki ) );