jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/334382 )

Change subject: Bump src/ to ba7b3046 + update domino
......................................................................


Bump src/ to ba7b3046 + update domino

Change-Id: I670a16a0e4e96450521422d4afe1a511f3a9d315
---
M node_modules/domino/CHANGELOG.md
M node_modules/domino/README.md
M node_modules/domino/lib/CharacterData.js
A node_modules/domino/lib/ChildNode.js
M node_modules/domino/lib/Document.js
M node_modules/domino/lib/DocumentType.js
M node_modules/domino/lib/Element.js
M node_modules/domino/lib/Leaf.js
M node_modules/domino/lib/Node.js
M node_modules/domino/lib/ProcessingInstruction.js
M node_modules/domino/lib/Window.js
M node_modules/domino/lib/attributes.js
A node_modules/domino/lib/config.js
M node_modules/domino/lib/htmlelts.js
M node_modules/domino/lib/index.js
M node_modules/domino/lib/select.js
M node_modules/domino/lib/utils.js
M node_modules/domino/package.json
M node_modules/domino/test/domino.js
M node_modules/domino/test/mocha.opts
M node_modules/domino/test/parsing.js
M node_modules/domino/test/w3c/harness/index.js
M 
node_modules/domino/test/w3c/level1/core/documentinvalidcharacterexceptioncreatepi.js
M 
node_modules/domino/test/w3c/level1/core/documentinvalidcharacterexceptioncreatepi1.js
M 
node_modules/domino/test/w3c/level1/core/hc_nodeappendchildnewchilddiffdocument.js
M 
node_modules/domino/test/w3c/level1/core/hc_nodeinsertbeforenewchilddiffdocument.js
M 
node_modules/domino/test/w3c/level1/core/hc_nodereplacechildnewchilddiffdocument.js
M node_modules/domino/test/w3c/level1/html/object06.js
M node_modules/domino/test/web-platform-tests.js
M src
30 files changed, 1,077 insertions(+), 378 deletions(-)

Approvals:
  Subramanya Sastry: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/node_modules/domino/CHANGELOG.md b/node_modules/domino/CHANGELOG.md
index 91029e7..62b3c55 100644
--- a/node_modules/domino/CHANGELOG.md
+++ b/node_modules/domino/CHANGELOG.md
@@ -1,3 +1,19 @@
+# domino 1.0.28 (27 Jan 2017)
+* Fix unescape mechanism in attribute values. (#95)
+* Disable nonstandard "ignore case" version of attribute matching.
+* Add `dom/nodes` tests from w3c/web-platform-tests. (#92, @pimterry)
+* Make selected API methods writable to support polyfills. (#89, @pimterry)
+* Fix `Element#hasAttribute`/`Element#hasAttributeNS` after
+  `Element#removeAttribute`/`Element#removeAttributeNS`. (#90, @clint-tseng)
+* Fix deep `Document#importNode`. (#93)
+* Ensure that `Node#parentNode` is `null` (not `undefined`) when removed.
+* Add an optional second argument to `domino.createWindow` to specify
+  the document's address.
+* Tweak JavaScript properties which are DOM reflections of element
+  attributes in order to more closely match the DOM 4 spec.
+* Implement `ChildNode#before()`, `ChildNode#after()`, and
+  `ChildNode#replaceWith()`.
+
 # domino 1.0.27 (17 Oct 2016)
 * Fix bug in AFE list replacement over existing bookmark.
 * Update htmlwg test suite to latest w3c/web-platform-tests.
diff --git a/node_modules/domino/README.md b/node_modules/domino/README.md
index c3c2d3e..d850e49 100644
--- a/node_modules/domino/README.md
+++ b/node_modules/domino/README.md
@@ -28,14 +28,41 @@
 
 ## Usage
 
+Domino supports the DOM level 4 API, and thus API documentation can be
+found on standard reference sites.  For example, you could start from
+MDN's documentation for
+[Document](https://developer.mozilla.org/en-US/docs/Web/API/Document) and
+[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node).
+
+The only exception is the initial creation of a document:
 ```javascript
 var domino = require('domino');
+var Element = domino.impl.Element; // etc
 
-var window = domino.createWindow('<h1>Hello world</h1>');
+var window = domino.createWindow('<h1>Hello world</h1>', 'http://example.com');
 var document = window.document;
+
+// alternatively: document = domino.createDocument(htmlString, true)
 
 var h1 = document.querySelector('h1');
 console.log(h1.innerHTML);
+console.log(h1 instanceof Element);
+```
+
+If you want a more standards-compliant way to create a `Document`, you can
+also use 
[DOMImplementation](https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation):
+```javascript
+var domino = require('domino');
+var domimpl = domino.createDOMImplementation();
+var doc = domimpl.createHTMLDocument();
+```
+
+By default many domino methods will be stored in writable properties, to
+allow polyfills (as browsers do).  You can lock down the implementation
+if desired as follows:
+```javascript
+global.__domino_frozen__ = true; // Must precede any `require('domino')`
+var domino = require('domino');
 ```
 
 ## Tests
diff --git a/node_modules/domino/lib/CharacterData.js 
b/node_modules/domino/lib/CharacterData.js
index 5ef46ef..db9c215 100644
--- a/node_modules/domino/lib/CharacterData.js
+++ b/node_modules/domino/lib/CharacterData.js
@@ -3,6 +3,7 @@
 
 var Leaf = require('./Leaf');
 var utils = require('./utils');
+var ChildNode = require('./ChildNode');
 var NonDocumentTypeChildNode = require('./NonDocumentTypeChildNode');
 
 function CharacterData() {
@@ -117,4 +118,5 @@
 
 });
 
+Object.defineProperties(CharacterData.prototype, ChildNode);
 Object.defineProperties(CharacterData.prototype, NonDocumentTypeChildNode);
diff --git a/node_modules/domino/lib/ChildNode.js 
b/node_modules/domino/lib/ChildNode.js
new file mode 100644
index 0000000..bfd7004
--- /dev/null
+++ b/node_modules/domino/lib/ChildNode.js
@@ -0,0 +1,97 @@
+"use strict";
+
+var Node = require('./Node');
+
+var createDocumentFragmentFromArguments = function(document, args) {
+  var docFrag = document.createDocumentFragment();
+
+  for (var i=0; i<args.length; i++) {
+    var argItem = args[i];
+    var isNode = argItem instanceof Node;
+    docFrag.appendChild(isNode ? argItem :
+                        document.createTextNode(String(argItem)));
+  }
+
+  return docFrag;
+};
+
+// The ChildNode interface contains methods that are particular to `Node`
+// objects that can have a parent.  It is implemented by `Element`,
+// `DocumentType`, and `CharacterData` objects.
+var ChildNode = {
+
+  // Inserts a set of Node or String objects in the children list of this
+  // ChildNode's parent, just after this ChildNode.  String objects are
+  // inserted as the equivalent Text nodes.
+  after: { value: function after() {
+    var argArr = Array.prototype.slice.call(arguments);
+    var parentNode = this.parentNode, nextSibling = this.nextSibling;
+    if (parentNode === null) { return; }
+    // Find "viable next sibling"; that is, next one not in argArr
+    while (nextSibling && argArr.some(function(v) { return v===nextSibling; }))
+      nextSibling = nextSibling.nextSibling;
+    // ok, parent and sibling are saved away since this node could itself
+    // appear in argArr and we're about to move argArr to a document fragment.
+    var docFrag = createDocumentFragmentFromArguments(this.doc, argArr);
+
+    parentNode.insertBefore(docFrag, nextSibling);
+  }},
+
+  // Inserts a set of Node or String objects in the children list of this
+  // ChildNode's parent, just before this ChildNode.  String objects are
+  // inserted as the equivalent Text nodes.
+  before: { value: function before() {
+    var argArr = Array.prototype.slice.call(arguments);
+    var parentNode = this.parentNode, prevSibling = this.previousSibling;
+    if (parentNode === null) { return; }
+    // Find "viable prev sibling"; that is, prev one not in argArr
+    while (prevSibling && argArr.some(function(v) { return v===prevSibling; }))
+      prevSibling = prevSibling.previousSibling;
+    // ok, parent and sibling are saved away since this node could itself
+    // appear in argArr and we're about to move argArr to a document fragment.
+    var docFrag = createDocumentFragmentFromArguments(this.doc, argArr);
+
+    var nextSibling =
+        prevSibling ? prevSibling.nextSibling : parentNode.firstChild;
+    parentNode.insertBefore(docFrag, nextSibling);
+  }},
+
+  // Remove this node from its parent
+  remove: { value: function remove() {
+    if (this.parentNode === null) return;
+
+    // Send mutation events if necessary
+    if (this.rooted && this.doc) this.doc.mutateRemove(this);
+
+    // Remove this node from its parents array of children
+    this.parentNode.childNodes.splice(this.index, 1);
+
+    // Update the structure id for all ancestors
+    this.parentNode.modify();
+
+    // Forget this node's parent
+    this.parentNode = null;
+  }},
+
+  // Replace this node with the nodes or strings provided as arguments.
+  replaceWith: { value: function replaceWith() {
+    var argArr = Array.prototype.slice.call(arguments);
+    var parentNode = this.parentNode, nextSibling = this.nextSibling;
+    if (parentNode === null) { return; }
+    // Find "viable next sibling"; that is, next one not in argArr
+    while (nextSibling && argArr.some(function(v) { return v===nextSibling; }))
+      nextSibling = nextSibling.nextSibling;
+    // ok, parent and sibling are saved away since this node could itself
+    // appear in argArr and we're about to move argArr to a document fragment.
+    var docFrag = createDocumentFragmentFromArguments(this.doc, argArr);
+    if (this.parentNode === parentNode) {
+      parentNode.replaceChild(docFrag, this);
+    } else {
+      // `this` was inserted into docFrag
+      parentNode.insertBefore(docFrag, nextSibling);
+    }
+  }},
+
+};
+
+module.exports = ChildNode;
diff --git a/node_modules/domino/lib/Document.js 
b/node_modules/domino/lib/Document.js
index 9064f51..aad8083 100644
--- a/node_modules/domino/lib/Document.js
+++ b/node_modules/domino/lib/Document.js
@@ -22,6 +22,7 @@
 var utils = require('./utils');
 var MUTATE = require('./MutationConstants');
 var NAMESPACE = utils.NAMESPACE;
+var isApiWritable = require("./config").isApiWritable;
 
 function Document(isHTML, address) {
   this.nodeType = Node.DOCUMENT_NODE;
@@ -82,6 +83,20 @@
   uievents: 'uievent'
 };
 
+var mirrorAttr = function(f, name, defaultValue) {
+  return {
+    get: function() {
+      var o = f.call(this);
+      if (o) { return o[name]; }
+      return defaultValue;
+    },
+    set: function(value) {
+      var o = f.call(this);
+      if (o) { o[name] = value; }
+    },
+  };
+};
+
 Document.prototype = Object.create(Node.prototype, {
   // This method allows dom.js to communicate with a renderer
   // that displays the document in some way
@@ -125,7 +140,6 @@
     return new DocumentFragment(this);
   }},
   createProcessingInstruction: { value: function(target, data) {
-    if (this.isHTML) utils.NotSupportedError();
     if (!xml.isValidName(target) || data.indexOf('?>') !== -1)
       utils.InvalidCharacterError();
     return new ProcessingInstruction(this, target, data);
@@ -135,7 +149,7 @@
     if (!xml.isValidName(localName)) utils.InvalidCharacterError();
     if (this.isHTML) localName = utils.toASCIILowerCase(localName);
     return html.createElement(this, localName, null);
-  }},
+  }, writable: isApiWritable },
 
   createElementNS: { value: function(namespace, qualifiedName) {
     if (!xml.isValidName(qualifiedName)) utils.InvalidCharacterError();
@@ -170,7 +184,7 @@
     }
 
     return new Element(this, localName, namespace, prefix);
-  }},
+  }, writable: isApiWritable },
 
   createEvent: { value: function createEvent(interfaceName) {
     interfaceName = interfaceName.toLowerCase();
@@ -219,102 +233,39 @@
     return new NodeIterator(root, whatToShow, filter);
   }},
 
-  // Add some (surprisingly complex) document hierarchy validity
-  // checks when adding, removing and replacing nodes into a
-  // document object, and also maintain the documentElement and
+  // Maintain the documentElement and
   // doctype properties of the document.  Each of the following
-  // 4 methods chains to the Node implementation of the method
+  // methods chains to the Node implementation of the method
   // to do the actual inserting, removal or replacement.
 
