Merge "(bug 32537) Pre-register default-loaded RL modules in the client-side loader."
authorBrion VIBBER <brion@wikimedia.org>
Wed, 25 Apr 2012 19:40:23 +0000 (19:40 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 25 Apr 2012 19:40:23 +0000 (19:40 +0000)
1  2 
includes/OutputPage.php
resources/mediawiki/mediawiki.js

diff --combined includes/OutputPage.php
@@@ -2573,12 -2573,24 +2573,24 @@@ $template
                                $extraQuery
                        );
                        $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
-                       // Drop modules that know they're empty
+                       // Extract modules that know they're empty
+                       $emptyModules = array ();
                        foreach ( $modules as $key => $module ) {
                                if ( $module->isKnownEmpty( $context ) ) {
+                                       $emptyModules[$key] = 'ready';
                                        unset( $modules[$key] );
                                }
                        }
+                       // Inline empty modules: since they're empty, just mark them as 'ready'
+                       if ( count( $emptyModules ) > 0 && $only !== ResourceLoaderModule::TYPE_STYLES ) {
+                               // If we're only getting the styles, we don't need to do anything for empty modules.
+                               $links .= Html::inlineScript(\r
+                                               ResourceLoader::makeLoaderConditionalScript(\r
+                                                               ResourceLoader::makeLoaderStateScript( $emptyModules )\r
+                                               )\r
+                               ) . "\n";
+                       }
                        // If there are no modules left, skip this group
                        if ( $modules === array() ) {
                                continue;
                                // Automatically select style/script elements
                                if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
                                        $link = Html::linkedStyle( $url );
-                               } else if ( $loadCall ) { 
+                               } else if ( $loadCall ) {
                                        $link = Html::inlineScript(
                                                ResourceLoader::makeLoaderConditionalScript(
                                                        Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
         */
        function getHeadScripts() {
                global $wgResourceLoaderExperimentalAsyncLoading;
-               
                // Startup - this will immediately load jquery and mediawiki modules
                $scripts = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
  
                                )
                        );
                }
-               
                if ( $wgResourceLoaderExperimentalAsyncLoading ) {
                        $scripts .= $this->getScriptsForBottomQueue( true );
                }
                // Legacy Scripts
                $scripts .= "\n" . $this->mScripts;
  
-               $userScripts = array();
+               $defaultModules = array();
  
                // Add site JS if enabled
                if ( $wgUseSiteJs ) {
                        $scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS,
                                /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
                        );
-                       if( $this->getUser()->isLoggedIn() ){
-                               $userScripts[] = 'user.groups';
-                       }
+                       $defaultModules['site'] = 'loading';
+               } else {
+                       // The wiki is configured to not allow a site module.
+                       $defaultModules['site'] = 'missing';
                }
  
                // Add user JS if enabled
-               if ( $wgAllowUserJs && $this->getUser()->isLoggedIn() ) {
-                       if( $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) {
-                               # XXX: additional security check/prompt?
-                               // We're on a preview of a JS subpage
-                               // Exclude this page from the user module in case it's in there (bug 26283)
-                               $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false,
-                                       array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
-                               );
-                               // Load the previewed JS
-                               $scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
+               if ( $wgAllowUserJs ) {
+                       if ( $this->getUser()->isLoggedIn() ) {
+                               if( $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) {
+                                       # XXX: additional security check/prompt?
+                                       // We're on a preview of a JS subpage
+                                       // Exclude this page from the user module in case it's in there (bug 26283)
+                                       $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false,
+                                               array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
+                                       );
+                                       // Load the previewed JS
+                                       $scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
+                                       // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded
+                                       // asynchronously and may arrive *after* the inline script here. So the previewed code
+                                       // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js...
+                               } else {
+                                       // Include the user module normally, i.e., raw to avoid it being wrapped in a closure.
+                                       $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
+                                               /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
+                                       );
+                               }
+                               $defaultModules['user'] = 'loading';
                        } else {
-                               // Include the user module normally
-                               // We can't do $userScripts[] = 'user'; because the user module would end up
-                               // being wrapped in a closure, so load it raw like 'site'
-                               $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
+                               // Non-logged-in users have no user module. Treat it as empty and 'ready' to avoid
+                               // blocking default gadgets that might depend on it. Although arguably default-enabled
+                               // gadgets should not depend on the user module, it's harmless and less error-prone to
+                               // handle this case.
+                               $defaultModules['user'] = 'ready';
+                       }
+               } else {
+                       // User JS diabled
+                       $defaultModules['user'] = 'missing';
+               }
+               // Group JS is only enabled if site JS is enabled.
+               if ( $wgUseSiteJs ) {
+                       if ( $this->getUser()->isLoggedIn() ) {
+                               $scripts .= $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED,
                                        /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
                                );
+                               $defaultModules['user.groups'] = 'loading';
+                       } else {
+                               // Non-logged-in users have no user.groups module. Treat it as empty and 'ready' to
+                               // avoid blocking gadgets that might depend upon the module.
+                               $defaultModules['user.groups'] = 'ready';
                        }
