"use strict" module.exports = function() { function appendChild(child) { var ancestor = this while (ancestor !== child && ancestor !== null) ancestor = ancestor.parentNode if (ancestor === child) throw new Error("Node cannot be inserted at the specified point in the hierarchy") if (child.nodeType == null) throw new Error("Argument is not a DOM element") var index = this.childNodes.indexOf(child) if (index > -1) this.childNodes.splice(index, 1) if (child.nodeType === 11) { while (child.firstChild != null) this.appendChild(child.firstChild) child.childNodes = [] } else { this.childNodes.push(child) if (child.parentNode != null && child.parentNode !== this) child.parentNode.removeChild(child) child.parentNode = this } } function removeChild(child) { var index = this.childNodes.indexOf(child) if (index > -1) { this.childNodes.splice(index, 1) child.parentNode = null } else throw new TypeError("Failed to execute 'removeChild'") } function insertBefore(child, reference) { var ancestor = this while (ancestor !== child && ancestor !== null) ancestor = ancestor.parentNode if (ancestor === child) throw new Error("Node cannot be inserted at the specified point in the hierarchy") if (child.nodeType == null) throw new Error("Argument is not a DOM element") var refIndex = this.childNodes.indexOf(reference) var index = this.childNodes.indexOf(child) if (reference !== null && refIndex < 0) throw new TypeError("Invalid argument") if (index > -1) this.childNodes.splice(index, 1) if (reference === null) this.appendChild(child) else { if (child.nodeType === 11) { this.childNodes.splice.apply(this.childNodes, [refIndex, 0].concat(child.childNodes)) while (child.firstChild) { var subchild = child.firstChild child.removeChild(subchild) subchild.parentNode = this } child.childNodes = [] } else { this.childNodes.splice(refIndex, 0, child) if (child.parentNode != null && child.parentNode !== this) child.parentNode.removeChild(child) child.parentNode = this } } } function setAttribute(name, value) { var nodeValue = String(value) this.attributes[name] = { namespaceURI: null, get nodeValue() {return nodeValue}, set nodeValue(value) {nodeValue = String(value)}, } } function setAttributeNS(ns, name, value) { this.setAttribute(name, value) this.attributes[name].namespaceURI = ns } function removeAttribute(name) { delete this.attributes[name] } var activeElement var $window = { document: { createElement: function(tag, is) { var style = {} var element = { nodeType: 1, nodeName: tag.toUpperCase(), namespaceURI: "http://www.w3.org/1999/xhtml", appendChild: appendChild, removeChild: removeChild, insertBefore: insertBefore, setAttribute: setAttribute, setAttributeNS: setAttributeNS, removeAttribute: removeAttribute, parentNode: null, childNodes: [], attributes: {}, get firstChild() { return this.childNodes[0] || null }, get nextSibling() { if (this.parentNode == null) return null var index = this.parentNode.childNodes.indexOf(this) if (index < 0) throw new TypeError("Parent's childNodes is out of sync") return this.parentNode.childNodes[index + 1] || null }, set textContent(value) { this.childNodes = [] if (value !== "") this.appendChild($window.document.createTextNode(value)) }, set innerHTML(value) { while (this.firstChild) this.removeChild(this.firstChild) var stack = [this], depth = 0, voidElements = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"] value.replace(/<([a-z0-9\-]+?)((?:\s+?[^=]+?=(?:"[^"]*?"|'[^']*?'|[^\s>]*))*?)(\s*\/)?>|<\/([a-z0-9\-]+?)>|([^<]+)/g, function(match, startTag, attrs, selfClosed, endTag, text) { if (startTag) { var element = $window.document.createElement(startTag) attrs.replace(/\s+?([^=]+?)=(?:"([^"]*?)"|'([^']*?)'|([^\s>]*))/g, function(match, key, doubleQuoted, singleQuoted, unquoted) { var keyParts = key.split(":") var name = keyParts.pop() var ns = keyParts[0] var value = doubleQuoted || singleQuoted || unquoted || "" if (ns != null) element.setAttributeNS(ns, name, value) else element.setAttribute(name, value) }) stack[depth].appendChild(element) if (!selfClosed && voidElements.indexOf(startTag.toLowerCase()) < 0) stack[++depth] = element } else if (endTag) { depth-- } else if (text) { stack[depth].appendChild($window.document.createTextNode(text)) // FIXME handle html entities } }) }, get style() { return style }, set style(value) { if (typeof value === "string") { for (var key in style) style[key] = "" var rules = value.split(";") for (var i = 0; i < rules.length; i++) { var rule = rules[i] var colonIndex = rule.indexOf(":") if (colonIndex > -1) { var key = rule.slice(0, colonIndex).trim().replace(/-\D/g, function(match) {return match[1].toUpperCase()}) var value = rule.slice(colonIndex + 1).trim() style[key] = value } } } }, focus: function() {activeElement = this}, dispatchEvent: function(e) { e.target = this if (typeof this["on" + e.type] === "function") this["on" + e.type](e) }, } if (element.nodeName === "A") { var href Object.defineProperty(element, "href", { get: function() {return this.attributes["href"] === undefined ? "" : "[FIXME implement]"}, set: function(value) {this.setAttribute("href", value)}, enumerable: true, }) } if (element.nodeName === "INPUT") { var checked Object.defineProperty(element, "checked", { get: function() {return checked === undefined ? this.attributes["checked"] !== undefined : checked}, set: function(value) {checked = Boolean(value)}, enumerable: true, }) } return element }, createElementNS: function(ns, tag, is) { var element = this.createElement(tag, is) element.nodeName = tag element.namespaceURI = ns return element }, createTextNode: function(text) { var nodeValue = String(text) return { nodeType: 3, nodeName: "#text", parentNode: null, get nodeValue() {return nodeValue}, set nodeValue(value) {nodeValue = String(value)}, } }, createDocumentFragment: function() { return { nodeType: 11, nodeName: "#document-fragment", appendChild: appendChild, insertBefore: insertBefore, removeChild: removeChild, parentNode: null, childNodes: [], get firstChild() { return this.childNodes[0] || null }, } }, createEvent: function() { return { initEvent: function(type) {this.type = type}, } }, get activeElement() {return activeElement}, }, } $window.document.documentElement = $window.document.createElement("html") $window.document.documentElement.appendChild($window.document.createElement("head")) $window.document.body = $window.document.createElement("body") $window.document.documentElement.appendChild($window.document.body) activeElement = $window.document.body return $window }