-  appendChild: { value: function(child) {
-    if (child.nodeType === Node.TEXT_NODE) utils.HierarchyRequestError();
-    if (child.nodeType === Node.ELEMENT_NODE) {
-      if (this.documentElement) // We already have a root element
-        utils.HierarchyRequestError();
-
-      this.documentElement = child;
+  _updateDocTypeElement: { value: function _updateDocTypeElement() {
+    var i, n, nodes = this.childNodes, length = nodes.length;
+    this.doctype = this.documentElement = null;
+    for (i=0; i<length; i++) {
+      n = nodes[i];
+      if (n.nodeType === Node.DOCUMENT_TYPE_NODE)
+        this.doctype = n;
+      else if (n.nodeType === Node.ELEMENT_NODE)
+        this.documentElement = n;
     }
-    if (child.nodeType === Node.DOCUMENT_TYPE_NODE) {
-      if (this.doctype ||      // Already have one
-        this.documentElement) // Or out-of-order
-        utils.HierarchyRequestError();
-
-      this.doctype = child;
-    }
-
-    // Now chain to our superclass
-    return Node.prototype.appendChild.call(this, child);
   }},
 
   insertBefore: { value: function insertBefore(child, refChild) {
-    if (refChild === null) return Document.prototype.appendChild.call(this, 
child);
-    if (refChild.parentNode !== this) utils.NotFoundError();
-    if (child.nodeType === Node.TEXT_NODE) utils.HierarchyRequestError();
-    if (child.nodeType === Node.ELEMENT_NODE) {
-      // If we already have a root element or if we're trying to
-      // insert it before the doctype
-      if (this.documentElement ||
-        (this.doctype && this.doctype.index >= refChild.index))
-        utils.HierarchyRequestError();
-
-      this.documentElement = child;
-    }
-    if (child.nodeType === Node.DOCUMENT_TYPE_NODE) {
-      if (this.doctype ||
-        (this.documentElement &&
-         refChild.index > this.documentElement.index))
-        utils.HierarchyRequestError();
-
-      this.doctype = child;
-    }
-    return Node.prototype.insertBefore.call(this, child, refChild);
+    Node.prototype.insertBefore.call(this, child, refChild);
+    this._updateDocTypeElement();
+    return child;
   }},
 
-  replaceChild: { value: function replaceChild(child, oldChild) {
-    if (oldChild.parentNode !== this) utils.NotFoundError();
-
-    if (child.nodeType === Node.TEXT_NODE) utils.HierarchyRequestError();
-    if (child.nodeType === Node.ELEMENT_NODE) {
-      // If we already have a root element and we're not replacing it
-      if (this.documentElement && this.documentElement !== oldChild)
-        utils.HierarchyRequestError();
-      // Or if we're trying to put the element before the doctype
-      // (replacing the doctype is okay)
-      if (this.doctype && oldChild.index < this.doctype.index)
-        utils.HierarchyRequestError();
-
-      if (oldChild === this.doctype) this.doctype = null;
-    }
-    else if (child.nodeType === Node.DOCUMENT_TYPE_NODE) {
-      // If we already have a doctype and we're not replacing it
-      if (this.doctype && oldChild !== this.doctype)
-        utils.HierarchyRequestError();
-      // If we have a document element and the old child
-      // comes after it
-      if (this.documentElement &&
-        oldChild.index > this.documentElement.index)
-        utils.HierarchyRequestError();
-
-      if (oldChild === this.documentElement)
-        this.documentElement = null;
-    }
-    else {
-      if (oldChild === this.documentElement)
-        this.documentElement = null;
-      else if (oldChild === this.doctype)
-        this.doctype = null;
-    }
-    return Node.prototype.replaceChild.call(this,child,oldChild);
+  replaceChild: { value: function replaceChild(node, child) {
+    Node.prototype.replaceChild.call(this, node, child);
+    this._updateDocTypeElement();
+    return child;
   }},
 
   removeChild: { value: function removeChild(child) {
-    if (child.nodeType === Node.DOCUMENT_TYPE_NODE)
-      this.doctype = null;
-    else if (child.nodeType === Node.ELEMENT_NODE)
-      this.documentElement = null;
-
-    // Now chain to our superclass
-    return Node.prototype.removeChild.call(this, child);
+    Node.prototype.removeChild.call(this, child);
+    this._updateDocTypeElement();
+    return child;
   }},
 
   getElementById: { value: function(id) {
@@ -333,8 +284,7 @@
   getElementsByClassName: { value: Element.prototype.getElementsByClassName },
 
   adoptNode: { value: function adoptNode(node) {
-    if (node.nodeType === Node.DOCUMENT_NODE ||
-      node.nodeType === Node.DOCUMENT_TYPE_NODE) utils.NotSupportedError();
+    if (node.nodeType === Node.DOCUMENT_NODE) utils.NotSupportedError();
 
     if (node.parentNode) node.parentNode.removeChild(node);
 
@@ -345,8 +295,8 @@
   }},
 
   importNode: { value: function importNode(node, deep) {
-    return this.adoptNode(node.cloneNode());
-  }},
+    return this.adoptNode(node.cloneNode(deep));
+  }, writable: isApiWritable },
 
   // The following attributes and methods are from the HTML spec
   URL: { get: utils.nyi },
@@ -386,21 +336,16 @@
       elt.textContent = value;
     }
   },
-  dir:  {
-    get: function() {
-      var htmlElement = this.documentElement;
-      if (htmlElement && htmlElement.tagName === 'HTML') {
-        return htmlElement.dir;
-      }
-      return '';
-    },
-    set: function(value) {
-      var htmlElement = this.documentElement;
-      if (htmlElement && htmlElement.tagName === 'HTML') {
-        htmlElement.dir = value;
-      }
-    },
-  },
+  dir: mirrorAttr(function() {
+    var htmlElement = this.documentElement;
+    if (htmlElement && htmlElement.tagName === 'HTML') { return htmlElement; }
+  }, 'dir', ''),
+  fgColor: mirrorAttr(function() { return this.body; }, 'text', ''),
+  linkColor: mirrorAttr(function() { return this.body; }, 'link', ''),
+  vlinkColor: mirrorAttr(function() { return this.body; }, 'vLink', ''),
+  alinkColor: mirrorAttr(function() { return this.body; }, 'aLink', ''),
+  bgColor: mirrorAttr(function() { return this.body; }, 'bgColor', ''),
+
   // Return the first <body> child of the document element.
   // XXX For now, setting this attribute is not implemented.
   body: {
@@ -633,6 +578,11 @@
     // XXX: This is not implemented correctly yet
     var url = this._address;
     if (url === 'about:blank') url = '/';
+
+    var base = this.querySelector('base[href]');
+    if (base) {
+      return new URL(url).resolve(base.getAttribute('href'));
+    }
     return url;
 
     // The document base URL of a Document object is the
diff --git a/node_modules/domino/lib/DocumentType.js 
b/node_modules/domino/lib/DocumentType.js
index 4febb7f..20199e0 100644
--- a/node_modules/domino/lib/DocumentType.js
+++ b/node_modules/domino/lib/DocumentType.js
@@ -4,6 +4,7 @@
 var Node = require('./Node');
 var Leaf = require('./Leaf');
 var utils = require('./utils');
+var ChildNode = require('./ChildNode');
 
 function DocumentType(name, publicId, systemId) {
   // Unlike other nodes, doctype nodes always start off unowned
@@ -33,3 +34,5 @@
       this.systemId === n.systemId;
   }}
 });
+
+Object.defineProperties(DocumentType.prototype, ChildNode);
diff --git a/node_modules/domino/lib/Element.js 
b/node_modules/domino/lib/Element.js
index bed9387..3e1052b 100644
--- a/node_modules/domino/lib/Element.js
+++ b/node_modules/domino/lib/Element.js
@@ -10,6 +10,7 @@
 var FilteredElementList = require('./FilteredElementList');
 var DOMTokenList = require('./DOMTokenList');
 var select = require('./select');
+var ChildNode = require('./ChildNode');
 var NonDocumentTypeChildNode = require('./NonDocumentTypeChildNode');
 
 function Element(doc, localName, namespaceURI, prefix) {
@@ -384,12 +385,12 @@
 
   hasAttribute: { value: function hasAttribute(qname) {
     if (this.isHTML) qname = utils.toASCIILowerCase(qname);
-    return qname in this._attrsByQName;
+    return this._attrsByQName[qname] !== undefined;
   }},
 
   hasAttributeNS: { value: function hasAttributeNS(ns, lname) {
     var key = (ns === null ? '' : ns) + '|' + lname;
-    return key in this._attrsByLName;
+    return this._attrsByLName[key] !== undefined;
   }},
 
   // Set the attribute without error checking. The parser uses this.
@@ -419,7 +420,7 @@
     if (!xml.isValidName(qname)) utils.InvalidCharacterError();
     if (this.isHTML) qname = utils.toASCIILowerCase(qname);
     if (qname.substring(0, 5) === 'xmlns') utils.NamespaceError();
-    this._setAttribute(qname, value);
+    this._setAttribute(qname, String(value));
   }},
 
 
@@ -486,7 +487,7 @@
        !(qname === 'xmlns' || prefix === 'xmlns')))
       utils.NamespaceError();
 
-    this._setAttributeNS(ns, qname, value);
+    this._setAttributeNS(ns, qname, String(value));
   }},
 
   removeAttribute: { value: function removeAttribute(qname) {
@@ -574,7 +575,7 @@
       attr = this._newattr(qname);
       isnew = true;
     }
-    attr.value = value;
+    attr.value = String(value);
     if (this._attributes) this._attributes[qname] = attr;
     if (isnew && this._newattrhook) this._newattrhook(qname, value);
   }},
@@ -684,6 +685,7 @@
 
 });
 
+Object.defineProperties(Element.prototype, ChildNode);
 Object.defineProperties(Element.prototype, NonDocumentTypeChildNode);
 
 // Register special handling for the id attribute
@@ -750,6 +752,7 @@
   },
 
   // Legacy aliases (see gh#70 and https://dom.spec.whatwg.org/#interface-attr)
+  get nodeName() { return this.name; },
   get nodeValue() { return this.value; },
   get textContent() { return this.value; },
   set nodeValue(v) { this.value = v; },
diff --git a/node_modules/domino/lib/Leaf.js b/node_modules/domino/lib/Leaf.js
index ed80fe0..151a129 100644
--- a/node_modules/domino/lib/Leaf.js
+++ b/node_modules/domino/lib/Leaf.js
@@ -1,8 +1,10 @@
 "use strict";
 module.exports = Leaf;
 
-var HierarchyRequestError = require('./utils').HierarchyRequestError;
 var Node = require('./Node');
+var utils = require('./utils');
+var HierarchyRequestError = utils.HierarchyRequestError;
+var NotFoundError = utils.NotFoundError;
 
 // This class defines common functionality for node subtypes that
 // can never have children
@@ -13,10 +15,18 @@
   hasChildNodes: { value: function() { return false; }},
   firstChild: { value: null },
   lastChild: { value: null },
-  insertBefore: { value: HierarchyRequestError },
-  replaceChild: { value: HierarchyRequestError },
-  removeChild: { value: HierarchyRequestError },
-  appendChild: { value: HierarchyRequestError },
+  insertBefore: { value: function(node, child) {
+    if (!node.nodeType) throw new TypeError('not a node');
+    HierarchyRequestError();
+  }},
+  replaceChild: { value: function(node, child) {
+    if (!node.nodeType) throw new TypeError('not a node');
+    HierarchyRequestError();
+  }},
+  removeChild: { value: function(node) {
+    if (!node.nodeType) throw new TypeError('not a node');
+    NotFoundError();
+  }},
   childNodes: { get: function() {
     if (!this._childNodes) this._childNodes = [];
     return this._childNodes;
diff --git a/node_modules/domino/lib/Node.js b/node_modules/domino/lib/Node.js
index 28ea99a..18c4ae4 100644
--- a/node_modules/domino/lib/Node.js
+++ b/node_modules/domino/lib/Node.js
@@ -110,26 +110,182 @@
     return i+1 === sibs.length ? null : sibs[i+1];
   }},
 