+               } else {
+                       // Site (and group JS) disabled
+                       $defaultModules['user.groups'] = 'missing';
                }
-               $scripts .= $this->makeResourceLoaderLink( $userScripts, ResourceLoaderModule::TYPE_COMBINED,
-                       /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
-               );
  
-               return $scripts;
+               $loaderInit = '';
+               if ( $inHead ) {
+                       // We generate loader calls anyway, so no need to fix the client-side loader's state to 'loading'.
+                       foreach ( $defaultModules as $m => $state ) {
+                               if ( $state == 'loading' ) {
+                                       unset( $defaultModules[$m] );
+                               }
+                       }
+               }
+               if ( count( $defaultModules ) > 0 ) {
+                       $loaderInit = Html::inlineScript(
+                               ResourceLoader::makeLoaderConditionalScript(
+                                       ResourceLoader::makeLoaderStateScript( $defaultModules )
+                               )
+                       ) . "\n";
+               }
+               return $loaderInit . $scripts;
        }
  
        /**
        }
  
        /**
 -       * @param $unused
 -       * @param $addContentType bool
 +       * @param $addContentType bool: Whether <meta> specifying content type should be returned
         *
 -       * @return string HTML tag links to be put in the header.
 +       * @return array in format "link name or number => 'link html'".
         */
 -      public function getHeadLinks( $unused = null, $addContentType = false ) {
 +      public function getHeadLinksArray( $addContentType = false ) {
                global $wgUniversalEditButton, $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI,
                        $wgSitename, $wgVersion, $wgHtml5, $wgMimeType,
                        $wgFeed, $wgOverrideSiteFeed, $wgAdvertisedFeedTypes,
                        if ( $wgHtml5 ) {
                                # More succinct than <meta http-equiv=Content-Type>, has the
                                # same effect
 -                              $tags[] = Html::element( 'meta', array( 'charset' => 'UTF-8' ) );
 +                              $tags['meta-charset'] = Html::element( 'meta', array( 'charset' => 'UTF-8' ) );
                        } else {
 -                              $tags[] = Html::element( 'meta', array(
 +                              $tags['meta-content-type'] = Html::element( 'meta', array(
                                        'http-equiv' => 'Content-Type',
                                        'content' => "$wgMimeType; charset=UTF-8"
                                ) );
 -                              $tags[] = Html::element( 'meta', array(  // bug 15835
 +                              $tags['meta-content-style-type'] = Html::element( 'meta', array(  // bug 15835
                                        'http-equiv' => 'Content-Style-Type',
                                        'content' => 'text/css'
                                ) );
                        }
                }
  
 -              $tags[] = Html::element( 'meta', array(
 +              $tags['meta-generator'] = Html::element( 'meta', array(
                        'name' => 'generator',
                        'content' => "MediaWiki $wgVersion",
                ) );
                if( $p !== 'index,follow' ) {
                        // http://www.robotstxt.org/wc/meta-user.html
                        // Only show if it's different from the default robots policy
 -                      $tags[] = Html::element( 'meta', array(
 +                      $tags['meta-robots'] = Html::element( 'meta', array(
                                'name' => 'robots',
                                'content' => $p,
                        ) );
                                "/<.*?" . ">/" => '',
                                "/_/" => ' '
                        );
 -                      $tags[] = Html::element( 'meta', array(
 +                      $tags['meta-keywords'] = Html::element( 'meta', array(
                                'name' => 'keywords',
                                'content' =>  preg_replace(
                                        array_keys( $strip ),
                        } else {
                                $a = 'name';
                        }
 -                      $tags[] = Html::element( 'meta',
 +                      $tagName = "meta-{$tag[0]}";
 +                      if ( isset( $tags[$tagName] ) ) {
 +                              $tagName .= $tag[1];
 +                      }
 +                      $tags[$tagName] = Html::element( 'meta',
                                array(
                                        $a => $tag[0],
                                        'content' => $tag[1]
                                && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) {
                                // Original UniversalEditButton
                                $msg = $this->msg( 'edit' )->text();
 -                              $tags[] = Html::element( 'link', array(
 +                              $tags['universal-edit-button'] = Html::element( 'link', array(
                                        'rel' => 'alternate',
                                        'type' => 'application/x-wiki',
                                        'title' => $msg,
                                        'href' => $this->getTitle()->getLocalURL( 'action=edit' )
                                ) );
                                // Alternate edit link
 -                              $tags[] = Html::element( 'link', array(
 +                              $tags['alternative-edit'] = Html::element( 'link', array(
                                        'rel' => 'edit',
                                        'title' => $msg,
                                        'href' => $this->getTitle()->getLocalURL( 'action=edit' )
                # uses whichever one appears later in the HTML source. Make sure
                # apple-touch-icon is specified first to avoid this.
                if ( $wgAppleTouchIcon !== false ) {
 -                      $tags[] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) );
 +                      $tags['apple-touch-icon'] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) );
                }
  
                if ( $wgFavicon !== false ) {
 -                      $tags[] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) );
 +                      $tags['favicon'] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) );
                }
  
                # OpenSearch description link
 -              $tags[] = Html::element( 'link', array(
 +              $tags['opensearch'] = Html::element( 'link', array(
                        'rel' => 'search',
                        'type' => 'application/opensearchdescription+xml',
                        'href' => wfScript( 'opensearch_desc' ),
                        # for the MediaWiki API (and potentially additional custom API
                        # support such as WordPress or Twitter-compatible APIs for a
                        # blogging extension, etc)
 -                      $tags[] = Html::element( 'link', array(
 +                      $tags['rsd'] = Html::element( 'link', array(
                                'rel' => 'EditURI',
                                'type' => 'application/rsd+xml',
                                // Output a protocol-relative URL here if $wgServer is protocol-relative
                                if ( !$urlvar ) {
                                        $variants = $lang->getVariants();
                                        foreach ( $variants as $_v ) {
 -                                              $tags[] = Html::element( 'link', array(
 +                                              $tags["variant-$_v"] = Html::element( 'link', array(
                                                        'rel' => 'alternate',
                                                        'hreflang' => $_v,
                                                        'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) )
                                                );
                                        }
                                } else {
 -                                      $tags[] = Html::element( 'link', array(
 +                                      $tags['canonical'] = Html::element( 'link', array(
                                                'rel' => 'canonical',
                                                'href' => $this->getTitle()->getCanonicalUrl()
                                        ) );
                }
  
                if ( $copyright ) {
 -                      $tags[] = Html::element( 'link', array(
 +                      $tags['copyright'] = Html::element( 'link', array(
                                'rel' => 'copyright',
                                'href' => $copyright )
                        );
                                }
                        }
                }
 -              return implode( "\n", $tags );
 +              return $tags;
 +      }
 +
 +      /**
 +       * @param $unused
 +       * @param $addContentType bool: Whether <meta> specifying content type should be returned
 +       *
 +       * @return string HTML tag links to be put in the header.
 +       */
 +      public function getHeadLinks( $unused = null, $addContentType = false ) {
 +              return implode( "\n", $this->getHeadLinksArray( $addContentType ) );
        }
  
        /**
                                $otherTags .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false,
                                        array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
                                );
-                               
                                // Load the previewed CSS
                                // If needed, Janus it first. This is user-supplied CSS, so it's
                                // assumed to be right for the content language directionality.
@@@ -431,8 -431,7 +431,8 @@@ var mw = ( function ( $, undefined ) 
                        function addInlineCSS( css ) {
                                var $style, style, $newStyle;
                                $style = getMarker().prev();
 -                              if ( $style.is( 'style' ) && $style.data( 'ResourceLoaderDynamicStyleTag' ) === true ) {
 +                              // Disable <style> tag recycling/concatenation because of bug 34669
 +                              if ( false && $style.is( 'style' ) && $style.data( 'ResourceLoaderDynamicStyleTag' ) === true ) {
                                        // There's already a dynamic <style> tag present, append to it. This recycling of
                                        // <style> tags is for bug 31676 (can't have more than 32 <style> tags in IE)
                                        style = $style.get( 0 );
                                                registry[module].state = 'loading';
                                                nestedAddScript( script, markModuleReady, registry[module].async, 0 );
                                        } else if ( $.isFunction( script ) ) {
 -                                              script();
 +                                              script( $ );
                                                markModuleReady();
                                        }
                                } catch ( e ) {
                                 */
                                state: function ( module, state ) {
                                        var m;
                                        if ( typeof module === 'object' ) {
                                                for ( m in module ) {
                                                        mw.loader.state( m, module[m] );
                                        if ( registry[module] === undefined ) {
                                                mw.loader.register( module );
                                        }
-                                       registry[module].state = state;
+                                       if ( state === 'ready' && registry[module].state !== state) {
+                                               // Make sure pending modules depending on this one get executed if their
+                                               // dependencies are now fulfilled!
+                                               registry[module].state = state;
+                                               handlePending( module );
+                                       } else {
+                                               registry[module].state = state;
+                                       }
                                },
                
                                /**