r84613 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r84612‎ | r84613 | r84614 >
Date:17:42, 23 March 2011
Author:catrope
Status:ok (Comments)
Tags:
Comment:
Modified paths:
  • /branches/wmf/1.17wmf1/includes/AutoLoader.php (modified) (history)
  • /branches/wmf/1.17wmf1/includes/DefaultSettings.php (modified) (history)
  • /branches/wmf/1.17wmf1/includes/OutputPage.php (modified) (history)
  • /branches/wmf/1.17wmf1/includes/api/ApiQueryBase.php (modified) (history)
  • /branches/wmf/1.17wmf1/includes/api/ApiQueryCategoryMembers.php (modified) (history)
  • /branches/wmf/1.17wmf1/includes/libs/CSSMin.php (modified) (history)
  • /branches/wmf/1.17wmf1/includes/libs/JavaScriptMinifier.php (added) (history)
  • /branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoader.php (modified) (history)
  • /branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderModule.php (modified) (history)
  • /branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderSiteModule.php (modified) (history)
  • /branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderUserModule.php (modified) (history)
  • /branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderWikiModule.php (modified) (history)
  • /branches/wmf/1.17wmf1/maintenance/minify.php (modified) (history)

Diff [purge]

Index: branches/wmf/1.17wmf1/maintenance/minify.php
@@ -17,9 +17,12 @@
1818 "Directory for output. If this is not specified, and neither is --outfile, then the\n" .
1919 "output files will be sent to the same directories as the input files.",
2020 false, true );
21 - $this->addOption( 'minify-vertical-space',
22 - "Boolean value for minifying the vertical space for javascript.",
 21+ $this->addOption( 'js-statements-on-own-line',
 22+ "Boolean value for putting statements on their own line when minifying JavaScript.",
2323 false, true );
 24+ $this->addOption( 'js-max-line-length',
 25+ "Maximum line length for JavaScript minification.",
 26+ false, true );
2427 $this->mDescription = "Minify a file or set of files.\n\n" .
2528 "If --outfile is not specified, then the output file names will have a .min extension\n" .
2629 "added, e.g. jquery.js -> jquery.min.js.";
@@ -81,7 +84,7 @@
8285 }
8386
8487 public function minify( $inPath, $outPath ) {
85 - global $wgResourceLoaderMinifyJSVerticalSpace;
 88+ global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength;
8689
8790 $extension = $this->getExtension( $inPath );
8891 $this->output( basename( $inPath ) . ' -> ' . basename( $outPath ) . '...' );
@@ -99,7 +102,10 @@
100103
101104 switch ( $extension ) {
102105 case 'js':
103 - $outText = JavaScriptDistiller::stripWhiteSpace( $inText, $this->getOption( 'minify-vertical-space', $wgResourceLoaderMinifyJSVerticalSpace ) );
 106+ $outText = JavaScriptMinifier::minify( $inText,
 107+ $this->getOption( 'js-statements-on-own-line', $wgResourceLoaderMinifierStatementsOnOwnLine ),
 108+ $this->getOption( 'js-max-line-length', $wgResourceLoaderMinifierMaxLineLength )
 109+ );
104110 break;
105111 case 'css':
106112 $outText = CSSMin::minify( $inText );
Index: branches/wmf/1.17wmf1/includes/OutputPage.php
@@ -2386,10 +2386,25 @@
23872387 if ( ( $group === 'user' || $group === 'private' ) && $wgUser->isLoggedIn() ) {
23882388 $query['user'] = $wgUser->getName();
23892389 }
 2390+
 2391+ // Create a fake request based on the one we are about to make so modules return
 2392+ // correct timestamp and emptiness data
 2393+ $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
 2394+ // Drop modules that know they're empty
 2395+ foreach ( $modules as $key => $module ) {
 2396+ if ( $module->isKnownEmpty( $context ) ) {
 2397+ unset( $modules[$key] );
 2398+ }
 2399+ }
 2400+ // If there are no modules left, skip this group
 2401+ if ( $modules === array() ) {
 2402+ continue;
 2403+ }
 2404+
23902405 $query['modules'] = implode( '|', array_keys( $modules ) );
 2406+
23912407 // Support inlining of private modules if configured as such
23922408 if ( $group === 'private' && $wgResourceLoaderInlinePrivateModules ) {
2393 - $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
23942409 if ( $only == 'styles' ) {
23952410 $links .= Html::inlineStyle(
23962411 $resourceLoader->makeModuleResponse( $context, $modules )
@@ -2403,13 +2418,12 @@
24042419 }
24052420 continue;
24062421 }
2407 - // Special handling for user and site groups; because users might change their stuff
2408 - // on-wiki like site or user pages, or user preferences; we need to find the highest
 2422+ // Special handling for the user group; because users might change their stuff
 2423+ // on-wiki like user pages, or user preferences; we need to find the highest
24092424 // timestamp of these user-changable modules so we can ensure cache misses on change
2410 - if ( $group === 'user' || $group === 'site' ) {
2411 - // Create a fake request based on the one we are about to make so modules return
2412 - // correct times
2413 - $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
 2425+ // This should NOT be done for the site group (bug 27564) because anons get that too
 2426+ // and we shouldn't be putting timestamps in Squid-cached HTML
 2427+ if ( $group === 'user' ) {
24142428 // Get the maximum timestamp
24152429 $timestamp = 1;
24162430 foreach ( $modules as $module ) {
Property changes on: branches/wmf/1.17wmf1/includes/OutputPage.php
___________________________________________________________________
Modified: svn:mergeinfo
24172431 Merged /trunk/phase3/includes/OutputPage.php:r81692,82468,83814,83885,83891,83897,83902-83903,83988-83989,83997-83998,84392
Index: branches/wmf/1.17wmf1/includes/api/ApiQueryBase.php
@@ -246,14 +246,21 @@
247247 * Execute a SELECT query based on the values in the internal arrays
248248 * @param $method string Function the query should be attributed to.
249249 * You should usually use __METHOD__ here
 250+ * @param $extraQuery array Query data to add but not store in the object
 251+ * Format is array( 'tables' => ..., 'fields' => ..., 'where' => ..., 'options' => ..., 'join_conds' => ... )
250252 * @return ResultWrapper
251253 */
252 - protected function select( $method ) {
 254+ protected function select( $method, $extraQuery = array() ) {
 255+ // Merge $this->tables with $extraQuery['tables'], $this->fields with $extraQuery['fields'], etc.
 256+ foreach ( array( 'tables', 'fields', 'where', 'options', 'join_conds' ) as $var ) {
 257+ $$var = array_merge( $this->{$var}, isset( $extraQuery[$var] ) ? (array)$extraQuery[$var] : array() );
 258+ }
 259+
253260 // getDB has its own profileDBIn/Out calls
254261 $db = $this->getDB();
255262
256263 $this->profileDBIn();
257 - $res = $db->select( $this->tables, $this->fields, $this->where, $method, $this->options, $this->join_conds );
 264+ $res = $db->select( $tables, $fields, $where, $method, $options, $join_conds );
258265 $this->profileDBOut();
259266
260267 return $res;
Index: branches/wmf/1.17wmf1/includes/api/ApiQueryCategoryMembers.php
@@ -70,22 +70,21 @@
7171 $fld_type = isset( $prop['type'] );
7272
7373 if ( is_null( $resultPageSet ) ) {
74 - $this->addFields( array( 'cl_from', 'page_namespace', 'page_title' ) );
 74+ $this->addFields( array( 'cl_from', 'cl_sortkey', 'cl_type', 'page_namespace', 'page_title' ) );
7575 $this->addFieldsIf( 'page_id', $fld_ids );
7676 $this->addFieldsIf( 'cl_sortkey_prefix', $fld_sortkeyprefix );
77 - $this->addFieldsIf( 'cl_sortkey', $fld_sortkey );
7877 } else {
7978 $this->addFields( $resultPageSet->getPageTableFields() ); // will include page_ id, ns, title
80 - $this->addFields( array( 'cl_from', 'cl_sortkey' ) );
 79+ $this->addFields( array( 'cl_from', 'cl_sortkey', 'cl_type' ) );
8180 }
8281
8382 $this->addFieldsIf( 'cl_timestamp', $fld_timestamp || $params['sort'] == 'timestamp' );
84 - $this->addFieldsIf( 'cl_type', $fld_type );
8583
8684 $this->addTables( array( 'page', 'categorylinks' ) ); // must be in this order for 'USE INDEX'
8785
8886 $this->addWhereFld( 'cl_to', $categoryTitle->getDBkey() );
89 - $this->addWhereFld( 'cl_type', $params['type'] );
 87+ $queryTypes = $params['type'];
 88+ $contWhere = false;
9089
9190 // Scanning large datasets for rare categories sucks, and I already told
9291 // how to have efficient subcategory access :-) ~~~~ (oh well, domas)
@@ -107,33 +106,80 @@
108107
109108 $this->addOption( 'USE INDEX', 'cl_timestamp' );
110109 } else {
111 - // The below produces ORDER BY cl_type, cl_sortkey, cl_from, possibly with DESC added to each of them
112 - $this->addWhereRange( 'cl_type', $dir, null, null );
113 - $this->addWhereRange( 'cl_sortkey',
114 - $dir,
115 - $params['startsortkey'],
116 - $params['endsortkey'] );
117 - $this->addWhereRange( 'cl_from', $dir, null, null );
 110+ if ( $params['continue'] ) {
 111+ // type|from|sortkey
 112+ $cont = explode( '|', $params['continue'], 3 );
 113+ if ( count( $cont ) != 3 ) {
 114+ $this->dieUsage( 'Invalid continue param. You should pass the original value returned '.
 115+ 'by the previous query', '_badcontinue'
 116+ );
 117+ }
 118+
 119+ // Remove the types to skip from $queryTypes
 120+ $contTypeIndex = array_search( $cont[0], $queryTypes );
 121+ $queryTypes = array_slice( $queryTypes, $contTypeIndex );
 122+
 123+ // Add a WHERE clause for sortkey and from
 124+ $from = intval( $cont[1] );
 125+ $escSortkey = $this->getDB()->addQuotes( $cont[2] );
 126+ $op = $dir == 'newer' ? '>' : '<';
 127+ // $contWhere is used further down
 128+ $contWhere = "cl_sortkey $op $escSortkey OR " .
 129+ "(cl_sortkey = $escSortkey AND " .
 130+ "cl_from $op= $from)";
 131+
 132+ } else {
 133+ // The below produces ORDER BY cl_sortkey, cl_from, possibly with DESC added to each of them
 134+ $this->addWhereRange( 'cl_sortkey',
 135+ $dir,
 136+ $params['startsortkey'],
 137+ $params['endsortkey'] );
 138+ $this->addWhereRange( 'cl_from', $dir, null, null );
 139+ }
118140 $this->addOption( 'USE INDEX', 'cl_sortkey' );
119141 }
120142
121 - $this->setContinuation( $params['continue'], $params['dir'] );
122 -
123143 $this->addWhere( 'cl_from=page_id' );
124144
125145 $limit = $params['limit'];
126146 $this->addOption( 'LIMIT', $limit + 1 );
127147
 148+ // Run a separate SELECT query for each value of cl_type.
 149+ // This is needed because cl_type is an enum, and MySQL has
 150+ // inconsistencies between ORDER BY cl_type and
 151+ // WHERE cl_type >= 'foo' making proper paging impossible
 152+ // and unindexed.
 153+ $rows = array();
 154+ $first = true;
 155+ foreach ( $queryTypes as $type ) {
 156+ $extraConds = array( 'cl_type' => $type );
 157+ if ( $first && $contWhere ) {
 158+ // Continuation condition. Only added to the
 159+ // first query, otherwise we'll skip things
 160+ $extraConds[] = $contWhere;
 161+ }
 162+ $res = $this->select( __METHOD__, array( 'where' => $extraConds ) );
 163+ $rows = array_merge( $rows, iterator_to_array( $res ) );
 164+ if ( count( $rows ) >= $limit + 1 ) {
 165+ break;
 166+ }
 167+ $first = false;
 168+ }
128169 $count = 0;
129 - $res = $this->select( __METHOD__ );
130 - foreach ( $res as $row ) {
 170+ foreach ( $rows as $row ) {
131171 if ( ++ $count > $limit ) {
132172 // We've reached the one extra which shows that there are additional pages to be had. Stop here...
133173 // TODO: Security issue - if the user has no right to view next title, it will still be shown
134174 if ( $params['sort'] == 'timestamp' ) {
135175 $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->cl_timestamp ) );
136176 } else {
137 - $this->setContinueEnumParameter( 'continue', $row->cl_from );
 177+ // Continue format is type|from|sortkey
 178+ // The order is a bit weird but it's convenient to put the sortkey at the end
 179+ // because we don't have to worry about pipes in the sortkey that way
 180+ // (and type and from can't contain pipes anyway)
 181+ $this->setContinueEnumParameter( 'continue',
 182+ "{$row->cl_type}|{$row->cl_from}|{$row->cl_sortkey}"
 183+ );
138184 }
139185 break;
140186 }
@@ -173,7 +219,9 @@
174220 if ( $params['sort'] == 'timestamp' ) {
175221 $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->cl_timestamp ) );
176222 } else {
177 - $this->setContinueEnumParameter( 'continue', $row->cl_from );
 223+ $this->setContinueEnumParameter( 'continue',
 224+ "{$row->cl_type}|{$row->cl_from}|{$row->cl_sortkey}"
 225+ );
178226 }
179227 break;
180228 }
@@ -188,21 +236,6 @@
189237 }
190238 }
191239
192 - /**
193 - * Add DB WHERE clause to continue previous query based on 'continue' parameter
194 - */
195 - private function setContinuation( $continue, $dir ) {
196 - if ( is_null( $continue ) ) {
197 - return; // This is not a continuation request
198 - }
199 -
200 - $encFrom = $this->getDB()->addQuotes( intval( $continue ) );
201 -
202 - $op = ( $dir == 'desc' ? '<=' : '>=' );
203 -
204 - $this->addWhere( "cl_from $op $encFrom" );
205 - }
206 -
207240 public function getAllowedParams() {
208241 return array(
209242 'title' => array(
@@ -296,7 +329,8 @@
297330 $desc['namespace'] = array(
298331 $desc['namespace'],
299332 'NOTE: Due to $wgMiserMode, using this may result in fewer than "limit" results',
300 - 'returned before continuing; in extreme cases, zero results may be returned',
 333+ 'returned before continuing; in extreme cases, zero results may be returned.',
 334+ 'Note that you can use cmtype=subcat or cmtype=file instead of cmnamespace=14 or 6.',
301335 );
302336 }
303337 return $desc;
Index: branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderSiteModule.php
@@ -36,15 +36,14 @@
3737 global $wgHandheldStyle;
3838
3939 $pages = array(
40 - 'Common.js' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'script' ),
41 - 'Common.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style' ),
42 - ucfirst( $context->getSkin() ) . '.js' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'script' ),
43 - ucfirst( $context->getSkin() ) . '.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style' ),
44 - 'Print.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style', 'media' => 'print' ),
 40+ 'MediaWiki:Common.js' => array( 'type' => 'script' ),
 41+ 'MediaWiki:Common.css' => array( 'type' => 'style' ),
 42+ 'MediaWiki:' . ucfirst( $context->getSkin() ) . '.js' => array( 'type' => 'script' ),
 43+ 'MediaWiki:' . ucfirst( $context->getSkin() ) . '.css' => array( 'type' => 'style' ),
 44+ 'MediaWiki:Print.css' => array( 'type' => 'style', 'media' => 'print' ),