-  insertBefore: { value: function insertBefore(child, refChild) {
+
+  _countChildrenOfType: { value: function(type) {
+    var sum = 0, nodes = this.childNodes, length = nodes.length, i;
+    for (i=0; i<length; i++) {
+      if (nodes[i].nodeType === type) sum++;
+    }
+    return sum;
+  }},
+
+  _ensureInsertValid: { value: function _ensureInsertValid(node, child, 
isPreinsert) {
+    var parent = this, i;
+    if (!node.nodeType) throw new TypeError('not a node');
+    // 1. If parent is not a Document, DocumentFragment, or Element
+    // node, throw a HierarchyRequestError.
+    switch (parent.nodeType) {
+    case DOCUMENT_NODE:
+    case DOCUMENT_FRAGMENT_NODE:
+    case ELEMENT_NODE:
+      break;
+    default: utils.HierarchyRequestError();
+    }
+    // 2. If node is a host-including inclusive ancestor of parent,
+    // throw a HierarchyRequestError.
+    if (node.isAncestor(parent)) utils.HierarchyRequestError();
+    // 3. If child is not null and its parent is not parent, then
+    // throw a NotFoundError. (replaceChild omits the 'child is not null'
+    // and throws a TypeError here if child is null.)
+    if (child !== null || !isPreinsert) {
+      if (child.parentNode !== parent) utils.NotFoundError();
+    }
+    // 4. If node is not a DocumentFragment, DocumentType, Element,
+    // Text, ProcessingInstruction, or Comment node, throw a
+    // HierarchyRequestError.
+    switch (node.nodeType) {
+    case DOCUMENT_FRAGMENT_NODE:
+    case DOCUMENT_TYPE_NODE:
+    case ELEMENT_NODE:
+    case TEXT_NODE:
+    case PROCESSING_INSTRUCTION_NODE:
+    case COMMENT_NODE:
+      break;
+    default: utils.HierarchyRequestError();
+    }
+    // 5. If either node is a Text node and parent is a document, or
+    // node is a doctype and parent is not a document, throw a
+    // HierarchyRequestError.
+    // 6. If parent is a document, and any of the statements below, switched
+    // on node, are true, throw a HierarchyRequestError.
+    if (parent.nodeType === DOCUMENT_NODE) {
+      switch (node.nodeType) {
+      case TEXT_NODE:
+        utils.HierarchyRequestError();
+        break;
+      case DOCUMENT_FRAGMENT_NODE:
+        // 6a1. If node has more than one element child or has a Text
+        // node child.
+        if (node._countChildrenOfType(TEXT_NODE) > 0)
+          utils.HierarchyRequestError();
+        switch (node._countChildrenOfType(ELEMENT_NODE)) {
+        case 0:
+          break;
+        case 1:
+          // 6a2. Otherwise, if node has one element child and either
+          // parent has an element child, child is a doctype, or child
+          // is not null and a doctype is following child. [preinsert]
+          // 6a2. Otherwise, if node has one element child and either
+          // parent has an element child that is not child or a
+          // doctype is following child. [replaceWith]
+          if (child !== null /* always true here for replaceWith */) {
+            if (isPreinsert && child.nodeType === DOCUMENT_TYPE_NODE)
+              utils.HierarchyRequestError();
+            for (i=parent.childNodes.length-1; i>=0; i--) {
+              if (parent.childNodes[i] === child) break;
+              if (parent.childNodes[i].nodeType === DOCUMENT_TYPE_NODE)
+                utils.HierarchyRequestError();
+            }
+          }
+          i = parent._countChildrenOfType(ELEMENT_NODE);
+          if (isPreinsert) {
+            // "parent has an element child"
+            if (i > 0)
+              utils.HierarchyRequestError();
+          } else {
+            // "parent has an element child that is not child"
+            if (i > 1 || (i === 1 && child.nodeType !== ELEMENT_NODE))
+              utils.HierarchyRequestError();
+          }
+          break;
+        default: // 6a1, continued. (more than one Element child)
+          utils.HierarchyRequestError();
+        }
+        break;
+      case ELEMENT_NODE:
+        // 6b. parent has an element child, child is a doctype, or
+        // child is not null and a doctype is following child. [preinsert]
+        // 6b. parent has an element child that is not child or a
+        // doctype is following child. [replaceWith]
+        if (child !== null /* always true here for replaceWith */) {
+          if (isPreinsert && child.nodeType === DOCUMENT_TYPE_NODE)
+            utils.HierarchyRequestError();
+          for (i=parent.childNodes.length-1; i>=0; i--) {
+            if (parent.childNodes[i] === child) break;
+            if (parent.childNodes[i].nodeType === DOCUMENT_TYPE_NODE)
+              utils.HierarchyRequestError();
+          }
+        }
+        i = parent._countChildrenOfType(ELEMENT_NODE);
+        if (isPreinsert) {
+          // "parent has an element child"
+          if (i > 0)
+            utils.HierarchyRequestError();
+        } else {
+          // "parent has an element child that is not child"
+          if (i > 1 || (i === 1 && child.nodeType !== ELEMENT_NODE))
+            utils.HierarchyRequestError();
+        }
+        break;
+      case DOCUMENT_TYPE_NODE:
+        // 6c. parent has a doctype child, child is non-null and an
+        // element is preceding child, or child is null and parent has
+        // an element child. [preinsert]
+        // 6c. parent has a doctype child that is not child, or an
+        // element is preceding child. [replaceWith]
+        if (child === null) {
+          if (parent._countChildrenOfType(ELEMENT_NODE))
+            utils.HierarchyRequestError();
+        } else {
+          // child is always non-null for [replaceWith] case
+          for (i=0; i<parent.childNodes.length; i++) {
+            if (parent.childNodes[i] === child) break;
+            if (parent.childNodes[i].nodeType === ELEMENT_NODE)
+              utils.HierarchyRequestError();
+          }
+        }
+        i = parent._countChildrenOfType(DOCUMENT_TYPE_NODE);
+        if (isPreinsert) {
+          // "parent has an doctype child"
+          if (i > 0)
+            utils.HierarchyRequestError();
+        } else {
+          // "parent has an doctype child that is not child"
+          if (i > 1 || (i === 1 && child.nodeType !== DOCUMENT_TYPE_NODE))
+            utils.HierarchyRequestError();
+        }
+        break;
+      }
+    } else {
+      // 5, continued: (parent is not a document)
+      if (node.nodeType === DOCUMENT_TYPE_NODE) utils.HierarchyRequestError();
+    }
+  }},
+
+  insertBefore: { value: function insertBefore(node, child) {
     var parent = this;
-    if (refChild === null) return this.appendChild(child);
-    if (refChild.parentNode !== parent) utils.NotFoundError();
-    if (child.isAncestor(parent)) utils.HierarchyRequestError();
-    if (child.nodeType === DOCUMENT_NODE) utils.HierarchyRequestError();
-    parent.ensureSameDoc(child);
-    child.insert(parent, refChild.index);
-    return child;
+    // 1. Ensure pre-insertion validity
+    parent._ensureInsertValid(node, child, true);
+    // 2. Let reference child be child.
+    var refChild = child;
+    // 3. If reference child is node, set it to node's next sibling
+    if (refChild === node) { refChild = node.nextSibling; }
+    // 4. Adopt node into parent's node document.
+    parent.doc.adoptNode(node);
+    // 5. Insert node into parent before reference child.
+    if (refChild === null) {
+      parent._appendChild(node);
+    } else {
+      node.insert(parent, refChild.index);
+    }
+    // 6. Return node
+    return node;
   }},
 
 
   appendChild: { value: function(child) {
-    var parent = this;
-    if (child.isAncestor(parent)) {
-      utils.HierarchyRequestError();
-    }
-    if (child.nodeType === DOCUMENT_NODE) utils.HierarchyRequestError();
-    parent.ensureSameDoc(child);
-    return parent._appendChild(child);
+    // This invokes _appendChild after doing validity checks.
+    return this.insertBefore(child, null);
   }},
 
   _appendChild: { value: function(child) {
@@ -139,18 +295,30 @@
 
   removeChild: { value: function removeChild(child) {
     var parent = this;
+    if (!child.nodeType) throw new TypeError('not a node');
     if (child.parentNode !== parent) utils.NotFoundError();
     child.remove();
     return child;
   }},
 
-  replaceChild: { value: function replaceChild(newChild, oldChild) {
+  // To replace a `child` with `node` within a `parent` (this)
+  replaceChild: { value: function replaceChild(node, child) {
     var parent = this;
-    if (oldChild.parentNode !== parent) utils.NotFoundError();
-    if (newChild.isAncestor(parent)) utils.HierarchyRequestError();
-    parent.ensureSameDoc(newChild);
-    newChild._insertOrReplace(parent, oldChild.index, true);
-    return oldChild;
+    // Ensure validity (slight differences from pre-insertion check)
+    parent._ensureInsertValid(node, child, false);
+    // Adopt node into parent's node document.
+    if (node.doc !== parent.doc) {
+      // XXX adoptNode has side-effect of removing node from its parent
+      // and generating a mutation event, thus causing the _insertOrReplace
+      // to generate two deletes and an insert instead of a 'move'
+      // event.  It looks like the new MutationObserver stuff avoids
+      // this problem, but for now let's only adopt (ie, remove `node`
+      // from its parent) here if we need to.
+      parent.doc.adoptNode(node);
+    }
+    // Do the replace.
+    node._insertOrReplace(parent, child.index, true);
+    return child;
   }},
 
   // See: http://ejohn.org/blog/comparing-document-position/
@@ -348,21 +516,6 @@
     }
   }},
 
-  // Remove this node from its parent
-  remove: { value: function remove() {
-    // Send mutation events if necessary
-    if (this.rooted) this.doc.mutateRemove(this);
-
-    // Remove this node from its parents array of children
-    this.parentNode.childNodes.splice(this.index, 1);
-
-    // Update the structure id for all ancestors
-    this.parentNode.modify();
-
-    // Forget this node's parent
-    this.parentNode = undefined;
-  }},
-
   // Remove all of this node's children.  This is a minor
   // optimization that only calls modify() once.
   removeChildren: { value: function removeChildren() {
@@ -371,7 +524,7 @@
       var root = this.rooted ? this.ownerDocument : null;
       for(var i = 0; i < n; i++) {
         if (root) root.mutateRemove(this.childNodes[i]);
-        this.childNodes[i].parentNode = undefined;
+        this.childNodes[i].parentNode = null;
       }
       this.childNodes.length = 0; // Forget all children
       this.modify();              // Update last modified type once only
@@ -413,7 +566,7 @@
     if (isReplace) {
       var oldChild = parent.childNodes[index];
       if (oldChild.rooted) oldChild.doc.mutateRemove(oldChild);
-      oldChild.parentNode = undefined;
+      oldChild.parentNode = null;
     }
 
     // If both the child and the parent are rooted, then we want to
@@ -453,7 +606,7 @@
         // Add all nodes to the new parent, overwriting the old child
         kids.splice.apply(kids, spliceArgs);
         // Call the mutation handlers
-        // Use spliceArgs since the original array has been destroyed. The 
+        // Use spliceArgs since the original array has been destroyed. The
         // liveness guarantee requires us to clone the array so that
         // references to the childNodes of the DocumentFragment will be empty
         // when the insertion handlers are called.
diff --git a/node_modules/domino/lib/ProcessingInstruction.js 
b/node_modules/domino/lib/ProcessingInstruction.js
index a00bb26..a92595c 100644
--- a/node_modules/domino/lib/ProcessingInstruction.js
+++ b/node_modules/domino/lib/ProcessingInstruction.js
@@ -2,7 +2,7 @@
 module.exports = ProcessingInstruction;
 
 var Node = require('./Node');
-var Leaf = require('./Leaf');
+var CharacterData = require('./CharacterData');
 
 function ProcessingInstruction(doc, target, data) {
   this.nodeType = Node.PROCESSING_INSTRUCTION_NODE;
@@ -19,7 +19,7 @@
   }
 };
 
-ProcessingInstruction.prototype = Object.create(Leaf.prototype, {
+ProcessingInstruction.prototype = Object.create(CharacterData.prototype, {
   nodeName: { get: function() { return this.target; }},
   nodeValue: nodeValue,
   textContent: nodeValue,
diff --git a/node_modules/domino/lib/Window.js 
b/node_modules/domino/lib/Window.js
index 9e49c64..5ee4ad1 100644
--- a/node_modules/domino/lib/Window.js
+++ b/node_modules/domino/lib/Window.js
@@ -11,7 +11,7 @@
   this.document = document || new DOMImplementation().createHTMLDocument("");
   this.document._scripting_enabled = true;
   this.document.defaultView = this;
-  this.location = new Location(this, "about:blank");
+  this.location = new Location(this, this.document._address || 'about:blank');
 }
 
 Window.prototype = Object.create(EventTarget.prototype, {
diff --git a/node_modules/domino/lib/attributes.js 
b/node_modules/domino/lib/attributes.js
index 6d973cb..271bacd 100644
--- a/node_modules/domino/lib/attributes.js
+++ b/node_modules/domino/lib/attributes.js
@@ -7,17 +7,21 @@
     attr.type.forEach(function(val) {
       valid[val.value || val] = val.alias || val;
     });
-    var defaultValue = attr.implied ? '' : valid[0];
+    var missingValueDefault = attr.missing;
+    if (missingValueDefault===undefined) { missingValueDefault = null; }
+    var invalidValueDefault = attr.invalid;
+    if (invalidValueDefault===undefined) { invalidValueDefault = 
missingValueDefault; }
     return {
       get: function() {
         var v = this._getattr(attr.name);
-        if (v === null) return defaultValue;
+        if (v === null) return missingValueDefault;
 
         v = valid[v.toLowerCase()];
         if (v !== undefined) return v;
-        return defaultValue;
+        if (invalidValueDefault !== null) return invalidValueDefault;
+        return v;
       },
-      set: function(v) { 
+      set: function(v) {
         this._setattr(attr.name, v);
       }
     };
@@ -37,13 +41,19 @@
       }
     };
   }
-  else if (attr.type === Number) {
+  else if (attr.type === Number ||
+           attr.type === "long" ||
+           attr.type === "unsigned long" ||
+           attr.type === "limited unsigned long with fallback") {
     return numberPropDesc(attr);
   }
   else if (!attr.type || attr.type === String) {
     return {
       get: function() { return this._getattr(attr.name) || ''; },
-      set: function(v) { this._setattr(attr.name, v); }
+      set: function(v) {
+        if (attr.treatNullAsEmptyString && v === null) { v = ''; }
+        this._setattr(attr.name, v);
+      }
     };
   }
   else if (typeof attr.type === 'function') {
@@ -76,22 +86,49 @@
     def = function() { return a.default; };
   }
   else {
-    def = function() { utils.assert(false); };
+    def = function() { utils.assert(false, typeof a.default); };
+  }
+  var unsigned_long = (a.type === 'unsigned long');
+  var signed_long = (a.type === 'long');
+  var unsigned_fallback = (a.type === 'limited unsigned long with fallback');
+  var min = a.min, max = a.max, setmin = a.setmin;
+  if (min === undefined) {
+    if (unsigned_long) min = 0;
+    if (signed_long) min = -0x80000000;
+    if (unsigned_fallback) min = 1;
+  }
+  if (max === undefined) {
+    if (unsigned_long || signed_long || unsigned_fallback) max = 0x7FFFFFFF;
   }
 
   return {
     get: function() {
       var v = this._getattr(a.name);
       var n = a.float ? parseFloat(v) : parseInt(v, 10);
-      if (!isFinite(n) || (a.min !== undefined && n < a.min) || (a.max !== 
undefined && n > a.max)) {
+      if (v === null || !isFinite(n) || (min !== undefined && n < min) || (max 
!== undefined && n > max)) {
         return def.call(this);
+      }
+      if (unsigned_long || signed_long || unsigned_fallback) {
+        if (!/^[ \t\n\f\r]*[-+]?[0-9]/.test(v)) { return def.call(this); }
+        n = n|0; // jshint ignore:line
       }
       return n;
     },
     set: function(v) {
-      if (a.setmin !== undefined && v < a.setmin) {
+      if (!a.float) { v = Math.floor(v); }
+      if (setmin !== undefined && v < setmin) {
         utils.IndexSizeError(a.name + ' set to ' + v);
       }
+      if (unsigned_long) {
+        v = (v < 0 || v > 0x7FFFFFFF) ? def.call(this) :
+          (v|0);  // jshint ignore:line
+      } else if (unsigned_fallback) {
+        v = (v < 1 || v > 0x7FFFFFFF) ? def.call(this) :
+          (v|0); // jshint ignore:line
+      } else if (signed_long) {
+        v = (v < -0x80000000 || v > 0x7FFFFFFF) ? def.call(this) :
+          (v|0); // jshint ignore:line
+      }
       this._setattr(a.name, String(v));
     }
   };
diff --git a/node_modules/domino/lib/config.js 
b/node_modules/domino/lib/config.js
new file mode 100644
index 0000000..abd3475
--- /dev/null
+++ b/node_modules/domino/lib/config.js
@@ -0,0 +1,7 @@
+/*
+ * This file defines Domino behaviour that can be externally configured.
+ * To change these settings, set the relevant global property *before*
+ * you call `require("domino")`.
+ */
+
+exports.isApiWritable = !global.__domino_frozen__;
diff --git a/node_modules/domino/lib/htmlelts.js 
b/node_modules/domino/lib/htmlelts.js
index 0c5fb20..b4ff0ad 100644
--- a/node_modules/domino/lib/htmlelts.js
+++ b/node_modules/domino/lib/htmlelts.js
@@ -22,13 +22,39 @@
   return {
     get: function() {
       var v = this._getattr(attr);
-      return this.doc._resolve(v);
+      if (v === null) { return ''; }
+      var url = this.doc._resolve(v);
+      return (url === null) ? v : url;
     },
     set: function(value) {
       this._setattr(attr, value);
     }
   };
 }
+
+function CORS(attr) {
+  return {
+    get: function() {
+      var v = this._getattr(attr);
+      if (v === null) { return null; }
+      if (v.toLowerCase() === 'use-credentials') { return 'use-credentials'; }
+      return 'anonymous';
+    },
+    set: function(value) {
+      if (value===null || value===undefined) {
+        this.removeAttribute(attr);
+      } else {
+        this._setattr(attr, value);
+      }
+    }
+  };
+}
+
+var REFERRER = {
+  type: ["", "no-referrer", "no-referrer-when-downgrade", "same-origin", 
"origin", "strict-origin", "origin-when-cross-origin", 
"strict-origin-when-cross-origin", "unsafe-url"],
+  missing: '',
+};
+
 
 // XXX: the default value for tabIndex should be 0 if the element is
 // focusable and -1 if it is not.  But the full definition of focusable
@@ -114,10 +140,10 @@
   attributes: {
     title: String,
     lang: String,
-    dir: {type: ["ltr", "rtl", "auto"], implied: true},
+    dir: {type: ["ltr", "rtl", "auto"], missing: ''},
     accessKey: String,
     hidden: Boolean,
-    tabIndex: {type: Number, default: function() {
+    tabIndex: {type: "long", default: function() {
       if (this.tagName in focusableElements ||
         this.contentEditable)
         return 0;
@@ -205,7 +231,14 @@
     rel: String,
     media: String,
     hreflang: String,
-    type: String
+    type: String,
+    referrerPolicy: REFERRER,
+    // Obsolete
+    coords: String,
+    charset: String,
+    name: String,
+    rev: String,
+    shape: String,
   }
 });
 URLUtils._inherit(htmlNameToImpl.a.prototype);
@@ -225,8 +258,12 @@
     hreflang: String,
     type: String,
     shape: String,
-    coords: String
+    coords: String,
+    ping: String,
     // XXX: also reflect relList
+    referrerPolicy: REFERRER,
+    // Obsolete
+    noHref: Boolean,
   }
 });
 
@@ -234,7 +271,11 @@
   tag: 'br',
   ctor: function HTMLBRElement(doc, localName, prefix) {
     HTMLElement.call(this, doc, localName, prefix);
-  }
+  },
+  attributes: {
+    // Obsolete
+    clear: String
+  },
 });
 
 define({
@@ -265,7 +306,16 @@
     "afterprint", "beforeprint", "beforeunload", "blur", "error",
     "focus","hashchange", "load", "message", "offline", "online",
     "pagehide", "pageshow","popstate","resize","scroll","storage","unload",
-  ]
+  ],
+  attributes: {
+    // Obsolete
+    text: { type: String, treatNullAsEmptyString: true },
+    link: { type: String, treatNullAsEmptyString: true },
+    vLink: { type: String, treatNullAsEmptyString: true },
+    aLink: { type: String, treatNullAsEmptyString: true },
+    bgColor: { type: String, treatNullAsEmptyString: true },
+    background: String,
+  }
 });
 
 define({
@@ -279,13 +329,11 @@
     value: String,
     disabled: Boolean,
     autofocus: Boolean,
-    type: ["submit", "reset", "button"],
+    type: { type:["submit", "reset", "button", "menu"], missing: 'submit' },
     formTarget: String,
     formNoValidate: Boolean,
-    formMethod: ["get", "post"],
-    formEnctype: [
-      "application/x-www-form-urlencoded", "multipart/form-data", "text/plain"
-    ]
+    formMethod: { type: ["get", "post", "dialog"], invalid: 'get', missing: '' 
},
+    formEnctype: { type: ["application/x-www-form-urlencoded", 
"multipart/form-data", "text/plain"], invalid: 
"application/x-www-form-urlencoded", missing: '' },
   }
 });
 
@@ -293,6 +341,20 @@
   tag: 'dl',
   ctor: function HTMLDListElement(doc, localName, prefix) {
     HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // Obsolete
+    compact: Boolean,
+  }
+});
+
+define({
+  tag: 'data',
+  ctor: function HTMLDataElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    value: String,
   }
 });
 
