Index: trunk/phase3/includes/OutputPage.php |
— | — | @@ -2494,9 +2494,10 @@ |
2495 | 2495 | * @param $only String ResourceLoaderModule TYPE_ class constant |
2496 | 2496 | * @param $useESI boolean |
2497 | 2497 | * @param $extraQuery Array with extra query parameters to add to each request. array( param => value ) |
| 2498 | + * @param $loadCall boolean If true, output a mw.loader.load() call rather than a <script src="..."> tag |
2498 | 2499 | * @return string html <script> and <style> tags |
2499 | 2500 | */ |
2500 | | - protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array() ) { |
| 2501 | + protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) { |
2501 | 2502 | global $wgResourceLoaderUseESI, $wgResourceLoaderInlinePrivateModules; |
2502 | 2503 | |
2503 | 2504 | if ( !count( $modules ) ) { |
— | — | @@ -2633,6 +2634,12 @@ |
2634 | 2635 | // Automatically select style/script elements |
2635 | 2636 | if ( $only === ResourceLoaderModule::TYPE_STYLES ) { |
2636 | 2637 | $link = Html::linkedStyle( $url ); |
| 2638 | + } else if ( $loadCall ) { |
| 2639 | + $link = Html::inlineScript( |
| 2640 | + ResourceLoader::makeLoaderConditionalScript( |
| 2641 | + Xml::encodeJsCall( 'mw.loader.load', array( $url ) ) |
| 2642 | + ) |
| 2643 | + ); |
2637 | 2644 | } else { |
2638 | 2645 | $link = Html::linkedScript( $url ); |
2639 | 2646 | } |
— | — | @@ -2654,6 +2661,8 @@ |
2655 | 2662 | * @return String: HTML fragment |
2656 | 2663 | */ |
2657 | 2664 | function getHeadScripts() { |
| 2665 | + global $wgResourceLoaderExperimentalAsyncLoading; |
| 2666 | + |
2658 | 2667 | // Startup - this will immediately load jquery and mediawiki modules |
2659 | 2668 | $scripts = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true ); |
2660 | 2669 | |
— | — | @@ -2679,27 +2688,43 @@ |
2680 | 2689 | if ( $modules ) { |
2681 | 2690 | $scripts .= Html::inlineScript( |
2682 | 2691 | ResourceLoader::makeLoaderConditionalScript( |
2683 | | - Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) |
| 2692 | + "mw.loader.setBlocking( true );\n" . |
| 2693 | + Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) . |
| 2694 | + "\nmw.loader.setBlocking( false );" |
2684 | 2695 | ) |
2685 | 2696 | ); |
2686 | 2697 | } |
| 2698 | + |
| 2699 | + if ( $wgResourceLoaderExperimentalAsyncLoading ) { |
| 2700 | + $scripts .= $this->getScriptsForBottomQueue( true ); |
| 2701 | + } |
2687 | 2702 | |
2688 | 2703 | return $scripts; |
2689 | 2704 | } |
2690 | 2705 | |
2691 | 2706 | /** |
2692 | | - * JS stuff to put at the bottom of the <body>: modules marked with position 'bottom', |
2693 | | - * legacy scripts ($this->mScripts), user preferences, site JS and user JS |
| 2707 | + * JS stuff to put at the 'bottom', which can either be the bottom of the <body> |
| 2708 | + * or the bottom of the <head> depending on $wgResourceLoaderExperimentalAsyncLoading: |
| 2709 | + * modules marked with position 'bottom', legacy scripts ($this->mScripts), |
| 2710 | + * user preferences, site JS and user JS |
2694 | 2711 | * |
| 2712 | + * @param $inHead boolean If true, this HTML goes into the <head>, if false it goes into the <body> |
2695 | 2713 | * @return string |
2696 | 2714 | */ |
2697 | | - function getBottomScripts() { |
| 2715 | + function getScriptsForBottomQueue( $inHead ) { |
2698 | 2716 | global $wgUseSiteJs, $wgAllowUserJs; |
2699 | 2717 | |
2700 | 2718 | // Script and Messages "only" requests marked for bottom inclusion |
| 2719 | + // If we're in the <head>, use load() calls rather than <script src="..."> tags |
2701 | 2720 | // Messages should go first |
2702 | | - $scripts = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ), ResourceLoaderModule::TYPE_MESSAGES ); |
2703 | | - $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ), ResourceLoaderModule::TYPE_SCRIPTS ); |
| 2721 | + $scripts = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ), |
| 2722 | + ResourceLoaderModule::TYPE_MESSAGES, /* $useESI = */ false, /* $extraQuery = */ array(), |
| 2723 | + /* $loadCall = */ $inHead |
| 2724 | + ); |
| 2725 | + $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ), |
| 2726 | + ResourceLoaderModule::TYPE_SCRIPTS, /* $useESI = */ false, /* $extraQuery = */ array(), |
| 2727 | + /* $loadCall = */ $inHead |
| 2728 | + ); |
2704 | 2729 | |
2705 | 2730 | // Modules requests - let the client calculate dependencies and batch requests as it likes |
2706 | 2731 | // Only load modules that have marked themselves for loading at the bottom |
— | — | @@ -2719,7 +2744,9 @@ |
2720 | 2745 | |
2721 | 2746 | // Add site JS if enabled |
2722 | 2747 | if ( $wgUseSiteJs ) { |
2723 | | - $scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS ); |
| 2748 | + $scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS, |
| 2749 | + /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead |
| 2750 | + ); |
2724 | 2751 | if( $this->getUser()->isLoggedIn() ){ |
2725 | 2752 | $userScripts[] = 'user.groups'; |
2726 | 2753 | } |
— | — | @@ -2732,7 +2759,7 @@ |
2733 | 2760 | // We're on a preview of a JS subpage |
2734 | 2761 | // Exclude this page from the user module in case it's in there (bug 26283) |
2735 | 2762 | $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false, |
2736 | | - array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ) |
| 2763 | + array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead |
2737 | 2764 | ); |
2738 | 2765 | // Load the previewed JS |
2739 | 2766 | $scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n"; |
— | — | @@ -2740,15 +2767,31 @@ |
2741 | 2768 | // Include the user module normally |
2742 | 2769 | // We can't do $userScripts[] = 'user'; because the user module would end up |
2743 | 2770 | // being wrapped in a closure, so load it raw like 'site' |
2744 | | - $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS ); |
| 2771 | + $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, |
| 2772 | + /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead |
| 2773 | + ); |
2745 | 2774 | } |
2746 | 2775 | } |
2747 | | - $scripts .= $this->makeResourceLoaderLink( $userScripts, ResourceLoaderModule::TYPE_COMBINED ); |
| 2776 | + $scripts .= $this->makeResourceLoaderLink( $userScripts, ResourceLoaderModule::TYPE_COMBINED, |
| 2777 | + /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead |
| 2778 | + ); |
2748 | 2779 | |
2749 | 2780 | return $scripts; |
2750 | 2781 | } |
2751 | 2782 | |
2752 | 2783 | /** |
| 2784 | + * JS stuff to put at the bottom of the <body> |
| 2785 | + */ |
| 2786 | + function getBottomScripts() { |
| 2787 | + global $wgResourceLoaderExperimentalAsyncLoading; |
| 2788 | + if ( !$wgResourceLoaderExperimentalAsyncLoading ) { |
| 2789 | + return $this->getScriptsForBottomQueue( false ); |
| 2790 | + } else { |
| 2791 | + return ''; |
| 2792 | + } |
| 2793 | + } |
| 2794 | + |
| 2795 | + /** |
2753 | 2796 | * Add one or more variables to be set in mw.config in JavaScript. |
2754 | 2797 | * |
2755 | 2798 | * @param $key {String|Array} Key or array of key/value pars. |
Index: trunk/phase3/includes/DefaultSettings.php |
— | — | @@ -2646,6 +2646,13 @@ |
2647 | 2647 | */ |
2648 | 2648 | $wgResourceLoaderValidateStaticJS = false; |
2649 | 2649 | |
| 2650 | +/** |
| 2651 | + * If set to true, asynchronous loading of bottom-queue scripts in the <head> |
| 2652 | + * will be enabled. This is an experimental feature that's supposed to make |
| 2653 | + * JavaScript load faster. |
| 2654 | + */ |
| 2655 | +$wgResourceLoaderExperimentalAsyncLoading = false; |
| 2656 | + |
2650 | 2657 | /** @} */ # End of resource loader settings } |
2651 | 2658 | |
2652 | 2659 | |
Index: trunk/phase3/resources/mediawiki/mediawiki.js |
— | — | @@ -346,8 +346,11 @@ |
347 | 347 | queue = [], |
348 | 348 | // List of callback functions waiting for modules to be ready to be called |
349 | 349 | jobs = [], |
350 | | - // Flag inidicating that document ready has occured |
| 350 | + // Flag indicating that document ready has occured |
351 | 351 | ready = false, |
| 352 | + // Whether we should try to load scripts in a blocking way |
| 353 | + // Set with setBlocking() |
| 354 | + blocking = false, |
352 | 355 | // Selector cache for the marker element. Use getMarker() to get/use the marker! |
353 | 356 | $marker = null; |
354 | 357 | |
— | — | @@ -569,15 +572,15 @@ |
570 | 573 | } |
571 | 574 | |
572 | 575 | /** |
573 | | - * Adds a script tag to the body, either using document.write or low-level DOM manipulation, |
574 | | - * depending on whether document-ready has occured yet. |
| 576 | + * Adds a script tag to the DOM, either using document.write or low-level DOM manipulation, |
| 577 | + * depending on whether document-ready has occured yet and whether we are in blocking mode. |
575 | 578 | * |
576 | 579 | * @param src String: URL to script, will be used as the src attribute in the script tag |
577 | 580 | * @param callback Function: Optional callback which will be run when the script is done |
578 | 581 | */ |
579 | 582 | function addScript( src, callback ) { |
580 | | - var done = false, script; |
581 | | - if ( ready ) { |
| 583 | + var done = false, script, head; |
| 584 | + if ( ready || !blocking ) { |
582 | 585 | // jQuery's getScript method is NOT better than doing this the old-fashioned way |
583 | 586 | // because jQuery will eval the script's code, and errors will not have sane |
584 | 587 | // line numbers. |
— | — | @@ -612,13 +615,18 @@ |
613 | 616 | } |
614 | 617 | }; |
615 | 618 | } |
616 | | - document.body.appendChild( script ); |
| 619 | + // IE-safe way of getting the <head> . document.documentElement.head doesn't |
| 620 | + // work in scripts that run in the <head> |
| 621 | + head = document.getElementsByTagName( 'head' )[0]; |
| 622 | + // Append to the <body> if available, to the <head> otherwise |
| 623 | + (document.body || head).appendChild( script ); |
617 | 624 | } else { |
618 | 625 | document.write( mw.html.element( |
619 | 626 | 'script', { 'type': 'text/javascript', 'src': src }, '' |
620 | 627 | ) ); |
621 | 628 | if ( $.isFunction( callback ) ) { |
622 | 629 | // Document.write is synchronous, so this is called when it's done |
| 630 | + // FIXME: that's a lie. doc.write isn't actually synchronous |
623 | 631 | callback(); |
624 | 632 | } |
625 | 633 | } |
— | — | @@ -1221,6 +1229,18 @@ |
1222 | 1230 | return key; |
1223 | 1231 | } ); |
1224 | 1232 | }, |
| 1233 | + |
| 1234 | + /** |
| 1235 | + * Enable or disable blocking. If blocking is enabled and |
| 1236 | + * document ready has not yet occurred, scripts will be loaded |
| 1237 | + * in a blocking way (using document.write) rather than |
| 1238 | + * asynchronously using DOM manipulation |
| 1239 | + * |
| 1240 | + * @param b {Boolean} True to enable blocking, false to disable it |
| 1241 | + */ |
| 1242 | + setBlocking: function( b ) { |
| 1243 | + blocking = b; |
| 1244 | + }, |
1225 | 1245 | |
1226 | 1246 | /** |
1227 | 1247 | * For backwards-compatibility with Squid-cached pages. Loads mw.user |