Index: trunk/phase3/includes/resourceloader/ResourceLoader.php |
— | — | @@ -25,33 +25,35 @@ |
26 | 26 | /** |
27 | 27 | * Dynamic JavaScript and CSS resource loading system. |
28 | 28 | * |
29 | | - * Most of the documention is on the MediaWiki documentation wiki starting at |
| 29 | + * Most of the documention is on the MediaWiki documentation wiki starting at: |
30 | 30 | * http://www.mediawiki.org/wiki/ResourceLoader |
31 | 31 | */ |
32 | 32 | class ResourceLoader { |
33 | 33 | |
34 | 34 | /* Protected Static Members */ |
35 | 35 | |
36 | | - // @var array list of module name/ResourceLoaderModule object pairs |
| 36 | + /** @var {array} List of module name/ResourceLoaderModule object pairs */ |
37 | 37 | protected $modules = array(); |
38 | 38 | |
39 | 39 | /* Protected Methods */ |
40 | | - |
| 40 | + |
41 | 41 | /** |
42 | | - * Loads information stored in the database about modules |
| 42 | + * Loads information stored in the database about modules. |
43 | 43 | * |
| 44 | + * This method grabs modules dependencies from the database and updates modules objects. |
| 45 | + * |
44 | 46 | * This is not inside the module code because it's so much more performant to request all of the information at once |
45 | | - * than it is to have each module requests it's own information. |
46 | | - * |
47 | | - * This method grab modules dependencies from the database and initialize modules object. |
48 | | - * A first pass compute dependencies, a second one the blob mtime. |
49 | | - * |
50 | | - * @param $modules Array List of module names to preload information for |
51 | | - * @param $context ResourceLoaderContext Context to load the information within |
| 47 | + * than it is to have each module requests it's own information. This sacrifice of modularity yields a profound |
| 48 | + * performance improvement. |
| 49 | + * |
| 50 | + * A first pass calculates dependent file modified times, a second one calculates message blob modified times. |
| 51 | + * |
| 52 | + * @param {array} $modules List of module names to preload information for |
| 53 | + * @param {ResourceLoaderContext} $context Context to load the information within |
52 | 54 | */ |
53 | 55 | protected function preloadModuleInfo( array $modules, ResourceLoaderContext $context ) { |
54 | 56 | if ( !count( $modules ) ) { |
55 | | - return; # or Database*::select() will explode |
| 57 | + return; // or else Database*::select() will explode, plus it's cheaper! |
56 | 58 | } |
57 | 59 | $dbr = wfGetDb( DB_SLAVE ); |
58 | 60 | $skin = $context->getSkin(); |
— | — | @@ -72,6 +74,7 @@ |
73 | 75 | ); |
74 | 76 | $modulesWithDeps[] = $row->md_module; |
75 | 77 | } |
| 78 | + |
76 | 79 | // Register the absence of a dependency row too |
77 | 80 | foreach ( array_diff( $modules, $modulesWithDeps ) as $name ) { |
78 | 81 | $this->modules[$name]->setFileDependencies( $skin, array() ); |
— | — | @@ -103,44 +106,40 @@ |
104 | 107 | } |
105 | 108 | |
106 | 109 | /** |
107 | | - * Runs text (js,CSS) through a filter, caching the filtered result for future calls. |
| 110 | + * Runs JavaScript or CSS data through a filter, caching the filtered result for future calls. |
108 | 111 | * |
109 | 112 | * Availables filters are: |
110 | 113 | * - minify-js \see JSMin::minify |
111 | 114 | * - minify-css \see CSSMin::minify |
112 | 115 | * - flip-css \see CSSJanus::transform |
113 | | - * When the filter names does not exist, text is returned as is. |
114 | | - * |
115 | | - * @param $filter String: name of filter to run |
116 | | - * @param $data String: text to filter, such as JavaScript or CSS text |
117 | | - * @param $file String: path to file being filtered, (optional: only required for CSS to resolve paths) |
118 | | - * @return String: filtered data |
| 116 | + * |
| 117 | + * If data is empty, only whitespace or the filter was unknown, data is returned unmodified. |
| 118 | + * |
| 119 | + * @param {string} $filter Name of filter to run |
| 120 | + * @param {string} $data Text to filter, such as JavaScript or CSS text |
| 121 | + * @return {string} Filtered data |
119 | 122 | */ |
120 | 123 | protected function filter( $filter, $data ) { |
121 | 124 | global $wgMemc; |
| 125 | + |
122 | 126 | wfProfileIn( __METHOD__ ); |
123 | 127 | |
124 | | - // For empty or whitespace-only things, don't do any processing |
125 | | - # FIXME: we should return the data unfiltered if $filter is not supported. |
126 | | - # that would save up a md5 computation and one memcached get. |
127 | | - if ( trim( $data ) === '' ) { |
| 128 | + // For empty/whitespace-only data or for unknown filters, don't perform any caching or processing |
| 129 | + if ( trim( $data ) === '' || !in_array( $filter, array( 'minify-js', 'minify-css', 'flip-css' ) ) ) { |
128 | 130 | wfProfileOut( __METHOD__ ); |
129 | 131 | return $data; |
130 | 132 | } |
131 | 133 | |
132 | | - // Try memcached |
| 134 | + // Try for Memcached hit |
133 | 135 | $key = wfMemcKey( 'resourceloader', 'filter', $filter, md5( $data ) ); |
134 | | - $cached = $wgMemc->get( $key ); |
135 | | - |
136 | | - if ( $cached !== false && $cached !== null ) { |
| 136 | + if ( is_string( $cache = $wgMemc->get( $key ) ) ) { |
137 | 137 | wfProfileOut( __METHOD__ ); |
138 | | - return $cached; |
| 138 | + return $cache; |
139 | 139 | } |
140 | 140 | |
141 | | - // Run the filter |
| 141 | + // Run the filter - we've already verified one of these will work |
142 | 142 | try { |
143 | 143 | switch ( $filter ) { |
144 | | - # note: if adding a new filter. Please update method documentation above. |
145 | 144 | case 'minify-js': |
146 | 145 | $result = JSMin::minify( $data ); |
147 | 146 | break; |
— | — | @@ -150,26 +149,23 @@ |
151 | 150 | case 'flip-css': |
152 | 151 | $result = CSSJanus::transform( $data, true, false ); |
153 | 152 | break; |
154 | | - default: |
155 | | - // Don't cache anything, just pass right through |
156 | | - wfProfileOut( __METHOD__ ); |
157 | | - return $data; |
158 | 153 | } |
159 | 154 | } catch ( Exception $exception ) { |
160 | | - throw new MWException( 'Filter threw an exception: ' . $exception->getMessage() ); |
| 155 | + throw new MWException( 'ResourceLoader filter error. Exception was thrown: ' . $exception->getMessage() ); |
161 | 156 | } |
162 | 157 | |
163 | | - // Save filtered text to memcached |
| 158 | + // Save filtered text to Memcached |
164 | 159 | $wgMemc->set( $key, $result ); |
165 | 160 | |
166 | 161 | wfProfileOut( __METHOD__ ); |
| 162 | + |
167 | 163 | return $result; |
168 | 164 | } |
169 | 165 | |
170 | 166 | /* Methods */ |
171 | 167 | |
172 | 168 | /** |
173 | | - * Registers core modules and runs registration hooks |
| 169 | + * Registers core modules and runs registration hooks. |
174 | 170 | */ |
175 | 171 | public function __construct() { |
176 | 172 | global $IP; |
— | — | @@ -186,23 +182,17 @@ |
187 | 183 | |
188 | 184 | /** |
189 | 185 | * Registers a module with the ResourceLoader system. |
190 | | - * |
191 | | - * Note that registering the same object under multiple names is not supported |
192 | | - * and may silently fail in all kinds of interesting ways. |
193 | | - * |
194 | | - * @param $name Mixed: string of name of module or array of name/object pairs |
195 | | - * @param $object ResourceLoaderModule: module object (optional when using |
196 | | - * multiple-registration calling style) |
197 | | - * @return Boolean: false if there were any errors, in which case one or more |
198 | | - * modules were not registered |
199 | | - * |
200 | | - * @todo We need much more clever error reporting, not just in detailing what |
201 | | - * happened, but in bringing errors to the client in a way that they can |
202 | | - * easily see them if they want to, such as by using FireBug |
| 186 | + * |
| 187 | + * @param {mixed} $name string of name of module or array of name/object pairs |
| 188 | + * @param {ResourceLoaderModule} $object module object (optional when using multiple-registration calling style) |
| 189 | + * @throws {MWException} If a duplicate module registration is attempted |
| 190 | + * @throws {MWException} If something other than a ResourceLoaderModule is being registered |
| 191 | + * @return {bool} false if there were any errors, in which case one or more modules were not registered |
203 | 192 | */ |
204 | 193 | public function register( $name, ResourceLoaderModule $object = null ) { |
| 194 | + |
205 | 195 | wfProfileIn( __METHOD__ ); |
206 | | - |
| 196 | + |
207 | 197 | // Allow multiple modules to be registered in one call |
208 | 198 | if ( is_array( $name ) && !isset( $object ) ) { |
209 | 199 | foreach ( $name as $key => $value ) { |
— | — | @@ -210,20 +200,23 @@ |
211 | 201 | } |
212 | 202 | |
213 | 203 | wfProfileOut( __METHOD__ ); |
| 204 | + |
214 | 205 | return; |
215 | 206 | } |
216 | 207 | |
217 | 208 | // Disallow duplicate registrations |
218 | 209 | if ( isset( $this->modules[$name] ) ) { |
219 | 210 | // A module has already been registered by this name |
220 | | - throw new MWException( 'Another module has already been registered as ' . $name ); |
| 211 | + throw new MWException( |
| 212 | + 'ResourceLoader duplicate registration error. Another module has already been registered as ' . $name |
| 213 | + ); |
221 | 214 | } |
222 | | - |
| 215 | + |
223 | 216 | // Validate the input (type hinting lets null through) |
224 | 217 | if ( !( $object instanceof ResourceLoaderModule ) ) { |
225 | | - throw new MWException( 'Invalid ResourceLoader module error. Instances of ResourceLoaderModule expected.' ); |
| 218 | + throw new MWException( 'ResourceLoader invalid module error. Instances of ResourceLoaderModule expected.' ); |
226 | 219 | } |
227 | | - |
| 220 | + |
228 | 221 | // Attach module |
229 | 222 | $this->modules[$name] = $object; |
230 | 223 | $object->setName( $name ); |
— | — | @@ -234,36 +227,35 @@ |
235 | 228 | /** |
236 | 229 | * Gets a map of all modules and their options |
237 | 230 | * |
238 | | - * @return Array: array( modulename => ResourceLoaderModule ) |
| 231 | + * @return {array} array( modulename => ResourceLoaderModule ) |
239 | 232 | */ |
240 | 233 | public function getModules() { |
241 | 234 | return $this->modules; |
242 | 235 | } |
243 | 236 | |
244 | 237 | /** |
245 | | - * Get the ResourceLoaderModule object for a given module name |
| 238 | + * Get the ResourceLoaderModule object for a given module name. |
246 | 239 | * |
247 | | - * @param $name String: module name |
248 | | - * @return mixed ResourceLoaderModule or null if not registered |
| 240 | + * @param {string} $name module name |
| 241 | + * @return {mixed} ResourceLoaderModule if module has been registered, null otherwise |
249 | 242 | */ |
250 | 243 | public function getModule( $name ) { |
251 | 244 | return isset( $this->modules[$name] ) ? $this->modules[$name] : null; |
252 | 245 | } |
253 | 246 | |
254 | 247 | /** |
255 | | - * Outputs a response to a resource load-request, including a content-type header |
| 248 | + * Outputs a response to a resource load-request, including a content-type header. |
256 | 249 | * |
257 | | - * @param $context ResourceLoaderContext object |
| 250 | + * @param {ResourceLoaderContext} $context Context in which a response should be formed |
258 | 251 | */ |
259 | 252 | public function respond( ResourceLoaderContext $context ) { |
260 | 253 | global $wgResourceLoaderMaxage, $wgCacheEpoch; |
261 | 254 | |
262 | 255 | wfProfileIn( __METHOD__ ); |
263 | | - |
| 256 | + |
264 | 257 | // Split requested modules into two groups, modules and missing |
265 | 258 | $modules = array(); |
266 | 259 | $missing = array(); |
267 | | - |
268 | 260 | foreach ( $context->getModules() as $name ) { |
269 | 261 | if ( isset( $this->modules[$name] ) ) { |
270 | 262 | $modules[$name] = $this->modules[$name]; |
— | — | @@ -272,14 +264,12 @@ |
273 | 265 | } |
274 | 266 | } |
275 | 267 | |
276 | | - // If a version wasn't specified we need a shorter expiry time for updates to |
277 | | - // propagate to clients quickly |
| 268 | + // If a version wasn't specified we need a shorter expiry time for updates to propagate to clients quickly |
278 | 269 | if ( is_null( $context->getVersion() ) ) { |
279 | 270 | $maxage = $wgResourceLoaderMaxage['unversioned']['client']; |
280 | 271 | $smaxage = $wgResourceLoaderMaxage['unversioned']['server']; |
281 | 272 | } |
282 | | - // If a version was specified we can use a longer expiry time since changing |
283 | | - // version numbers causes cache misses |
| 273 | + // If a version was specified we can use a longer expiry time since changing version numbers causes cache misses |
284 | 274 | else { |
285 | 275 | $maxage = $wgResourceLoaderMaxage['versioned']['client']; |
286 | 276 | $smaxage = $wgResourceLoaderMaxage['versioned']['server']; |
— | — | @@ -288,9 +278,9 @@ |
289 | 279 | // Preload information needed to the mtime calculation below |
290 | 280 | $this->preloadModuleInfo( array_keys( $modules ), $context ); |
291 | 281 | |
292 | | - // To send Last-Modified and support If-Modified-Since, we need to detect |
293 | | - // the last modified time |
294 | 282 | wfProfileIn( __METHOD__.'-getModifiedTime' ); |
| 283 | + |
| 284 | + // To send Last-Modified and support If-Modified-Since, we need to detect the last modified time |
295 | 285 | $mtime = wfTimestamp( TS_UNIX, $wgCacheEpoch ); |
296 | 286 | foreach ( $modules as $module ) { |
297 | 287 | // Bypass squid cache if the request includes any private modules |
— | — | @@ -300,6 +290,7 @@ |
301 | 291 | // Calculate maximum modified time |
302 | 292 | $mtime = max( $mtime, $module->getModifiedTime( $context ) ); |
303 | 293 | } |
| 294 | + |
304 | 295 | wfProfileOut( __METHOD__.'-getModifiedTime' ); |
305 | 296 | |
306 | 297 | header( 'Content-Type: ' . ( $context->getOnly() === 'styles' ? 'text/css' : 'text/javascript' ) ); |
— | — | @@ -315,11 +306,15 @@ |
316 | 307 | wfProfileOut( __METHOD__ ); |
317 | 308 | return; |
318 | 309 | } |
319 | | - |
| 310 | + |
| 311 | + // Generate a response |
320 | 312 | $response = $this->makeModuleResponse( $context, $modules, $missing ); |
| 313 | + |
| 314 | + // Tack on PHP warnings as a comment in debug mode |
321 | 315 | if ( $context->getDebug() && strlen( $warnings = ob_get_contents() ) ) { |
322 | 316 | $response .= "/*\n$warnings\n*/"; |
323 | 317 | } |
| 318 | + |
324 | 319 | // Clear any warnings from the buffer |
325 | 320 | ob_clean(); |
326 | 321 | echo $response; |
— | — | @@ -328,10 +323,12 @@ |
329 | 324 | } |
330 | 325 | |
331 | 326 | /** |
332 | | - * |
333 | | - * @param $context ResourceLoaderContext |
334 | | - * @param $modules Array array( modulename => ResourceLoaderModule ) |
335 | | - * @param $missing Unavailables modules (Default null) |
| 327 | + * Generates code for a response |
| 328 | + * |
| 329 | + * @param {ResourceLoaderContext} $context Context in which to generate a response |
| 330 | + * @param {array} $modules List of module objects keyed by module name |
| 331 | + * @param {array} $missing List of unavailables modules (optional) |
| 332 | + * @return {string} Response data |
336 | 333 | */ |
337 | 334 | public function makeModuleResponse( ResourceLoaderContext $context, array $modules, $missing = null ) { |
338 | 335 | // Pre-fetch blobs |
— | — | @@ -341,6 +338,7 @@ |
342 | 339 | // Generate output |
343 | 340 | $out = ''; |
344 | 341 | foreach ( $modules as $name => $module ) { |
| 342 | + |
345 | 343 | wfProfileIn( __METHOD__ . '-' . $name ); |
346 | 344 | |
347 | 345 | // Scripts |
— | — | @@ -384,7 +382,7 @@ |
385 | 383 | $out .= self::makeLoaderImplementScript( $name, $scripts, $styles, $messages ); |
386 | 384 | break; |
387 | 385 | } |
388 | | - |
| 386 | + |
389 | 387 | wfProfileOut( __METHOD__ . '-' . $name ); |
390 | 388 | } |
391 | 389 | |
— | — | @@ -410,9 +408,9 @@ |
411 | 409 | } |
412 | 410 | } |
413 | 411 | } |
414 | | - |
| 412 | + |
415 | 413 | /* Static Methods */ |
416 | | - |
| 414 | + |
417 | 415 | public static function makeLoaderImplementScript( $name, $scripts, $styles, $messages ) { |
418 | 416 | if ( is_array( $scripts ) ) { |
419 | 417 | $scripts = implode( $scripts, "\n" ); |
Index: trunk/phase3/includes/resourceloader/ResourceLoaderFileModule.php |
— | — | @@ -23,7 +23,7 @@ |
24 | 24 | defined( 'MEDIAWIKI' ) || die( 1 ); |
25 | 25 | |
26 | 26 | /** |
27 | | - * ResourceLoader module based on local JS/CSS files |
| 27 | + * ResourceLoader module based on local JavaScript/CSS files. |
28 | 28 | */ |
29 | 29 | class ResourceLoaderFileModule extends ResourceLoaderModule { |
30 | 30 | |
— | — | @@ -55,7 +55,7 @@ |
56 | 56 | /* Methods */ |
57 | 57 | |
58 | 58 | /** |
59 | | - * Construct a new module from an options array. |
| 59 | + * Constructs a new module from an options array. |
60 | 60 | * |
61 | 61 | * @param {array} $options Options array. If not given or empty, an empty module will be constructed |
62 | 62 | * @param {string} $basePath base path to prepend to all paths in $options |
— | — | @@ -121,7 +121,7 @@ |
122 | 122 | } |
123 | 123 | |
124 | 124 | /** |
125 | | - * Gets all scripts for a given context concatenated together |
| 125 | + * Gets all scripts for a given context concatenated together. |
126 | 126 | * |
127 | 127 | * @param {ResourceLoaderContext} $context Context in which to generate script |
128 | 128 | * @return {string} JavaScript code for $context |
— | — | @@ -137,7 +137,7 @@ |
138 | 138 | } |
139 | 139 | |
140 | 140 | /** |
141 | | - * Gets loader script |
| 141 | + * Gets loader script. |
142 | 142 | * |
143 | 143 | * @return {string} JavaScript code to be added to startup module |
144 | 144 | */ |
— | — | @@ -149,7 +149,7 @@ |
150 | 150 | } |
151 | 151 | |
152 | 152 | /** |
153 | | - * Gets all styles for a given context concatenated together |
| 153 | + * Gets all styles for a given context concatenated together. |
154 | 154 | * |
155 | 155 | * @param {ResourceLoaderContext} $context Context in which to generate styles |
156 | 156 | * @return {string} CSS code for $context |
— | — | @@ -185,7 +185,7 @@ |
186 | 186 | } |
187 | 187 | |
188 | 188 | /** |
189 | | - * Gets list of message keys used by this module |
| 189 | + * Gets list of message keys used by this module. |
190 | 190 | * |
191 | 191 | * @return {array} List of message keys |
192 | 192 | */ |
— | — | @@ -194,7 +194,7 @@ |
195 | 195 | } |
196 | 196 | |
197 | 197 | /** |
198 | | - * Gets the name of the group this module should be loaded in |
| 198 | + * Gets the name of the group this module should be loaded in. |
199 | 199 | * |
200 | 200 | * @return {string} Group name |
201 | 201 | */ |
— | — | @@ -203,7 +203,7 @@ |
204 | 204 | } |
205 | 205 | |
206 | 206 | /** |
207 | | - * Gets list of names of modules this module depends on |
| 207 | + * Gets list of names of modules this module depends on. |
208 | 208 | * |
209 | 209 | * @return {array} List of module names |
210 | 210 | */ |
— | — | @@ -212,10 +212,12 @@ |
213 | 213 | } |
214 | 214 | |
215 | 215 | /** |
216 | | - * Get the last modified timestamp of this module, which is calculated as the highest last modified timestamp of its |
217 | | - * constituent files and the files it depends on. This function is context-sensitive, only performing calculations |
218 | | - * on files relevant to the given language, skin and debug mode. |
| 216 | + * Get the last modified timestamp of this module. |
219 | 217 | * |
| 218 | + * Last modified timestamps are calculated from the highest last modified timestamp of this module's constituent |
| 219 | + * files as well as the files it depends on. This function is context-sensitive, only performing calculations on |
| 220 | + * files relevant to the given language, skin and debug mode. |
| 221 | + * |
220 | 222 | * @param {ResourceLoaderContext} $context Context in which to calculate the modified time |
221 | 223 | * @return {integer} UNIX timestamp |
222 | 224 | * @see {ResourceLoaderModule::getFileDependencies} |
— | — | @@ -262,7 +264,7 @@ |
263 | 265 | /* Protected Members */ |
264 | 266 | |
265 | 267 | /** |
266 | | - * Prefixes each file path in a list |
| 268 | + * Prefixes each file path in a list. |
267 | 269 | * |
268 | 270 | * @param {array} $list List of file paths in any combination of index/path or path/options pairs |
269 | 271 | * @param {string} $prefix String to prepend to each file path in $list |
— | — | @@ -283,7 +285,7 @@ |
284 | 286 | } |
285 | 287 | |
286 | 288 | /** |
287 | | - * Collates file paths by option (where provided) |
| 289 | + * Collates file paths by option (where provided). |
288 | 290 | * |
289 | 291 | * @param {array} $list List of file paths in any combination of index/path or path/options pairs |
290 | 292 | * @return {array} List of file paths, collated by $option |
— | — | @@ -310,7 +312,7 @@ |
311 | 313 | } |
312 | 314 | |
313 | 315 | /** |
314 | | - * Gets a list of element that match a key, optionally using a fallback key |
| 316 | + * Gets a list of element that match a key, optionally using a fallback key. |
315 | 317 | * |
316 | 318 | * @param {array} $map Map of lists to select from |
317 | 319 | * @param {string} $key Key to look for in $map |
— | — | @@ -327,7 +329,7 @@ |
328 | 330 | } |
329 | 331 | |
330 | 332 | /** |
331 | | - * Get the contents of a list of JavaScript files |
| 333 | + * Gets the contents of a list of JavaScript files. |
332 | 334 | * |
333 | 335 | * @param {array} $scripts List of file paths to scripts to read, remap and concetenate |
334 | 336 | * @return {string} Concatenated and remapped JavaScript data from $scripts |
— | — | @@ -340,7 +342,7 @@ |
341 | 343 | } |
342 | 344 | |
343 | 345 | /** |
344 | | - * Get the contents of a list of CSS files |
| 346 | + * Gets the contents of a list of CSS files. |
345 | 347 | * |
346 | 348 | * @param {array} $styles List of file paths to styles to read, remap and concetenate |
347 | 349 | * @return {array} List of concatenated and remapped CSS data from $styles, keyed by media type |
— | — | @@ -359,7 +361,7 @@ |
360 | 362 | } |
361 | 363 | |
362 | 364 | /** |
363 | | - * Reads a script file |
| 365 | + * Reads a script file. |
364 | 366 | * |
365 | 367 | * This method can be used as a callback for array_map() |
366 | 368 | * |
— | — | @@ -373,7 +375,7 @@ |
374 | 376 | } |
375 | 377 | |
376 | 378 | /** |
377 | | - * Reads a style file |
| 379 | + * Reads a style file. |
378 | 380 | * |
379 | 381 | * This method can be used as a callback for array_map() |
380 | 382 | * |
— | — | @@ -389,7 +391,7 @@ |
390 | 392 | } |
391 | 393 | |
392 | 394 | /** |
393 | | - * Resolve a file name |
| 395 | + * Resolves a file name. |
394 | 396 | * |
395 | 397 | * This method can be used as a callback for array_map() |
396 | 398 | * |