@@ -317,6 +379,10 @@
   tag: 'div',
   ctor: function HTMLDivElement(doc, localName, prefix) {
     HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // Obsolete
+    align: String
   }
 });
 
@@ -329,7 +395,10 @@
     src: URL,
     type: String,
     width: String,
-    height: String
+    height: String,
+    // Obsolete
+    align: String,
+    name: String,
   }
 });
 
@@ -352,15 +421,15 @@
   },
   attributes: {
     action: String,
-    autocomplete: ['on', 'off'],
+    autocomplete: {type:['on', 'off'], missing: 'on'},
     name: String,
     acceptCharset: {name: "accept-charset"},
     target: String,
     noValidate: Boolean,
-    method: ["get", "post"],
+    method: { type: ["get", "post", "dialog"], invalid: 'get', missing: 'get' 
},
     // Both enctype and encoding reflect the enctype content attribute
-    enctype: ["application/x-www-form-urlencoded", "multipart/form-data", 
"text/plain"],
-    encoding: {name: 'enctype', type: ["application/x-www-form-urlencoded", 
"multipart/form-data", "text/plain"]}
+    enctype: { type: ["application/x-www-form-urlencoded", 
"multipart/form-data", "text/plain"], invalid: 
"application/x-www-form-urlencoded", missing: 
"application/x-www-form-urlencoded" },
+    encoding: {name: 'enctype', type: ["application/x-www-form-urlencoded", 
"multipart/form-data", "text/plain"], invalid: 
"application/x-www-form-urlencoded", missing: 
"application/x-www-form-urlencoded" },
   }
 });
 
@@ -368,7 +437,15 @@
   tag: 'hr',
   ctor: function HTMLHRElement(doc, localName, prefix) {
     HTMLElement.call(this, doc, localName, prefix);
-  }
+  },
+  attributes: {
+    // Obsolete
+    align: String,
+    color: String,
+    noShade: Boolean,
+    size: String,
+    width: String,
+  },
 });
 
 define({
@@ -382,13 +459,21 @@
   tags: ['h1','h2','h3','h4','h5','h6'],
   ctor: function HTMLHeadingElement(doc, localName, prefix) {
     HTMLElement.call(this, doc, localName, prefix);
-  }
+  },
+  attributes: {
+    // Obsolete
+    align: String,
+  },
 });
 
 define({
   tag: 'html',
   ctor: function HTMLHtmlElement(doc, localName, prefix) {
     HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // Obsolete
+    version: String
   }
 });
 
@@ -415,14 +500,17 @@
     height: String,
     // XXX: sandbox is a reflected settable token list
     seamless: Boolean,
+    allowFullscreen: Boolean,
+    allowUserMedia: Boolean,
+    allowPaymentRequest: Boolean,
+    referrerPolicy: REFERRER,
+    // Obsolete
     align: String,
-    allowfullscreen: Boolean,
-    frameBorder: String,
-    longDesc: String,
-    marginHeight: String,
-    marginWidth: String,
-    referrerPolicy: String,
     scrolling: String,
+    frameBorder: String,
+    longDesc: URL,
+    marginHeight: { type: String, treatNullAsEmptyString: true },
+    marginWidth: { type: String, treatNullAsEmptyString: true },
   }
 });
 
@@ -432,13 +520,23 @@
     HTMLElement.call(this, doc, localName, prefix);
   },
   attributes: {
-    src: URL,
     alt: String,
-    crossOrigin: String,
+    src: URL,
+    srcset: String,
+    crossOrigin: CORS,
     useMap: String,
     isMap: Boolean,
-    height: { type: Number, default: 0 },
-    width: { type: Number, default: 0 }
+    height: { type: "unsigned long", default: 0 },
+    width: { type: "unsigned long", default: 0 },
+    referrerPolicy: REFERRER,
+    // Obsolete:
+    name: String,
+    lowsrc: URL,
+    align: String,
+    hspace: { type: "unsigned long", default: 0 },
+    vspace: { type: "unsigned long", default: 0 },
+    longDesc: URL,
+    border: { type: String, treatNullAsEmptyString: true },
   }
 });
 
@@ -482,18 +580,23 @@
     value: String,
     src: URL,
     defaultChecked: {name: 'checked', type: Boolean},
-    size: {type: Number, default: 20, min: 1, setmin: 1},
-    maxLength: {min: 0, setmin: 0},
-    autocomplete: ["on", "off"],
-    type: ["text", "hidden", "search", "tel", "url", "email", "password",
-      "datetime", "date", "month", "week", "time", "datetime-local",
-      "number", "range", "color", "checkbox", "radio", "file", "submit",
-      "image", "reset", "button"
-    ],
+    size: {type: 'unsigned long', default: 20, min: 1, setmin: 1},
+    maxLength: {type: 'unsigned long', min: 0, setmin: 0, default: -1},
+    autocomplete: String, // It's complicated
+    type: { type:
+            ["text", "hidden", "search", "tel", "url", "email", "password",
+             "datetime", "date", "month", "week", "time", "datetime-local",
+             "number", "range", "color", "checkbox", "radio", "file", "submit",
+             "image", "reset", "button"],
+            missing: 'text' },
     formTarget: String,
     formNoValidate: Boolean,
-    formMethod: ["get", "post"],
-    formEnctype: ["application/x-www-form-urlencoded", "multipart/form-data", 
"text/plain"]
+    formMethod: { type: ["get", "post"], invalid: 'get', missing: '' },
+    formEnctype: { type: ["application/x-www-form-urlencoded", 
"multipart/form-data", "text/plain"], invalid: 
"application/x-www-form-urlencoded", missing: '' },
+    inputMode: { type: [ "verbatim", "latin", "latin-name", "latin-prose", 
"full-width-latin", "kana", "kana-name", "katakana", "numeric", "tel", "email", 
"url" ], missing: '' },
+    // Obsolete
+    align: String,
+    useMap: String,
   }
 });
 
@@ -508,7 +611,7 @@
     disabled: Boolean,
     autofocus: Boolean,
     challenge: String,
-    keytype: ["rsa"]
+    keytype: { type:["rsa"], missing: '' },
   }
 });
 
@@ -518,7 +621,9 @@
     HTMLElement.call(this, doc, localName, prefix);
   },
   attributes: {
-    value: {type: Number, default: 0},
+    value: {type: "long", default: 0},
+    // Obsolete
+    type: String,
   }
 });
 
@@ -529,7 +634,7 @@
   },
   props: formAssociatedProps,
   attributes: {
-    htmlFor: {name: 'for'}
+    htmlFor: {name: 'for', type: String}
   }
 });
 
@@ -537,7 +642,11 @@
   tag: 'legend',
   ctor: function HTMLLegendElement(doc, localName, prefix) {
     HTMLElement.call(this, doc, localName, prefix);
-  }
+  },
+  attributes: {
+    // Obsolete
+    align: String
+  },
 });
 
 define({
@@ -551,7 +660,15 @@
     rel: String,
     media: String,
     hreflang: String,
-    type: String
+    type: String,
+    crossOrigin: CORS,
+    nonce: String,
+    integrity: String,
+    referrerPolicy: REFERRER,
+    // Obsolete
+    charset: String,
+    rev: String,
+    target: String,
   }
 });
 
@@ -571,8 +688,12 @@
     HTMLElement.call(this, doc, localName, prefix);
   },
   attributes: {
-    type: String,
-    label: String
+    // XXX: not quite right, default should be popup if parent element is
+    // popup.
+    type: { type: [ 'context', 'popup', 'toolbar' ], missing: 'toolbar' },
+    label: String,
+    // Obsolete
+    compact: Boolean,
   }
 });
 
@@ -584,8 +705,9 @@
   attributes: {
     name: String,
     content: String,
+    httpEquiv: {name: 'http-equiv', type: String},
+    // Obsolete
     scheme: String,
-    httpEquiv: {name: 'http-equiv', type: String}
   }
 });
 
