├── .gitignore ├── LICENSE-GPL ├── LICENSE-MIT ├── README.md ├── dist ├── eCSSential.js └── eCSSential.min.js ├── examples ├── concat │ ├── css │ │ ├── all.css │ │ ├── combined │ │ │ ├── all.min-20em.css │ │ │ ├── all.min-20em.min-37.5em.css │ │ │ ├── all.min-20em.min-37.5em.min-50em.css │ │ │ ├── all.min-20em.min-37.5em.min-50em.min-62.5em.css │ │ │ ├── min-20em.min-37.5em.min-50em.min-62.5em.css │ │ │ ├── min-37.5em.min-50em.min-62.5em.css │ │ │ ├── min-50em.min-62.5em.css │ │ │ └── min-62.5em.css │ │ ├── min-20em.css │ │ ├── min-37.5em.css │ │ ├── min-50em.css │ │ └── min-62.5em.css │ └── index.html ├── default │ ├── css │ │ ├── all.css │ │ ├── min-20em.css │ │ ├── min-37.5em.css │ │ ├── min-50em.css │ │ └── min-62.5em.css │ └── index.html ├── logdata.js ├── oldIE-separate-CSS │ ├── css │ │ ├── all.css │ │ ├── min-20em.css │ │ ├── min-20em.min-37.5em.min-50em.min-62.5em.css │ │ ├── min-37.5em.css │ │ ├── min-50em.css │ │ └── min-62.5em.css │ └── index.html └── oldIE │ ├── css │ ├── all.css │ ├── min-20em.css │ ├── min-37.5em.css │ ├── min-50em.css │ └── min-62.5em.css │ ├── index.html │ └── respond.min.js ├── grunt.js ├── libs ├── matchMedia │ └── matchMedia.js └── qunit │ ├── qunit.css │ └── qunit.js ├── package.json ├── src └── eCSSential.js └── test └── unit ├── eCSSential.html └── eCSSential_test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /LICENSE-GPL: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Scott Jehl 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # No Longer Maintained 2 | Thanks for checking out this project. We're keeping it here for historical purposes but otherwise, it's no longer maintained and we do not recommend using it now that standards for CSS loading have evolved. 3 | 4 | # eCSSential 5 | ## Making responsive CSS load the way it should. 6 | 7 | - [c]2012 @scottjehl, Filament Group, Inc. 8 | - Licenses: MIT, GPLv2 9 | 10 | ### The Problem 11 | 12 | Loading CSS in an optimized or prioritized fashion is very difficult. In order for a website to load cleanly, all CSS needed for rendering that page layout must be referenced in the `head` of a document. This is because stylesheets loaded in this way will block page rendering until they are loaded and ready to apply. If a stylesheet is referenced later in a document, or loaded dynamically via JS, users will often see a FOUC while that stylesheet loads concurrently with page rendering. 13 | 14 | Unfortunately, this limitation can make for a lot of overhead in responsive designs, particularly if a stylesheet contains a large amount of CSS for breakpoints that don't currently apply at a particular viewport size, or worse, CSS that won't ever apply on a particular device. More unfortunate, using separate `link` elements with `media` attributes to reference stylesheets with their intended breakpoints [doesn't prevent those stylesheets from downloading and blocking page rendering](http://scottjehl.github.com/CSS-Download-Tests/), even in environments where they don't currently or will never apply. 15 | 16 | Lastly, if for some reason a stylesheet takes a long time to load, most browsers will let it continue to block page rendering for 30 seconds or more! 17 | 18 | 19 | ### How eCSSential Helps 20 | 21 | eCSSential is a JavaScript utility that is designed to make browsers download files in a faster, more responsible manner than they do by default. Technically speaking, it is a tiny bit of JavaScript that when placed in the `head` of a page, determines which of your stylesheets should be loaded immediately and block page rendering (any stylesheets intended for mobile-first breakpoints that currently apply), which stylesheets should be deferred to load asynchronously (any stylesheets intended for breakpoints that don't currently apply to the current viewport size, but could apply later, given the device's screen size), and which stylesheets should never be loaded at all (any stylesheets intended for viewport dimensions that are larger than the device's screen). Once sorted, the essential (or eCSSential if you will) files are loaded in a way that ensures page rendering will be blocked until they're ready. The other less-essential files are loaded in a non-blocking way, letting the page render while they are fetched. 22 | 23 | In one further improvement to browsers' default loading behavior, stylesheets that are loaded in a blocking manner are given 8 seconds (by default) to load before they are refetched asynchonously, allowing the page to appear and be used. 24 | 25 | 26 | ## Check out the demos 27 | 28 | - [Default eCSSential Usage](http://scottjehl.github.com/eCSSential/examples/default) 29 | - [eCSSential with concatenated CSS files](http://scottjehl.github.com/eCSSential/examples/concat) 30 | - [eCSSential with IE-specific stylesheets](http://scottjehl.github.com/eCSSential/examples/oldIE-separate-CSS) 31 | - [eCSSential with a media query polyfill](http://scottjehl.github.com/eCSSential/examples/oldIE) 32 | 33 | ### Seeing the Results 34 | 35 | If you're in a desktop browser, you can pop open the demo page (try the [concatentated files demo](http://scottjehl.github.com/eCSSential/examples/concat) for the best performance) and check your developer console to see which stylesheets were loaded immediately and which were deferred; changing your viewport/window width and reloading the page will change where this loading split occurs. 36 | 37 | 38 | ## How To Use eCSSential 39 | 40 | Using eCSSential is as easy as including the full source of eCSSential.min.js (find that in the `/dist` folder) inline in the head of your page, and then calling `eCSSential()`, passing each of the paths to your CSS files paired with a media query describing where they are intended to apply. 41 | 42 | 43 | ... 44 | 55 | 56 | 57 | From this, eCSSential will be able to sort which are important up-front, and which can be deferred, or never loaded at all, and inject `link` elements to load the files accordingly. 58 | 59 | Because eCSSential requires JavaScript support to perform its optimizations, you might want to follow it with a `noscript` element containing references to any stylesheets you would prefer to load in non-JavaScript environments. If you don't do this, non-JavaScript users will simply receive an unstyled page, so it's up to you how you provide a fallback. 60 | 61 | 62 | ... 63 | 70 | 71 | 76 | 77 | 78 | That's it! With eCSSential in place, your pages will now render much faster on many devices (particularly small screens). 79 | 80 | 81 | ## Setting configuration options 82 | 83 | eCSSential comes with a number of defaults that can be overridden on a per-call basis. These options are configured by passing a second argument to the `eCSSential` function in the form of an object with one or more key/value pairs. 84 | 85 | eCSSential( { 86 | "all": "css/all.css", 87 | "(min-width: 20em)": "css/min-20em.css", 88 | [_...more files..._] 89 | }, 90 | // SET CONFIGURATION OPTIONS HERE 91 | { optionA: true, optionB: 500 } ); 92 | 93 | The following sections will reference configuration options that are defined in this manner. 94 | 95 | 96 | ## Optimizing Further with File Concatenation 97 | 98 | By default, eCSSential creates individual `link` elements for each stylesheet it requests, which, depending on the number of CSS files you have, can make for a lot of HTTP requests. Reducing HTTP requests is one of the best ways to improve the performance of a site, so eCSSential is designed to work with concatenated files if you instruct it to do so. 99 | 100 | Optionally, eCSSential can be configured to fetch all of your CSS via only 2 HTTP requests, of which the first request is immediate and blocking (synchronous), and the second is deferred and non-blocking (asynchronous). To use this feature, you'll need the help of a server-side concatenation tool, such as [QuickConcat](https://github.com/filamentgroup/quickconcat), or a build system that generates all static versions of your potential CSS file combinations. The `examples` directory contains a demo of this feature. You can also find it [here](http://scottjehl.github.com/eCSSential/examples/concat). The demo uses static generated combinations of the CSS files. 101 | 102 | To configure eCSSential to fetch concatenated files via a single request, you'll need to define a configuration property of `concat` as a function. The only rules for that `concat` function is that it should accept an array and return a string. For example, if your concatenated CSS files have filenames that are a long joined name of the files they contain, separated by periods with their directories and extensions removed, you might define a `concat` option like this: 103 | 104 | eCSSential({ 105 | "all": "css/all.css", 106 | "(min-width: 20em)": "css/min-20em.css", 107 | [_...more files..._] 108 | }, 109 | { 110 | concat: function( files ){ 111 | return "combined/" + arr.join("").replace( /css\/?/gmi, "" ) + "css"; 112 | } 113 | }); 114 | 115 | With that in place, a concatenated URL would end up somethign like this: `css/all.css.min-20em.css.min-37em.css`. 116 | 117 | To use with a dynamic concatenator like [QuickConcat](https://github.com/filamentgroup/quickconcat), your concat function might prepend comma-joined CSS files with a `quickconcat.php?files=` path... 118 | 119 | eCSSential({ 120 | "all": "all.css", 121 | "(min-width: 20em)": "min-20em.css", 122 | [_...more files..._] 123 | }, 124 | { 125 | concat: function( files ){ 126 | return "quickconcat.php?files=" + files.join(); 127 | } 128 | }); 129 | 130 | Because it's free-form, the `concat` option can work with any concatenation tool you'd like. Check out the `examples/concat/index.html` file for a working example of the `concat` option. 131 | 132 | 133 | ## Supporting Older Versions of IE 134 | 135 | Internet Explorer versions 6-8 have no CSS3 Media Query support. Because of this, you'll need to do a little extra work to get a mobile-first responsive design to render with a proper layout in these browsers. 136 | 137 | Generally, you can do that one of two ways: 138 | 139 | - create a separate stylesheet to conditionally deliver layout fixes to these browsers 140 | - use a media query polyfill like [Respond.js](https://github.com/scottjehl/Respond) to make the media queries work like they do in other browsers. 141 | 142 | Regardless of your preferred approach, eCSSential has you covered. 143 | 144 | ### Delivering IE-specific stylesheets 145 | 146 | To deliver stylesheets ONLY to specific versions of Internet Explorer, you can reference stylesheets using `IE6`, `IE7`, or `IE8` in place of a media query. You can use one or many of these too, depending on which versions you'd like a stylesheet to load. In the following example, the last stylesheet referenced load in IE 6-8: 147 | 148 | eCSSential({ 149 | "all": "all.css", 150 | "(min-width: 20em)": "css/min-20em.css", 151 | "(min-width: 37.5em)": "css/min-37.5em.css", 152 | "(min-width: 50em)": "css/min-50em.css", 153 | "(min-width: 62.5em)": "css/min-62.5em.css", 154 | "IE6 IE7 IE8": "css/iedesktopfixes.css" 155 | }); 156 | 157 | ### Delivering all stylesheets to IE with intent to polyfill media query support 158 | 159 | Alternatively, if you would like to load all of your stylesheets in IE 6-8 polyfill media query support to make them work, you can simply pass the configuration `oldIE` option, via the same mechanism used to define a `concat` option above. 160 | 161 | eCSSential({ 162 | "all": "all.css", 163 | "(min-width: 20em)": "css/min-20em.css", 164 | "(min-width: 37.5em)": "css/min-37.5em.css", 165 | "(min-width: 50em)": "css/min-50em.css", 166 | "(min-width: 62.5em)": "css/min-62.5em.css" 167 | }, { oldIE: true } ); 168 | 169 | When doing this, just be sure to add [Respond.js](https://github.com/scottjehl/Respond) or an equivalent workaround after the references to these CSS files. 170 | 171 | 172 | ## Changing the maximum time rendering will block 173 | 174 | By default, eCSSential will allow a blocking stylesheet to load for 8 seconds before refetching it and showing the page in whatever state it may be. If you'd like to change this timeout, just pass a different millisecond-based `patience` value in the configuration object, like so: 175 | 176 | eCSSential({ 177 | "all": "all.css", 178 | "(min-width: 20em)": "css/min-20em.css", 179 | "(min-width: 37.5em)": "css/min-37.5em.css", 180 | "(min-width: 50em)": "css/min-50em.css", 181 | "(min-width: 62.5em)": "css/min-62.5em.css" 182 | //set the max rendering timeout to 6 seconds instead of 8 183 | }, { patience: 6000 } ); 184 | 185 | 186 | ## Disabling the default deferred stylesheet qualifier 187 | 188 | By default, eCSSential will not load stylesheets that are targeted at dimensions not possible on a particular device (based on its screen size). This ammounts to better performance on small screens by reducing HTTP requests, but it does have the potential drawback that if the browser window is moved to a different sized screen, it may not have all of the styles it needs optimize for that new screen. This is somewhat of an edge case, but if you'd like to make sure every non-applicable CSS file is loaded asynchonously, you can pass the `deferAll` option as true. 189 | 190 | eCSSential({ 191 | "all": "all.css", 192 | "(min-width: 20em)": "css/min-20em.css", 193 | "(min-width: 37.5em)": "css/min-37.5em.css", 194 | "(min-width: 50em)": "css/min-50em.css", 195 | "(min-width: 62.5em)": "css/min-62.5em.css" 196 | }, { deferAll: true } ); 197 | 198 | 199 | ## Further notes 200 | 201 | - eCSSential includes the `window.matchMedia` polyfill so that it can run CSS3 media queries via JavaScript in browsers that don't support matchMedia. If you don't need to include the `window.matchMedia` polyfill (if say, perhaps it's already in your page), just remove the reference to it in the `grunt.js` file and generate a new build. 202 | 203 | 204 | ## FAQ 205 | 206 | ### Are There Drawbacks to using eCSSential? 207 | 208 | It will make your responsive websites load a lot faster on mobile devices. Oh wait, did you say drawbacks? 209 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /dist/eCSSential.js: -------------------------------------------------------------------------------- 1 | /*! eCSSential - v0.1.0 - 2012-06-08 2 | * https://github.com/scottjehl/eCSSential 3 | * Copyright (c) 2012 Scott Jehl, @scottjehl, Filament Group, Inc.; Licensed GPL, MIT; Includes matchMedia.js: http://j.mp/jay3wJ (MIT) */ 4 | 5 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ 6 | 7 | window.matchMedia = window.matchMedia || (function(doc, undefined){ 8 | 9 | var bool, 10 | docElem = doc.documentElement, 11 | refNode = docElem.firstElementChild || docElem.firstChild, 12 | // fakeBody required for 13 | fakeBody = doc.createElement('body'), 14 | div = doc.createElement('div'); 15 | 16 | div.id = 'mq-test-1'; 17 | div.style.cssText = "position:absolute;top:-100em"; 18 | fakeBody.style.background = "none"; 19 | fakeBody.appendChild(div); 20 | 21 | return function(q){ 22 | 23 | div.innerHTML = '­'; 24 | 25 | docElem.insertBefore(fakeBody, refNode); 26 | bool = div.offsetWidth === 42; 27 | docElem.removeChild(fakeBody); 28 | 29 | return { matches: bool, media: q }; 30 | }; 31 | 32 | }(document)); 33 | /*! eCSSential 34 | * https://github.com/scottjehl/eCSSential 35 | * Copyright (c) 2012 Scott Jehl, @scottjehl, Filament Group, Inc.; Licensed GPL, MIT; Includes matchMedia.js: http://j.mp/jay3wJ (MIT) */ 36 | 37 | window.eCSSential = function( css, config ){ 38 | "use strict"; 39 | var load = [], 40 | defer = [], 41 | timedout = [], 42 | // All options false or null by default; no need for a mixin 43 | o = config || {}, 44 | w = window, 45 | d = w.document, 46 | insLoc = d.getElementsByTagName( "script" )[0], 47 | whre = /(min|max)-(width|height)/gmi, 48 | ieV = w.navigator.appVersion.match( /MSIE ([678])\./ ) && RegExp.$1, 49 | ieRe = new RegExp( "(IE" + ieV + ")|(IE)", "g" ); 50 | 51 | for( var mq in css ){ 52 | if( css.hasOwnProperty( mq ) ){ 53 | // if media query evaluates true, 54 | // or if the browser is IE 6-8 and the key is a IEx match, or the o.oldIE option is true, 55 | // queue the stylesheet for a renderer-blocking load 56 | var iekey = mq.match( ieRe ); 57 | 58 | if( w.matchMedia( mq ).matches || ( ieV && ( o.oldIE || iekey && iekey[ 1 ] ) ) ){ 59 | load.push( { 60 | //keep the media attribute, but leave it as "all" if it was an "IEx" key 61 | mq: o.oldIE || iekey ? "all" : mq, 62 | href: css[ mq ] 63 | } ); 64 | } 65 | // otherwise, queue for deferred load some stylesheets that didn't evaluate true the first time 66 | // Note: this means many stylesheets intended for conditions that could never apply (such as a width wider than the maximum device width) will be loaded anyway, causing more HTTP requests. 67 | // min/max-width/height queries are by default evaluated to see if they could never apply on the current screen 68 | // by running them as a "device" query instead of a viewport query. 69 | // You can disable this behavior and defer every stylesheet by setting the deferAll configuration option 70 | else if( !iekey && ( o.deferAll || !mq.match( whre ) || w.matchMedia( mq.replace( whre, "$1-device-$2" ) ).matches ) ){ 71 | defer.push( { mq: mq, href: css[ mq ] } ); 72 | } 73 | } 74 | } 75 | 76 | // Make link elements (or one concat'd link) from an array of Stylesheets 77 | // first argument is array of urls, second argument is bool for inserting meta element marker (only used in block) 78 | function makeLinks( arr ){ 79 | var marker = arr === load ? '' : '', 80 | start = '', 82 | hrefs = [], 83 | hrefmqs = []; 84 | 85 | for( var i in arr ){ 86 | if( arr.hasOwnProperty( i ) ){ 87 | hrefs.push( arr[ i ].href ); 88 | hrefmqs.push( arr[ i ].href + '" media="' + arr[ i ].mq ); 89 | } 90 | } 91 | 92 | // if the concat option is specified (recommended), pass the array through it and dump the resulting string into a single stylesheet url 93 | if( o.concat ){ 94 | return start + o.concat( hrefs ) + end + marker; 95 | } 96 | // otherwise, make separate link elements 97 | else { 98 | return start + hrefmqs.join( '" ' + end + start ) + end + marker; 99 | } 100 | } 101 | 102 | // document.write the stylesheets that should block 103 | if( load.length ){ 104 | d.write( makeLinks( load ) ); 105 | insLoc = d.getElementById( "eCSS" ); 106 | 107 | // set up timeout to stop a stylesheet from blocking after 8 seconds 108 | // or by however many ms are passed via o.patience 109 | var links = insLoc.parentNode.getElementsByTagName( "link" ); 110 | for(var i = 0, il = links.length; i< il; i++ ){ 111 | (function( c ){ 112 | var t = w.setTimeout(function(){ 113 | var next = c.nextSibling; 114 | c.parentNode.removeChild( c ); 115 | next.parentNode.insertBefore( c, next ); 116 | timedout.push( c ); 117 | }, o.patience || 8000 ); 118 | c.onload = function(){ 119 | clearTimeout( t ); 120 | }; 121 | }( links[ i ] )); 122 | } 123 | } 124 | 125 | // defer the load of the stylesheet that could later apply 126 | if( defer.length ){ 127 | var div = d.createElement( "div" ); 128 | div.innerHTML = makeLinks( defer ); 129 | insLoc.parentNode.insertBefore( div, insLoc ); 130 | } 131 | // return data for testing 132 | return { css: css, config: config, block: load, defer: defer, timedout: timedout }; 133 | }; 134 | -------------------------------------------------------------------------------- /dist/eCSSential.min.js: -------------------------------------------------------------------------------- 1 | /*! eCSSential - v0.1.0 - 2012-06-08 2 | * https://github.com/scottjehl/eCSSential 3 | * Copyright (c) 2012 Scott Jehl, @scottjehl, Filament Group, Inc.; Licensed GPL, MIT; Includes matchMedia.js: http://j.mp/jay3wJ (MIT) */ 4 | window.matchMedia=window.matchMedia||function(a,b){var c,d=a.documentElement,e=d.firstElementChild||d.firstChild,f=a.createElement("body"),g=a.createElement("div");return g.id="mq-test-1",g.style.cssText="position:absolute;top:-100em",f.style.background="none",f.appendChild(g),function(a){return g.innerHTML='­',d.insertBefore(f,e),c=g.offsetWidth===42,d.removeChild(f),{matches:c,media:a}}}(document),window.eCSSential=function(a,b){function o(a){var b=a===c?'':"",d='',g=[],h=[];for(var i in a)a.hasOwnProperty(i)&&(g.push(a[i].href),h.push(a[i].href+'" media="'+a[i].mq));return f.concat?d+f.concat(g)+e+b:d+h.join('" '+e+d)+e+b}"use strict";var c=[],d=[],e=[],f=b||{},g=window,h=g.document,i=h.getElementsByTagName("script")[0],j=/(min|max)-(width|height)/gmi,k=g.navigator.appVersion.match(/MSIE ([678])\./)&&RegExp.$1,l=new RegExp("(IE"+k+")|(IE)","g");for(var m in a)if(a.hasOwnProperty(m)){var n=m.match(l);g.matchMedia(m).matches||k&&(f.oldIE||n&&n[1])?c.push({mq:f.oldIE||n?"all":m,href:a[m]}):!n&&(f.deferAll||!m.match(j)||g.matchMedia(m.replace(j,"$1-device-$2")).matches)&&d.push({mq:m,href:a[m]})}if(c.length){h.write(o(c)),i=h.getElementById("eCSS");var p=i.parentNode.getElementsByTagName("link");for(var q=0,r=p.length;q 2 | 3 | 4 | 5 | 6 | eCSSential | Test Template 7 | 8 | 23 | 24 | 25 | 26 | 27 | 28 |