4545 );
4646 if ( $wgHandheldStyle ) {
47 - $pages['Handheld.css'] = array(
48 - 'ns' => NS_MEDIAWIKI,
 47+ $pages['MediaWiki:Handheld.css'] = array(
4948 'type' => 'style',
5049 'media' => 'handheld' );
5150 }
Index: branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoader.php
@@ -29,7 +29,7 @@
3030 class ResourceLoader {
3131
3232 /* Protected Static Members */
33 - protected static $filterCacheVersion = 1;
 33+ protected static $filterCacheVersion = 2;
3434
3535 /** Array: List of module name/ResourceLoaderModule object pairs */
3636 protected $modules = array();
@@ -110,7 +110,7 @@
111111 * Runs JavaScript or CSS data through a filter, caching the filtered result for future calls.
112112 *
113113 * Available filters are:
114 - * - minify-js \see JavaScriptDistiller::stripWhiteSpace
 114+ * - minify-js \see JavaScriptMinifier::minify
115115 * - minify-css \see CSSMin::minify
116116 *
117117 * If $data is empty, only contains whitespace or the filter was unknown,
@@ -121,8 +121,7 @@
122122 * @return String: Filtered data, or a comment containing an error message
123123 */
124124 protected function filter( $filter, $data ) {
125 - global $wgResourceLoaderMinifyJSVerticalSpace;
126 -
 125+ global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength;
127126 wfProfileIn( __METHOD__ );
128127
129128 // For empty/whitespace-only data or for unknown filters, don't perform
@@ -148,8 +147,9 @@
149148 try {
150149 switch ( $filter ) {
151150 case 'minify-js':
152 - $result = JavaScriptDistiller::stripWhiteSpace(
153 - $data, $wgResourceLoaderMinifyJSVerticalSpace
 151+ $result = JavaScriptMinifier::minify( $data,
 152+ $wgResourceLoaderMinifierStatementsOnOwnLine,
 153+ $wgResourceLoaderMinifierMaxLineLength
154154 );
155155 $result .= "\n\n/* cache key: $key */\n";
156156 break;
@@ -467,7 +467,9 @@
468468 // Scripts
469469 $scripts = '';
470470 if ( $context->shouldIncludeScripts() ) {
471 - $scripts .= $module->getScript( $context ) . "\n";
 471+ // bug 27054: Append semicolon to prevent weird bugs
 472+ // caused by files not terminating their statements right
 473+ $scripts .= $module->getScript( $context ) . ";\n";
472474 }
473475
474476 // Styles
Property changes on: branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoader.php
___________________________________________________________________
Modified: svn:mergeinfo
475477 Merged /trunk/phase3/includes/resourceloader/ResourceLoader.php:r81692,82468,83814,83885,83891,83897,83902-83903,83988-83989,83997-83998,84392
Index: branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderUserModule.php
@@ -31,12 +31,12 @@
3232 if ( $context->getUser() ) {
3333 $username = $context->getUser();
3434 return array(
35 - "$username/common.js" => array( 'ns' => NS_USER, 'type' => 'script' ),
36 - "$username/" . $context->getSkin() . '.js' =>
37 - array( 'ns' => NS_USER, 'type' => 'script' ),
38 - "$username/common.css" => array( 'ns' => NS_USER, 'type' => 'style' ),
39 - "$username/" . $context->getSkin() . '.css' =>
40 - array( 'ns' => NS_USER, 'type' => 'style' ),
 35+ "User:$username/common.js" => array( 'type' => 'script' ),
 36+ "User:$username/" . $context->getSkin() . '.js' =>
 37+ array( 'type' => 'script' ),
 38+ "User:$username/common.css" => array( 'type' => 'style' ),
 39+ "User:$username/" . $context->getSkin() . '.css' =>
 40+ array( 'type' => 'style' ),
4141 );
4242 }
4343 return array();
Index: branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderModule.php
@@ -228,4 +228,17 @@
229229 // 0 would mean now
230230 return 1;
231231 }
 232+
 233+ /**
 234+ * Check whether this module is known to be empty. If a child class
 235+ * has an easy and cheap way to determine that this module is
 236+ * definitely going to be empty, it should override this method to
 237+ * return true in that case. Callers may optimize the request for this
 238+ * module away if this function returns true.
 239+ * @param $context ResourceLoaderContext: Context object
 240+ * @return Boolean
 241+ */
 242+ public function isKnownEmpty( ResourceLoaderContext $context ) {
 243+ return false;
 244+ }
232245 }
Index: branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderWikiModule.php
@@ -33,8 +33,8 @@
3434
3535 /* Protected Members */
3636
37 - // In-object cache for modified time
38 - protected $modifiedTime = array();
 37+ // In-object cache for title mtimes
 38+ protected $titleMtimes = array();
3939
4040 /* Abstract Protected Methods */
4141
@@ -42,12 +42,12 @@
4343
4444 /* Protected Methods */
4545
46 - protected function getContent( $page, $ns ) {
47 - if ( $ns === NS_MEDIAWIKI ) {
48 - return wfEmptyMsg( $page ) ? '' : wfMsgExt( $page, 'content' );
 46+ protected function getContent( $title ) {
 47+ if ( $title->getNamespace() === NS_MEDIAWIKI ) {
 48+ $dbkey = $title->getDBkey();
 49+ return wfEmptyMsg( $dbkey ) ? '' : wfMsgExt( $dbkey, 'content' );
4950 }
50 - $title = Title::newFromText( $page, $ns );
51 - if ( !$title || !$title->isCssJsSubpage() ) {
 51+ if ( !$title->isCssJsSubpage() ) {
5252 return null;
5353 }
5454 $revision = Revision::newFromTitle( $title );
@@ -61,15 +61,21 @@
6262
6363 public function getScript( ResourceLoaderContext $context ) {
6464 $scripts = '';
65 - foreach ( $this->getPages( $context ) as $page => $options ) {
 65+ foreach ( $this->getPages( $context ) as $titleText => $options ) {
6666 if ( $options['type'] !== 'script' ) {
6767 continue;
6868 }
69 - $script = $this->getContent( $page, $options['ns'] );
70 - if ( $script ) {
71 - $ns = MWNamespace::getCanonicalName( $options['ns'] );
72 - $scripts .= "/* $ns:$page */\n$script\n";
 69+ $title = Title::newFromText( $titleText );
 70+ if ( !$title ) {
 71+ continue;
7372 }
 73+ $script = $this->getContent( $title );
 74+ if ( strval( $script ) !== '' ) {
 75+ if ( strpos( $titleText, '*/' ) === false ) {
 76+ $scripts .= "/* $titleText */\n";
 77+ }
 78+ $scripts .= $script . "\n";
 79+ }
7480 }
7581 return $scripts;
7682 }
@@ -78,13 +84,17 @@
7985 global $wgScriptPath;
8086
8187 $styles = array();
82 - foreach ( $this->getPages( $context ) as $page => $options ) {
 88+ foreach ( $this->getPages( $context ) as $titleText => $options ) {
8389 if ( $options['type'] !== 'style' ) {
8490 continue;
8591 }
 92+ $title = Title::newFromText( $titleText );
 93+ if ( !$title ) {
 94+ continue;
 95+ }
8696 $media = isset( $options['media'] ) ? $options['media'] : 'all';
87 - $style = $this->getContent( $page, $options['ns'] );
88 - if ( !$style ) {
 97+ $style = $this->getContent( $title );
 98+ if ( strval( $style ) === '' ) {
8999 continue;
90100 }
91101 if ( $this->getFlip( $context ) ) {
@@ -94,36 +104,58 @@
95105 if ( !isset( $styles[$media] ) ) {
96106 $styles[$media] = '';
97107 }
98 - $ns = MWNamespace::getCanonicalName( $options['ns'] );
99 - $styles[$media] .= "/* $ns:$page */\n$style\n";
 108+ if ( strpos( $titleText, '*/' ) === false ) {
 109+ $styles[$media] .= "/* $titleText */\n";
 110+ }
 111+ $styles[$media] .= $style . "\n";
100112 }
101113 return $styles;
102114 }
103115
104116 public function getModifiedTime( ResourceLoaderContext $context ) {
 117+ $modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
 118+ $mtimes = $this->getTitleMtimes( $context );
 119+ if ( count( $mtimes ) ) {
 120+ $modifiedTime = max( $modifiedTime, max( $mtimes ) );
 121+ }
 122+ return $modifiedTime;
 123+ }
 124+
 125+ public function isKnownEmpty( ResourceLoaderContext $context ) {
 126+ return count( $this->getTitleMtimes( $context ) ) == 0;
 127+ }
 128+
 129+ /**
 130+ * Get the modification times of all titles that would be loaded for
 131+ * a given context.
 132+ * @param $context ResourceLoaderContext: Context object
 133+ * @return array( prefixed DB key => UNIX timestamp ), nonexistent titles are dropped
 134+ */
 135+ protected function getTitleMtimes( ResourceLoaderContext $context ) {
105136 $hash = $context->getHash();
106 - if ( isset( $this->modifiedTime[$hash] ) ) {
107 - return $this->modifiedTime[$hash];
 137+ if ( isset( $this->titleMtimes[$hash] ) ) {
 138+ return $this->titleMtimes[$hash];
108139 }
109 -
110 - $titles = array();
111 - foreach ( $this->getPages( $context ) as $page => $options ) {
112 - $titles[$options['ns']][str_replace( ' ', '_', $page)] = true;
 140+
 141+ $this->titleMtimes[$hash] = array();
 142+ $batch = new LinkBatch;
 143+ foreach ( $this->getPages( $context ) as $titleText => $options ) {
 144+ $batch->addObj( Title::newFromText( $titleText ) );
113145 }
114 -
115 - $modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
116 -
117 - if ( $titles ) {
 146+
 147+ if ( !$batch->isEmpty() ) {
118148 $dbr = wfGetDB( DB_SLAVE );
119 - $latest = $dbr->selectField( 'page', 'MAX(page_touched)',
120 - $dbr->makeWhereFrom2d( $titles, 'page_namespace', 'page_title' ),
121 - __METHOD__ );
122 -
123 - if ( $latest ) {
124 - $modifiedTime = wfTimestamp( TS_UNIX, $latest );
 149+ $res = $dbr->select( 'page',
 150+ array( 'page_namespace', 'page_title', 'page_touched' ),
 151+ $batch->constructSet( 'page', $dbr ),
 152+ __METHOD__
 153+ );
 154+ foreach ( $res as $row ) {
 155+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
 156+ $this->titleMtimes[$hash][$title->getPrefixedDBkey()] =
 157+ wfTimestamp( TS_UNIX, $row->page_touched );
125158 }
126159 }
127 -
128 - return $this->modifiedTime[$hash] = $modifiedTime;
 160+ return $this->titleMtimes[$hash];
129161 }
130162 }
Index: branches/wmf/1.17wmf1/includes/AutoLoader.php
@@ -137,6 +137,7 @@
138138 'Interwiki' => 'includes/Interwiki.php',
139139 'IP' => 'includes/IP.php',
140140 'JavaScriptDistiller' => 'includes/libs/JavaScriptDistiller.php',
 141+ 'JavaScriptMinifier' => 'includes/libs/JavaScriptMinifier.php',
141142 'LCStore_DB' => 'includes/LocalisationCache.php',
142143 'LCStore_CDB' => 'includes/LocalisationCache.php',
143144 'LCStore_Null' => 'includes/LocalisationCache.php',
Property changes on: branches/wmf/1.17wmf1/includes/AutoLoader.php
___________________________________________________________________
Modified: svn:mergeinfo
144145 Merged /trunk/phase3/includes/AutoLoader.php:r81692,82468,83814,83885,83891,83897,83902-83903,83988-83989,83997-83998,84392
Index: branches/wmf/1.17wmf1/includes/libs/JavaScriptMinifier.php
@@ -0,0 +1,578 @@
 2+<?php
 3+/**
 4+ * JavaScript Minifier
 5+ *
 6+ * This class is meant to safely minify javascript code, while leaving syntactically correct
 7+ * programs intact. Other libraries, such as JSMin require a certain coding style to work
 8+ * correctly. OTOH, libraries like jsminplus, that do parse the code correctly are rather
 9+ * slow, because they construct a complete parse tree before outputting the code minified.
 10+ * So this class is meant to allow arbitrary (but syntactically correct) input, while being
 11+ * fast enough to be used for on-the-fly minifying.
 12+ *
 13+ * Author: Paul Copperman <paul.copperman@gmail.com>
 14+ * License: choose any of Apache, MIT, GPL, LGPL
 15+ */
 16+
 17+class JavaScriptMinifier {
 18+
 19+ /* Class constants */
 20+ /* Parsing states.
 21+ * The state machine is only necessary to decide whether to parse a slash as division
 22+ * operator or as regexp literal.
 23+ * States are named after the next expected item. We only distinguish states when the
 24+ * distinction is relevant for our purpose.
 25+ */
 26+ const STATEMENT = 0;
 27+ const CONDITION = 1;
 28+ const PROPERTY_ASSIGNMENT = 2;
 29+ const EXPRESSION = 3;
 30+ const EXPRESSION_NO_NL = 4; // only relevant for semicolon insertion
 31+ const EXPRESSION_OP = 5;
 32+ const EXPRESSION_FUNC = 6;
 33+ const EXPRESSION_TERNARY = 7; // used to determine the role of a colon
 34+ const EXPRESSION_TERNARY_OP = 8;
 35+ const EXPRESSION_TERNARY_FUNC = 9;
 36+ const PAREN_EXPRESSION = 10; // expression which is not on the top level
 37+ const PAREN_EXPRESSION_OP = 11;
 38+ const PAREN_EXPRESSION_FUNC = 12;
 39+ const PROPERTY_EXPRESSION = 13; // expression which is within an object literal
 40+ const PROPERTY_EXPRESSION_OP = 14;
 41+ const PROPERTY_EXPRESSION_FUNC = 15;
 42+
 43+ /* Token types */
 44+ const TYPE_UN_OP = 1; // unary operators
 45+ const TYPE_INCR_OP = 2; // ++ and --
 46+ const TYPE_BIN_OP = 3; // binary operators
 47+ const TYPE_ADD_OP = 4; // + and - which can be either unary or binary ops
 48+ const TYPE_HOOK = 5; // ?
 49+ const TYPE_COLON = 6; // :
 50+ const TYPE_COMMA = 7; // ,
 51+ const TYPE_SEMICOLON = 8; // ;
 52+ const TYPE_BRACE_OPEN = 9; // {
 53+ const TYPE_BRACE_CLOSE = 10; // }
 54+ const TYPE_PAREN_OPEN = 11; // ( and [
 55+ const TYPE_PAREN_CLOSE = 12; // ) and ]
 56+ const TYPE_RETURN = 13; // keywords: break, continue, return, throw
 57+ const TYPE_IF = 14; // keywords: catch, for, with, switch, while, if
 58+ const TYPE_DO = 15; // keywords: case, var, finally, else, do, try
 59+ const TYPE_FUNC = 16; // keywords: function
 60+ const TYPE_LITERAL = 17; // all literals, identifiers and unrecognised tokens
 61+
 62+ // Sanity limit to avoid excessive memory usage
 63+ const STACK_LIMIT = 1000;
 64+
 65+ /* Static functions */
 66+
 67+ /**
 68+ * Returns minified JavaScript code.
 69+ *
 70+ * NOTE: $maxLineLength isn't a strict maximum. Longer lines will be produced when
 71+ * literals (e.g. quoted strings) longer than $maxLineLength are encountered
 72+ * or when required to guard against semicolon insertion.
 73+ *
 74+ * @param $s String JavaScript code to minify
 75+ * @param $statementsOnOwnLine Bool Whether to put each statement on its own line
 76+ * @param $maxLineLength Int Maximum length of a single line, or -1 for no maximum.
 77+ * @return String Minified code
 78+ */
 79+ public static function minify( $s, $statementsOnOwnLine = false, $maxLineLength = 1000 ) {
 80+ // First we declare a few tables that contain our parsing rules
 81+
 82+ // $opChars : characters, which can be combined without whitespace in between them
 83+ $opChars = array(
 84+ '!' => true,
 85+ '"' => true,
 86+ '%' => true,
 87+ '&' => true,
 88+ "'" => true,
 89+ '(' => true,
 90+ ')' => true,
 91+ '*' => true,
 92+ '+' => true,
 93+ ',' => true,
 94+ '-' => true,
 95+ '.' => true,
 96+ '/' => true,
 97+ ':' => true,
 98+ ';' => true,
 99+ '<' => true,
 100+ '=' => true,
 101+ '>' => true,
 102+ '?' => true,
 103+ '[' => true,
 104+ ']' => true,
 105+ '^' => true,
 106+ '{' => true,
 107+ '|' => true,
 108+ '}' => true,
 109+ '~' => true
 110+ );
 111+
 112+ // $tokenTypes : maps keywords and operators to their corresponding token type
 113+ $tokenTypes = array(
 114+ '!' => self::TYPE_UN_OP,
 115+ '~' => self::TYPE_UN_OP,
 116+ 'delete' => self::TYPE_UN_OP,
 117+ 'new' => self::TYPE_UN_OP,
 118+ 'typeof' => self::TYPE_UN_OP,
 119+ 'void' => self::TYPE_UN_OP,
 120+ '++' => self::TYPE_INCR_OP,
 121+ '--' => self::TYPE_INCR_OP,
 122+ '!=' => self::TYPE_BIN_OP,
 123+ '!==' => self::TYPE_BIN_OP,
 124+ '%' => self::TYPE_BIN_OP,
 125+ '%=' => self::TYPE_BIN_OP,
 126+ '&' => self::TYPE_BIN_OP,
 127+ '&&' => self::TYPE_BIN_OP,
 128+ '&=' => self::TYPE_BIN_OP,
 129+ '*' => self::TYPE_BIN_OP,
 130+ '*=' => self::TYPE_BIN_OP,
 131+ '+=' => self::TYPE_BIN_OP,
 132+ '-=' => self::TYPE_BIN_OP,
 133+ '.' => self::TYPE_BIN_OP,
 134+ '/' => self::TYPE_BIN_OP,
 135+ '/=' => self::TYPE_BIN_OP,
 136+ '<' => self::TYPE_BIN_OP,
 137+ '<<' => self::TYPE_BIN_OP,
 138+ '<<=' => self::TYPE_BIN_OP,
 139+ '<=' => self::TYPE_BIN_OP,
 140+ '=' => self::TYPE_BIN_OP,
 141+ '==' => self::TYPE_BIN_OP,
 142+ '===' => self::TYPE_BIN_OP,
 143+ '>' => self::TYPE_BIN_OP,
 144+ '>=' => self::TYPE_BIN_OP,
 145+ '>>' => self::TYPE_BIN_OP,
 146+ '>>=' => self::TYPE_BIN_OP,
 147+ '>>>' => self::TYPE_BIN_OP,
 148+ '>>>=' => self::TYPE_BIN_OP,
 149+ '^' => self::TYPE_BIN_OP,
 150+ '^=' => self::TYPE_BIN_OP,
 151+ '|' => self::TYPE_BIN_OP,
 152+ '|=' => self::TYPE_BIN_OP,
 153+ '||' => self::TYPE_BIN_OP,
 154+ 'in' => self::TYPE_BIN_OP,
 155+ 'instanceof' => self::TYPE_BIN_OP,
 156+ '+' => self::TYPE_ADD_OP,
 157+ '-' => self::TYPE_ADD_OP,
 158+ '?' => self::TYPE_HOOK,
 159+ ':' => self::TYPE_COLON,
 160+ ',' => self::TYPE_COMMA,
 161+ ';' => self::TYPE_SEMICOLON,
 162+ '{' => self::TYPE_BRACE_OPEN,
 163+ '}' => self::TYPE_BRACE_CLOSE,
 164+ '(' => self::TYPE_PAREN_OPEN,
 165+ '[' => self::TYPE_PAREN_OPEN,
 166+ ')' => self::TYPE_PAREN_CLOSE,
 167+ ']' => self::TYPE_PAREN_CLOSE,
 168+ 'break' => self::TYPE_RETURN,
 169+ 'continue' => self::TYPE_RETURN,
 170+ 'return' => self::TYPE_RETURN,
 171+ 'throw' => self::TYPE_RETURN,
 172+ 'catch' => self::TYPE_IF,
 173+ 'for' => self::TYPE_IF,
 174+ 'if' => self::TYPE_IF,
 175+ 'switch' => self::TYPE_IF,
 176+ 'while' => self::TYPE_IF,
 177+ 'with' => self::TYPE_IF,
 178+ 'case' => self::TYPE_DO,
 179+ 'do' => self::TYPE_DO,
 180+ 'else' => self::TYPE_DO,
 181+ 'finally' => self::TYPE_DO,
 182+ 'try' => self::TYPE_DO,
 183+ 'var' => self::TYPE_DO,
 184+ 'function' => self::TYPE_FUNC
 185+ );
 186+
 187+ // $goto : This is the main table for our state machine. For every state/token pair
 188+ // the following state is defined. When no rule exists for a given pair,
 189+ // the state is left unchanged.
 190+ $goto = array(
 191+ self::STATEMENT => array(
 192+ self::TYPE_UN_OP => self::EXPRESSION,
 193+ self::TYPE_INCR_OP => self::EXPRESSION,
 194+ self::TYPE_ADD_OP => self::EXPRESSION,
 195+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
 196+ self::TYPE_RETURN => self::EXPRESSION_NO_NL,
 197+ self::TYPE_IF => self::CONDITION,
 198+ self::TYPE_FUNC => self::CONDITION,
 199+ self::TYPE_LITERAL => self::EXPRESSION_OP
 200+ ),
 201+ self::CONDITION => array(
 202+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
 203+ ),
 204+ self::PROPERTY_ASSIGNMENT => array(
 205+ self::TYPE_COLON => self::PROPERTY_EXPRESSION,
 206+ self::TYPE_BRACE_OPEN => self::STATEMENT
 207+ ),
 208+ self::EXPRESSION => array(
 209+ self::TYPE_SEMICOLON => self::STATEMENT,
 210+ self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT,
 211+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
 212+ self::TYPE_FUNC => self::EXPRESSION_FUNC,
 213+ self::TYPE_LITERAL => self::EXPRESSION_OP
 214+ ),
 215+ self::EXPRESSION_NO_NL => array(
 216+ self::TYPE_SEMICOLON => self::STATEMENT,
 217+ self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT,
 218+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
 219+ self::TYPE_FUNC => self::EXPRESSION_FUNC,
 220+ self::TYPE_LITERAL => self::EXPRESSION_OP
 221+ ),
 222+ self::EXPRESSION_OP => array(
 223+ self::TYPE_BIN_OP => self::EXPRESSION,
 224+ self::TYPE_ADD_OP => self::EXPRESSION,
 225+ self::TYPE_HOOK => self::EXPRESSION_TERNARY,
 226+ self::TYPE_COLON => self::STATEMENT,
 227+ self::TYPE_COMMA => self::EXPRESSION,
 228+ self::TYPE_SEMICOLON => self::STATEMENT,
 229+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
 230+ ),
 231+ self::EXPRESSION_FUNC => array(
 232+ self::TYPE_BRACE_OPEN => self::STATEMENT
 233+ ),
 234+ self::EXPRESSION_TERNARY => array(
 235+ self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT,
 236+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
 237+ self::TYPE_FUNC => self::EXPRESSION_TERNARY_FUNC,
 238+ self::TYPE_LITERAL => self::EXPRESSION_TERNARY_OP
 239+ ),
 240+ self::EXPRESSION_TERNARY_OP => array(
 241+ self::TYPE_BIN_OP => self::EXPRESSION_TERNARY,
 242+ self::TYPE_ADD_OP => self::EXPRESSION_TERNARY,
 243+ self::TYPE_HOOK => self::EXPRESSION_TERNARY,
 244+ self::TYPE_COMMA => self::EXPRESSION_TERNARY,
 245+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
 246+ ),
 247+ self::EXPRESSION_TERNARY_FUNC => array(
 248+ self::TYPE_BRACE_OPEN => self::STATEMENT
 249+ ),
 250+ self::PAREN_EXPRESSION => array(
 251+ self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT,
 252+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
 253+ self::TYPE_FUNC => self::PAREN_EXPRESSION_FUNC,
 254+ self::TYPE_LITERAL => self::PAREN_EXPRESSION_OP
 255+ ),
 256+ self::PAREN_EXPRESSION_OP => array(
 257+ self::TYPE_BIN_OP => self::PAREN_EXPRESSION,
 258+ self::TYPE_ADD_OP => self::PAREN_EXPRESSION,
 259+ self::TYPE_HOOK => self::PAREN_EXPRESSION,
 260+ self::TYPE_COLON => self::PAREN_EXPRESSION,
 261+ self::TYPE_COMMA => self::PAREN_EXPRESSION,
 262+ self::TYPE_SEMICOLON => self::PAREN_EXPRESSION,
 263+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
 264+ ),
 265+ self::PAREN_EXPRESSION_FUNC => array(
 266+ self::TYPE_BRACE_OPEN => self::STATEMENT
 267+ ),
 268+ self::PROPERTY_EXPRESSION => array(
 269+ self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT,
 270+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
 271+ self::TYPE_FUNC => self::PROPERTY_EXPRESSION_FUNC,
 272+ self::TYPE_LITERAL => self::PROPERTY_EXPRESSION_OP
 273+ ),
 274+ self::PROPERTY_EXPRESSION_OP => array(
 275+ self::TYPE_BIN_OP => self::PROPERTY_EXPRESSION,
 276+ self::TYPE_ADD_OP => self::PROPERTY_EXPRESSION,
 277+ self::TYPE_HOOK => self::PROPERTY_EXPRESSION,
 278+ self::TYPE_COMMA => self::PROPERTY_ASSIGNMENT,
 279+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
 280+ ),
 281+ self::PROPERTY_EXPRESSION_FUNC => array(
 282+ self::TYPE_BRACE_OPEN => self::STATEMENT
 283+ )
 284+ );
 285+
 286+ // $push : This table contains the rules for when to push a state onto the stack.
 287+ // The pushed state is the state to return to when the corresponding
 288+ // closing token is found
 289+ $push = array(
 290+ self::STATEMENT => array(
 291+ self::TYPE_BRACE_OPEN => self::STATEMENT,
 292+ self::TYPE_PAREN_OPEN => self::EXPRESSION_OP
 293+ ),
 294+ self::CONDITION => array(
 295+ self::TYPE_PAREN_OPEN => self::STATEMENT
 296+ ),
 297+ self::PROPERTY_ASSIGNMENT => array(
 298+ self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT
 299+ ),
 300+ self::EXPRESSION => array(
 301+ self::TYPE_BRACE_OPEN => self::EXPRESSION_OP,
 302+ self::TYPE_PAREN_OPEN => self::EXPRESSION_OP
 303+ ),
 304+ self::EXPRESSION_NO_NL => array(
 305+ self::TYPE_BRACE_OPEN => self::EXPRESSION_OP,
 306+ self::TYPE_PAREN_OPEN => self::EXPRESSION_OP
 307+ ),
 308+ self::EXPRESSION_OP => array(
 309+ self::TYPE_HOOK => self::EXPRESSION,
 310+ self::TYPE_PAREN_OPEN => self::EXPRESSION_OP
 311+ ),
 312+ self::EXPRESSION_FUNC => array(
 313+ self::TYPE_BRACE_OPEN => self::EXPRESSION_OP
 314+ ),
 315+ self::EXPRESSION_TERNARY => array(
 316+ self::TYPE_BRACE_OPEN => self::EXPRESSION_TERNARY_OP,
 317+ self::TYPE_PAREN_OPEN => self::EXPRESSION_TERNARY_OP
 318+ ),
 319+ self::EXPRESSION_TERNARY_OP => array(
 320+ self::TYPE_HOOK => self::EXPRESSION_TERNARY,
 321+ self::TYPE_PAREN_OPEN => self::EXPRESSION_TERNARY_OP
 322+ ),
 323+ self::EXPRESSION_TERNARY_FUNC => array(
 324+ self::TYPE_BRACE_OPEN => self::EXPRESSION_TERNARY_OP
 325+ ),
 326+ self::PAREN_EXPRESSION => array(
 327+ self::TYPE_BRACE_OPEN => self::PAREN_EXPRESSION_OP,
 328+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION_OP
 329+ ),
 330+ self::PAREN_EXPRESSION_OP => array(
 331+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION_OP
 332+ ),
 333+ self::PAREN_EXPRESSION_FUNC => array(
 334+ self::TYPE_BRACE_OPEN => self::PAREN_EXPRESSION_OP
 335+ ),
 336+ self::PROPERTY_EXPRESSION => array(
 337+ self::TYPE_BRACE_OPEN => self::PROPERTY_EXPRESSION_OP,
 338+ self::TYPE_PAREN_OPEN => self::PROPERTY_EXPRESSION_OP
 339+ ),
 340+ self::PROPERTY_EXPRESSION_OP => array(
 341+ self::TYPE_PAREN_OPEN => self::PROPERTY_EXPRESSION_OP
 342+ ),
 343+ self::PROPERTY_EXPRESSION_FUNC => array(
 344+ self::TYPE_BRACE_OPEN => self::PROPERTY_EXPRESSION_OP
 345+ )
 346+ );
 347+
 348+ // $pop : Rules for when to pop a state from the stack
 349+ $pop = array(
 350+ self::STATEMENT => array( self::TYPE_BRACE_CLOSE => true ),
 351+ self::PROPERTY_ASSIGNMENT => array( self::TYPE_BRACE_CLOSE => true ),
 352+ self::EXPRESSION => array( self::TYPE_BRACE_CLOSE => true ),
 353+ self::EXPRESSION_NO_NL => array( self::TYPE_BRACE_CLOSE => true ),
 354+ self::EXPRESSION_OP => array( self::TYPE_BRACE_CLOSE => true ),
 355+ self::EXPRESSION_TERNARY_OP => array( self::TYPE_COLON => true ),
 356+ self::PAREN_EXPRESSION => array( self::TYPE_PAREN_CLOSE => true ),
 357+ self::PAREN_EXPRESSION_OP => array( self::TYPE_PAREN_CLOSE => true ),
 358+ self::PROPERTY_EXPRESSION => array( self::TYPE_BRACE_CLOSE => true ),
 359+ self::PROPERTY_EXPRESSION_OP => array( self::TYPE_BRACE_CLOSE => true )
 360+ );
 361+
 362+ // $semicolon : Rules for when a semicolon insertion is appropriate
 363+ $semicolon = array(
 364+ self::EXPRESSION_NO_NL => array(
 365+ self::TYPE_UN_OP => true,
 366+ self::TYPE_INCR_OP => true,
 367+ self::TYPE_ADD_OP => true,
 368+ self::TYPE_BRACE_OPEN => true,
 369+ self::TYPE_PAREN_OPEN => true,
 370+ self::TYPE_RETURN => true,
 371+ self::TYPE_IF => true,
 372+ self::TYPE_DO => true,
 373+ self::TYPE_FUNC => true,
 374+ self::TYPE_LITERAL => true
 375+ ),
 376+ self::EXPRESSION_OP => array(
 377+ self::TYPE_UN_OP => true,
 378+ self::TYPE_INCR_OP => true,
 379+ self::TYPE_BRACE_OPEN => true,
 380+ self::TYPE_RETURN => true,
 381+ self::TYPE_IF => true,
 382+ self::TYPE_DO => true,
 383+ self::TYPE_FUNC => true,
 384+ self::TYPE_LITERAL => true
 385+ )
 386+ );
 387+
 388+ // Rules for when newlines should be inserted if
 389+ // $statementsOnOwnLine is enabled.
 390+ // $newlineBefore is checked before switching state,
 391+ // $newlineAfter is checked after
 392+ $newlineBefore = array(
 393+ self::STATEMENT => array(
 394+ self::TYPE_BRACE_CLOSE => true,
 395+ ),
 396+ );
 397+ $newlineAfter = array(
 398+ self::STATEMENT => array(
 399+ self::TYPE_BRACE_OPEN => true,
 400+ self::TYPE_PAREN_CLOSE => true,
 401+ self::TYPE_SEMICOLON => true,
 402+ ),
 403+ );
 404+
 405+ // $divStates : Contains all states that can be followed by a division operator
 406+ $divStates = array(
 407+ self::EXPRESSION_OP => true,
 408+ self::EXPRESSION_TERNARY_OP => true,
 409+ self::PAREN_EXPRESSION_OP => true,
 410+ self::PROPERTY_EXPRESSION_OP => true
 411+ );
 412+
 413+ // Here's where the minifying takes place: Loop through the input, looking for tokens
 414+ // and output them to $out, taking actions to the above defined rules when appropriate.
 415+ $out = '';
 416+ $pos = 0;
 417+ $length = strlen( $s );
 418+ $lineLength = 0;
 419+ $newlineFound = true;
 420+ $state = self::STATEMENT;
 421+ $stack = array();
 422+ $last = ';'; // Pretend that we have seen a semicolon yet
 423+ while( $pos < $length ) {
 424+ // First, skip over any whitespace and multiline comments, recording whether we
 425+ // found any newline character
 426+ $skip = strspn( $s, " \t\n\r\xb\xc", $pos );
 427+ if( !$skip ) {
 428+ $ch = $s[$pos];
 429+ if( $ch === '/' && substr( $s, $pos, 2 ) === '/*' ) {
 430+ // Multiline comment. Search for the end token or EOT.
 431+ $end = strpos( $s, '*/', $pos + 2 );
 432+ $skip = $end === false ? $length - $pos : $end - $pos + 2;
 433+ }
 434+ }
 435+ if( $skip ) {
 436+ // The semicolon insertion mechanism needs to know whether there was a newline
 437+ // between two tokens, so record it now.
 438+ if( !$newlineFound && strcspn( $s, "\r\n", $pos, $skip ) !== $skip ) {
 439+ $newlineFound = true;
 440+ }
 441+ $pos += $skip;
 442+ continue;
 443+ }
 444+ // Handle C++-style comments and html comments, which are treated as single line
 445+ // comments by the browser, regardless of whether the end tag is on the same line.
 446+ // Handle --> the same way, but only if it's at the beginning of the line
 447+ if( ( $ch === '/' && substr( $s, $pos, 2 ) === '//' )
 448+ || ( $ch === '<' && substr( $s, $pos, 4 ) === '<!--' )
 449+ || ( $ch === '-' && $newlineFound && substr( $s, $pos, 3 ) === '-->' )
 450+ ) {
 451+ $pos += strcspn( $s, "\r\n", $pos );
 452+ continue;
 453+ }
 454+
 455+ // Find out which kind of token we're handling. $end will point past the end of it.
 456+ $end = $pos + 1;
 457+ // Handle string literals
 458+ if( $ch === "'" || $ch === '"' ) {
 459+ // Search to the end of the string literal, skipping over backslash escapes
 460+ $search = $ch . '\\';
 461+ do{
 462+ $end += strcspn( $s, $search, $end ) + 2;
 463+ } while( $end - 2 < $length && $s[$end - 2] === '\\' );
 464+ $end--;
 465+ // We have to distinguish between regexp literals and division operators
 466+ // A division operator is only possible in certain states
 467+ } elseif( $ch === '/' && !isset( $divStates[$state] ) ) {
 468+ // Regexp literal, search to the end, skipping over backslash escapes and
 469+ // character classes
 470+ for( ; ; ) {
 471+ do{
 472+ $end += strcspn( $s, '/[\\', $end ) + 2;
 473+ } while( $end - 2 < $length && $s[$end - 2] === '\\' );
 474+ $end--;
 475+ if( $end - 1 >= $length || $s[$end - 1] === '/' ) {
 476+ break;
 477+ }
 478+ do{
 479+ $end += strcspn( $s, ']\\', $end ) + 2;
 480+ } while( $end - 2 < $length && $s[$end - 2] === '\\' );
 481+ $end--;
 482+ };
 483+ // Search past the regexp modifiers (gi)
 484+ while( $end < $length && ctype_alpha( $s[$end] ) ) {
 485+ $end++;
 486+ }
 487+ } elseif(
 488+ ctype_digit( $ch )
 489+ || ( $ch === '.' && $pos + 1 < $length && ctype_digit( $s[$pos + 1] ) )
 490+ ) {
 491+ // Numeric literal. Search for the end of it, but don't care about [+-]exponent
 492+ // at the end, as the results of "numeric [+-] numeric" and "numeric" are
 493+ // identical to our state machine.
 494+ $end += strspn( $s, '0123456789ABCDEFabcdefXx.', $end );
 495+ while( $s[$end - 1] === '.' ) {
 496+ // Special case: When a numeric ends with a dot, we have to check the
 497+ // literal for proper syntax
 498+ $decimal = strspn( $s, '0123456789', $pos, $end - $pos - 1 );
 499+ if( $decimal === $end - $pos - 1 ) {
 500+ break;
 501+ } else {
 502+ $end--;
 503+ }
 504+ }
 505+ } elseif( isset( $opChars[$ch] ) ) {
 506+ // Punctuation character. Search for the longest matching operator.
 507+ while(
 508+ $end < $length
 509+ && isset( $tokenTypes[substr( $s, $pos, $end - $pos + 1 )] )
 510+ ) {
 511+ $end++;
 512+ }
 513+ } else {
 514+ // Identifier or reserved word. Search for the end by excluding whitespace and
 515+ // punctuation.
 516+ $end += strcspn( $s, " \t\n.;,=<>+-{}()[]?:*/%'\"!&|^~\xb\xc\r", $end );
 517+ }
 518+
 519+ // Now get the token type from our type array
 520+ $token = substr( $s, $pos, $end - $pos ); // so $end - $pos == strlen( $token )
 521+ $type = isset( $tokenTypes[$token] ) ? $tokenTypes[$token] : self::TYPE_LITERAL;
 522+
 523+ if( $newlineFound && isset( $semicolon[$state][$type] ) ) {
 524+ // This token triggers the semicolon insertion mechanism of javascript. While we
 525+ // could add the ; token here ourselves, keeping the newline has a few advantages.
 526+ $out .= "\n";
 527+ $state = self::STATEMENT;
 528+ $lineLength = 0;
 529+ } elseif( $maxLineLength > 0 && $lineLength + $end - $pos > $maxLineLength &&
 530+ !isset( $semicolon[$state][$type] ) )
 531+ {
 532+ // This line would get too long if we added $token, so add a newline first.
 533+ // Only do this if it won't trigger semicolon insertion though.
 534+ $out .= "\n";
 535+ $lineLength = 0;
 536+ // Check, whether we have to separate the token from the last one with whitespace
 537+ } elseif( !isset( $opChars[$last] ) && !isset( $opChars[$ch] ) ) {
 538+ $out .= ' ';
 539+ $lineLength++;
 540+ // Don't accidentally create ++, -- or // tokens
 541+ } elseif( $last === $ch && ( $ch === '+' || $ch === '-' || $ch === '/' ) ) {
 542+ $out .= ' ';
 543+ $lineLength++;
 544+ }
 545+
 546+ $out .= $token;
 547+ $lineLength += $end - $pos; // += strlen( $token )
 548+ $last = $s[$end - 1];
 549+ $pos = $end;
 550+ $newlineFound = false;
 551+
 552+ // Output a newline after the token if required
 553+ // This is checked before AND after switching state
 554+ $newlineAdded = false;
 555+ if ( $statementsOnOwnLine && !$newlineAdded && isset( $newlineBefore[$state][$type] ) ) {
 556+ $out .= "\n";
 557+ $lineLength = 0;
 558+ $newlineAdded = true;
 559+ }
 560+
 561+ // Now that we have output our token, transition into the new state.
 562+ if( isset( $push[$state][$type] ) && count( $stack ) < self::STACK_LIMIT ) {
 563+ $stack[] = $push[$state][$type];
 564+ }
 565+ if( $stack && isset( $pop[$state][$type] ) ) {
 566+ $state = array_pop( $stack );
 567+ } elseif( isset( $goto[$state][$type] ) ) {
 568+ $state = $goto[$state][$type];
 569+ }
 570+
 571+ // Check for newline insertion again
 572+ if ( $statementsOnOwnLine && !$newlineAdded && isset( $newlineAfter[$state][$type] ) ) {
 573+ $out .= "\n";
 574+ $lineLength = 0;
 575+ }
 576+ }
 577+ return $out;
 578+ }
 579+}
Property changes on: branches/wmf/1.17wmf1/includes/libs/JavaScriptMinifier.php
___________________________________________________________________
Added: svn:eol-style
1580 + native
Index: branches/wmf/1.17wmf1/includes/libs/CSSMin.php
@@ -144,6 +144,9 @@
145145 $query = $match['query'][0];
146146 $url = "{$remote}/{$match['file'][0]}";
147147 $file = "{$local}/{$match['file'][0]}";
 148+ // bug 27052 - Guard against double slashes, because foo//../bar
 149+ // apparently resolves to foo/bar on (some?) clients
 150+ $url = preg_replace( '#([^:])//+#', '\1/', $url );
148151 $replacement = false;
149152 if ( $local !== false && file_exists( $file ) ) {
150153 // Add version parameter as a time-stamp in ISO 8601 format,
Index: branches/wmf/1.17wmf1/includes/DefaultSettings.php
@@ -2434,11 +2434,18 @@
24352435 $wgResourceLoaderUseESI = false;
24362436
24372437 /**
2438 - * Enable removal of some of the vertical whitespace (like \r and \n) from
2439 - * JavaScript code when minifying.
 2438+ * Put each statement on its own line when minifying JavaScript. This makes
 2439+ * debugging in non-debug mode a bit easier.
24402440 */
2441 -$wgResourceLoaderMinifyJSVerticalSpace = false;
 2441+$wgResourceLoaderMinifierStatementsOnOwnLine = false;
24422442
 2443+/**
 2444+ * Maximum line length when minifying JavaScript. This is not a hard maximum:
 2445+ * the minifier will try not to produce lines longer than this, but may be
 2446+ * forced to do so in certain cases.
 2447+ */
 2448+$wgResourceLoaderMinifierMaxLineLength = 1000;
 2449+
24432450 /** @} */ # End of resource loader settings }
24442451
24452452
Property changes on: branches/wmf/1.17wmf1/includes/DefaultSettings.php
___________________________________________________________________
Modified: svn:mergeinfo
24462453 Merged /trunk/phase3/includes/DefaultSettings.php:r81692,82468,83814,83885,83891,83897,83902-83903,83988-83989,83997-83998,84392

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r81692Fixes for ResourceLoaderWikiModule r72776. No serious bugs found, do not merg...tstarling06:34, 8 February 2011
r82468(bug 27302) Avoid unnecessary requests for user and site modules if the relev...catrope17:07, 19 February 2011
r83814(bug 27965) Paging in list=categorymembers was completely broken. It was pagi...catrope10:39, 13 March 2011
r83885(bug 27528) Incorporate Paul Copperman's minifiercatrope11:44, 14 March 2011
r83891Followup r83885: implement maximum line length and statement termination (eac...catrope13:24, 14 March 2011
r83897(bug 27054) Implicit end of statement can break stuff when files are combined...catrope14:25, 14 March 2011
r83902(bug 27052) CSSMin generated URLs like foo//../bar in some cases, which appar...catrope15:01, 14 March 2011
r83903(bug 27564) Timestamp of site module embedded in Squid-cached page HTMLcatrope15:16, 14 March 2011
r83988keep double slash filter from breaking absolute URLs, fix to r83902kaldari01:57, 15 March 2011
r83989oops, don't delete char before the slash, followup to r83902kaldari02:03, 15 March 2011
r83997the vertical tab \v and form feed \f escapes are not available in php5.2.4 wh...neilk04:37, 15 March 2011
r83998removing another use of the vertical tab & form feed escapes, which are not a...neilk04:41, 15 March 2011
r84392Per r83812 CR, solve the categorymembers paging problem by doing separate que...catrope16:25, 20 March 2011

Comments

#Comment by Bawolff (talk | contribs)   15:45, 24 March 2011

Note, r82524 of gadgets also needs to be backported to 1.17wmf1 since its needed to be compatible with r81692. (Causing bug 28215).

#Comment by Catrope (talk | contribs)   15:46, 24 March 2011

I did that in r84683, four minutes before you submitted this comment ;)

Status & tagging log