@@ -603,7 +725,7 @@
     HTMLElement.call(this, doc, localName, prefix);
   },
   attributes: {
-    cite: String,
+    cite: URL,
     dateTime: String
   }
 });
@@ -629,7 +751,7 @@
     type: String,
     reversed: Boolean,
     start: {
-      type: Number,
+      type: "long",
       default: function() {
        // The default value of the start attribute is 1 unless the list is
        // reversed. Then it is the # of li children
@@ -638,7 +760,9 @@
        else
          return 1;
       }
-    }
+    },
+    // Obsolete
+    compact: Boolean,
   }
 });
 
@@ -649,13 +773,24 @@
   },
   props: formAssociatedProps,
   attributes: {
-    data: String,
+    data: URL,
     type: String,
     name: String,
     useMap: String,
     typeMustMatch: Boolean,
     width: String,
-    height: String
+    height: String,
+    // Obsolete
+    align: String,
+    archive: String,
+    code: String,
+    declare: Boolean,
+    hspace: { type: "unsigned long", default: 0 },
+    standby: String,
+    vspace: { type: "unsigned long", default: 0 },
+    codeBase: URL,
+    codeType: String,
+    border: { type: String, treatNullAsEmptyString: true },
   }
 });
 
@@ -687,7 +822,8 @@
   attributes: {
     disabled: Boolean,
     defaultSelected: {name: 'selected', type: Boolean},
-    label: String
+    label: String,
+    value: String,
   }
 });
 
@@ -707,6 +843,10 @@
   tag: 'p',
   ctor: function HTMLParagraphElement(doc, localName, prefix) {
     HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // Obsolete
+    align: String
   }
 });
 
@@ -717,7 +857,10 @@
   },
   attributes: {
     name: String,
-    value: String
+    value: String,
+    // Obsolete
+    type: String,
+    valueType: String,
   }
 });
 
@@ -725,6 +868,10 @@
   tags: ['pre',/*legacy elements:*/'listing','xmp'],
   ctor: function HTMLPreElement(doc, localName, prefix) {
     HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // Obsolete
+    width: { type: "long", default: 0 },
   }
 });
 
@@ -778,7 +925,10 @@
     type: String,
     charset: String,
     defer: Boolean,
-    async: Boolean
+    async: Boolean,
+    crossOrigin: CORS,
+    nonce: String,
+    integrity: String,
   }
 });
 
@@ -799,7 +949,7 @@
     autofocus: Boolean,
     multiple: Boolean,
     required: Boolean,
-    size: {type: Number, default: 0}
+    size: {type: "unsigned long", default: 0}
   }
 });
 
@@ -838,6 +988,10 @@
   tag: 'caption',
   ctor: function HTMLTableCaptionElement(doc, localName, prefix) {
     HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // Obsolete
+    align: String,
   }
 });
 
@@ -847,9 +1001,21 @@
     HTMLElement.call(this, doc, localName, prefix);
   },
   attributes: {
-    colSpan: {type: Number, default: 1, min: 1, setmin: 1},
-    rowSpan: {type: Number, default: 1}
+    colSpan: {type: "unsigned long", default: 1},
+    rowSpan: {type: "unsigned long", default: 1},
     //XXX Also reflect settable token list headers
+    scope: { type: ['row','col','rowgroup','colgroup'], missing: '' },
+    abbr: String,
+    // Obsolete
+    align: String,
+    axis: String,
+    height: String,
+    width: String,
+    ch: { name: 'char', type: String },
+    chOff: { name: 'charoff', type: String },
+    noWrap: Boolean,
+    vAlign: String,
+    bgColor: { type: String, treatNullAsEmptyString: true },
   }
 });
 
@@ -859,7 +1025,13 @@
     HTMLElement.call(this, doc, localName, prefix);
   },
   attributes: {
-    span: {type: Number, default: 1, min: 1, setmin: 1}
+    span: {type: 'limited unsigned long with fallback', default: 1, min: 1},
+    // Obsolete
+    align: String,
+    ch: { name: 'char', type: String },
+    chOff: { name: 'charoff', type: String },
+    vAlign: String,
+    width: String,
   }
 });
 
@@ -874,7 +1046,16 @@
     }}
   },
   attributes: {
-    border: String
+    // Obsolete
+    align: String,
+    border: String,
+    frame: String,
+    rules: String,
+    summary: String,
+    width: String,
+    bgColor: { type: String, treatNullAsEmptyString: true },
+    cellPadding: { type: String, treatNullAsEmptyString: true },
+    cellSpacing: { type: String, treatNullAsEmptyString: true },
   }
 });
 
@@ -899,7 +1080,15 @@
     cells: { get: function() {
       return this.querySelectorAll('td,th');
     }}
-  }
+  },
+  attributes: {
+    // Obsolete
+    align: String,
+    ch: { name: 'char', type: String },
+    chOff: { name: 'charoff', type: String },
+    vAlign: String,
+    bgColor: { type: String, treatNullAsEmptyString: true },
+  },
 });
 
 define({
@@ -911,6 +1100,13 @@
     rows: { get: function() {
       return this.getElementsByTagName('tr');
     }}
+  },
+  attributes: {
+    // Obsolete
+    align: String,
+    ch: { name: 'char', type: String },
+    chOff: { name: 'charoff', type: String },
+    vAlign: String,
   }
 });
 
@@ -921,6 +1117,7 @@
   },
   props: formAssociatedProps,
   attributes: {
+    autocomplete: String, // It's complicated
     name: String,
     disabled: Boolean,
     autofocus: Boolean,
@@ -929,9 +1126,10 @@
     dirName: String,
     required: Boolean,
     readOnly: Boolean,
-    rows: {type: Number, default: 2, min: 1, setmin: 1},
-    cols: {type: Number, default: 20, min: 1, setmin: 1},
-    maxLength: {type: Number, min: 0, setmin: 0}
+    rows: {type: 'limited unsigned long with fallback', default: 2 },
+    cols: {type: 'limited unsigned long with fallback', default: 20 },
+    maxLength: {type: 'unsigned long', min: 0, setmin: 0, default: -1},
+    inputMode: { type: [ "verbatim", "latin", "latin-name", "latin-prose", 
"full-width-latin", "kana", "kana-name", "katakana", "numeric", "tel", "email", 
"url" ], missing: '' },
   }
 });
 
@@ -959,23 +1157,14 @@
 });
 
 define({
-  tag: 'track',
-  ctor: function HTMLTrackElement(doc, localName, prefix) {
-    HTMLElement.call(this, doc, localName, prefix);
-  },
-  attributes: {
-    src: URL,
-    srclang: String,
-    label: String,
-    default: Boolean,
-    kind: ["subtitles", "captions", "descriptions", "chapters", "metadata"]
-  }
-});
-
-define({
   tag: 'ul',
   ctor: function HTMLUListElement(doc, localName, prefix) {
     HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    type: String,
+    // Obsolete
+    compact: Boolean,
   }
 });
 
@@ -985,8 +1174,8 @@
   },
   attributes: {
     src: URL,
-    crossOrigin: String,
-    preload: ["metadata", "none", "auto", {value: "", alias: "auto"}],
+    crossOrigin: CORS,
+    preload: { type:["metadata", "none", "auto", {value: "", alias: "auto"}], 
missing: 'auto' },
     loop: Boolean,
     autoplay: Boolean,
     mediaGroup: String,
@@ -1010,9 +1199,9 @@
     htmlElements.HTMLMediaElement.call(this, doc, localName, prefix);
   },
   attributes: {
-    poster: String,
-    width: {type: Number, min: 0, setmin: 0},
-    height: {type: Number, min: 0, setmin: 0}
+    poster: URL,
+    width: {type: "unsigned long", min: 0, default: 0 },
+    height: {type: "unsigned long", min: 0, default: 0 }
   }
 });
 
@@ -1030,9 +1219,6 @@
   ctor: function HTMLTableHeaderCellElement(doc, localName, prefix) {
     htmlElements.HTMLTableCellElement.call(this, doc, localName, prefix);
   },
-  attributes: {
-    scope: ["", "row", "col", "rowgroup", "colgroup"]
-  }
 });
 
 define({
@@ -1063,8 +1249,8 @@
     toBlob: { value: utils.nyi }
   },
   attributes: {
-    width: { type: Number, default: 0},
-    height: { type: Number, default: 0}
+    width: { type: "unsigned long", default: 300},
+    height: { type: "unsigned long", default: 150}
   }
 });
 
@@ -1113,8 +1299,8 @@
     }
   },
   attributes: {
-    type: String,
-    icon: String,
+    type: { type: ["command","checkbox","radio"], missing: 'command' },
+    icon: URL,
     disabled: Boolean,
     checked: Boolean,
     radiogroup: String,
@@ -1131,7 +1317,7 @@
     srcset: String,
     sizes: String,
     media: String,
-    src: String,
+    src: URL,
     type: String
   }
 });
@@ -1142,11 +1328,11 @@
     HTMLElement.call(this, doc, localName, prefix);
   },
   attributes: {
-    kind: String,
-    src: String,
+    src: URL,
     srclang: String,
     label: String,
-    default: Boolean
+    default: Boolean,
+    kind: { type: ["subtitles", "captions", "descriptions", "chapters", 
"metadata"], missing: 'subtitles', invalid: 'metadata' },
   },
   props: {
     NONE: { get: function() { return 0; } },
diff --git a/node_modules/domino/lib/index.js b/node_modules/domino/lib/index.js
index 88f3324..b6c8ad0 100644
--- a/node_modules/domino/lib/index.js
+++ b/node_modules/domino/lib/index.js
@@ -19,8 +19,9 @@
   return new DOMImplementation().createHTMLDocument("");
 };
 
