Optimize how user options are delivered to the client
authorOri Livneh <ori@wikimedia.org>
Thu, 4 Dec 2014 07:37:56 +0000 (23:37 -0800)
committerKrinkle <krinklemail@gmail.com>
Fri, 5 Dec 2014 19:36:45 +0000 (19:36 +0000)
We currently embed the full set of user options in a <script> tag in the HTML
output of every page. This is grossly inefficient, because the full set of
options is usually largely made up of site defaults which the user hasn't
customized.

So instead of doing that, let's emit the default options using one
ResourceLoader module and then apply the user's customizations on top.

This has the effect of slightly increasing the total bytes of JavaScript code
(because options that the user has customized will be emitted twice: once with
their default value in the user.defaults module, and then again with the
customized value in user.options). But this is more than offset by the
fact that the bulk of user options code (~4 kB uncompressed on enwiki) becomes
cacheable across requests.

Bonus round:
* Varnish gets to cache 4 kB fewer per page.
* Changes to the default options don't take 30 days to propagate.

Change-Id: I5a7e258d2d69159381bf5cc363227088b8fd6019

autoload.php
includes/User.php
includes/resourceloader/ResourceLoaderUserDefaultsModule.php [new file with mode: 0644]
includes/resourceloader/ResourceLoaderUserOptionsModule.php
resources/Resources.php

index dbc28ed..8003284 100644 (file)
@@ -961,6 +961,7 @@ $wgAutoloadLocalClasses = array(
        'ResourceLoaderUserCSSPrefsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php',
        'ResourceLoaderUserGroupsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserGroupsModule.php',
        'ResourceLoaderUserModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserModule.php',
+       'ResourceLoaderUserDefaultsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserDefaultsModule.php',
        'ResourceLoaderUserOptionsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserOptionsModule.php',
        'ResourceLoaderUserTokensModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserTokensModule.php',
        'ResourceLoaderWikiModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderWikiModule.php',
index 5348020..3cbb052 100644 (file)
@@ -58,6 +58,12 @@ class User implements IDBAccessObject {
         */
        const MAX_WATCHED_ITEMS_CACHE = 100;
 
+       /**
+        * Exclude user options that are set to their default value.
+        * @since 1.25
+        */
+       const GETOPTIONS_EXCLUDE_DEFAULTS = 1;
+
        /**
         * @var PasswordFactory Lazily loaded factory object for passwords
         */
@@ -2547,9 +2553,12 @@ class User implements IDBAccessObject {
        /**
         * Get all user's options
         *
+        * @param int $flags Bitwise combination of:
+        *   User::GETOPTIONS_EXCLUDE_DEFAULTS  Exclude user options that are set
+        *                                      to the default value. (Since 1.25)
         * @return array
         */
-       public function getOptions() {
+       public function getOptions( $flags = 0 ) {
                global $wgHiddenPrefs;
                $this->loadOptions();
                $options = $this->mOptions;
@@ -2566,6 +2575,10 @@ class User implements IDBAccessObject {
                        }
                }
 
+               if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
+                       $options = array_diff_assoc( $options, self::getDefaultOptions() );
+               }
+
                return $options;
        }
 
diff --git a/includes/resourceloader/ResourceLoaderUserDefaultsModule.php b/includes/resourceloader/ResourceLoaderUserDefaultsModule.php
new file mode 100644 (file)
index 0000000..1249cf8
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Resource loader module for default user preferences.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Ori Livneh
+ */
+
+/**
+ * Module for default user preferences.
+ */
+class ResourceLoaderUserDefaultsModule extends ResourceLoaderModule {
+
+       /* Protected Members */
+
+       protected $targets = array( 'desktop', 'mobile' );
+
+       /* Methods */
+
+       /**
+        * @param ResourceLoaderContext $context
+        * @return string Hash
+        */
+       public function getModifiedHash( ResourceLoaderContext $context ) {
+               return md5( serialize( User::getDefaultOptions() ) );
+       }
+
+       /**
+        * @param ResourceLoaderContext $context
+        * @return array|int|mixed
+        */
+       public function getModifiedTime( ResourceLoaderContext $context ) {
+               return $this->getHashMtime( $context );
+       }
+
+       /**
+        * @param ResourceLoaderContext $context
+        * @return string
+        */
+       public function getScript( ResourceLoaderContext $context ) {
+               return Xml::encodeJsCall( 'mw.user.options.set', array( User::getDefaultOptions() ) );
+       }
+}
index e6b07c1..a1847fb 100644 (file)
@@ -37,6 +37,13 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
 
        /* Methods */
 
+       /**
+        * @return array List of module names as strings
+        */
+       public function getDependencies() {
+               return array( 'user.defaults' );
+       }
+
        /**
         * @param ResourceLoaderContext $context
         * @return array|int|mixed
@@ -56,7 +63,7 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
         */
        public function getScript( ResourceLoaderContext $context ) {
                return Xml::encodeJsCall( 'mw.user.options.set',
-                       array( $context->getUserObj()->getOptions() ),
+                       array( $context->getUserObj()->getOptions( User::GETOPTIONS_EXCLUDE_DEFAULTS ) ),
                        ResourceLoader::inDebugMode()
                );
        }
index d6a3181..9c43987 100644 (file)
@@ -44,6 +44,7 @@ return array(
        'user.cssprefs' => array( 'class' => 'ResourceLoaderUserCSSPrefsModule' ),
 
        // Populate mediawiki.user placeholders with information about the current user
+       'user.defaults' => array( 'class' => 'ResourceLoaderUserDefaultsModule' ),
        'user.options' => array( 'class' => 'ResourceLoaderUserOptionsModule' ),
        'user.tokens' => array( 'class' => 'ResourceLoaderUserTokensModule' ),