eCSSential

29 |

An experiment in optimized loading of mobile-first responsive CSS.

30 |

[c]2012 @scottjehl, Filament Group, Inc. MIT/GPLv2

31 | 32 |

Page load data

33 |
34 | JavaScript wasn't available so the fallback stylesheet loaded. 35 |
36 | 37 | 38 | 39 | 40 |

CSS output

41 |

In browsers that support CSS pseudo elements (like :after{}), each loaded stylesheet will add a line of text below to announce its presence and whether its intended breakpoint is currently active. Note that this script is merely concerned with improving stylesheet loading performance, but how and where those loaded stylesheets apply is still controlled by CSS media queries.

42 | 43 | 44 |

45 |

46 |

47 |

48 |

49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /examples/default/css/all.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | margin: 50px 5%; 4 | line-height: 1.2; 5 | } 6 | em { 7 | font-size: .8em; 8 | font-style: normal; 9 | color: #666; 10 | } 11 | .explain, #data { 12 | padding-bottom: 2em; 13 | font-size: .9em; 14 | } 15 | #data { 16 | background: #f3f3f3; 17 | padding: 2em; 18 | } 19 | #data p,#data dt { 20 | font-weight: bold; 21 | } 22 | #data dl { 23 | overflow: hidden; 24 | margin: 0; 25 | } 26 | #data dt { 27 | display:block; 28 | margin: 0; 29 | padding: 0 .5em .5em 2em; 30 | } 31 | #data dt:after { 32 | content: ":"; 33 | } 34 | #data dd { 35 | margin: 0; 36 | display:block; 37 | padding: 0 .5em 1em 2em; 38 | } 39 | 40 | .a:after { 41 | display:block; 42 | content: "all.css was loaded and is currently applied."; 43 | margin-top: 1em; 44 | } -------------------------------------------------------------------------------- /examples/default/css/min-20em.css: -------------------------------------------------------------------------------- 1 | .b:after { 2 | display:block; 3 | content: "min-20em.css was loaded."; 4 | margin-top: 1em; 5 | } 6 | 7 | @media (min-width: 20em){ 8 | .b:after { 9 | content: "min-20em.css was loaded and is currently applied."; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/default/css/min-37.5em.css: -------------------------------------------------------------------------------- 1 | .c:after { 2 | display:block; 3 | content: "min-37.5em.css was loaded."; 4 | margin-top: 1em; 5 | } 6 | 7 | @media (min-width: 37.5em){ 8 | .c:after { 9 | content: "min-37.5em.css was loaded and is currently applied."; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/default/css/min-50em.css: -------------------------------------------------------------------------------- 1 | .d:after { 2 | display:block; 3 | content: "min-50em.css was loaded."; 4 | margin-top: 1em; 5 | } 6 | 7 | @media (min-width: 50em){ 8 | .d:after { 9 | content: "min-50em.css was loaded and is currently applied."; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/default/css/min-62.5em.css: -------------------------------------------------------------------------------- 1 | .e:after { 2 | display:block; 3 | content: "min-62.5em.css was loaded."; 4 | margin-top: 1em; 5 | } 6 | 7 | @media (min-width: 62.5em){ 8 | .e:after { 9 | content: "min-62.5em.css was loaded and is currently applied."; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/default/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | eCSSential | Test Template 7 | 8 | 9 | 19 | 20 | 24 | 25 | 26 | 27 |

eCSSential

28 |

An experiment in optimized loading of mobile-first responsive CSS.

29 |

[c]2012 @scottjehl, Filament Group, Inc. MIT/GPLv2

30 | 31 | 32 |

Page load data

33 |
34 | JavaScript wasn't available so the fallback stylesheet loaded. 35 |
36 | 37 | 38 | 39 | 40 |

CSS output

41 |

In browsers that support CSS pseudo elements (like :after{}), each loaded stylesheet will add a line of text below to announce its presence and whether its intended breakpoint is currently active. Note that this script is merely concerned with improving stylesheet loading performance, but how and where those loaded stylesheets apply is still controlled by CSS media queries.

42 | 43 | 44 |

45 |

46 |

47 |

48 |

49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /examples/logdata.js: -------------------------------------------------------------------------------- 1 | /* this file is for logging data in the example pages. Nothing fancy here; look away. */ 2 | (function(){ 3 | var data = document.getElementById( "data" ); 4 | if( data && loadCSS ){ 5 | var info = [], 6 | loaded = []; 7 | 8 | // available css info 9 | info.push( "

Available StyleSheets

"); 21 | 22 | // blocking css info 23 | info.push( "

StyleSheets that loaded immediately (these blocked page rendering)

"); 34 | 35 | // deferred css info 36 | info.push( "

StyleSheets that loaded lazily (these did not block page rendering)

"); 47 | 48 | // no load css info 49 | info.push( "

StyleSheets that were excluded from loading at all (often only applies to small screens)

"); 71 | 72 | // General Stats 73 | info.push( "

General information

" ); 74 | info.push("
"); 75 | 76 | var numlinks = document.getElementsByTagName( "link" ).length; 77 | 78 | info.push("
Total number of CSS requests
"+ numlinks +"
"); 79 | info.push("
Total number of blocking CSS requests
"+ (loadCSS.config && loadCSS.config.concat && loadCSS.block.length ? 1 : loadCSS.block.length ) +"
"); 80 | info.push("
Total number of non-blocking CSS requests
"+ (loadCSS.config && loadCSS.config.concat && loadCSS.defer.length ? 1 : loadCSS.defer.length) +"
"); 81 | info.push("
CSS files that timed out and were refetched
"+ (loadCSS.length ? loadCSS.timedout.join(", ") : "0" ) +"
"); 82 | info.push("
Initial viewport dimensions
Width: "+ window.innerWidth +", Height: "+ window.innerHeight +"
"); 83 | info.push("
Initial screen dimensions
Width: "+ screen.width +", Height: "+ screen.height +"
"); 84 | info.push("
"); 85 | 86 | info.push( "

NOTE: Try resizing your browser and refreshing to see what changes.

" ); 87 | 88 | // write! 89 | data.innerHTML = info.join(""); 90 | } 91 | }()); -------------------------------------------------------------------------------- /examples/oldIE-separate-CSS/css/all.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | margin: 50px 5%; 4 | line-height: 1.2; 5 | } 6 | em { 7 | font-size: .8em; 8 | font-style: normal; 9 | color: #666; 10 | } 11 | .explain, #data { 12 | padding-bottom: 2em; 13 | font-size: .9em; 14 | } 15 | #data { 16 | background: #f3f3f3; 17 | padding: 2em; 18 | } 19 | #data p,#data dt { 20 | font-weight: bold; 21 | } 22 | #data dl { 23 | overflow: hidden; 24 | margin: 0; 25 | } 26 | #data dt { 27 | display:block; 28 | margin: 0; 29 | padding: 0 .5em .5em 2em; 30 | } 31 | #data dt:after { 32 | content: ":"; 33 | } 34 | #data dd { 35 | margin: 0; 36 | display:block; 37 | padding: 0 .5em 1em 2em; 38 | } 39 | 40 | .a:after { 41 | display:block; 42 | content: "all.css was loaded and is currently applied."; 43 | margin-top: 1em; 44 | } -------------------------------------------------------------------------------- /examples/oldIE-separate-CSS/css/min-20em.css: -------------------------------------------------------------------------------- 1 | .b:after { 2 | display:block; 3 | content: "min-20em.css was loaded."; 4 | margin-top: 1em; 5 | } 6 | 7 | @media (min-width: 20em){ 8 | .b:after { 9 | content: "min-20em.css was loaded and is currently applied."; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/oldIE-separate-CSS/css/min-20em.min-37.5em.min-50em.min-62.5em.css: -------------------------------------------------------------------------------- 1 | .b:after { 2 | display:block; 3 | content: "min-20em.css was loaded."; 4 | margin-top: 1em; 5 | } 6 | 7 | @media (min-width: 20em){ 8 | .b:after { 9 | content: "min-20em.css was loaded and is currently applied."; 10 | } 11 | } 12 | .c:after { 13 | display:block; 14 | content: "min-37.5em.css was loaded."; 15 | margin-top: 1em; 16 | } 17 | 18 | @media (min-width: 37.5em){ 19 | .c:after { 20 | content: "min-37.5em.css was loaded and is currently applied."; 21 | } 22 | } 23 | 24 | .d:after { 25 | display:block; 26 | content: "min-50em.css was loaded."; 27 | margin-top: 1em; 28 | } 29 | 30 | @media (min-width: 50em){ 31 | .d:after { 32 | content: "min-50em.css was loaded and is currently applied."; 33 | } 34 | } 35 | .e:after { 36 | display:block; 37 | content: "min-62.5em.css was loaded."; 38 | margin-top: 1em; 39 | } 40 | 41 | @media (min-width: 62.5em){ 42 | .e:after { 43 | content: "min-62.5em.css was loaded and is currently applied."; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/oldIE-separate-CSS/css/min-37.5em.css: -------------------------------------------------------------------------------- 1 | .c:after { 2 | display:block; 3 | content: "min-37.5em.css was loaded."; 4 | margin-top: 1em; 5 | } 6 | 7 | @media (min-width: 37.5em){ 8 | .c:after { 9 | content: "min-37.5em.css was loaded and is currently applied."; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/oldIE-separate-CSS/css/min-50em.css: -------------------------------------------------------------------------------- 1 | .d:after { 2 | display:block; 3 | content: "min-50em.css was loaded."; 4 | margin-top: 1em; 5 | } 6 | 7 | @media (min-width: 50em){ 8 | .d:after { 9 | content: "min-50em.css was loaded and is currently applied."; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/oldIE-separate-CSS/css/min-62.5em.css: -------------------------------------------------------------------------------- 1 | .e:after { 2 | display:block; 3 | content: "min-62.5em.css was loaded."; 4 | margin-top: 1em; 5 | } 6 | 7 | @media (min-width: 62.5em){ 8 | .e:after { 9 | content: "min-62.5em.css was loaded and is currently applied."; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/oldIE-separate-CSS/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | eCSSential | Test Template 7 | 8 | 9 | 21 | 22 | 26 | 27 | 28 | 29 | 30 |

eCSSential

31 |

An experiment in optimized loading of mobile-first responsive CSS.

32 |

[c]2012 @scottjehl, Filament Group, Inc. MIT/GPLv2

33 | 34 |

Page load data

35 |
36 | JavaScript wasn't available so the fallback stylesheet loaded. 37 |
38 | 39 | 40 | 41 | 42 |

CSS output

43 |

In browsers that support CSS pseudo elements (like :after{}), each loaded stylesheet will add a line of text below to announce its presence and whether its intended breakpoint is currently active. Note that this script is merely concerned with improving stylesheet loading performance, but how and where those loaded stylesheets apply is still controlled by CSS media queries.

44 | 45 | 46 |

47 |

48 |

49 |

50 |

51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /examples/oldIE/css/all.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | margin: 50px 5%; 4 | line-height: 1.2; 5 | } 6 | em { 7 | font-size: .8em; 8 | font-style: normal; 9 | color: #666; 10 | } 11 | .explain, #data { 12 | padding-bottom: 2em; 13 | font-size: .9em; 14 | } 15 | #data { 16 | background: #f3f3f3; 17 | padding: 2em; 18 | } 19 | #data p,#data dt { 20 | font-weight: bold; 21 | } 22 | #data dl { 23 | overflow: hidden; 24 | margin: 0; 25 | } 26 | #data dt { 27 | display:block; 28 | margin: 0; 29 | padding: 0 .5em .5em 2em; 30 | } 31 | #data dt:after { 32 | content: ":"; 33 | } 34 | #data dd { 35 | margin: 0; 36 | display:block; 37 | padding: 0 .5em 1em 2em; 38 | } 39 | 40 | .a:after { 41 | display:block; 42 | content: "all.css was loaded and is currently applied."; 43 | margin-top: 1em; 44 | } -------------------------------------------------------------------------------- /examples/oldIE/css/min-20em.css: -------------------------------------------------------------------------------- 1 | .b:after { 2 | display:block; 3 | content: "min-20em.css was loaded."; 4 | margin-top: 1em; 5 | } 6 | 7 | @media (min-width: 20em){ 8 | .b:after { 9 | content: "min-20em.css was loaded and is currently applied."; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/oldIE/css/min-37.5em.css: -------------------------------------------------------------------------------- 1 | .c:after { 2 | display:block; 3 | content: "min-37.5em.css was loaded."; 4 | margin-top: 1em; 5 | } 6 | 7 | @media (min-width: 37.5em){ 8 | .c:after { 9 | content: "min-37.5em.css was loaded and is currently applied."; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/oldIE/css/min-50em.css: -------------------------------------------------------------------------------- 1 | .d:after { 2 | display:block; 3 | content: "min-50em.css was loaded."; 4 | margin-top: 1em; 5 | } 6 | 7 | @media (min-width: 50em){ 8 | .d:after { 9 | content: "min-50em.css was loaded and is currently applied."; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/oldIE/css/min-62.5em.css: -------------------------------------------------------------------------------- 1 | .e:after { 2 | display:block; 3 | content: "min-62.5em.css was loaded."; 4 | margin-top: 1em; 5 | } 6 | 7 | @media (min-width: 62.5em){ 8 | .e:after { 9 | content: "min-62.5em.css was loaded and is currently applied."; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/oldIE/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | eCSSential | Test Template 7 | 8 | 9 | 25 | 26 | 30 | 31 | 32 | 33 | 34 |

eCSSential

35 |

An experiment in optimized loading of mobile-first responsive CSS.

36 |

[c]2012 @scottjehl, Filament Group, Inc. MIT/GPLv2

37 | 38 |

Page load data

39 |
40 | JavaScript wasn't available so the fallback stylesheet loaded. 41 |
42 | 43 | 44 | 45 | 46 |

CSS output

47 |

In browsers that support CSS pseudo elements (like :after{}), each loaded stylesheet will add a line of text below to announce its presence and whether its intended breakpoint is currently active. Note that this script is merely concerned with improving stylesheet loading performance, but how and where those loaded stylesheets apply is still controlled by CSS media queries.

48 | 49 | 50 |

51 |

52 |

53 |

54 |

55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /examples/oldIE/respond.min.js: -------------------------------------------------------------------------------- 1 | /*! Respond.js v1.1.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */ 2 | /*! Note: no matchMedia polyfill included, as its already inc'd in eCSSential.js */ 3 | (function(e){e.respond={};respond.update=function(){};respond.mediaQueriesSupported=e.matchMedia&&e.matchMedia("only all").matches;if(respond.mediaQueriesSupported){return}var w=e.document,s=w.documentElement,i=[],k=[],q=[],o={},h=30,f=w.getElementsByTagName("head")[0]||s,g=w.getElementsByTagName("base")[0],b=f.getElementsByTagName("link"),d=[],a=function(){var D=b,y=D.length,B=0,A,z,C,x;for(;B-1,minw:F.match(/\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:F.match(/\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}}j()},l,r,v=function(){var z,A=w.createElement("div"),x=w.body,y=false;A.style.cssText="position:absolute;font-size:1em;width:1em";if(!x){x=y=w.createElement("body");x.style.background="none"}x.appendChild(A);s.insertBefore(x,s.firstChild);z=A.offsetWidth;if(y){s.removeChild(x)}else{x.removeChild(A)}z=p=parseFloat(z);return z},p,j=function(I){var x="clientWidth",B=s[x],H=w.compatMode==="CSS1Compat"&&B||w.body[x]||B,D={},G=b[b.length-1],z=(new Date()).getTime();if(I&&l&&z-l-1?(p||v()):1)}if(!!J){J=parseFloat(J)*(J.indexOf(y)>-1?(p||v()):1)}if(!K.hasquery||(!A||!L)&&(A||H>=C)&&(L||H<=J)){if(!D[K.media]){D[K.media]=[]}D[K.media].push(k[K.rules])}}for(var E in q){if(q[E]&&q[E].parentNode===f){f.removeChild(q[E])}}for(var E in D){var M=w.createElement("style"),F=D[E].join("\n");M.type="text/css";M.media=E;f.insertBefore(M,G.nextSibling);if(M.styleSheet){M.styleSheet.cssText=F}else{M.appendChild(w.createTextNode(F))}q.push(M)}},n=function(x,z){var y=c();if(!y){return}y.open("GET",x,true);y.onreadystatechange=function(){if(y.readyState!=4||y.status!=200&&y.status!=304){return}z(y.responseText)};if(y.readyState==4){return}y.send(null)},c=(function(){var x=false;try{x=new XMLHttpRequest()}catch(y){x=new ActiveXObject("Microsoft.XMLHTTP")}return function(){return x}})();a();respond.update=a;function t(){j(true)}if(e.addEventListener){e.addEventListener("resize",t,false)}else{if(e.attachEvent){e.attachEvent("onresize",t)}}})(this); -------------------------------------------------------------------------------- /grunt.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | module.exports = function(grunt) { 3 | 4 | // Project configuration. 5 | grunt.initConfig({ 6 | pkg: '', 7 | meta: { 8 | banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 9 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 10 | '<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' + 11 | '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + 12 | ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>;' + 13 | ' Includes matchMedia.js: http://j.mp/jay3wJ (MIT) */' 14 | }, 15 | concat: { 16 | dist: { 17 | src: ['', '', '.js>'], 18 | dest: 'dist/<%= pkg.name %>.js' 19 | } 20 | }, 21 | min: { 22 | dist: { 23 | src: ['', ''], 24 | dest: 'dist/<%= pkg.name %>.min.js' 25 | } 26 | }, 27 | lint: { 28 | files: ['grunt.js', 'libs/matchMedia/*.js', 'src/**/*.js'] 29 | }, 30 | qunit: { 31 | files: ['test/unit/*.html'] 32 | }, 33 | watch: { 34 | files: ['', ''], 35 | tasks: 'lint qunit concat min' 36 | }, 37 | jshint: { 38 | options: { 39 | curly: true, 40 | eqeqeq: true, 41 | immed: true, 42 | latedef: true, 43 | newcap: true, 44 | noarg: true, 45 | sub: true, 46 | undef: true, 47 | boss: true, 48 | eqnull: true, 49 | browser: true, 50 | loopfunc: true 51 | }, 52 | globals: { 53 | jQuery: true 54 | } 55 | }, 56 | uglify: {} 57 | }); 58 | 59 | // Default task. 60 | grunt.registerTask('default', 'lint qunit concat min'); 61 | 62 | }; 63 | -------------------------------------------------------------------------------- /libs/matchMedia/matchMedia.js: -------------------------------------------------------------------------------- 1 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ 2 | 3 | window.matchMedia = window.matchMedia || (function(doc, undefined){ 4 | 5 | var bool, 6 | docElem = doc.documentElement, 7 | refNode = docElem.firstElementChild || docElem.firstChild, 8 | // fakeBody required for 9 | fakeBody = doc.createElement('body'), 10 | div = doc.createElement('div'); 11 | 12 | div.id = 'mq-test-1'; 13 | div.style.cssText = "position:absolute;top:-100em"; 14 | fakeBody.style.background = "none"; 15 | fakeBody.appendChild(div); 16 | 17 | return function(q){ 18 | 19 | div.innerHTML = '­'; 20 | 21 | docElem.insertBefore(fakeBody, refNode); 22 | bool = div.offsetWidth === 42; 23 | docElem.removeChild(fakeBody); 24 | 25 | return { matches: bool, media: q }; 26 | }; 27 | 28 | }(document)); -------------------------------------------------------------------------------- /libs/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.4.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2012 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 15px 15px 0 0; 42 | -moz-border-radius: 15px 15px 0 0; 43 | -webkit-border-top-right-radius: 15px; 44 | -webkit-border-top-left-radius: 15px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-header label { 58 | display: inline-block; 59 | } 60 | 61 | #qunit-banner { 62 | height: 5px; 63 | } 64 | 65 | #qunit-testrunner-toolbar { 66 | padding: 0.5em 0 0.5em 2em; 67 | color: #5E740B; 68 | background-color: #eee; 69 | } 70 | 71 | #qunit-userAgent { 72 | padding: 0.5em 0 0.5em 2.5em; 73 | background-color: #2b81af; 74 | color: #fff; 75 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 76 | } 77 | 78 | 79 | /** Tests: Pass/Fail */ 80 | 81 | #qunit-tests { 82 | list-style-position: inside; 83 | } 84 | 85 | #qunit-tests li { 86 | padding: 0.4em 0.5em 0.4em 2.5em; 87 | border-bottom: 1px solid #fff; 88 | list-style-position: inside; 89 | } 90 | 91 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 92 | display: none; 93 | } 94 | 95 | #qunit-tests li strong { 96 | cursor: pointer; 97 | } 98 | 99 | #qunit-tests li a { 100 | padding: 0.5em; 101 | color: #c2ccd1; 102 | text-decoration: none; 103 | } 104 | #qunit-tests li a:hover, 105 | #qunit-tests li a:focus { 106 | color: #000; 107 | } 108 | 109 | #qunit-tests ol { 110 | margin-top: 0.5em; 111 | padding: 0.5em; 112 | 113 | background-color: #fff; 114 | 115 | border-radius: 15px; 116 | -moz-border-radius: 15px; 117 | -webkit-border-radius: 15px; 118 | 119 | box-shadow: inset 0px 2px 13px #999; 120 | -moz-box-shadow: inset 0px 2px 13px #999; 121 | -webkit-box-shadow: inset 0px 2px 13px #999; 122 | } 123 | 124 | #qunit-tests table { 125 | border-collapse: collapse; 126 | margin-top: .2em; 127 | } 128 | 129 | #qunit-tests th { 130 | text-align: right; 131 | vertical-align: top; 132 | padding: 0 .5em 0 0; 133 | } 134 | 135 | #qunit-tests td { 136 | vertical-align: top; 137 | } 138 | 139 | #qunit-tests pre { 140 | margin: 0; 141 | white-space: pre-wrap; 142 | word-wrap: break-word; 143 | } 144 | 145 | #qunit-tests del { 146 | background-color: #e0f2be; 147 | color: #374e0c; 148 | text-decoration: none; 149 | } 150 | 151 | #qunit-tests ins { 152 | background-color: #ffcaca; 153 | color: #500; 154 | text-decoration: none; 155 | } 156 | 157 | /*** Test Counts */ 158 | 159 | #qunit-tests b.counts { color: black; } 160 | #qunit-tests b.passed { color: #5E740B; } 161 | #qunit-tests b.failed { color: #710909; } 162 | 163 | #qunit-tests li li { 164 | margin: 0.5em; 165 | padding: 0.4em 0.5em 0.4em 0.5em; 166 | background-color: #fff; 167 | border-bottom: none; 168 | list-style-position: inside; 169 | } 170 | 171 | /*** Passing Styles */ 172 | 173 | #qunit-tests li li.pass { 174 | color: #5E740B; 175 | background-color: #fff; 176 | border-left: 26px solid #C6E746; 177 | } 178 | 179 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 180 | #qunit-tests .pass .test-name { color: #366097; } 181 | 182 | #qunit-tests .pass .test-actual, 183 | #qunit-tests .pass .test-expected { color: #999999; } 184 | 185 | #qunit-banner.qunit-pass { background-color: #C6E746; } 186 | 187 | /*** Failing Styles */ 188 | 189 | #qunit-tests li li.fail { 190 | color: #710909; 191 | background-color: #fff; 192 | border-left: 26px solid #EE5757; 193 | white-space: pre; 194 | } 195 | 196 | #qunit-tests > li:last-child { 197 | border-radius: 0 0 15px 15px; 198 | -moz-border-radius: 0 0 15px 15px; 199 | -webkit-border-bottom-right-radius: 15px; 200 | -webkit-border-bottom-left-radius: 15px; 201 | } 202 | 203 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 204 | #qunit-tests .fail .test-name, 205 | #qunit-tests .fail .module-name { color: #000000; } 206 | 207 | #qunit-tests .fail .test-actual { color: #EE5757; } 208 | #qunit-tests .fail .test-expected { color: green; } 209 | 210 | #qunit-banner.qunit-fail { background-color: #EE5757; } 211 | 212 | 213 | /** Result */ 214 | 215 | #qunit-testresult { 216 | padding: 0.5em 0.5em 0.5em 2.5em; 217 | 218 | color: #2b81af; 219 | background-color: #D2E0E6; 220 | 221 | border-bottom: 1px solid white; 222 | } 223 | 224 | /** Fixture */ 225 | 226 | #qunit-fixture { 227 | position: absolute; 228 | top: -10000px; 229 | left: -10000px; 230 | width: 1000px; 231 | height: 1000px; 232 | } 233 | -------------------------------------------------------------------------------- /libs/qunit/qunit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.4.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2012 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | (function(window) { 12 | 13 | var defined = { 14 | setTimeout: typeof window.setTimeout !== "undefined", 15 | sessionStorage: (function() { 16 | var x = "qunit-test-string"; 17 | try { 18 | sessionStorage.setItem(x, x); 19 | sessionStorage.removeItem(x); 20 | return true; 21 | } catch(e) { 22 | return false; 23 | } 24 | }()) 25 | }; 26 | 27 | var testId = 0, 28 | toString = Object.prototype.toString, 29 | hasOwn = Object.prototype.hasOwnProperty; 30 | 31 | var Test = function(name, testName, expected, async, callback) { 32 | this.name = name; 33 | this.testName = testName; 34 | this.expected = expected; 35 | this.async = async; 36 | this.callback = callback; 37 | this.assertions = []; 38 | }; 39 | Test.prototype = { 40 | init: function() { 41 | var tests = id("qunit-tests"); 42 | if (tests) { 43 | var b = document.createElement("strong"); 44 | b.innerHTML = "Running " + this.name; 45 | var li = document.createElement("li"); 46 | li.appendChild( b ); 47 | li.className = "running"; 48 | li.id = this.id = "test-output" + testId++; 49 | tests.appendChild( li ); 50 | } 51 | }, 52 | setup: function() { 53 | if (this.module != config.previousModule) { 54 | if ( config.previousModule ) { 55 | runLoggingCallbacks('moduleDone', QUnit, { 56 | name: config.previousModule, 57 | failed: config.moduleStats.bad, 58 | passed: config.moduleStats.all - config.moduleStats.bad, 59 | total: config.moduleStats.all 60 | } ); 61 | } 62 | config.previousModule = this.module; 63 | config.moduleStats = { all: 0, bad: 0 }; 64 | runLoggingCallbacks( 'moduleStart', QUnit, { 65 | name: this.module 66 | } ); 67 | } else if (config.autorun) { 68 | runLoggingCallbacks( 'moduleStart', QUnit, { 69 | name: this.module 70 | } ); 71 | } 72 | 73 | config.current = this; 74 | this.testEnvironment = extend({ 75 | setup: function() {}, 76 | teardown: function() {} 77 | }, this.moduleTestEnvironment); 78 | 79 | runLoggingCallbacks( 'testStart', QUnit, { 80 | name: this.testName, 81 | module: this.module 82 | }); 83 | 84 | // allow utility functions to access the current test environment 85 | // TODO why?? 86 | QUnit.current_testEnvironment = this.testEnvironment; 87 | 88 | if ( !config.pollution ) { 89 | saveGlobal(); 90 | } 91 | if ( config.notrycatch ) { 92 | this.testEnvironment.setup.call(this.testEnvironment); 93 | return; 94 | } 95 | try { 96 | this.testEnvironment.setup.call(this.testEnvironment); 97 | } catch(e) { 98 | QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); 99 | } 100 | }, 101 | run: function() { 102 | config.current = this; 103 | if ( this.async ) { 104 | QUnit.stop(); 105 | } 106 | 107 | if ( config.notrycatch ) { 108 | this.callback.call(this.testEnvironment); 109 | return; 110 | } 111 | try { 112 | this.callback.call(this.testEnvironment); 113 | } catch(e) { 114 | QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + ": " + e.message, extractStacktrace( e, 1 ) ); 115 | // else next test will carry the responsibility 116 | saveGlobal(); 117 | 118 | // Restart the tests if they're blocking 119 | if ( config.blocking ) { 120 | QUnit.start(); 121 | } 122 | } 123 | }, 124 | teardown: function() { 125 | config.current = this; 126 | if ( config.notrycatch ) { 127 | this.testEnvironment.teardown.call(this.testEnvironment); 128 | return; 129 | } else { 130 | try { 131 | this.testEnvironment.teardown.call(this.testEnvironment); 132 | } catch(e) { 133 | QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); 134 | } 135 | } 136 | checkPollution(); 137 | }, 138 | finish: function() { 139 | config.current = this; 140 | if ( this.expected != null && this.expected != this.assertions.length ) { 141 | QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); 142 | } else if ( this.expected == null && !this.assertions.length ) { 143 | QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions." ); 144 | } 145 | 146 | var good = 0, bad = 0, 147 | li, i, 148 | tests = id("qunit-tests"); 149 | 150 | config.stats.all += this.assertions.length; 151 | config.moduleStats.all += this.assertions.length; 152 | 153 | if ( tests ) { 154 | var ol = document.createElement("ol"); 155 | 156 | for ( i = 0; i < this.assertions.length; i++ ) { 157 | var assertion = this.assertions[i]; 158 | 159 | li = document.createElement("li"); 160 | li.className = assertion.result ? "pass" : "fail"; 161 | li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); 162 | ol.appendChild( li ); 163 | 164 | if ( assertion.result ) { 165 | good++; 166 | } else { 167 | bad++; 168 | config.stats.bad++; 169 | config.moduleStats.bad++; 170 | } 171 | } 172 | 173 | // store result when possible 174 | if ( QUnit.config.reorder && defined.sessionStorage ) { 175 | if (bad) { 176 | sessionStorage.setItem("qunit-test-" + this.module + "-" + this.testName, bad); 177 | } else { 178 | sessionStorage.removeItem("qunit-test-" + this.module + "-" + this.testName); 179 | } 180 | } 181 | 182 | if (bad === 0) { 183 | ol.style.display = "none"; 184 | } 185 | 186 | var b = document.createElement("strong"); 187 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; 188 | 189 | var a = document.createElement("a"); 190 | a.innerHTML = "Rerun"; 191 | a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); 192 | 193 | addEvent(b, "click", function() { 194 | var next = b.nextSibling.nextSibling, 195 | display = next.style.display; 196 | next.style.display = display === "none" ? "block" : "none"; 197 | }); 198 | 199 | addEvent(b, "dblclick", function(e) { 200 | var target = e && e.target ? e.target : window.event.srcElement; 201 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { 202 | target = target.parentNode; 203 | } 204 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 205 | window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); 206 | } 207 | }); 208 | 209 | li = id(this.id); 210 | li.className = bad ? "fail" : "pass"; 211 | li.removeChild( li.firstChild ); 212 | li.appendChild( b ); 213 | li.appendChild( a ); 214 | li.appendChild( ol ); 215 | 216 | } else { 217 | for ( i = 0; i < this.assertions.length; i++ ) { 218 | if ( !this.assertions[i].result ) { 219 | bad++; 220 | config.stats.bad++; 221 | config.moduleStats.bad++; 222 | } 223 | } 224 | } 225 | 226 | QUnit.reset(); 227 | 228 | runLoggingCallbacks( 'testDone', QUnit, { 229 | name: this.testName, 230 | module: this.module, 231 | failed: bad, 232 | passed: this.assertions.length - bad, 233 | total: this.assertions.length 234 | } ); 235 | }, 236 | 237 | queue: function() { 238 | var test = this; 239 | synchronize(function() { 240 | test.init(); 241 | }); 242 | function run() { 243 | // each of these can by async 244 | synchronize(function() { 245 | test.setup(); 246 | }); 247 | synchronize(function() { 248 | test.run(); 249 | }); 250 | synchronize(function() { 251 | test.teardown(); 252 | }); 253 | synchronize(function() { 254 | test.finish(); 255 | }); 256 | } 257 | // defer when previous test run passed, if storage is available 258 | var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-test-" + this.module + "-" + this.testName); 259 | if (bad) { 260 | run(); 261 | } else { 262 | synchronize(run, true); 263 | } 264 | } 265 | 266 | }; 267 | 268 | var QUnit = { 269 | 270 | // call on start of module test to prepend name to all tests 271 | module: function(name, testEnvironment) { 272 | config.currentModule = name; 273 | config.currentModuleTestEnviroment = testEnvironment; 274 | }, 275 | 276 | asyncTest: function(testName, expected, callback) { 277 | if ( arguments.length === 2 ) { 278 | callback = expected; 279 | expected = null; 280 | } 281 | 282 | QUnit.test(testName, expected, callback, true); 283 | }, 284 | 285 | test: function(testName, expected, callback, async) { 286 | var name = '' + escapeInnerText(testName) + ''; 287 | 288 | if ( arguments.length === 2 ) { 289 | callback = expected; 290 | expected = null; 291 | } 292 | 293 | if ( config.currentModule ) { 294 | name = '' + config.currentModule + ": " + name; 295 | } 296 | 297 | if ( !validTest(config.currentModule + ": " + testName) ) { 298 | return; 299 | } 300 | 301 | var test = new Test(name, testName, expected, async, callback); 302 | test.module = config.currentModule; 303 | test.moduleTestEnvironment = config.currentModuleTestEnviroment; 304 | test.queue(); 305 | }, 306 | 307 | // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 308 | expect: function(asserts) { 309 | config.current.expected = asserts; 310 | }, 311 | 312 | // Asserts true. 313 | // @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 314 | ok: function(result, msg) { 315 | if (!config.current) { 316 | throw new Error("ok() assertion outside test context, was " + sourceFromStacktrace(2)); 317 | } 318 | result = !!result; 319 | var details = { 320 | result: result, 321 | message: msg 322 | }; 323 | msg = escapeInnerText(msg || (result ? "okay" : "failed")); 324 | if ( !result ) { 325 | var source = sourceFromStacktrace(2); 326 | if (source) { 327 | details.source = source; 328 | msg += '
Source:
' + escapeInnerText(source) + '
'; 329 | } 330 | } 331 | runLoggingCallbacks( 'log', QUnit, details ); 332 | config.current.assertions.push({ 333 | result: result, 334 | message: msg 335 | }); 336 | }, 337 | 338 | // Checks that the first two arguments are equal, with an optional message. Prints out both actual and expected values. 339 | // @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); 340 | equal: function(actual, expected, message) { 341 | QUnit.push(expected == actual, actual, expected, message); 342 | }, 343 | 344 | notEqual: function(actual, expected, message) { 345 | QUnit.push(expected != actual, actual, expected, message); 346 | }, 347 | 348 | deepEqual: function(actual, expected, message) { 349 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); 350 | }, 351 | 352 | notDeepEqual: function(actual, expected, message) { 353 | QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); 354 | }, 355 | 356 | strictEqual: function(actual, expected, message) { 357 | QUnit.push(expected === actual, actual, expected, message); 358 | }, 359 | 360 | notStrictEqual: function(actual, expected, message) { 361 | QUnit.push(expected !== actual, actual, expected, message); 362 | }, 363 | 364 | raises: function(block, expected, message) { 365 | var actual, ok = false; 366 | 367 | if (typeof expected === 'string') { 368 | message = expected; 369 | expected = null; 370 | } 371 | 372 | try { 373 | block(); 374 | } catch (e) { 375 | actual = e; 376 | } 377 | 378 | if (actual) { 379 | // we don't want to validate thrown error 380 | if (!expected) { 381 | ok = true; 382 | // expected is a regexp 383 | } else if (QUnit.objectType(expected) === "regexp") { 384 | ok = expected.test(actual); 385 | // expected is a constructor 386 | } else if (actual instanceof expected) { 387 | ok = true; 388 | // expected is a validation function which returns true is validation passed 389 | } else if (expected.call({}, actual) === true) { 390 | ok = true; 391 | } 392 | } 393 | 394 | QUnit.ok(ok, message); 395 | }, 396 | 397 | start: function(count) { 398 | config.semaphore -= count || 1; 399 | if (config.semaphore > 0) { 400 | // don't start until equal number of stop-calls 401 | return; 402 | } 403 | if (config.semaphore < 0) { 404 | // ignore if start is called more often then stop 405 | config.semaphore = 0; 406 | } 407 | // A slight delay, to avoid any current callbacks 408 | if ( defined.setTimeout ) { 409 | window.setTimeout(function() { 410 | if (config.semaphore > 0) { 411 | return; 412 | } 413 | if ( config.timeout ) { 414 | clearTimeout(config.timeout); 415 | } 416 | 417 | config.blocking = false; 418 | process(true); 419 | }, 13); 420 | } else { 421 | config.blocking = false; 422 | process(true); 423 | } 424 | }, 425 | 426 | stop: function(count) { 427 | config.semaphore += count || 1; 428 | config.blocking = true; 429 | 430 | if ( config.testTimeout && defined.setTimeout ) { 431 | clearTimeout(config.timeout); 432 | config.timeout = window.setTimeout(function() { 433 | QUnit.ok( false, "Test timed out" ); 434 | config.semaphore = 1; 435 | QUnit.start(); 436 | }, config.testTimeout); 437 | } 438 | } 439 | }; 440 | 441 | //We want access to the constructor's prototype 442 | (function() { 443 | function F(){} 444 | F.prototype = QUnit; 445 | QUnit = new F(); 446 | //Make F QUnit's constructor so that we can add to the prototype later 447 | QUnit.constructor = F; 448 | }()); 449 | 450 | // deprecated; still export them to window to provide clear error messages 451 | // next step: remove entirely 452 | QUnit.equals = function() { 453 | QUnit.push(false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead"); 454 | }; 455 | QUnit.same = function() { 456 | QUnit.push(false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead"); 457 | }; 458 | 459 | // Maintain internal state 460 | var config = { 461 | // The queue of tests to run 462 | queue: [], 463 | 464 | // block until document ready 465 | blocking: true, 466 | 467 | // when enabled, show only failing tests 468 | // gets persisted through sessionStorage and can be changed in UI via checkbox 469 | hidepassed: false, 470 | 471 | // by default, run previously failed tests first 472 | // very useful in combination with "Hide passed tests" checked 473 | reorder: true, 474 | 475 | // by default, modify document.title when suite is done 476 | altertitle: true, 477 | 478 | urlConfig: ['noglobals', 'notrycatch'], 479 | 480 | //logging callback queues 481 | begin: [], 482 | done: [], 483 | log: [], 484 | testStart: [], 485 | testDone: [], 486 | moduleStart: [], 487 | moduleDone: [] 488 | }; 489 | 490 | // Load paramaters 491 | (function() { 492 | var location = window.location || { search: "", protocol: "file:" }, 493 | params = location.search.slice( 1 ).split( "&" ), 494 | length = params.length, 495 | urlParams = {}, 496 | current; 497 | 498 | if ( params[ 0 ] ) { 499 | for ( var i = 0; i < length; i++ ) { 500 | current = params[ i ].split( "=" ); 501 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); 502 | // allow just a key to turn on a flag, e.g., test.html?noglobals 503 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 504 | urlParams[ current[ 0 ] ] = current[ 1 ]; 505 | } 506 | } 507 | 508 | QUnit.urlParams = urlParams; 509 | config.filter = urlParams.filter; 510 | 511 | // Figure out if we're running the tests from a server or not 512 | QUnit.isLocal = location.protocol === 'file:'; 513 | }()); 514 | 515 | // Expose the API as global variables, unless an 'exports' 516 | // object exists, in that case we assume we're in CommonJS - export everything at the end 517 | if ( typeof exports === "undefined" || typeof require === "undefined" ) { 518 | extend(window, QUnit); 519 | window.QUnit = QUnit; 520 | } 521 | 522 | // define these after exposing globals to keep them in these QUnit namespace only 523 | extend(QUnit, { 524 | config: config, 525 | 526 | // Initialize the configuration options 527 | init: function() { 528 | extend(config, { 529 | stats: { all: 0, bad: 0 }, 530 | moduleStats: { all: 0, bad: 0 }, 531 | started: +new Date(), 532 | updateRate: 1000, 533 | blocking: false, 534 | autostart: true, 535 | autorun: false, 536 | filter: "", 537 | queue: [], 538 | semaphore: 0 539 | }); 540 | 541 | var qunit = id( "qunit" ); 542 | if ( qunit ) { 543 | qunit.innerHTML = 544 | '

' + escapeInnerText( document.title ) + '

' + 545 | '

' + 546 | '
' + 547 | '

' + 548 | '
    '; 549 | } 550 | 551 | var tests = id( "qunit-tests" ), 552 | banner = id( "qunit-banner" ), 553 | result = id( "qunit-testresult" ); 554 | 555 | if ( tests ) { 556 | tests.innerHTML = ""; 557 | } 558 | 559 | if ( banner ) { 560 | banner.className = ""; 561 | } 562 | 563 | if ( result ) { 564 | result.parentNode.removeChild( result ); 565 | } 566 | 567 | if ( tests ) { 568 | result = document.createElement( "p" ); 569 | result.id = "qunit-testresult"; 570 | result.className = "result"; 571 | tests.parentNode.insertBefore( result, tests ); 572 | result.innerHTML = 'Running...
     '; 573 | } 574 | }, 575 | 576 | // Resets the test setup. Useful for tests that modify the DOM. 577 | // If jQuery is available, uses jQuery's html(), otherwise just innerHTML. 578 | reset: function() { 579 | if ( window.jQuery ) { 580 | jQuery( "#qunit-fixture" ).html( config.fixture ); 581 | } else { 582 | var main = id( 'qunit-fixture' ); 583 | if ( main ) { 584 | main.innerHTML = config.fixture; 585 | } 586 | } 587 | }, 588 | 589 | // Trigger an event on an element. 590 | // @example triggerEvent( document.body, "click" ); 591 | triggerEvent: function( elem, type, event ) { 592 | if ( document.createEvent ) { 593 | event = document.createEvent("MouseEvents"); 594 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 595 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 596 | elem.dispatchEvent( event ); 597 | 598 | } else if ( elem.fireEvent ) { 599 | elem.fireEvent("on"+type); 600 | } 601 | }, 602 | 603 | // Safe object type checking 604 | is: function( type, obj ) { 605 | return QUnit.objectType( obj ) == type; 606 | }, 607 | 608 | objectType: function( obj ) { 609 | if (typeof obj === "undefined") { 610 | return "undefined"; 611 | 612 | // consider: typeof null === object 613 | } 614 | if (obj === null) { 615 | return "null"; 616 | } 617 | 618 | var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ''; 619 | 620 | switch (type) { 621 | case 'Number': 622 | if (isNaN(obj)) { 623 | return "nan"; 624 | } 625 | return "number"; 626 | case 'String': 627 | case 'Boolean': 628 | case 'Array': 629 | case 'Date': 630 | case 'RegExp': 631 | case 'Function': 632 | return type.toLowerCase(); 633 | } 634 | if (typeof obj === "object") { 635 | return "object"; 636 | } 637 | return undefined; 638 | }, 639 | 640 | push: function(result, actual, expected, message) { 641 | if (!config.current) { 642 | throw new Error("assertion outside test context, was " + sourceFromStacktrace()); 643 | } 644 | var details = { 645 | result: result, 646 | message: message, 647 | actual: actual, 648 | expected: expected 649 | }; 650 | 651 | message = escapeInnerText(message) || (result ? "okay" : "failed"); 652 | message = '' + message + ""; 653 | var output = message; 654 | if (!result) { 655 | expected = escapeInnerText(QUnit.jsDump.parse(expected)); 656 | actual = escapeInnerText(QUnit.jsDump.parse(actual)); 657 | output += ''; 658 | if (actual != expected) { 659 | output += ''; 660 | output += ''; 661 | } 662 | var source = sourceFromStacktrace(); 663 | if (source) { 664 | details.source = source; 665 | output += ''; 666 | } 667 | output += "
    Expected:
    ' + expected + '
    Result:
    ' + actual + '
    Diff:
    ' + QUnit.diff(expected, actual) +'
    Source:
    ' + escapeInnerText(source) + '
    "; 668 | } 669 | 670 | runLoggingCallbacks( 'log', QUnit, details ); 671 | 672 | config.current.assertions.push({ 673 | result: !!result, 674 | message: output 675 | }); 676 | }, 677 | 678 | pushFailure: function(message, source) { 679 | var details = { 680 | result: false, 681 | message: message 682 | }; 683 | var output = escapeInnerText(message); 684 | if (source) { 685 | details.source = source; 686 | output += '
    Source:
    ' + escapeInnerText(source) + '
    '; 687 | } 688 | runLoggingCallbacks( 'log', QUnit, details ); 689 | config.current.assertions.push({ 690 | result: false, 691 | message: output 692 | }); 693 | }, 694 | 695 | url: function( params ) { 696 | params = extend( extend( {}, QUnit.urlParams ), params ); 697 | var querystring = "?", 698 | key; 699 | for ( key in params ) { 700 | if ( !hasOwn.call( params, key ) ) { 701 | continue; 702 | } 703 | querystring += encodeURIComponent( key ) + "=" + 704 | encodeURIComponent( params[ key ] ) + "&"; 705 | } 706 | return window.location.pathname + querystring.slice( 0, -1 ); 707 | }, 708 | 709 | extend: extend, 710 | id: id, 711 | addEvent: addEvent 712 | }); 713 | 714 | //QUnit.constructor is set to the empty F() above so that we can add to it's prototype later 715 | //Doing this allows us to tell if the following methods have been overwritten on the actual 716 | //QUnit object, which is a deprecated way of using the callbacks. 717 | extend(QUnit.constructor.prototype, { 718 | // Logging callbacks; all receive a single argument with the listed properties 719 | // run test/logs.html for any related changes 720 | begin: registerLoggingCallback('begin'), 721 | // done: { failed, passed, total, runtime } 722 | done: registerLoggingCallback('done'), 723 | // log: { result, actual, expected, message } 724 | log: registerLoggingCallback('log'), 725 | // testStart: { name } 726 | testStart: registerLoggingCallback('testStart'), 727 | // testDone: { name, failed, passed, total } 728 | testDone: registerLoggingCallback('testDone'), 729 | // moduleStart: { name } 730 | moduleStart: registerLoggingCallback('moduleStart'), 731 | // moduleDone: { name, failed, passed, total } 732 | moduleDone: registerLoggingCallback('moduleDone') 733 | }); 734 | 735 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 736 | config.autorun = true; 737 | } 738 | 739 | QUnit.load = function() { 740 | runLoggingCallbacks( 'begin', QUnit, {} ); 741 | 742 | // Initialize the config, saving the execution queue 743 | var oldconfig = extend({}, config); 744 | QUnit.init(); 745 | extend(config, oldconfig); 746 | 747 | config.blocking = false; 748 | 749 | var urlConfigHtml = '', len = config.urlConfig.length; 750 | for ( var i = 0, val; i < len; i++ ) { 751 | val = config.urlConfig[i]; 752 | config[val] = QUnit.urlParams[val]; 753 | urlConfigHtml += ''; 754 | } 755 | 756 | var userAgent = id("qunit-userAgent"); 757 | if ( userAgent ) { 758 | userAgent.innerHTML = navigator.userAgent; 759 | } 760 | var banner = id("qunit-header"); 761 | if ( banner ) { 762 | banner.innerHTML = ' ' + banner.innerHTML + ' ' + urlConfigHtml; 763 | addEvent( banner, "change", function( event ) { 764 | var params = {}; 765 | params[ event.target.name ] = event.target.checked ? true : undefined; 766 | window.location = QUnit.url( params ); 767 | }); 768 | } 769 | 770 | var toolbar = id("qunit-testrunner-toolbar"); 771 | if ( toolbar ) { 772 | var filter = document.createElement("input"); 773 | filter.type = "checkbox"; 774 | filter.id = "qunit-filter-pass"; 775 | addEvent( filter, "click", function() { 776 | var ol = document.getElementById("qunit-tests"); 777 | if ( filter.checked ) { 778 | ol.className = ol.className + " hidepass"; 779 | } else { 780 | var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; 781 | ol.className = tmp.replace(/ hidepass /, " "); 782 | } 783 | if ( defined.sessionStorage ) { 784 | if (filter.checked) { 785 | sessionStorage.setItem("qunit-filter-passed-tests", "true"); 786 | } else { 787 | sessionStorage.removeItem("qunit-filter-passed-tests"); 788 | } 789 | } 790 | }); 791 | if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { 792 | filter.checked = true; 793 | var ol = document.getElementById("qunit-tests"); 794 | ol.className = ol.className + " hidepass"; 795 | } 796 | toolbar.appendChild( filter ); 797 | 798 | var label = document.createElement("label"); 799 | label.setAttribute("for", "qunit-filter-pass"); 800 | label.innerHTML = "Hide passed tests"; 801 | toolbar.appendChild( label ); 802 | } 803 | 804 | var main = id('qunit-fixture'); 805 | if ( main ) { 806 | config.fixture = main.innerHTML; 807 | } 808 | 809 | if (config.autostart) { 810 | QUnit.start(); 811 | } 812 | }; 813 | 814 | addEvent(window, "load", QUnit.load); 815 | 816 | // addEvent(window, "error") gives us a useless event object 817 | window.onerror = function( message, file, line ) { 818 | if ( QUnit.config.current ) { 819 | QUnit.pushFailure( message, file + ":" + line ); 820 | } else { 821 | QUnit.test( "global failure", function() { 822 | QUnit.pushFailure( message, file + ":" + line ); 823 | }); 824 | } 825 | }; 826 | 827 | function done() { 828 | config.autorun = true; 829 | 830 | // Log the last module results 831 | if ( config.currentModule ) { 832 | runLoggingCallbacks( 'moduleDone', QUnit, { 833 | name: config.currentModule, 834 | failed: config.moduleStats.bad, 835 | passed: config.moduleStats.all - config.moduleStats.bad, 836 | total: config.moduleStats.all 837 | } ); 838 | } 839 | 840 | var banner = id("qunit-banner"), 841 | tests = id("qunit-tests"), 842 | runtime = +new Date() - config.started, 843 | passed = config.stats.all - config.stats.bad, 844 | html = [ 845 | 'Tests completed in ', 846 | runtime, 847 | ' milliseconds.
    ', 848 | '', 849 | passed, 850 | ' tests of ', 851 | config.stats.all, 852 | ' passed, ', 853 | config.stats.bad, 854 | ' failed.' 855 | ].join(''); 856 | 857 | if ( banner ) { 858 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); 859 | } 860 | 861 | if ( tests ) { 862 | id( "qunit-testresult" ).innerHTML = html; 863 | } 864 | 865 | if ( config.altertitle && typeof document !== "undefined" && document.title ) { 866 | // show ✖ for good, ✔ for bad suite result in title 867 | // use escape sequences in case file gets loaded with non-utf-8-charset 868 | document.title = [ 869 | (config.stats.bad ? "\u2716" : "\u2714"), 870 | document.title.replace(/^[\u2714\u2716] /i, "") 871 | ].join(" "); 872 | } 873 | 874 | // clear own sessionStorage items if all tests passed 875 | if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { 876 | for (var key in sessionStorage) { 877 | if (sessionStorage.hasOwnProperty(key) && key.indexOf("qunit-test-") === 0 ) { 878 | sessionStorage.removeItem(key); 879 | } 880 | } 881 | } 882 | 883 | runLoggingCallbacks( 'done', QUnit, { 884 | failed: config.stats.bad, 885 | passed: passed, 886 | total: config.stats.all, 887 | runtime: runtime 888 | } ); 889 | } 890 | 891 | function validTest( name ) { 892 | var filter = config.filter, 893 | run = false; 894 | 895 | if ( !filter ) { 896 | return true; 897 | } 898 | 899 | var not = filter.charAt( 0 ) === "!"; 900 | if ( not ) { 901 | filter = filter.slice( 1 ); 902 | } 903 | 904 | if ( name.indexOf( filter ) !== -1 ) { 905 | return !not; 906 | } 907 | 908 | if ( not ) { 909 | run = true; 910 | } 911 | 912 | return run; 913 | } 914 | 915 | // so far supports only Firefox, Chrome and Opera (buggy) 916 | // could be extended in the future to use something like https://github.com/csnover/TraceKit 917 | function extractStacktrace( e, offset ) { 918 | offset = offset || 3; 919 | if (e.stacktrace) { 920 | // Opera 921 | return e.stacktrace.split("\n")[offset + 3]; 922 | } else if (e.stack) { 923 | // Firefox, Chrome 924 | var stack = e.stack.split("\n"); 925 | if (/^error$/i.test(stack[0])) { 926 | stack.shift(); 927 | } 928 | return stack[offset]; 929 | } else if (e.sourceURL) { 930 | // Safari, PhantomJS 931 | // hopefully one day Safari provides actual stacktraces 932 | // exclude useless self-reference for generated Error objects 933 | if ( /qunit.js$/.test( e.sourceURL ) ) { 934 | return; 935 | } 936 | // for actual exceptions, this is useful 937 | return e.sourceURL + ":" + e.line; 938 | } 939 | } 940 | function sourceFromStacktrace(offset) { 941 | try { 942 | throw new Error(); 943 | } catch ( e ) { 944 | return extractStacktrace( e, offset ); 945 | } 946 | } 947 | 948 | function escapeInnerText(s) { 949 | if (!s) { 950 | return ""; 951 | } 952 | s = s + ""; 953 | return s.replace(/[\&<>]/g, function(s) { 954 | switch(s) { 955 | case "&": return "&"; 956 | case "<": return "<"; 957 | case ">": return ">"; 958 | default: return s; 959 | } 960 | }); 961 | } 962 | 963 | function synchronize( callback, last ) { 964 | config.queue.push( callback ); 965 | 966 | if ( config.autorun && !config.blocking ) { 967 | process(last); 968 | } 969 | } 970 | 971 | function process( last ) { 972 | function next() { 973 | process( last ); 974 | } 975 | var start = new Date().getTime(); 976 | config.depth = config.depth ? config.depth + 1 : 1; 977 | 978 | while ( config.queue.length && !config.blocking ) { 979 | if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { 980 | config.queue.shift()(); 981 | } else { 982 | window.setTimeout( next, 13 ); 983 | break; 984 | } 985 | } 986 | config.depth--; 987 | if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { 988 | done(); 989 | } 990 | } 991 | 992 | function saveGlobal() { 993 | config.pollution = []; 994 | 995 | if ( config.noglobals ) { 996 | for ( var key in window ) { 997 | if ( !hasOwn.call( window, key ) ) { 998 | continue; 999 | } 1000 | config.pollution.push( key ); 1001 | } 1002 | } 1003 | } 1004 | 1005 | function checkPollution( name ) { 1006 | var old = config.pollution; 1007 | saveGlobal(); 1008 | 1009 | var newGlobals = diff( config.pollution, old ); 1010 | if ( newGlobals.length > 0 ) { 1011 | QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); 1012 | } 1013 | 1014 | var deletedGlobals = diff( old, config.pollution ); 1015 | if ( deletedGlobals.length > 0 ) { 1016 | QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); 1017 | } 1018 | } 1019 | 1020 | // returns a new Array with the elements that are in a but not in b 1021 | function diff( a, b ) { 1022 | var result = a.slice(); 1023 | for ( var i = 0; i < result.length; i++ ) { 1024 | for ( var j = 0; j < b.length; j++ ) { 1025 | if ( result[i] === b[j] ) { 1026 | result.splice(i, 1); 1027 | i--; 1028 | break; 1029 | } 1030 | } 1031 | } 1032 | return result; 1033 | } 1034 | 1035 | function extend(a, b) { 1036 | for ( var prop in b ) { 1037 | if ( b[prop] === undefined ) { 1038 | delete a[prop]; 1039 | 1040 | // Avoid "Member not found" error in IE8 caused by setting window.constructor 1041 | } else if ( prop !== "constructor" || a !== window ) { 1042 | a[prop] = b[prop]; 1043 | } 1044 | } 1045 | 1046 | return a; 1047 | } 1048 | 1049 | function addEvent(elem, type, fn) { 1050 | if ( elem.addEventListener ) { 1051 | elem.addEventListener( type, fn, false ); 1052 | } else if ( elem.attachEvent ) { 1053 | elem.attachEvent( "on" + type, fn ); 1054 | } else { 1055 | fn(); 1056 | } 1057 | } 1058 | 1059 | function id(name) { 1060 | return !!(typeof document !== "undefined" && document && document.getElementById) && 1061 | document.getElementById( name ); 1062 | } 1063 | 1064 | function registerLoggingCallback(key){ 1065 | return function(callback){ 1066 | config[key].push( callback ); 1067 | }; 1068 | } 1069 | 1070 | // Supports deprecated method of completely overwriting logging callbacks 1071 | function runLoggingCallbacks(key, scope, args) { 1072 | //debugger; 1073 | var callbacks; 1074 | if ( QUnit.hasOwnProperty(key) ) { 1075 | QUnit[key].call(scope, args); 1076 | } else { 1077 | callbacks = config[key]; 1078 | for( var i = 0; i < callbacks.length; i++ ) { 1079 | callbacks[i].call( scope, args ); 1080 | } 1081 | } 1082 | } 1083 | 1084 | // Test for equality any JavaScript type. 1085 | // Author: Philippe Rathé 1086 | QUnit.equiv = (function() { 1087 | 1088 | var innerEquiv; // the real equiv function 1089 | var callers = []; // stack to decide between skip/abort functions 1090 | var parents = []; // stack to avoiding loops from circular referencing 1091 | 1092 | // Call the o related callback with the given arguments. 1093 | function bindCallbacks(o, callbacks, args) { 1094 | var prop = QUnit.objectType(o); 1095 | if (prop) { 1096 | if (QUnit.objectType(callbacks[prop]) === "function") { 1097 | return callbacks[prop].apply(callbacks, args); 1098 | } else { 1099 | return callbacks[prop]; // or undefined 1100 | } 1101 | } 1102 | } 1103 | 1104 | var getProto = Object.getPrototypeOf || function (obj) { 1105 | return obj.__proto__; 1106 | }; 1107 | 1108 | var callbacks = (function () { 1109 | 1110 | // for string, boolean, number and null 1111 | function useStrictEquality(b, a) { 1112 | if (b instanceof a.constructor || a instanceof b.constructor) { 1113 | // to catch short annotaion VS 'new' annotation of a 1114 | // declaration 1115 | // e.g. var i = 1; 1116 | // var j = new Number(1); 1117 | return a == b; 1118 | } else { 1119 | return a === b; 1120 | } 1121 | } 1122 | 1123 | return { 1124 | "string" : useStrictEquality, 1125 | "boolean" : useStrictEquality, 1126 | "number" : useStrictEquality, 1127 | "null" : useStrictEquality, 1128 | "undefined" : useStrictEquality, 1129 | 1130 | "nan" : function(b) { 1131 | return isNaN(b); 1132 | }, 1133 | 1134 | "date" : function(b, a) { 1135 | return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); 1136 | }, 1137 | 1138 | "regexp" : function(b, a) { 1139 | return QUnit.objectType(b) === "regexp" && 1140 | // the regex itself 1141 | a.source === b.source && 1142 | // and its modifers 1143 | a.global === b.global && 1144 | // (gmi) ... 1145 | a.ignoreCase === b.ignoreCase && 1146 | a.multiline === b.multiline; 1147 | }, 1148 | 1149 | // - skip when the property is a method of an instance (OOP) 1150 | // - abort otherwise, 1151 | // initial === would have catch identical references anyway 1152 | "function" : function() { 1153 | var caller = callers[callers.length - 1]; 1154 | return caller !== Object && typeof caller !== "undefined"; 1155 | }, 1156 | 1157 | "array" : function(b, a) { 1158 | var i, j, loop; 1159 | var len; 1160 | 1161 | // b could be an object literal here 1162 | if (QUnit.objectType(b) !== "array") { 1163 | return false; 1164 | } 1165 | 1166 | len = a.length; 1167 | if (len !== b.length) { // safe and faster 1168 | return false; 1169 | } 1170 | 1171 | // track reference to avoid circular references 1172 | parents.push(a); 1173 | for (i = 0; i < len; i++) { 1174 | loop = false; 1175 | for (j = 0; j < parents.length; j++) { 1176 | if (parents[j] === a[i]) { 1177 | loop = true;// dont rewalk array 1178 | } 1179 | } 1180 | if (!loop && !innerEquiv(a[i], b[i])) { 1181 | parents.pop(); 1182 | return false; 1183 | } 1184 | } 1185 | parents.pop(); 1186 | return true; 1187 | }, 1188 | 1189 | "object" : function(b, a) { 1190 | var i, j, loop; 1191 | var eq = true; // unless we can proove it 1192 | var aProperties = [], bProperties = []; // collection of 1193 | // strings 1194 | 1195 | // comparing constructors is more strict than using 1196 | // instanceof 1197 | if (a.constructor !== b.constructor) { 1198 | // Allow objects with no prototype to be equivalent to 1199 | // objects with Object as their constructor. 1200 | if (!((getProto(a) === null && getProto(b) === Object.prototype) || 1201 | (getProto(b) === null && getProto(a) === Object.prototype))) 1202 | { 1203 | return false; 1204 | } 1205 | } 1206 | 1207 | // stack constructor before traversing properties 1208 | callers.push(a.constructor); 1209 | // track reference to avoid circular references 1210 | parents.push(a); 1211 | 1212 | for (i in a) { // be strict: don't ensures hasOwnProperty 1213 | // and go deep 1214 | loop = false; 1215 | for (j = 0; j < parents.length; j++) { 1216 | if (parents[j] === a[i]) { 1217 | // don't go down the same path twice 1218 | loop = true; 1219 | } 1220 | } 1221 | aProperties.push(i); // collect a's properties 1222 | 1223 | if (!loop && !innerEquiv(a[i], b[i])) { 1224 | eq = false; 1225 | break; 1226 | } 1227 | } 1228 | 1229 | callers.pop(); // unstack, we are done 1230 | parents.pop(); 1231 | 1232 | for (i in b) { 1233 | bProperties.push(i); // collect b's properties 1234 | } 1235 | 1236 | // Ensures identical properties name 1237 | return eq && innerEquiv(aProperties.sort(), bProperties.sort()); 1238 | } 1239 | }; 1240 | }()); 1241 | 1242 | innerEquiv = function() { // can take multiple arguments 1243 | var args = Array.prototype.slice.apply(arguments); 1244 | if (args.length < 2) { 1245 | return true; // end transition 1246 | } 1247 | 1248 | return (function(a, b) { 1249 | if (a === b) { 1250 | return true; // catch the most you can 1251 | } else if (a === null || b === null || typeof a === "undefined" || 1252 | typeof b === "undefined" || 1253 | QUnit.objectType(a) !== QUnit.objectType(b)) { 1254 | return false; // don't lose time with error prone cases 1255 | } else { 1256 | return bindCallbacks(a, callbacks, [ b, a ]); 1257 | } 1258 | 1259 | // apply transition with (1..n) arguments 1260 | }(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length - 1))); 1261 | }; 1262 | 1263 | return innerEquiv; 1264 | 1265 | }()); 1266 | 1267 | /** 1268 | * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | 1269 | * http://flesler.blogspot.com Licensed under BSD 1270 | * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 1271 | * 1272 | * @projectDescription Advanced and extensible data dumping for Javascript. 1273 | * @version 1.0.0 1274 | * @author Ariel Flesler 1275 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} 1276 | */ 1277 | QUnit.jsDump = (function() { 1278 | function quote( str ) { 1279 | return '"' + str.toString().replace(/"/g, '\\"') + '"'; 1280 | } 1281 | function literal( o ) { 1282 | return o + ''; 1283 | } 1284 | function join( pre, arr, post ) { 1285 | var s = jsDump.separator(), 1286 | base = jsDump.indent(), 1287 | inner = jsDump.indent(1); 1288 | if ( arr.join ) { 1289 | arr = arr.join( ',' + s + inner ); 1290 | } 1291 | if ( !arr ) { 1292 | return pre + post; 1293 | } 1294 | return [ pre, inner + arr, base + post ].join(s); 1295 | } 1296 | function array( arr, stack ) { 1297 | var i = arr.length, ret = new Array(i); 1298 | this.up(); 1299 | while ( i-- ) { 1300 | ret[i] = this.parse( arr[i] , undefined , stack); 1301 | } 1302 | this.down(); 1303 | return join( '[', ret, ']' ); 1304 | } 1305 | 1306 | var reName = /^function (\w+)/; 1307 | 1308 | var jsDump = { 1309 | parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance 1310 | stack = stack || [ ]; 1311 | var parser = this.parsers[ type || this.typeOf(obj) ]; 1312 | type = typeof parser; 1313 | var inStack = inArray(obj, stack); 1314 | if (inStack != -1) { 1315 | return 'recursion('+(inStack - stack.length)+')'; 1316 | } 1317 | //else 1318 | if (type == 'function') { 1319 | stack.push(obj); 1320 | var res = parser.call( this, obj, stack ); 1321 | stack.pop(); 1322 | return res; 1323 | } 1324 | // else 1325 | return (type == 'string') ? parser : this.parsers.error; 1326 | }, 1327 | typeOf: function( obj ) { 1328 | var type; 1329 | if ( obj === null ) { 1330 | type = "null"; 1331 | } else if (typeof obj === "undefined") { 1332 | type = "undefined"; 1333 | } else if (QUnit.is("RegExp", obj)) { 1334 | type = "regexp"; 1335 | } else if (QUnit.is("Date", obj)) { 1336 | type = "date"; 1337 | } else if (QUnit.is("Function", obj)) { 1338 | type = "function"; 1339 | } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { 1340 | type = "window"; 1341 | } else if (obj.nodeType === 9) { 1342 | type = "document"; 1343 | } else if (obj.nodeType) { 1344 | type = "node"; 1345 | } else if ( 1346 | // native arrays 1347 | toString.call( obj ) === "[object Array]" || 1348 | // NodeList objects 1349 | ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) 1350 | ) { 1351 | type = "array"; 1352 | } else { 1353 | type = typeof obj; 1354 | } 1355 | return type; 1356 | }, 1357 | separator: function() { 1358 | return this.multiline ? this.HTML ? '
    ' : '\n' : this.HTML ? ' ' : ' '; 1359 | }, 1360 | indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 1361 | if ( !this.multiline ) { 1362 | return ''; 1363 | } 1364 | var chr = this.indentChar; 1365 | if ( this.HTML ) { 1366 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 1367 | } 1368 | return new Array( this._depth_ + (extra||0) ).join(chr); 1369 | }, 1370 | up: function( a ) { 1371 | this._depth_ += a || 1; 1372 | }, 1373 | down: function( a ) { 1374 | this._depth_ -= a || 1; 1375 | }, 1376 | setParser: function( name, parser ) { 1377 | this.parsers[name] = parser; 1378 | }, 1379 | // The next 3 are exposed so you can use them 1380 | quote: quote, 1381 | literal: literal, 1382 | join: join, 1383 | // 1384 | _depth_: 1, 1385 | // This is the list of parsers, to modify them, use jsDump.setParser 1386 | parsers: { 1387 | window: '[Window]', 1388 | document: '[Document]', 1389 | error: '[ERROR]', //when no parser is found, shouldn't happen 1390 | unknown: '[Unknown]', 1391 | 'null': 'null', 1392 | 'undefined': 'undefined', 1393 | 'function': function( fn ) { 1394 | var ret = 'function', 1395 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 1396 | if ( name ) { 1397 | ret += ' ' + name; 1398 | } 1399 | ret += '('; 1400 | 1401 | ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); 1402 | return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); 1403 | }, 1404 | array: array, 1405 | nodelist: array, 1406 | 'arguments': array, 1407 | object: function( map, stack ) { 1408 | var ret = [ ], keys, key, val, i; 1409 | QUnit.jsDump.up(); 1410 | if (Object.keys) { 1411 | keys = Object.keys( map ); 1412 | } else { 1413 | keys = []; 1414 | for (key in map) { keys.push( key ); } 1415 | } 1416 | keys.sort(); 1417 | for (i = 0; i < keys.length; i++) { 1418 | key = keys[ i ]; 1419 | val = map[ key ]; 1420 | ret.push( QUnit.jsDump.parse( key, 'key' ) + ': ' + QUnit.jsDump.parse( val, undefined, stack ) ); 1421 | } 1422 | QUnit.jsDump.down(); 1423 | return join( '{', ret, '}' ); 1424 | }, 1425 | node: function( node ) { 1426 | var open = QUnit.jsDump.HTML ? '<' : '<', 1427 | close = QUnit.jsDump.HTML ? '>' : '>'; 1428 | 1429 | var tag = node.nodeName.toLowerCase(), 1430 | ret = open + tag; 1431 | 1432 | for ( var a in QUnit.jsDump.DOMAttrs ) { 1433 | var val = node[QUnit.jsDump.DOMAttrs[a]]; 1434 | if ( val ) { 1435 | ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); 1436 | } 1437 | } 1438 | return ret + close + open + '/' + tag + close; 1439 | }, 1440 | functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function 1441 | var l = fn.length; 1442 | if ( !l ) { 1443 | return ''; 1444 | } 1445 | 1446 | var args = new Array(l); 1447 | while ( l-- ) { 1448 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1449 | } 1450 | return ' ' + args.join(', ') + ' '; 1451 | }, 1452 | key: quote, //object calls it internally, the key part of an item in a map 1453 | functionCode: '[code]', //function calls it internally, it's the content of the function 1454 | attribute: quote, //node calls it internally, it's an html attribute value 1455 | string: quote, 1456 | date: quote, 1457 | regexp: literal, //regex 1458 | number: literal, 1459 | 'boolean': literal 1460 | }, 1461 | DOMAttrs:{//attributes to dump from nodes, name=>realName 1462 | id:'id', 1463 | name:'name', 1464 | 'class':'className' 1465 | }, 1466 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) 1467 | indentChar:' ',//indentation unit 1468 | multiline:true //if true, items in a collection, are separated by a \n, else just a space. 1469 | }; 1470 | 1471 | return jsDump; 1472 | }()); 1473 | 1474 | // from Sizzle.js 1475 | function getText( elems ) { 1476 | var ret = "", elem; 1477 | 1478 | for ( var i = 0; elems[i]; i++ ) { 1479 | elem = elems[i]; 1480 | 1481 | // Get the text from text nodes and CDATA nodes 1482 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 1483 | ret += elem.nodeValue; 1484 | 1485 | // Traverse everything else, except comment nodes 1486 | } else if ( elem.nodeType !== 8 ) { 1487 | ret += getText( elem.childNodes ); 1488 | } 1489 | } 1490 | 1491 | return ret; 1492 | } 1493 | 1494 | //from jquery.js 1495 | function inArray( elem, array ) { 1496 | if ( array.indexOf ) { 1497 | return array.indexOf( elem ); 1498 | } 1499 | 1500 | for ( var i = 0, length = array.length; i < length; i++ ) { 1501 | if ( array[ i ] === elem ) { 1502 | return i; 1503 | } 1504 | } 1505 | 1506 | return -1; 1507 | } 1508 | 1509 | /* 1510 | * Javascript Diff Algorithm 1511 | * By John Resig (http://ejohn.org/) 1512 | * Modified by Chu Alan "sprite" 1513 | * 1514 | * Released under the MIT license. 1515 | * 1516 | * More Info: 1517 | * http://ejohn.org/projects/javascript-diff-algorithm/ 1518 | * 1519 | * Usage: QUnit.diff(expected, actual) 1520 | * 1521 | * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" 1522 | */ 1523 | QUnit.diff = (function() { 1524 | function diff(o, n) { 1525 | var ns = {}; 1526 | var os = {}; 1527 | var i; 1528 | 1529 | for (i = 0; i < n.length; i++) { 1530 | if (ns[n[i]] == null) { 1531 | ns[n[i]] = { 1532 | rows: [], 1533 | o: null 1534 | }; 1535 | } 1536 | ns[n[i]].rows.push(i); 1537 | } 1538 | 1539 | for (i = 0; i < o.length; i++) { 1540 | if (os[o[i]] == null) { 1541 | os[o[i]] = { 1542 | rows: [], 1543 | n: null 1544 | }; 1545 | } 1546 | os[o[i]].rows.push(i); 1547 | } 1548 | 1549 | for (i in ns) { 1550 | if ( !hasOwn.call( ns, i ) ) { 1551 | continue; 1552 | } 1553 | if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { 1554 | n[ns[i].rows[0]] = { 1555 | text: n[ns[i].rows[0]], 1556 | row: os[i].rows[0] 1557 | }; 1558 | o[os[i].rows[0]] = { 1559 | text: o[os[i].rows[0]], 1560 | row: ns[i].rows[0] 1561 | }; 1562 | } 1563 | } 1564 | 1565 | for (i = 0; i < n.length - 1; i++) { 1566 | if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && 1567 | n[i + 1] == o[n[i].row + 1]) { 1568 | n[i + 1] = { 1569 | text: n[i + 1], 1570 | row: n[i].row + 1 1571 | }; 1572 | o[n[i].row + 1] = { 1573 | text: o[n[i].row + 1], 1574 | row: i + 1 1575 | }; 1576 | } 1577 | } 1578 | 1579 | for (i = n.length - 1; i > 0; i--) { 1580 | if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && 1581 | n[i - 1] == o[n[i].row - 1]) { 1582 | n[i - 1] = { 1583 | text: n[i - 1], 1584 | row: n[i].row - 1 1585 | }; 1586 | o[n[i].row - 1] = { 1587 | text: o[n[i].row - 1], 1588 | row: i - 1 1589 | }; 1590 | } 1591 | } 1592 | 1593 | return { 1594 | o: o, 1595 | n: n 1596 | }; 1597 | } 1598 | 1599 | return function(o, n) { 1600 | o = o.replace(/\s+$/, ''); 1601 | n = n.replace(/\s+$/, ''); 1602 | var out = diff(o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/)); 1603 | 1604 | var str = ""; 1605 | var i; 1606 | 1607 | var oSpace = o.match(/\s+/g); 1608 | if (oSpace == null) { 1609 | oSpace = [" "]; 1610 | } 1611 | else { 1612 | oSpace.push(" "); 1613 | } 1614 | var nSpace = n.match(/\s+/g); 1615 | if (nSpace == null) { 1616 | nSpace = [" "]; 1617 | } 1618 | else { 1619 | nSpace.push(" "); 1620 | } 1621 | 1622 | if (out.n.length === 0) { 1623 | for (i = 0; i < out.o.length; i++) { 1624 | str += '' + out.o[i] + oSpace[i] + ""; 1625 | } 1626 | } 1627 | else { 1628 | if (out.n[0].text == null) { 1629 | for (n = 0; n < out.o.length && out.o[n].text == null; n++) { 1630 | str += '' + out.o[n] + oSpace[n] + ""; 1631 | } 1632 | } 1633 | 1634 | for (i = 0; i < out.n.length; i++) { 1635 | if (out.n[i].text == null) { 1636 | str += '' + out.n[i] + nSpace[i] + ""; 1637 | } 1638 | else { 1639 | var pre = ""; 1640 | 1641 | for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { 1642 | pre += '' + out.o[n] + oSpace[n] + ""; 1643 | } 1644 | str += " " + out.n[i].text + nSpace[i] + pre; 1645 | } 1646 | } 1647 | } 1648 | 1649 | return str; 1650 | }; 1651 | }()); 1652 | 1653 | // for CommonJS enviroments, export everything 1654 | if ( typeof exports !== "undefined" || typeof require !== "undefined" ) { 1655 | extend(exports, QUnit); 1656 | } 1657 | 1658 | // get at whatever the global object is, like window in browsers 1659 | }( (function() {return this;}.call()) )); 1660 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eCSSential", 3 | "title": "eCSSential", 4 | "description": "Responsible, responsive CSS loading.", 5 | "version": "0.1.0", 6 | "homepage": "https://github.com/scottjehl/eCSSential", 7 | "author": { 8 | "name": "Scott Jehl, @scottjehl, Filament Group, Inc.", 9 | "email": "", 10 | "url": "" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/scottjehl/eCSSential" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/scottjehl/eCSSential/issues" 18 | }, 19 | "licenses": [ 20 | { 21 | "type": "GPL", 22 | "url": "https://github.com/scottjehl/eCSSential/blob/master/LICENSE-GPL" 23 | }, 24 | { 25 | "type": "MIT", 26 | "url": "https://github.com/scottjehl/eCSSential/blob/master/LICENSE-MIT" 27 | } 28 | ], 29 | "dependencies": { 30 | "jquery": "~1.5" 31 | }, 32 | "keywords": [] 33 | } -------------------------------------------------------------------------------- /src/eCSSential.js: -------------------------------------------------------------------------------- 1 | /*! eCSSential 2 | * https://github.com/scottjehl/eCSSential 3 | * Copyright (c) 2012 Scott Jehl, @scottjehl, Filament Group, Inc.; Licensed GPL, MIT; Includes matchMedia.js: http://j.mp/jay3wJ (MIT) */ 4 | 5 | window.eCSSential = function( css, config ){ 6 | "use strict"; 7 | var load = [], 8 | defer = [], 9 | timedout = [], 10 | // All options false or null by default; no need for a mixin 11 | o = config || {}, 12 | w = window, 13 | d = w.document, 14 | insLoc = d.getElementsByTagName( "script" )[0], 15 | whre = /(min|max)-(width|height)/gmi, 16 | ieV = w.navigator.appVersion.match( /MSIE ([678])\./ ) && RegExp.$1, 17 | ieRe = new RegExp( "(IE" + ieV + ")|(IE)", "g" ); 18 | 19 | for( var mq in css ){ 20 | if( css.hasOwnProperty( mq ) ){ 21 | // if media query evaluates true, 22 | // or if the browser is IE 6-8 and the key is a IEx match, or the o.oldIE option is true, 23 | // queue the stylesheet for a renderer-blocking load 24 | var iekey = mq.match( ieRe ); 25 | 26 | if( w.matchMedia( mq ).matches || ( ieV && ( o.oldIE || iekey && iekey[ 1 ] ) ) ){ 27 | load.push( { 28 | //keep the media attribute, but leave it as "all" if it was an "IEx" key 29 | mq: o.oldIE || iekey ? "all" : mq, 30 | href: css[ mq ] 31 | } ); 32 | } 33 | // otherwise, queue for deferred load some stylesheets that didn't evaluate true the first time 34 | // Note: this means many stylesheets intended for conditions that could never apply (such as a width wider than the maximum device width) will be loaded anyway, causing more HTTP requests. 35 | // min/max-width/height queries are by default evaluated to see if they could never apply on the current screen 36 | // by running them as a "device" query instead of a viewport query. 37 | // You can disable this behavior and defer every stylesheet by setting the deferAll configuration option 38 | else if( !iekey && ( o.deferAll || !mq.match( whre ) || w.matchMedia( mq.replace( whre, "$1-device-$2" ) ).matches ) ){ 39 | defer.push( { mq: mq, href: css[ mq ] } ); 40 | } 41 | } 42 | } 43 | 44 | // Make link elements (or one concat'd link) from an array of Stylesheets 45 | // first argument is array of urls, second argument is bool for inserting meta element marker (only used in block) 46 | function makeLinks( arr ){ 47 | var marker = arr === load ? '' : '', 48 | start = '', 50 | hrefs = [], 51 | hrefmqs = []; 52 | 53 | for( var i in arr ){ 54 | if( arr.hasOwnProperty( i ) ){ 55 | hrefs.push( arr[ i ].href ); 56 | hrefmqs.push( arr[ i ].href + '" media="' + arr[ i ].mq ); 57 | } 58 | } 59 | 60 | // if the concat option is specified (recommended), pass the array through it and dump the resulting string into a single stylesheet url 61 | if( o.concat ){ 62 | return start + o.concat( hrefs ) + end + marker; 63 | } 64 | // otherwise, make separate link elements 65 | else { 66 | return start + hrefmqs.join( '" ' + end + start ) + end + marker; 67 | } 68 | } 69 | 70 | // document.write the stylesheets that should block 71 | if( load.length ){ 72 | d.write( makeLinks( load ) ); 73 | insLoc = d.getElementById( "eCSS" ); 74 | 75 | // set up timeout to stop a stylesheet from blocking after 8 seconds 76 | // or by however many ms are passed via o.patience 77 | var links = insLoc.parentNode.getElementsByTagName( "link" ); 78 | for(var i = 0, il = links.length; i< il; i++ ){ 79 | (function( c ){ 80 | var t = w.setTimeout(function(){ 81 | var next = c.nextSibling; 82 | c.parentNode.removeChild( c ); 83 | next.parentNode.insertBefore( c, next ); 84 | timedout.push( c ); 85 | }, o.patience || 8000 ); 86 | c.onload = function(){ 87 | clearTimeout( t ); 88 | }; 89 | }( links[ i ] )); 90 | } 91 | } 92 | 93 | // defer the load of the stylesheet that could later apply 94 | if( defer.length ){ 95 | var div = d.createElement( "div" ); 96 | div.innerHTML = makeLinks( defer ); 97 | insLoc.parentNode.insertBefore( div, insLoc ); 98 | } 99 | // return data for testing 100 | return { css: css, config: config, block: load, defer: defer, timedout: timedout }; 101 | }; 102 | -------------------------------------------------------------------------------- /test/unit/eCSSential.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | eCSSential Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

    eCSSential Test Suite

    16 |

    17 |
    18 |

    19 |
      20 |
      21 | 22 | 23 |
      24 | 25 | 26 | -------------------------------------------------------------------------------- /test/unit/eCSSential_test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottjehl/eCSSential/d20cb3ce41b6292d5c01b52a87d12dcd5eca23cd/test/unit/eCSSential_test.js --------------------------------------------------------------------------------