[Pkg-javascript-commits] [node-jsdom] 01/01: Imported Upstream version 0.8.10+dfsg1

Dmitry Smirnov onlyjob at moszumanska.debian.org
Mon Dec 30 22:41:24 UTC 2013


This is an automated email from the git hooks/post-receive script.

onlyjob pushed a commit to branch upstream
in repository node-jsdom.

commit a909bb1 (upstream)
Author: Dmitry Smirnov <onlyjob at member.fsf.org>
Date:   Mon Dec 30 22:02:20 2013

    Imported Upstream version 0.8.10+dfsg1
---
 .npmignore                            |   10 +-
 .travis.yml                           |    4 +
 Changelog.md                          |  383 +++++++++
 Contributing.md                       |   54 ++
 README.md                             |  469 ++++++-----
 changelog                             |  154 ----
 lib/jsdom.js                          |  430 +++++-----
 lib/jsdom/browser/documentfeatures.js |   10 +-
 lib/jsdom/browser/domtohtml.js        |   20 +-
 lib/jsdom/browser/history.js          |   99 +++
 lib/jsdom/browser/htmltodom.js        |   24 +-
 lib/jsdom/browser/index.js            |  311 +++----
 lib/jsdom/browser/location.js         |   98 +++
 lib/jsdom/browser/utils.js            |   12 +
 lib/jsdom/level1/core.js              |  296 +++----
 lib/jsdom/level2/core.js              |    8 +-
 lib/jsdom/level2/events.js            |    7 +-
 lib/jsdom/level2/html.js              |  345 ++++++--
 lib/jsdom/level2/style.js             |   96 ++-
 lib/jsdom/level3/core.js              |   57 +-
 lib/jsdom/level3/index.js             |    2 +-
 lib/jsdom/level3/xpath.js             |    5 +-
 lib/jsdom/selectors/index.js          |   34 +-
 lib/jsdom/selectors/sizzle.js         | 1449 ---------------------------------
 lib/jsdom/utils.js                    |   16 +-
 package.json                          |  212 ++---
 26 files changed, 1965 insertions(+), 2640 deletions(-)

diff --git a/.npmignore b/.npmignore
index 6ca468a..7d8c2c0 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,6 +1,14 @@
+benchmark/
+example/
 test/
+Changelog.md
+status.json
+Contributing.md
+
+.npmignore
 .DS_Store
 .svn
+.travis.yml
 .*.swp
 **gmon.out