-exports.createWindow = function(html) {
+exports.createWindow = function(html, address) {
   var document = exports.createDocument(html);
+  if (address !== undefined) { document._address = address; }
   return new Window(document);
 };
 
diff --git a/node_modules/domino/lib/select.js 
b/node_modules/domino/lib/select.js
index 8568fd4..b5ef86c 100644
--- a/node_modules/domino/lib/select.js
+++ b/node_modules/domino/lib/select.js
@@ -58,9 +58,28 @@
 var unquote = function(str) {
   if (!str) return str;
   var ch = str[0];
-  return ch === '"' || ch === '\''
-    ? str.slice(1, -1)
-    : str;
+  if (ch === '"' || ch === '\'') {
+    if (str[str.length-1] === ch) {
+      str = str.slice(1, -1);
+    } else {
+      // bad string.
+      str = str.slice(1);
+    }
+    return str.replace(rules.str_escape, function(s) {
+      var m = /^\\(?:([0-9A-Fa-f]+)|([\r\n\f]+))/.exec(s);
+      if (!m) { return s.slice(1); }
+      if (m[2]) { return ''; /* escaped newlines are ignored in strings. */ }
+      var cp = parseInt(m[1], 16);
+      return String.fromCodePoint ? String.fromCodePoint(cp) :
+        // Not all JavaScript implementations have String.fromCodePoint yet.
+        String.fromCharCode(cp);
+    });
+  } else if (rules.ident.test(str)) {
+    return decodeid(str);
+  } else {
+    // NUMBER, PERCENTAGE, DIMENSION, etc
+    return str;
+  }
 };
 
 var decodeid = function(str) {
@@ -586,6 +605,7 @@
 
 var rules = {
   escape: /\\(?:[^0-9A-Fa-f\r\n]|[0-9A-Fa-f]{1,6}[\r\n\t ]?)/g,
+  str_escape: /(escape)|\\(\n|\r\n?|\f)/g,
   nonascii: /[\u00A0-\uFFFF]/,
   cssid: /(?:(?!-?[0-9])(?:escape|nonascii|[-_a-zA-Z0-9])+)/,
   qname: /^ *(cssid|\*)/,
@@ -594,7 +614,8 @@
   combinator: /^(?: +([^ \w*.#\\]) +|( )+|([^ \w*.#\\]))(?! *$)/,
   attr: /^\[(cssid)(?:([^\w]?=)(inside))?\]/,
   pseudo: /^(:cssid)(?:\((inside)\))?/,
-  inside: /(?:"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|<[^"'>]*>|\\["'>]|[^"'>])*/
+  inside: /(?:"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|<[^"'>]*>|\\["'>]|[^"'>])*/,
+  ident: /^(cssid)$/
 };
 
 rules.cssid = replace(rules.cssid, 'nonascii', rules.nonascii);
@@ -609,6 +630,8 @@
 rules.pseudo = replace(rules.pseudo, 'inside', makeInside('\\(', '\\)'));
 rules.simple = replace(rules.simple, 'pseudo', rules.pseudo);
 rules.simple = replace(rules.simple, 'attr', rules.attr);
+rules.ident = replace(rules.ident, 'cssid', rules.cssid);
+rules.str_escape = replace(rules.str_escape, 'escape', rules.escape);
 
 /**
  * Compiling
@@ -671,6 +694,7 @@
       op = 'noop';
     }
 
+    if (!combinators[op]) { throw new SyntaxError('Bad combinator.'); }
     filter.push(combinators[op](makeSimple(buff)));
     buff = [];
   }
@@ -726,16 +750,10 @@
   // attr op
   // attr value
   if (cap[4]) {
-    var i;
-    if (cap[6]) {
-      i = cap[6].length;
-      cap[6] = cap[6].replace(/ +i$/, '');
-      i = i > cap[6].length;
-    }
-    return selectors.attr(decodeid(cap[4]), cap[5] || '-', unquote(cap[6]), i);
+    return selectors.attr(decodeid(cap[4]), cap[5] || '-', unquote(cap[6]), 
false);
   }
 
-  throw new Error('Unknown Selector.');
+  throw new SyntaxError('Unknown Selector.');
 };
 
 var makeSimple = function(func) {
diff --git a/node_modules/domino/lib/utils.js b/node_modules/domino/lib/utils.js
index 7c2a749..f16e4e0 100644
--- a/node_modules/domino/lib/utils.js
+++ b/node_modules/domino/lib/utils.js
@@ -1,6 +1,7 @@
 "use strict";
 var DOMException = require('./DOMException');
 var ERR = DOMException;
+var isApiWritable = require("./config").isApiWritable;
 
 exports.NAMESPACE = {
   HTML: 'http://www.w3.org/1999/xhtml',
@@ -48,7 +49,7 @@
 
 exports.expose = function(src, c) {
   for (var n in src) {
-    Object.defineProperty(c.prototype, n, {value: src[n] });
+    Object.defineProperty(c.prototype, n, { value: src[n], writable: 
isApiWritable });
   }
 };
 
diff --git a/node_modules/domino/package.json b/node_modules/domino/package.json
index 58e701e..450c9a5 100644
--- a/node_modules/domino/package.json
+++ b/node_modules/domino/package.json
@@ -2,38 +2,38 @@
   "_args": [
     [
       {
-        "raw": "domino@https://registry.npmjs.org/domino/-/domino-1.0.27.tgz";,
+        "raw": "domino@https://registry.npmjs.org/domino/-/domino-1.0.28.tgz";,
         "scope": null,
         "escapedName": "domino",
         "name": "domino",
-        "rawSpec": "https://registry.npmjs.org/domino/-/domino-1.0.27.tgz";,
-        "spec": "https://registry.npmjs.org/domino/-/domino-1.0.27.tgz";,
+        "rawSpec": "https://registry.npmjs.org/domino/-/domino-1.0.28.tgz";,
+        "spec": "https://registry.npmjs.org/domino/-/domino-1.0.28.tgz";,
         "type": "remote"
       },
       "/Users/arlolra/Work/Wikimedia/services/deploy"
     ]
   ],
-  "_from": "domino@>=1.0.27 <2.0.0",
-  "_id": "[email protected]",
+  "_from": "[email protected]",
+  "_id": "[email protected]",
   "_inCache": true,
   "_location": "/domino",
   "_phantomChildren": {},
   "_requested": {
-    "raw": "domino@https://registry.npmjs.org/domino/-/domino-1.0.27.tgz";,
+    "raw": "domino@https://registry.npmjs.org/domino/-/domino-1.0.28.tgz";,
     "scope": null,
     "escapedName": "domino",
     "name": "domino",
-    "rawSpec": "https://registry.npmjs.org/domino/-/domino-1.0.27.tgz";,
-    "spec": "https://registry.npmjs.org/domino/-/domino-1.0.27.tgz";,
+    "rawSpec": "https://registry.npmjs.org/domino/-/domino-1.0.28.tgz";,
+    "spec": "https://registry.npmjs.org/domino/-/domino-1.0.28.tgz";,
     "type": "remote"
   },
   "_requiredBy": [
     "/"
   ],
-  "_resolved": "https://registry.npmjs.org/domino/-/domino-1.0.27.tgz";,
-  "_shasum": "26bc01f739707505c51456af7f59e3373369475d",
+  "_resolved": "https://registry.npmjs.org/domino/-/domino-1.0.28.tgz";,
+  "_shasum": "9ce3f6a9221a2c3288984b14ea191cd27b392f87",
   "_shrinkwrap": null,
-  "_spec": "domino@https://registry.npmjs.org/domino/-/domino-1.0.27.tgz";,
+  "_spec": "domino@https://registry.npmjs.org/domino/-/domino-1.0.28.tgz";,
   "_where": "/Users/arlolra/Work/Wikimedia/services/deploy",
   "author": {
     "name": "Felix Gnass",
@@ -46,26 +46,26 @@
   "description": "Server-side DOM implementation based on Mozilla's dom.js",
   "devDependencies": {
     "jshint": "^2.9.1",
-    "mocha": "^2.4.5",
-    "should": "^8.3.0"
+    "mocha": "^3.2.0",
+    "should": "^11.1.2"
   },
   "homepage": "https://github.com/fgnass/domino";,
   "main": "./lib",
   "name": "domino",
   "optionalDependencies": {},
-  "readme": "# Server-side DOM implementation based on Mozilla's 
dom.js\n\n[![Build Status][1]][2] [![dependency status][3]][4] [![dev 
dependency status][5]][6]\n\nAs the name might suggest, domino's goal is to 
provide a <b>DOM in No</b>de.\n\nIn contrast to the original 
[dom.js](https://github.com/andreasgal/dom.js) project, domino was not designed 
to run untrusted code. Hence it doesn't have to hide its internals behind a 
proxy facade which makes the code not only simpler, but also [more 
performant](https://github.com/fgnass/dombench).\n\nDomino currently doesn't 
use any harmony features like proxies or WeakMaps and therefore also runs in 
older Node versions.\n\n## Speed over Compliance\n\nDomino is intended for 
_building_ pages rather than scraping them. Hence Domino doesn't execute 
scripts nor does it download external resources.\n\nAlso Domino doesn't 
implement any properties which have been deprecated in HTML5.\n\nDomino sticks 
to the [DOM level 
4](http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#interface-attr) 
working draft, which means that Attributes do not inherit the Node interface. 
Also 
[Element.attributes](http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-element-attributes)
 returns a read-only array instead of a NamedNodeMap.\n\n<b>Note that</b> 
because domino does not use proxies,\n`Element.attributes` is not a true 
JavaScript array; it is an object\nwith a `length` property and an `item(n)` 
accessor method.  See\n[github issue 
#27](https://github.com/fgnass/domino/issues/27) for\nfurther discussion.\n\n## 
CSS Selector Support\n\nDomino provides support for `querySelector()`, 
`querySelectorAll()`, and `matches()` backed by the 
[Zest](https://github.com/chjj/zest) selector engine.\n\n## 
Usage\n\n```javascript\nvar domino = require('domino');\n\nvar window = 
domino.createWindow('<h1>Hello world</h1>');\nvar document = 
window.document;\n\nvar h1 = 
document.querySelector('h1');\nconsole.log(h1.innerHTML);\n```\n\n## 
Tests\n\nDomino includes test from the [W3C DOM Conformance 
Suites](http://www.w3.org/DOM/Test/)\nas well as tests from [HTML Working 
Group](http://www.w3.org/html/wg/wiki/Testing).\n\nThe tests can be run via 
`npm test` or directly though the [Mocha](http://visionmedia.github.com/mocha/) 
command 
line:\n\n![Screenshot](http://fgnass.github.com/images/domino.png)\n\n## 
License and Credits\n\nThe majority of the code was written by [Andreas 
Gal](https://github.com/andreasgal/) and [David 
Flanagan](https://github.com/davidflanagan) as part of the 
[dom.js](https://github.com/andreasgal/dom.js) project. Please refer to the 
included LICENSE file for the original copyright notice and disclaimer.\n\n[1]: 
https://travis-ci.org/fgnass/domino.png\n[2]: 
https://travis-ci.org/fgnass/domino\n[3]: 
https://david-dm.org/fgnass/domino.png\n[4]: 
https://david-dm.org/fgnass/domino\n[5]: 
https://david-dm.org/fgnass/domino/dev-status.png\n[6]: 
https://david-dm.org/fgnass/domino#info=devDependencies\n";,
+  "readme": "# Server-side DOM implementation based on Mozilla's 
dom.js\n\n[![Build Status][1]][2] [![dependency status][3]][4] [![dev 
dependency status][5]][6]\n\nAs the name might suggest, domino's goal is to 
provide a <b>DOM in No</b>de.\n\nIn contrast to the original 
[dom.js](https://github.com/andreasgal/dom.js) project, domino was not designed 
to run untrusted code. Hence it doesn't have to hide its internals behind a 
proxy facade which makes the code not only simpler, but also [more 
performant](https://github.com/fgnass/dombench).\n\nDomino currently doesn't 
use any harmony features like proxies or WeakMaps and therefore also runs in 
older Node versions.\n\n## Speed over Compliance\n\nDomino is intended for 
_building_ pages rather than scraping them. Hence Domino doesn't execute 
scripts nor does it download external resources.\n\nAlso Domino doesn't 
implement any properties which have been deprecated in HTML5.\n\nDomino sticks 
to the [DOM level 
4](http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#interface-attr) 
working draft, which means that Attributes do not inherit the Node interface. 
Also 
[Element.attributes](http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-element-attributes)
 returns a read-only array instead of a NamedNodeMap.\n\n<b>Note that</b> 
because domino does not use proxies,\n`Element.attributes` is not a true 
JavaScript array; it is an object\nwith a `length` property and an `item(n)` 
accessor method.  See\n[github issue 
#27](https://github.com/fgnass/domino/issues/27) for\nfurther discussion.\n\n## 
CSS Selector Support\n\nDomino provides support for `querySelector()`, 
`querySelectorAll()`, and `matches()` backed by the 
[Zest](https://github.com/chjj/zest) selector engine.\n\n## Usage\n\nDomino 
supports the DOM level 4 API, and thus API documentation can be\nfound on 
standard reference sites.  For example, you could start from\nMDN's 
documentation 
for\n[Document](https://developer.mozilla.org/en-US/docs/Web/API/Document) 
and\n[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node).\n\nThe only 
exception is the initial creation of a document:\n```javascript\nvar domino = 
require('domino');\nvar Element = domino.impl.Element; // etc\n\nvar window = 
domino.createWindow('<h1>Hello world</h1>', 'http://example.com');\nvar 
document = window.document;\n\n// alternatively: document = 
domino.createDocument(htmlString, true)\n\nvar h1 = 
document.querySelector('h1');\nconsole.log(h1.innerHTML);\nconsole.log(h1 
instanceof Element);\n```\n\nIf you want a more standards-compliant way to 
create a `Document`, you can\nalso use 
[DOMImplementation](https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation):\n```javascript\nvar
 domino = require('domino');\nvar domimpl = 
domino.createDOMImplementation();\nvar doc = 
domimpl.createHTMLDocument();\n```\n\nBy default many domino methods will be 
stored in writable properties, to\nallow polyfills (as browsers do).  You can 
lock down the implementation\nif desired as 
follows:\n```javascript\nglobal.__domino_frozen__ = true; // Must precede any 
`require('domino')`\nvar domino = require('domino');\n```\n\n## Tests\n\nDomino 
includes test from the [W3C DOM Conformance 
Suites](http://www.w3.org/DOM/Test/)\nas well as tests from [HTML Working 
Group](http://www.w3.org/html/wg/wiki/Testing).\n\nThe tests can be run via 
`npm test` or directly though the [Mocha](http://visionmedia.github.com/mocha/) 
command 
line:\n\n![Screenshot](http://fgnass.github.com/images/domino.png)\n\n## 
License and Credits\n\nThe majority of the code was written by [Andreas 
Gal](https://github.com/andreasgal/) and [David 
Flanagan](https://github.com/davidflanagan) as part of the 
[dom.js](https://github.com/andreasgal/dom.js) project. Please refer to the 
included LICENSE file for the original copyright notice and disclaimer.\n\n[1]: 
https://travis-ci.org/fgnass/domino.png\n[2]: 
https://travis-ci.org/fgnass/domino\n[3]: 
https://david-dm.org/fgnass/domino.png\n[4]: 
https://david-dm.org/fgnass/domino\n[5]: 
https://david-dm.org/fgnass/domino/dev-status.png\n[6]: 
https://david-dm.org/fgnass/domino#info=devDependencies\n";,
   "readmeFilename": "README.md",
   "repository": {
     "type": "git",
     "url": "git+https://github.com/fgnass/domino.git";
   },
   "scripts": {
-    "lint": "jshint lib",
+    "lint": "jshint lib test/*.js",
     "mocha": "mocha",
     "mocha-spec": "mocha -R spec",
     "regen-html5lib-tests": "node test/tools/update-html5lib-tests.js 
test/html5lib-tests.json",
     "test": "npm run lint && npm run mocha",
     "test-spec": "npm run lint && npm run mocha-spec"
   },
-  "version": "1.0.27"
+  "version": "1.0.28"
 }
diff --git a/node_modules/domino/test/domino.js 
b/node_modules/domino/test/domino.js
index c7236ff..b693c91 100644
--- a/node_modules/domino/test/domino.js
+++ b/node_modules/domino/test/domino.js
@@ -1,3 +1,4 @@
+'use strict';
 var domino = require('../lib');
 var fs = require('fs');
 var html = fs.readFileSync(__dirname + '/fixture/doc.html', 'utf8');
@@ -27,7 +28,7 @@
   nodeList.should.have.length(2);
   nodeList = d.querySelectorAll('tt:not(.bar)');
   nodeList.should.have.length(1);
-}
+};
 
 exports.qsaOrder = function() {
   var window = 
domino.createDocument('<h2></h2><h3></h3><h3></h3><h2></h2><h3></h3>');
@@ -35,7 +36,7 @@
     return el.tagName;
   })
   .should.eql(['H2', 'H3', 'H3', 'H2', 'H3']);
-}
+};
 
 exports.orphanQSA = function() {
   var document = domino.createDocument('<h1>foo</h1>');
@@ -92,6 +93,7 @@
 
 exports.evilHandler = function() {
   var window = domino.createDocument('<a id="a" 
onclick="alert(\'breakin&#39;-stuff\')">');
+  window = window; // avoid defined-but-not-used lint error
 };
 
 exports.title = function() {
@@ -133,7 +135,7 @@
   d.body.appendChild(d.createElement('p'));
   a = Array.prototype.slice.call(c);
   a.should.have.length(5);
-}
+};
 
 
 exports.attributes1 = function() {
@@ -149,7 +151,7 @@
   el.setAttribute('baz', 'baz');
   el.attributes.should.have.length(2);
   el.attributes.item(1).value.should.equal('baz');
-}
+};
 
 exports.classList = function() {
   var d = domino.createDocument();
@@ -169,7 +171,7 @@
   cl.should.have.length(3);
   el.className.should.not.match(/foo/);
   cl[0].should.not.equal('foo');
-}
+};
 
 exports.attributes2 = function() {
   var d = domino.createDocument();
@@ -179,7 +181,7 @@
   div.attributes.onclick.should.have.property('value', 't');
   div.removeAttribute('onclick');
   (div.attributes.onclick === undefined).should.be.true();
-}
+};
 
 exports.jquery1_9 = function() {
   var window = domino.createWindow(html);
@@ -195,7 +197,7 @@
   window.$.should.be.ok();
   window.$('.foo').should.have.length(3);
   window.$.ajaxTransport("test", function() {
-       return { send: function() {}, abort: function() {} };
+    return { send: function() {}, abort: function() {} };
   });
   window.$.ajax({ url: 'test://', dataType: "test", timeout: 1, async: true });
 };
@@ -225,7 +227,7 @@
     root.lastChild.firstChild,
     root.lastChild.lastChild.firstChild
   ]);
-}
+};
 
 exports.nodeIterator = function() {
   var window = domino.createWindow(html);
@@ -240,9 +242,9 @@
   ni.whatToShow.should.equal(0x4);
   ni.filter.constructor.should.equal(window.NodeFilter.constructor);
 
-  var actual = [], n;
+  var actual = [];
   for (var n = ni.nextNode(); n ; n = ni.nextNode()) {
-       actual.push(n);
+    actual.push(n);
   }
 
   actual.length.should.equal(4);
@@ -252,12 +254,12 @@
     root.lastChild.firstChild,
     root.lastChild.lastChild.firstChild
   ]);
-}
+};
 
 exports.innerHTML = function() {
   var d = domino.createDocument();
   ['pre','textarea','listing'].forEach(function(elementName) {
-    var div = d.createElement('div')
+    var div = d.createElement('div');
     var el = d.createElement(elementName);
     el.innerHTML = "a";
     div.appendChild(el);
@@ -268,7 +270,7 @@
     // see https://github.com/whatwg/html/issues/944
     div.innerHTML.should.equal('<'+elementName+'>\nb</'+elementName+'>');
   });
-}
+};
 
 exports.outerHTML = function() {
   var tests = [
@@ -283,7 +285,7 @@
     // Verify round-tripping.
     d.body.outerHTML.should.equal(html);
   });
-}
+};
 
 exports.largeAttribute = function() {
   var size = 400000;
@@ -356,7 +358,7 @@
   var html = '<div\rid=a data-test=1\rfoo="\r"\rbar=\'\r\'\rbat=\r>\r</div\r>';
   var doc = domino.createDocument(html);
   var div = doc.querySelector('#a');
-  (div != null).should.be.true();
+  (div != null).should.be.true(); // jshint ignore:line
   // all \r should be converted to \n
   div.outerHTML.should.equal('<div id="a" data-test="1" foo="\n" bar="\n" 
bat="">\n</div>');
 };
@@ -365,7 +367,7 @@
   var html = "<div id=a ==x><a=B></A=b></div>";
   var doc = domino.createDocument(html);
   var div = doc.querySelector('#a');
-  (div != null).should.be.true();
+  (div != null).should.be.true(); // jshint ignore:line
   div.attributes.length.should.equal(2);
   div.attributes.item(1).name.should.equal('=');
   div.children.length.should.equal(1);
@@ -386,7 +388,7 @@
   var html = "<div id=a b=\"x &quot;y\" c='a \rb'><\np></div>";
   var doc = domino.createDocument(html);
   var div = doc.querySelector('#a');
-  (div != null).should.be.true();
+  (div != null).should.be.true(); // jshint ignore:line
   div.attributes.length.should.equal(3);
   div.attributes.item(1).value.should.equal('x "y');
   div.attributes.item(2).value.should.equal('a \nb');
@@ -397,7 +399,7 @@
   var html = "<a 
href='http://user:[email protected]:1234/foo/bar?bat#baz'>!</a>";
   var doc = domino.createDocument(html);
   var a = doc.querySelector('a');
-  (a != null).should.be.true();
+  (a != null).should.be.true(); // jshint ignore:line
   a.href.should.equal('http://user:[email protected]:1234/foo/bar?bat#baz');
   a.protocol.should.equal('http:');
   a.host.should.equal('example.com:1234');
@@ -533,6 +535,7 @@
   var f = doc.createElement('f');
   df.replaceChild(f, e);
   df.serialize().should.equal('<d></d><f></f>');
+  d = d; // avoid defined-but-not-used warning
 
   // Replace rooted node with document fragment
   root.appendChild(a);
@@ -638,7 +641,7 @@
 // HTMLTemplateElement.innerHTML
 // see https://github.com/w3c/DOM-Parsing/issues/1
 exports.template3 = function() {
-  var document = domino.createDocument()
+  var document = domino.createDocument();
   var t = document.createElement("template");
   t.should.be.an.instanceof(domino.impl.HTMLTemplateElement);
   t.childNodes.length.should.equal(0);
@@ -751,25 +754,25 @@
 };
 
 exports.sourceTag = function() {
-  var document = domino.createDocument('<video controls><source src="foo.webm" 
type="video/webm">Sorry, no HTML5 video.');
+  var document = domino.createDocument('<base href=http://example.com><video 
controls><source src="foo.webm" type="video/webm">Sorry, no HTML5 video.');
   document.body.innerHTML.should.equal('<video controls=""><source 
src="foo.webm" type="video/webm">Sorry, no HTML5 video.</video>');
   var source = document.querySelector('source');
   source.should.be.instanceof(domino.impl.HTMLElement);
   source.should.be.instanceof(domino.impl.HTMLSourceElement);
   source.should.not.be.instanceof(domino.impl.HTMLUnknownElement);
-  source.src.should.equal("foo.webm");
+  source.src.should.equal("http://example.com/foo.webm";);
   source.type.should.equal("video/webm");
 };
 
 exports.trackTag = function() {
-  var document = domino.createDocument('<video poster="foo.jpg"><source 
src="foo.webm" type="video/webm"><track kind="captions" src="en-captions.vtt" 
srclang="en">');
+  var document = domino.createDocument('<base href=http://example.com><video 
poster="foo.jpg"><source src="foo.webm" type="video/webm"><track 
kind="captions" src="en-captions.vtt" srclang="en">');
   document.body.innerHTML.should.equal('<video poster="foo.jpg"><source 
src="foo.webm" type="video/webm"><track kind="captions" src="en-captions.vtt" 
srclang="en"></video>');
   var track = document.querySelector('track');
   track.should.be.instanceof(domino.impl.HTMLElement);
   track.should.be.instanceof(domino.impl.HTMLTrackElement);
   track.should.not.be.instanceof(domino.impl.HTMLUnknownElement);
   track.kind.should.equal("captions");
-  track.src.should.equal("en-captions.vtt");
+  track.src.should.equal("http://example.com/en-captions.vtt";);
   track.srclang.should.equal("en");
 };
 
@@ -823,7 +826,7 @@
   var document = domino.createDocument(doc);
   document.body.innerHTML.should.equal('<menuitem id="a" label=" b 
"></menuitem><menuitem id="c"> d <b> e </b></menuitem>');
   var itema = document.getElementById('a');
-  (itema != null).should.be.true();
+  (itema != null).should.be.true(); // jshint ignore:line
   itema.should.be.an.instanceof(domino.impl.HTMLMenuItemElement);
   itema.label.should.equal(' b ');
   itema.label = ' x ';
@@ -833,7 +836,7 @@
   itema.outerHTML.should.equal('<menuitem id="a" label=" x "></menuitem>');
 
   var itemb = document.getElementById('c');
-  (itemb != null).should.be.true();
+  (itemb != null).should.be.true(); // jshint ignore:line
   itemb.should.be.an.instanceof(domino.impl.HTMLMenuItemElement);
   itemb.label.should.equal('d e');
   itemb.label = ' y ';
@@ -851,4 +854,66 @@
 
   svg.should.be.instanceOf(domino.impl.SVGSVGElement);
   document.body.innerHTML.should.equal("<svg></svg>");
-}
+};
+
+exports.gh95 = function() {
+    var document = domino.createDocument(
+        '<body><a href="foo\'s">bar</a></body>'
+    );
+    (function() {
+        document.querySelectorAll("a[href=foo's]");
+    }).should.throw({ name: 'SyntaxError' });
+    document.querySelectorAll("a[href=foo\\'s]").length.should.equal(1);
+};
+
+exports.propertyWritability = function () { // gh #89
+  var window = domino.createWindow('');
+  var document = domino.createDocument();
+
+  var assertWritable = function(object, property) {
+    var replacement = function () { };
+    object[property] = replacement;
+    object[property].should.equal(replacement, property + " should be 
writable");
+  };
+
+  assertWritable(window, 'HTMLElement');
+  assertWritable(document, 'importNode');
+  assertWritable(document, 'createElement');
+  assertWritable(document, 'createElementNS');
+};
+
+exports.gh90 = function() {
+  var doc = '<input type="checkbox">';
+  var document = domino.createDocument(doc);
+  document.body.innerHTML.should.equal(doc);
+
+  var input = document.querySelector('input');
+  input.checked.should.equal(false);
+
+  input.checked = true;
+  input.checked.should.equal(true);
+  input.outerHTML.should.equal('<input type="checkbox" checked="">');
+
+  input.checked = false;
+  input.checked.should.equal(false);
+  input.outerHTML.should.equal(doc);
+
+  // Now test again, using hasAttribute/hasAttributeNS directly.
+  input.hasAttribute('checked').should.equal(false);
+  input.hasAttributeNS(null, 'checked').should.equal(false);
+  input.hasAttributeNS('foo', 'checked').should.equal(false);
+
+  input.setAttribute('checked', 'bar');
+  input.setAttributeNS('foo', 'checked', 'bat');
+
+  input.hasAttribute('checked').should.equal(true);
+  input.hasAttributeNS(null, 'checked').should.equal(true);
+  input.hasAttributeNS('foo', 'checked').should.equal(true);
+
+  input.removeAttribute('checked');
+  input.removeAttributeNS('foo', 'checked');
+
+  input.hasAttribute('checked').should.equal(false);
+  input.hasAttributeNS(null, 'checked').should.equal(false);
+  input.hasAttributeNS('foo', 'checked').should.equal(false);
+};
diff --git a/node_modules/domino/test/mocha.opts 
b/node_modules/domino/test/mocha.opts
index 34d4aa8..e5171c0 100644
--- a/node_modules/domino/test/mocha.opts
+++ b/node_modules/domino/test/mocha.opts
@@ -2,5 +2,6 @@
 --reporter dot
 --require should
 --slow 20
+--timeout 15000
 --growl
 --check-leaks
diff --git a/node_modules/domino/test/parsing.js 
b/node_modules/domino/test/parsing.js
index b8bf11b..4799243 100644
--- a/node_modules/domino/test/parsing.js
+++ b/node_modules/domino/test/parsing.js
@@ -1,3 +1,4 @@
+'use strict';
 var domino = require('../');
 var html5lib_tests = require('./html5lib-tests.json');
 
@@ -33,11 +34,11 @@
     r[trimmed] = makeOneTest(fragment, input, expected);
     return r;
   }, {});
-};
+}
 
 function makeOneTest(fragment, input, expected) {
   return function() {
-    var doc;
+    var doc, context;
     if (fragment) {
       doc = domino.createDocument();
       context = (fragment==='body') ? doc.body : doc.createElement(fragment);
@@ -48,7 +49,7 @@
       doc.outerHTML.should.equal(expected);
     }
   };
-};
+}
 
 exports.parseAlgorithm = Object.keys(html5lib_tests).reduce(function(r, file) {
   r[file] = cases(file, html5lib_tests[file]);
diff --git a/node_modules/domino/test/w3c/harness/index.js 
b/node_modules/domino/test/w3c/harness/index.js
index 4c7990c..2eef3e4 100644
--- a/node_modules/domino/test/w3c/harness/index.js
+++ b/node_modules/domino/test/w3c/harness/index.js
@@ -68,7 +68,9 @@
         load: function(docRef, name, href) {
           var doc = Path.resolve(__dirname, '..', path, 'files', href) + 
'.html';
           var html = fs.readFileSync(doc, 'utf8');
-          return domino.createDocument(html);
+          var url = 'http://example.com/'+Path.join(path,'files',href)+'.html';
+          var win = domino.createWindow(html, url);
+          return win.document;
         }
       };
     };
diff --git 
a/node_modules/domino/test/w3c/level1/core/documentinvalidcharacterexceptioncreatepi.js
 
b/node_modules/domino/test/w3c/level1/core/documentinvalidcharacterexceptioncreatepi.js
index 3216c53..82b31a8 100644
--- 
a/node_modules/domino/test/w3c/level1/core/documentinvalidcharacterexceptioncreatepi.js
+++ 
b/node_modules/domino/test/w3c/level1/core/documentinvalidcharacterexceptioncreatepi.js
@@ -101,7 +101,7 @@
       
        if(
        
-       (builder.contentType == "text/html")
+       (builder.contentType == "text/html" && false /*CSA: allowed in DOM 4*/)
 
        ) {
        
diff --git 
a/node_modules/domino/test/w3c/level1/core/documentinvalidcharacterexceptioncreatepi1.js
 
b/node_modules/domino/test/w3c/level1/core/documentinvalidcharacterexceptioncreatepi1.js
index fb6104d..01b169d 100644
--- 
a/node_modules/domino/test/w3c/level1/core/documentinvalidcharacterexceptioncreatepi1.js
+++ 
b/node_modules/domino/test/w3c/level1/core/documentinvalidcharacterexceptioncreatepi1.js
@@ -98,7 +98,7 @@
       
        if(
        
-       (builder.contentType == "text/html")
+       (builder.contentType == "text/html" && false /*CSA: allowed in DOM 4*/)
 
        ) {
        
diff --git 
a/node_modules/domino/test/w3c/level1/core/hc_nodeappendchildnewchilddiffdocument.js
 
b/node_modules/domino/test/w3c/level1/core/hc_nodeappendchildnewchilddiffdocument.js
index 1be650b..346163f 100644
--- 
a/node_modules/domino/test/w3c/level1/core/hc_nodeappendchildnewchilddiffdocument.js
+++ 
b/node_modules/domino/test/w3c/level1/core/hc_nodeappendchildnewchilddiffdocument.js
@@ -140,5 +140,6 @@
 
 
 function runTest() {
+   return; // CSA: disabled; latest DOM spec adopts rather than throws.
    hc_nodeappendchildnewchilddiffdocument();
 }
diff --git 
a/node_modules/domino/test/w3c/level1/core/hc_nodeinsertbeforenewchilddiffdocument.js
 
b/node_modules/domino/test/w3c/level1/core/hc_nodeinsertbeforenewchilddiffdocument.js
index 915104f..3d8f2d3 100644
--- 
a/node_modules/domino/test/w3c/level1/core/hc_nodeinsertbeforenewchilddiffdocument.js
+++ 
b/node_modules/domino/test/w3c/level1/core/hc_nodeinsertbeforenewchilddiffdocument.js
@@ -143,5 +143,6 @@
 
 
 function runTest() {
+   return; // CSA: disabled; latest DOM spec adopts rather than throws.
    hc_nodeinsertbeforenewchilddiffdocument();
 }
diff --git 
a/node_modules/domino/test/w3c/level1/core/hc_nodereplacechildnewchilddiffdocument.js
 
b/node_modules/domino/test/w3c/level1/core/hc_nodereplacechildnewchilddiffdocument.js
index 23af319..43decd6 100644
--- 
a/node_modules/domino/test/w3c/level1/core/hc_nodereplacechildnewchilddiffdocument.js
+++ 
b/node_modules/domino/test/w3c/level1/core/hc_nodereplacechildnewchilddiffdocument.js
@@ -143,5 +143,6 @@
 
 
 function runTest() {
+   return; // CSA: disabled; latest DOM spec adopts rather than throws.
    hc_nodereplacechildnewchilddiffdocument();
 }
diff --git a/node_modules/domino/test/w3c/level1/html/object06.js 
b/node_modules/domino/test/w3c/level1/html/object06.js
index 947ee95..af13ec9 100644
--- a/node_modules/domino/test/w3c/level1/html/object06.js
+++ b/node_modules/domino/test/w3c/level1/html/object06.js
@@ -99,6 +99,7 @@
       assertSize("Asize",2,nodeList);
 testNode = nodeList.item(0);
       vdata = testNode.data;
+      vdata = testNode.getAttribute('data'); //CSA hack
 
       assertEquals("dataLink","./pix/logo.gif",vdata);
        
diff --git a/node_modules/domino/test/web-platform-tests.js 
b/node_modules/domino/test/web-platform-tests.js
index ba358e1..c89d101 100644
--- a/node_modules/domino/test/web-platform-tests.js
+++ b/node_modules/domino/test/web-platform-tests.js
@@ -1,13 +1,17 @@
+/* globals add_completion_callback */
+'use strict';
 var fs = require('fs');
-var assert = require('assert');
 var Path = require('path');
 var domino = require('../lib');
 
 // These are the tests we currently fail.
 // Some of these failures are bugs we ought to fix.
 var blacklist = [
+  // web-platform-tests/html/dom
   'interfaces',
+  'reflection-obsolete',
   'documents dom-tree-accessors Document.body',
+  'documents dom-tree-accessors Document.currentScript',
   'documents dom-tree-accessors Document.currentScript.sub',
   'documents dom-tree-accessors document.embeds-document.plugins-01',
   'documents dom-tree-accessors document.forms',
@@ -68,6 +72,93 @@
   'elements global-attributes the-translate-attribute-010',
   'elements global-attributes the-translate-attribute-011',
   'elements global-attributes the-translate-attribute-012',
+
+  // web-platform-tests/dom/nodes
+  'CharacterData-appendData',
+  'CharacterData-data',
+  'CharacterData-deleteData',
+  'CharacterData-insertData',
+  'CharacterData-replaceData',
+  'CharacterData-substringData',
+  'Comment-constructor',
+  'DOMImplementation-createDocument',
+  'DOMImplementation-createDocumentType',
+  'DOMImplementation-createHTMLDocument',
+  'DOMImplementation-hasFeature',
+  'Document-URL.sub',
+  'Document-characterSet-normalization',
+  'Document-constructor',
+  /Document-contentType/,
+  'Document-createAttribute',
+  'Document-createComment',
+  'Document-createElement-namespace',
+  'Document-createElement',
+  'Document-createElementNS',
+  'Document-createEvent',
+  'Document-createTreeWalker',
+  'Document-getElementById',
+  'Document-getElementsByTagName',
+  'Document-getElementsByTagName-xhtml',
+  'Document-getElementsByTagNameNS',
+  'Element-childElement-null-xhtml',
+  'Element-childElementCount-dynamic-add-xhtml',
+  'Element-childElementCount-dynamic-remove-xhtml',
+  'Element-childElementCount-nochild-xhtml',
+  'Element-childElementCount-xhtml',
+  'Element-children',
+  'Element-classlist',
+  'Element-closest',
+  'Element-firstElementChild-entity-xhtml',
+  'Element-firstElementChild-xhtml',
+  'Element-firstElementChild-namespace-xhtml',
+  'Element-getElementsByClassName',
+  'Element-getElementsByTagName-change-document-HTMLNess',
+  'Element-getElementsByTagName',
+  'Element-getElementsByTagNameNS',
+  'Element-hasAttributes',
+  'Element-insertAdjacentElement',
+  'Element-insertAdjacentText',
+  'Element-lastElementChild-xhtml',
+  'Element-matches',
+  'Element-nextElementSibling-xhtml',
+  'Element-previousElementSibling-xhtml',
+  'Element-siblingElement-null-xhtml',
+  'Element-tagName',
+  'MutationObserver-attributes',
+  'MutationObserver-characterData',
+  'MutationObserver-childList',
+  'MutationObserver-disconnect',
+  'MutationObserver-document',
+  'MutationObserver-inner-outer',
+  'MutationObserver-takeRecords',
+  'Node-baseURI',
+  'Node-childNodes',
+  'Node-cloneNode',
+  'Node-compareDocumentPosition',
+  'Node-constants',
+  'Node-isConnected',
+  'Node-isEqualNode',
+  'Node-isEqualNode-xhtml',
+  'Node-lookupPrefix',
+  'Node-lookupNamespaceURI',
+  'Node-nodeName-xhtml',
+  'Node-nodeValue',
+  'Node-properties',
+  'Node-textContent',
+  'NodeList-Iterable',
+  'ParentNode-append',
+  'ParentNode-prepend',
+  'ParentNode-querySelector-All-content',
+  'ParentNode-querySelector-All',
+  /^ProcessingInstruction-/,
+  'Text-constructor',
+  'append-on-Document',
+  'attributes',
+  'case',
+  'insert-adjacent',
+  'prepend-on-Document',
+  'remove-unscopable',
+  'rootNode',
 ].map(function(s) {
   // Convert strings to equivalent regular expression matchers.
   if (typeof s === 'string') {
@@ -108,59 +199,83 @@
   return result;
 }
 
-var harness = function(path) {
-  return list(path, '', function(name, file) {
-    var html = read(file);
-    var window = domino.createWindow(html);
-    window._run(testharness);
-    var scripts = window.document.getElementsByTagName('script');
-    scripts = [].slice.call(scripts);
-
-    return function() {
-      var listen = onBlacklist(name) ? function listenForSuccess() {
-        add_completion_callback(function(tests, status) {
-          var failed = tests.filter(function(t) {
-            return t.status === t.FAIL || t.status === t.TIMEOUT;
-          });
-          if (failed.length===0) {
-            throw new Error("Expected blacklisted test to fail");
-          }
-        });
-      } : function listenForFailures() {
-        add_completion_callback(function(tests, status) {
-          var failed = tests.filter(function(t) {
-            return t.status === t.FAIL || t.status === t.TIMEOUT;
-          });
-          if (failed.length) {
-            throw new Error(failed[0].name+": "+failed[0].message);
-          }
-        });
-      };
-      window._run("(" + listen.toString() + ")();");
-
-      var concatenatedScripts = scripts.map(function(script) {
-        if (/^text\/plain$/.test(script.getAttribute('type')||'')) {
-          return '';
-        }
-        return script.textContent + '\n';
-      }).join("\n");
-      // Workaround for https://github.com/w3c/web-platform-tests/pull/3984
-      concatenatedScripts = 'var x;\n' + concatenatedScripts;
-      concatenatedScripts += '\nwindow.dispatchEvent(new Event("load"));';
-
-      var go = function() {
-        window._run(concatenatedScripts);
-      };
-      try {
-        go();
-      } catch (e) {
-        if ((!onBlacklist(name)) ||
-            /^Expected blacklisted test to fail/.test(e.message||'')) {
-          throw e;
-        }
+var harness = function() {
+  var paths = [].slice.call(arguments);
+  return paths.map(function (path) {
+    return list(path, '', function(name, file) {
+      if (/\/html\/dom\/reflection-original.html$/.test(file)) {
+        // This is a compilation file & not a test suite.
+        return; // skip
       }
-    };
+      var html = read(file);
+      var window = domino.createWindow(html, 'http://example.com/');
+      window._run(testharness);
+      var scripts = window.document.getElementsByTagName('script');
+      scripts = [].slice.call(scripts);
+
+      return function() {
+        var listen = onBlacklist(name) ? function listenForSuccess() {
+          add_completion_callback(function(tests, status) {
+            var failed = tests.filter(function(t) {
+              return t.status === t.FAIL || t.status === t.TIMEOUT;
+            });
+            if (failed.length===0) {
+              throw new Error("Expected blacklisted test to fail");
+            }
+          });
+        } : function listenForFailures() {
+          add_completion_callback(function(tests, status) {
+            var failed = tests.filter(function(t) {
+              return t.status === t.FAIL || t.status === t.TIMEOUT;
+            });
+            if (failed.length) {
+              throw new Error(failed[0].name+": "+failed[0].message);
+            }
+          });
+        };
+        window._run("(" + listen.toString() + ")();");
+
+        var concatenatedScripts = scripts.map(function(script) {
+          if (/^text\/plain$/.test(script.getAttribute('type')||'')) {
+            return '';
+          }
+          if (/^(\w+|..)/.test(script.getAttribute('src')||'')) {
+            var f = Path.resolve(path, script.getAttribute('src'));
+            if (fs.existsSync(f)) { return read(f); }
+          }
+          return script.textContent + '\n';
+        }).join("\n");
+        concatenatedScripts =
+          concatenatedScripts.replace(/\.attributes\[(\w+)\]/g,
+                                      '.attributes.item($1)');
+        // Workaround for https://github.com/w3c/web-platform-tests/pull/3984
+        concatenatedScripts =
+          'var x, doc, ReflectionTests;\n' +
+          // Hack in globals on window object
+          '"String|Boolean|Number".split("|").forEach(function(x){' +
+            'window[x] = global[x];})\n' +
+          // Hack in frames on window object
+          
'Array.prototype.forEach.call(document.getElementsByTagName("iframe"),' +
+            'function(f,i){window[i]=f.contentWindow;});\n' +
+          'window.setup = function(f) { f(); };\n' +
+          concatenatedScripts +
+          '\nwindow.dispatchEvent(new Event("load"));';
+
+        var go = function() {
+          window._run(concatenatedScripts);
+        };
+        try {
+          go();
+        } catch (e) {
+          if ((!onBlacklist(name)) ||
+              /^Expected blacklisted test to fail/.test(e.message||'')) {
+            throw e;
+          }
+        }
+      };
+    });
   });
 };
 
-module.exports = harness(__dirname + '/web-platform-tests/html/dom');
+module.exports = harness(__dirname + '/web-platform-tests/html/dom',
+                         __dirname + '/web-platform-tests/dom/nodes');
diff --git a/src b/src
index d000fdb..ba7b304 160000
--- a/src
+++ b/src
@@ -1 +1 @@
-Subproject commit d000fdb45cb4216d2154b610cad437da3ff62e78
+Subproject commit ba7b3046b62bdb1efe8d6311e55478a88fb26128

-- 
To view, visit https://gerrit.wikimedia.org/r/334382
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I670a16a0e4e96450521422d4afe1a511f3a9d315
Gerrit-PatchSet: 2
Gerrit-Project: mediawiki/services/parsoid/deploy
Gerrit-Branch: master
Gerrit-Owner: Arlolra <[email protected]>
Gerrit-Reviewer: Arlolra <[email protected]>
Gerrit-Reviewer: C. Scott Ananian <[email protected]>
Gerrit-Reviewer: Subramanya Sastry <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to