Index: branches/wmf/1.17wmf1/maintenance/minify.php |
— | — | @@ -17,9 +17,12 @@ |
18 | 18 | "Directory for output. If this is not specified, and neither is --outfile, then the\n" . |
19 | 19 | "output files will be sent to the same directories as the input files.", |
20 | 20 | 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.", |
23 | 23 | false, true ); |
| 24 | + $this->addOption( 'js-max-line-length', |
| 25 | + "Maximum line length for JavaScript minification.", |
| 26 | + false, true ); |
24 | 27 | $this->mDescription = "Minify a file or set of files.\n\n" . |
25 | 28 | "If --outfile is not specified, then the output file names will have a .min extension\n" . |
26 | 29 | "added, e.g. jquery.js -> jquery.min.js."; |
— | — | @@ -81,7 +84,7 @@ |
82 | 85 | } |
83 | 86 | |
84 | 87 | public function minify( $inPath, $outPath ) { |
85 | | - global $wgResourceLoaderMinifyJSVerticalSpace; |
| 88 | + global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength; |
86 | 89 | |
87 | 90 | $extension = $this->getExtension( $inPath ); |
88 | 91 | $this->output( basename( $inPath ) . ' -> ' . basename( $outPath ) . '...' ); |
— | — | @@ -99,7 +102,10 @@ |
100 | 103 | |
101 | 104 | switch ( $extension ) { |
102 | 105 | 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 | + ); |
104 | 110 | break; |
105 | 111 | case 'css': |
106 | 112 | $outText = CSSMin::minify( $inText ); |
Index: branches/wmf/1.17wmf1/includes/OutputPage.php |
— | — | @@ -2386,10 +2386,25 @@ |
2387 | 2387 | if ( ( $group === 'user' || $group === 'private' ) && $wgUser->isLoggedIn() ) { |
2388 | 2388 | $query['user'] = $wgUser->getName(); |
2389 | 2389 | } |
| 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 | + |
2390 | 2405 | $query['modules'] = implode( '|', array_keys( $modules ) ); |
| 2406 | + |
2391 | 2407 | // Support inlining of private modules if configured as such |
2392 | 2408 | if ( $group === 'private' && $wgResourceLoaderInlinePrivateModules ) { |
2393 | | - $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) ); |
2394 | 2409 | if ( $only == 'styles' ) { |
2395 | 2410 | $links .= Html::inlineStyle( |
2396 | 2411 | $resourceLoader->makeModuleResponse( $context, $modules ) |
— | — | @@ -2403,13 +2418,12 @@ |
2404 | 2419 | } |
2405 | 2420 | continue; |
2406 | 2421 | } |
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 |
2409 | 2424 | // 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' ) { |
2414 | 2428 | // Get the maximum timestamp |
2415 | 2429 | $timestamp = 1; |
2416 | 2430 | foreach ( $modules as $module ) { |
Property changes on: branches/wmf/1.17wmf1/includes/OutputPage.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
2417 | 2431 | 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 @@ |
247 | 247 | * Execute a SELECT query based on the values in the internal arrays |
248 | 248 | * @param $method string Function the query should be attributed to. |
249 | 249 | * 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' => ... ) |
250 | 252 | * @return ResultWrapper |
251 | 253 | */ |
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 | + |
253 | 260 | // getDB has its own profileDBIn/Out calls |
254 | 261 | $db = $this->getDB(); |
255 | 262 | |
256 | 263 | $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 ); |
258 | 265 | $this->profileDBOut(); |
259 | 266 | |
260 | 267 | return $res; |
Index: branches/wmf/1.17wmf1/includes/api/ApiQueryCategoryMembers.php |
— | — | @@ -70,22 +70,21 @@ |
71 | 71 | $fld_type = isset( $prop['type'] ); |
72 | 72 | |
73 | 73 | 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' ) ); |
75 | 75 | $this->addFieldsIf( 'page_id', $fld_ids ); |
76 | 76 | $this->addFieldsIf( 'cl_sortkey_prefix', $fld_sortkeyprefix ); |
77 | | - $this->addFieldsIf( 'cl_sortkey', $fld_sortkey ); |
78 | 77 | } else { |
79 | 78 | $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' ) ); |
81 | 80 | } |
82 | 81 | |
83 | 82 | $this->addFieldsIf( 'cl_timestamp', $fld_timestamp || $params['sort'] == 'timestamp' ); |
84 | | - $this->addFieldsIf( 'cl_type', $fld_type ); |
85 | 83 | |
86 | 84 | $this->addTables( array( 'page', 'categorylinks' ) ); // must be in this order for 'USE INDEX' |
87 | 85 | |
88 | 86 | $this->addWhereFld( 'cl_to', $categoryTitle->getDBkey() ); |
89 | | - $this->addWhereFld( 'cl_type', $params['type'] ); |
| 87 | + $queryTypes = $params['type']; |
| 88 | + $contWhere = false; |
90 | 89 | |
91 | 90 | // Scanning large datasets for rare categories sucks, and I already told |
92 | 91 | // how to have efficient subcategory access :-) ~~~~ (oh well, domas) |
— | — | @@ -107,33 +106,80 @@ |
108 | 107 | |
109 | 108 | $this->addOption( 'USE INDEX', 'cl_timestamp' ); |
110 | 109 | } 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 | + } |
118 | 140 | $this->addOption( 'USE INDEX', 'cl_sortkey' ); |
119 | 141 | } |
120 | 142 | |
121 | | - $this->setContinuation( $params['continue'], $params['dir'] ); |
122 | | - |
123 | 143 | $this->addWhere( 'cl_from=page_id' ); |
124 | 144 | |
125 | 145 | $limit = $params['limit']; |
126 | 146 | $this->addOption( 'LIMIT', $limit + 1 ); |
127 | 147 | |
| 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 | + } |
128 | 169 | $count = 0; |
129 | | - $res = $this->select( __METHOD__ ); |
130 | | - foreach ( $res as $row ) { |
| 170 | + foreach ( $rows as $row ) { |
131 | 171 | if ( ++ $count > $limit ) { |
132 | 172 | // We've reached the one extra which shows that there are additional pages to be had. Stop here... |
133 | 173 | // TODO: Security issue - if the user has no right to view next title, it will still be shown |
134 | 174 | if ( $params['sort'] == 'timestamp' ) { |
135 | 175 | $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->cl_timestamp ) ); |
136 | 176 | } 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 | + ); |
138 | 184 | } |
139 | 185 | break; |
140 | 186 | } |
— | — | @@ -173,7 +219,9 @@ |
174 | 220 | if ( $params['sort'] == 'timestamp' ) { |
175 | 221 | $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->cl_timestamp ) ); |
176 | 222 | } else { |
177 | | - $this->setContinueEnumParameter( 'continue', $row->cl_from ); |
| 223 | + $this->setContinueEnumParameter( 'continue', |
| 224 | + "{$row->cl_type}|{$row->cl_from}|{$row->cl_sortkey}" |
| 225 | + ); |
178 | 226 | } |
179 | 227 | break; |
180 | 228 | } |
— | — | @@ -188,21 +236,6 @@ |
189 | 237 | } |
190 | 238 | } |
191 | 239 | |
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 | | - |
207 | 240 | public function getAllowedParams() { |
208 | 241 | return array( |
209 | 242 | 'title' => array( |
— | — | @@ -296,7 +329,8 @@ |
297 | 330 | $desc['namespace'] = array( |
298 | 331 | $desc['namespace'], |
299 | 332 | '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.', |
301 | 335 | ); |
302 | 336 | } |
303 | 337 | return $desc; |
Index: branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderSiteModule.php |
— | — | @@ -36,15 +36,14 @@ |
37 | 37 | global $wgHandheldStyle; |
38 | 38 | |
39 | 39 | $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' ), |
45 | 45 | ); |
46 | 46 | if ( $wgHandheldStyle ) { |
47 | | - $pages['Handheld.css'] = array( |
48 | | - 'ns' => NS_MEDIAWIKI, |
| 47 | + $pages['MediaWiki:Handheld.css'] = array( |
49 | 48 | 'type' => 'style', |
50 | 49 | 'media' => 'handheld' ); |
51 | 50 | } |
Index: branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoader.php |
— | — | @@ -29,7 +29,7 @@ |
30 | 30 | class ResourceLoader { |
31 | 31 | |
32 | 32 | /* Protected Static Members */ |
33 | | - protected static $filterCacheVersion = 1; |
| 33 | + protected static $filterCacheVersion = 2; |
34 | 34 | |
35 | 35 | /** Array: List of module name/ResourceLoaderModule object pairs */ |
36 | 36 | protected $modules = array(); |
— | — | @@ -110,7 +110,7 @@ |
111 | 111 | * Runs JavaScript or CSS data through a filter, caching the filtered result for future calls. |
112 | 112 | * |
113 | 113 | * Available filters are: |
114 | | - * - minify-js \see JavaScriptDistiller::stripWhiteSpace |
| 114 | + * - minify-js \see JavaScriptMinifier::minify |
115 | 115 | * - minify-css \see CSSMin::minify |
116 | 116 | * |
117 | 117 | * If $data is empty, only contains whitespace or the filter was unknown, |
— | — | @@ -121,8 +121,7 @@ |
122 | 122 | * @return String: Filtered data, or a comment containing an error message |
123 | 123 | */ |
124 | 124 | protected function filter( $filter, $data ) { |
125 | | - global $wgResourceLoaderMinifyJSVerticalSpace; |
126 | | - |
| 125 | + global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength; |
127 | 126 | wfProfileIn( __METHOD__ ); |
128 | 127 | |
129 | 128 | // For empty/whitespace-only data or for unknown filters, don't perform |
— | — | @@ -148,8 +147,9 @@ |
149 | 148 | try { |
150 | 149 | switch ( $filter ) { |
151 | 150 | case 'minify-js': |
152 | | - $result = JavaScriptDistiller::stripWhiteSpace( |
153 | | - $data, $wgResourceLoaderMinifyJSVerticalSpace |
| 151 | + $result = JavaScriptMinifier::minify( $data, |
| 152 | + $wgResourceLoaderMinifierStatementsOnOwnLine, |
| 153 | + $wgResourceLoaderMinifierMaxLineLength |
154 | 154 | ); |
155 | 155 | $result .= "\n\n/* cache key: $key */\n"; |
156 | 156 | break; |
— | — | @@ -467,7 +467,9 @@ |
468 | 468 | // Scripts |
469 | 469 | $scripts = ''; |
470 | 470 | 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"; |
472 | 474 | } |
473 | 475 | |
474 | 476 | // Styles |
Property changes on: branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoader.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
475 | 477 | 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 @@ |
32 | 32 | if ( $context->getUser() ) { |
33 | 33 | $username = $context->getUser(); |
34 | 34 | 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' ), |
41 | 41 | ); |
42 | 42 | } |
43 | 43 | return array(); |
Index: branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderModule.php |
— | — | @@ -228,4 +228,17 @@ |
229 | 229 | // 0 would mean now |
230 | 230 | return 1; |
231 | 231 | } |
| 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 | + } |
232 | 245 | } |
Index: branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderWikiModule.php |
— | — | @@ -33,8 +33,8 @@ |
34 | 34 | |
35 | 35 | /* Protected Members */ |
36 | 36 | |
37 | | - // In-object cache for modified time |
38 | | - protected $modifiedTime = array(); |
| 37 | + // In-object cache for title mtimes |
| 38 | + protected $titleMtimes = array(); |
39 | 39 | |
40 | 40 | /* Abstract Protected Methods */ |
41 | 41 | |
— | — | @@ -42,12 +42,12 @@ |
43 | 43 | |
44 | 44 | /* Protected Methods */ |
45 | 45 | |
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' ); |
49 | 50 | } |
50 | | - $title = Title::newFromText( $page, $ns ); |
51 | | - if ( !$title || !$title->isCssJsSubpage() ) { |
| 51 | + if ( !$title->isCssJsSubpage() ) { |
52 | 52 | return null; |
53 | 53 | } |
54 | 54 | $revision = Revision::newFromTitle( $title ); |
— | — | @@ -61,15 +61,21 @@ |
62 | 62 | |
63 | 63 | public function getScript( ResourceLoaderContext $context ) { |
64 | 64 | $scripts = ''; |
65 | | - foreach ( $this->getPages( $context ) as $page => $options ) { |
| 65 | + foreach ( $this->getPages( $context ) as $titleText => $options ) { |
66 | 66 | if ( $options['type'] !== 'script' ) { |
67 | 67 | continue; |
68 | 68 | } |
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; |
73 | 72 | } |
| 73 | + $script = $this->getContent( $title ); |
| 74 | + if ( strval( $script ) !== '' ) { |
| 75 | + if ( strpos( $titleText, '*/' ) === false ) { |
| 76 | + $scripts .= "/* $titleText */\n"; |
| 77 | + } |
| 78 | + $scripts .= $script . "\n"; |
| 79 | + } |
74 | 80 | } |
75 | 81 | return $scripts; |
76 | 82 | } |
— | — | @@ -78,13 +84,17 @@ |
79 | 85 | global $wgScriptPath; |
80 | 86 | |
81 | 87 | $styles = array(); |
82 | | - foreach ( $this->getPages( $context ) as $page => $options ) { |
| 88 | + foreach ( $this->getPages( $context ) as $titleText => $options ) { |
83 | 89 | if ( $options['type'] !== 'style' ) { |
84 | 90 | continue; |
85 | 91 | } |
| 92 | + $title = Title::newFromText( $titleText ); |
| 93 | + if ( !$title ) { |
| 94 | + continue; |
| 95 | + } |
86 | 96 | $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 ) === '' ) { |
89 | 99 | continue; |
90 | 100 | } |
91 | 101 | if ( $this->getFlip( $context ) ) { |
— | — | @@ -94,36 +104,58 @@ |
95 | 105 | if ( !isset( $styles[$media] ) ) { |
96 | 106 | $styles[$media] = ''; |
97 | 107 | } |
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"; |
100 | 112 | } |
101 | 113 | return $styles; |
102 | 114 | } |
103 | 115 | |
104 | 116 | 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 ) { |
105 | 136 | $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]; |
108 | 139 | } |
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 ) ); |
113 | 145 | } |
114 | | - |
115 | | - $modifiedTime = 1; // wfTimestamp() interprets 0 as "now" |
116 | | - |
117 | | - if ( $titles ) { |
| 146 | + |
| 147 | + if ( !$batch->isEmpty() ) { |
118 | 148 | $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 ); |
125 | 158 | } |
126 | 159 | } |
127 | | - |
128 | | - return $this->modifiedTime[$hash] = $modifiedTime; |
| 160 | + return $this->titleMtimes[$hash]; |
129 | 161 | } |
130 | 162 | } |
Index: branches/wmf/1.17wmf1/includes/AutoLoader.php |
— | — | @@ -137,6 +137,7 @@ |
138 | 138 | 'Interwiki' => 'includes/Interwiki.php', |
139 | 139 | 'IP' => 'includes/IP.php', |
140 | 140 | 'JavaScriptDistiller' => 'includes/libs/JavaScriptDistiller.php', |
| 141 | + 'JavaScriptMinifier' => 'includes/libs/JavaScriptMinifier.php', |
141 | 142 | 'LCStore_DB' => 'includes/LocalisationCache.php', |
142 | 143 | 'LCStore_CDB' => 'includes/LocalisationCache.php', |
143 | 144 | 'LCStore_Null' => 'includes/LocalisationCache.php', |
Property changes on: branches/wmf/1.17wmf1/includes/AutoLoader.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
144 | 145 | 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 |
1 | 580 | + native |
Index: branches/wmf/1.17wmf1/includes/libs/CSSMin.php |
— | — | @@ -144,6 +144,9 @@ |
145 | 145 | $query = $match['query'][0]; |
146 | 146 | $url = "{$remote}/{$match['file'][0]}"; |
147 | 147 | $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 ); |
148 | 151 | $replacement = false; |
149 | 152 | if ( $local !== false && file_exists( $file ) ) { |
150 | 153 | // Add version parameter as a time-stamp in ISO 8601 format, |
Index: branches/wmf/1.17wmf1/includes/DefaultSettings.php |
— | — | @@ -2434,11 +2434,18 @@ |
2435 | 2435 | $wgResourceLoaderUseESI = false; |
2436 | 2436 | |
2437 | 2437 | /** |
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. |
2440 | 2440 | */ |
2441 | | -$wgResourceLoaderMinifyJSVerticalSpace = false; |
| 2441 | +$wgResourceLoaderMinifierStatementsOnOwnLine = false; |
2442 | 2442 | |
| 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 | + |
2443 | 2450 | /** @} */ # End of resource loader settings } |
2444 | 2451 | |
2445 | 2452 | |
Property changes on: branches/wmf/1.17wmf1/includes/DefaultSettings.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
2446 | 2453 | Merged /trunk/phase3/includes/DefaultSettings.php:r81692,82468,83814,83885,83891,83897,83902-83903,83988-83989,83997-83998,84392 |