-**v8.log
\ No newline at end of file
+**v8.log
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..cc4dba2
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,4 @@
+language: node_js
+node_js:
+  - "0.8"
+  - "0.10"
diff --git a/Changelog.md b/Changelog.md
new file mode 100644
index 0000000..40b6105
--- /dev/null
+++ b/Changelog.md
@@ -0,0 +1,383 @@
+## 0.8.10
+
+* Add: `hash` property to `HTMLAnchorElement`. (fr0z3nk0)
+
+## 0.8.9
+
+* Upgrade: `cssom` to 0.3.0, adding support for `@-moz-document` and fixing a few other issues.
+* Upgrade: `cssstyle` to 0.2.6, adding support for many shorthand properties and better unit handling.
+
+## 0.8.8
+
+* Fix: avoid repeated `NodeList.prototype.length` calculation, for a speed improvement. (peller)
+
+## 0.8.7
+
+* Add: `host` property to `HTMLAnchorElement`. (sporchia)
+
+## 0.8.6
+
+* Fix: stop accidentally modifying `Error.prototype`. (mitar)
+* Add: a dummy `getBoundingClientRect` method, that returns `0` for all properties of the rectangle, is now implemented. (F1LT3R)
+
+## 0.8.5
+
+* Add: `href` property on `CSSStyleSheet` instances for external CSS files. (FrozenCow)
+
+## 0.8.4
+
+ * Add: typed array constructors on the `window`. (nlacasse)
+ * Fix: `querySelector` and `querySelectorAll` should be on the prototypes of `Element` and `Document`, not own-properties. (mbostock)
+
+## 0.8.3
+
+ * Fix: when auto-detecting whether the first parameter to `jsdom.env` is a HTML string or a filename, deal with long strings correctly instead of erroring. (baryshev)
+
+## 0.8.2
+
+ * Add: basic `window.history` support, including `back`, `forward`, `go`, `pushState`, and `replaceState`. (ralphholzmann)
+ * Add: if an `<?xml?>` declaration starts the document, will try to parse as XML, e.g. not lowercasing the tags. (robdodson)
+ * Fix: tag names passed to `createElement` are coerced to strings before evaluating.
+
+## 0.8.1 (hotfix)
+
+ * Fix: a casing issue that prevented jsdom from loading on Unix and Solaris systems. (dai-shi)
+ * Fix: `window.location.replace` was broken. (dai-shi)
+ * Fix: update minimum htmlparser2 version, to ensure you get the latest parsing-related bugfixes.
+
+## 0.8.0
+
+ * Add: working `XMLHttpRequest` support, including cookie passing! (dai-shi)
+ * Add: there is now a `window.navigator.noUI` property that evaluates to true, if you want to specifically distinguish jsdom in your tests.
+
+## 0.7.0
+
+ * Change: the logic when passing `jsdom.env` a string is more accurate, and you can be explicit by using the `html`, `url`, or `file` properties. This is a breaking change in the behavior of `html`, which used to do the same auto-detection logic as the string-only version.
+ * Fix: errors raised in scripts are now passed to `jsdom.env`'s callback. (airportyh)
+ * Fix: set `window.location.href` correctly when using `jsdom.env` to construct a window from a URL, when that URL causes a redirect. (fegs)
+ * Add: a more complete and accurate `window.location` object, which includes firing `hashchange` events when the hash is changed. (dai-shi)
+ * Add: when using a non-implemented feature, mention exactly what it was that is not implemented in the error message. (papandreou)
+
+## 0.6.5
+
+ * Fix: custom attributes whose names were the same as properties of `Object.prototype`, e.g. `"constructor"`, would confuse jsdom massively.
+
+## 0.6.4
+
+ * Fix: CSS selectors which contain commas inside quotes are no longer misinterpreted. (chad3814)
+ * Add: `<img>` elements now fire `"load"` events when their `src` attributes are changed. (kapouer)
+
+## 0.6.3
+
+ * Fix: better automatic detection of URLs vs. HTML fragments when using `jsdom.env`. (jden)
+
+## 0.6.2
+
+ * Fix: URL resolution to be amazing and extremely browser-compatible, including the interplay between the document's original URL, any `<base>` tags that were set, and any relative `href`s. This impacts many parts of jsdom having to do with external resources or accurate `href` and `src` attributes. (deitch)
+ * Add: access to frames and iframes via named properties. (adrianlang)
+ * Fix: node-canvas integration, which had been broken since 0.5.7.
+
+## 0.6.1
+
+ * Make the code parseable with Esprima. (squarooticus)
+ * Use the correct `package.json` field `"repository"` instead of `"repositories"` to prevent npm warnings. (jonathanong)
+
+## 0.6.0
+
+Integrated a new HTML parser, [htmlparser2](https://npmjs.org/package/htmlparser2), from fb55. This is an actively maintained and much less buggy parser, fixing many of our parsing issues, including:
+
+ * Parsing elements with optional closing tags, like `<p>` or `<td>`.
+ * The `innerHTML` of `<script>` tags no longer cuts off the first character.
+ * Empty attributes now have `""` as their value instead of the attribute name.
+ * Multiline attributes no longer get horribly mangled.
+ * Attribute names can now be any value allowed by HTML5, including crazy things like `^`.
+ * Attribute values can now contain any value allowed by HTML5, including e.g. `>` and `<`.
+
+## 0.5.7
+
+ * Fix: make event handlers attached via `on<event>` more spec-compatible, supporting `return false` and passing the `event` argument. (adrianlang)
+ * Fix: make the getter for `textContent` more accurate, e.g. in cases involving comment nodes or processing instruction nodes. (adrianlang)
+ * Fix: make `<canvas>` behave like a `<div>` when the `node-canvas` package isn't available, instead of crashing. (stepheneb)
+
+## 0.5.6
+
+ * Fix: `on<event>` properties are correctly updated when using `setAttributeNode`, `attributeNode.value =`, `removeAttribute`, and `removeAttributeNode`; before it only worked with `setAttribute`. (adrianlang)
+ * Fix: `HTMLCollection`s now have named properties based on their members' `id` and `name` attributes, e.g. `form.elements.inputId` is now present. (adrianlang)
+
+## 0.5.5
+
+ * Fix: `readOnly` and `selected` properties were not correct when their attribute values were falsy, e.g. `<option selected="">`. (adrianlang)
+
+## 0.5.4
+
+This release, and all future releases, require at least Node.js 0.8.
+
+ * Add: parser can now be set via `jsdom.env` configuration. (xavi-)
+ * Fix: accessing `rowIndex` for table rows that are not part of a table would throw. (medikoo)
+ * Fix: several places in the code accidentally created global variables, or referenced nonexistant values. (xavi-)
+ * Fix: `<img>` elements' `src` properties now evaluate relative to `location.href`, just like `<a>` elements' `href` properties. (brianmaissy)
+
+## 0.5.3
+
+This release is compatible with Node.js 0.6, whereas all future releases will require at least Node.js 0.8.
+
+ * Fix: `getAttributeNS` now returns `null` for attributes that are not present, just like `getAttribute`. (mbostock)
+ * Change: `"request"` dependency pinned to version 2.14 for Node.js 0.6 compatibility.
+
+## 0.5.2
+
+ * Fix: stylesheets with `@-webkit-keyframes` rules were crashing calls to `getComputedStyle`.
+ * Fix: handling of `features` option to `jsdom.env`.
+ * Change: retain the value of the `style` attribute until the element's `style` property is touched. (papandreou)
+
+## 0.5.1
+
+ * Fix: `selectedIndex` now changes correctly in response to `<option>` elements being selected. This makes `<select>` elements actually work like you would want, especially with jQuery. (xcoderzach)
+ * Fix: `checked` works correctly on radio buttons, i.e. only one can be checked and clicking on one does not uncheck it. Previously they worked just like checkboxes. (xcoderzach)
+ * Fix: `click()` on `<input>` elements now fires a click event. (xcoderzach)
+
+## 0.5.0
+
+ * Fix: Make `contextify` a non-optional dependency. jsdom never worked without it, really, so this just caused confusion.
+
+## 0.4.2
+
+ * Fix: `selected` now returns true for the first `<option>` in a `<select>` if nothing is explicitly set.
+ * Fix: tweaks to accuracy and speed of the `querySelectorAll` implementation.
+
+## 0.4.1 (hotfix)
+
+ * Fix: crashes when loading HTML files with `<a>` tags with no `href` attribute. (eleith)
+
+## 0.4.0
+
+ * Fix: `getAttribute` now returns `null` for attributes that are not present, as per DOM4 (but in contradiction to DOM1 through DOM3).
+ * Fix: static `NodeList`-returning methods (such as `querySelectorAll`) now return a real `NodeList` instance.
+ * Change: `NodeList`s no longer expose nonstandard properties to the world, like `toArray`, without first prefixing them with an underscore.
+ * Change: `NodeList`s no longer inconsistently have array methods. Previously, live node lists would have `indexOf`, while static node lists would have them all. Now, they have no array methods at all, as is correct per the specification.
+
+## 0.3.4
+
+ * Fix: stylesheets with `@media` rules were crashing calls to `getComputedStyle`, e.g. those in jQuery's initialization.
+
+## 0.3.3
+
+ * Fix: make `document.write` calls insert new elements correctly. (johanoverip, kblomquist).
+ * Fix: `<input>` tags with no `type` attribute now return a default value of `"text"` when calling `inputEl.getAttribute("type")`.
+
+## 0.3.2
+
+ * Fix: stylesheets with "joining" rules (i.e. those containing comma-separated selectors) now apply when using `getComputedStyle`. (chad3814, godmar)
+ * Add: support for running the tests using @aredridel's [html5](https://npmjs.org/package/html5) parser, as a prelude toward maybe eventually making this the default and fixing various parsing bugs.
+
+## 0.3.1 (hotfix)
+
+ * Fix: crashes when invalid selectors were present in stylesheets.
+
+## 0.3.0
+
+ * Fix: a real `querySelector` implementation, courtesy of the nwmatcher project, solves many outstanding `querySelector` bugs.
+ * Add: `matchesSelector`, again via nwmatcher.
+ * Add: support for styles coming from `<style>` and `<link rel="stylesheet">` elements being applied to the results of `window.getComputedStyle`. (chad3814)
+ * Add: basic implementation of `focus()` and `blur()` methods on appropriate elements. More work remains.
+ * Fix: script filenames containing spaces will now work when passed to `jsdom.env`. (TomNomNom)
+ * Fix: elements with IDs `toString`, `hasOwnProperty`, etc. could cause lots of problems.
+ * Change: A window's `load` event always fires asynchronously now, even if no external resources are necessary.
+ * Change: turning off mutation events is not supported, since doing so breaks external-resource fetching.
+
+## 0.2.19
+
+ * Fix: URL resolution was broken on pages that included `href`-less `<base>` tags.
+ * Fix: avoid putting `attr` in the global scope when using node-canvas. (starsquare)
+ * Add: New `SkipExternalResources` feature accepts a regular expression. (fgalassi)
+
+## 0.2.18
+
+ * Un-revert: cssstyle has fixed its memory problems, so we get back accurate `cssText` and `style` properties again.
+
+## 0.2.17 (hotfix)
+
+ * Revert: had to revert the use of the cssstyle package. `cssText` and `style` properties are no longer as accurate.
+ * Fix: cssstyle was causing out-of-memory errors on some larger real-world pages, e.g. reddit.com.
+
+## 0.2.16
+ * Update: Sizzle version updated to circa September 2012.
+ * Fix: when setting a text node's value to a falsy value, convert it to a string instead of coercing it to `""`.
+ * Fix: Use the cssstyle package for `CSSStyleDeclaration`, giving much more accurate `cssText` and `style` properties on all elements. (chad3814)
+ * Fix: the `checked` property on checkboxes and radiobuttons now reflects the attribute correctly.
+ * Fix: `HTMLOptionElement`'s `text` property should return the option's text, not its value.
+ * Fix: make the `name` property only exist on certain specific tags, and accurately reflect the corresponding `name` attribute.
+ * Fix: don't format `outerHTML` (especially important for `<pre>` elements).
+ * Fix: remove the `value` property from `Text` instances (e.g. text nodes).
+ * Fix: don't break in the presence of a `String.prototype.normalize` method, like that of sugar.js.
+ * Fix: include level3/xpath correctly.
+ * Fix: many more tests passing, especially related to file:/// URLs on Windows. Tests can now be run with `npm test`.
+
+## 0.2.15
+ * Fix: make sure that doctypes don't get set as the documentElement (Aria Stewart)
+ * Add: HTTP proxy support for jsdom.env (Eugene Ware)
+ * Add: .hostname and .pathname properties to Anchor elements to comply with WHATWG standard (Avi Deitcher)
+ * Fix: Only decode HTML entities in text when not inside a `<script>` or `<style>` tag. (Andreas Lind Petersen)
+ * Fix: HTMLSelectElement single selection implemented its type incorrectly as 'select' instead of 'select-one' (John Roberts)
+
+## 0.2.14
+ * Fix: when serializing single tags use ' />' instead of '/>' (kapouer)
+ * Fix: support for contextify simulation using vm.runInContext (trodrigues)
+ * Fix: allow jsdom.env's config.html to handle file paths which contain spaces (shinuza)
+ * Fix: Isolate QuerySelector from prototype (Nao Iizuka)
+ * Add: setting textContent to '' or clears children (Jason Davies)
+ * Fix: jsdom.env swallows exceptions that occur in the callback (Xavi)
+
+## 0.2.13
+ * Fix: remove unused style property which was causing explosions in 0.2.12 and node 0.4.7
+
+## 0.2.12
+ * Fix: do not include gmon.out/v8.log/tests in npm distribution
+
+## 0.2.11
+ * Add: allow non-unique element ids (Avi Deitcher)
+ * Fix: make contexify an optional dependency (Isaac Schlueter)
+ * Add: scripts injected by jsdom are now marked with a 'jsdom' class for serialization's sake (Peter Lyons)
+ * Fix: definition for ldquo entity (Andrew Morton)
+ * Fix: access NamedNodeMap items via property (Brian McDaniel)
+ * Add: upgrade sizzle from 1.0 to [fe2f6181](https://github.com/jquery/sizzle/commit/fe2f618106bb76857b229113d6d11653707d0b22) which is roughly 1.5.1
+ * Add: documentation now includes `jsdom.level(x, 'feature')`
+ * Fix: make `toArray` and `item` on `NodeList` objects non-enumerable properties
+ * Add: a reference to `window.close` in the readme
+ * Fix: Major performance boost (Felix Gnass)
+ * Fix: Using querySelector `:not()` throws a `ReferenceError` (Felix Gnass)
+
+## 0.2.10
+ * Fix: problems with lax dependency versions
+ * Fix: CSSOM constructors are hung off of the dom (Brian McDaniel)
+ * Fix: move away from deprecated 'sys' module
+ * Fix: attribute event handlers on bubbling path aren't called (Brian McDaniel)
+ * Fix: setting textarea.value to markup should not be parsed (Andreas Lind Petersen)
+ * Fix: content of script tags should not be escaped (Ken Sternberg)
+ * Fix: DocumentFeatures for iframes with no src attribute. (Brian McDaniel) Closes #355
+ * Fix: 'trigger' to 'raise' to be a bit more descriptive
+ * Fix: When `ProcessExternalResources['script']` is disabled, do _not_ run inline event handlers. #355
+ * Add: verbose flag to test runner (to show tests as they are running and finishing)
+
+## 0.2.9
+ * Fix: ensure features are properly reset after a jsdom.env invocation. Closes #239
+ * Fix: ReferenceError in the scanForImportRules helper function
+ * Fix: bug in appendHtmlToElement with HTML5 parser (Brian McDaniel)
+ * Add: jsonp support (lheiskan)
+ * Fix: for setting script element's text property (Brian McDaniel)
+ * Fix: for jsdom.env src bug
+ * Add: test for jsdom.env src bug (multiple done calls)
+ * Fix: NodeList properties should enumerate like arrays (Felix Gnass)
+ * Fix: when downloading a file, include the url.search in file path
+ * Add: test for making a jsonp request with jquery from jsdom window
+ * Add: test case for issue #338
+ * Fix: double load behavior when mixing jsdom.env's `scripts` and `src` properties (cjroebuck)
+
+## 0.2.8 (hotfix)
+ * Fix: inline event handlers are ignored by everything except for the javascript context
+
+## 0.2.7 (hotfix)
+ * Fix stylesheet loading
+
+## 0.2.6
+ * Add: support for window.location.search and document.cookie (Derek Lindahl)
+ * Add: jsdom.env now has a document configuation option which allows users to change the referer of the document (Derek Lindahl)
+ * Fix: allow users to use different jsdom levels in the same process (sinegar)
+ * Fix: removeAttributeNS no longer has a return value (Jason Davies)
+ * Add: support for encoding/decoding all html entities from html4/5 (papandreou)
+ * Add: jsdom.env() accepts the same features object seen in jsdom.jsdom and friends
+
+## 0.2.5
+ * Fix: serialize special characters in Element.innerHTML/Element.attributes like a grade A browser (Jason Priestley)
+ * Fix: ensure Element.getElementById only returns elements that are attached to the document
+ * Fix: ensure an Element's id is updated when changing the nodeValue of the 'id' attribute (Felix Gnass)
+ * Add: stacktrace to error reporter (Josh Marshall)
+ * Fix: events now bubble up to the window (Jason Davies)
+ * Add: initial window.location.hash support (Josh Marshall)
+ * Add: Node#insertBefore should do nothing when both params are the same node (Jason Davies)
+ * Add: fixes for DOMAttrModified mutation events (Felix Gnass)
+
+## 0.2.4
+ * Fix: adding script to invalid/incomplete dom (document.documentElement) now catches the error and passes it in the `.env` callback (Gregory Tomlinson)
+ * Cleanup: trigger and html tests
+ * Add: support for inline event handlers (ie: `<div onclick='some.horrible.string()'>`) (Brian McDaniel)
+ * Fix: script loading over https (Brian McDaniel) #280
+ * Add: using style.setProperty updates the style attribute (Jimmy Mabey).
+ * Add: invalid markup is reported as an error and attached to the associated element and document
+ * Fix: crash when setChild() failes to create new DOM element (John Hurliman)
+ * Added test for issue #287.
+ * Added support for inline event handlers.
+ * Moved frame tests to test/window/frame.js and cleaned up formatting.
+ * Moved script execution tests to test/window/script.js.
+ * Fix a crash when setChild() fails to create a new DOM element
+ * Override CSSOM to update style attribute
+
+## 0.2.3
+ * Fix: segfault due to window being garbage collected prematurely
+    NOTE: you must manually close the window to free memory (window.close())
+
+## 0.2.2
+ * Switch to Contextify to manage the window's script execution.
+ * Fix: allow nodelists to have a length of 0 and toArray to return an empty array
+ * Fix: style serialization; issues #230 and #259
+ * Fix: Incomplete DOCTYPE causes JavaScript error
+ * Fix: indentation, removed outdated debug code and trailing whitespace.
+ * Prevent JavaScript error when parsing incomplete `<!DOCTYPE>`. Closes #259.
+ * Adding a test from brianmcd that ensures that setTimeout callbacks execute in the context of the window
+ * Fixes issue 250: make `document.parentWindow === window` work
+ * Added test to ensure that timer callbacks execute in the window context.
+ * Fixes 2 issues in ResourceQueue
+ * Make frame/iframe load/process scripts if the parent has the features enabled
+
+## 0.2.1
+ * Javascript execution fixes [#248, #163, #179]
+ * XPath (Yonathan and Daniel Cassidy)
+ * Start of cssom integration (Yonathan)
+ * Conversion of tests to nodeunit! (Martin Davis)
+ * Added sizzle tests, only failing 3/15
+ * Set the title node's textContent rather than its innerHTML #242.  (Andreas Lind Petersen)
+ * The textContent getter now walks the DOM and extract the text properly. (Andreas Lind Petersen)
+ * Empty scripts won't cause jsdom.env to hang #172 (Karuna Sagar)
+ * Every document has either a body or a frameset #82. (Karuna Sagar)
+ * Added the ability to grab a level by string + feature. ie: jsdom.level(2, 'html') (Aria Stewart)
+ * Cleaned up htmlencoding and fixed character (de)entification #147, #177 (Andreas Lind Petersen)
+ * htmlencoding.HTMLDecode: Fixed decoding of `<`, `>`, `&`, and `'`. Closes #147 and #177.
+ * Require dom level as a string or object. (Aria Stewart)
+ * JS errors ar triggered on the script element, not document. (Yonathan)
+ * Added configuration property 'headers' for HTTP request headers. (antonj)
+ * Attr.specified is readonly - Karuna Sagar
+ * Removed return value from setAttributeNS() #207 (Karuna Sagar)
+ * Pass the correct script filename to runInContext. (robin)
+ * Add http referrer support for the download() function. (Robin)
+ * First attempt at fixing the horrible memory leak via window.stopTimers() (d-ash)
+ * Use vm instead of evals binding (d-ash)
+ * Add a way to set the encoding of the jsdom.env html request.
+ * Fixed various typos/lint problems (d-ash)
+ * The first parameter download is now the object returned by URL.parse(). (Robin)
+ * Fixed serialization of elements with a style attribute.
+ * Added src config option to jsdom.env() (Jerry Sievert)
+ * Removed dead code from getNamedItemNS() (Karuna Sagar)
+ * Changes to language/javascript so jsdom would work on v0.5.0-pre (Gord Tanner)
+ * Correct spelling of "Hierarchy request error" (Daniel Cassidy)
+ * Node and Exception type constants are available in all levels. (Daniel Cassidy)
+ * Use \n instead of \r\n during serialization
+ * Fixed auto-insertion of body/html tags  (Adrian Makowski)
+ * Adopt unowned nodes when added to the tree. (Aria Stewart)
+ * Fix the selected and defaultSelected fields of `option` element. - Yonathan
+ * Fix: EventTarget.getListeners() now returns a shallow copy so that listeners can be safely removed while an event is being dispatched. (Felix Gnass)
+ * Added removeEventListener() to DOMWindow (Felix Gnass)
+ * Added the ability to pre-load scripts for jsdom.env() (Jerry Sievert)
+ * Mutation event tests/fixes (Felix Gnass)
+ * Changed HTML serialization code to (optionally) pretty print while traversing the tree instead of doing a regexp-based postprocessing. (Andreas Lind Petersen)
+ * Relative and absolute urls now work as expected
+ * setNamedItem no longer sets Node.parentNode #153 (Karuna Sagar)
+ * Added missing semicolon after entity name - Felix Gnass
+ * Added NodeList#indexOf implementation/tests (Karuna Sagar)
+ * resourceLoader.download now works correctly with https and redirects (waslogic)
+ * Scheme-less URLs default to the current protocol #87 (Alexander Flatter)
+ * Simplification the prevSibling(), appendChild(), insertBefore() and replaceChild() code (Karuna Sagar)
+ * Javascript errors use core.Node.trigger (Alexander Flatter)
+ * Add core.Document.trigger in level1/core and level2/events; Make DOMWindow.console use it (Alexander Flatter)
+ * Resource resolver fixes (Alexander Flatter)
+ * Fix serialization of doctypes with new lines #148 (Karuna Sagar)
+ * Child nodes are calculated immediately instead of after .length is called #169, #171, #176 (Karuna Sagar)
diff --git a/Contributing.md b/Contributing.md
new file mode 100644
index 0000000..13c2ddb
--- /dev/null
+++ b/Contributing.md
@@ -0,0 +1,54 @@
+## Mission
+
+jsdom is, as said in our tagline, “A JavaScript implementation of the W3C DOM.” Anything that helps us be better at that is welcome.
+
+## Status
+
+We're pretty happy with our DOM2 implementation, modulo bugs. DOM3 is nowhere near complete, and DOM4 is almost nonexistant.
+
+## Existing Tests
+
+The DOM, thankfully, has lots of tests already out there. Those already included in the repository are of two types:
+
+* Auto-generated or adapted from existing W3C tests.
+* Written by contributors to plug gaps in the W3C tests.
+
+Of these, of course, the first is preferable. When we find gaps, we usually add the tests at the bottom of the relevant auto-generated test suite, e.g. in `test/level2/html.js`.
+
+The current test compliance is tracked [in the README](https://github.com/tmpvar/jsdom#test-compliance).
+
+## Contributing
+
+When contributing, the first question you should ask is:
+
+**Can I exhibit how the browsers differ from what jsdom is doing?**
+
+If you can, then you've almost certainly found a bug in or missing feature of jsdom, and we'd love to have your contribution. In that case, move on to:
+
+**What W3C spec covers this potential contribution?**
+
+Some likely ones include:
+
+* [DOM1](http://www.w3.org/TR/1998/REC-DOM-Level-1-19981001/cover.html)
+* [DOM2 Core](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/), [DOM2 HTML](http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/), [DOM2 Events](http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/), [DOM2 Style](http://www.w3.org/TR/2000/REC-DOM-Level-2-Style-20001113/)
+* [DOM3 Core](http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/), [DOM3 Events](http://www.w3.org/TR/DOM-Level-3-Events/)
+* [DOM4](http://www.w3.org/TR/2012/WD-dom-20120405/)
+* [DOM Living Standard](http://dom.spec.whatwg.org/)
+* [Other W3C Dom Specs](http://www.w3.org/standards/techs/dom)
+* [HTML5](http://www.w3.org/TR/html5/)
+
+Once you have those nailed down, you'll want to ask:
+
+**Where can I get a W3C test for this functionality?**
+
+We already have all the DOM1 and DOM2 tests. We even have some DOM3 ones, although sadly they are currently disabled, due to our DOM3 support not being complete. (Maybe you could help break them out into complete vs. work in progress?)
+
+DOM4 has no officially-finished test suite, but many tests are found [on w3c-test.org](http://w3c-test.org/). Check in a few different directories, e.g. [html](http://w3c-test.org/html/tests/) and [webapps](http://w3c-test.org/webapps/), or perhaps browse through the nice [test runner interface](http://w3c-test.org/framework/app/suite). If you really can't find anything, you can always ask [public-webapps-testsuite at w3.org](mailto:public-webapps-testsuite at w3.org), [like I did](http://list [...]
+
+More recently there's been an attempt to consolidate tests for HTML5 in the [w3c/web-platform-tests](https://github.com/w3c/web-platform-tests) repository, so you can try to find things there. Many of the directories are empty, however; it seems that's still largely a work in progress.
+
+If there is no W3C test covering the functionality you're after, then you can write your own, placing it in the appropriate level. But in this case you'll probably want to alert the authors of the relevant test suite that they missed something!
+
+## Issues
+
+Finally, we have [an active and full issue tracker](https://github.com/tmpvar/jsdom/issues) that we'd love you to help with. Go find something broken, and fix it!
diff --git a/README.md b/README.md
index 9e10483..78a3bf6 100644
--- a/README.md
+++ b/README.md
@@ -1,282 +1,349 @@
 # jsdom
 
-A javascript implementation of the W3C DOM.
+A JavaScript implementation of the W3C DOM.
 
 ## Install
 
-    npm install jsdom
+```bash
+$ npm install jsdom
+```
 
-or
-
-    git clone http://github.com/tmpvar/jsdom.git
-    cd jsdom
-    npm link
+If this gives you trouble with errors about installing Contextify, especially on Windows, see [below](#contextify).
 
 ## Human contact
 
-see: [mailing list][]
+see: [mailing list](http://groups.google.com/group/jsdom)
 
-  [mailing list]: http://groups.google.com/group/jsdom
+## Easymode
 
+Bootstrapping a DOM is generally a difficult process involving many error prone steps. We didn't want jsdom to fall into the same trap and that is why a new method, `jsdom.env()`, has been added in jsdom 0.2.0 which should make everyone's lives easier.
 
+You can use it with a URL
 
+```js
+// Count all of the links from the Node.js build page
+var jsdom = require("jsdom");
 
-## Easymode
+jsdom.env(
+  "http://nodejs.org/dist/",
+  ["http://code.jquery.com/jquery.js"],
+  function (errors, window) {
+    console.log("there have been", window.$("a").length, "nodejs releases!");
+  }
+);
+```
 
-Bootstrapping a DOM is generally a difficult process involving many error prone steps. We didn't want jsdom to fall into the same trap and that is why a new method, `jsdom.env()`, has been added in jsdom 0.2.0 which should make everyone's lives easier.
+or with raw HTML
 
-with URL
+```js
+// Run some jQuery on a html fragment
+var jsdom = require("jsdom");
 
-    // Count all of the links from the nodejs build page
-    var jsdom = require("jsdom");
+jsdom.env(
+  '<p><a class="the-link" href="https://github.com/tmpvar/jsdom">jsdom\'s Homepage</a></p>',
+  ["http://code.jquery.com/jquery.js"],
+  function (errors, window) {
+    console.log("contents of a.the-link:", window.$("a.the-link").text());
+  }
+);
+```
 
-    jsdom.env("http://nodejs.org/dist/", [
-      'http://code.jquery.com/jquery-1.5.min.js'
-    ],
-    function(errors, window) {
-      console.log("there have been", window.$("a").length, "nodejs releases!");
+or with a configuration object
+
+```js
+// Print all of the news items on hackernews
+var jsdom = require("jsdom");
+
+jsdom.env({
+  url: "http://news.ycombinator.com/",
+  scripts: ["http://code.jquery.com/jquery.js"],
+  done: function (errors, window) {
+    var $ = window.$;
+    console.log("HN Links");
+    $("td.title:not(:last) a").each(function() {
+      console.log(" -", $(this).text());
     });
+  }
+});
+```
+
+or with raw JavaScript source
+
+```js
+// Print all of the news items on hackernews
+var jsdom = require("jsdom");
+var fs = require("fs");
+var jquery = fs.readFileSync("./jquery.js", "utf-8");
+
+jsdom.env({
+  url: "http://news.ycombinator.com/",
+  src: [jquery],
+  done: function (errors, window) {
+    var $ = window.$;
+    console.log("HN Links");
+    $("td.title:not(:last) a").each(function () {
+      console.log(" -", $(this).text());
+    });
+  }
+});
+```
+
+### How it works
+`jsdom.env` is built for ease of use, which is rare in the world of the DOM! Since the web has some absolutely horrible JavaScript on it, as of jsdom 0.2.0 `jsdom.env` will not process external resources (scripts, images, etc).  If you want to process the JavaScript use one of the methods below (`jsdom.jsdom` or `jsdom.jQueryify`)
+
+```js
+jsdom.env(string, [scripts], [config], callback);
+```
+
+The arguments are:
+
+- `string`: may be a URL, file name, or HTML fragment
+- `scripts`: a string or array of strings, containing file names or URLs that will be inserted as `<script>` tags
+- `config`: see below
+- `callback`: takes two arguments
+  - `error`: either an `Error` object if something failed initializing the window, or an array of error messages from the DOM if there were script errors
+  - `window`: a brand new `window`
+
+_Example:_
+
+```js
+jsdom.env(html, function (errors, window) {
+  // free memory associated with the window
+  window.close();
+});
+```
+
+If you would like to specify a configuration object only:
+
+```js
+jsdom.env(config);
+```
+
+- `config.html`: a HTML fragment
+- `config.file`: a file which jsdom will load HTML from; the resulting window's `location.href` will be a `file://` URL.
+- `config.url`: sets the resulting window's `location.href`; if `config.html` and `config.file` are not provided, jsdom will load HTML from this URL.
+- `config.scripts`: see `scripts` above.
+- `config.src`: an array of JavaScript strings that will be evaluated against the resulting document. Similar to `scripts`, but it accepts JavaScript instead of paths/URLs.
+- `config.done`: see `callback` above.
+- `config.document`:
+  - `referer`: the new document will have this referer.
+  - `cookie`: manually set a cookie value, e.g. `'key=value; expires=Wed, Sep 21 2011 12:00:00 GMT; path=/'`.
+  - `cookieDomain`: a cookie domain for the manually set cookie; defaults to `127.0.0.1`.
+- `config.features` : see `Flexibility` section below. **Note**: the default feature set for jsdom.env does _not_ include fetching remote JavaScript and executing it. This is something that you will need to **carefully** enable yourself.
+
+Note that `config.done` is required, as is one of `config.html`, `config.file`, or `config.url`.
 
-or with raw html
+## For the hardcore
 
-    // Run some jQuery on a html fragment
-    var jsdom = require('jsdom');
+If you want to spawn a document/window and specify all sorts of options this is the section for you. This section covers the `jsdom.jsdom` method:
 
-    jsdom.env('<p><a class="the-link" href="http://jsdom.org>JSDOM\'s Homepage</a></p>', [
-      'http://code.jquery.com/jquery-1.5.min.js'
-    ],
-    function(errors, window) {
-      console.log("contents of a.the-link:", window.$("a.the-link").text());
-    });
+```js
+var jsdom = require("jsdom").jsdom;
+var doc = jsdom(markup, level, options);
+var window = doc.parentWindow;
+```
 
+- `markup` is an HTML/XML document to be parsed. You can also pass `null` or an undefined value to get a basic document with empty `<head>` and `<body>` tags. Document fragments are also supported (including `""`), and will behave as sanely as possible (e.g. the resulting document will lack the `head`, `body` and `documentElement` properties if the corresponding elements aren't included).
 
-or with a configuration object
+- `level` is `null` (which means level3) by default, but you can pass another level if you'd like.
 
-    // Print all of the news items on hackernews
-    var jsdom = require('jsdom');
-
-    jsdom.env({
-      html: 'http://news.ycombinator.com/',
-      scripts: [
-        'http://code.jquery.com/jquery-1.5.min.js'
-      ],
-      done: function(errors, window) {
-        var $ = window.$;
-        console.log('HN Links');
-        $('td.title:not(:last) a').each(function() {
-          console.log(' -', $(this).text());
-        });
-      }
-    });
+  ```js
+  var jsdom = require("jsdom");
+  var doc = jsdom.jsdom("<html><body></body></html>", jsdom.level(1, "core"));
+  ```
 
-or with raw javascript source
-
-    // Print all of the news items on hackernews
-    var jsdom  = require('jsdom');
-    var fs     = require('fs');
-    var jquery = fs.readFileSync("./jquery-1.6.2.min.js").toString();
-
-    jsdom.env({
-      html: 'http://news.ycombinator.com/',
-      src: [
-        jquery
-      ],
-      done: function(errors, window) {
-        var $ = window.$;
-        console.log('HN Links');
-        $('td.title:not(:last) a').each(function() {
-          console.log(' -', $(this).text());
-        });
+- `options` see the **Flexibility** section below.
+
+### Flexibility
+
+One of the goals of jsdom is to be as minimal and light as possible. This section details how someone can change the behavior of `Document`s on the fly.  These features are baked into the `DOMImplementation` that every `Document` has, and may be tweaked in two ways:
+
+1. When you create a new `Document` using the jsdom builder (`require("jsdom").jsdom()`)
+
+  ```js
+  var jsdom = require("jsdom").jsdom;
+  var doc = jsdom("<html><body></body></html>", null, {
+      features: {
+        FetchExternalResources : ["img"]
       }
-    });
+  });
+  ```
 
-### How it works
-  `jsdom.env` is built for ease of use, which is rare in the world of the DOM!  Since the web has some absolutely horrible javascript on it, as of jsdom 0.2.0 `jsdom.env` will not process external resources (scripts, images, etc).  If you want to process the javascript use one of the methods below (`jsdom.jsdom` or `jsdom.jQueryify`)
+  Do note, that this will only affect the document that is currently being created. All other documents will use the defaults specified below (see: Default Features).
 
-    jsdom.env(html, [scripts], [config], callback)
+2. Before creating any documents, you can modify the defaults for all future documents:
 
-  - `html` (**required**)
-    May be a url, html fragment, or file
+  ```js
+  require("jsdom").defaultDocumentFeatures = {
+      FetchExternalResources: ["script"],
+      ProcessExternalResources: false
+  };
+  ```
 
-  - `scripts` (**optional**)
-    May contain files or urls
+#### Default Features
 
-  - `callback` (**required**)
-    Takes 2 arguments:
-    - `errors` : array of errors
-    - `window` : a brand new window
+Default features are extremely important for jsdom as they lower the configuration requirement and present developers a set of consistent default behaviors. The following sections detail the available features, their defaults, and the values that jsdom uses.
 
-    _example:_  
 
-        jsdom.env(html, function(`errors`, `window`) {
-          // free memory associated with the window
-          window.close();
-        });
+`FetchExternalResources`
 
-If you would like to specify a configuration object
+- _Default_: `["script"]`
+- _Allowed_: `["script", "img", "css", "frame", "iframe", "link"]` or `false`
 
-    jsdom.env({ /* config */ })
+Enables/disables fetching files over the file system/HTTP.
 
-  - config.html     : see `html` above
-  - config.scripts  : see `scripts` above
-  - config.src      : An array of javascript strings that will be evaluated against the resulting document.  Similar to `scripts`, but it accepts javascript instead of paths/urls.
-  - config.done     : see `callback` above
-  - config.document :
-   - referer : the new document will have this referer
-   - cookie : manually set a cookie value i.e. `'key=value; expires=Wed, Sep 21 2011 12:00:00 GMT; path=/'`
-  - config.features : see `Flexibility` section below. **Note**: the default feature set for jsdom.env does _not_ include fetching remote javascript and executing it.  This is something that you will need to **carefully** enable yourself.
+`ProcessExternalResources`
 
-## For the hardcore
+- _Default_: `["script"]`
+- _Allowed_: `["script"]` or `false`
 
-If you want to spawn a document/window and specify all sorts of options this is the section for you. This section covers the `jsdom.jsdom` method:
+Disabling this will disable script execution (currently only JavaScript).
 
-    var jsdom  = require("jsdom").jsdom,
-        doc    = jsdom(markup, level, options),
-        window = doc.createWindow();
+`SkipExternalResources`
 
- - `markup` is an html/xml document to be parsed. You can also pass `null` or an undefined value to get a basic document with empty head and body tags. Document fragments are also supported (including `""`), and will behave as sanely as possible (eg. the resulting document will lack the `head`, `body` and `documentElement` properties if the corresponding elements aren't included).
- - `level` is `null` (which means level3) by default, but you can pass another level if you'd like.
+- _Default_: `false`
+- _Allowed_: `/url to be skipped/` or `false`
+- _Example_: `/http:\/\/example.org/js/bad\.js/`
 
+Do not download and process resources with url matching a regular expression.
 
-        var jsdom = require('jsdom'),
-            doc   = jsdom.jsdom('<html><body></body></html>', jsdom.level(1, 'core'))
+### Canvas
 
- - `options` see the **Flexibility** section below
+jsdom includes support for using the [canvas](https://npmjs.org/package/canvas) package to extend any `<canvas>` elements with the canvas API. To make this work, you need to include canvas as a dependency in your project, as a peer of jsdom. If jsdom can find the canvas package, it will use it, but if it's not present, then `<canvas>` elements will behave like `<div>`s.
 
-### Flexibility
+## More Examples
 
-One of the goals of jsdom is to be as minimal and light as possible. This section details how
-someone can change the behavior of `Document`s on the fly.  These features are baked into
-the `DOMImplementation` that every `Document` has, and may be tweaked in two ways:
+### Creating a document
 
-1. When you create a new `Document` using the jsdom builder (`require('jsdom').jsdom()`)
+```js
+var jsdom = require("jsdom");
+var doc = new (jsdom.level(1, "core").Document)();
 
-        var jsdom = require('jsdom').jsdom,
-            doc   = jsdom("<html><body></body></html>", null, {
-              features: {
-                FetchExternalResources : ['img']
-              }
-            });
+console.log(doc.nodeName); // outputs: #document
+```
 
- Do note, that this will only affect the document that is currently being created.  All other documents
-will use the defaults specified below (see: Default Features)
+### Creating a browser-like BOM/DOM/Window
 
-2. Previous to creating any documents you can modify the defaults for all future documents
+```js
+var jsdom = require("jsdom").jsdom;
+var document = jsdom("<html><head></head><body>hello world</body></html>");
+var window = document.parentWindow;
 
-        require('jsdom').defaultDocumentFeatures = {
-          FetchExternalResources   : ['script'],
-          ProcessExternalResources : false,
-          MutationEvents           : false,
-          QuerySelector            : false
-        }
+console.log(window.document.innerHTML);
+// output: "<html><head></head><body>hello world</body></html>"
 
+console.log(window.innerWidth);
+// output: 1024
 
+console.log(typeof window.document.getElementsByClassName);
+// outputs: function
+```
 
-#### Default Features
+### jQueryify
 
-Default features are extremely important for jsdom as they lower the configuration requirement and present developers a set of consistent default behaviors. The following sections detail the available features, their defaults, and the values that jsdom uses.
+```js
+var jsdom = require("jsdom");
+var window = jsdom.jsdom().parentWindow;
 
+jsdom.jQueryify(window, "http://code.jquery.com/jquery.js", function () {
+  window.$("body").append('<div class="testing">Hello World, It works</div>');
 
-`FetchExternalResources`
-_Default_: ['script']
-_Allowed_: ['script', 'img', 'css', 'frame', 'link'] or false
+  console.log(window.$(".testing").text());
+});
+```
 
-Enables/Disables fetching files over the filesystem/http
+### Passing objects to scripts inside the page
 
-`ProcessExternalResources`
-_default_: ['script']
-_allowed_: ['script'] or false
+```js
+var jsdom = require("jsdom").jsdom;
+var window = jsdom().parentWindow;
 
-Disabling this will disable script execution (currently only javascript).
+window.__myObject = { foo: "bar" };
 
-`MutationEvents`
-_default_: '2.0'
-_allowed_ : '2.0' or false
+var scriptEl = window.document.createElement("script");
+scriptEl.src = "anotherScript.js";
+window.document.body.appendChild(scriptEl);
 
-Initially enabled to be up to spec. Disable this if you do not need mutation events and want jsdom to be a bit more efficient.
+// anotherScript.js will have the ability to read `window.__myObject`, even
+// though it originated in Node!
+```
 
-**Note**: `ProcessExternalResources` requires this to be enabled
+## Test Compliance:
 
-`QuerySelector`
-_default_ : false
-_allowed_ : true
+```
+ level1/core         535/535      100%
+ level1/html         238/238      100%
+ level1/svg          527/527      100%
+ level2/core         283/283      100%
+ level2/html         708/708      100%
+ level2/style          15/15      100%
+ level2/extra            4/4      100%
+ level2/events         24/24      100%
+ level3/xpath          93/93      100%
+ window/index            7/7      100%
+ window/history          5/5      100%
+ window/script         10/10      100%
+ window/console          2/2      100%
+ window/frame          16/16      100%
+ sizzle/index          14/14      100%
+ jsdom/index           76/76      100%
+ jsdom/parsing         11/11      100%
+ jsdom/env             25/25      100%
+ jsonp/jsonp             1/1      100%
+ browser/css             1/1      100%
+ browser/index         34/34      100%
+---------------------------------------
+TOTALS: 0/2629 failed; 100% success
+```
 
-This feature is backed by [sizzle][] but currently causes problems with some libraries.  Enable this if you want `document.querySelector` and friends, but be aware that many libraries feature detect for this, and it may cause you a bit of trouble.
+### Running the tests
 
-[sizzle]:http://sizzlejs.com/
+First you'll want to `npm install`. To run all the tests, use `npm test`, which just calls `node test/runner`.
 
-# More Examples
+Using `test/runner` directly, you can slice and dice which tests your want to run from different levels. Usage is as follows:
 
-## Creating a document-less window
+```
+test/runner --help
+Run the jsdom test suite
 
-    var jsdom  = require("jsdom"),
-        window = jsdom.createWindow();
+Options:
+-s, --suites     suites that you want to run. ie: -s level1/core,1/html,html [string]
+-f, --fail-fast  stop on the first failed test
+-h, --help       show the help
+-t, --tests      choose the test cases to run. ie: -t jquery
+```
 
-    console.log(window.document);
-    // output: undefined
+## Contextify
 
-## Creating a document
-    var jsdom = require("jsdom"),
-        doc   = new (jsdom.level(1, 'core').Document)();
-    console.log(doc.nodeName);
-    // outputs: #document
+[Contextify](https://npmjs.org/package/contextify) is a dependency of jsdom, used for running `<script>` tags within the
+page. In other words, it allows jsdom, which is run in Node.js, to run strings of JavaScript in an isolated environment
+that pretends to be a browser environment instead of a server. You can see how this is an important feature.
 
-## Creating a browser-like BOM/DOM/Window
+Unfortunately, doing this kind of magic requires C++. And in Node.js, using C++ from JavaScript means using "native
+modules." Native modules are compiled at installation time so that they work precisely for your machine; that is, you
+don't download a contextify binary from npm, but instead build one locally after downloading the source from npm.
 
-    var jsdom    = require("./lib/jsdom").jsdom,
-        document = jsdom("<html><head></head><body>hello world</body></html>"),
-        window   = document.createWindow();
 
-    console.log(window.document.innerHTML);
-    // output: '<html><head></head><body>hello world</body></html>'
+Unfortunately, getting C++ compiled within npm's installation system can be tricky, especially for Windows users. Thus,
+one of the most common problems with jsdom is trying to use it without the proper compilation tools installed.
+Here's what you need to compile Contextify, and thus to install jsdom:
 
-    console.log(window.innerWidth)
-    // output: 1024
+### Windows
 
-    console.log(typeof window.document.getElementsByClassName);
-    // outputs: function
+* A recent copy of the *x86* version of [Node.js for Windows](http://nodejs.org/download/), *not* the x64 version.
+* A copy of [Visual C++ 2010 Express](http://www.microsoft.com/visualstudio/eng/downloads#d-2010-express).
+* A copy of [Python 2.7](http://www.python.org/download/), installed in the default location of `C:\Python27`.
 
+There are some slight modifications to this that can work; for example full versions of Visual Studio usually work, and
+sometimes you can even get an x64 version of Node.js working too. But it's tricky, so start with the basics!
 
-## jQueryify
+### Mac
 
-    var jsdom  = require("jsdom"),
-        window = jsdom.jsdom().createWindow();
+* XCode needs to be installed
+* "Command line tools for XCode" need to be installed
+* Launch XCode once to accept the license, etc. and ensure it's properly installed
 
-    jsdom.jQueryify(window, 'http://code.jquery.com/jquery-1.4.2.min.js' , function() {
-      window.$('body').append('<div class="testing">Hello World, It works</div>');
-      console.log(window.$('.testing').text());
-    });
+### Linux
 
-# Test Compliance:
-
-     level1/core        531/531      100%
-     level1/html        238/238      100%
-     level1/svg         527/527      100%
-     level2/core        283/283      100%
-     level2/html        687/687      100%
-     level2/style           4/4      100%
-     level2/extra           4/4      100%
-     level3/xpath         93/93      100%
-     window/index           5/5      100%
-     window/script          8/8      100%
-     window/frame         14/14      100%
-     sizzle/index         12/15       80%
-     jsdom/index          63/63      100%
-    --------------------------------------
-    TOTALS: 3/2472 failed; 99% success
-    TIME: 16730ms
-
-## Running the tests
-
-First you'll want to `npm install -g nodeunit` then `npm install --dev`
-
-Using `test/runner` you can slice and dice which tests your want to run from different levels. Usage is as follows:
-
-    test/runner --help
-    Run the jsdom test suite
-
-    Options:
-    -s, --suites     suites that you want to run. ie: -s level1/core,1/html,html [string]
-    -f, --fail-fast  stop on the first failed test
-    -h, --help       show the help
-    -t, --tests      choose the test cases to run. ie: -t jquery
\ No newline at end of file
+You'll need various build tools installed, like `make`, Python 2.7, and a compiler toolchain. How to install these will
+be specific to your distro, if you don't already have them.
diff --git a/changelog b/changelog
deleted file mode 100644
index 15ea1bd..0000000
--- a/changelog
+++ /dev/null
@@ -1,154 +0,0 @@
-0.2.13
- * Fix: remove unused style property which was causing explosions in 0.2.12 and node 0.4.7
-
-0.2.12
- * Fix: do not include gmon.out/v8.log/tests in npm distribution
-
-0.2.11
- * Add: allow non-unique element ids (Avi Deitcher)
- * Fix: make contexify an optional dependency (Isaac Schlueter)
- * Add: scripts injected by jsdom are now marked with a 'jsdom' class for serialization's sake (Peter Lyons)
- * Fix: definition for ldquo entity (Andrew Morton)
- * Fix: access NamedNodeMap items via property (Brian McDaniel)
- * Add: upgrade sizzle from 1.0 to (https://github.com/jquery/sizzle/commit/fe2f618106bb76857b229113d6d11653707d0b22) which is roughly 1.5.1
- * Add: documentation now includes `jsdom.level(x, 'feature')`
- * Fix: make toArray and item on NodeList objects non-enumerable properties
- * Add: a reference to `window.close` in the readme
- * Fix: Major performance boost (Felix Gnass)
- * Fix: Using querySelector :not() throws a ReferenceError (Felix Gnass)
-
-0.2.10
- * Fix: problems with lax dependency versions
- * Fix: CSSOM constructors are hung off of the dom (Brian McDaniel)
- * Fix: move away from deprecated 'sys' module
- * Fix: attribute event handlers on bubbling path aren't called (Brian McDaniel)
- * Fix: setting textarea.value to markup should not be parsed (Andreas Lind Petersen)
- * Fix: content of script tags should not be escaped (Ken Sternberg)
- * Fix: DocumentFeatures for iframes with no src attribute. (Brian McDaniel) Closes #355
- * Fix: 'trigger' to 'raise' to be a bit more descriptive
- * Fix: When `ProcessExternalResources['script']` is disabled, do _not_ run inline event handlers. #355
- * Add: verbose flag to test runner (to show tests as they are running and finishing)
-
-0.2.9
- * Fix: ensure features are properly reset after a jsdom.env invocation. Closes #239
- * Fix: ReferenceError in the scanForImportRules helper function
- * Fix: bug in appendHtmlToElement with HTML5 parser (Brian McDaniel)
- * Add: jsonp support (lheiskan)
- * Fix: for setting script element's text property (Brian McDaniel)
- * Fix: for jsdom.env src bug
- * Add: test for jsdom.env src bug (multiple done calls)
- * Fix: NodeList properties should enumerate like arrays (Felix Gnass)
- * Fix: when downloading a file, include the url.search in file path
- * Add: test for making a jsonp request with jquery from jsdom window
- * Add: test case for issue #338
- * Fix: double load behavior when mixing jsdom.env's `scripts` and `src` properties (cjroebuck)
-
-0.2.8 (hotfix)
- * Fix: inline event handlers are ignored by everything except for the javascript context
-
-0.2.7 (hotfix)
- * Fix stylesheet loading
-
-0.2.6
- * Add: support for window.location.search and document.cookie (Derek Lindahl)
- * Add: jsdom.env now has a document configuation option which allows users to change the referer of the document (Derek Lindahl)
- * Fix: allow users to use different jsdom levels in the same process (sinegar)
- * Fix: removeAttributeNS no longer has a return value (Jason Davies)
- * Add: support for encoding/decoding all html entities from html4/5 (papandreou)
- * Add: jsdom.env() accepts the same features object seen in jsdom.jsdom and friends
-
-0.2.5
- * Fix: serialize special characters in Element.innerHTML/Element.attributes like a grade A browser (Jason Priestley)
- * Fix: ensure Element.getElementById only returns elements that are attached to the document
- * Fix: ensure an Element's id is updated when changing the nodeValue of the 'id' attribute (Felix Gnass)
- * Add: stacktrace to error reporter (Josh Marshall)
- * Fix: events now bubble up to the window (Jason Davies)
- * Add: initial window.location.hash support (Josh Marshall)
- * Add: Node#insertBefore should do nothing when both params are the same node (Jason Davies)
- * Add: fixes for DOMAttrModified mutation events (Felix Gnass)
-
-0.2.4
- * Fix: adding script to invalid/incomplete dom (document.documentElement) now catches the error and passes it in the `.env` callback (Gregory Tomlinson)
- * Cleanup: trigger and html tests
- * Add: support for inline event handlers (ie: <div onclick='some.horrible.string()'>) (Brian McDaniel)
- * Fix: script loading over https (Brian McDaniel) #280
- * Add: using style.setProperty updates the style attribute (Jimmy Mabey).
- * Add: invalid markup is reported as an error and attached to the associated element and document
- * Fix: crash when setChild() failes to create new DOM element (John Hurliman)
- * Added test for issue #287.
- * Added support for inline event handlers.
- * Moved frame tests to test/window/frame.js and cleaned up formatting.
- * Moved script execution tests to test/window/script.js.
- * Fix a crash when setChild() fails to create a new DOM element
- * Override CSSOM to update style attribute
-
-0.2.3
- * Fix: segfault due to window being garbage collected prematurely
-    NOTE: you must manually close the window to free memory (window.close())
-
-0.2.2
- * Switch to Contextify to manage the window's script execution.
- * Fix: allow nodelists to have a length of 0 and toArray to return an empty array
- * Fix: style serialization; issues #230 and #259
- * Fix: Incomplete DOCTYPE causes JavaScript error
- * Fix: indentation, removed outdated debug code and trailing whitespace.
- * Prevent JavaScript error when parsing incomplete <!DOCTYPE>. Closes #259.
- * Adding a test from brianmcd that ensures that setTimeout callbacks execute in the context of the window
- * Fixes issue 250: make document.parentWindow===window work
- * Added test to ensure that timer callbacks execute in the window context.
- * Fixes 2 issues in ResourceQueue
- * Make frame/iframe load/process scripts if the parent has the features enabled
-
-0.2.1
- * Javascript execution fixes [#248, #163, #179]
- * XPath (Yonathan and Daniel Cassidy)
- * Start of cssom integration (Yonathan)
- * Conversion of tests to nodeunit! (Martin Davis)
- * Added sizzle tests, only failing 3/15
- * Set the title node's textContent rather than its innerHTML [#242].  (Andreas Lind Petersen)
- * The textContent getter now walks the DOM and extract the text properly. (Andreas Lind Petersen)
- * Empty scripts won't cause jsdom.env to hang [#172] (Karuna Sagar)
- * Every document has either a body or a frameset [#82]. (Karuna Sagar)
- * Added the ability to grab a level by string + feature. ie: jsdom.level(2, 'html') (Aria Stewart)
- * Cleaned up htmlencoding and fixed character (de)entification [#147, #177] (Andreas Lind Petersen)
- * htmlencoding.HTMLDecode: Fixed decoding of `<`, `>`, `&`, and `'`. Closes #147 and #177. ()
- * Require dom level as a string or object. (Aria Stewart)
- * JS errors ar triggered on the script element, not document. (Yonathan)
- * Added configuration property 'headers' for HTTP request headers. (antonj)
- * Attr.specified is readonly - Karuna Sagar
- * Removed return value from setAttributeNS() [#207] (Karuna Sagar)
- * Pass the correct script filename to runInContext. (robin)
- * Add http referrer support for the download() function. (Robin)
- * First attempt at fixing the horrible memory leak via window.stopTimers() (d-ash)
- * Use vm instead of evals binding (d-ash)
- * Add a way to set the encoding of the jsdom.env html request.
- * Fixed various typos/lint problems (d-ash)
- * The first parameter download is now the object returned by URL.parse(). (Robin)
- * Fixed serialization of elements with a style attribute.
- * Added src config option to jsdom.env() (Jerry Sievert)
- * Removed dead code from getNamedItemNS() (Karuna Sagar)
- * Changes to language/javascript so jsdom would work on v0.5.0-pre (Gord Tanner)
- * Correct spelling of "Hierarchy request error" (Daniel Cassidy)
- * Node and Exception type constants are available in all levels. (Daniel Cassidy)
- * Use \n instead of \r\n during serialization
- * Fixed auto-insertion of body/html tags  (Adrian Makowski)
- * Adopt unowned nodes when added to the tree. (Aria Stewart)
- * Fix the selected and defaultSelected fields of `option` element. - Yonathan
- * Fix: EventTarget.getListeners() now returns a shallow copy so that listeners can be safely removed while an event is being dispatched. (Felix Gnass)
- * Added removeEventListener() to DOMWindow (Felix Gnass)
- * Added the ability to pre-load scripts for jsdom.env() (Jerry Sievert)
- * Mutation event tests/fixes (Felix Gnass)
- * Changed HTML serialization code to (optionally) pretty print while traversing the tree instead of doing a regexp-based postprocessing. (Andreas Lind Petersen)
- * Relative and absolute urls now work as expected
- * setNamedItem no longer sets Node.parentNode [#153] (Karuna Sagar)
- * Added missing semicolon after entity name - Felix Gnass
- * Added NodeList#indexOf implementation/tests (Karuna Sagar)
- * resourceLoader.download now works correctly with https and redirects (waslogic)
- * Scheme-less URLs default to the current protocol [#87] (Alexander Flatter)
- * Simplification the prevSibling(), appendChild(), insertBefore() and replaceChild() code (Karuna Sagar)
- * Javascript errors use core.Node.trigger (Alexander Flatter)
- * Add core.Document.trigger in level1/core and level2/events; Make DOMWindow.console use it (Alexander Flatter)
- * Resource resolver fixes (Alexander Flatter)
- * Fix serialization of doctypes with new lines [#148] (Karuna Sagar)
- * Child nodes are calculated immediately instead of after .length is called [#169, #171, #176] (Karuna Sagar)
-
diff --git a/lib/jsdom.js b/lib/jsdom.js
index f4fbac8..0751f7d 100644
--- a/lib/jsdom.js
+++ b/lib/jsdom.js
@@ -1,14 +1,18 @@
-var dom      = exports.dom = require("./jsdom/level3/index").dom,
-    features = require('./jsdom/browser/documentfeatures'),
-    fs       = require("fs"),
-    pkg      = JSON.parse(fs.readFileSync(__dirname + "/../package.json")),
-    request  = require('request'),
-    URL      = require('url');
+var fs = require('fs');
+var path = require('path');
+var URL = require('url');
+var request = require('request');
+var pkg = require('../package.json');
 
+var toFileUrl = require('./jsdom/utils').toFileUrl;
 var style = require('./jsdom/level2/style');
+var features = require('./jsdom/browser/documentfeatures');
+var dom = exports.dom = require('./jsdom/level3/index').dom;
+var createWindow = exports.createWindow = require('./jsdom/browser/index').createWindow;
+
 exports.defaultLevel = dom.level3.html;
-exports.browserAugmentation = require("./jsdom/browser/index").browserAugmentation;
-exports.windowAugmentation = require("./jsdom/browser/index").windowAugmentation;
+exports.browserAugmentation = require('./jsdom/browser/index').browserAugmentation;
+exports.windowAugmentation = require('./jsdom/browser/index').windowAugmentation;
 
 // Proxy feature functions to features module.
 ['availableDocumentFeatures',
@@ -24,22 +28,23 @@ exports.windowAugmentation = require("./jsdom/browser/index").windowAugmentation
 
 exports.debugMode = false;
 
-var createWindow = exports.createWindow = require("./jsdom/browser/index").createWindow;
-
 exports.__defineGetter__('version', function() {
   return pkg.version;
 });
 
 exports.level = function (level, feature) {
-	if(!feature) feature = 'core'
-	return require('./jsdom/level' + level + '/' + feature).dom['level' + level][feature]
-}
+	if(!feature) {
+    feature = 'core';
+  }
+
+	return require('./jsdom/level' + level + '/' + feature).dom['level' + level][feature];
+};
 
 exports.jsdom = function (html, level, options) {
 
   options = options || {};
-  if(typeof level == "string") {
-    level = exports.level(level, 'html')
+  if(typeof level == 'string') {
+    level = exports.level(level, 'html');
   } else {
     level   = level || exports.defaultLevel;
   }
@@ -48,10 +53,11 @@ exports.jsdom = function (html, level, options) {
     options.url = (module.parent.id === 'jsdom') ?
                   module.parent.parent.filename  :
                   module.parent.filename;
-  }
-
-  if (options.features && options.features.QuerySelector) {
-    require("./jsdom/selectors/index").applyQuerySelectorPrototype(level);
+    options.url = options.url.replace(/\\/g, '/');
+    if (options.url[0] !== '/') {
+      options.url = '/' + options.url;
+    }
+    options.url = 'file://' + options.url;
   }
 
   var browser = exports.browserAugmentation(level, options),
@@ -59,6 +65,8 @@ exports.jsdom = function (html, level, options) {
                  new browser.HTMLDocument(options) :
                  new browser.Document(options);
 
+  require('./jsdom/selectors/index').applyQuerySelectorPrototype(level);
+
   features.applyDocumentFeatures(doc, options.features);
 
   if (typeof html === 'undefined' || html === null) {
@@ -110,8 +118,8 @@ exports.jQueryify = exports.jsdom.jQueryify = function (window /* path [optional
   var args = Array.prototype.slice.call(arguments),
       callback = (typeof(args[args.length - 1]) === 'function') && args.pop(),
       path,
-      jQueryTag = window.document.createElement("script");
-      jQueryTag.className = "jsdom";
+      jQueryTag = window.document.createElement('script');
+      jQueryTag.className = 'jsdom';
 
   if (args.length > 1 && typeof(args[1] === 'string')) {
     path = args[1];
@@ -121,7 +129,7 @@ exports.jQueryify = exports.jsdom.jQueryify = function (window /* path [optional
 
   window.document.implementation.addFeature('FetchExternalResources', ['script']);
   window.document.implementation.addFeature('ProcessExternalResources', ['script']);
-  window.document.implementation.addFeature('MutationEvents', ["1.0"]);
+  window.document.implementation.addFeature('MutationEvents', ['2.0']);
   jQueryTag.src = path || 'http://code.jquery.com/jquery-latest.js';
   window.document.body.appendChild(jQueryTag);
 
@@ -137,224 +145,220 @@ exports.jQueryify = exports.jsdom.jQueryify = function (window /* path [optional
 };
 
 
-exports.env = exports.jsdom.env = function() {
-  var
-  args        = Array.prototype.slice.call(arguments),
-  config      = exports.env.processArguments(args),
-  callback    = config.done,
-  processHTML = function(err, html) {
+exports.env = exports.jsdom.env = function () {
+  var config = getConfigFromArguments(arguments);
+  var callback = config.done;
 
-    html += '';
-    if(err) {
-      return callback(err);
-    }
+  if (config.file) {
+    fs.readFile(config.file, 'utf-8', function (err, text) {
+      if (err) {
+        return callback(err);
+      }
 
-    config.scripts = config.scripts || [];
-    if (typeof config.scripts === 'string') {
-      config.scripts = [config.scripts];
+      config.html = text;
+      processHTML(config);
+    });
+  } else if (config.html) {
+    processHTML(config);
+  } else if (config.url) {
+    handleUrl(config);
+  } else if (config.somethingToAutodetect) {
+    var url = URL.parse(config.somethingToAutodetect);
+    if (url.protocol && url.hostname) {
+      config.url = config.somethingToAutodetect;
+      handleUrl(config.somethingToAutodetect);
+    } else {
+      fs.readFile(config.somethingToAutodetect, 'utf-8', function (err, text) {
+        if (err) {
+          if (err.code === 'ENOENT' || err.code === 'ENAMETOOLONG') {
+            config.html = config.somethingToAutodetect;
+            processHTML(config);
+          } else {
+            callback(err);
+          }
+        } else {
+          config.html = text;
+          config.url = toFileUrl(config.somethingToAutodetect);
+          processHTML(config);
+        }
+      });
     }
+  }
 
-    config.src = config.src || [];
-    if (typeof config.src === 'string') {
-      config.src = [config.src];
-    }
+  function handleUrl() {
+    var options = {
+      uri: config.url,
+      encoding: config.encoding || 'utf8',
+      headers: config.headers || {},
+      proxy: config.proxy || null
+    };
+
+    request(options, function (err, res, responseText) {
+      if (err) {
+        return callback(err);
+      }
 
-    var
-    options    = {
-      features: config.features || {
-        'FetchExternalResources' : false,
-        'ProcessExternalResources' : false
-      },
-      url: config.url
-    },
-    window     = exports.html(html, null, options).createWindow(),
-    features   = JSON.parse(JSON.stringify(window.document.implementation._features)),
-    docsLoaded = 0,
-    totalDocs  = config.scripts.length + config.src.length,
-    readyState = null,
-    errors     = null;
-
-    if (!window || !window.document) {
-      return callback(new Error('JSDOM: a window object could not be created.'));
-    }
+      // The use of `res.request.uri.href` ensures that `window.location.href`
+      // is updated when `request` follows redirects.
+      config.html = responseText;
+      config.url = res.request.uri.href;
+      processHTML(config);
+    });
+  }
+};
 
-    if( config.document ) {
-      window.document._referrer = config.document.referrer;
-      window.document._cookie = config.document.cookie;
-    }
+function processHTML(config) {
+  var callback = config.done;
+  var options = {
+    features: config.features,
+    url: config.url,
+    parser: config.parser
+  };
 
-    window.document.implementation.addFeature('FetchExternalResources', ['script']);
-    window.document.implementation.addFeature('ProcessExternalResources', ['script']);
-    window.document.implementation.addFeature('MutationEvents', ['1.0']);
+  if (config.document) {
+    options.referrer = config.document.referrer;
+    options.cookie = config.document.cookie;
+    options.cookieDomain = config.document.cookieDomain;
+  }
 
-    var scriptComplete = function() {
-      docsLoaded++;
-      if (docsLoaded >= totalDocs) {
-        window.document.implementation._features = features;
+  var window = exports.html(config.html, null, options).createWindow();
+  var features = JSON.parse(JSON.stringify(window.document.implementation._features));
 
-        if (errors) {
-          errors = errors.concat(window.document.errors || []);
-        }
+  var docsLoaded = 0;
+  var totalDocs = config.scripts.length + config.src.length;
+  var readyState = null;
+  var errors = [];
 
-        callback(errors, window);
+  if (!window || !window.document) {
+    return callback(new Error('JSDOM: a window object could not be created.'));
+  }
+
+  window.document.implementation.addFeature('FetchExternalResources', ['script']);
+  window.document.implementation.addFeature('ProcessExternalResources', ['script']);
+  window.document.implementation.addFeature('MutationEvents', ['2.0']);
+
+  function scriptComplete() {
+    docsLoaded++;
+
+    if (docsLoaded >= totalDocs) {
+      window.document.implementation._features = features;
+
+      errors = errors.concat(window.document.errors || []);
+      if (errors.length === 0) {
+        errors = null;
       }
-    }
 
-    if (config.scripts.length > 0 || config.src.length > 0) {
-      config.scripts.forEach(function(src) {
-        var script = window.document.createElement('script');
-        script.className = "jsdom";
-        script.onload = function() {
-          scriptComplete()
-        };
-
-        script.onerror = function(e) {
-          if (!errors) {
-            errors = [];
-          }
-          errors.push(e.error);
-          scriptComplete();
-        };
-
-        script.src = src;
-        try {
-          // project against invalid dom
-          // ex: http://www.google.com/foo#bar
-          window.document.documentElement.appendChild(script);
-        } catch(e) {
-          if(!errors) {
-            errors=[];
-          }
-          errors.push(e.error || e.message);
-          scriptComplete();
-        }
+      process.nextTick(function() {
+        callback(errors, window);
       });
+    }
+  }
 
-      config.src.forEach(function(src) {
-        var script = window.document.createElement('script');
-        script.onload = function() {
-          process.nextTick(scriptComplete);
-        };
+  function handleScriptError(e) {
+    if (!errors) {
+      errors = [];
+    }
+    errors.push(e.error || e.message);
 
-        script.onerror = function(e) {
-          if (!errors) {
-            errors = [];
-          }
-          errors.push(e.error || e.message);
-          // nextTick so that an exception within scriptComplete won't cause
-          // another script onerror (which would be an infinite loop)
-          process.nextTick(scriptComplete);
-        };
+    // nextTick so that an exception within scriptComplete won't cause
+    // another script onerror (which would be an infinite loop)
+    process.nextTick(scriptComplete);
+  }
 
-        script.text = src;
+  if (config.scripts.length > 0 || config.src.length > 0) {
+    config.scripts.forEach(function (scriptSrc) {
+      var script = window.document.createElement('script');
+      script.className = 'jsdom';
+      script.onload = scriptComplete;
+      script.onerror = handleScriptError;
+      script.src = scriptSrc;
+
+      try {
+        // protect against invalid dom
+        // ex: http://www.google.com/foo#bar
         window.document.documentElement.appendChild(script);
-        window.document.documentElement.removeChild(script);
-      });
-    } else {
-      scriptComplete();
-    }
-  };
+      } catch (e) {
+        handleScriptError(e);
+      }
+    });
 
-  config.html += '';
+    config.src.forEach(function (scriptText) {
+      var script = window.document.createElement('script');
+      script.onload = scriptComplete;
+      script.onerror = handleScriptError;
+      script.text = scriptText;
 
-  // Handle markup
-  if (config.html.indexOf("\n") > 0 || config.html.match(/^\W*</)) {
-    processHTML(null, config.html);
+      window.document.documentElement.appendChild(script);
+      window.document.documentElement.removeChild(script);
+    });
+  } else {
+    scriptComplete();
+  }
+}
 
-  // Handle url/file
+function getConfigFromArguments(args, callback) {
+  var config = {};
+  if (typeof args[0] === 'object') {
+    var configToClone = args[0];
+    Object.keys(configToClone).forEach(function (key) {
+      config[key] = configToClone[key];
+    });
   } else {
-    var url = URL.parse(config.html);
-    config.url = config.url || url.href;
-    if (url.hostname) {
-      request({
-        uri      : url,
-        encoding : config.encoding || 'utf8',
-        headers  : config.headers || {}
-      },
-      function(err, request, body) {
-        processHTML(err, body);
-      });
-    } else {
-      fs.readFile(url.pathname, processHTML);
-    }
+    var stringToAutodetect = null;
+
+    Array.prototype.forEach.call(args, function (arg) {
+      switch (typeof arg) {
+        case 'string':
+          config.somethingToAutodetect = arg;
+          break;
+        case 'function':
+          config.done = arg;
+          break;
+        case 'object':
+          if (Array.isArray(arg)) {
+            config.scripts = arg;
+          } else {
+            extend(config, arg);
+          }
+          break;
+      }
+    });
   }
-};
 
-/*
-  Since jsdom.env() is a helper for quickly and easily setting up a
-  window with scripts and such already loaded into it, the arguments
-  should be fairly flexible.  Here are the requirements
-
-  1) collect `html` (url, string, or file on disk)  (STRING)
-  2) load `code` into the window (array of scripts) (ARRAY)
-  3) callback when resources are `done`             (FUNCTION)
-  4) configuration                                  (OBJECT)
-
-  Rules:
-  + if there is one argument it had better be an object with atleast
-    a `html` and `done` property (other properties are gravy)
-
-  + arguments above are pulled out of the arguments and put into the
-    config object that is returned
-*/
-exports.env.processArguments = function(args) {
-  if (!args || !args.length || args.length < 1) {
-    throw new Error('No arguments passed to jsdom.env().');
+  if (!config.done) {
+    throw new Error('Must pass a "done" option or a callback to jsdom.env.');
   }
 
-  var
-  props = {
-    'html'    : true,
-    'done'    : true,
-    'scripts' : false,
-    'config'  : false,
-    'url'     : false,  // the URL for location.href if different from html
-    'document': false   // HTMLDocument properties
-  },
-  propKeys = Object.keys(props),
-  config = {
-    code : []
-  },
-  l    = args.length
-  ;
-  if (l === 1) {
-    config = args[0];
-  } else {
-    args.forEach(function(v) {
-      var type = typeof v;
-      if (!v) {
-        return;
-      }
-      if (type === 'string' || v + '' === v) {
-        config.html = v;
-      } else if (type === 'object') {
-        // Array
-        if (v.length && v[0]) {
-          config.scripts = v;
-        } else {
-          // apply missing required properties if appropriate
-          propKeys.forEach(function(req) {
+  if (!config.somethingToAutodetect && !config.html && !config.file && !config.url) {
+    throw new Error('Must pass a "html", "file", or "url" option, or a string, to jsdom.env');
+  }
 
-            if (typeof v[req] !== 'undefined' &&
-                typeof config[req] === 'undefined') {
+  config.scripts = ensureArray(config.scripts);
+  config.src = ensureArray(config.src);
 
-              config[req] = v[req];
-              delete v[req];
-            }
-          });
-          config.config = v;
-        }
-      } else if (type === 'function') {
-        config.done = v;
-      }
-    });
+  config.features = config.features || {
+    FetchExternalResources: false,
+    ProcessExternalResources: false,
+    SkipExternalResources: false
+  };
+
+  if (!config.url && config.file) {
+    config.url = toFileUrl(config.file);
   }
 
-  propKeys.forEach(function(req) {
-    var required = props[req];
-    if (required && typeof config[req] === 'undefined') {
-      throw new Error("jsdom.env requires a '" + req + "' argument");
-    }
-  });
   return config;
-};
+}
+
+function ensureArray(value) {
+  var array = value || [];
+  if (typeof array === 'string') {
+    array = [array];
+  }
+  return array;
+}
+
+function extend(config, overrides) {
+  Object.keys(overrides).forEach(function (key) {
+    config[key] = overrides[key];
+  });
+}
diff --git a/lib/jsdom/browser/documentfeatures.js b/lib/jsdom/browser/documentfeatures.js
index 5bbd2c5..2673631 100644
--- a/lib/jsdom/browser/documentfeatures.js
+++ b/lib/jsdom/browser/documentfeatures.js
@@ -2,14 +2,14 @@ exports.availableDocumentFeatures = [
   'FetchExternalResources',
   'ProcessExternalResources',
   'MutationEvents',
-  'QuerySelector'
+  'SkipExternalResources'
 ];
 
 exports.defaultDocumentFeatures = {
-  "FetchExternalResources"   : ['script'/*, 'img', 'css', 'frame', 'link'*/],
-  "ProcessExternalResources" : ['script'/*, 'frame', 'iframe'*/],
-  "MutationEvents"           : '2.0',
-  "QuerySelector"            : false
+  "FetchExternalResources": ['script', 'link'/*, 'img', 'css', 'frame'*/],
+  "ProcessExternalResources": ['script'/*, 'frame', 'iframe'*/],
+  "MutationEvents": '2.0',
+  "SkipExternalResources": false
 };
 
 exports.applyDocumentFeatures = function(doc, features) {
diff --git a/lib/jsdom/browser/domtohtml.js b/lib/jsdom/browser/domtohtml.js
index 1692979..4c69858 100644
--- a/lib/jsdom/browser/domtohtml.js
+++ b/lib/jsdom/browser/domtohtml.js
@@ -1,7 +1,3 @@
-//Make configurable from docType??
-//Set in option
-var isXHTML = false;
-
 //List from node-htmlparser
 var singleTags = {
   area: 1,
@@ -59,10 +55,7 @@ exports.stringifyElement = function stringifyElement(element) {
   ret.start += attributes.join(" ");
 
   if (singleTags[tagName]) {
-    if (isXHTML) {
-        ret.start += "/";
-    }
-    ret.start += ">";
+    ret.start += " />";
     ret.end = '';
   } else {
     ret.start += ">";
@@ -122,14 +115,15 @@ exports.makeHtmlGenerator = function makeHtmlGenerator(indentUnit, eol) {
           } else {
             ret += curIndent + current.start;
           }
-          if (node._childNodes.length > 0) {
+          var len = node._childNodes.length;
+          if (len > 0) {
             if (node._childNodes[0].nodeType !== node.TEXT_NODE) {
               ret += eol;
             }
-            for (i=0; i<node._childNodes.length; i++) {
+            for (i=0; i<len; i++) {
               ret += generateHtmlRecursive(node._childNodes[i], childNodesRawText, curIndent + indentUnit);
             }
-            if (node._childNodes[node._childNodes.length - 1].nodeType !== node.TEXT_NODE) {
+            if (node._childNodes[len - 1].nodeType !== node.TEXT_NODE) {
               ret += curIndent;
             }
             ret += current.end + eol;
@@ -163,9 +157,9 @@ exports.makeHtmlGenerator = function makeHtmlGenerator(indentUnit, eol) {
 exports.domToHtml = function(dom, noformat, raw) {
   var htmlGenerator = exports.makeHtmlGenerator(noformat ? "" : "  ",
                                                 noformat ? "" : "\n");
-  if (dom.toArray) {
+  if (dom._toArray) {
     // node list
-    dom = dom.toArray();
+    dom = dom._toArray();
   }
   if (typeof dom.length !== 'undefined') {
     var ret = "";
diff --git a/lib/jsdom/browser/history.js b/lib/jsdom/browser/history.js
new file mode 100644
index 0000000..3b8bb65
--- /dev/null
+++ b/lib/jsdom/browser/history.js
@@ -0,0 +1,99 @@
+"use strict";
+
+function StateEntry(data, title, url) {
+  this.data = data;
+  this.title = title;
+  this.url = url;
+}
+
+module.exports = History;
+
+function History(window) {
+  this._states = [];
+  this._index = -1;
+  this._window = window;
+  this._location = window.location;
+}
+
+History.prototype = {
+  constructor: History,
+
+  get length() {
+    return this._states.length;
+  },
+
+  get state() {
+    var state = this._states[this._index];
+    return state ? state.data : null;
+  },
+
+  back: function () {
+    this.go(-1);
+  },
+
+  forward: function () {
+    this.go(1);
+  },
+
+  go: function (delta) {
+    if (typeof delta === "undefined" || delta === 0) {
+      this._location.reload();
+      return;
+    }
+
+    var newIndex = this._index + delta;
+
+    if (newIndex < 0 || newIndex >= this.length) {
+      return;
+    }
+
+    this._index = newIndex;
+    this._applyState(this._states[this._index]);
+  },
+
+  pushState: function (data, title, url) {
+    var state = new StateEntry(data, title, url);
+    if (this._index + 1 !== this._states.length) {
+      this._states = this._states.slice(0, this._index + 1);
+    }
+    this._states.push(state);
+    this._applyState(state);
+    this._index++;
+  },
+
+  replaceState: function (data, title, url) {
+    var state = new StateEntry(data, title, url);
+    this._states[this._index] = state;
+    this._applyState(state);
+  },
+
+  _applyState: function(state) {
+    var url = state.url;
+    var pathname;
+
+    // Absolute path
+    if (state.url.charAt(0) === "/") {
+      pathname = state.url;
+    } else {
+      pathname = [this._location.pathname, state.url].join("/");
+    }
+
+    this._location._url.pathname = pathname;
+    this._signalPopstate(state);
+  },
+
+  _signalPopstate: function(state) {
+    if (this._window.document) {
+      var ev = this._window.document.createEvent("HTMLEvents");
+      ev.initEvent("popstate", false, false);
+      ev.state = state.data;
+      process.nextTick(function () {
+        this._window.dispatchEvent(ev);
+      }.bind(this));
+    }
+  },
+
+  toString: function () {
+    return "[object History]";
+  }
+};
diff --git a/lib/jsdom/browser/htmltodom.js b/lib/jsdom/browser/htmltodom.js
index c31d709..99c7e45 100644
--- a/lib/jsdom/browser/htmltodom.js
+++ b/lib/jsdom/browser/htmltodom.js
@@ -54,11 +54,18 @@ function HtmlToDom(parser) {
     if (parser.ParseHtml) {
       // davglass/node-htmlparser
     } else if (parser.DefaultHandler){
-      // tautologistics/node-htmlparser
+      // fb55/htmlparser2
+
+      parser.ParseHtml = function(rawHtml) {
+        var handler = new parser.DefaultHandler();
+        // Check if document is XML
+        var isXML = (/^<\?\s*xml.*version=["']1\.0["'].*\s*\?>/i).test(rawHtml);
+        var parserInstance = new parser.Parser(handler, {
+          xmlMode: isXML,
+          lowerCaseTags: !isXML,
+          lowerCaseAttributeNames: !isXML
+        });
 
-      var handler        = new parser.DefaultHandler(),
-          parserInstance = new parser.Parser(handler);
-      parser.ParseHtml = function(rawHtml){
         parserInstance.includeLocation = false;
         parserInstance.parseComplete(rawHtml);
         return handler.dom;
@@ -139,7 +146,10 @@ function setChild(parent, node) {
     break;
 
     case 'text':
-      newNode = currentDocument.createTextNode(HTMLDecode(node.data));
+      // Decode HTML entities if we're not inside a <script> or <style> tag:
+      newNode = currentDocument.createTextNode(/^(?:script|style)$/i.test(parent.nodeName) ?
+                                                   node.data :
+                                                   HTMLDecode(node.data));
     break;
 
     case 'comment':
@@ -159,7 +169,7 @@ function setChild(parent, node) {
       // catchin errors here helps with improperly escaped attributes
       // but properly fixing parent should (can only?) be done in the htmlparser itself
       try {
-        newNode.setAttribute(c.toLowerCase(), HTMLDecode(node.attribs[c]));
+        newNode.setAttribute(c, HTMLDecode(node.attribs[c]));
       } catch(e2) { /* noop */ }
     }
   }
@@ -177,7 +187,7 @@ function setChild(parent, node) {
           exception: err,
           node : node
         });
-    return null;    
+    return null;
   }
 }
 
diff --git a/lib/jsdom/browser/index.js b/lib/jsdom/browser/index.js
index 99b5cc5..31701a2 100644
--- a/lib/jsdom/browser/index.js
+++ b/lib/jsdom/browser/index.js
@@ -6,52 +6,19 @@ var http          = require('http'),
     HTMLEncode    = htmlencoding.HTMLEncode,
     HTMLDecode    = htmlencoding.HTMLDecode,
     jsdom         = require('../../jsdom'),
-    Contextify    = null;
-
-try {
-  Contextify = require('contextify');
-} catch (e) {
-  // Shim for when the contextify compilation fails.
-  // This is not quite as correct, but it gets the job done.
-  Contextify = function(sandbox) {
-    var vm = require('vm');
-    var global = null;
-
-    sandbox.run = function(code, filename) {
-      return vm.runInContext(code, sandbox, filename);
-    };
-
-    sandbox.getGlobal = function() {
-      if (!global) {
-        global = vm.runInContext('this', sandbox);
-      }
-      return global;
-    };
-
-    sandbox.dispose = function() {
-      global = null;
-      sandbox.run = function () {
-        throw new Error("Called run() after dispose().");
-      };
-      sandbox.getGlobal = function () {
-        throw new Error("Called getGlobal() after dispose().");
-      };
-      sandbox.dispose = function () {
-        throw new Error("Called dispose() after dispose().");
-      };
-    };
-
-    return sandbox;
-  };
-}
-
-function NOT_IMPLEMENTED(target) {
-  return function() {
-    if (!jsdom.debugMode) {
-      var raise = target ? target.raise : this.raise;
-      raise.call(this, 'error', 'NOT IMPLEMENTED');
-    }
-  };
+    Location      = require('./location'),
+    History       = require('./history'),
+    NOT_IMPLEMENTED = require('./utils').NOT_IMPLEMENTED,
+    CSSStyleDeclaration = require('cssstyle').CSSStyleDeclaration,
+    toFileUrl = require('../utils').toFileUrl,
+    Contextify    = require('contextify');
+
+function matchesDontThrow(el, selector) {
+  try {
+    return el.matchesSelector(selector);
+  } catch (e) {
+    return false;
+  }
 }
 
 /**
@@ -65,10 +32,6 @@ exports.windowAugmentation = function(dom, options) {
   if (!options.document) {
     var browser = browserAugmentation(dom, options);
 
-    if (options.features && options.features.QuerySelector) {
-      require(__dirname + "/../selectors/index").applyQuerySelectorPrototype(browser);
-    }
-
     options.document = (browser.HTMLDocument)             ?
                         new browser.HTMLDocument(options) :
                         new browser.Document(options);
@@ -84,7 +47,9 @@ exports.windowAugmentation = function(dom, options) {
     if (doc.readyState == 'complete') {
       var ev = doc.createEvent('HTMLEvents');
       ev.initEvent('load', false, false);
-      window.dispatchEvent(ev);
+      process.nextTick(function () {
+        window.dispatchEvent(ev);
+      });
     }
     else {
       doc.addEventListener('load', function(ev) {
@@ -101,6 +66,7 @@ exports.windowAugmentation = function(dom, options) {
  */
 exports.createWindow = function(dom, options) {
   var timers = [];
+  var cssSelectorSplitRE = /((?:[^,"']|"[^"]*"|'[^']*')+)/;
 
   function startTimer(startFn, stopFn, callback, ms) {
 	  var res = startFn(callback, ms);
@@ -129,44 +95,16 @@ exports.createWindow = function(dom, options) {
   }
 
   function DOMWindow(options) {
-    var href = (options || {}).url || 'file://' + __filename;
-    this.location = URL.parse(href);
-    this.location.reload = NOT_IMPLEMENTED(this);
-    this.location.replace = NOT_IMPLEMENTED(this);
-    this.location.toString = function() {
-      return href;
-    };
+    var url = (options || {}).url || toFileUrl(__filename);
+    this.location = new Location(url, this);
+    this.history = new History(this);
 
-    var window = this.console._window = this;
-
-    /* Location hash support */
-    this.location.__defineGetter__("hash", function() {
-      return (window.location.href.split("#").length > 1)
-        ? "#"+window.location.href.split("#")[1]
-        : "";
-    });
-
-    this.location.__defineSetter__("hash", function(val) {
-      /* TODO: Should fire a hashchange event, but tests aren't working */
-      window.location.href = window.location.href.split("#")[0] + val;
-    });
-
-    /* Location search support */
-    this.location.__defineGetter__("search", function() {
-      return (window.location.href.split("?").length > 1)
-        ? "?"+window.location.href.match(/\?([^#]+)/)[1]
-        : "";
-    });
-
-    this.location.__defineSetter__("search", function(val) {
-      window.location.href = (window.location.href.indexOf("?") > 0)
-        ? window.location.href.replace(/\?([^#]+)/, val)
-        : window.location.href.match(/^([^#?]+)/)[0] + val + window.location.hash;
-    });
+    this.console._window = this;
 
     if (options && options.document) {
       options.document.location = this.location;
     }
+
     this.addEventListener = function() {
       dom.Node.prototype.addEventListener.apply(window, arguments);
     };
@@ -235,16 +173,42 @@ exports.createWindow = function(dom, options) {
     },
     getComputedStyle: function(node) {
       var s = node.style,
-          cs = {};
+          cs = new CSSStyleDeclaration(),
+          forEach = Array.prototype.forEach;
 
-      for (var n in s) {
-        cs[n] = s[n];
-      }
-      cs.__proto__ = {
-        getPropertyValue: function(name) {
-          return node.style[name];
+      function setPropertiesFromRule(rule) {
+        if (!rule.selectorText) {
+          return;
         }
-      };
+
+        var selectors = rule.selectorText.split(cssSelectorSplitRE);
+        var matched = false;
+        selectors.forEach(function (selectorText) {
+          if (selectorText !== '' && selectorText !== ',' && !matched && matchesDontThrow(node, selectorText)) {
+            matched = true;
+            forEach.call(rule.style, function (property) {
+              cs.setProperty(property, rule.style.getPropertyValue(property), rule.style.getPropertyPriority(property));
+            });
+          }
+        });
+      }
+
+      forEach.call(node.ownerDocument.styleSheets, function (sheet) {
+        forEach.call(sheet.cssRules, function (rule) {
+          if (rule.media) {
+            if (Array.prototype.indexOf.call(rule.media, 'screen') !== -1) {
+              forEach.call(rule.cssRules, setPropertiesFromRule);
+            }
+          } else {
+            setPropertiesFromRule(rule);
+          }
+        });
+      });
+
+      forEach.call(s, function (property) {
+        cs.setProperty(property, s.getPropertyValue(property), s.getPropertyPriority(property));
+      });
+
       return cs;
     },
     console: {
@@ -257,9 +221,35 @@ exports.createWindow = function(dom, options) {
       userAgent: 'Node.js (' + process.platform + '; U; rv:' + process.version + ')',
       appName: 'Node.js jsDom',
       platform: process.platform,
-      appVersion: process.version
+      appVersion: process.version,
+      noUI: true
+    },
+    XMLHttpRequest: function() {
+      var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
+      var xhr = new XMLHttpRequest();
+      var lastUrl = '';
+      xhr._open = xhr.open;
+      xhr.open = function(method, url, async, user, password) {
+        url = URL.resolve(options.url, url);
+        lastUrl = url;
+        return xhr._open(method, url, async, user, password);
+      };
+      xhr._send = xhr.send;
+      xhr.send = function(data) {
+        if (window.document.cookie) {
+          var cookieDomain = window.document._cookieDomain;
+          var url = URL.parse(lastUrl);
+          var host = url.host.split(':')[0];
+          if (host.indexOf(cookieDomain, host.length - cookieDomain.length) !== -1) {
+            xhr.setDisableHeaderCheck(true);
+            xhr.setRequestHeader('cookie', window.document.cookie);
+            xhr.setDisableHeaderCheck(false);
+          }
+        }
+        return xhr._send(data);
+      };
+      return xhr;
     },
-    XMLHttpRequest: function XMLHttpRequest() {},
 
     name: 'nodejs',
     innerWidth: 1024,
@@ -276,26 +266,41 @@ exports.createWindow = function(dom, options) {
     scrollY: 0,
     scrollTop: 0,
     scrollLeft: 0,
-    alert: NOT_IMPLEMENTED(),
-    blur: NOT_IMPLEMENTED(),
-    confirm: NOT_IMPLEMENTED(),
-    createPopup: NOT_IMPLEMENTED(),
-    focus: NOT_IMPLEMENTED(),
-    moveBy: NOT_IMPLEMENTED(),
-    moveTo: NOT_IMPLEMENTED(),
-    open: NOT_IMPLEMENTED(),
-    print: NOT_IMPLEMENTED(),
-    prompt: NOT_IMPLEMENTED(),
-    resizeBy: NOT_IMPLEMENTED(),
-    resizeTo: NOT_IMPLEMENTED(),
-    scroll: NOT_IMPLEMENTED(),
-    scrollBy: NOT_IMPLEMENTED(),
-    scrollTo: NOT_IMPLEMENTED(),
+    alert: NOT_IMPLEMENTED(null, 'window.alert'),
+    blur: NOT_IMPLEMENTED(null, 'window.blur'),
+    confirm: NOT_IMPLEMENTED(null, 'window.confirm'),
+    createPopup: NOT_IMPLEMENTED(null, 'window.createPopup'),
+    focus: NOT_IMPLEMENTED(null, 'window.focus'),
+    moveBy: NOT_IMPLEMENTED(null, 'window.moveBy'),
+    moveTo: NOT_IMPLEMENTED(null, 'window.moveTo'),
+    open: NOT_IMPLEMENTED(null, 'window.open'),
+    print: NOT_IMPLEMENTED(null, 'window.print'),
+    prompt: NOT_IMPLEMENTED(null, 'window.prompt'),
+    resizeBy: NOT_IMPLEMENTED(null, 'window.resizeBy'),
+    resizeTo: NOT_IMPLEMENTED(null, 'window.resizeTo'),
+    scroll: NOT_IMPLEMENTED(null, 'window.scroll'),
+    scrollBy: NOT_IMPLEMENTED(null, 'window.scrollBy'),
+    scrollTo: NOT_IMPLEMENTED(null, 'window.scrollTo'),
     screen : {
       width : 0,
       height : 0
     },
-    Image : NOT_IMPLEMENTED()
+    Image : NOT_IMPLEMENTED(null, 'window.Image'),
+
+    // Note: these will not be necessary for newer Node.js versions, which have
+    // typed arrays in V8 and thus on every global object. (That is, in newer
+    // versions we'll get `ArrayBuffer` just as automatically as we get
+    // `Array`.) But to support older versions, we explicitly set them here.
+    Int8Array: Int8Array,
+    Int16Array: Int16Array,
+    Int32Array: Int32Array,
+    Float32Array: Float32Array,
+    Float64Array: Float64Array,
+    Uint8Array: Uint8Array,
+    Uint8ClampedArray: Uint8ClampedArray,
+    Uint16Array: Uint16Array,
+    Uint32Array: Uint32Array,
+    ArrayBuffer: ArrayBuffer
   };
 
   var window = new DOMWindow(options);
@@ -324,40 +329,45 @@ exports.createWindow = function(dom, options) {
 * Without cache: ~1800+ms
 * With cache: ~80ms
 */
+// TODO: is this even needed in modern Node.js versions?
 var defaultParser = null;
-function getDefaultParser() {
+var getDefaultParser = exports.getDefaultParser = function () {
   if (defaultParser === null) {
-    try {
-      defaultParser = require('htmlparser');
-    }
-    catch (e) {
-      try {
-        defaultParser = require('node-htmlparser/lib/node-htmlparser');
-      }
-      catch (e2) {
-        defaultParser = undefined;
-      }
-    }
+    defaultParser = require('htmlparser2');
   }
   return defaultParser;
 }
 
 /**
+ * Export getter/setter of default parser to facilitate testing
+ * with different HTML parsers.
+ */
+exports.setDefaultParser = function (parser) {
+  if (typeof parser == 'object') {
+    defaultParser = parser;
+  } else if (typeof parser == 'string')
+    defaultParser = require(parser);
+}
+
+/**
  * Augments the given DOM by adding browser-specific properties and methods (BOM).
  * Returns the augmented DOM.
  */
 var browserAugmentation = exports.browserAugmentation = function(dom, options) {
 
-  if (dom._augmented) {
-    return dom;
-  }
-
   if(!options) {
     options = {};
   }
 
   // set up html parser - use a provided one or try and load from library
-  var htmltodom = new HtmlToDom(options.parser || getDefaultParser());
+  var parser = options.parser || getDefaultParser();
+
+  if (dom._augmented && dom._parser === parser) {
+    return dom;
+  }
+
+  dom._parser = parser;
+  var htmltodom = new HtmlToDom(parser);
 
   if (!dom.HTMLDocument) {
     dom.HTMLDocument = dom.Document;
@@ -414,27 +424,26 @@ var browserAugmentation = exports.browserAugmentation = function(dom, options) {
   });
 
   dom.Document.prototype.__defineGetter__('outerHTML', function() {
-    return domToHtml(this);
+    return domToHtml(this, true);
   });
 
   dom.Element.prototype.__defineGetter__('outerHTML', function() {
-    return domToHtml(this);
+    return domToHtml(this, true);
   });
 
   dom.Element.prototype.__defineGetter__('innerHTML', function() {
-
-    if (this._tagName === 'script' &&
-        this._attributes.length > 0 &&
-        typeof(this._attributes._nodes.type) !== "undefined" &&
-        this._attributes._nodes.type._nodeValue.indexOf("text") === 0) {
+    if (/^(?:script|style)$/.test(this._tagName)) {
+      var type = this.getAttribute('type');
+      if (!type || /^text\//i.test(type) || /\/javascript$/i.test(type)) {
         return domToHtml(this._childNodes, true, true);
+      }
     }
 
     return domToHtml(this._childNodes, true);
   });
 
   dom.Element.prototype.__defineSetter__('doctype', function() {
-    throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
+    throw new dom.DOMException(dom.NO_MODIFICATION_ALLOWED_ERR);
   });
   dom.Element.prototype.__defineGetter__('doctype', function() {
     var r = null;
@@ -447,12 +456,6 @@ var browserAugmentation = exports.browserAugmentation = function(dom, options) {
   });
 
   dom.Element.prototype.__defineSetter__('innerHTML', function(html) {
-    //Check for lib first
-
-    if (html === null) {
-      return null;
-    }
-
     //Clear the children first:
     var child;
     while ((child = this._childNodes[0])) {
@@ -462,7 +465,9 @@ var browserAugmentation = exports.browserAugmentation = function(dom, options) {
     if (this.nodeName === '#document') {
       parseDocType(this, html);
     }
-    var nodes = htmltodom.appendHtmlToElement(html, this);
+    if (html !== "" && html != null) {
+      htmltodom.appendHtmlToElement(html, this);
+    }
     return html;
   });
 
@@ -472,12 +477,6 @@ var browserAugmentation = exports.browserAugmentation = function(dom, options) {
   });
 
   dom.Document.prototype.__defineSetter__('innerHTML', function(html) {
-    //Check for lib first
-
-    if (html === null) {
-      return null;
-    }
-
     //Clear the children first:
     var child;
     while ((child = this._childNodes[0])) {
@@ -487,7 +486,9 @@ var browserAugmentation = exports.browserAugmentation = function(dom, options) {
     if (this.nodeName === '#document') {
       parseDocType(this, html);
     }
-    var nodes = htmltodom.appendHtmlToElement(html, this);
+    if (html !== "" && html != null) {
+      htmltodom.appendHtmlToElement(html, this);
+    }
     return html;
   });
 
@@ -602,13 +603,21 @@ var browserAugmentation = exports.browserAugmentation = function(dom, options) {
 
   dom.Document.prototype.__defineGetter__('parentWindow', function() {
     if (!this._parentWindow) {
-      var window = exports.windowAugmentation(dom, {document: this, url: this.URL});
-      this._parentWindow = window.getGlobal();
+      this.parentWindow = exports.windowAugmentation(dom, {document: this, url: this.URL});
     }
     return this._parentWindow;
   });
 
   dom.Document.prototype.__defineSetter__('parentWindow', function(window) {
+    // Contextify does not support getters and setters, so we have to set them
+    // on the original object instead.
+    window._frame = function (name, frame) {
+      if (typeof frame === 'undefined') {
+        delete window[name];
+      } else {
+        window.__defineGetter__(name, function () { return frame.contentWindow; });
+      }
+    };
     this._parentWindow = window.getGlobal();
   });
 
diff --git a/lib/jsdom/browser/location.js b/lib/jsdom/browser/location.js
new file mode 100644
index 0000000..111a325
--- /dev/null
+++ b/lib/jsdom/browser/location.js
@@ -0,0 +1,98 @@
+"use strict";
+
+var URL = require("url");
+var NOT_IMPLEMENTED = require("./utils").NOT_IMPLEMENTED;
+
+module.exports = Location;
+
+function Location(urlString, window) {
+  this._url = URL.parse(urlString);
+  this._window = window;
+}
+
+Location.prototype = {
+  constructor: Location,
+  reload: function () {
+    NOT_IMPLEMENTED(this._window, "location.reload")();
+  },
+  get protocol() { return this._url.protocol; },
+  get host() { return this._url.host; },
+  get auth() { return this._url.auth; },
+  get hostname() { return this._url.hostname; },
+  get port() { return this._url.port; },
+  get pathname() { return this._url.pathname; },
+  get href() { return this._url.href; },
+  get hash() { return this._url.hash || ""; },
+  get search() { return this._url.search || ""; },
+
+  set href(val) {
+    var oldUrl = this._url.href;
+    var oldProtocol = this._url.protocol;
+    var oldHost = this._url.host;
+    var oldPathname = this._url.pathname;
+    var oldHash = this._url.hash || "";
+
+    this._url = URL.parse(URL.resolve(oldUrl, val));
+    var newUrl = this._url.href;
+    var newProtocol = this._url.protocol;
+    var newHost = this._url.host;
+    var newPathname = this._url.pathname;
+    var newHash = this._url.hash || "";
+
+    if (oldProtocol === newProtocol && oldHost === newHost && oldPathname === newPathname && oldHash !== newHash) {
+      this._signalHashChange(oldUrl, newUrl);
+    } else {
+      NOT_IMPLEMENTED(this._window, "location.href (no reload)")();
+    }
+  },
+
+  set hash(val) {
+    var oldUrl = this._url.href;
+    var oldHash = this._url.hash || "";
+
+    if (val.lastIndexOf("#", 0) !== 0) {
+      val = "#" + val;
+    }
+
+    this._url = URL.parse(URL.resolve(oldUrl, val));
+    var newUrl = this._url.href;
+    var newHash = this._url.hash || "";
+
+    if (oldHash !== newHash) {
+      this._signalHashChange(oldUrl, newUrl);
+    }
+  },
+
+  set search(val) {
+    var oldUrl = this._url.href;
+    var oldHash = this._url.hash || "";
+    if (val.length) {
+      if (val.lastIndexOf("?", 0) !== 0) {
+        val = "?" + val;
+      }
+      this._url = URL.parse(URL.resolve(oldUrl, val + oldHash));
+    } else {
+      this._url = URL.parse(oldUrl.replace(/\?([^#]+)/, ""));
+    }
+  },
+
+  replace: function (val) {
+    this.href = val;
+  },
+
+  toString: function () {
+    return this._url.href;
+  },
+
+  _signalHashChange: function (oldUrl, newUrl) {
+    if (this._window.document) {
+      var ev = this._window.document.createEvent("HTMLEvents");
+      ev.initEvent("hashchange", false, false);
+      ev.oldUrl = oldUrl;
+      ev.newUrl = newUrl;
+      process.nextTick(function () {
+        this._window.dispatchEvent(ev);
+      }.bind(this));
+    }
+  }
+};
diff --git a/lib/jsdom/browser/utils.js b/lib/jsdom/browser/utils.js
new file mode 100644
index 0000000..c282f77
--- /dev/null
+++ b/lib/jsdom/browser/utils.js
@@ -0,0 +1,12 @@
+"use strict";
+
+var jsdom = require("../../jsdom");
+
+exports.NOT_IMPLEMENTED = function (target, nameForErrorMessage) {
+  return function () {
+    if (!jsdom.debugMode) {
+      var raise = target ? target.raise : this.raise;
+      raise.call(this, "error", "NOT IMPLEMENTED" + (nameForErrorMessage ? ": " + nameForErrorMessage : ""));
+    }
+  };
+};
diff --git a/lib/jsdom/level1/core.js b/lib/jsdom/level1/core.js
index 9eff9fb..5763f79 100644
--- a/lib/jsdom/level1/core.js
+++ b/lib/jsdom/level1/core.js
@@ -40,7 +40,7 @@ var core = {
   // Returns Array
   mapDOMNodes : function(parent, recursive, callback) {
     function visit(parent, result) {
-      return parent.childNodes.reduce(reducer, result);
+      return Array.prototype.reduce.call(parent.childNodes, reducer, result);
     }
 
     function reducer(array, child) {
@@ -166,73 +166,87 @@ core.DOMException.prototype = {
 
 core.DOMException.prototype.__proto__ = Error.prototype;
 
-var NodeArray = function() {};
-NodeArray.prototype = new Array();
-NodeArray.prototype.item = function(i) {
-  return this[i];
-};
-
 core.NodeList = function NodeList(element, query) {
   if (!query) {
     // Non-live NodeList
-    var list = new NodeArray();
     if (Array.isArray(element)) {
-      Array.prototype.push.apply(list, element);
-    }
-
-    return list;
+      Array.prototype.push.apply(this, element);
+    }
+    Object.defineProperties(this, {
+      _length: {value: element ? element.length : 0, writable:true}
+    });
+  } else {
+    Object.defineProperties(this, {
+      _element: {value: element},
+      _query: {value: query},
+      _snapshot: {writable: true},
+      _length: {value: 0, writable: true},
+      _version: {value: -1, writable: true}
+    });
+    this._update();
   }
-  Object.defineProperties(this, {
-    _element: {value: element},
-    _query: {value: query},
-    _snapshot: {writable: true},
-    _length: {value: 0, writable: true},
-    _version: {value: -1, writable: true}
-  });
-  this.update();
 };
+
+function lengthFromProperties(arrayLike) {
+  var max = -1;
+  for (var i in arrayLike) {
+    var asNumber = +i;
+    if (!isNaN(asNumber) && asNumber > max) {
+      max = asNumber;
+    }
+  }
+  return max + 1;
+}
 core.NodeList.prototype = {
-  update: function() {
+  _update: function() {
     var i;
-    if (this._element && this._version < this._element._version) {
-      for (i = 0; i < this._length; i++) {
-        this[i] = undefined;
-      }
-      var nodes = this._snapshot = this._query();
-      this._length = nodes.length;
-      for (i = 0; i < nodes.length; i++) {
-        this[i] = nodes[i];
+
+    if (!this._element) {
+      this._length = lengthFromProperties(this);
+    } else {
+      if (this._version < this._element._version) {
+        var nodes = this._snapshot = this._query();
+        this._resetTo(nodes);
+        this._version = this._element._version;
       }
-      this._version = this._element._version;
     }
-    return this._snapshot;
   },
-  toArray: function() {
-    return this.update() || [];
+  _resetTo: function(array) {
+    var startingLength = lengthFromProperties(this);
+    for (var i = 0; i < startingLength; ++i) {
+      delete this[i];
+    }
+
+    for (var j = 0; j < array.length; ++j) {
+      this[j] = array[j];
+    }
+    this._length = array.length;
+  },
+  _toArray: function() {
+    if (this._element) {
+      this._update();
+      return this._snapshot;
+    }
+
+    return Array.prototype.slice.call(this);
   },
   get length() {
-    this.update();
+    this._update();
     return this._length || 0;
   },
   item: function(index) {
-    this.update();
+    this._update();
     return this[index] || null;
   },
   toString: function() {
     return '[ jsdom NodeList ]: contains ' + this.length + ' items';
-  },
-  indexOf: function(node) {
-    var len = this.update().length;
-
-    for (var i = 0; i < len; i++) {
-      if (this[i] == node) {
-        return i;
-      }
-    }
-
-    return -1; // not found
   }
 };
+Object.defineProperty(core.NodeList.prototype, 'constructor', {
+  value: core.NodeList,
+  writable: true,
+  configurable: true
+});
 
 core.DOMImplementation = function DOMImplementation(document, /* Object */ features) {
   this._ownerDocument = document;
@@ -297,7 +311,13 @@ core.DOMImplementation.prototype = {
     } else if (typeof versions === 'string') {
       return versions === version;
     } else if (versions.indexOf && versions.length > 0) {
-       return versions.indexOf(version) !== -1;
+      for (var i = 0; i < versions.length; i++) {
+        var found = versions[i] instanceof RegExp ?
+          versions[i].test(version) :
+          versions[i] === version;
+        if (found) { return true; }
+      }
+      return false;
     } else {
       return false;
     }
@@ -378,7 +398,7 @@ core.Node.prototype = {
     if (!this._childrenList) {
       var self = this;
       this._childrenList = new core.NodeList(this, function() {
-        return self._childNodes.filter(function(node) {
+        return Array.prototype.filter.call(self._childNodes, function(node) {
           return node.tagName;
         });
       });
@@ -409,12 +429,12 @@ core.Node.prototype = {
     }
     return name;
   },
-  set nodeName() { throw new core.DOMException();},
+  set nodeName(unused) { throw new core.DOMException();},
   get attributes() { return this._attributes;},
   get firstChild() {
     return this._childNodes.length > 0 ? this._childNodes[0] : null;
   },
-  set firstChild() { throw new core.DOMException();},
+  set firstChild(unused) { throw new core.DOMException();},
   get ownerDocument() { return this._ownerDocument;},
   get readonly() { return this._readonly;},
 
@@ -422,12 +442,12 @@ core.Node.prototype = {
     var len = this._childNodes.length;
     return len > 0 ? this._childNodes[len -1] : null;
   },
-  set lastChild() { throw new core.DOMException();},
+  set lastChild(unused) { throw new core.DOMException();},
 
   get childNodes() {
     return this._childNodes;
   },
-  set childNodes() { throw new core.DOMException();},
+  set childNodes(unused) { throw new core.DOMException();},
 
   _indexOf: function(/*Node*/ child) {
     if (!this._childNodes ||
@@ -464,7 +484,7 @@ core.Node.prototype = {
 
     return this._parentNode._childNodes[index+1] || null;
   },
-  set nextSibling() { throw new core.DOMException();},
+  set nextSibling(unused) { throw new core.DOMException();},
 
   get previousSibling() {
     if (!this._parentNode || !this._parentNode._indexOf) {
@@ -479,7 +499,7 @@ core.Node.prototype = {
 
     return this._parentNode._childNodes[index-1] || null;
   },
-  set previousSibling() { throw new core.DOMException();},
+  set previousSibling(unused) { throw new core.DOMException();},
 
   /* returns Node */
   insertBefore :  function(/* Node */ newChild, /* Node*/ refChild) {
@@ -509,8 +529,8 @@ core.Node.prototype = {
 
     // fragments are merged into the element
     if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE) {
-      var tmpNode;
-      while (newChild._childNodes.length > 0) {
+      var tmpNode, i = newChild._childNodes.length;
+      while (i-- > 0) {
         tmpNode = newChild.removeChild(newChild.firstChild);
         this.insertBefore(tmpNode, refChild);
       }
@@ -531,7 +551,8 @@ core.Node.prototype = {
         }
       }
 
-      this._childNodes.splice(refChildIndex, 0, newChild);
+      Array.prototype.splice.call(this._childNodes, refChildIndex, 0, newChild);
+
       newChild._parentNode = this;
       if (this._attached && newChild._attach) {
         newChild._attach();
@@ -549,8 +570,9 @@ core.Node.prototype = {
       this._ownerDocument._version++;
     }
 
-    if (this._childrenList) this._childrenList.update();
-    //this._childNodesList.update();
+    if (this._childrenList) {
+      this._childrenList._update();
+    }
   },
 
   _attrModified: function(name, value, oldValue) {
@@ -559,6 +581,41 @@ core.Node.prototype = {
       detachId(oldValue,this,doc);
       attachId(value,this,doc);
     }
+
+    // Check for inline event handlers.
+    // We can't set these like other attributes then look it up in
+    // dispatchEvent() because that would create 2 'traditional' event handlers
+    // in the case where there's an inline event handler attribute, plus one
+    // set using element.on* in a script.
+    //
+    // @see http://www.w3.org/TR/2011/WD-html5-20110405/webappapis.html#event-handler-content-attributes
+    if ((name.length > 2) && (name[0] == 'o') && (name[1] == 'n')) {
+        if (value) {
+          var self = this;
+          // Check whether we're the window. This can happen because inline
+          // handlers on the body are proxied to the window.
+          var w = (typeof self.run !== 'undefined') ? self : self._ownerDocument.parentWindow;
+          self[name] = function (event) {
+              // The handler code probably refers to functions declared in the
+              // window context, so we need to call run().
+
+              // Use awesome hacks to get the correct `this` context for the
+              // inline event handler. This would only be necessary if we're an
+              // element, but for the sake of simplicity we also do it on window.
+
+              // Also set event variable and support `return false`.
+              w.__tempContextForInlineEventHandler = self;
+              w.__tempEvent = event;
+              w.run("if ((function (event) {" + value + "}).call(" +
+                "window.__tempContextForInlineEventHandler, window.__tempEvent) === false) {" +
+                "window.__tempEvent.preventDefault()}");
+              delete w.__tempContextForInlineEventHandler;
+              delete w.__tempEvent;
+          };
+        } else {
+          this[name] = null;
+        }
+    }
   },
 
   /* returns Node */
@@ -573,7 +630,7 @@ core.Node.prototype = {
     if (this.id) {
       attachId(this.id,this,this._ownerDocument);
     }
-    for (var i=0;i<this._childNodes.length;i++) {
+    for (var i=0,len=this._childNodes.length;i<len;i++) {
       if (this._childNodes[i]._attach) {
         this._childNodes[i]._attach();
       }
@@ -586,7 +643,7 @@ core.Node.prototype = {
     if (this.id) {
       detachId(this.id,this,this._ownerDocument);
     }
-    for (var i=0;i<this._childNodes.length;i++) {
+    for (var i=0,len=this._childNodes.length;i<len;i++) {
       this._childNodes[i]._detach();
     }
   },
@@ -603,7 +660,7 @@ core.Node.prototype = {
       throw new core.DOMException(NOT_FOUND_ERR);
     }
 
-    this._childNodes.splice(oldChildIndex, 1);
+    Array.prototype.splice.call(this._childNodes, oldChildIndex, 1);
     oldChild._parentNode = null;
     this._modified();
     oldChild._detach();
@@ -734,7 +791,7 @@ core.Node.prototype = {
       }
 
       // Level2/core clean off empty nodes
-      if (child.value === "") {
+      if (child.nodeValue === "") {
         this.removeChild(child);
         i--;
         continue;
@@ -748,7 +805,7 @@ core.Node.prototype = {
         {
 
           // remove the child and decrement i
-          prevChild.appendData(child.value);
+          prevChild.appendData(child.nodeValue);
 
           this.removeChild(child);
           i--;
@@ -805,7 +862,7 @@ core.Node.prototype = {
 
 
 core.NamedNodeMap = function NamedNodeMap(document) {
-  this._nodes = {};
+  this._nodes = Object.create(null);
   this._nsStore = {};
   this.length = 0;
   this._ownerDocument = document;
@@ -842,7 +899,7 @@ core.NamedNodeMap.prototype = {
       throw new core.DOMException(INUSE_ATTRIBUTE_ERR);
     }
 
-    var name = arg.name;
+    var name = arg.name || arg.tagName;
     var ret = this._nodes[name];
     if (!ret) {
       this.length++;
@@ -861,7 +918,7 @@ core.NamedNodeMap.prototype = {
       throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
     }
 
-    if (!this._nodes.hasOwnProperty(name)) {
+    if (!this._nodes[name]) {
       throw new core.DOMException(NOT_FOUND_ERR);
     }
 
@@ -877,12 +934,10 @@ core.NamedNodeMap.prototype = {
   item: function(/* int */ index) {
     var current = 0;
     for (var member in this._nodes) {
-      if (this._nodes.hasOwnProperty(member)) {
-        if (current === index && this._nodes[member]) {
-          return this._nodes[member];
-        }
-        current++;
+      if (current === index && this._nodes[member]) {
+        return this._nodes[member];
       }
+      current++;
     }
     return null;
   }
@@ -993,38 +1048,17 @@ core.Element.prototype = {
     return this._attributes;
   },
 
-  get name() { return this.nodeName;},
   /* returns string */
   getAttribute: function(/* string */ name) {
     var attribute = this._attributes.getNamedItem(name);
     if (attribute) {
       return attribute.value;
     }
-    return "";
+    return null;
   },
 
   /* returns string */
   setAttribute: function(/* string */ name, /* string */ value) {
-    // Check for inline event handlers.
-    // We can't set these like other attributes then look it up in
-    // dispatchEvent() because that would create 2 'traditional' event handlers
-    // in the case where there's an inline event handler attribute, plus one
-    // set using element.on* in a script.
-    if ((name.length > 2) && (name[0] == 'o') && (name[1] == 'n')) {
-        var self = this;
-        self[name] = function () {
-            // The handler code probably refers to functions declared in the
-            // window context, so we need to call run().
-            if (self.run != undefined) {
-                // We're the window. This can happen because inline handlers
-                // on the body are proxied to the window.
-                self.run(value);
-            } else {
-                // We're an element.
-                self._ownerDocument.parentWindow.run(value);
-            }
-        };
-    }
     if (this._ownerDocument) {
       var attr = this._ownerDocument.createAttribute(name);
       attr.value = value;
@@ -1098,7 +1132,7 @@ core.DocumentFragment = function DocumentFragment(document) {
 core.DocumentFragment.prototype = {
   nodeType : DOCUMENT_FRAGMENT_NODE,
   get nodeValue() { return null;},
-  set nodeValue() { /* do nothing */ },
+  set nodeValue(unused) { /* do nothing */ },
   get attributes() { return null;}
 };
 core.DocumentFragment.prototype.__proto__ = core.Node.prototype;
@@ -1118,7 +1152,7 @@ core.ProcessingInstruction.prototype = {
   get nodeValue() { return this._nodeValue;},
   set nodeValue(value) { this._nodeValue = value},
   get data()   { return this._nodeValue;},
-  set data()   { throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);},
+  set data(unused)   { throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);},
   get attributes() { return null;}
 
 };
@@ -1139,7 +1173,7 @@ core.Document = function Document(options) {
   this._doctype = options._doctype;
   this._implementation = options.implementation || new (core.DOMImplementation)();
   this._documentElement = null;
-  this._ids = {};
+  this._ids = Object.create(null);
   this._attached = true;
   this._ownerDocument = this;
   this._readonly = false;
@@ -1148,29 +1182,11 @@ core.Document = function Document(options) {
 
 var tagRegEx = /[^\w:\d_\.-]+/i;
 var entRegEx = /[^\w\d_\-&;]+/;
-var invalidAttrRegEx = /[^\w:\d_\.-]+/;
+var invalidAttrRegEx = /[\s"'>/=\u0000-\u001A]/;
 
 core.Document.prototype = {
   nodeType : DOCUMENT_NODE,
-  _elementBuilders : {
-    canvas : function(document, tagName) {
-      var element = new core.Element(document, tagName),
-          canvas;
-
-      // require node-canvas and catch the error if it blows up
-      try {
-        canvas = new (require('canvas'))(0,0);
-        for (attr in element) {
-          if (!canvas[attr]) {
-            canvas[attr] = element[attr];
-          }
-        }
-        return canvas;
-      } catch (e) {
-        return element;
-      }
-    }
-  },
+  _elementBuilders : { },
   _defaultElementBuilder: function(document, tagName) {
     return new core.Element(document, tagName);
   },
@@ -1199,26 +1215,22 @@ core.Document.prototype = {
     return null;
   },
   get nodeValue() { return null; },
-  set nodeValue() { /* noop */ },
+  set nodeValue(unused) { /* noop */ },
   get attributes() { return null;},
   get ownerDocument() { return null;},
   get readonly() { return this._readonly;},
-  /* returns Element */
-  createElement: function(/* string */ tagName) {
-    var c = [], lower = tagName.toLowerCase(), element;
-
-    if (!tagName || !tagName.match || (c = tagName.match(tagRegEx))) {
-      throw new core.DOMException(INVALID_CHARACTER_ERR, 'Invalid character in tag name: ' + c.pop());
-    }
 
-    element = (this._elementBuilders[lower] || this._defaultElementBuilder)(this, tagName);
+  /* returns Element */
+  _createElementNoTagNameValidation: function(/*string*/ tagName) {
+    var lower = tagName.toLowerCase();
+    var element = (this._elementBuilders[lower] || this._defaultElementBuilder)(this, tagName);
 
     // Check for and introduce default elements
     if (this._doctype && this._doctype._attributes && this._doctype.name.toLowerCase() !== "html") {
       var attrElement = this._doctype._attributes.getNamedItem(tagName);
       if (attrElement && attrElement._childNodes) {
 
-        attrs = attrElement.attributes;
+        var attrs = attrElement.attributes;
         var attr, len = attrs.length, defaultAttr;
         for (var i = 0; i < len; i++) {
           defaultAttr = attrs.item(i);
@@ -1234,6 +1246,19 @@ core.Document.prototype = {
 
     element._created = true;
     return element;
+  },
+
+  /* returns Element */
+  createElement: function(/* string */ tagName) {
+    tagName = String(tagName);
+
+    var c = [];
+
+    if (tagName.length === 0 || (c = tagName.match(tagRegEx))) {
+      throw new core.DOMException(INVALID_CHARACTER_ERR, 'Invalid character in tag name: ' + c.pop());
+    }
+
+    return this._createElementNoTagNameValidation(tagName);
   }, //raises: function(DOMException) {},
 
   /* returns DocumentFragment */
@@ -1402,7 +1427,7 @@ core.Document.prototype.__proto__ = core.Node.prototype;
 core.CharacterData = function CharacterData(document, value) {
   core.Node.call(this, document);
 
-  this._nodeValue = (value) ? value + "" : "";
+  this._nodeValue = value + "";
 };
 core.CharacterData.prototype = {
 
@@ -1530,7 +1555,7 @@ core.Attr.prototype =  {
     for (var i=0,len=this._childNodes.length;i<len;i++) {
       var child = this._childNodes[i];
       if (child.nodeType === ENTITY_REFERENCE_NODE) {
-        val += child.childNodes.reduce(function(prev, c) {
+        val += Array.prototype.reduce.call(child.childNodes, function(prev, c) {
           return prev += (c.nodeValue || c);
         }, '');
       } else {
@@ -1545,8 +1570,7 @@ core.Attr.prototype =  {
       throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
     }
 
-    this._childNodes.length = 0;
-    this._childNodes.push(this._ownerDocument.createTextNode(value));
+    this._childNodes._resetTo([this._ownerDocument.createTextNode(value)]);
     this._modified();
     this._specified = true;
     var prev = this._nodeValue;
@@ -1598,8 +1622,6 @@ core.Text = function Text(document, text, readonly) {
 core.Text.prototype = {
   nodeType : TEXT_NODE,
   get attributes() { return null;},
-  get value() { return this._nodeValue;},
-  set value(value) { this.nodeValue = value;},
 
   /* returns Text */
   splitText: function(offset) {
@@ -1667,7 +1689,7 @@ core.DocumentType = function DocumentType(document, name, entities, notations, a
 core.DocumentType.prototype = {
   nodeType : DOCUMENT_TYPE_NODE,
   get nodeValue() { return null;},
-  set nodeValue() { /* do nothing */ },
+  set nodeValue(unused) { /* do nothing */ },
   get name() { return this._name;},
   get entities() { return this._entities;},
   get notations() { return this._notations;},
@@ -1690,7 +1712,7 @@ core.Notation.prototype = {
   get systemId() { return this._systemId;},
   get name() { return this._name || this._nodeName;},
   get attributes() { /* as per spec */ return null;},
-  set nodeValue() { /* intentionally left blank */ },
+  set nodeValue(unused) { /* intentionally left blank */ },
   get nodeValue() { return this._nodeValue;},
 };
 core.Notation.prototype.__proto__ = core.Node.prototype;
@@ -1709,7 +1731,7 @@ core.Entity = function Entity(document, name) {
 core.Entity.prototype = {
   nodeType : ENTITY_NODE,
   get nodeValue() { return null;},
-  set nodeValue() {
+  set nodeValue(unused) {
     // readonly
     if (this._readonly === true) {
       // TODO: is this needed?
@@ -1741,7 +1763,7 @@ core.EntityReference = function EntityReference(document, entity) {
 core.EntityReference.prototype = {
   nodeType : ENTITY_REFERENCE_NODE,
   get nodeValue() { return (this._entity) ? this._entity.nodeValue : null;},
-  set nodeValue() {
+  set nodeValue(unused) {
     // readonly
     if (this._readonly === true) {
       // TODO: is this needed?
diff --git a/lib/jsdom/level2/core.js b/lib/jsdom/level2/core.js
index bd45965..4fb5d1f 100644
--- a/lib/jsdom/level2/core.js
+++ b/lib/jsdom/level2/core.js
@@ -80,7 +80,7 @@ core.DOMImplementation.prototype.createDocument = function(/* String */       na
   }
 
   var document = new core.Document();
-  
+
   if (doctype) {
     document.doctype = doctype;
     doctype._ownerDocument = document;
@@ -90,7 +90,7 @@ core.DOMImplementation.prototype.createDocument = function(/* String */       na
   }
 
   if (doctype && !doctype.entities) {
-    doctype.entities = new dom.EntityNodeMap();
+    doctype.entities = new core.EntityNodeMap();
   }
 
   document._ownerDocument = document;
@@ -211,7 +211,7 @@ core.NamedNodeMap.prototype.setNamedItemNS = function(/* Node */ arg)
 
   // readonly
   if (this._readonly === true) {
-    throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
+    throw new core.DOMException(core.NO_MODIFICATION_ALLOWED_ERR);
   }
 
 
@@ -324,7 +324,7 @@ core.Element.prototype.getAttributeNS = function(/* string */ namespaceURI,
                                                  /* string */ localName)
 {
   var attr =  this._attributes.getNamedItemNS(namespaceURI, localName);
-  return (attr) ? attr.nodeValue : '';
+  return attr && attr.nodeValue;
 };
 
 core.Element.prototype.setAttributeNS = function(/* string */ namespaceURI,
diff --git a/lib/jsdom/level2/events.js b/lib/jsdom/level2/events.js
index ea2e744..546a9be 100644
--- a/lib/jsdom/level2/events.js
+++ b/lib/jsdom/level2/events.js
@@ -72,11 +72,6 @@ events.Event.prototype = {
     get timeStamp() { return this._timeStamp; }
 };
 
-events.HTMLEvent = function(eventType) {
-    events.Event.call(this, eventType);
-};
-events.HTMLEvent.prototype.__proto__ = events.Event.prototype;
-
 
 events.UIEvent = function(eventType) {
     events.Event.call(this, eventType);
@@ -401,7 +396,7 @@ core.Document.prototype.createEvent = function(eventType) {
         case "MutationEvents": return new events.MutationEvent(eventType);
         case "UIEvents": return new events.UIEvent(eventType);
         case "MouseEvents": return new events.MouseEvent(eventType);
-        case "HTMLEvents": return new events.HTMLEvent(eventType);
+        case "HTMLEvents": return new events.Event(eventType);
     }
     return new events.Event(eventType);
 };
diff --git a/lib/jsdom/level2/html.js b/lib/jsdom/level2/html.js
index d7593cb..964c3da 100644
--- a/lib/jsdom/level2/html.js
+++ b/lib/jsdom/level2/html.js
@@ -15,6 +15,28 @@ core.languageProcessors = {
   javascript : require("./languages/javascript").javascript
 };
 
+// TODO its own package? Pull request to Node?
+function resolveHref(baseUrl, href) {
+  // When switching protocols, the path doesn't get canonicalized (i.e. .s and ..s are still left):
+  // https://github.com/joyent/node/issues/5453
+  var intermediate = URL.resolve(baseUrl, href);
+
+  // This canonicalizes the path, at the cost of overwriting the hash.
+  var nextStep = URL.resolve(intermediate, '#');
+
+  // So, insert the hash back in, if there was one.
+  var parsed = URL.parse(intermediate);
+  var fixed = nextStep.slice(0, -1) + (parsed.hash || '');
+
+  // Finally, fix file:/// URLs on Windows, where Node removes their drive letters:
+  // https://github.com/joyent/node/issues/5452
+  if (/^file\:\/\/\/[a-z]\:\//i.test(baseUrl) && /^file\:\/\/\//.test(fixed) && !/^file\:\/\/\/[a-z]\:\//i.test(fixed)) {
+    fixed = fixed.replace(/^file\:\/\/\//, baseUrl.substring(0, 11));
+  }
+
+  return fixed;
+}
+
 core.resourceLoader = {
   load: function(element, href, callback) {
     var ownerImplementation = element._ownerDocument.implementation;
@@ -22,8 +44,11 @@ core.resourceLoader = {
     if (ownerImplementation.hasFeature('FetchExternalResources', element.tagName.toLowerCase())) {
       var full = this.resolve(element._ownerDocument, href);
       var url = URL.parse(full);
+      if (ownerImplementation.hasFeature('SkipExternalResources', full)) {
+        return false;
+      }
       if (url.hostname) {
-        this.download(url, this.baseUrl(element._ownerDocument), this.enqueue(element, callback, full));
+        this.download(url, element._ownerDocument._cookie, element._ownerDocument._cookieDomain, this.baseUrl(element._ownerDocument), this.enqueue(element, callback, full));
       }
       else {
         this.readFile(url.pathname, this.enqueue(element, callback, full));
@@ -63,34 +88,30 @@ core.resourceLoader = {
   },
 
   baseUrl: function(document) {
-    var baseElements = document.getElementsByTagName('base'),
-        baseUrl      = document.URL;
+    var baseElements = document.getElementsByTagName('base');
+    var baseUrl = document.URL;
 
     if (baseElements.length > 0) {
-      baseUrl = baseElements.item(0).href;
+      var baseHref = baseElements.item(0).href;
+      if (baseHref) {
+        baseUrl = resolveHref(baseUrl, baseHref);
+      }
     }
 
     return baseUrl;
   },
   resolve: function(document, href) {
-    if (href.match(/^\w+:\/\//)) {
-      return href;
+    // if getAttribute returns null, there is no href
+    // lets resolve to an empty string (nulls are not expected farther up)
+    if (href === null) {
+      return '';
     }
 
     var baseUrl = this.baseUrl(document);
 
-    // See RFC 2396 section 3 for this weirdness. URLs without protocol
-    // have their protocol default to the current one.
-    // http://www.ietf.org/rfc/rfc2396.txt
-    if (href.match(/^\/\//)) {
-      return baseUrl ? baseUrl.match(/^(\w+:)\/\//)[1] + href : null;
-    } else if (!href.match(/^\/[^\/]/)) {
-      href = href.replace(/^\//, "");
-    }
-
-    return URL.resolve(baseUrl, href);
+    return resolveHref(baseUrl, href);
   },
-  download: function(url, referrer, callback) {
+  download: function(url, cookie, cookieDomain, referrer, callback) {
     var path    = url.pathname + (url.search || ''),
         options = {'method': 'GET', 'host': url.hostname, 'path': path},
         request;
@@ -106,13 +127,19 @@ core.resourceLoader = {
     if (referrer) {
         request.setHeader('Referer', referrer);
     }
+    if (cookie) {
+      var host = url.host.split(':')[0];
+      if (host.indexOf(cookieDomain, host.length - cookieDomain.length) !== -1) {
+        request.setHeader('cookie', cookie);
+      }
+    }
 
     request.on('response', function (response) {
       var data = '';
       function success () {
         if ([301, 302, 303, 307].indexOf(response.statusCode) > -1) {
           var redirect = URL.resolve(url, response.headers["location"]);
-          core.resourceLoader.download(URL.parse(redirect), referrer, callback);
+          core.resourceLoader.download(URL.parse(redirect), cookie, cookieDomain, referrer, callback);
         } else {
           callback(null, data);
         }
@@ -140,7 +167,7 @@ core.resourceLoader = {
     request.end();
   },
   readFile: function(url, callback) {
-    fs.readFile(url.replace(/^file:\/\//, ""), 'utf8', callback);
+    fs.readFile(url.replace(/^file:\/\//, "").replace(/^\/([a-z]):\//i, '$1:/').replace(/%20/g, ' '), 'utf8', callback);
   }
 };
 
@@ -170,12 +197,12 @@ function define(elementClass, def) {
         elem.prototype.__defineGetter__(prop, function() {
           var s = this.getAttribute(attr);
           if (n.type && n.type === 'boolean') {
-            return !!s;
+            return s !== null;
           }
           if (n.type && n.type === 'long') {
             return +s;
           }
-          if (n.normalize) {
+          if (typeof n === 'object' && n.normalize) { // see GH-491
             return n.normalize(s);
           }
           return s;
@@ -201,6 +228,11 @@ function define(elementClass, def) {
   tagNames.forEach(function(tag) {
     core.Document.prototype._elementBuilders[tag.toLowerCase()] = function(doc, s) {
       var el = new elem(doc, s);
+
+      if (def.elementBuilder) {
+        return def.elementBuilder(el, doc, s);
+      }
+
       return el;
     };
   });
@@ -209,11 +241,17 @@ function define(elementClass, def) {
 
 
 core.HTMLCollection = function HTMLCollection(element, query) {
+  this._keys = [];
   core.NodeList.call(this, element, query);
 };
 core.HTMLCollection.prototype = {
-  namedItem : function(name) {
-    var results = this.toArray(),
+  namedItem: function(name) {
+    // Try property shortcut; should work in most cases
+    if (Object.prototype.hasOwnProperty.call(this, name)) {
+      return this[name];
+    }
+
+    var results = this._toArray(),
         l       = results.length,
         node,
         matchingName = null;
@@ -230,8 +268,38 @@ core.HTMLCollection.prototype = {
   },
   toString: function() {
     return '[ jsdom HTMLCollection ]: contains ' + this.length + ' items';
+  },
+  _resetTo: function(array) {
+    var i, _this = this;
+
+    for (i = 0; i < this._keys.length; ++i) {
+      delete this[this._keys[i]];
+    }
+    this._keys = [];
+
+    core.NodeList.prototype._resetTo.apply(this, arguments);
+
+    function testAttr(node, attr) {
+      var val = node.getAttribute(attr);
+      if (val && !Object.prototype.hasOwnProperty.call(_this, val)) {
+        _this[val] = node;
+        _this._keys.push(val);
+      }
+    }
+    for (i = 0; i < array.length; ++i) {
+      testAttr(array[i], 'id');
+    }
+    for (i = 0; i < array.length; ++i) {
+      testAttr(array[i], 'name');
+    }
   }
 };
+Object.defineProperty(core.HTMLCollection.prototype, 'constructor', {
+  value: core.NodeList,
+  writable: true,
+  configurable: true
+});
+
 core.HTMLCollection.prototype.__proto__ = core.NodeList.prototype;
 
 core.HTMLOptionsCollection = core.HTMLCollection;
@@ -252,7 +320,7 @@ function closest(e, tagName) {
 function descendants(e, tagName, recursive) {
   var owner = recursive ? e._ownerDocument || e : e;
   return new core.HTMLCollection(owner, core.mapper(e, function(n) {
-    return n.nodeName === tagName;
+    return n.nodeName === tagName && typeof n._publicId == 'undefined';
   }, recursive));
 }
 
@@ -318,6 +386,7 @@ core.HTMLDocument = function HTMLDocument(options) {
   core.Document.call(this, options);
   this._referrer = options.referrer;
   this._cookie = options.cookie;
+  this._cookieDomain = options.cookieDomain || '127.0.0.1';
   this._URL = options.url || '/';
   this._documentRoot = options.documentRoot || Path.dirname(this._URL);
   this._queue = new ResourceQueue(options.deferClose);
@@ -376,7 +445,7 @@ core.HTMLDocument.prototype = {
     return this.getElementsByTagName('A');
   },
   open  : function() {
-    this._childNodes = [];
+    this._childNodes = new core.NodeList();
     this._documentElement = null;
     this._modified();
   },
@@ -393,9 +462,25 @@ core.HTMLDocument.prototype = {
   },
 
   write : function(text) {
-    if (this.readyState === "loading") {
+    if (this._writeAfterElement) {
+      // If called from an script element directly (during the first tick),
+      // the new elements are inserted right after that element.
+      var tempDiv       = this.createElement('div');
+      tempDiv.innerHTML = text;
+
+      var child    = tempDiv.firstChild;
+      var previous = this._writeAfterElement;
+      var parent   = this._writeAfterElement.parentNode;
+
+      while (child) {
+        var node = child;
+        child    = child.nextSibling;
+        parent.insertBefore(node, previous.nextSibling);
+        previous = node;
+      }
+    } else if (this.readyState === "loading") {
       // During page loading, document.write appends to the current element
-      // Find the last child that has been added to the document.
+      // Find the last child that has ben added to the document.
       var node = this;
       while (node.lastChild && node.lastChild.nodeType === this.ELEMENT_NODE) {
         node = node.lastChild;
@@ -440,7 +525,7 @@ core.HTMLDocument.prototype = {
     return firstChild(this.documentElement, 'HEAD');
   },
 
-  set head() { /* noop */ },
+  set head(unused) { /* noop */ },
 
   get body() {
     var body = firstChild(this.documentElement, 'BODY');
@@ -481,6 +566,16 @@ define('HTMLElement', {
       }
       return outcome;
     },
+    getBoundingClientRect: function () {
+      return {
+        bottom: 0,
+        height: 0,
+        left: 0,
+        right: 0,
+        top: 0,
+        width: 0
+      };
+    },
     _eventDefaults : {}
   },
   attributes: [
@@ -520,7 +615,7 @@ define('HTMLFormElement', {
     submit: function() {
     },
     reset: function() {
-      this.elements.toArray().forEach(function(el) {
+      this.elements._toArray().forEach(function(el) {
         el.value = el.defaultValue;
       });
     }
@@ -668,13 +763,13 @@ define('HTMLSelectElement', {
     },
 
     get selectedIndex() {
-      return this.options.toArray().reduceRight(function(prev, option, i) {
+      return this.options._toArray().reduceRight(function(prev, option, i) {
         return option.selected ? i : prev;
       }, -1);
     },
 
     set selectedIndex(index) {
-      this.options.toArray().forEach(function(option, i) {
+      this.options._toArray().forEach(function(option, i) {
         option.selected = i === index;
       });
     },
@@ -692,7 +787,7 @@ define('HTMLSelectElement', {
 
     set value(val) {
       var self = this;
-      this.options.toArray().forEach(function(option) {
+      this.options._toArray().forEach(function(option) {
         if (option.value === val) {
           option.selected = true;
         } else {
@@ -710,7 +805,7 @@ define('HTMLSelectElement', {
     },
 
     get type() {
-      return this.multiple ? 'select-multiple' : 'select';
+      return this.multiple ? 'select-multiple' : 'select-one';
     },
 
     add: function(opt, before) {
@@ -723,20 +818,20 @@ define('HTMLSelectElement', {
     },
 
     remove: function(index) {
-      var opts = this.options.toArray();
+      var opts = this.options._toArray();
       if (index >= 0 && index < opts.length) {
         var el = opts[index];
         el._parentNode.removeChild(el);
       }
     },
 
-    blur: function() {
-      //TODO
+    blur : function() {
+      this._ownerDocument.activeElement = this._ownerDocument.body;
     },
-
-    focus: function() {
-      //TODO
+    focus : function() {
+      this._ownerDocument.activeElement = this;
     }
+
   },
   attributes: [
     {prop: 'disabled', type: 'boolean'},
@@ -768,28 +863,48 @@ define('HTMLOptionElement', {
       return closest(this, 'FORM');
     },
     get defaultSelected() {
-      return !!this.getAttribute('selected');
+      return this.getAttribute('selected') !== null;
     },
     set defaultSelected(s) {
       if (s) this.setAttribute('selected', 'selected');
       else this.removeAttribute('selected');
     },
     get text() {
-        return (this.hasAttribute('value')) ? this.getAttribute('value') : this.innerHTML;
+      return this.innerHTML;
     },
     get value() {
-        return (this.hasAttribute('value')) ? this.getAttribute('value') : this.innerHTML;
+      return (this.hasAttribute('value')) ? this.getAttribute('value') : this.innerHTML;
     },
     set value(val) {
       this.setAttribute('value', val);
     },
     get index() {
-      return closest(this, 'SELECT').options.toArray().indexOf(this);
+      return closest(this, 'SELECT').options._toArray().indexOf(this);
     },
     get selected() {
       if (this._selected === undefined) {
         this._selected = this.defaultSelected;
       }
+
+      if (!this._selected && this.parentNode) {
+        var select = closest(this, 'SELECT');
+
+        if (select) {
+          var options = select.options;
+
+          if (options.item(0) === this && !select.hasAttribute('multiple')) {
+            var found = false, optArray = options._toArray();
+
+            for (var i = 1, l = optArray.length; i<l; i++) {
+              if (optArray[i]._selected) {
+                return false;
+              }
+            }
+            return true;
+          }
+        }
+      }
+
       return this._selected;
     },
     set selected(s) {
@@ -824,6 +939,11 @@ define('HTMLOptionElement', {
 
 define('HTMLInputElement', {
   tagName: 'INPUT',
+  init: function() {
+    if (!this.hasAttribute('type')) {
+      this.setAttribute('type', 'text');
+    }
+  },
   proto: {
     _initDefaultValue: function() {
       if (this._defaultValue === undefined) {
@@ -848,11 +968,23 @@ define('HTMLInputElement', {
       return this._initDefaultChecked();
     },
     get checked() {
-      return !!this.getAttribute('checked');
+      return !!this._attributes.getNamedItem('checked');
     },
     set checked(checked) {
       this._initDefaultChecked();
-      this.setAttribute('checked', checked);
+      if (checked) {
+        this.setAttribute('checked', 'checked');
+        if (this.type === 'radio') {
+          var elements = this._ownerDocument.getElementsByName(this.name);
+          for (var i = 0; i < elements.length; i++) {
+            if (elements[i] !== this && elements[i].tagName === "INPUT" && elements[i].type === "radio") {
+              elements[i].checked = false;
+            }
+          }
+        }
+      } else {
+        this.removeAttribute('checked');
+      }
     },
     get value() {
       return this.getAttribute('value');
@@ -866,22 +998,35 @@ define('HTMLInputElement', {
         this.setAttribute('value', val);
       }
     },
-    blur: function() {
+    blur : function() {
+      this._ownerDocument.activeElement = this._ownerDocument.body;
     },
-    focus: function() {
+    focus : function() {
+      this._ownerDocument.activeElement = this;
     },
     select: function() {
     },
+
+    _dispatchClickEvent: function() {
+      var event = this._ownerDocument.createEvent("HTMLEvents");
+      event.initEvent("click", true, true);
+      this.dispatchEvent(event);
+    },
+
     click: function() {
-      if (this.type === 'checkbox' || this.type === 'radio') {
+      if (this.type === 'checkbox') {
         this.checked = !this.checked;
       }
+      else if (this.type === 'radio') {
+        this.checked = true;
+      }
       else if (this.type === 'submit') {
         var form = this.form;
         if (form) {
           form._dispatchSubmitEvent();
         }
       }
+      this._dispatchClickEvent();
     }
   },
   attributes: [
@@ -928,9 +1073,11 @@ define('HTMLTextAreaElement', {
     get type() {
       return 'textarea';
     },
-    blur: function() {
+    blur : function() {
+      this._ownerDocument.activeElement = this._ownerDocument.body;
     },
-    focus: function() {
+    focus : function() {
+      this._ownerDocument.activeElement = this;
     },
     select: function() {
     }
@@ -952,6 +1099,12 @@ define('HTMLButtonElement', {
   proto: {
     get form() {
       return closest(this, 'FORM');
+    },
+    focus : function() {
+      this._ownerDocument.activeElement = this;
+    },
+    blur : function() {
+      this._ownerDocument.activeElement = this._ownerDocument.body;
     }
   },
   attributes: [
@@ -1045,6 +1198,27 @@ define('HTMLLIElement', {
   ]
 });
 
+define('HTMLCanvasElement', {
+  tagName: 'CANVAS',
+  attributes: [
+    'align'
+  ],
+  elementBuilder: function (element) {
+    // require node-canvas and catch the error if it blows up
+    try {
+      var canvas = new (require('canvas'))(0,0);
+      for (var attr in element) {
+        if (!canvas[attr]) {
+          canvas[attr] = element[attr];
+        }
+      }
+      return canvas;
+    } catch (e) {
+      return element;
+    }
+  }
+});
+
 define('HTMLDivElement', {
   tagName: 'DIV',
   attributes: [
@@ -1127,12 +1301,26 @@ define('HTMLAnchorElement', {
   tagName: 'A',
 
   proto: {
-    blur: function() {
+    blur : function() {
+      this._ownerDocument.activeElement = this._ownerDocument.body;
     },
-    focus: function() {
+    focus : function() {
+      this._ownerDocument.activeElement = this;
     },
     get href() {
       return core.resourceLoader.resolve(this._ownerDocument, this.getAttribute('href'));
+    },
+    get hostname() {
+      return URL.parse(this.href).hostname;
+    },
+    get host() {
+      return URL.parse(this.href).host;
+    },
+    get pathname() {
+      return URL.parse(this.href).pathname;
+    },
+    get hash() {
+      return URL.parse(this.href).hash;
     }
   },
   attributes: [
@@ -1153,6 +1341,16 @@ define('HTMLAnchorElement', {
 
 define('HTMLImageElement', {
   tagName: 'IMG',
+  proto: {
+    _attrModified: function(name, value, oldVal) {
+      if (name == 'src' && value !== oldVal) {
+        core.resourceLoader.enqueue(this, function() {})();
+      }
+    },
+    get src() {
+      return core.resourceLoader.resolve(this._ownerDocument, this.getAttribute('src'));
+    }
+  },
   attributes: [
     'name',
     'align',
@@ -1162,7 +1360,7 @@ define('HTMLImageElement', {
     {prop: 'hspace', type: 'long'},
     {prop: 'isMap', type: 'boolean'},
     'longDesc',
-    'src',
+    {prop: 'src', type: 'string', read: false},
     'useMap',
     {prop: 'vspace', type: 'long'},
     {prop: 'width', type: 'long'}
@@ -1279,7 +1477,9 @@ define('HTMLScriptElement', {
           this.language                                                                      &&
           core.languageProcessors[this.language])
       {
+        this._ownerDocument._writeAfterElement = this;
         core.languageProcessors[this.language](this, text, filename);
+        delete this._ownerDocument._writeAfterElement;
       }
     },
     get language() {
@@ -1290,7 +1490,7 @@ define('HTMLScriptElement', {
       var i=0, children = this.childNodes, l = children.length, ret = [];
 
       for (i; i<l; i++) {
-        ret.push(children.item(i).value);
+        ret.push(children.item(i).nodeValue);
       }
 
       return ret.join("");
@@ -1328,7 +1528,7 @@ define('HTMLTableElement', {
       if (!this._rows) {
         var table = this;
         this._rows = new core.HTMLCollection(this._ownerDocument, function() {
-          var sections = [table.tHead].concat(table.tBodies.toArray(), table.tFoot).filter(function(s) { return !!s });
+          var sections = [table.tHead].concat(table.tBodies._toArray(), table.tFoot).filter(function(s) { return !!s });
 
           if (sections.length === 0) {
             return core.mapDOMNodes(table, false, function(el) {
@@ -1337,7 +1537,7 @@ define('HTMLTableElement', {
           }
 
           return sections.reduce(function(prev, s) {
-            return prev.concat(s.rows.toArray());
+            return prev.concat(s.rows._toArray());
           }, []);
 
         });
@@ -1397,7 +1597,7 @@ define('HTMLTableElement', {
       if (this.childNodes.length === 0) {
         this.appendChild(this._ownerDocument.createElement('TBODY'));
       }
-      var rows = this.rows.toArray();
+      var rows = this.rows._toArray();
       if (index < -1 || index > rows.length) {
         throw new core.DOMException(core.INDEX_SIZE_ERR);
       }
@@ -1415,7 +1615,7 @@ define('HTMLTableElement', {
       return tr;
     },
     deleteRow: function(index) {
-      var rows = this.rows.toArray(), l = rows.length;
+      var rows = this.rows._toArray(), l = rows.length;
       if (index === -1) {
         index = l-1;
       }
@@ -1469,7 +1669,7 @@ define('HTMLTableSectionElement', {
     },
     insertRow: function(index) {
       var tr = this._ownerDocument.createElement('TR');
-      var rows = this.rows.toArray();
+      var rows = this.rows._toArray();
       if (index < -1 || index > rows.length) {
         throw new core.DOMException(core.INDEX_SIZE_ERR);
       }
@@ -1483,7 +1683,7 @@ define('HTMLTableSectionElement', {
       return tr;
     },
     deleteRow: function(index) {
-      var rows = this.rows.toArray();
+      var rows = this.rows._toArray();
       if (index === -1) {
         index = rows.length-1;
       }
@@ -1516,15 +1716,16 @@ define('HTMLTableRowElement', {
       return this._cells;
     },
     get rowIndex() {
-      return closest(this, 'TABLE').rows.toArray().indexOf(this);
+      var table = closest(this, 'TABLE');
+      return table ? table.rows._toArray().indexOf(this) : -1;
     },
 
     get sectionRowIndex() {
-      return this._parentNode.rows.toArray().indexOf(this);
+      return this._parentNode.rows._toArray().indexOf(this);
     },
     insertCell: function(index) {
       var td = this._ownerDocument.createElement('TD');
-      var cells = this.cells.toArray();
+      var cells = this.cells._toArray();
       if (index < -1 || index > cells.length) {
         throw new core.DOMException(core.INDEX_SIZE_ERR);
       }
@@ -1538,7 +1739,7 @@ define('HTMLTableRowElement', {
       return td;
     },
     deleteCell: function(index) {
-      var cells = this.cells.toArray();
+      var cells = this.cells._toArray();
       if (index === -1) {
         index = cells.length-1;
       }
@@ -1591,7 +1792,7 @@ define('HTMLTableCellElement', {
       return headings.join(' ');
     },
     get cellIndex() {
-      return closest(this, 'TR').cells.toArray().indexOf(this);
+      return closest(this, 'TR').cells._toArray().indexOf(this);
     }
   },
   attributes: [
@@ -1682,14 +1883,18 @@ define('HTMLFrameElement', {
     }, false);
   },
   proto: {
-    setAttribute: function(name, value) {
-      core.HTMLElement.prototype.setAttribute.call(this, name, value);
+    _attrModified: function(name, value, oldVal) {
+      core.HTMLElement.prototype._attrModified.call(this, name, value, oldVal);
       var self = this;
       if (name === 'name') {
+        // Remove named frame access.
+        if (oldVal) {
+          this._ownerDocument.parentWindow._frame(oldVal);
+        }
         // Set up named frame access.
-        this._ownerDocument.parentWindow.__defineGetter__(value, function () {
-          return self.contentWindow;
-        });
+        if (value) {
+          this._ownerDocument.parentWindow._frame(value, this);
+        }
       } else if (name === 'src') {
         // Page we don't fetch the page until the node is inserted. This at
         // least seems to be the way Chrome does it.
diff --git a/lib/jsdom/level2/style.js b/lib/jsdom/level2/style.js
index 4cdc4de..bb95737 100644
--- a/lib/jsdom/level2/style.js
+++ b/lib/jsdom/level2/style.js
@@ -2,6 +2,7 @@ var core = require("./core").dom.level2.core,
     html = require("./html").dom.level2.html,
     utils = require("../utils"),
     cssom = require("cssom"),
+    cssstyle = require("cssstyle"),
     assert = require('assert');
 
 // What works now:
@@ -28,7 +29,7 @@ core.CSSRule = cssom.CSSRule;
 core.CSSStyleRule = cssom.CSSStyleRule;
 core.CSSMediaRule = cssom.CSSMediaRule;
 core.CSSImportRule = cssom.CSSImportRule;
-core.CSSStyleDeclaration = cssom.CSSStyleDeclaration;
+core.CSSStyleDeclaration = cssstyle.CSSStyleDeclaration;
 
 // Relavant specs
 // http://www.w3.org/TR/DOM-Level-2-Style (2000)
@@ -56,9 +57,24 @@ core.CSSStyleDeclaration = cssom.CSSStyleDeclaration;
 //   Rect
 //   Counter
 
-// StyleSheetList has the same interface as NodeList, so we'll use the same
-// object.
-core.StyleSheetList = core.NodeList;
+// StyleSheetList -
+// http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html#StyleSheets-StyleSheetList
+// added a push method to help manage the length
+core.StyleSheetList = function() {
+  this._length = 0;
+};
+core.StyleSheetList.prototype = {
+  item: function (i) {
+    return this[i];
+  },
+  push: function (sheet) {
+    this[this._length] = sheet;
+    this._length++;
+  },
+  get length() {
+    return this._length;
+  }
+};
 
 core.Document.prototype.__defineGetter__('styleSheets', function() {
   if (!this._styleSheets) {
@@ -80,6 +96,7 @@ function fetchStylesheet(url, sheet) {
   html.resourceLoader.load(this, url, function(data, filename) {
     // TODO: abort if the content-type is not text/css, and the document is
     // in strict mode
+    sheet.href = html.resourceLoader.resolve(this.ownerDocument, url);
     evaluateStylesheet.call(this, data, sheet, url);
   });
 }
@@ -96,6 +113,7 @@ function evaluateStylesheet(data, sheet, baseUrl) {
   spliceArgs.unshift(0, sheet.cssRules.length);
   Array.prototype.splice.apply(sheet.cssRules, spliceArgs);
   scanForImportRules.call(this, sheet.cssRules, baseUrl);
+  this.ownerDocument.styleSheets.push(sheet);
 }
 /**
  * @this {html.HTMLLinkElement|html.HTMLStyleElement}
@@ -120,28 +138,11 @@ function scanForImportRules(cssRules, baseUrl) {
 
 /**
  * @param {string} data
- * @param {cssom.CSSStyleDeclaration} style
+ * @param {cssstyle.CSSStyleDeclaration} style
  */
 function evaluateStyleAttribute(data) {
   // this is the element.
 
-  // currently, cssom's parse doesn't really work if you pass in
-  // {state: 'name'}, so instead we just build a dummy sheet.
-  var styleSheet = cssom.parse('dummy{' + data + '}');
-  var style = this.style;
-  while (style.length) {
-    style.removeProperty(style[0]);
-  }
-  if (styleSheet.cssRules.length > 0 && styleSheet.cssRules[0].style) {
-    var newStyle = styleSheet.cssRules[0].style;
-    for (var i = 0; i < newStyle.length; ++i) {
-      var prop = newStyle[i];
-      style.setProperty(
-          prop,
-          newStyle.getPropertyValue(prop),
-          newStyle.getPropertyPriority(prop));
-    }
-  }
 }
 
 /**
@@ -150,14 +151,20 @@ function evaluateStyleAttribute(data) {
 function StyleAttr(node, value) {
   this._node = node;
   core.Attr.call(this, node.ownerDocument, 'style');
-  this.nodeValue = value;
+  if (!this._node._ignoreValueOfStyleAttr) {
+    this.nodeValue = value;
+  }
 }
 StyleAttr.prototype = {
   get nodeValue() {
-    return this._node.style.cssText;
+    if (typeof this._node._style === 'string') {
+      return this._node._style;
+    } else {
+      return this._node.style.cssText;
+    }
   },
   set nodeValue(value) {
-    evaluateStyleAttribute.call(this._node, value);
+    this._node._style = value;
   }
 };
 StyleAttr.prototype.__proto__ = core.Attr.prototype;
@@ -177,14 +184,33 @@ utils.intercept(core.AttrNodeMap, 'setNamedItem', function(_super, args, attr) {
  * Lazily create a CSSStyleDeclaration.
  */
 html.HTMLElement.prototype.__defineGetter__('style', function() {
-  var style = this._cssStyleDeclaration;
-  if (!style) {
-    style = this._cssStyleDeclaration = new cssom.CSSStyleDeclaration();
-    if (!this.getAttributeNode('style')) {
-      this.setAttribute('style', '');
+  if (typeof this._style === 'string') {
+    // currently, cssom's parse doesn't really work if you pass in
+    // {state: 'name'}, so instead we just build a dummy sheet.
+    var styleSheet = cssom.parse('dummy{' + this._style + '}');
+    this._style = new cssstyle.CSSStyleDeclaration();
+    if (styleSheet.cssRules.length > 0 && styleSheet.cssRules[0].style) {
+      var newStyle = styleSheet.cssRules[0].style;
+      for (var i = 0; i < newStyle.length; ++i) {
+        var prop = newStyle[i];
+        this._style.setProperty(
+            prop,
+            newStyle.getPropertyValue(prop),
+            newStyle.getPropertyPriority(prop));
+      }
     }
   }
-  return style;
+  if (!this._style) {
+    this._style = new cssstyle.CSSStyleDeclaration();
+
+  }
+  if (!this.getAttributeNode('style')) {
+    // Tell the StyleAttr constructor to not overwrite this._style
+    this._ignoreValueOfStyleAttr = true;
+    this.setAttribute('style');
+    this._ignoreValueOfStyleAttr = false;
+  }
+  return this._style;
 });
 
 assert.equal(undefined, html.HTMLLinkElement._init);
@@ -223,7 +249,13 @@ html.HTMLStyleElement._init = function() {
       //console.log('bad type: ' + this.type)
       return;
     }
-    evaluateStylesheet.call(this, this.textContent, this.sheet, this._ownerDocument.URL);
+    var content = '';
+    Array.prototype.forEach.call(this.childNodes, function (child) {
+      if (child.nodeType === child.TEXT_NODE) { // text node
+        content += child.nodeValue;
+      }
+    });
+    evaluateStylesheet.call(this, content, this.sheet, this._ownerDocument.URL);
   });
 };
 html.HTMLStyleElement.prototype.__defineGetter__('sheet', getOrCreateSheet);
diff --git a/lib/jsdom/level3/core.js b/lib/jsdom/level3/core.js
index d6d18bd..c9221e7 100644
--- a/lib/jsdom/level3/core.js
+++ b/lib/jsdom/level3/core.js
@@ -103,10 +103,10 @@ core.Node.prototype.compareDocumentPosition = function compareDocumentPosition(
   if( thisOwner !== otherOwner ) return DOCUMENT_POSITION_DISCONNECTED
 
   // Text nodes for attributes does not have a _parentNode. So we need to find them as attribute child.
-  if( this.nodeType === this.ATTRIBUTE_NODE && this._childNodes && this._childNodes.indexOf(otherNode) !== -1)
+  if( this.nodeType === this.ATTRIBUTE_NODE && this._childNodes && this._childNodes._toArray().indexOf(otherNode) !== -1)
     return DOCUMENT_POSITION_FOLLOWING + DOCUMENT_POSITION_CONTAINED_BY
 
-  if( otherNode.nodeType === this.ATTRIBUTE_NODE && otherNode._childNodes && otherNode._childNodes.indexOf(this) !== -1)
+  if( otherNode.nodeType === this.ATTRIBUTE_NODE && otherNode._childNodes && otherNode._childNodes._toArray().indexOf(this) !== -1)
     return DOCUMENT_POSITION_PRECEDING + DOCUMENT_POSITION_CONTAINS
 
   var point = this
@@ -124,8 +124,8 @@ core.Node.prototype.compareDocumentPosition = function compareDocumentPosition(
     var location_index = parents.indexOf( point )
     if( location_index !== -1) {
      var smallest_common_ancestor = parents[ location_index ]
-     var this_index = smallest_common_ancestor._childNodes.indexOf( parents[location_index - 1] )
-     var other_index = smallest_common_ancestor._childNodes.indexOf( previous )
+     var this_index = smallest_common_ancestor._childNodes._toArray().indexOf( parents[location_index - 1] )
+     var other_index = smallest_common_ancestor._childNodes._toArray().indexOf( previous )
      if( this_index > other_index ) {
            return DOCUMENT_POSITION_PRECEDING
      }
@@ -148,31 +148,40 @@ core.Node.prototype.isSameNode = function(other) {
   return (other === this);
 };
 
+// @see http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#Node3-textContent
 core.Node.prototype.__defineGetter__('textContent', function() {
-  if (this.nodeType === this.TEXT_NODE || this.nodeType === this.COMMENT_NODE || this.nodeType === this.ATTRIBUTE_NODE || this.nodeType === this.CDATA_SECTION_NODE) {
-    return this.nodeValue;
-  } else if (this.nodeType === this.ELEMENT_NODE || this.nodeType === this.DOCUMENT_FRAGMENT_NODE) {
-    var out = '';
-    for (var i = 0 ; i < this.childNodes.length ; i += 1) {
-      out += this.childNodes[i].textContent || '';
-    }
-    return out;
-  } else {
-    return null;
+  switch (this.nodeType) {
+    case this.COMMENT_NODE:
+    case this.CDATA_SECTION_NODE:
+    case this.PROCESSING_INSTRUCTION_NODE:
+    case this.TEXT_NODE:
+      return this.nodeValue;
+
+    case this.ATTRIBUTE_NODE:
+    case this.DOCUMENT_FRAGMENT_NODE:
+    case this.ELEMENT_NODE:
+    case this.ENTITY_NODE:
+    case this.ENTITY_REFERENCE_NODE:
+      var out = '';
+      for (var i = 0 ; i < this.childNodes.length ; ++i) {
+        if (this.childNodes[i].nodeType !== this.COMMENT_NODE &&
+            this.childNodes[i].nodeType !== this.PROCESSING_INSTRUCTION_NODE) {
+          out += this.childNodes[i].textContent || '';
+        }
+      }
+      return out;
+
+    default:
+      return null;
   }
 });
 
 core.Node.prototype.__defineSetter__('textContent', function(txt) {
-  if (txt) {
-    var i        = this.childNodes.length-1,
-        children = this.childNodes,
-        textNode = this._ownerDocument.createTextNode(txt);
-
-    for (i; i>=0; i--) {
-      this.removeChild(this.childNodes.item(i));
-    }
-
-    this.appendChild(textNode);
+  for (var i = this.childNodes.length; --i >=0;) {
+    this.removeChild(this.childNodes.item(i));
+  }
+  if (txt !== "" && txt != null) {
+    this.appendChild(this._ownerDocument.createTextNode(txt));
   }
   return txt;
 });
diff --git a/lib/jsdom/level3/index.js b/lib/jsdom/level3/index.js
index ec7ea6e..03bf073 100644
--- a/lib/jsdom/level3/index.js
+++ b/lib/jsdom/level3/index.js
@@ -1,7 +1,7 @@
 module.exports.dom = {
   level3 : {
     core   : require("./core").dom.level3.core,
-    xpath  : require("./xpath").xpath,
+    xpath  : require("./xpath"),
     events : require("./events").dom.level3.events,
     html   : require("./html").dom.level3.html,
   }
diff --git a/lib/jsdom/level3/xpath.js b/lib/jsdom/level3/xpath.js
index 8eee6cc..a45ec68 100644
--- a/lib/jsdom/level3/xpath.js
+++ b/lib/jsdom/level3/xpath.js
@@ -1202,8 +1202,7 @@
           }
         }
         nodeMultiSet.finalize();
-        r = sortNodeMultiSet(nodeMultiSet);
-        return r;
+        return sortNodeMultiSet(nodeMultiSet);
       },
     'descendant':
       function descenant(nodeList  /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
@@ -1680,7 +1679,7 @@
     this.name = 'XPathException';
     this.code = code;
   }
-  XPathException.prototype = Error.prototype;
+  XPathException.prototype = Object.create(Error.prototype);
   XPathException.prototype.__proto__ = XPathException;
   XPathException.INVALID_EXPRESSION_ERR = 51;
   XPathException.TYPE_ERR = 52;
diff --git a/lib/jsdom/selectors/index.js b/lib/jsdom/selectors/index.js
index e2f1776..36e90af 100644
--- a/lib/jsdom/selectors/index.js
+++ b/lib/jsdom/selectors/index.js
@@ -1,31 +1,31 @@
-var createSizzle = require("./sizzle");
-exports.applyQuerySelectorPrototype = function(dom) {
-  var addSizzle = function(document) {
+var nwmatcher = require("nwmatcher/src/nwmatcher-noqsa");
 
-    if (!document._sizzle) {
-      document._sizzle = createSizzle(document);
-    }
-    return document._sizzle;
-  };
+function addNwmatcher(document) {
+  if (!document._nwmatcher) {
+    document._nwmatcher = nwmatcher({ document: document });
+    document._nwmatcher.configure({ UNIQUE_ID: false });
+  }
+  return document._nwmatcher;
+}
 
+exports.applyQuerySelectorPrototype = function(dom) {
   dom.Document.prototype.querySelector = function(selector) {
-    return addSizzle(this)(selector, this)[0];
+    return addNwmatcher(this).first(selector, this);
   };
 
   dom.Document.prototype.querySelectorAll = function(selector) {
-    return new dom.NodeList(addSizzle(this)(selector, this));
+    return new dom.NodeList(addNwmatcher(this).select(selector, this));
   };
 
   dom.Element.prototype.querySelector = function(selector) {
-    return addSizzle(this.ownerDocument)(selector, this)[0];
+    return addNwmatcher(this.ownerDocument).first(selector, this);
   };
 
   dom.Element.prototype.querySelectorAll = function(selector) {
-    var el = this;
-    if (!this.parentNode) {
-      el = this.ownerDocument.createElement("div");
-      el.appendChild(this);
-    }
-    return new dom.NodeList(addSizzle(this.ownerDocument)(selector, el.parentNode || el));
+    return new dom.NodeList(addNwmatcher(this.ownerDocument).select(selector, this));
+  };
+
+  dom.Element.prototype.matchesSelector = function(selector) {
+    return addNwmatcher(this.ownerDocument).match(this, selector);
   };
 };
diff --git a/lib/jsdom/selectors/sizzle.js b/lib/jsdom/selectors/sizzle.js
deleted file mode 100755
index f8c890c..0000000
--- a/lib/jsdom/selectors/sizzle.js
+++ /dev/null
@@ -1,1449 +0,0 @@
-/*!
- * Sizzle CSS Selector Engine
- *  Copyright 2011, The Dojo Foundation
- *  Released under the MIT, BSD, and GPL Licenses.
- *  More information: http://sizzlejs.com/
- */
-// Patch for jsdom
-module.exports = function(document){
-
-var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
-	expando = "sizcache" + (Math.random() + '').replace('.', ''),
-	done = 0,
-	toString = Object.prototype.toString,
-	hasDuplicate = false,
-	baseHasDuplicate = true,
-	rBackslash = /\\/g,
-	rReturn = /\r\n/g,
-	rNonWord = /\W/;
-
-
-// Here we check if the JavaScript engine is using some sort of
-// optimization where it does not always call our comparision
-// function. If that is the case, discard the hasDuplicate value.
-//   Thus far that includes Google Chrome.
-[0, 0].sort(function() {
-	baseHasDuplicate = false;
-	return 0;
-});
-
-var Sizzle = function( selector, context, results, seed ) {
-	results = results || [];
-	// PATCH for jsdom
-	// context = context || document;
-	// See: https://github.com/tmpvar/jsdom/issues/375
-	context = context || seed[0].ownerDocument;
-	var origContext = context;
-
-	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
-		return [];
-	}
-
-	if ( !selector || typeof selector !== "string" ) {
-		return results;
-	}
-
-	var m, set, checkSet, extra, ret, cur, pop, i,
-		prune = true,
-		contextXML = Sizzle.isXML( context ),
-		parts = [],
-		soFar = selector;
-
-	// Reset the position of the chunker regexp (start from head)
-	do {
-		chunker.exec( "" );
-		m = chunker.exec( soFar );
-
-		if ( m ) {
-			soFar = m[3];
-
-			parts.push( m[1] );
-
-			if ( m[2] ) {
-				extra = m[3];
-				break;
-			}
-		}
-	} while ( m );
-
-	if ( parts.length > 1 && origPOS.exec( selector ) ) {
-
-		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
-			set = posProcess( parts[0] + parts[1], context, seed );
-
-		} else {
-			set = Expr.relative[ parts[0] ] ?
-				[ context ] :
-				Sizzle( parts.shift(), context );
-
-			while ( parts.length ) {
-				selector = parts.shift();
-
-				if ( Expr.relative[ selector ] ) {
-					selector += parts.shift();
-				}
-
-				set = posProcess( selector, set, seed );
-			}
-		}
-
-	} else {
-		// Take a shortcut and set the context if the root selector is an ID
-		// (but not if it'll be faster if the inner selector is an ID)
-		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
-				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
-
-			ret = Sizzle.find( parts.shift(), context, contextXML );
-			context = ret.expr ?
-				Sizzle.filter( ret.expr, ret.set )[0] :
-				ret.set[0];
-		}
-
-		if ( context ) {
-			ret = seed ?
-				{ expr: parts.pop(), set: makeArray(seed) } :
-				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
-
-			set = ret.expr ?
-				Sizzle.filter( ret.expr, ret.set ) :
-				ret.set;
-
-			if ( parts.length > 0 ) {
-				checkSet = makeArray( set );
-
-			} else {
-				prune = false;
-			}
-
-			while ( parts.length ) {
-				cur = parts.pop();
-				pop = cur;
-
-				if ( !Expr.relative[ cur ] ) {
-					cur = "";
-				} else {
-					pop = parts.pop();
-				}
-
-				if ( pop == null ) {
-					pop = context;
-				}
-
-				Expr.relative[ cur ]( checkSet, pop, contextXML );
-			}
-
-		} else {
-			checkSet = parts = [];
-		}
-	}
-
-	if ( !checkSet ) {
-		checkSet = set;
-	}
-
-	if ( !checkSet ) {
-		Sizzle.error( cur || selector );
-	}
-
-	if ( toString.call(checkSet) === "[object Array]" ) {
-		if ( !prune ) {
-			results.push.apply( results, checkSet );
-
-		} else if ( context && context.nodeType === 1 ) {
-			for ( i = 0; checkSet[i] != null; i++ ) {
-				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
-					results.push( set[i] );
-				}
-			}
-
-		} else {
-			for ( i = 0; checkSet[i] != null; i++ ) {
-				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
-					results.push( set[i] );
-				}
-			}
-		}
-
-	} else {
-		makeArray( checkSet, results );
-	}
-
-	if ( extra ) {
-		Sizzle( extra, origContext, results, seed );
-		Sizzle.uniqueSort( results );
-	}
-
-	return results;
-};
-
-Sizzle.uniqueSort = function( results ) {
-	if ( sortOrder ) {
-		hasDuplicate = baseHasDuplicate;
-		results.sort( sortOrder );
-
-		if ( hasDuplicate ) {
-			for ( var i = 1; i < results.length; i++ ) {
-				if ( results[i] === results[ i - 1 ] ) {
-					results.splice( i--, 1 );
-				}
-			}
-		}
-	}
-
-	return results;
-};
-
-Sizzle.matches = function( expr, set ) {
-	return Sizzle( expr, null, null, set );
-};
-
-Sizzle.matchesSelector = function( node, expr ) {
-	return Sizzle( expr, null, null, [node] ).length > 0;
-};
-
-Sizzle.find = function( expr, context, isXML ) {
-	var set, i, len, match, type, left;
-
-	if ( !expr ) {
-		return [];
-	}
-
-	for ( i = 0, len = Expr.order.length; i < len; i++ ) {
-		type = Expr.order[i];
-
-		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
-			left = match[1];
-			match.splice( 1, 1 );
-
-			if ( left.substr( left.length - 1 ) !== "\\" ) {
-				match[1] = (match[1] || "").replace( rBackslash, "" );
-				set = Expr.find[ type ]( match, context, isXML );
-
-				if ( set != null ) {
-					expr = expr.replace( Expr.match[ type ], "" );
-					break;
-				}
-			}
-		}
-	}
-
-	if ( !set ) {
-		set = typeof context.getElementsByTagName !== "undefined" ?
-			context.getElementsByTagName( "*" ) :
-			[];
-	}
-
-	return { set: set, expr: expr };
-};
-
-Sizzle.filter = function( expr, set, inplace, not ) {
-	var match, anyFound,
-		type, found, item, filter, left,
-		i, pass,
-		old = expr,
-		result = [],
-		curLoop = set,
-		isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
-
-	while ( expr && set.length ) {
-		for ( type in Expr.filter ) {
-			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
-				filter = Expr.filter[ type ];
-				left = match[1];
-
-				anyFound = false;
-
-				match.splice(1,1);
-
-				if ( left.substr( left.length - 1 ) === "\\" ) {
-					continue;
-				}
-
-				if ( curLoop === result ) {
-					result = [];
-				}
-
-				if ( Expr.preFilter[ type ] ) {
-					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
-
-					if ( !match ) {
-						anyFound = found = true;
-
-					} else if ( match === true ) {
-						continue;
-					}
-				}
-
-				if ( match ) {
-					for ( i = 0; (item = curLoop[i]) != null; i++ ) {
-						if ( item ) {
-							found = filter( item, match, i, curLoop );
-							pass = not ^ found;
-
-							if ( inplace && found != null ) {
-								if ( pass ) {
-									anyFound = true;
-
-								} else {
-									curLoop[i] = false;
-								}
-
-							} else if ( pass ) {
-								result.push( item );
-								anyFound = true;
-							}
-						}
-					}
-				}
-
-				if ( found !== undefined ) {
-					if ( !inplace ) {
-						curLoop = result;
-					}
-
-					expr = expr.replace( Expr.match[ type ], "" );
-
-					if ( !anyFound ) {
-						return [];
-					}
-
-					break;
-				}
-			}
-		}
-
-		// Improper expression
-		if ( expr === old ) {
-			if ( anyFound == null ) {
-				Sizzle.error( expr );
-
-			} else {
-				break;
-			}
-		}
-
-		old = expr;
-	}
-
-	return curLoop;
-};
-
-Sizzle.error = function( msg ) {
-	throw new Error( "Syntax error, unrecognized expression: " + msg );
-};
-
-/**
- * Utility function for retreiving the text value of an array of DOM nodes
- * @param {Array|Element} elem
- */
-var getText = Sizzle.getText = function( elem ) {
-    var i, node,
-		nodeType = elem.nodeType,
-		ret = "";
-
-	if ( nodeType ) {
-		if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
-			// Use textContent || innerText for elements
-			if ( typeof elem.textContent === 'string' ) {
-				return elem.textContent;
-			} else if ( typeof elem.innerText === 'string' ) {
-				// Replace IE's carriage returns
-				return elem.innerText.replace( rReturn, '' );
-			} else {
-				// Traverse it's children
-				for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
-					ret += getText( elem );
-				}
-			}
-		} else if ( nodeType === 3 || nodeType === 4 ) {
-			return elem.nodeValue;
-		}
-	} else {
-
-		// If no nodeType, this is expected to be an array
-		for ( i = 0; (node = elem[i]); i++ ) {
-			// Do not traverse comment nodes
-			if ( node.nodeType !== 8 ) {
-				ret += getText( node );
-			}
-		}
-	}
-	return ret;
-};
-
-var Expr = Sizzle.selectors = {
-	order: [ "ID", "NAME", "TAG" ],
-
-	match: {
-		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
-		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
-		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
-		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
-		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
-		CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
-		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
-		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
-	},
-
-	leftMatch: {},
-
-	attrMap: {
-		"class": "className",
-		"for": "htmlFor"
-	},
-
-	attrHandle: {
-		href: function( elem ) {
-			return elem.getAttribute( "href" );
-		},
-		type: function( elem ) {
-			return elem.getAttribute( "type" );
-		}
-	},
-
-	relative: {
-		"+": function(checkSet, part){
-			var isPartStr = typeof part === "string",
-				isTag = isPartStr && !rNonWord.test( part ),
-				isPartStrNotTag = isPartStr && !isTag;
-
-			if ( isTag ) {
-				part = part.toLowerCase();
-			}
-
-			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
-				if ( (elem = checkSet[i]) ) {
-					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
-
-					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
-						elem || false :
-						elem === part;
-				}
-			}
-
-			if ( isPartStrNotTag ) {
-				Sizzle.filter( part, checkSet, true );
-			}
-		},
-
-		">": function( checkSet, part ) {
-			var elem,
-				isPartStr = typeof part === "string",
-				i = 0,
-				l = checkSet.length;
-
-			if ( isPartStr && !rNonWord.test( part ) ) {
-				part = part.toLowerCase();
-
-				for ( ; i < l; i++ ) {
-					elem = checkSet[i];
-
-					if ( elem ) {
-						var parent = elem.parentNode;
-						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
-					}
-				}
-
-			} else {
-				for ( ; i < l; i++ ) {
-					elem = checkSet[i];
-
-					if ( elem ) {
-						checkSet[i] = isPartStr ?
-							elem.parentNode :
-							elem.parentNode === part;
-					}
-				}
-
-				if ( isPartStr ) {
-					Sizzle.filter( part, checkSet, true );
-				}
-			}
-		},
-
-		"": function(checkSet, part, isXML){
-			var nodeCheck,
-				doneName = done++,
-				checkFn = dirCheck;
-
-			if ( typeof part === "string" && !rNonWord.test( part ) ) {
-				part = part.toLowerCase();
-				nodeCheck = part;
-				checkFn = dirNodeCheck;
-			}
-
-			checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
-		},
-
-		"~": function( checkSet, part, isXML ) {
-			var nodeCheck,
-				doneName = done++,
-				checkFn = dirCheck;
-
-			if ( typeof part === "string" && !rNonWord.test( part ) ) {
-				part = part.toLowerCase();
-				nodeCheck = part;
-				checkFn = dirNodeCheck;
-			}
-
-			checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
-		}
-	},
-
-	find: {
-		ID: function( match, context, isXML ) {
-			if ( typeof context.getElementById !== "undefined" && !isXML ) {
-				var m = context.getElementById(match[1]);
-				// Check parentNode to catch when Blackberry 4.6 returns
-				// nodes that are no longer in the document #6963
-				return m && m.parentNode ? [m] : [];
-			}
-		},
-
-		NAME: function( match, context ) {
-			if ( typeof context.getElementsByName !== "undefined" ) {
-				var ret = [],
-					results = context.getElementsByName( match[1] );
-
-				for ( var i = 0, l = results.length; i < l; i++ ) {
-					if ( results[i].getAttribute("name") === match[1] ) {
-						ret.push( results[i] );
-					}
-				}
-
-				return ret.length === 0 ? null : ret;
-			}
-		},
-
-		TAG: function( match, context ) {
-			if ( typeof context.getElementsByTagName !== "undefined" ) {
-				return context.getElementsByTagName( match[1] );
-			}
-		}
-	},
-	preFilter: {
-		CLASS: function( match, curLoop, inplace, result, not, isXML ) {
-			match = " " + match[1].replace( rBackslash, "" ) + " ";
-
-			if ( isXML ) {
-				return match;
-			}
-
-			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
-				if ( elem ) {
-					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
-						if ( !inplace ) {
-							result.push( elem );
-						}
-
-					} else if ( inplace ) {
-						curLoop[i] = false;
-					}
-				}
-			}
-
-			return false;
-		},
-
-		ID: function( match ) {
-			return match[1].replace( rBackslash, "" );
-		},
-
-		TAG: function( match, curLoop ) {
-			return match[1].replace( rBackslash, "" ).toLowerCase();
-		},
-
-		CHILD: function( match ) {
-			if ( match[1] === "nth" ) {
-				if ( !match[2] ) {
-					Sizzle.error( match[0] );
-				}
-
-				match[2] = match[2].replace(/^\+|\s*/g, '');
-
-				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
-				var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
-					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
-					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
-
-				// calculate the numbers (first)n+(last) including if they are negative
-				match[2] = (test[1] + (test[2] || 1)) - 0;
-				match[3] = test[3] - 0;
-			}
-			else if ( match[2] ) {
-				Sizzle.error( match[0] );
-			}
-
-			// TODO: Move to normal caching system
-			match[0] = done++;
-
-			return match;
-		},
-
-		ATTR: function( match, curLoop, inplace, result, not, isXML ) {
-			var name = match[1] = match[1].replace( rBackslash, "" );
-
-			if ( !isXML && Expr.attrMap[name] ) {
-				match[1] = Expr.attrMap[name];
-			}
-
-			// Handle if an un-quoted value was used
-			match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
-
-			if ( match[2] === "~=" ) {
-				match[4] = " " + match[4] + " ";
-			}
-
-			return match;
-		},
-
-		PSEUDO: function( match, curLoop, inplace, result, not ) {
-			if ( match[1] === "not" ) {
-				// If we're dealing with a complex expression, or a simple one
-				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
-					match[3] = Sizzle(match[3], null, null, curLoop);
-
-				} else {
-					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
-
-					if ( !inplace ) {
-						result.push.apply( result, ret );
-					}
-
-					return false;
-				}
-
-			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
-				return true;
-			}
-
-			return match;
-		},
-
-		POS: function( match ) {
-			match.unshift( true );
-
-			return match;
-		}
-	},
-
-	filters: {
-		enabled: function( elem ) {
-			return elem.disabled === false && elem.type !== "hidden";
-		},
-
-		disabled: function( elem ) {
-			return elem.disabled === true;
-		},
-
-		checked: function( elem ) {
-			return elem.checked === true;
-		},
-
-		selected: function( elem ) {
-			// Accessing this property makes selected-by-default
-			// options in Safari work properly
-			if ( elem.parentNode ) {
-				elem.parentNode.selectedIndex;
-			}
-
-			return elem.selected === true;
-		},
-
-		parent: function( elem ) {
-			return !!elem.firstChild;
-		},
-
-		empty: function( elem ) {
-			return !elem.firstChild;
-		},
-
-		has: function( elem, i, match ) {
-			return !!Sizzle( match[3], elem ).length;
-		},
-
-		header: function( elem ) {
-			return (/h\d/i).test( elem.nodeName );
-		},
-
-		text: function( elem ) {
-			var attr = elem.getAttribute( "type" ), type = elem.type;
-			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
-			// use getAttribute instead to test this case
-			return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
-		},
-
-		radio: function( elem ) {
-			return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
-		},
-
-		checkbox: function( elem ) {
-			return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
-		},
-
-		file: function( elem ) {
-			return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
-		},
-
-		password: function( elem ) {
-			return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
-		},
-
-		submit: function( elem ) {
-			var name = elem.nodeName.toLowerCase();
-			return (name === "input" || name === "button") && "submit" === elem.type;
-		},
-
-		image: function( elem ) {
-			return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
-		},
-
-		reset: function( elem ) {
-			var name = elem.nodeName.toLowerCase();
-			return (name === "input" || name === "button") && "reset" === elem.type;
-		},
-
-		button: function( elem ) {
-			var name = elem.nodeName.toLowerCase();
-			return name === "input" && "button" === elem.type || name === "button";
-		},
-
-		input: function( elem ) {
-			return (/input|select|textarea|button/i).test( elem.nodeName );
-		},
-
-		focus: function( elem ) {
-			return elem === elem.ownerDocument.activeElement;
-		}
-	},
-	setFilters: {
-		first: function( elem, i ) {
-			return i === 0;
-		},
-
-		last: function( elem, i, match, array ) {
-			return i === array.length - 1;
-		},
-
-		even: function( elem, i ) {
-			return i % 2 === 0;
-		},
-
-		odd: function( elem, i ) {
-			return i % 2 === 1;
-		},
-
-		lt: function( elem, i, match ) {
-			return i < match[3] - 0;
-		},
-
-		gt: function( elem, i, match ) {
-			return i > match[3] - 0;
-		},
-
-		nth: function( elem, i, match ) {
-			return match[3] - 0 === i;
-		},
-
-		eq: function( elem, i, match ) {
-			return match[3] - 0 === i;
-		}
-	},
-	filter: {
-		PSEUDO: function( elem, match, i, array ) {
-			var name = match[1],
-				filter = Expr.filters[ name ];
-
-			if ( filter ) {
-				return filter( elem, i, match, array );
-
-			} else if ( name === "contains" ) {
-				return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
-
-			} else if ( name === "not" ) {
-				var not = match[3];
-
-				for ( var j = 0, l = not.length; j < l; j++ ) {
-					if ( not[j] === elem ) {
-						return false;
-					}
-				}
-
-				return true;
-
-			} else {
-				Sizzle.error( name );
-			}
-		},
-
-		CHILD: function( elem, match ) {
-			var first, last,
-				doneName, parent, cache,
-				count, diff,
-				type = match[1],
-				node = elem;
-
-			switch ( type ) {
-				case "only":
-				case "first":
-					while ( (node = node.previousSibling) ) {
-						if ( node.nodeType === 1 ) {
-							return false;
-						}
-					}
-
-					if ( type === "first" ) {
-						return true;
-					}
-
-					node = elem;
-
-					/* falls through */
-				case "last":
-					while ( (node = node.nextSibling) ) {
-						if ( node.nodeType === 1 ) {
-							return false;
-						}
-					}
-
-					return true;
-
-				case "nth":
-					first = match[2];
-					last = match[3];
-
-					if ( first === 1 && last === 0 ) {
-						return true;
-					}
-
-					doneName = match[0];
-					parent = elem.parentNode;
-
-					if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
-						count = 0;
-
-						for ( node = parent.firstChild; node; node = node.nextSibling ) {
-							if ( node.nodeType === 1 ) {
-								node.nodeIndex = ++count;
-							}
-						}
-
-						parent[ expando ] = doneName;
-					}
-
-					diff = elem.nodeIndex - last;
-
-					if ( first === 0 ) {
-						return diff === 0;
-
-					} else {
-						return ( diff % first === 0 && diff / first >= 0 );
-					}
-			}
-		},
-
-		ID: function( elem, match ) {
-			return elem.nodeType === 1 && elem.getAttribute("id") === match;
-		},
-
-		TAG: function( elem, match ) {
-			return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
-		},
-
-		CLASS: function( elem, match ) {
-			return (" " + (elem.className || elem.getAttribute("class")) + " ")
-				.indexOf( match ) > -1;
-		},
-
-		ATTR: function( elem, match ) {
-			var name = match[1],
-				result = Sizzle.attr ?
-					Sizzle.attr( elem, name ) :
-					Expr.attrHandle[ name ] ?
-					Expr.attrHandle[ name ]( elem ) :
-					elem[ name ] != null ?
-						elem[ name ] :
-						elem.getAttribute( name ),
-				value = result + "",
-				type = match[2],
-				check = match[4];
-
-			return result == null ?
-				type === "!=" :
-				!type && Sizzle.attr ?
-				result != null :
-				type === "=" ?
-				value === check :
-				type === "*=" ?
-				value.indexOf(check) >= 0 :
-				type === "~=" ?
-				(" " + value + " ").indexOf(check) >= 0 :
-				!check ?
-				value && result !== false :
-				type === "!=" ?
-				value !== check :
-				type === "^=" ?
-				value.indexOf(check) === 0 :
-				type === "$=" ?
-				value.substr(value.length - check.length) === check :
-				type === "|=" ?
-				value === check || value.substr(0, check.length + 1) === check + "-" :
-				false;
-		},
-
-		POS: function( elem, match, i, array ) {
-			var name = match[2],
-				filter = Expr.setFilters[ name ];
-
-			if ( filter ) {
-				return filter( elem, i, match, array );
-			}
-		}
-	}
-};
-
-var origPOS = Expr.match.POS,
-	fescape = function(all, num){
-		return "\\" + (num - 0 + 1);
-	};
-
-for ( var type in Expr.match ) {
-	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
-	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
-}
-// Expose origPOS
-// "global" as in regardless of relation to brackets/parens
-Expr.match.globalPOS = origPOS;
-
-var makeArray = function( array, results ) {
-	array = Array.prototype.slice.call( array, 0 );
-
-	if ( results ) {
-		results.push.apply( results, array );
-		return results;
-	}
-
-	return array;
-};
-
-// Perform a simple check to determine if the browser is capable of
-// converting a NodeList to an array using builtin methods.
-// Also verifies that the returned array holds DOM nodes
-// (which is not the case in the Blackberry browser)
-try {
-	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
-
-// Provide a fallback method if it does not work
-} catch( e ) {
-	makeArray = function( array, results ) {
-		var i = 0,
-			ret = results || [];
-
-		if ( toString.call(array) === "[object Array]" ) {
-			Array.prototype.push.apply( ret, array );
-
-		} else {
-			if ( typeof array.length === "number" ) {
-				for ( var l = array.length; i < l; i++ ) {
-					ret.push( array[i] );
-				}
-
-			} else {
-				for ( ; array[i]; i++ ) {
-					ret.push( array[i] );
-				}
-			}
-		}
-
-		return ret;
-	};
-}
-
-var sortOrder, siblingCheck;
-
-if ( document.documentElement.compareDocumentPosition ) {
-	sortOrder = function( a, b ) {
-		if ( a === b ) {
-			hasDuplicate = true;
-			return 0;
-		}
-
-		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
-			return a.compareDocumentPosition ? -1 : 1;
-		}
-
-		return a.compareDocumentPosition(b) & 4 ? -1 : 1;
-	};
-
-} else {
-	sortOrder = function( a, b ) {
-		// The nodes are identical, we can exit early
-		if ( a === b ) {
-			hasDuplicate = true;
-			return 0;
-
-		// Fallback to using sourceIndex (in IE) if it's available on both nodes
-		} else if ( a.sourceIndex && b.sourceIndex ) {
-			return a.sourceIndex - b.sourceIndex;
-		}
-
-		var al, bl,
-			ap = [],
-			bp = [],
-			aup = a.parentNode,
-			bup = b.parentNode,
-			cur = aup;
-
-		// If the nodes are siblings (or identical) we can do a quick check
-		if ( aup === bup ) {
-			return siblingCheck( a, b );
-
-		// If no parents were found then the nodes are disconnected
-		} else if ( !aup ) {
-			return -1;
-
-		} else if ( !bup ) {
-			return 1;
-		}
-
-		// Otherwise they're somewhere else in the tree so we need
-		// to build up a full list of the parentNodes for comparison
-		while ( cur ) {
-			ap.unshift( cur );
-			cur = cur.parentNode;
-		}
-
-		cur = bup;
-
-		while ( cur ) {
-			bp.unshift( cur );
-			cur = cur.parentNode;
-		}
-
-		al = ap.length;
-		bl = bp.length;
-
-		// Start walking down the tree looking for a discrepancy
-		for ( var i = 0; i < al && i < bl; i++ ) {
-			if ( ap[i] !== bp[i] ) {
-				return siblingCheck( ap[i], bp[i] );
-			}
-		}
-
-		// We ended someplace up the tree so do a sibling check
-		return i === al ?
-			siblingCheck( a, bp[i], -1 ) :
-			siblingCheck( ap[i], b, 1 );
-	};
-
-	siblingCheck = function( a, b, ret ) {
-		if ( a === b ) {
-			return ret;
-		}
-
-		var cur = a.nextSibling;
-
-		while ( cur ) {
-			if ( cur === b ) {
-				return -1;
-			}
-
-			cur = cur.nextSibling;
-		}
-
-		return 1;
-	};
-}
-
-// Check to see if the browser returns elements by name when
-// querying by getElementById (and provide a workaround)
-(function(){
-	// We're going to inject a fake input element with a specified name
-	var form = document.createElement("div"),
-		id = "script" + (new Date()).getTime(),
-		root = document.documentElement;
-
-	form.innerHTML = "<a name='" + id + "'/>";
-
-	// Inject it into the root element, check its status, and remove it quickly
-	root.insertBefore( form, root.firstChild );
-
-	// The workaround has to do additional checks after a getElementById
-	// Which slows things down for other browsers (hence the branching)
-	if ( document.getElementById( id ) ) {
-		Expr.find.ID = function( match, context, isXML ) {
-			if ( typeof context.getElementById !== "undefined" && !isXML ) {
-				var m = context.getElementById(match[1]);
-
-				return m ?
-					m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
-						[m] :
-						undefined :
-					[];
-			}
-		};
-
-		Expr.filter.ID = function( elem, match ) {
-			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
-
-			return elem.nodeType === 1 && node && node.nodeValue === match;
-		};
-	}
-
-	root.removeChild( form );
-
-	// release memory in IE
-	root = form = null;
-})();
-
-(function(){
-	// Check to see if the browser returns only elements
-	// when doing getElementsByTagName("*")
-
-	// Create a fake element
-	var div = document.createElement("div");
-	div.appendChild( document.createComment("") );
-
-	// Make sure no comments are found
-	if ( div.getElementsByTagName("*").length > 0 ) {
-		Expr.find.TAG = function( match, context ) {
-			var results = context.getElementsByTagName( match[1] );
-
-			// Filter out possible comments
-			if ( match[1] === "*" ) {
-				var tmp = [];
-
-				for ( var i = 0; results[i]; i++ ) {
-					if ( results[i].nodeType === 1 ) {
-						tmp.push( results[i] );
-					}
-				}
-
-				results = tmp;
-			}
-
-			return results;
-		};
-	}
-
-	// Check to see if an attribute returns normalized href attributes
-	div.innerHTML = "<a href='#'></a>";
-
-	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
-			div.firstChild.getAttribute("href") !== "#" ) {
-
-		Expr.attrHandle.href = function( elem ) {
-			return elem.getAttribute( "href", 2 );
-		};
-	}
-
-	// release memory in IE
-	div = null;
-})();
-
-// Patch for jsdom
-if ( document.querySelectorAll && false ) {
-	(function(){
-		var oldSizzle = Sizzle,
-			div = document.createElement("div"),
-			id = "__sizzle__";
-
-		div.innerHTML = "<p class='TEST'></p>";
-
-		// Safari can't handle uppercase or unicode characters when
-		// in quirks mode.
-		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
-			return;
-		}
-
-		Sizzle = function( query, context, extra, seed ) {
-			context = context || document;
-
-			// Only use querySelectorAll on non-XML documents
-			// (ID selectors don't work in non-HTML documents)
-			if ( !seed && !Sizzle.isXML(context) ) {
-				// See if we find a selector to speed up
-				var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
-
-				if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
-					// Speed-up: Sizzle("TAG")
-					if ( match[1] ) {
-						return makeArray( context.getElementsByTagName( query ), extra );
-
-					// Speed-up: Sizzle(".CLASS")
-					} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
-						return makeArray( context.getElementsByClassName( match[2] ), extra );
-					}
-				}
-
-				if ( context.nodeType === 9 ) {
-					// Speed-up: Sizzle("body")
-					// The body element only exists once, optimize finding it
-					if ( query === "body" && context.body ) {
-						return makeArray( [ context.body ], extra );
-
-					// Speed-up: Sizzle("#ID")
-					} else if ( match && match[3] ) {
-						var elem = context.getElementById( match[3] );
-
-						// Check parentNode to catch when Blackberry 4.6 returns
-						// nodes that are no longer in the document #6963
-						if ( elem && elem.parentNode ) {
-							// Handle the case where IE and Opera return items
-							// by name instead of ID
-							if ( elem.id === match[3] ) {
-								return makeArray( [ elem ], extra );
-							}
-
-						} else {
-							return makeArray( [], extra );
-						}
-					}
-
-					try {
-						return makeArray( context.querySelectorAll(query), extra );
-					} catch(qsaError) {}
-
-				// qSA works strangely on Element-rooted queries
-				// We can work around this by specifying an extra ID on the root
-				// and working up from there (Thanks to Andrew Dupont for the technique)
-				// IE 8 doesn't work on object elements
-				} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
-					var oldContext = context,
-						old = context.getAttribute( "id" ),
-						nid = old || id,
-						hasParent = context.parentNode,
-						relativeHierarchySelector = /^\s*[+~]/.test( query );
-
-					if ( !old ) {
-						context.setAttribute( "id", nid );
-					} else {
-						nid = nid.replace( /'/g, "\\$&" );
-					}
-					if ( relativeHierarchySelector && hasParent ) {
-						context = context.parentNode;
-					}
-
-					try {
-						if ( !relativeHierarchySelector || hasParent ) {
-							return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
-						}
-
-					} catch(pseudoError) {
-					} finally {
-						if ( !old ) {
-							oldContext.removeAttribute( "id" );
-						}
-					}
-				}
-			}
-
-			return oldSizzle(query, context, extra, seed);
-		};
-
-		for ( var prop in oldSizzle ) {
-			Sizzle[ prop ] = oldSizzle[ prop ];
-		}
-
-		// release memory in IE
-		div = null;
-	})();
-}
-
-(function(){
-	var html = document.documentElement,
-		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
-
-	if ( matches ) {
-		// Check to see if it's possible to do matchesSelector
-		// on a disconnected node (IE 9 fails this)
-		var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
-			pseudoWorks = false;
-
-		try {
-			// This should fail with an exception
-			// Gecko does not error, returns false instead
-			matches.call( document.documentElement, "[test!='']:sizzle" );
-
-		} catch( pseudoError ) {
-			pseudoWorks = true;
-		}
-
-		Sizzle.matchesSelector = function( node, expr ) {
-			// Make sure that attribute selectors are quoted
-			expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
-
-			if ( !Sizzle.isXML( node ) ) {
-				try {
-					if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
-						var ret = matches.call( node, expr );
-
-						// IE 9's matchesSelector returns false on disconnected nodes
-						if ( ret || !disconnectedMatch ||
-								// As well, disconnected nodes are said to be in a document
-								// fragment in IE 9, so check for that
-								node.document && node.document.nodeType !== 11 ) {
-							return ret;
-						}
-					}
-				} catch(e) {}
-			}
-
-			return Sizzle(expr, null, null, [node]).length > 0;
-		};
-	}
-})();
-
-(function(){
-	var div = document.createElement("div");
-
-	div.innerHTML = "<div class='test e'></div><div class='test'></div>";
-
-	// Opera can't find a second classname (in 9.6)
-	// Also, make sure that getElementsByClassName actually exists
-	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
-		return;
-	}
-
-	// Safari caches class attributes, doesn't catch changes (in 3.2)
-	div.lastChild.className = "e";
-
-	if ( div.getElementsByClassName("e").length === 1 ) {
-		return;
-	}
-
-	Expr.order.splice(1, 0, "CLASS");
-	Expr.find.CLASS = function( match, context, isXML ) {
-		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
-			return context.getElementsByClassName(match[1]);
-		}
-	};
-
-	// release memory in IE
-	div = null;
-})();
-
-function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
-	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
-		var elem = checkSet[i];
-
-		if ( elem ) {
-			var match = false;
-
-			elem = elem[dir];
-
-			while ( elem ) {
-				if ( elem[ expando ] === doneName ) {
-					match = checkSet[elem.sizset];
-					break;
-				}
-
-				if ( elem.nodeType === 1 && !isXML ){
-					elem[ expando ] = doneName;
-					elem.sizset = i;
-				}
-
-				if ( elem.nodeName.toLowerCase() === cur ) {
-					match = elem;
-					break;
-				}
-
-				elem = elem[dir];
-			}
-
-			checkSet[i] = match;
-		}
-	}
-}
-
-function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
-	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
-		var elem = checkSet[i];
-
-		if ( elem ) {
-			var match = false;
-
-			elem = elem[dir];
-
-			while ( elem ) {
-				if ( elem[ expando ] === doneName ) {
-					match = checkSet[elem.sizset];
-					break;
-				}
-
-				if ( elem.nodeType === 1 ) {
-					if ( !isXML ) {
-						elem[ expando ] = doneName;
-						elem.sizset = i;
-					}
-
-					if ( typeof cur !== "string" ) {
-						if ( elem === cur ) {
-							match = true;
-							break;
-						}
-
-					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
-						match = elem;
-						break;
-					}
-				}
-
-				elem = elem[dir];
-			}
-
-			checkSet[i] = match;
-		}
-	}
-}
-
-if ( document.documentElement.contains ) {
-	Sizzle.contains = function( a, b ) {
-		return a !== b && (a.contains ? a.contains(b) : true);
-	};
-
-} else if ( document.documentElement.compareDocumentPosition ) {
-	Sizzle.contains = function( a, b ) {
-		return !!(a.compareDocumentPosition(b) & 16);
-	};
-
-} else {
-	Sizzle.contains = function() {
-		return false;
-	};
-}
-
-Sizzle.isXML = function( elem ) {
-	// documentElement is verified for cases where it doesn't yet exist
-	// (such as loading iframes in IE - #4833)
-	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
-
-	return documentElement ? documentElement.nodeName !== "HTML" : false;
-};
-
-var posProcess = function( selector, context, seed ) {
-	var match,
-		tmpSet = [],
-		later = "",
-		root = context.nodeType ? [context] : context;
-
-	// Position selectors must be done after the filter
-	// And so must :not(positional) so we move all PSEUDOs to the end
-	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
-		later += match[0];
-		selector = selector.replace( Expr.match.PSEUDO, "" );
-	}
-
-	selector = Expr.relative[selector] ? selector + "*" : selector;
-
-	for ( var i = 0, l = root.length; i < l; i++ ) {
-		Sizzle( selector, root[i], tmpSet, seed );
-	}
-
-	return Sizzle.filter( later, tmpSet );
-};
-
-// EXPOSE
-return Sizzle;
-
-};
\ No newline at end of file
diff --git a/lib/jsdom/utils.js b/lib/jsdom/utils.js
index 7711d2f..ad93527 100644
--- a/lib/jsdom/utils.js
+++ b/lib/jsdom/utils.js
@@ -1,7 +1,9 @@
+var path = require('path');
+
 /**
  * Intercepts a method by replacing the prototype's implementation
  * with a wrapper that invokes the given interceptor instead.
- * 
+ *
  *     utils.intercept(core.Element, 'inserBefore',
  *       function(_super, args, newChild, refChild) {
  *         console.log('insertBefore', newChild, refChild);
@@ -25,3 +27,15 @@ exports.intercept = function(clazz, method, interceptor) {
     }
   };
 };
+
+exports.toFileUrl = function (fileName) {
+  // Beyond just the `path.resolve`, this is mostly for the benefit of Windows,
+  // where we need to convert '\' to '/' and add an extra '/' prefix before the
+  // drive letter.
+  var pathname = path.resolve(process.cwd(), fileName).replace(/\\/g, '/');
+  if (pathname[0] !== '/') {
+    pathname = '/' + pathname;
+  }
+
+  return 'file://' + pathname;
+};
diff --git a/package.json b/package.json
index 239a867..81dfa34 100644
--- a/package.json
+++ b/package.json
@@ -1,170 +1,80 @@
 {
     "name": "jsdom",
-    "version": "0.2.13",
-    "description": "A javascript implementation of the W3C DOM",
-    "keywords": [
-      "dom",
-      "w3c",
-      "javascript"
+    "version": "0.8.10",
+    "description": "A JavaScript implementation of the W3C DOM",
+    "keywords": ["dom", "w3c", "html"],
+    "maintainers": [
+        "Elijah Insua <tmpvar at gmail.com> (http://tmpvar.com)",
+        "Domenic Denicola <domenic at domenicdenicola.com (http://domenicdenicola.com)"
     ],
-    "maintainers": [{
-      "name": "Elijah Insua",
-      "email": "tmpvar at gmail.com",
-      "url": "http://tmpvar.com"
-    }],
     "contributors": [
-      {
-        "name":  "Vincent Greene",
-        "email": "ulteriorlife at gmail.com"
-      },
-      {
-        "name":  "Dav Glass",
-        "email": "davglass at gmail.com"
-      },
-      {
-        "name":  "Felix Gnass",
-        "email": "fgnass at gmail.com"
-      },
-      {
-        "name":  "Charlie Robbins",
-        "email": "charlie.robbins at gmail.com"
-      },
-      {
-        "name":  "Aria Stewart",
-        "email": "aredridel at nbtsc.org"
-      },
-      {
-        "name":  "Matthew",
-        "email": "N.A.",
-        "url":   "http://github.com/matthewpflueger/"
-      },
-      {
-        "name":  "Olivier El Mekki",
-        "email": "unknown",
-        "url":   "http://blog.olivier-elmekki.com/"
-      },
-      {
-        "name":  "Shimon Dookdin",
-        "email": "helpmepro1 at gmail.com"
-      },
-      {
-        "name":  "Daniel Cassidy",
-        "email": "mail at danielcassidy.me.uk",
-        "url":   "http://www.danielcassidy.me.uk/"
-      },
-      {
-        "name":  "Sam Ruby",
-        "email": "N/A",
-        "url":   "http://intertwingly.net/blog/"
-      },
-      {
-        "name":  "hij1nx",
-        "url":   "http://github.com/hij1nx"
-      },
-      {
-        "name":  "Yonathan Randolph",
-        "url":   "http://github.com/yonran"
-      },
-      {
-        "name":  "Martin Davis",
-        "url":   "http://github.com/waslogic"
-      },
-      {
-        "name":  "Andreas Lind Petersen",
-        "email": "andreas at one.com"
-      },
-      {
-        "name":  "d-ash",
-        "url":   "http://github.com/d-ash"
-      },
-      {
-        "name":  "Robin Zhong",
-        "email": "fbzhong at gmail.com"
-      },
-      {
-        "name":  "Alexander Flatter",
-        "email": "flatter at gmail.com"
-      },
-      {
-        "name":  "Heng Liu",
-        "email": "liucougar at gmail.com"
-      },
-      {
-        "name":  "Brian McDaniel",
-        "url":   "http://github.com/brianmcd"
-      },
-      {
-        "name":  "John Hurliman",
-        "email": "jhurliman at jhurliman.org"
-      },
-      {
-        "name":  "Jimmy Mabey"
-      },
-      {
-        "name":  "Gregory Tomlinson"
-      },
-      {
-        "name":  "Jason Davies",
-        "url":   "http://www.jasondavies.com/"
-      },
-      {
-        "name":  "Josh Marshall",
-        "url":   "http://www.ponderingtheobvious.com/"
-      },
-      {
-        "name" : "Jason Priestley",
-        "url" : "https://github.com/jhp"
-      },
-      {
-        "name" : "Derek Lindahl",
-        "url" : "https://github.com/dlindahl"
-      },
-      {
-        "name" : "Chris Roebuck",
-        "email" : "chris at quillu.com",
-        "url" : "http://www.quillu.com"
-      },
-      {
-        "name" : "Avi Deitcher",
-        "url" : "https://github.com/deitch"
-      }
+        "Vincent Greene <ulteriorlife at gmail.com>",
+        "Dav Glass <davglass at gmail.com>",
+        "Felix Gnass <fgnass at gmail.com>",
+        "Charlie Robbins <charlie.robbins at gmail.com>",
+        "Aria Stewart <aredridel at nbtsc.org>",
+        "Matthew <N.A.> (http://github.com/matthewpflueger/)",
+        "Olivier El Mekki <unknown> (http://blog.olivier-elmekki.com/)",
+        "Shimon Dookdin <helpmepro1 at gmail.com>",
+        "Daniel Cassidy <mail at danielcassidy.me.uk> (http://www.danielcassidy.me.uk/)",
+        "Sam Ruby (http://intertwingly.net/blog/)",
+        "hij1nx (http://github.com/hij1nx)",
+        "Yonathan Randolph (http://github.com/yonran)",
+        "Martin Davis (http://github.com/waslogic)",
+        "Andreas Lind Petersen <andreas at one.com>",
+        "d-ash (http://github.com/d-ash)",
+        "Robin Zhong <fbzhong at gmail.com>",
+        "Alexander Flatter <flatter at gmail.com>",
+        "Heng Liu <liucougar at gmail.com>",
+        "Brian McDaniel (http://github.com/brianmcd)",
+        "John Hurliman <jhurliman at jhurliman.org>",
+        "Jimmy Mabey",
+        "Gregory Tomlinson",
+        "Jason Davies (http://www.jasondavies.com/)",
+        "Josh Marshall (http://www.ponderingtheobvious.com/)",
+        "Jason Priestley (https://github.com/jhp)",
+        "Derek Lindahl (https://github.com/dlindahl)",
+        "Chris Roebuck <chris at quillu.com> (http://www.quillu.com)",
+        "Avi Deitcher (https://github.com/deitch)",
+        "Nao Iizuka <iizuka at kyu-mu.net> (https://github.com/iizukanao)",
+        "Peter Perenyi (https://github.com/sinegar)",
+        "Tiago Rodrigues <tmcrodrigues at gmail.com> (http://trodrigues.net)",
+        "Samori Gorse <samorigorse at gmail.com> (http://github.com/shinuza)",
+        "John Roberts <jroberts at logitech.com>",
+        "Chad Walker <chad at chad-cat-lore-eddie.com> (https://github.com/chad3814)",
+        "Zach Smith <x.coder.zach at gmail.com> (https://github.com/xcoderzach)"
     ],
     "bugs": {
         "email": "tmpvar at gmail.com",
         "url": "http://github.com/tmpvar/jsdom/issues"
     },
-    "licenses": [
-        {
-            "type": "MIT",
-            "url": "http://github.com/tmpvar/jsdom/blob/master/LICENSE.txt"
-        }
-    ],
-    "repositories": [
-        {
-            "type": "git",
-            "url": "http://github.com/tmpvar/jsdom.git"
-        }
-    ],
+    "license": {
+        "type": "MIT",
+        "url": "http://github.com/tmpvar/jsdom/blob/master/LICENSE.txt"
+    },
+    "repository": {
+        "type": "git",
+        "url": "git://github.com/tmpvar/jsdom.git"
+    },
     "implements": [
         "http://www.w3.org/TR/REC-DOM-Level-1"
     ],
     "dependencies": {
-       "htmlparser" : "1.x",
-       "request"    : "2.x",
-       "cssom"      : "0.2.x",
-       "contextify" : "0.1.x"
-    },
-    "optionalDependencies": {
-       "contextify" : "0.1.x"
+       "htmlparser2": ">= 3.1.5 <4",
+       "nwmatcher": "~1.3.1",
+       "request": "2.x",
+       "xmlhttprequest": ">=1.5.0",
+       "cssom": "~0.3.0",
+       "cssstyle": "~0.2.6",
+       "contextify": "~0.1.5"
     },
     "devDependencies" : {
-      "nodeunit" : ">=0.5.x",
-      "console.log" : "*",
-      "optimist"   : "*"
+      "nodeunit": "~0.8.0",
+      "optimist": "*",
+      "urlmaster": ">=0.2.15"
     },
-    "engines" : { "node" : ">=0.1.9" },
-    "directories": {
-        "lib": "./lib/jsdom"
+    "scripts": {
+        "test": "node ./test/runner"
     },
     "main": "./lib/jsdom"
 }

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/collab-maint/node-jsdom.git



More information about the Pkg-javascript-commits mailing list