| 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 |