Merge "mw.Map: add ability to map over an existing object other than 'window'"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 7 Nov 2013 15:58:42 +0000 (15:58 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 7 Nov 2013 15:58:42 +0000 (15:58 +0000)
1  2 
resources/mediawiki/mediawiki.js

@@@ -21,7 -21,6 +21,7 @@@ var mw = ( function ( $, undefined ) 
         * fine. No need for optimization here, which would only result in losing logs.
         *
         * @private
 +       * @method log_
         * @param {string} msg text for the log entry.
         * @param {Error} [e]
         */
         * @class mw.Map
         *
         * @constructor
-        * @param {boolean} [global=false] Whether to store the values in the global window
-        *  object or a exclusively in the object property 'values'.
+        * @param {Object|boolean} [values] Value-bearing object to map, or boolean
+        *  true to map over the global object. Defaults to an empty object.
         */
-       function Map( global ) {
-               this.values = global === true ? window : {};
+       function Map( values ) {
+               this.values = values === true ? window : ( values || {} );
                return this;
        }
  
                }
        };
  
 +      /**
 +       * @class mw
 +       */
        return {
                /* Public Members */
  
                                }
  
                                if ( registry[module].state === 'ready' ) {
 -                                      // The current module became 'ready'. Recursively execute all dependent modules that are loaded
 -                                      // and now have all dependencies satisfied.
 +                                      // The current module became 'ready'. Set it in the module store, and recursively execute all
 +                                      // dependent modules that are loaded and now have all dependencies satisfied.
 +                                      mw.loader.store.set( module, registry[module] );
                                        for ( m in registry ) {
                                                if ( registry[m].state === 'loaded' && allReady( registry[m].dependencies ) ) {
                                                        execute( m );
                                addScript( sourceLoadScript + '?' + $.param( request ) + '&*', null, async );
                        }
  
 -                      /* Public Methods */
 +                      /* Public Members */
                        return {
                                /**
                                 * The module registry is exposed as an aid for debugging and inspecting page
                                 */
                                work: function () {
                                        var     reqBase, splits, maxQueryLength, q, b, bSource, bGroup, bSourceGroup,
 -                                              source, group, g, i, modules, maxVersion, sourceLoadScript,
 +                                              source, concatSource, group, g, i, modules, maxVersion, sourceLoadScript,
                                                currReqBase, currReqBaseLength, moduleMap, l,
                                                lastDotIndex, prefix, suffix, bytesAdded, async;
  
                                                        }
                                                }
                                        }
 +
 +                                      mw.loader.store.init();
 +                                      if ( mw.loader.store.enabled ) {
 +                                              concatSource = [];
 +                                              batch = $.grep( batch, function ( module ) {
 +                                                      var source = mw.loader.store.get( module );
 +                                                      if ( source ) {
 +                                                              concatSource.push( source );
 +                                                              return false;
 +                                                      }
 +                                                      return true;
 +                                              } );
 +                                              $.globalEval( concatSource.join( ';' ) );
 +                                      }
 +
                                        // Early exit if there's nothing to load...
                                        if ( !batch.length ) {
                                                return;
                                 * @param {Function|Array} script Function with module code or Array of URLs to
                                 *  be used as the src attribute of a new `<script>` tag.
                                 * @param {Object} style Should follow one of the following patterns:
 +                               *
                                 *     { "css": [css, ..] }
                                 *     { "url": { <media>: [url, ..] } }
 +                               *
                                 * And for backwards compatibility (needs to be supported forever due to caching):
 +                               *
                                 *     { <media>: css }
                                 *     { <media>: [url, ..] }
                                 *
                                 * The reason css strings are not concatenated anymore is bug 31676. We now check
                                 * whether it's safe to extend the stylesheet (see #canExpandStylesheetWith).
                                 *
 -                               * @param {Object} msgs List of key/value pairs to be added to {@link mw#messages}.
 +                               * @param {Object} msgs List of key/value pairs to be added to mw#messages.
                                 */
                                implement: function ( module, script, style, msgs ) {
                                        // Validate input
                                        mw.loader.using( 'mediawiki.inspect', function () {
                                                mw.inspect.runReports.apply( mw.inspect, args );
                                        } );
 -                              }
 +                              },
  
 +                              /**
 +                               * On browsers that implement the localStorage API, the module store serves as a
 +                               * smart complement to the browser cache. Unlike the browser cache, the module store
 +                               * can slice a concatenated response from ResourceLoader into its constituent
 +                               * modules and cache each of them separately, using each module's versioning scheme
 +                               * to determine when the cache should be invalidated.
 +                               *
 +                               * @singleton
 +                               * @class mw.loader.store
 +                               */
 +                              store: {
 +                                      // Whether the store is in use on this page.
 +                                      enabled: null,
 +
 +                                      // The contents of the store, mapping '[module name]@[version]' keys
 +                                      // to module implementations.
 +                                      items: {},
 +
 +                                      // Cache hit stats
 +                                      stats: { hits: 0, misses: 0, expired: 0 },
 +
 +                                      /**
 +                                       * Construct a JSON-serializable object representing the content of the store.
 +                                       * @return {Object} Module store contents.
 +                                       */
 +                                      toJSON: function () {
 +                                              return { items: mw.loader.store.items, vary: mw.loader.store.getVary() };
 +                                      },
 +
 +                                      /**
 +                                       * Get the localStorage key for the entire module store. The key references
 +                                       * $wgDBname to prevent clashes between wikis which share a common host.
 +                                       *
 +                                       * @return {string} localStorage item key
 +                                       */
 +                                      getStoreKey: function () {
 +                                              return 'MediaWikiModuleStore:' + mw.config.get( 'wgDBname' );
 +                                      },
 +
 +                                      /**
 +                                       * Get a string key on which to vary the module cache.
 +                                       * @return {string} String of concatenated vary conditions.
 +                                       */
 +                                      getVary: function () {
 +                                              return [
 +                                                      mw.config.get( 'skin' ),
 +                                                      mw.config.get( 'wgResourceLoaderStorageVersion' ),
 +                                                      mw.config.get( 'wgUserLanguage' )
 +                                              ].join(':');
 +                                      },
 +
 +                                      /**
 +                                       * Get a string key for a specific module. The key format is '[name]@[version]'.
 +                                       *
 +                                       * @param {string} module Module name
 +                                       * @return {string|null} Module key or null if module does not exist
 +                                       */
 +                                      getModuleKey: function ( module ) {
 +                                              return typeof registry[module] === 'object' ?
 +                                                      ( module + '@' + registry[module].version ) : null;
 +                                      },
 +
 +                                      /**
 +                                       * Initialize the store by retrieving it from localStorage and (if successfully
 +                                       * retrieved) decoding the stored JSON value to a plain object.
 +                                       *
 +                                       * The try / catch block is used for JSON & localStorage feature detection.
 +                                       * See the in-line documentation for Modernizr's localStorage feature detection
 +                                       * code for a full account of why we need a try / catch: <http://git.io/4NEwKg>.
 +                                       */
 +                                      init: function () {
 +                                              var raw, data, optedIn;
 +
 +                                              if ( mw.loader.store.enabled !== null ) {
 +                                                      // #init already ran.
 +                                                      return;
 +                                              }
 +
 +                                              // Temporarily allow users to opt-in during mw.loader.store test phase by
 +                                              // manually setting a cookie (bug 56397).
 +                                              optedIn = /ResourceLoaderStorageEnabled=1/.test( document.cookie );
 +
 +                                              if ( !( mw.config.get( 'wgResourceLoaderStorageEnabled' ) || optedIn ) || mw.config.get( 'debug' ) ) {
 +                                                      // Disabled by configuration, or because debug mode is set.
 +                                                      mw.loader.store.enabled = false;
 +                                                      return;
 +                                              }
 +
 +                                              try {
 +                                                      raw = localStorage.getItem( mw.loader.store.getStoreKey() );
 +                                                      // If we get here, localStorage is available; mark enabled.
 +                                                      mw.loader.store.enabled = true;
 +                                                      data = JSON.parse( raw );
 +                                                      if ( data && typeof data.items === 'object' && data.vary === mw.loader.store.getVary() ) {
 +                                                              mw.loader.store.items = data.items;
 +                                                              return;
 +                                                      }
 +                                              } catch (e) {}
 +
 +                                              if ( raw === undefined ) {
 +                                                      mw.loader.store.enabled = false;  // localStorage failed; disable store.
 +                                              } else {
 +                                                      mw.loader.store.update();
 +                                              }
 +                                      },
 +
 +                                      /**
 +                                       * Retrieve a module from the store and update cache hit stats.
 +                                       *
 +                                       * @param {string} module Module name
 +                                       * @return {string|boolean} Module implementation or false if unavailable
 +                                       */
 +                                      get: function ( module ) {
 +                                              var key;
 +
 +                                              if ( mw.loader.store.enabled !== true ) {
 +                                                      return false;
 +                                              }
 +
 +                                              key = mw.loader.store.getModuleKey( module );
 +                                              if ( key in mw.loader.store.items ) {
 +                                                      mw.loader.store.stats.hits++;
 +                                                      return mw.loader.store.items[key];
 +                                              }
 +                                              mw.loader.store.stats.misses++;
 +                                              return false;
 +                                      },
 +
 +                                      /**
 +                                       * Stringify a module and queue it for storage.
 +                                       *
 +                                       * @param {string} module Module name
 +                                       * @param {Object} descriptor The module's descriptor as set in the registry
 +                                       */
 +                                      set: function ( module, descriptor ) {
 +                                              var args, key;
 +
 +                                              if ( mw.loader.store.enabled !== true ) {
 +                                                      return false;
 +                                              }
 +
 +                                              key = mw.loader.store.getModuleKey( module );
 +
 +                                              if ( key in mw.loader.store.items ) {
 +                                                      // Already set; decline to store.
 +                                                      return false;
 +                                              }
 +
 +                                              if ( descriptor.state !== 'ready' ) {
 +                                                      // Module failed to load; decline to store.
 +                                                      return false;
 +                                              }
 +
 +                                              if ( !descriptor.version || $.inArray( descriptor.group, [ 'private', 'user', 'site' ] ) !== -1 ) {
 +                                                      // Unversioned, private, or site-/user-specific; decline to store.
 +                                                      return false;
 +                                              }
 +
 +                                              if ( $.inArray( undefined, [ descriptor.script, descriptor.style, descriptor.messages ] ) !== -1 ) {
 +                                                      // Partial descriptor; decline to store.
 +                                                      return false;
 +                                              }
 +
 +                                              try {
 +                                                      args = [
 +                                                              JSON.stringify( module ),
 +                                                              typeof descriptor.script === 'function' ?
 +                                                                      String( descriptor.script ) : JSON.stringify( descriptor.script ),
 +                                                              JSON.stringify( descriptor.style ),
 +                                                              JSON.stringify( descriptor.messages )
 +                                                      ];
 +                                              } catch (e) {
 +                                                      return;
 +                                              }
 +                                              mw.loader.store.items[key] = 'mw.loader.implement(' + args.join(',') + ');';
 +                                              mw.loader.store.update();
 +                                      },
 +
 +                                      /**
 +                                       * Iterate through the module store, removing any item that does not correspond
 +                                       * (in name and version) to an item in the module registry.
 +                                       */
 +                                      prune: function () {
 +                                              var key, module;
 +
 +                                              if ( mw.loader.store.enabled !== true ) {
 +                                                      return false;
 +                                              }
 +
 +                                              for ( key in mw.loader.store.items ) {
 +                                                      module = key.substring( 0, key.indexOf( '@' ) );
 +                                                      if ( mw.loader.store.getModuleKey( module ) !== key ) {
 +                                                              mw.loader.store.stats.expired++;
 +                                                              delete mw.loader.store.items[key];
 +                                                      }
 +                                              }
 +                                      },
 +
 +                                      /**
 +                                       * Sync modules to localStorage.
 +                                       *
 +                                       * This function debounces localStorage updates. When called multiple times in
 +                                       * quick succession, the calls are coalesced into a single update operation.
 +                                       * This allows us to call #update without having to consider the module load
 +                                       * queue; the call to localStorage.setItem will be naturally deferred until the
 +                                       * page is quiescent.
 +                                       *
 +                                       * Because localStorage is shared by all pages with the same origin, if multiple
 +                                       * pages are loaded with different module sets, the possibility exists that
 +                                       * modules saved by one page will be clobbered by another. But the impact would
 +                                       * be minor and the problem would be corrected by subsequent page views.
 +                                       */
 +                                      update: ( function () {
 +                                              var timer;
 +
 +                                              function flush() {
 +                                                      var data;
 +                                                      if ( mw.loader.store.enabled !== true ) {
 +                                                              return false;
 +                                                      }
 +                                                      mw.loader.store.prune();
 +                                                      try {
 +                                                              data = JSON.stringify( mw.loader.store );
 +                                                              localStorage.setItem( mw.loader.store.getStoreKey(), data );
 +                                                      } catch (e) {}
 +                                              }
 +
 +                                              return function () {
 +                                                      clearTimeout( timer );
 +                                                      timer = setTimeout( flush, 2000 );
 +                                              };
 +                                      }() )
 +                              }
                        };
                }() ),