Last active
June 13, 2019 04:18
-
-
Save vectorsize/8053096 to your computer and use it in GitHub Desktop.
D3 in a virtual DOM proof of concept.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.domino=e():"undefined"!=typeof global?global.domino=e():"undefined"!=typeof self&&(self.domino=e())}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | |
var parserlib = require('./cssparser'); | |
module.exports = CSSStyleDeclaration; | |
function CSSStyleDeclaration(elt) { | |
this._element = elt; | |
} | |
// Utility function for parsing style declarations | |
// Pass in a string like "margin-left: 5px; border-style: solid" | |
// and this function returns an object like | |
// {"margin-left":"5px", "border-style":"solid"} | |
function parseStyles(s) { | |
var parser = new parserlib.css.Parser(); | |
var result = {}; | |
parser.addListener("property", function(e) { | |
if (e.invalid) return; // Skip errors | |
result[e.property.text] = e.value.text; | |
if (e.important) result.important[name] = e.important; | |
}); | |
s = (''+s).replace(/^;/, ''); | |
parser.parseStyleAttribute(s); | |
return result; | |
} | |
CSSStyleDeclaration.prototype = Object.create(Object.prototype, { | |
// Return the parsed form of the element's style attribute. | |
// If the element's style attribute has never been parsed | |
// or if it has changed since the last parse, then reparse it | |
// Note that the styles don't get parsed until they're actually needed | |
_parsed: { get: function() { | |
if (!this._parsedStyles || this.cssText !== this._lastParsedText) { | |
var text = this.cssText; | |
this._parsedStyles = parseStyles(text); | |
this._lastParsedText = text; | |
delete this._names; | |
} | |
return this._parsedStyles; | |
}}, | |
// Call this method any time the parsed representation of the | |
// style changes. It converts the style properties to a string and | |
// sets cssText and the element's style attribute | |
_serialize: { value: function() { | |
var styles = this._parsed; | |
var s = ""; | |
for(var name in styles) { | |
if (s) s += "; "; | |
s += name + ":" + styles[name] | |
} | |
this.cssText = s; // also sets the style attribute | |
this._lastParsedText = s; // so we don't reparse | |
delete this._names; | |
}}, | |
cssText: { | |
get: function() { | |
// XXX: this is a CSSStyleDeclaration for an element. | |
// A different impl might be necessary for a set of styles | |
// associated returned by getComputedStyle(), e.g. | |
return this._element.getAttribute("style"); | |
}, | |
set: function(value) { | |
// XXX: I should parse and serialize the value to | |
// normalize it and remove errors. FF and chrome do that. | |
this._element.setAttribute("style", value); | |
} | |
}, | |
length: { get: function() { | |
if (!this._names) | |
this._names = Object.getOwnPropertyNames(this._parsed); | |
return this._names.length; | |
}}, | |
item: { value: function(n) { | |
if (!this._names) | |
this._names = Object.getOwnPropertyNames(this._parsed); | |
return this._names[n]; | |
}}, | |
getPropertyValue: { value: function(property) { | |
return this._parsed[property.toLowerCase()]; | |
}}, | |
// XXX: for now we ignore !important declarations | |
getPropertyPriority: { value: function(property) { | |
return ""; | |
}}, | |
// XXX the priority argument is ignored for now | |
setProperty: { value: function(property, value, priority) { | |
property = property.toLowerCase(); | |
if (value === null || value === undefined) { | |
value = ""; | |
} | |
// String coercion | |
value = "" + value; | |
// XXX are there other legal priority values? | |
if (priority !== undefined && priority !== "important") | |
return; | |
// We don't just accept the property value. Instead | |
// we parse it to ensure that it is something valid. | |
// If it contains a semicolon it is invalid | |
if (value.indexOf(";") !== -1) return; | |
var newvalue = value; | |
if (value.length) { | |
var props = parseStyles(property + ":" + value); | |
newvalue = props[property]; | |
// If there is no value now, it wasn't valid | |
if (!newvalue) return; | |
} | |
var styles = this._parsed; | |
// If the value didn't change, return without doing anything. | |
var oldvalue = styles[property]; | |
if (newvalue === oldvalue) return; | |
styles[property] = value; | |
// Serialize and update cssText and element.style! | |
this._serialize(); | |
}}, | |
removeProperty: { value: function(property) { | |
property = property.toLowerCase(); | |
var styles = this._parsed; | |
if (property in styles) { | |
delete styles[property]; | |
// Serialize and update cssText and element.style! | |
this._serialize(); | |
} | |
}}, | |
}); | |
var cssProperties = { | |
background: "background", | |
backgroundAttachment: "background-attachment", | |
backgroundColor: "background-color", | |
backgroundImage: "background-image", | |
backgroundPosition: "background-position", | |
backgroundRepeat: "background-repeat", | |
border: "border", | |
borderCollapse: "border-collapse", | |
borderColor: "border-color", | |
borderSpacing: "border-spacing", | |
borderStyle: "border-style", | |
borderTop: "border-top", | |
borderRight: "border-right", | |
borderBottom: "border-bottom", | |
borderLeft: "border-left", | |
borderTopColor: "border-top-color", | |
borderRightColor: "border-right-color", | |
borderBottomColor: "border-bottom-color", | |
borderLeftColor: "border-left-color", | |
borderTopStyle: "border-top-style", | |
borderRightStyle: "border-right-style", | |
borderBottomStyle: "border-bottom-style", | |
borderLeftStyle: "border-left-style", | |
borderTopWidth: "border-top-width", | |
borderRightWidth: "border-right-width", | |
borderBottomWidth: "border-bottom-width", | |
borderLeftWidth: "border-left-width", | |
borderWidth: "border-width", | |
bottom: "bottom", | |
captionSide: "caption-side", | |
clear: "clear", | |
clip: "clip", | |
color: "color", | |
content: "content", | |
counterIncrement: "counter-increment", | |
counterReset: "counter-reset", | |
cursor: "cursor", | |
direction: "direction", | |
display: "display", | |
emptyCells: "empty-cells", | |
cssFloat: "float", | |
font: "font", | |
fontFamily: "font-family", | |
fontSize: "font-size", | |
fontSizeAdjust: "font-size-adjust", | |
fontStretch: "font-stretch", | |
fontStyle: "font-style", | |
fontVariant: "font-variant", | |
fontWeight: "font-weight", | |
height: "height", | |
left: "left", | |
letterSpacing: "letter-spacing", | |
lineHeight: "line-height", | |
listStyle: "list-style", | |
listStyleImage: "list-style-image", | |
listStylePosition: "list-style-position", | |
listStyleType: "list-style-type", | |
margin: "margin", | |
marginTop: "margin-top", | |
marginRight: "margin-right", | |
marginBottom: "margin-bottom", | |
marginLeft: "margin-left", | |
markerOffset: "marker-offset", | |
marks: "marks", | |
maxHeight: "max-height", | |
maxWidth: "max-width", | |
minHeight: "min-height", | |
minWidth: "min-width", | |
opacity: "opacity", | |
orphans: "orphans", | |
outline: "outline", | |
outlineColor: "outline-color", | |
outlineStyle: "outline-style", | |
outlineWidth: "outline-width", | |
overflow: "overflow", | |
padding: "padding", | |
paddingTop: "padding-top", | |
paddingRight: "padding-right", | |
paddingBottom: "padding-bottom", | |
paddingLeft: "padding-left", | |
page: "page", | |
pageBreakAfter: "page-break-after", | |
pageBreakBefore: "page-break-before", | |
pageBreakInside: "page-break-inside", | |
position: "position", | |
quotes: "quotes", | |
right: "right", | |
size: "size", | |
tableLayout: "table-layout", | |
textAlign: "text-align", | |
textDecoration: "text-decoration", | |
textIndent: "text-indent", | |
textShadow: "text-shadow", | |
textTransform: "text-transform", | |
top: "top", | |
unicodeBidi: "unicode-bidi", | |
verticalAlign: "vertical-align", | |
visibility: "visibility", | |
whiteSpace: "white-space", | |
widows: "widows", | |
width: "width", | |
wordSpacing: "word-spacing", | |
zIndex: "z-index", | |
}; | |
for(var prop in cssProperties) defineStyleProperty(prop); | |
function defineStyleProperty(jsname) { | |
var cssname = cssProperties[jsname]; | |
Object.defineProperty(CSSStyleDeclaration.prototype, jsname, { | |
get: function() { | |
return this.getPropertyValue(cssname); | |
}, | |
set: function(value) { | |
// XXX Handle important declarations here! | |
this.setProperty(cssname, value); | |
} | |
}); | |
} | |
},{"./cssparser":31}],2:[function(require,module,exports){ | |
module.exports = CharacterData; | |
var Leaf = require('./Leaf'); | |
var utils = require('./utils'); | |
function CharacterData() { | |
} | |
CharacterData.prototype = Object.create(Leaf.prototype, { | |
// DOMString substringData(unsigned long offset, | |
// unsigned long count); | |
// The substringData(offset, count) method must run these steps: | |
// | |
// If offset is greater than the context object's | |
// length, throw an INDEX_SIZE_ERR exception and | |
// terminate these steps. | |
// | |
// If offset+count is greater than the context | |
// object's length, return a DOMString whose value is | |
// the UTF-16 code units from the offsetth UTF-16 code | |
// unit to the end of data. | |
// | |
// Return a DOMString whose value is the UTF-16 code | |
// units from the offsetth UTF-16 code unit to the | |
// offset+countth UTF-16 code unit in data. | |
substringData: { value: function substringData(offset, count) { | |
if (offset > this.data.length || offset < 0 || count < 0) | |
utils.IndexSizeError(); | |
return this.data.substring(offset, offset+count); | |
}}, | |
// void appendData(DOMString data); | |
// The appendData(data) method must append data to the context | |
// object's data. | |
appendData: { value: function appendData(data) { | |
this.data = this.data + data; | |
}}, | |
// void insertData(unsigned long offset, DOMString data); | |
// The insertData(offset, data) method must run these steps: | |
// | |
// If offset is greater than the context object's | |
// length, throw an INDEX_SIZE_ERR exception and | |
// terminate these steps. | |
// | |
// Insert data into the context object's data after | |
// offset UTF-16 code units. | |
// | |
insertData: { value: function insertData(offset, data) { | |
var curtext = this.data; | |
if (offset > curtext.length || offset < 0) utils.IndexSizeError(); | |
var prefix = curtext.substring(0, offset), | |
suffix = curtext.substring(offset); | |
this.data = prefix + data + suffix; | |
}}, | |
// void deleteData(unsigned long offset, unsigned long count); | |
// The deleteData(offset, count) method must run these steps: | |
// | |
// If offset is greater than the context object's | |
// length, throw an INDEX_SIZE_ERR exception and | |
// terminate these steps. | |
// | |
// If offset+count is greater than the context | |
// object's length var count be length-offset. | |
// | |
// Starting from offset UTF-16 code units remove count | |
// UTF-16 code units from the context object's data. | |
deleteData: { value: function deleteData(offset, count) { | |
var curtext = this.data, len = curtext.length; | |
if (offset > len || offset < 0) utils.IndexSizeError(); | |
if (offset+count > len) | |
count = len - offset; | |
var prefix = curtext.substring(0, offset), | |
suffix = curtext.substring(offset+count); | |
this.data = prefix + suffix; | |
}}, | |
// void replaceData(unsigned long offset, unsigned long count, | |
// DOMString data); | |
// | |
// The replaceData(offset, count, data) method must act as | |
// if the deleteData() method is invoked with offset and | |
// count as arguments followed by the insertData() method | |
// with offset and data as arguments and re-throw any | |
// exceptions these methods might have thrown. | |
replaceData: { value: function replaceData(offset, count, data) { | |
var curtext = this.data, len = curtext.length; | |
if (offset > len || offset < 0) utils.IndexSizeError(); | |
if (offset+count > len) | |
count = len - offset; | |
var prefix = curtext.substring(0, offset), | |
suffix = curtext.substring(offset+count); | |
this.data = prefix + data + suffix; | |
}}, | |
// Utility method that Node.isEqualNode() calls to test Text and | |
// Comment nodes for equality. It is okay to put it here, since | |
// Node will have already verified that nodeType is equal | |
isEqual: { value: function isEqual(n) { | |
return this._data === n._data; | |
}}, | |
length: { get: function() { return this.data.length; }} | |
}); | |
},{"./Leaf":16,"./utils":37}],3:[function(require,module,exports){ | |
module.exports = Comment; | |
var Node = require('./Node'); | |
var CharacterData = require('./CharacterData'); | |
function Comment(doc, data) { | |
this.nodeType = Node.COMMENT_NODE; | |
this.ownerDocument = doc; | |
this._data = data; | |
this._index = undefined; | |
} | |
var nodeValue = { | |
get: function() { return this._data; }, | |
set: function(v) { | |
this._data = v; | |
if (this.rooted) | |
this.ownerDocument.mutateValue(this); | |
} | |
}; | |
Comment.prototype = Object.create(CharacterData.prototype, { | |
nodeName: { value: '#comment' }, | |
nodeValue: nodeValue, | |
textContent: nodeValue, | |
data: nodeValue, | |
// Utility methods | |
clone: { value: function clone() { | |
return new Comment(this.ownerDocument, this._data); | |
}}, | |
}); | |
},{"./CharacterData":2,"./Node":20}],4:[function(require,module,exports){ | |
module.exports = CustomEvent; | |
var Event = require('./Event'); | |
function CustomEvent(type, dictionary) { | |
// Just use the superclass constructor to initialize | |
Event.call(this, type, dictionary); | |
} | |
CustomEvent.prototype = Object.create(Event.prototype, { | |
constructor: { value: CustomEvent } | |
}); | |
},{"./Event":12}],5:[function(require,module,exports){ | |
module.exports = DOMException; | |
var INDEX_SIZE_ERR = 1; | |
var HIERARCHY_REQUEST_ERR = 3; | |
var WRONG_DOCUMENT_ERR = 4; | |
var INVALID_CHARACTER_ERR = 5; | |
var NO_MODIFICATION_ALLOWED_ERR = 7; | |
var NOT_FOUND_ERR = 8; | |
var NOT_SUPPORTED_ERR = 9; | |
var INVALID_STATE_ERR = 11; | |
var SYNTAX_ERR = 12; | |
var INVALID_MODIFICATION_ERR = 13; | |
var NAMESPACE_ERR = 14; | |
var INVALID_ACCESS_ERR = 15; | |
var TYPE_MISMATCH_ERR = 17; | |
var SECURITY_ERR = 18; | |
var NETWORK_ERR = 19; | |
var ABORT_ERR = 20; | |
var URL_MISMATCH_ERR = 21; | |
var QUOTA_EXCEEDED_ERR = 22; | |
var TIMEOUT_ERR = 23; | |
var INVALID_NODE_TYPE_ERR = 24; | |
var DATA_CLONE_ERR = 25; | |
// Code to name | |
var names = [ | |
null, // No error with code 0 | |
'INDEX_SIZE_ERR', | |
null, // historical | |
'HIERARCHY_REQUEST_ERR', | |
'WRONG_DOCUMENT_ERR', | |
'INVALID_CHARACTER_ERR', | |
null, // historical | |
'NO_MODIFICATION_ALLOWED_ERR', | |
'NOT_FOUND_ERR', | |
'NOT_SUPPORTED_ERR', | |
null, // historical | |
'INVALID_STATE_ERR', | |
'SYNTAX_ERR', | |
'INVALID_MODIFICATION_ERR', | |
'NAMESPACE_ERR', | |
'INVALID_ACCESS_ERR', | |
null, // historical | |
'TYPE_MISMATCH_ERR', | |
'SECURITY_ERR', | |
'NETWORK_ERR', | |
'ABORT_ERR', | |
'URL_MISMATCH_ERR', | |
'QUOTA_EXCEEDED_ERR', | |
'TIMEOUT_ERR', | |
'INVALID_NODE_TYPE_ERR', | |
'DATA_CLONE_ERR', | |
]; | |
// Code to message | |
// These strings are from the 13 May 2011 Editor's Draft of DOM Core. | |
// http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html | |
// Copyright © 2011 W3C® (MIT, ERCIM, Keio), All Rights Reserved. | |
// Used under the terms of the W3C Document License: | |
// http://www.w3.org/Consortium/Legal/2002/copyright-documents-20021231 | |
var messages = [ | |
null, // No error with code 0 | |
'INDEX_SIZE_ERR (1): the index is not in the allowed range', | |
null, | |
'HIERARCHY_REQUEST_ERR (3): the operation would yield an incorrect nodes model', | |
'WRONG_DOCUMENT_ERR (4): the object is in the wrong Document, a call to importNode is required', | |
'INVALID_CHARACTER_ERR (5): the string contains invalid characters', | |
null, | |
'NO_MODIFICATION_ALLOWED_ERR (7): the object can not be modified', | |
'NOT_FOUND_ERR (8): the object can not be found here', | |
'NOT_SUPPORTED_ERR (9): this operation is not supported', | |
null, | |
'INVALID_STATE_ERR (11): the object is in an invalid state', | |
'SYNTAX_ERR (12): the string did not match the expected pattern', | |
'INVALID_MODIFICATION_ERR (13): the object can not be modified in this way', | |
'NAMESPACE_ERR (14): the operation is not allowed by Namespaces in XML', | |
'INVALID_ACCESS_ERR (15): the object does not support the operation or argument', | |
null, | |
'TYPE_MISMATCH_ERR (17): the type of the object does not match the expected type', | |
'SECURITY_ERR (18): the operation is insecure', | |
'NETWORK_ERR (19): a network error occurred', | |
'ABORT_ERR (20): the user aborted an operation', | |
'URL_MISMATCH_ERR (21): the given URL does not match another URL', | |
'QUOTA_EXCEEDED_ERR (22): the quota has been exceeded', | |
'TIMEOUT_ERR (23): a timeout occurred', | |
'INVALID_NODE_TYPE_ERR (24): the supplied node is invalid or has an invalid ancestor for this operation', | |
'DATA_CLONE_ERR (25): the object can not be cloned.' | |
]; | |
// Name to code | |
var constants = { | |
INDEX_SIZE_ERR: INDEX_SIZE_ERR, | |
DOMSTRING_SIZE_ERR: 2, // historical | |
HIERARCHY_REQUEST_ERR: HIERARCHY_REQUEST_ERR, | |
WRONG_DOCUMENT_ERR: WRONG_DOCUMENT_ERR, | |
INVALID_CHARACTER_ERR: INVALID_CHARACTER_ERR, | |
NO_DATA_ALLOWED_ERR: 6, // historical | |
NO_MODIFICATION_ALLOWED_ERR: NO_MODIFICATION_ALLOWED_ERR, | |
NOT_FOUND_ERR: NOT_FOUND_ERR, | |
NOT_SUPPORTED_ERR: NOT_SUPPORTED_ERR, | |
INUSE_ATTRIBUTE_ERR: 10, // historical | |
INVALID_STATE_ERR: INVALID_STATE_ERR, | |
SYNTAX_ERR: SYNTAX_ERR, | |
INVALID_MODIFICATION_ERR: INVALID_MODIFICATION_ERR, | |
NAMESPACE_ERR: NAMESPACE_ERR, | |
INVALID_ACCESS_ERR: INVALID_ACCESS_ERR, | |
VALIDATION_ERR: 16, // historical | |
TYPE_MISMATCH_ERR: TYPE_MISMATCH_ERR, | |
SECURITY_ERR: SECURITY_ERR, | |
NETWORK_ERR: NETWORK_ERR, | |
ABORT_ERR: ABORT_ERR, | |
URL_MISMATCH_ERR: URL_MISMATCH_ERR, | |
QUOTA_EXCEEDED_ERR: QUOTA_EXCEEDED_ERR, | |
TIMEOUT_ERR: TIMEOUT_ERR, | |
INVALID_NODE_TYPE_ERR: INVALID_NODE_TYPE_ERR, | |
DATA_CLONE_ERR: DATA_CLONE_ERR | |
}; | |
function DOMException(code) { | |
Error.call(this); | |
Error.captureStackTrace(this, arguments.callee); | |
this.code = code; | |
this.message = messages[code]; | |
this.name = names[code]; | |
} | |
DOMException.prototype.__proto__ = Error.prototype; | |
// Initialize the constants on DOMException and DOMException.prototype | |
for(var c in constants) { | |
var v = { value: constants[c] }; | |
Object.defineProperty(DOMException, c, v); | |
Object.defineProperty(DOMException.prototype, c, v); | |
} | |
},{}],6:[function(require,module,exports){ | |
module.exports = DOMImplementation; | |
var Document = require('./Document'); | |
var DocumentType = require('./DocumentType'); | |
var HTMLParser = require('./HTMLParser'); | |
var utils = require('./utils'); | |
var xml = require('./xmlnames'); | |
// Each document must have its own instance of the domimplementation object | |
// Even though these objects have no state | |
function DOMImplementation() {} | |
// Feature/version pairs that DOMImplementation.hasFeature() returns | |
// true for. It returns false for anything else. | |
var supportedFeatures = { | |
'xml': { '': true, '1.0': true, '2.0': true }, // DOM Core | |
'core': { '': true, '2.0': true }, // DOM Core | |
'html': { '': true, '1.0': true, '2.0': true} , // HTML | |
'xhtml': { '': true, '1.0': true, '2.0': true} , // HTML | |
}; | |
DOMImplementation.prototype = { | |
hasFeature: function hasFeature(feature, version) { | |
var f = supportedFeatures[(feature || '').toLowerCase()]; | |
return (f && f[version || '']) || false; | |
}, | |
createDocumentType: function createDocumentType(qualifiedName, publicId, systemId) { | |
if (!xml.isValidName(qualifiedName)) utils.InvalidCharacterError(); | |
if (!xml.isValidQName(qualifiedName)) utils.NamespaceError(); | |
return new DocumentType(qualifiedName, publicId, systemId); | |
}, | |
createDocument: function createDocument(namespace, qualifiedName, doctype) { | |
// | |
// Note that the current DOMCore spec makes it impossible to | |
// create an HTML document with this function, even if the | |
// namespace and doctype are propertly set. See this thread: | |
// http://lists.w3.org/Archives/Public/www-dom/2011AprJun/0132.html | |
// | |
var d = new Document(false, null); | |
var e; | |
if (qualifiedName) | |
e = d.createElementNS(namespace, qualifiedName); | |
else | |
e = null; | |
if (doctype) { | |
if (doctype.ownerDocument) utils.WrongDocumentError(); | |
d.appendChild(doctype); | |
} | |
if (e) d.appendChild(e); | |
return d; | |
}, | |
createHTMLDocument: function createHTMLDocument(titleText) { | |
var d = new Document(true, null); | |
d.appendChild(new DocumentType('html')); | |
var html = d.createElement('html'); | |
d.appendChild(html); | |
var head = d.createElement('head'); | |
html.appendChild(head); | |
var title = d.createElement('title'); | |
head.appendChild(title); | |
title.appendChild(d.createTextNode(titleText)); | |
html.appendChild(d.createElement('body')); | |
d.modclock = 1; // Start tracking modifications | |
return d; | |
}, | |
mozSetOutputMutationHandler: function(doc, handler) { | |
doc.mutationHandler = handler; | |
}, | |
mozGetInputMutationHandler: function(doc) { | |
utils.nyi(); | |
}, | |
mozHTMLParser: HTMLParser, | |
}; | |
},{"./Document":8,"./DocumentType":10,"./HTMLParser":15,"./utils":37,"./xmlnames":38}],7:[function(require,module,exports){ | |
// DOMTokenList implementation based on https://github.com/Raynos/DOM-shim | |
var utils = require('./utils'); | |
module.exports = DOMTokenList; | |
function DOMTokenList(getter, setter) { | |
this._getString = getter; | |
this._setString = setter; | |
fixIndex(this, getter().split(" ")); | |
} | |
DOMTokenList.prototype = { | |
item: function(index) { | |
if (index >= this.length) { | |
return null; | |
} | |
return this._getString().split(" ")[index]; | |
}, | |
contains: function(token) { | |
handleErrors(token); | |
var list = getList(this); | |
return list.indexOf(token) > -1; | |
}, | |
add: function(token) { | |
handleErrors(token); | |
var list = getList(this); | |
if (list.indexOf(token) > -1) { | |
return; | |
} | |
list.push(token); | |
this._setString(list.join(" ").trim()); | |
fixIndex(this, list); | |
}, | |
remove: function(token) { | |
handleErrors(token); | |
var list = getList(this); | |
var index = list.indexOf(token); | |
if (index > -1) { | |
list.splice(index, 1); | |
this._setString(list.join(" ").trim()); | |
} | |
fixIndex(this, list); | |
}, | |
toggle: function toggle(token) { | |
if (this.contains(token)) { | |
this.remove(token); | |
return false; | |
} | |
else { | |
this.add(token); | |
return true; | |
} | |
}, | |
toString: function() { | |
return this._getString(); | |
} | |
}; | |
function fixIndex(clist, list) { | |
clist.length = list.length; | |
for (var i = 0; i < list.length; i++) { | |
clist[i] = list[i]; | |
} | |
} | |
function handleErrors(token) { | |
if (token === "" || token === undefined) { | |
utils.SyntaxError(); | |
} | |
if (token.indexOf(" ") > -1) { | |
utils.InvalidCharacterError(); | |
} | |
} | |
function getList(clist) { | |
var str = clist._getString(); | |
if (str === "") { | |
return []; | |
} | |
else { | |
return str.split(" "); | |
} | |
} | |
},{"./utils":37}],8:[function(require,module,exports){ | |
module.exports = Document; | |
var Node = require('./Node'); | |
var NodeList = require('./NodeList'); | |
var Element = require('./Element'); | |
var Text = require('./Text'); | |
var Comment = require('./Comment'); | |
var Event = require('./Event'); | |
var DocumentFragment = require('./DocumentFragment'); | |
var ProcessingInstruction = require('./ProcessingInstruction'); | |
var DOMImplementation = require('./DOMImplementation'); | |
var FilteredElementList = require('./FilteredElementList'); | |
var TreeWalker = require('./TreeWalker'); | |
var NodeFilter = require('./NodeFilter'); | |
var URL = require('./URL'); | |
var select = require('./select') | |
var events = require('./events'); | |
var xml = require('./xmlnames'); | |
var html = require('./htmlelts'); | |
var impl = html.elements; | |
var utils = require('./utils'); | |
var MUTATE = require('./MutationConstants'); | |
var NAMESPACE = utils.NAMESPACE; | |
function Document(isHTML, address) { | |
this.nodeType = Node.DOCUMENT_NODE; | |
this.isHTML = isHTML; | |
this._address = address || 'about:blank'; | |
this.readyState = 'loading'; | |
this.implementation = new DOMImplementation(); | |
// DOMCore says that documents are always associated with themselves | |
this.ownerDocument = null; // ... but W3C tests expect null | |
// These will be initialized by our custom versions of | |
// appendChild and insertBefore that override the inherited | |
// Node methods. | |
// XXX: override those methods! | |
this.doctype = null; | |
this.documentElement = null; | |
this.childNodes = new NodeList(); | |
// Documents are always rooted, by definition | |
this._nid = 1; | |
this._nextnid = 2; // For numbering children of the document | |
this._nodes = [null, this]; // nid to node map | |
// This maintains the mapping from element ids to element nodes. | |
// We may need to update this mapping every time a node is rooted | |
// or uprooted, and any time an attribute is added, removed or changed | |
// on a rooted element. | |
this.byId = {}; | |
// This property holds a monotonically increasing value akin to | |
// a timestamp used to record the last modification time of nodes | |
// and their subtrees. See the lastModTime attribute and modify() | |
// method of the Node class. And see FilteredElementList for an example | |
// of the use of lastModTime | |
this.modclock = 0; | |
} | |
// Map from lowercase event category names (used as arguments to | |
// createEvent()) to the property name in the impl object of the | |
// event constructor. | |
var supportedEvents = { | |
event: 'Event', | |
customevent: 'CustomEvent', | |
uievent: 'UIEvent', | |
mouseevent: 'MouseEvent' | |
}; | |
// Certain arguments to document.createEvent() must be treated specially | |
var replacementEvent = { | |
events: 'event', | |
htmlevents: 'event', | |
mouseevents: 'mouseevent', | |
mutationevents: 'mutationevent', | |
uievents: 'uievent' | |
}; | |
Document.prototype = Object.create(Node.prototype, { | |
// This method allows dom.js to communicate with a renderer | |
// that displays the document in some way | |
// XXX: I should probably move this to the window object | |
_setMutationHandler: { value: function(handler) { | |
this.mutationHandler = handler; | |
}}, | |
// This method allows dom.js to receive event notifications | |
// from the renderer. | |
// XXX: I should probably move this to the window object | |
_dispatchRendererEvent: { value: function(targetNid, type, details) { | |
var target = this._nodes[targetNid]; | |
if (!target) return; | |
target._dispatchEvent(new Event(type, details), true); | |
}}, | |
nodeName: { value: '#document'}, | |
nodeValue: { | |
get: function() { | |
return null; | |
}, | |
set: function() {} | |
}, | |
// XXX: DOMCore may remove documentURI, so it is NYI for now | |
documentURI: { get: utils.nyi, set: utils.nyi }, | |
compatMode: { get: function() { | |
// The _quirks property is set by the HTML parser | |
return this._quirks ? 'BackCompat' : 'CSS1Compat'; | |
}}, | |
parentNode: { value: null }, | |
createTextNode: { value: function(data) { | |
return new Text(this, '' + data); | |
}}, | |
createComment: { value: function(data) { | |
return new Comment(this, data); | |
}}, | |
createDocumentFragment: { value: function() { | |
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); | |
}}, | |
createElement: { value: function(localName) { | |
if (!xml.isValidName(localName)) utils.InvalidCharacterError(); | |
if (this.isHTML) localName = localName.toLowerCase(); | |
return html.createElement(this, localName, null); | |
}}, | |
createElementNS: { value: function(namespace, qualifiedName) { | |
if (!xml.isValidName(qualifiedName)) utils.InvalidCharacterError(); | |
if (!xml.isValidQName(qualifiedName)) utils.NamespaceError(); | |
var pos, prefix, localName; | |
if ((pos = qualifiedName.indexOf(':')) !== -1) { | |
prefix = qualifiedName.substring(0, pos); | |
localName = qualifiedName.substring(pos+1); | |
if (namespace === '' || | |
(prefix === 'xml' && namespace !== NAMESPACE.XML)) | |
utils.NamespaceError(); | |
} | |
else { | |
prefix = null; | |
localName = qualifiedName; | |
} | |
if (((qualifiedName === 'xmlns' || prefix === 'xmlns') && | |
namespace !== NAMESPACE.XMLNS) || | |
(namespace === NAMESPACE.XMLNS && | |
qualifiedName !== 'xmlns' && | |
prefix !== 'xmlns')) | |
utils.NamespaceError(); | |
if (namespace === NAMESPACE.HTML) { | |
return html.createElement(this, localName, prefix); | |
} | |
return new Element(this, localName, namespace, prefix); | |
}}, | |
createEvent: { value: function createEvent(interfaceName) { | |
interfaceName = interfaceName.toLowerCase(); | |
var name = replacementEvent[interfaceName] || interfaceName; | |
var constructor = events[supportedEvents[name]]; | |
if (constructor) { | |
var e = new constructor(); | |
e._initialized = false; | |
return e; | |
} | |
else { | |
utils.NotSupportedError(); | |
} | |
}}, | |
// See: http://www.w3.org/TR/dom/#dom-document-createtreewalker | |
createTreeWalker: {value: function (root, whatToShow, filter) { | |
whatToShow = whatToShow === undefined ? NodeFilter.SHOW_ALL : whatToShow; | |
if (filter && typeof filter.acceptNode == 'function') { | |
filter = filter.acceptNode; | |
// Support filter being a function | |
// https://developer.mozilla.org/en-US/docs/DOM/document.createTreeWalker | |
} | |
else if (typeof filter != 'function') { | |
filter = null; | |
} | |
return new TreeWalker(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 | |
// doctype properties of the document. Each of the following | |
// 4 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; | |
} | |
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); | |
}}, | |
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); | |
}}, | |
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); | |
}}, | |
getElementById: { value: function(id) { | |
var n = this.byId[id]; | |
if (!n) return null; | |
if (Array.isArray(n)) { // there was more than one element with this id | |
return n[0]; // array is sorted in document order | |
} | |
return n; | |
}}, | |
// Just copy this method from the Element prototype | |
getElementsByTagName: { value: Element.prototype.getElementsByTagName }, | |
getElementsByTagNameNS: { value: Element.prototype.getElementsByTagNameNS }, | |
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.parentNode) node.parentNode.removeChild(node); | |
if (node.ownerDocument !== this) | |
recursivelySetOwner(node, this); | |
return node; | |
}}, | |
importNode: { value: function importNode(node, deep) { | |
return this.adoptNode(node.cloneNode()); | |
}}, | |
// The following attributes and methods are from the HTML spec | |
URL: { get: utils.nyi }, | |
domain: { get: utils.nyi, set: utils.nyi }, | |
referrer: { get: utils.nyi }, | |
cookie: { get: utils.nyi, set: utils.nyi }, | |
lastModified: { get: utils.nyi }, | |
title: { | |
get: function() { | |
// Return the text of the first <title> child of the <head> element. | |
var elt = namedHTMLChild(this.head, 'title'); | |
return elt && elt.textContent || ''; | |
}, | |
set: function(value) { | |
var head = this.head; | |
if (!head) { return; /* according to spec */ } | |
var elt = namedHTMLChild(head, 'title'); | |
if (!elt) { | |
elt = this.createElement('title'); | |
head.appendChild(elt); | |
} | |
elt.textContent = value; | |
} | |
}, | |
dir: { get: utils.nyi, set: utils.nyi }, | |
// Return the first <body> child of the document element. | |
// XXX For now, setting this attribute is not implemented. | |
body: { | |
get: function() { | |
return namedHTMLChild(this.documentElement, 'body'); | |
}, | |
set: utils.nyi | |
}, | |
// Return the first <head> child of the document element. | |
head: { get: function() { | |
return namedHTMLChild(this.documentElement, 'head'); | |
}}, | |
images: { get: utils.nyi }, | |
embeds: { get: utils.nyi }, | |
plugins: { get: utils.nyi }, | |
links: { get: utils.nyi }, | |
forms: { get: utils.nyi }, | |
scripts: { get: utils.nyi }, | |
innerHTML: { | |
get: function() { return this.serialize(); }, | |
set: utils.nyi | |
}, | |
outerHTML: { | |
get: function() { return this.serialize(); }, | |
set: utils.nyi | |
}, | |
write: { value: function(args) { | |
if (!this.isHTML) utils.InvalidStateError(); | |
// XXX: still have to implement the ignore part | |
if (!this._parser /* && this._ignore_destructive_writes > 0 */ ) | |
return; | |
if (!this._parser) { | |
// XXX call document.open, etc. | |
} | |
var s = arguments.join(''); | |
// If the Document object's reload override flag is set, then | |
// append the string consisting of the concatenation of all the | |
// arguments to the method to the Document's reload override | |
// buffer. | |
// XXX: don't know what this is about. Still have to do it | |
// If there is no pending parsing-blocking script, have the | |
// tokenizer process the characters that were inserted, one at a | |
// time, processing resulting tokens as they are emitted, and | |
// stopping when the tokenizer reaches the insertion point or when | |
// the processing of the tokenizer is aborted by the tree | |
// construction stage (this can happen if a script end tag token is | |
// emitted by the tokenizer). | |
// XXX: still have to do the above. Sounds as if we don't | |
// always call parse() here. If we're blocked, then we just | |
// insert the text into the stream but don't parse it reentrantly... | |
// Invoke the parser reentrantly | |
this._parser.parse(s); | |
}}, | |
writeln: { value: function writeln(args) { | |
this.write(Array.prototype.join.call(arguments, '') + '\n'); | |
}}, | |
open: { value: function() { | |
this.documentElement = null; | |
}}, | |
close: { value: function() { | |
this.readyState = 'complete'; | |
var ev = new Event('DOMContentLoaded'); | |
this._dispatchEvent(ev, true); | |
if (this.defaultView) { | |
ev = new Event('load'); | |
this.defaultView._dispatchEvent(ev, true); | |
} | |
}}, | |
// Utility methods | |
clone: { value: function clone() { | |
// Can't clone an entire document | |
utils.DataCloneError(); | |
}}, | |
isEqual: { value: function isEqual(n) { | |
// Any two documents are shallowly equal. | |
// Node.isEqualNode will also test the children | |
return true; | |
}}, | |
// Implementation-specific function. Called when a text, comment, | |
// or pi value changes. | |
mutateValue: { value: function(node) { | |
if (this.mutationHandler) { | |
this.mutationHandler({ | |
type: MUTATE.VALUE, | |
target: node, | |
data: node.data | |
}); | |
} | |
}}, | |
// Invoked when an attribute's value changes. Attr holds the new | |
// value. oldval is the old value. Attribute mutations can also | |
// involve changes to the prefix (and therefore the qualified name) | |
mutateAttr: { value: function(attr, oldval) { | |
// Manage id->element mapping for getElementsById() | |
// XXX: this special case id handling should not go here, | |
// but in the attribute declaration for the id attribute | |
/* | |
if (attr.localName === 'id' && attr.namespaceURI === null) { | |
if (oldval) delId(oldval, attr.ownerElement); | |
addId(attr.value, attr.ownerElement); | |
} | |
*/ | |
if (this.mutationHandler) { | |
this.mutationHandler({ | |
type: MUTATE.ATTR, | |
target: attr.ownerElement, | |
attr: attr | |
}); | |
} | |
}}, | |
// Used by removeAttribute and removeAttributeNS for attributes. | |
mutateRemoveAttr: { value: function(attr) { | |
/* | |
* This is now handled in Attributes.js | |
// Manage id to element mapping | |
if (attr.localName === 'id' && attr.namespaceURI === null) { | |
this.delId(attr.value, attr.ownerElement); | |
} | |
*/ | |
if (this.mutationHandler) { | |
this.mutationHandler({ | |
type: MUTATE.REMOVE_ATTR, | |
target: attr.ownerElement, | |
attr: attr | |
}); | |
} | |
}}, | |
// Called by Node.removeChild, etc. to remove a rooted element from | |
// the tree. Only needs to generate a single mutation event when a | |
// node is removed, but must recursively mark all descendants as not | |
// rooted. | |
mutateRemove: { value: function(node) { | |
// Send a single mutation event | |
if (this.mutationHandler) { | |
this.mutationHandler({ | |
type: MUTATE.REMOVE, | |
target: node.parentNode, | |
node: node | |
}); | |
} | |
// Mark this and all descendants as not rooted | |
recursivelyUproot(node); | |
}}, | |
// Called when a new element becomes rooted. It must recursively | |
// generate mutation events for each of the children, and mark them all | |
// as rooted. | |
mutateInsert: { value: function(node) { | |
// Mark node and its descendants as rooted | |
recursivelyRoot(node); | |
// Send a single mutation event | |
if (this.mutationHandler) { | |
this.mutationHandler({ | |
type: MUTATE.INSERT, | |
target: node.parentNode, | |
node: node | |
}); | |
} | |
}}, | |
// Called when a rooted element is moved within the document | |
mutateMove: { value: function(node) { | |
if (this.mutationHandler) { | |
this.mutationHandler({ | |
type: MUTATE.MOVE, | |
target: node | |
}); | |
} | |
}}, | |
// Add a mapping from id to n for n.ownerDocument | |
addId: { value: function addId(id, n) { | |
var val = this.byId[id]; | |
if (!val) { | |
this.byId[id] = n; | |
} | |
else { | |
// TODO: Add a way to opt-out console warnings | |
//console.warn('Duplicate element id ' + id); | |
if (!Array.isArray(val)) { | |
val = [val]; | |
this.byId[id] = val; | |
} | |
val.push(n); | |
val.sort(utils.documentOrder); | |
} | |
}}, | |
// Delete the mapping from id to n for n.ownerDocument | |
delId: { value: function delId(id, n) { | |
var val = this.byId[id]; | |
utils.assert(val); | |
if (Array.isArray(val)) { | |
var idx = val.indexOf(n); | |
val.splice(idx, 1); | |
if (val.length == 1) { // convert back to a single node | |
this.byId[id] = val[0]; | |
} | |
} | |
else { | |
this.byId[id] = undefined; | |
} | |
}}, | |
_resolve: { value: function(href) { | |
//XXX: Cache the URL | |
return new URL(this._documentBaseURL).resolve(href); | |
}}, | |
_documentBaseURL: { get: function() { | |
// XXX: This is not implemented correctly yet | |
var url = this._address; | |
if (url == 'about:blank') url = '/'; | |
return url; | |
// The document base URL of a Document object is the | |
// absolute URL obtained by running these substeps: | |
// Let fallback base url be the document's address. | |
// If fallback base url is about:blank, and the | |
// Document's browsing context has a creator browsing | |
// context, then let fallback base url be the document | |
// base URL of the creator Document instead. | |
// If the Document is an iframe srcdoc document, then | |
// let fallback base url be the document base URL of | |
// the Document's browsing context's browsing context | |
// container's Document instead. | |
// If there is no base element that has an href | |
// attribute, then the document base URL is fallback | |
// base url; abort these steps. Otherwise, let url be | |
// the value of the href attribute of the first such | |
// element. | |
// Resolve url relative to fallback base url (thus, | |
// the base href attribute isn't affected by xml:base | |
// attributes). | |
// The document base URL is the result of the previous | |
// step if it was successful; otherwise it is fallback | |
// base url. | |
}}, | |
querySelector: { value: function(selector) { | |
return select(selector, this)[0]; | |
}}, | |
querySelectorAll: { value: function(selector) { | |
var nodes = select(selector, this); | |
return nodes.item ? nodes : new NodeList(nodes); | |
}} | |
}); | |
var eventHandlerTypes = [ | |
'abort', 'canplay', 'canplaythrough', 'change', 'click', 'contextmenu', | |
'cuechange', 'dblclick', 'drag', 'dragend', 'dragenter', 'dragleave', | |
'dragover', 'dragstart', 'drop', 'durationchange', 'emptied', 'ended', | |
'input', 'invalid', 'keydown', 'keypress', 'keyup', 'loadeddata', | |
'loadedmetadata', 'loadstart', 'mousedown', 'mousemove', 'mouseout', | |
'mouseover', 'mouseup', 'mousewheel', 'pause', 'play', 'playing', | |
'progress', 'ratechange', 'readystatechange', 'reset', 'seeked', | |
'seeking', 'select', 'show', 'stalled', 'submit', 'suspend', | |
'timeupdate', 'volumechange', 'waiting', | |
'blur', 'error', 'focus', 'load', 'scroll' | |
]; | |
// Add event handler idl attribute getters and setters to Document | |
eventHandlerTypes.forEach(function(type) { | |
// Define the event handler registration IDL attribute for this type | |
Object.defineProperty(Document.prototype, 'on' + type, { | |
get: function() { | |
return this._getEventHandler(type); | |
}, | |
set: function(v) { | |
this._setEventHandler(type, v); | |
} | |
}); | |
}); | |
function namedHTMLChild(parent, name) { | |
if (parent && parent.isHTML) { | |
var kids = parent.childNodes; | |
for(var i = 0, n = kids.length; i < n; i++) { | |
if (kids[i].nodeType === Node.ELEMENT_NODE && | |
kids[i].localName === name && | |
kids[i].namespaceURI === NAMESPACE.HTML) { | |
return kids[i]; | |
} | |
} | |
} | |
return null; | |
} | |
function root(n) { | |
n._nid = n.ownerDocument._nextnid++; | |
n.ownerDocument._nodes[n._nid] = n; | |
// Manage id to element mapping | |
if (n.nodeType === Node.ELEMENT_NODE) { | |
var id = n.getAttribute('id'); | |
if (id) n.ownerDocument.addId(id, n); | |
// Script elements need to know when they're inserted | |
// into the document | |
if (n._roothook) n._roothook(); | |
} | |
} | |
function uproot(n) { | |
// Manage id to element mapping | |
if (n.nodeType === Node.ELEMENT_NODE) { | |
var id = n.getAttribute('id'); | |
if (id) n.ownerDocument.delId(id, n); | |
} | |
n.ownerDocument._nodes[n._nid] = undefined; | |
n._nid = undefined; | |
} | |
function recursivelyRoot(node) { | |
root(node); | |
// XXX: | |
// accessing childNodes on a leaf node creates a new array the | |
// first time, so be careful to write this loop so that it | |
// doesn't do that. node is polymorphic, so maybe this is hard to | |
// optimize? Try switching on nodeType? | |
/* | |
if (node.hasChildNodes()) { | |
var kids = node.childNodes; | |
for(var i = 0, n = kids.length; i < n; i++) | |
recursivelyRoot(kids[i]); | |
} | |
*/ | |
if (node.nodeType === Node.ELEMENT_NODE) { | |
var kids = node.childNodes; | |
for(var i = 0, n = kids.length; i < n; i++) | |
recursivelyRoot(kids[i]); | |
} | |
} | |
function recursivelyUproot(node) { | |
uproot(node); | |
for(var i = 0, n = node.childNodes.length; i < n; i++) | |
recursivelyUproot(node.childNodes[i]); | |
} | |
function recursivelySetOwner(node, owner) { | |
node.ownerDocument = owner; | |
node._lastModTime = undefined; // mod times are document-based | |
var kids = node.childNodes; | |
for(var i = 0, n = kids.length; i < n; i++) | |
recursivelySetOwner(kids[i], owner); | |
} | |
},{"./Comment":3,"./DOMImplementation":6,"./DocumentFragment":9,"./Element":11,"./Event":12,"./FilteredElementList":14,"./MutationConstants":19,"./Node":20,"./NodeFilter":21,"./NodeList":22,"./ProcessingInstruction":23,"./Text":24,"./TreeWalker":25,"./URL":27,"./events":32,"./htmlelts":33,"./select":36,"./utils":37,"./xmlnames":38}],9:[function(require,module,exports){ | |
module.exports = DocumentFragment; | |
var Node = require('./Node'); | |
var NodeList = require('./NodeList'); | |
var Element = require('./Element'); | |
var select = require('./select'); | |
function DocumentFragment(doc) { | |
this.nodeType = Node.DOCUMENT_FRAGMENT_NODE; | |
this.ownerDocument = doc; | |
this.childNodes = []; | |
} | |
DocumentFragment.prototype = Object.create(Node.prototype, { | |
nodeName: { value: '#document-fragment' }, | |
nodeValue: { | |
get: function() { | |
return null; | |
}, | |
set: function() {} | |
}, | |
// Copy the text content getter/setter from Element | |
textContent: Object.getOwnPropertyDescriptor(Element.prototype, 'textContent'), | |
querySelector: { value: function(selector) { | |
// implement in terms of querySelectorAll | |
var nodes = this.querySelectorAll(selector); | |
return nodes.length ? nodes[0] : null; | |
}}, | |
querySelectorAll: { value: function(selector) { | |
// create a context | |
var context = Object.create(this); | |
// add some methods to the context for zest implementation, without | |
// adding them to the public DocumentFragment API | |
context.isHTML = true; // in HTML namespace (case-insensitive match) | |
context.getElementsByTagName = Element.prototype.getElementsByTagName; | |
context.nextElement = | |
Object.getOwnPropertyDescriptor(Element.prototype, 'firstElementChild'). | |
get; | |
// invoke zest | |
var nodes = select(selector, context); | |
return nodes.item ? nodes : new NodeList(nodes); | |
}}, | |
// Utility methods | |
clone: { value: function clone() { | |
return new DocumentFragment(this.ownerDocument); | |
}}, | |
isEqual: { value: function isEqual(n) { | |
// Any two document fragments are shallowly equal. | |
// Node.isEqualNode() will test their children for equality | |
return true; | |
}}, | |
}); | |
},{"./Element":11,"./Node":20,"./NodeList":22,"./select":36}],10:[function(require,module,exports){ | |
module.exports = DocumentType; | |
var Node = require('./Node'); | |
var Leaf = require('./Leaf'); | |
var utils = require('./utils'); | |
function DocumentType(name, publicId, systemId) { | |
// Unlike other nodes, doctype nodes always start off unowned | |
// until inserted | |
this.nodeType = Node.DOCUMENT_TYPE_NODE; | |
this.ownerDocument = null; | |
this.name = name; | |
this.publicId = publicId || ""; | |
this.systemId = systemId || ""; | |
} | |
DocumentType.prototype = Object.create(Leaf.prototype, { | |
nodeName: { get: function() { return this.name; }}, | |
nodeValue: { | |
get: function() { return null; }, | |
set: function() {} | |
}, | |
// Utility methods | |
clone: { value: function clone() { | |
utils.DataCloneError(); | |
}}, | |
isEqual: { value: function isEqual(n) { | |
return this.name === n.name && | |
this.publicId === n.publicId && | |
this.systemId === n.systemId; | |
}} | |
}); | |
},{"./Leaf":16,"./Node":20,"./utils":37}],11:[function(require,module,exports){ | |
module.exports = Element; | |
var xml = require('./xmlnames'); | |
var utils = require('./utils'); | |
var NAMESPACE = utils.NAMESPACE; | |
var attributes = require('./attributes'); | |
var Node = require('./Node'); | |
var NodeList = require('./NodeList'); | |
var FilteredElementList = require('./FilteredElementList'); | |
var DOMTokenList = require('./DOMTokenList'); | |
var select = require('./select'); | |
function Element(doc, localName, namespaceURI, prefix) { | |
this.nodeType = Node.ELEMENT_NODE; | |
this.ownerDocument = doc; | |
this.localName = localName; | |
this.namespaceURI = namespaceURI; | |
this.prefix = prefix; | |
this.tagName = (prefix !== null) ? prefix + ':' + localName : localName; | |
if (namespaceURI !== NAMESPACE.HTML || (!namespaceURI && !doc.isHTML)) this.isHTML = false; | |
if (this.isHTML) this.tagName = this.tagName.toUpperCase(); | |
this.childNodes = new NodeList(); | |
// These properties maintain the set of attributes | |
this._attrsByQName = {}; // The qname->Attr map | |
this._attrsByLName = {}; // The ns|lname->Attr map | |
this._attrKeys = []; // attr index -> ns|lname | |
this._index = undefined; | |
} | |
function recursiveGetText(node, a) { | |
if (node.nodeType === Node.TEXT_NODE) { | |
a.push(node._data); | |
} | |
else { | |
for(var i = 0, n = node.childNodes.length; i < n; i++) | |
recursiveGetText(node.childNodes[i], a); | |
} | |
} | |
Element.prototype = Object.create(Node.prototype, { | |
nodeName: { get: function() { return this.tagName; }}, | |
nodeValue: { | |
get: function() { | |
return null; | |
}, | |
set: function() {} | |
}, | |
textContent: { | |
get: function() { | |
var strings = []; | |
recursiveGetText(this, strings); | |
return strings.join(''); | |
}, | |
set: function(newtext) { | |
this.removeChildren(); | |
if (newtext !== null && newtext !== '') { | |
this._appendChild(this.ownerDocument.createTextNode(newtext)); | |
} | |
} | |
}, | |
innerHTML: { | |
get: function() { | |
return this.serialize(); | |
}, | |
set: utils.nyi | |
}, | |
outerHTML: { | |
get: function() { | |
// "the attribute must return the result of running the HTML fragment | |
// serialization algorithm on a fictional node whose only child is | |
// the context object" | |
var fictional = { | |
childNodes: [ this ], | |
nodeType: 0 | |
}; | |
return this.serialize.call(fictional); | |
}, | |
set: utils.nyi | |
}, | |
children: { get: function() { | |
if (!this._children) { | |
this._children = new ChildrenCollection(this); | |
} | |
return this._children; | |
}}, | |
attributes: { get: function() { | |
if (!this._attributes) { | |
this._attributes = new AttributesArray(this); | |
} | |
return this._attributes; | |
}}, | |
firstElementChild: { get: function() { | |
var kids = this.childNodes; | |
for(var i = 0, n = kids.length; i < n; i++) { | |
if (kids[i].nodeType === Node.ELEMENT_NODE) return kids[i]; | |
} | |
return null; | |
}}, | |
lastElementChild: { get: function() { | |
var kids = this.childNodes; | |
for(var i = kids.length-1; i >= 0; i--) { | |
if (kids[i].nodeType === Node.ELEMENT_NODE) return kids[i]; | |
} | |
return null; | |
}}, | |
nextElementSibling: { get: function() { | |
if (this.parentNode) { | |
var sibs = this.parentNode.childNodes; | |
for(var i = this.index+1, n = sibs.length; i < n; i++) { | |
if (sibs[i].nodeType === Node.ELEMENT_NODE) return sibs[i]; | |
} | |
} | |
return null; | |
}}, | |
previousElementSibling: { get: function() { | |
if (this.parentNode) { | |
var sibs = this.parentNode.childNodes; | |
for(var i = this.index-1; i >= 0; i--) { | |
if (sibs[i].nodeType === Node.ELEMENT_NODE) return sibs[i]; | |
} | |
} | |
return null; | |
}}, | |
childElementCount: { get: function() { | |
return this.children.length; | |
}}, | |
// Return the next element, in source order, after this one or | |
// null if there are no more. If root element is specified, | |
// then don't traverse beyond its subtree. | |
// | |
// This is not a DOM method, but is convenient for | |
// lazy traversals of the tree. | |
nextElement: { value: function(root) { | |
if (!root) root = this.ownerDocument.documentElement; | |
var next = this.firstElementChild; | |
if (!next) { | |
// don't use sibling if we're at root | |
if (this===root) return null; | |
next = this.nextElementSibling; | |
} | |
if (next) return next; | |
// If we can't go down or across, then we have to go up | |
// and across to the parent sibling or another ancestor's | |
// sibling. Be careful, though: if we reach the root | |
// element, or if we reach the documentElement, then | |
// the traversal ends. | |
for(var parent = this.parentElement; | |
parent && parent !== root; | |
parent = parent.parentElement) { | |
next = parent.nextElementSibling; | |
if (next) return next; | |
} | |
return null; | |
}}, | |
// XXX: | |
// Tests are currently failing for this function. | |
// Awaiting resolution of: | |
// http://lists.w3.org/Archives/Public/www-dom/2011JulSep/0016.html | |
getElementsByTagName: { value: function getElementsByTagName(lname) { | |
var filter; | |
if (!lname) return new NodeList(); | |
if (lname === '*') | |
filter = function() { return true }; | |
else if (this.isHTML) | |
filter = htmlLocalNameElementFilter(lname); | |
else | |
filter = localNameElementFilter(lname); | |
return new FilteredElementList(this, filter); | |
}}, | |
getElementsByTagNameNS: { value: function getElementsByTagNameNS(ns, lname){ | |
var filter; | |
if (ns === '*' && lname === '*') | |
filter = ftrue; | |
else if (ns === '*') | |
filter = localNameElementFilter(lname); | |
else if (lname === '*') | |
filter = namespaceElementFilter(ns); | |
else | |
filter = namespaceLocalNameElementFilter(ns, lname); | |
return new FilteredElementList(this, filter); | |
}}, | |
getElementsByClassName: { value: function getElementsByClassName(names){ | |
names = names.trim(); | |
if (names === '') { | |
var result = new NodeList(); // Empty node list | |
return result; | |
} | |
names = names.split(/\s+/); // Split on spaces | |
return new FilteredElementList(this, classNamesElementFilter(names)); | |
}}, | |
getElementsByName: { value: function getElementsByName(name) { | |
return new FilteredElementList(this, elementNameFilter(name)); | |
}}, | |
// Overwritten in the constructor if not in the HTML namespace | |
isHTML: { value: true }, | |
// Utility methods used by the public API methods above | |
clone: { value: function clone() { | |
var e; | |
// XXX: | |
// Modify this to use the constructor directly or | |
// avoid error checking in some other way. In case we try | |
// to clone an invalid node that the parser inserted. | |
// | |
if (this.namespaceURI !== NAMESPACE.HTML || this.prefix) | |
e = this.ownerDocument.createElementNS(this.namespaceURI, | |
this.tagName); | |
else | |
e = this.ownerDocument.createElement(this.localName); | |
for(var i = 0, n = this._attrKeys.length; i < n; i++) { | |
var lname = this._attrKeys[i]; | |
var a = this._attrsByLName[lname]; | |
var b = new Attr(e, a.localName, a.prefix, a.namespaceURI); | |
b.data = a.data; | |
e._attrsByLName[lname] = b; | |
e._addQName(b); | |
} | |
e._attrKeys = this._attrKeys.concat(); | |
return e; | |
}}, | |
isEqual: { value: function isEqual(that) { | |
if (this.localName !== that.localName || | |
this.namespaceURI !== that.namespaceURI || | |
this.prefix !== that.prefix || | |
this._numattrs !== that._numattrs) | |
return false; | |
// Compare the sets of attributes, ignoring order | |
// and ignoring attribute prefixes. | |
for(var i = 0, n = this._numattrs; i < n; i++) { | |
var a = this._attr(i); | |
if (!that.hasAttributeNS(a.namespaceURI, a.localName)) | |
return false; | |
if (that.getAttributeNS(a.namespaceURI,a.localName) !== a.value) | |
return false; | |
} | |
return true; | |
}}, | |
// This is the 'locate a namespace prefix' algorithm from the | |
// DOMCore specification. It is used by Node.lookupPrefix() | |
locateNamespacePrefix: { value: function locateNamespacePrefix(ns) { | |
if (this.namespaceURI === ns && this.prefix !== null) | |
return this.prefix; | |
for(var i = 0, n = this._numattrs; i < n; i++) { | |
var a = this._attr(i); | |
if (a.prefix === 'xmlns' && a.value === ns) | |
return a.localName; | |
} | |
var parent = this.parentElement; | |
return parent ? parent.locateNamespacePrefix(ns) : null; | |
}}, | |
// This is the 'locate a namespace' algorithm for Element nodes | |
// from the DOM Core spec. It is used by Node.lookupNamespaceURI | |
locateNamespace: { value: function locateNamespace(prefix) { | |
if (this.prefix === prefix && this.namespaceURI !== null) | |
return this.namespaceURI; | |
for(var i = 0, n = this._numattrs; i < n; i++) { | |
var a = this._attr(i); | |
if ((a.prefix === 'xmlns' && a.localName === prefix) || | |
(a.prefix === null && a.localName === 'xmlns')) { | |
return a.value || null; | |
} | |
} | |
var parent = this.parentElement; | |
return parent ? parent.locateNamespace(prefix) : null; | |
}}, | |
// | |
// Attribute handling methods and utilities | |
// | |
/* | |
* Attributes in the DOM are tricky: | |
* | |
* - there are the 8 basic get/set/has/removeAttribute{NS} methods | |
* | |
* - but many HTML attributes are also 'reflected' through IDL | |
* attributes which means that they can be queried and set through | |
* regular properties of the element. There is just one attribute | |
* value, but two ways to get and set it. | |
* | |
* - Different HTML element types have different sets of reflected | |
attributes. | |
* | |
* - attributes can also be queried and set through the .attributes | |
* property of an element. This property behaves like an array of | |
* Attr objects. The value property of each Attr is writeable, so | |
* this is a third way to read and write attributes. | |
* | |
* - for efficiency, we really want to store attributes in some kind | |
* of name->attr map. But the attributes[] array is an array, not a | |
* map, which is kind of unnatural. | |
* | |
* - When using namespaces and prefixes, and mixing the NS methods | |
* with the non-NS methods, it is apparently actually possible for | |
* an attributes[] array to have more than one attribute with the | |
* same qualified name. And certain methods must operate on only | |
* the first attribute with such a name. So for these methods, an | |
* inefficient array-like data structure would be easier to | |
* implement. | |
* | |
* - The attributes[] array is live, not a snapshot, so changes to the | |
* attributes must be immediately visible through existing arrays. | |
* | |
* - When attributes are queried and set through IDL properties | |
* (instead of the get/setAttributes() method or the attributes[] | |
* array) they may be subject to type conversions, URL | |
* normalization, etc., so some extra processing is required in that | |
* case. | |
* | |
* - But access through IDL properties is probably the most common | |
* case, so we'd like that to be as fast as possible. | |
* | |
* - We can't just store attribute values in their parsed idl form, | |
* because setAttribute() has to return whatever string is passed to | |
* getAttribute even if it is not a legal, parseable value. So | |
* attribute values must be stored in unparsed string form. | |
* | |
* - We need to be able to send change notifications or mutation | |
* events of some sort to the renderer whenever an attribute value | |
* changes, regardless of the way in which it changes. | |
* | |
* - Some attributes, such as id and class affect other parts of the | |
* DOM API, like getElementById and getElementsByClassName and so | |
* for efficiency, we need to specially track changes to these | |
* special attributes. | |
* | |
* - Some attributes like class have different names (className) when | |
* reflected. | |
* | |
* - Attributes whose names begin with the string 'data-' are treated | |
specially. | |
* | |
* - Reflected attributes that have a boolean type in IDL have special | |
* behavior: setting them to false (in IDL) is the same as removing | |
* them with removeAttribute() | |
* | |
* - numeric attributes (like HTMLElement.tabIndex) can have default | |
* values that must be returned by the idl getter even if the | |
* content attribute does not exist. (The default tabIndex value | |
* actually varies based on the type of the element, so that is a | |
* tricky one). | |
* | |
* See | |
* http://www.whatwg.org/specs/web-apps/current-work/multipage/urls.html#reflect | |
* for rules on how attributes are reflected. | |
* | |
*/ | |
getAttribute: { value: function getAttribute(qname) { | |
if (this.isHTML) qname = qname.toLowerCase(); | |
var attr = this._attrsByQName[qname]; | |
if (!attr) return null; | |
if (Array.isArray(attr)) // If there is more than one | |
attr = attr[0]; // use the first | |
return attr.value; | |
}}, | |
getAttributeNS: { value: function getAttributeNS(ns, lname) { | |
var attr = this._attrsByLName[ns + '|' + lname]; | |
return attr ? attr.value : null; | |
}}, | |
hasAttribute: { value: function hasAttribute(qname) { | |
if (this.isHTML) qname = qname.toLowerCase(); | |
return qname in this._attrsByQName; | |
}}, | |
hasAttributeNS: { value: function hasAttributeNS(ns, lname) { | |
var key = ns + '|' + lname; | |
return key in this._attrsByLName; | |
}}, | |
// Set the attribute without error checking. The parser uses this. | |
_setAttribute: { value: function _setAttribute(qname, value) { | |
// XXX: the spec says that this next search should be done | |
// on the local name, but I think that is an error. | |
// email pending on www-dom about it. | |
var attr = this._attrsByQName[qname]; | |
var isnew; | |
if (!attr) { | |
attr = this._newattr(qname); | |
isnew = true; | |
} | |
else { | |
if (Array.isArray(attr)) attr = attr[0]; | |
} | |
// Now set the attribute value on the new or existing Attr object. | |
// The Attr.value setter method handles mutation events, etc. | |
attr.value = value; | |
if (this._attributes) this._attributes[qname] = attr; | |
if (isnew && this._newattrhook) this._newattrhook(qname, value); | |
}}, | |
// Check for errors, and then set the attribute | |
setAttribute: {value: function setAttribute(qname, value) { | |
if (!xml.isValidName(qname)) utils.InvalidCharacterError(); | |
if (this.isHTML) qname = qname.toLowerCase(); | |
if (qname.substring(0, 5) === 'xmlns') utils.NamespaceError(); | |
this._setAttribute(qname, value); | |
}}, | |
// The version with no error checking used by the parser | |
_setAttributeNS: { value: function _setAttributeNS(ns, qname, value) { | |
var pos = qname.indexOf(':'), prefix, lname; | |
if (pos === -1) { | |
prefix = null; | |
lname = qname; | |
} | |
else { | |
prefix = qname.substring(0, pos); | |
lname = qname.substring(pos+1); | |
} | |
var key = ns + '|' + lname; | |
if (ns === '') ns = null; | |
var attr = this._attrsByLName[key]; | |
var isnew; | |
if (!attr) { | |
attr = new Attr(this, lname, prefix, ns); | |
isnew = true; | |
this._attrsByLName[key] = attr; | |
this._attrKeys.push(key); | |
// We also have to make the attr searchable by qname. | |
// But we have to be careful because there may already | |
// be an attr with this qname. | |
this._addQName(attr); | |
} | |
else { | |
// Calling setAttributeNS() can change the prefix of an | |
// existing attribute! | |
if (attr.prefix !== prefix) { | |
// Unbind the old qname | |
this._removeQName(attr); | |
// Update the prefix | |
attr.prefix = prefix; | |
// Bind the new qname | |
this._addQName(attr); | |
} | |
} | |
attr.value = value; // Automatically sends mutation event | |
if (isnew && this._newattrhook) this._newattrhook(qname, value); | |
}}, | |
// Do error checking then call _setAttributeNS | |
setAttributeNS: { value: function setAttributeNS(ns, qname, value) { | |
if (!xml.isValidName(qname)) utils.InvalidCharacterError(); | |
if (!xml.isValidQName(qname)) utils.NamespaceError(); | |
var pos = qname.indexOf(':'); | |
var prefix = (pos === -1) ? null : qname.substring(0, pos); | |
if (ns === '') ns = null; | |
if ((prefix !== null && ns === null) || | |
(prefix === 'xml' && ns !== NAMESPACE.XML) || | |
((qname === 'xmlns' || prefix === 'xmlns') && | |
(ns !== NAMESPACE.XMLNS)) || | |
(ns === NAMESPACE.XMLNS && | |
!(qname === 'xmlns' || prefix === 'xmlns'))) | |
utils.NamespaceError(); | |
this._setAttributeNS(ns, qname, value); | |
}}, | |
removeAttribute: { value: function removeAttribute(qname) { | |
if (this.isHTML) qname = qname.toLowerCase(); | |
var attr = this._attrsByQName[qname]; | |
if (!attr) return; | |
// If there is more than one match for this qname | |
// so don't delete the qname mapping, just remove the first | |
// element from it. | |
if (Array.isArray(attr)) { | |
if (attr.length > 2) { | |
attr = attr.shift(); // remove it from the array | |
} | |
else { | |
this._attrsByQName[qname] = attr[1]; | |
attr = attr[0]; | |
} | |
} | |
else { | |
// only a single match, so remove the qname mapping | |
this._attrsByQName[qname] = undefined; | |
} | |
// Now attr is the removed attribute. Figure out its | |
// ns+lname key and remove it from the other mapping as well. | |
var key = (attr.namespaceURI || '') + '|' + attr.localName; | |
this._attrsByLName[key] = undefined; | |
var i = this._attrKeys.indexOf(key); | |
this._attrKeys.splice(i, 1); | |
if (this._attributes) | |
this._attributes[qname] = undefined | |
// Onchange handler for the attribute | |
if (attr.onchange) | |
attr.onchange(this, attr.localName, attr.value, null); | |
// Mutation event | |
if (this.rooted) this.ownerDocument.mutateRemoveAttr(attr); | |
}}, | |
removeAttributeNS: { value: function removeAttributeNS(ns, lname) { | |
var key = (ns || '') + '|' + lname; | |
var attr = this._attrsByLName[key]; | |
if (!attr) return; | |
this._attrsByLName[key] = undefined; | |
var i = this._attrKeys.indexOf(key); | |
this._attrKeys.splice(i, 1); | |
// Now find the same Attr object in the qname mapping and remove it | |
// But be careful because there may be more than one match. | |
this._removeQName(attr); | |
// Onchange handler for the attribute | |
if (attr.onchange) | |
attr.onchange(this, attr.localName, attr.value, null); | |
// Mutation event | |
if (this.rooted) this.ownerDocument.mutateRemoveAttr(attr); | |
}}, | |
// This 'raw' version of getAttribute is used by the getter functions | |
// of reflected attributes. It skips some error checking and | |
// namespace steps | |
_getattr: { value: function _getattr(qname) { | |
// Assume that qname is already lowercased, so don't do it here. | |
// Also don't check whether attr is an array: a qname with no | |
// prefix will never have two matching Attr objects (because | |
// setAttributeNS doesn't allow a non-null namespace with a | |
// null prefix. | |
var attr = this._attrsByQName[qname]; | |
return attr ? attr.value : null; | |
}}, | |
// The raw version of setAttribute for reflected idl attributes. | |
_setattr: { value: function _setattr(qname, value) { | |
var attr = this._attrsByQName[qname]; | |
var isnew; | |
if (!attr) { | |
attr = this._newattr(qname); | |
isnew = true; | |
} | |
attr.value = value; | |
if (this._attributes) this._attributes[qname] = attr; | |
if (isnew && this._newattrhook) this._newattrhook(qname, value); | |
}}, | |
// Create a new Attr object, insert it, and return it. | |
// Used by setAttribute() and by set() | |
_newattr: { value: function _newattr(qname) { | |
var attr = new Attr(this, qname); | |
var key = '|' + qname; | |
this._attrsByQName[qname] = attr; | |
this._attrsByLName[key] = attr; | |
this._attrKeys.push(key); | |
return attr; | |
}}, | |
// Add a qname->Attr mapping to the _attrsByQName object, taking into | |
// account that there may be more than one attr object with the | |
// same qname | |
_addQName: { value: function(attr) { | |
var qname = attr.name; | |
var existing = this._attrsByQName[qname]; | |
if (!existing) { | |
this._attrsByQName[qname] = attr; | |
} | |
else if (Array.isArray(existing)) { | |
push(existing, attr); | |
} | |
else { | |
this._attrsByQName[qname] = [existing, attr]; | |
} | |
if (this._attributes) this._attributes[qname] = attr; | |
}}, | |
// Remove a qname->Attr mapping to the _attrsByQName object, taking into | |
// account that there may be more than one attr object with the | |
// same qname | |
_removeQName: { value: function(attr) { | |
var qname = attr.name; | |
var target = this._attrsByQName[qname]; | |
if (Array.isArray(target)) { | |
var idx = target.indexOf(attr); | |
assert(idx !== -1); // It must be here somewhere | |
if (target.length === 2) { | |
this._attrsByQName[qname] = target[1-idx]; | |
} | |
else { | |
target.splice(idx, 1); | |
} | |
} | |
else { | |
assert(target === attr); // If only one, it must match | |
this._attrsByQName[qname] = undefined; | |
} | |
}}, | |
// Return the number of attributes | |
_numattrs: { get: function() { return this._attrKeys.length; }}, | |
// Return the nth Attr object | |
_attr: { value: function(n) { | |
return this._attrsByLName[this._attrKeys[n]]; | |
}}, | |
// Define getters and setters for an 'id' property that reflects | |
// the content attribute 'id'. | |
id: attributes.property({name: 'id'}), | |
// Define getters and setters for a 'className' property that reflects | |
// the content attribute 'class'. | |
className: attributes.property({name: 'class'}), | |
classList: { get: function() { | |
var self = this; | |
if (this._classList) { | |
return this._classList; | |
} | |
var dtlist = new DOMTokenList( | |
function() { | |
return self.className || ""; | |
}, | |
function(v) { | |
self.className = v; | |
} | |
); | |
this._classList = dtlist; | |
return dtlist; | |
}}, | |
querySelector: { value: function(selector) { | |
return select(selector, this)[0]; | |
}}, | |
querySelectorAll: { value: function(selector) { | |
var nodes = select(selector, this); | |
return nodes.item ? nodes : new NodeList(nodes); | |
}} | |
}); | |
// Register special handling for the id attribute | |
attributes.registerChangeHandler(Element, 'id', | |
function(element, lname, oldval, newval) { | |
if (element.rooted) { | |
if (oldval) { | |
element.ownerDocument.delId(oldval, element); | |
} | |
if (newval) { | |
element.ownerDocument.addId(newval, element); | |
} | |
} | |
} | |
); | |
// The Attr class represents a single attribute. The values in | |
// _attrsByQName and _attrsByLName are instances of this class. | |
function Attr(elt, lname, prefix, namespace) { | |
// Always remember what element we're associated with. | |
// We need this to property handle mutations | |
this.ownerElement = elt; | |
if (!namespace && !prefix && elt._attributeChangeHandlers[lname]) | |
this.onchange = elt._attributeChangeHandlers[lname]; | |
// localName and namespace are constant for any attr object. | |
// But value may change. And so can prefix, and so, therefore can name. | |
this.localName = lname; | |
this.prefix = prefix || null; | |
this.namespaceURI = namespace || null; | |
} | |
Attr.prototype = { | |
get name() { | |
return this.prefix ? this.prefix + ':' + this.localName : this.localName; | |
}, | |
get value() { | |
return this.data; | |
}, | |
get specified() { | |
// Deprecated | |
return true; | |
}, | |
set value(value) { | |
var oldval = this.data; | |
value = (value === undefined) ? '' : value + ''; | |
if (value === oldval) return; | |
this.data = value; | |
// Run the onchange hook for the attribute | |
// if there is one. | |
if (this.onchange) | |
this.onchange(this.ownerElement,this.localName, oldval, value); | |
// Generate a mutation event if the element is rooted | |
if (this.ownerElement.rooted) | |
this.ownerElement.ownerDocument.mutateAttr(this, oldval); | |
} | |
}; | |
// The attributes property of an Element will be an instance of this class. | |
// This class is really just a dummy, though. It only defines a length | |
// property and an item() method. The AttrArrayProxy that | |
// defines the public API just uses the Element object itself. | |
function AttributesArray(elt) { | |
this.element = elt; | |
for (var name in elt._attrsByQName) { | |
this[name] = elt._attrsByQName[name] | |
} | |
} | |
AttributesArray.prototype = { | |
get length() { | |
return this.element._attrKeys.length; | |
}, | |
item: function(n) { | |
return this.element._attrsByLName[this.element._attrKeys[n]]; | |
} | |
}; | |
// The children property of an Element will be an instance of this class. | |
// It defines length, item() and namedItem() and will be wrapped by an | |
// HTMLCollection when exposed through the DOM. | |
function ChildrenCollection(e) { | |
this.element = e; | |
this.updateCache(); | |
} | |
ChildrenCollection.prototype = { | |
get length() { | |
this.updateCache(); | |
return this.childrenByNumber.length; | |
}, | |
item: function item(n) { | |
this.updateCache(); | |
return this.childrenByNumber[n] || null; | |
}, | |
namedItem: function namedItem(name) { | |
this.updateCache(); | |
return this.childrenByName[name] || null; | |
}, | |
// This attribute returns the entire name->element map. | |
// It is not part of the HTMLCollection API, but we need it in | |
// src/HTMLCollectionProxy | |
get namedItems() { | |
this.updateCache(); | |
return this.childrenByName; | |
}, | |
updateCache: function updateCache() { | |
var namedElts = /^(a|applet|area|embed|form|frame|frameset|iframe|img|object)$/; | |
if (this.lastModTime !== this.element.lastModTime) { | |
this.lastModTime = this.element.lastModTime; | |
var n = this.childrenByNumber && this.childrenByNumber.length || 0; | |
for(var i = 0; i < n; i++) { | |
this[i] = undefined; | |
} | |
this.childrenByNumber = []; | |
this.childrenByName = {}; | |
for(i = 0, n = this.element.childNodes.length; i < n; i++) { | |
var c = this.element.childNodes[i]; | |
if (c.nodeType == Node.ELEMENT_NODE) { | |
this[this.childrenByNumber.length] = c; | |
this.childrenByNumber.push(c); | |
// XXX Are there any requirements about the namespace | |
// of the id property? | |
var id = c.getAttribute('id'); | |
// If there is an id that is not already in use... | |
if (id && !this.childrenByName[id]) | |
this.childrenByName[id] = c; | |
// For certain HTML elements we check the name attribute | |
var name = c.getAttribute('name'); | |
if (name && | |
this.element.namespaceURI === NAMESPACE.HTML && | |
namedElts.test(this.element.localName) && | |
!this.childrenByName[name]) | |
this.childrenByName[id] = c; | |
} | |
} | |
} | |
} | |
}; | |
// These functions return predicates for filtering elements. | |
// They're used by the Document and Element classes for methods like | |
// getElementsByTagName and getElementsByClassName | |
function localNameElementFilter(lname) { | |
return function(e) { return e.localName === lname; }; | |
} | |
function htmlLocalNameElementFilter(lname) { | |
var lclname = lname.toLowerCase(); | |
if (lclname === lname) | |
return localNameElementFilter(lname); | |
return function(e) { | |
return e.isHTML ? e.localName === lclname : e.localName === lname; | |
}; | |
} | |
function namespaceElementFilter(ns) { | |
return function(e) { return e.namespaceURI === ns; }; | |
} | |
function namespaceLocalNameElementFilter(ns, lname) { | |
return function(e) { | |
return e.namespaceURI === ns && e.localName === lname; | |
}; | |
} | |
// XXX | |
// Optimize this when I implement classList. | |
function classNamesElementFilter(names) { | |
return function(e) { | |
var classAttr = e.getAttribute('class'); | |
if (!classAttr) return false; | |
var classes = classAttr.trim().split(/\s+/); | |
return names.every(function(n) { | |
return classes.indexOf(n) !== -1; | |
}); | |
}; | |
} | |
function elementNameFilter(name) { | |
return function(e) { | |
return e.getAttribute('name') === name; | |
}; | |
} | |
},{"./DOMTokenList":7,"./FilteredElementList":14,"./Node":20,"./NodeList":22,"./attributes":30,"./select":36,"./utils":37,"./xmlnames":38}],12:[function(require,module,exports){ | |
module.exports = Event; | |
Event.CAPTURING_PHASE = 1; | |
Event.AT_TARGET = 2; | |
Event.BUBBLING_PHASE = 3; | |
function Event(type, dictionary) { | |
// Initialize basic event properties | |
this.type = ''; | |
this.target = null; | |
this.currentTarget = null; | |
this.eventPhase = Event.AT_TARGET; | |
this.bubbles = false; | |
this.cancelable = false; | |
this.isTrusted = false; | |
this.defaultPrevented = false; | |
this.timeStamp = Date.now(); | |
// Initialize internal flags | |
// XXX: Would it be better to inherit these defaults from the prototype? | |
this._propagationStopped = false; | |
this._immediatePropagationStopped = false; | |
this._initialized = true; | |
this._dispatching = false; | |
// Now initialize based on the constructor arguments (if any) | |
if (type) this.type = type; | |
if (dictionary) { | |
for(var p in dictionary) { | |
this[p] = dictionary[p]; | |
} | |
} | |
} | |
Event.prototype = Object.create(Object.prototype, { | |
constructor: { value: Event }, | |
stopPropagation: { value: function stopPropagation() { | |
this._propagationStopped = true; | |
}}, | |
stopImmediatePropagation: { value: function stopImmediatePropagation() { | |
this._propagationStopped = true; | |
this._immediatePropagationStopped = true; | |
}}, | |
preventDefault: { value: function preventDefault() { | |
if (this.cancelable) this.defaultPrevented = true; | |
}}, | |
initEvent: { value: function initEvent(type, bubbles, cancelable) { | |
this._initialized = true; | |
if (this._dispatching) return; | |
this._propagationStopped = false; | |
this._immediatePropagationStopped = false; | |
this.defaultPrevented = false; | |
this.isTrusted = false; | |
this.target = null; | |
this.type = type; | |
this.bubbles = bubbles; | |
this.cancelable = cancelable; | |
}}, | |
}); | |
},{}],13:[function(require,module,exports){ | |
var Event = require('./Event'); | |
var MouseEvent = require('./MouseEvent'); | |
var utils = require('./utils'); | |
module.exports = EventTarget; | |
function EventTarget() {} | |
EventTarget.prototype = { | |
// XXX | |
// See WebIDL §4.8 for details on object event handlers | |
// and how they should behave. We actually have to accept | |
// any object to addEventListener... Can't type check it. | |
// on registration. | |
// XXX: | |
// Capturing event listeners are sort of rare. I think I can optimize | |
// them so that dispatchEvent can skip the capturing phase (or much of | |
// it). Each time a capturing listener is added, increment a flag on | |
// the target node and each of its ancestors. Decrement when removed. | |
// And update the counter when nodes are added and removed from the | |
// tree as well. Then, in dispatch event, the capturing phase can | |
// abort if it sees any node with a zero count. | |
addEventListener: function addEventListener(type, listener, capture) { | |
if (!listener) return; | |
if (capture === undefined) capture = false; | |
if (!this._listeners) this._listeners = {}; | |
if (!this._listeners[type]) this._listeners[type] = []; | |
var list = this._listeners[type]; | |
// If this listener has already been registered, just return | |
for(var i = 0, n = list.length; i < n; i++) { | |
var l = list[i]; | |
if (l.listener === listener && l.capture === capture) | |
return; | |
} | |
// Add an object to the list of listeners | |
var obj = { listener: listener, capture: capture }; | |
if (typeof listener === 'function') obj.f = listener; | |
list.push(obj); | |
}, | |
removeEventListener: function removeEventListener(type, | |
listener, | |
capture) { | |
if (capture === undefined) capture = false; | |
if (this._listeners) { | |
var list = this._listeners[type]; | |
if (list) { | |
// Find the listener in the list and remove it | |
for(var i = 0, n = list.length; i < n; i++) { | |
var l = list[i]; | |
if (l.listener === listener && l.capture === capture) { | |
if (list.length === 1) { | |
this._listeners[type] = undefined; | |
} | |
else { | |
list.splice(i, 1); | |
} | |
return; | |
} | |
} | |
} | |
} | |
}, | |
// This is the public API for dispatching untrusted public events. | |
// See _dispatchEvent for the implementation | |
dispatchEvent: function dispatchEvent(event) { | |
// Dispatch an untrusted event | |
return this._dispatchEvent(event, false); | |
}, | |
// | |
// See DOMCore §4.4 | |
// XXX: I'll probably need another version of this method for | |
// internal use, one that does not set isTrusted to false. | |
// XXX: see Document._dispatchEvent: perhaps that and this could | |
// call a common internal function with different settings of | |
// a trusted boolean argument | |
// | |
// XXX: | |
// The spec has changed in how to deal with handlers registered | |
// on idl or content attributes rather than with addEventListener. | |
// Used to say that they always ran first. That's how webkit does it | |
// Spec now says that they run in a position determined by | |
// when they were first set. FF does it that way. See: | |
// http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#event-handlers | |
// | |
_dispatchEvent: function _dispatchEvent(event, trusted) { | |
if (typeof trusted !== 'boolean') trusted = false; | |
function invoke(target, event) { | |
var type = event.type, phase = event.eventPhase; | |
event.currentTarget = target; | |
// If there was an individual handler defined, invoke it first | |
// XXX: see comment above: this shouldn't always be first. | |
if (phase !== Event.CAPTURING_PHASE && | |
target._handlers && target._handlers[type]) | |
{ | |
var handler = target._handlers[type]; | |
var rv; | |
if (typeof handler === 'function') { | |
rv=handler.call(event.currentTarget, event); | |
} | |
else { | |
var f = handler.handleEvent; | |
if (typeof f !== 'function') | |
throw TypeError('handleEvent property of ' + | |
'event handler object is' + | |
'not a function.'); | |
rv=f.call(handler, event); | |
} | |
switch(event.type) { | |
case 'mouseover': | |
if (rv === true) // Historical baggage | |
event.preventDefault(); | |
break; | |
case 'beforeunload': | |
// XXX: eventually we need a special case here | |
default: | |
if (rv === false) | |
event.preventDefault(); | |
break; | |
} | |
} | |
// Now invoke list list of listeners for this target and type | |
var list = target._listeners && target._listeners[type]; | |
if (!list) return; | |
list = list.slice(); | |
for(var i = 0, n = list.length; i < n; i++) { | |
if (event._stopImmediatePropagation) return; | |
var l = list[i]; | |
if ((phase === Event.CAPTURING_PHASE && !l.capture) || | |
(phase === Event.BUBBLING_PHASE && l.capture)) | |
continue; | |
if (l.f) { | |
l.f.call(event.currentTarget, event); | |
} | |
else { | |
var fn = l.listener.handleEvent; | |
if (typeof fn !== 'function') | |
throw TypeError('handleEvent property of event listener object is not a function.'); | |
fn.call(l.listener, event); | |
} | |
} | |
} | |
if (!event._initialized || event._dispatching) utils.InvalidStateError(); | |
event.isTrusted = trusted; | |
// Begin dispatching the event now | |
event._dispatching = true; | |
event.target = this; | |
// Build the list of targets for the capturing and bubbling phases | |
// XXX: we'll eventually have to add Window to this list. | |
var ancestors = []; | |
for(var n = this.parentNode; n; n = n.parentNode) | |
ancestors.push(n); | |
// Capturing phase | |
event.eventPhase = Event.CAPTURING_PHASE; | |
for(var i = ancestors.length-1; i >= 0; i--) { | |
invoke(ancestors[i], event); | |
if (event._propagationStopped) break; | |
} | |
// At target phase | |
if (!event._propagationStopped) { | |
event.eventPhase = Event.AT_TARGET; | |
invoke(this, event); | |
} | |
// Bubbling phase | |
if (event.bubbles && !event._propagationStopped) { | |
event.eventPhase = Event.BUBBLING_PHASE; | |
for(var i = 0, n = ancestors.length; i < n; i++) { | |
invoke(ancestors[i], event); | |
if (event._propagationStopped) break; | |
} | |
} | |
event._dispatching = false; | |
event.eventPhase = Event.AT_TARGET; | |
event.currentTarget = null; | |
// Deal with mouse events and figure out when | |
// a click has happened | |
if (trusted && !event.defaultPrevented && event instanceof MouseEvent) { | |
switch(event.type) { | |
case 'mousedown': | |
this._armed = { | |
x: event.clientX, | |
y: event.clientY, | |
t: event.timeStamp | |
}; | |
break; | |
case 'mouseout': | |
case 'mouseover': | |
this._armed = null; | |
break; | |
case 'mouseup': | |
if (this._isClick(event)) this._doClick(event); | |
this._armed = null; | |
break; | |
} | |
} | |
return !event.defaultPrevented; | |
}, | |
// Determine whether a click occurred | |
// XXX We don't support double clicks for now | |
_isClick: function(event) { | |
return (this._armed !== null && | |
event.type === 'mouseup' && | |
event.isTrusted && | |
event.button === 0 && | |
event.timeStamp - this._armed.t < 1000 && | |
Math.abs(event.clientX - this._armed.x) < 10 && | |
Math.abs(event.clientY - this._armed.Y) < 10); | |
}, | |
// Clicks are handled like this: | |
// http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#interactive-content-0 | |
// | |
// Note that this method is similar to the HTMLElement.click() method | |
// The event argument must be the trusted mouseup event | |
_doClick: function(event) { | |
if (this._click_in_progress) return; | |
this._click_in_progress = true; | |
// Find the nearest enclosing element that is activatable | |
// An element is activatable if it has a | |
// _post_click_activation_steps hook | |
var activated = this; | |
while(activated && !activated._post_click_activation_steps) | |
activated = activated.parentNode; | |
if (activated && activated._pre_click_activation_steps) { | |
activated._pre_click_activation_steps(); | |
} | |
var click = this.ownerDocument.createEvent('MouseEvent'); | |
click.initMouseEvent('click', true, true, | |
this.ownerDocument.defaultView, 1, | |
event.screenX, event.screenY, | |
event.clientX, event.clientY, | |
event.ctrlKey, event.altKey, | |
event.shiftKey, event.metaKey, | |
event.button, null); | |
var result = this._dispatchEvent(click, true); | |
if (activated) { | |
if (result) { | |
// This is where hyperlinks get followed, for example. | |
if (activated._post_click_activation_steps) | |
activated._post_click_activation_steps(click); | |
} | |
else { | |
if (activated._cancelled_activation_steps) | |
activated._cancelled_activation_steps(); | |
} | |
} | |
}, | |
// | |
// An event handler is like an event listener, but it registered | |
// by setting an IDL or content attribute like onload or onclick. | |
// There can only be one of these at a time for any event type. | |
// This is an internal method for the attribute accessors and | |
// content attribute handlers that need to register events handlers. | |
// The type argument is the same as in addEventListener(). | |
// The handler argument is the same as listeners in addEventListener: | |
// it can be a function or an object. Pass null to remove any existing | |
// handler. Handlers are always invoked before any listeners of | |
// the same type. They are not invoked during the capturing phase | |
// of event dispatch. | |
// | |
_setEventHandler: function _setEventHandler(type, handler) { | |
if (!this._handlers) this._handlers = {}; | |
this._handlers[type] = handler; | |
}, | |
_getEventHandler: function _getEventHandler(type) { | |
return (this._handlers && this._handlers[type]) || null; | |
} | |
}; | |
},{"./Event":12,"./MouseEvent":18,"./utils":37}],14:[function(require,module,exports){ | |
module.exports = FilteredElementList; | |
var Node = require('./Node'); | |
// | |
// This file defines node list implementation that lazily traverses | |
// the document tree (or a subtree rooted at any element) and includes | |
// only those elements for which a specified filter function returns true. | |
// It is used to implement the | |
// {Document,Element}.getElementsBy{TagName,ClassName}{,NS} methods. | |
// | |
function FilteredElementList(root, filter) { | |
this.root = root; | |
this.filter = filter; | |
this.lastModTime = root.lastModTime; | |
this.done = false; | |
this.cache = []; | |
this.traverse(); | |
} | |
FilteredElementList.prototype = { | |
get length() { | |
this.checkcache(); | |
if (!this.done) this.traverse(); | |
return this.cache.length; | |
}, | |
item: function(n) { | |
this.checkcache(); | |
if (!this.done && n >= this.cache.length) this.traverse(n); | |
return this.cache[n]; | |
}, | |
checkcache: function() { | |
if (this.lastModTime !== this.root.lastModTime) { | |
// subtree has changed, so invalidate cache | |
for (var i = this.cache.length-1; i>=0; i--) { | |
this[i] = undefined; | |
} | |
this.cache.length = 0; | |
this.done = false; | |
this.lastModTime = this.root.lastModTime; | |
} | |
}, | |
// If n is specified, then traverse the tree until we've found the nth | |
// item (or until we've found all items). If n is not specified, | |
// traverse until we've found all items. | |
traverse: function(n) { | |
// increment n so we can compare to length, and so it is never falsy | |
if (n !== undefined) n++; | |
var elt; | |
while(elt = this.next()) { | |
this[this.cache.length] = elt; //XXX Use proxy instead | |
this.cache.push(elt); | |
if (n && this.cache.length === n) return; | |
} | |
// no next element, so we've found everything | |
this.done = true; | |
}, | |
// Return the next element under root that matches filter | |
next: function() { | |
var start = (this.cache.length === 0) ? this.root // Start at the root or at | |
: this.cache[this.cache.length-1]; // the last element we found | |
var elt; | |
if (start.nodeType === Node.DOCUMENT_NODE) | |
elt = start.documentElement; | |
else | |
elt = start.nextElement(this.root); | |
while(elt) { | |
if (this.filter(elt)) { | |
return elt; | |
} | |
elt = elt.nextElement(this.root); | |
} | |
return null; | |
} | |
}; | |
},{"./Node":20}],15:[function(require,module,exports){ | |
module.exports = HTMLParser; | |
var Document = require('./Document'); | |
var DocumentType = require('./DocumentType'); | |
var Node = require('./Node'); | |
var NAMESPACE = require('./utils').NAMESPACE; | |
var html = require('./htmlelts'); | |
var impl = html.elements; | |
var pushAll = Function.prototype.apply.bind(Array.prototype.push); | |
/* | |
* This file contains an implementation of the HTML parsing algorithm. | |
* The algorithm and the implementation are complex because HTML | |
* explicitly defines how the parser should behave for all possible | |
* valid and invalid inputs. | |
* | |
* Usage: | |
* | |
* The file defines a single HTMLParser() function, which dom.js exposes | |
* publicly as document.implementation.mozHTMLParser(). This is a | |
* factory function, not a constructor. | |
* | |
* When you call document.implementation.mozHTMLParser(), it returns | |
* an object that has parse() and document() methods. To parse HTML text, | |
* pass the text (in one or more chunks) to the parse() method. When | |
* you've passed all the text (on the last chunk, or afterward) pass | |
* true as the second argument to parse() to tell the parser that there | |
* is no more coming. Call document() to get the document object that | |
* the parser is parsing into. You can call this at any time, before | |
* or after calling parse(). | |
* | |
* The first argument to mozHTMLParser is the absolute URL of the document. | |
* | |
* The second argument is optional and is for internal use only. Pass an | |
* element as the fragmentContext to do innerHTML parsing for the | |
* element. To do innerHTML parsing on a document, pass null. Otherwise, | |
* omit the 2nd argument. See HTMLElement.innerHTML for an example. Note | |
* that if you pass a context element, the end() method will return an | |
* unwrapped document instead of a wrapped one. | |
* | |
* Implementation details: | |
* | |
* This is a long file of almost 7000 lines. It is structured as one | |
* big function nested within another big function. The outer | |
* function defines a bunch of constant data, utility functions | |
* that use that data, and a couple of classes used by the parser. | |
* The outer function also defines and returns the | |
* inner function. This inner function is the HTMLParser factory | |
* function that implements the parser and holds all the parser state | |
* as local variables. The HTMLParser function is quite big because | |
* it defines many nested functions that use those local variables. | |
* | |
* There are three tightly coupled parser stages: a scanner, a | |
* tokenizer and a tree builder. In a (possibly misguided) attempt at | |
* efficiency, the stages are not implemented as separate classes: | |
* everything shares state and is (mostly) implemented in imperative | |
* (rather than OO) style. | |
* | |
* The stages of the parser work like this: When the client code calls | |
* the parser's parse() method, the specified string is passed to | |
* scanChars(). The scanner loops through that string and passes characters | |
* (sometimes one at a time, sometimes in chunks) to the tokenizer stage. | |
* The tokenizer groups the characters into tokens: tags, endtags, runs | |
* of text, comments, doctype declarations, and the end-of-file (EOF) | |
* token. These tokens are then passed to the tree building stage via | |
* the insertToken() function. The tree building stage builds up the | |
* document tree. | |
* | |
* The tokenizer stage is a finite state machine. Each state is | |
* implemented as a function with a name that ends in "_state". The | |
* initial state is data_state(). The current tokenizer state is stored | |
* in the variable 'tokenizer'. Most state functions expect a single | |
* integer argument which represents a single UTF-16 codepoint. Some | |
* states want more characters and set a lookahead property on | |
* themselves. The scanChars() function in the scanner checks for this | |
* lookahead property. If it doesn't exist, then scanChars() just passes | |
* the next input character to the current tokenizer state function. | |
* Otherwise, scanChars() looks ahead (a given # of characters, or for a | |
* matching string, or for a matching regexp) and passes a string of | |
* characters to the current tokenizer state function. | |
* | |
* As a shortcut, certain states of the tokenizer use regular expressions | |
* to look ahead in the scanner's input buffer for runs of text, simple | |
* tags and attributes. For well-formed input, these shortcuts skip a | |
* lot of state transitions and speed things up a bit. | |
* | |
* When a tokenizer state function has consumed a complete token, it | |
* emits that token, by calling insertToken(), or by calling a utility | |
* function that itself calls insertToken(). These tokens are passed to | |
* the tree building stage, which is also a state machine. Like the | |
* tokenizer, the tree building states are implemented as functions, and | |
* these functions have names that end with _mode (because the HTML spec | |
* refers to them as insertion modes). The current insertion mode is held | |
* by the 'parser' variable. Each insertion mode function takes up to 4 | |
* arguments. The first is a token type, represented by the constants | |
* TAG, ENDTAG, TEXT, COMMENT, DOCTYPE and EOF. The second argument is | |
* the value of the token: the text or comment data, or tagname or | |
* doctype. For tags, the 3rd argument is an array of attributes. For | |
* DOCTYPES it is the optional public id. For tags, the 4th argument is | |
* true if the tag is self-closing. For doctypes, the 4th argument is the | |
* optional system id. | |
* | |
* Search for "***" to find the major sub-divisions in the code. | |
*/ | |
/*** | |
* Data prolog. Lots of constants declared here, including some | |
* very large objects. They're used throughout the code that follows | |
*/ | |
// Token types for the tree builder. | |
var EOF = -1; | |
var TEXT = 1; | |
var TAG = 2; | |
var ENDTAG = 3; | |
var COMMENT = 4; | |
var DOCTYPE = 5; | |
// A re-usable empty array | |
var NOATTRS = []; | |
// These DTD public ids put the browser in quirks mode | |
var quirkyPublicIds = /^HTML$|^-\/\/W3O\/\/DTD W3 HTML Strict 3\.0\/\/EN\/\/$|^-\/W3C\/DTD HTML 4\.0 Transitional\/EN$|^\+\/\/Silmaril\/\/dtd html Pro v0r11 19970101\/\/|^-\/\/AdvaSoft Ltd\/\/DTD HTML 3\.0 asWedit \+ extensions\/\/|^-\/\/AS\/\/DTD HTML 3\.0 asWedit \+ extensions\/\/|^-\/\/IETF\/\/DTD HTML 2\.0 Level 1\/\/|^-\/\/IETF\/\/DTD HTML 2\.0 Level 2\/\/|^-\/\/IETF\/\/DTD HTML 2\.0 Strict Level 1\/\/|^-\/\/IETF\/\/DTD HTML 2\.0 Strict Level 2\/\/|^-\/\/IETF\/\/DTD HTML 2\.0 Strict\/\/|^-\/\/IETF\/\/DTD HTML 2\.0\/\/|^-\/\/IETF\/\/DTD HTML 2\.1E\/\/|^-\/\/IETF\/\/DTD HTML 3\.0\/\/|^-\/\/IETF\/\/DTD HTML 3\.2 Final\/\/|^-\/\/IETF\/\/DTD HTML 3\.2\/\/|^-\/\/IETF\/\/DTD HTML 3\/\/|^-\/\/IETF\/\/DTD HTML Level 0\/\/|^-\/\/IETF\/\/DTD HTML Level 1\/\/|^-\/\/IETF\/\/DTD HTML Level 2\/\/|^-\/\/IETF\/\/DTD HTML Level 3\/\/|^-\/\/IETF\/\/DTD HTML Strict Level 0\/\/|^-\/\/IETF\/\/DTD HTML Strict Level 1\/\/|^-\/\/IETF\/\/DTD HTML Strict Level 2\/\/|^-\/\/IETF\/\/DTD HTML Strict Level 3\/\/|^-\/\/IETF\/\/DTD HTML Strict\/\/|^-\/\/IETF\/\/DTD HTML\/\/|^-\/\/Metrius\/\/DTD Metrius Presentational\/\/|^-\/\/Microsoft\/\/DTD Internet Explorer 2\.0 HTML Strict\/\/|^-\/\/Microsoft\/\/DTD Internet Explorer 2\.0 HTML\/\/|^-\/\/Microsoft\/\/DTD Internet Explorer 2\.0 Tables\/\/|^-\/\/Microsoft\/\/DTD Internet Explorer 3\.0 HTML Strict\/\/|^-\/\/Microsoft\/\/DTD Internet Explorer 3\.0 HTML\/\/|^-\/\/Microsoft\/\/DTD Internet Explorer 3\.0 Tables\/\/|^-\/\/Netscape Comm\. Corp\.\/\/DTD HTML\/\/|^-\/\/Netscape Comm\. Corp\.\/\/DTD Strict HTML\/\/|^-\/\/O'Reilly and Associates\/\/DTD HTML 2\.0\/\/|^-\/\/O'Reilly and Associates\/\/DTD HTML Extended 1\.0\/\/|^-\/\/O'Reilly and Associates\/\/DTD HTML Extended Relaxed 1\.0\/\/|^-\/\/SoftQuad Software\/\/DTD HoTMetaL PRO 6\.0::19990601::extensions to HTML 4\.0\/\/|^-\/\/SoftQuad\/\/DTD HoTMetaL PRO 4\.0::19971010::extensions to HTML 4\.0\/\/|^-\/\/Spyglass\/\/DTD HTML 2\.0 Extended\/\/|^-\/\/SQ\/\/DTD HTML 2\.0 HoTMetaL \+ extensions\/\/|^-\/\/Sun Microsystems Corp\.\/\/DTD HotJava HTML\/\/|^-\/\/Sun Microsystems Corp\.\/\/DTD HotJava Strict HTML\/\/|^-\/\/W3C\/\/DTD HTML 3 1995-03-24\/\/|^-\/\/W3C\/\/DTD HTML 3\.2 Draft\/\/|^-\/\/W3C\/\/DTD HTML 3\.2 Final\/\/|^-\/\/W3C\/\/DTD HTML 3\.2\/\/|^-\/\/W3C\/\/DTD HTML 3\.2S Draft\/\/|^-\/\/W3C\/\/DTD HTML 4\.0 Frameset\/\/|^-\/\/W3C\/\/DTD HTML 4\.0 Transitional\/\/|^-\/\/W3C\/\/DTD HTML Experimental 19960712\/\/|^-\/\/W3C\/\/DTD HTML Experimental 970421\/\/|^-\/\/W3C\/\/DTD W3 HTML\/\/|^-\/\/W3O\/\/DTD W3 HTML 3\.0\/\/|^-\/\/WebTechs\/\/DTD Mozilla HTML 2\.0\/\/|^-\/\/WebTechs\/\/DTD Mozilla HTML\/\//i; | |
var quirkySystemId = "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd"; | |
var conditionallyQuirkyPublicIds = /^-\/\/W3C\/\/DTD HTML 4\.01 Frameset\/\/|^-\/\/W3C\/\/DTD HTML 4\.01 Transitional\/\//i; | |
// These DTD public ids put the browser in limited quirks mode | |
var limitedQuirkyPublicIds = /^-\/\/W3C\/\/DTD XHTML 1\.0 Frameset\/\/|^-\/\/W3C\/\/DTD XHTML 1\.0 Transitional\/\//i; | |
// Element sets below. See the isA() function for a way to test | |
// whether an element is a member of a set | |
var specialSet = {}; | |
specialSet[NAMESPACE.HTML] = { | |
"address":true, "applet":true, "area":true, "article":true, | |
"aside":true, "base":true, "basefont":true, "bgsound":true, | |
"blockquote":true, "body":true, "br":true, "button":true, | |
"caption":true, "center":true, "col":true, "colgroup":true, | |
"command":true, "dd":true, "details":true, "dir":true, | |
"div":true, "dl":true, "dt":true, "embed":true, | |
"fieldset":true, "figcaption":true, "figure":true, "footer":true, | |
"form":true, "frame":true, "frameset":true, "h1":true, | |
"h2":true, "h3":true, "h4":true, "h5":true, | |
"h6":true, "head":true, "header":true, "hgroup":true, | |
"hr":true, "html":true, "iframe":true, "img":true, | |
"input":true, "isindex":true, "li":true, "link":true, | |
"listing":true, "marquee":true, "menu":true, "meta":true, | |
"nav":true, "noembed":true, "noframes":true, "noscript":true, | |
"object":true, "ol":true, "p":true, "param":true, | |
"plaintext":true, "pre":true, "script":true, "section":true, | |
"select":true, "style":true, "summary":true, "table":true, | |
"tbody":true, "td":true, "textarea":true, "tfoot":true, | |
"th":true, "thead":true, "title":true, "tr":true, | |
"ul":true, "wbr":true, "xmp":true | |
}; | |
specialSet[NAMESPACE.SVG] = { | |
"foreignObject": true, "desc": true, "title": true | |
}; | |
specialSet[NAMESPACE.MATHML] = { | |
"mi":true, "mo":true, "mn":true, "ms":true, | |
"mtext":true, "annotation-xml":true | |
}; | |
// The set of address, div, and p HTML tags | |
var addressdivpSet = {}; | |
addressdivpSet[NAMESPACE.HTML] = { | |
"address":true, "div":true, "p":true | |
}; | |
var dddtSet = {}; | |
dddtSet[NAMESPACE.HTML] = { | |
"dd":true, "dt":true | |
}; | |
var tablesectionrowSet = {}; | |
tablesectionrowSet[NAMESPACE.HTML] = { | |
"table":true, "thead":true, "tbody":true, "tfoot":true, "tr":true | |
}; | |
var impliedEndTagsSet = {}; | |
impliedEndTagsSet[NAMESPACE.HTML] = { | |
"dd": true, "dt": true, "li": true, "option": true, | |
"optgroup": true, "p": true, "rp": true, "rt": true | |
}; | |
// See http://www.w3.org/TR/html5/forms.html#form-associated-element | |
var formassociatedSet = {}; | |
formassociatedSet[NAMESPACE.HTML] = { | |
"button": true, "fieldset": true, "input": true, "keygen": true, | |
"label": true, "meter": true, "object": true, "output": true, | |
"progress": true, "select": true, "textarea": true | |
}; | |
var inScopeSet = {}; | |
inScopeSet[NAMESPACE.HTML]= { | |
"applet":true, "caption":true, "html":true, "table":true, | |
"td":true, "th":true, "marquee":true, "object":true | |
}; | |
inScopeSet[NAMESPACE.MATHML] = { | |
"mi":true, "mo":true, "mn":true, "ms":true, | |
"mtext":true, "annotation-xml":true | |
}; | |
inScopeSet[NAMESPACE.SVG] = { | |
"foreignObject":true, "desc":true, "title":true | |
}; | |
var inListItemScopeSet = Object.create(inScopeSet); | |
inListItemScopeSet[NAMESPACE.HTML] = | |
Object.create(inScopeSet[NAMESPACE.HTML]); | |
inListItemScopeSet[NAMESPACE.HTML].ol = true; | |
inListItemScopeSet[NAMESPACE.HTML].ul = true; | |
var inButtonScopeSet = Object.create(inScopeSet); | |
inButtonScopeSet[NAMESPACE.HTML] = | |
Object.create(inScopeSet[NAMESPACE.HTML]); | |
inButtonScopeSet[NAMESPACE.HTML].button = true; | |
var inTableScopeSet = {}; | |
inTableScopeSet[NAMESPACE.HTML] = { | |
"html":true, "table":true | |
}; | |
// The set of elements for select scope is the everything *except* these | |
var invertedSelectScopeSet = {}; | |
invertedSelectScopeSet[NAMESPACE.HTML] = { | |
"optgroup":true, "option":true | |
}; | |
var mathmlTextIntegrationPointSet = {}; | |
mathmlTextIntegrationPointSet[NAMESPACE.MATHML] = { | |
mi: true, | |
mo: true, | |
mn: true, | |
ms: true, | |
mtext: true | |
}; | |
var htmlIntegrationPointSet = {}; | |
htmlIntegrationPointSet[NAMESPACE.SVG] = { | |
foreignObject: true, | |
desc: true, | |
title: true | |
}; | |
var foreignAttributes = { | |
"xlink:actuate": NAMESPACE.XLINK, "xlink:arcrole": NAMESPACE.XLINK, | |
"xlink:href": NAMESPACE.XLINK, "xlink:role": NAMESPACE.XLINK, | |
"xlink:show": NAMESPACE.XLINK, "xlink:title": NAMESPACE.XLINK, | |
"xlink:type": NAMESPACE.XLINK, "xml:base": NAMESPACE.XML, | |
"xml:lang": NAMESPACE.XML, "xml:space": NAMESPACE.XML, | |
"xmlns": NAMESPACE.XMLNS, "xmlns:xlink": NAMESPACE.XMLNS | |
}; | |
// Lowercase to mixed case mapping for SVG attributes and tagnames | |
var svgAttrAdjustments = { | |
attributename: "attributeName", attributetype: "attributeType", | |
basefrequency: "baseFrequency", baseprofile: "baseProfile", | |
calcmode: "calcMode", clippathunits: "clipPathUnits", | |
contentscripttype: "contentScriptType", | |
contentstyletype: "contentStyleType", | |
diffuseconstant: "diffuseConstant", | |
edgemode: "edgeMode", | |
externalresourcesrequired: "externalResourcesRequired", | |
filterres: "filterRes", filterunits: "filterUnits", | |
glyphref: "glyphRef", gradienttransform: "gradientTransform", | |
gradientunits: "gradientUnits", kernelmatrix: "kernelMatrix", | |
kernelunitlength: "kernelUnitLength", keypoints: "keyPoints", | |
keysplines: "keySplines", keytimes: "keyTimes", | |
lengthadjust: "lengthAdjust", limitingconeangle: "limitingConeAngle", | |
markerheight: "markerHeight", markerunits: "markerUnits", | |
markerwidth: "markerWidth", maskcontentunits: "maskContentUnits", | |
maskunits: "maskUnits", numoctaves: "numOctaves", | |
pathlength: "pathLength", patterncontentunits: "patternContentUnits", | |
patterntransform: "patternTransform", patternunits: "patternUnits", | |
pointsatx: "pointsAtX", pointsaty: "pointsAtY", | |
pointsatz: "pointsAtZ", preservealpha: "preserveAlpha", | |
preserveaspectratio: "preserveAspectRatio", | |
primitiveunits: "primitiveUnits", refx: "refX", | |
refy: "refY", repeatcount: "repeatCount", | |
repeatdur: "repeatDur", requiredextensions: "requiredExtensions", | |
requiredfeatures: "requiredFeatures", | |
specularconstant: "specularConstant", | |
specularexponent: "specularExponent", spreadmethod: "spreadMethod", | |
startoffset: "startOffset", stddeviation: "stdDeviation", | |
stitchtiles: "stitchTiles", surfacescale: "surfaceScale", | |
systemlanguage: "systemLanguage", tablevalues: "tableValues", | |
targetx: "targetX", targety: "targetY", | |
textlength: "textLength", viewbox: "viewBox", | |
viewtarget: "viewTarget", xchannelselector: "xChannelSelector", | |
ychannelselector: "yChannelSelector", zoomandpan: "zoomAndPan" | |
}; | |
var svgTagNameAdjustments = { | |
altglyph: "altGlyph", altglyphdef: "altGlyphDef", | |
altglyphitem: "altGlyphItem", animatecolor: "animateColor", | |
animatemotion: "animateMotion", animatetransform: "animateTransform", | |
clippath: "clipPath", feblend: "feBlend", | |
fecolormatrix: "feColorMatrix", | |
fecomponenttransfer: "feComponentTransfer", fecomposite: "feComposite", | |
feconvolvematrix: "feConvolveMatrix", | |
fediffuselighting: "feDiffuseLighting", | |
fedisplacementmap: "feDisplacementMap", | |
fedistantlight: "feDistantLight", feflood: "feFlood", | |
fefunca: "feFuncA", fefuncb: "feFuncB", | |
fefuncg: "feFuncG", fefuncr: "feFuncR", | |
fegaussianblur: "feGaussianBlur", feimage: "feImage", | |
femerge: "feMerge", femergenode: "feMergeNode", | |
femorphology: "feMorphology", feoffset: "feOffset", | |
fepointlight: "fePointLight", fespecularlighting: "feSpecularLighting", | |
fespotlight: "feSpotLight", fetile: "feTile", | |
feturbulence: "feTurbulence", foreignobject: "foreignObject", | |
glyphref: "glyphRef", lineargradient: "linearGradient", | |
radialgradient: "radialGradient", textpath: "textPath" | |
}; | |
// Data for parsing numeric and named character references | |
// These next 3 objects are direct translations of tables | |
// in the HTML spec into JavaScript object format | |
var numericCharRefReplacements = { | |
0x00:0xFFFD, 0x80:0x20AC, 0x82:0x201A, 0x83:0x0192, 0x84:0x201E, | |
0x85:0x2026, 0x86:0x2020, 0x87:0x2021, 0x88:0x02C6, 0x89:0x2030, | |
0x8A:0x0160, 0x8B:0x2039, 0x8C:0x0152, 0x8E:0x017D, 0x91:0x2018, | |
0x92:0x2019, 0x93:0x201C, 0x94:0x201D, 0x95:0x2022, 0x96:0x2013, | |
0x97:0x2014, 0x98:0x02DC, 0x99:0x2122, 0x9A:0x0161, 0x9B:0x203A, | |
0x9C:0x0153, 0x9E:0x017E, 0x9F:0x0178 | |
}; | |
// These named character references work even without the semicolon | |
var namedCharRefsNoSemi = { | |
"AElig":0xC6, "AMP":0x26, "Aacute":0xC1, "Acirc":0xC2, | |
"Agrave":0xC0, "Aring":0xC5, "Atilde":0xC3, "Auml":0xC4, | |
"COPY":0xA9, "Ccedil":0xC7, "ETH":0xD0, "Eacute":0xC9, | |
"Ecirc":0xCA, "Egrave":0xC8, "Euml":0xCB, "GT":0x3E, | |
"Iacute":0xCD, "Icirc":0xCE, "Igrave":0xCC, "Iuml":0xCF, | |
"LT":0x3C, "Ntilde":0xD1, "Oacute":0xD3, "Ocirc":0xD4, | |
"Ograve":0xD2, "Oslash":0xD8, "Otilde":0xD5, "Ouml":0xD6, | |
"QUOT":0x22, "REG":0xAE, "THORN":0xDE, "Uacute":0xDA, | |
"Ucirc":0xDB, "Ugrave":0xD9, "Uuml":0xDC, "Yacute":0xDD, | |
"aacute":0xE1, "acirc":0xE2, "acute":0xB4, "aelig":0xE6, | |
"agrave":0xE0, "amp":0x26, "aring":0xE5, "atilde":0xE3, | |
"auml":0xE4, "brvbar":0xA6, "ccedil":0xE7, "cedil":0xB8, | |
"cent":0xA2, "copy":0xA9, "curren":0xA4, "deg":0xB0, | |
"divide":0xF7, "eacute":0xE9, "ecirc":0xEA, "egrave":0xE8, | |
"eth":0xF0, "euml":0xEB, "frac12":0xBD, "frac14":0xBC, | |
"frac34":0xBE, "gt":0x3E, "iacute":0xED, "icirc":0xEE, | |
"iexcl":0xA1, "igrave":0xEC, "iquest":0xBF, "iuml":0xEF, | |
"laquo":0xAB, "lt":0x3C, "macr":0xAF, "micro":0xB5, | |
"middot":0xB7, "nbsp":0xA0, "not":0xAC, "ntilde":0xF1, | |
"oacute":0xF3, "ocirc":0xF4, "ograve":0xF2, "ordf":0xAA, | |
"ordm":0xBA, "oslash":0xF8, "otilde":0xF5, "ouml":0xF6, | |
"para":0xB6, "plusmn":0xB1, "pound":0xA3, "quot":0x22, | |
"raquo":0xBB, "reg":0xAE, "sect":0xA7, "shy":0xAD, | |
"sup1":0xB9, "sup2":0xB2, "sup3":0xB3, "szlig":0xDF, | |
"thorn":0xFE, "times":0xD7, "uacute":0xFA, "ucirc":0xFB, | |
"ugrave":0xF9, "uml":0xA8, "uuml":0xFC, "yacute":0xFD, | |
"yen":0xA5, "yuml":0xFF | |
}; | |
var namedCharRefs = { | |
"AElig;":0xc6, "AMP;":0x26, | |
"Aacute;":0xc1, "Abreve;":0x102, | |
"Acirc;":0xc2, "Acy;":0x410, | |
"Afr;":[0xd835,0xdd04], "Agrave;":0xc0, | |
"Alpha;":0x391, "Amacr;":0x100, | |
"And;":0x2a53, "Aogon;":0x104, | |
"Aopf;":[0xd835,0xdd38], "ApplyFunction;":0x2061, | |
"Aring;":0xc5, "Ascr;":[0xd835,0xdc9c], | |
"Assign;":0x2254, "Atilde;":0xc3, | |
"Auml;":0xc4, "Backslash;":0x2216, | |
"Barv;":0x2ae7, "Barwed;":0x2306, | |
"Bcy;":0x411, "Because;":0x2235, | |
"Bernoullis;":0x212c, "Beta;":0x392, | |
"Bfr;":[0xd835,0xdd05], "Bopf;":[0xd835,0xdd39], | |
"Breve;":0x2d8, "Bscr;":0x212c, | |
"Bumpeq;":0x224e, "CHcy;":0x427, | |
"COPY;":0xa9, "Cacute;":0x106, | |
"Cap;":0x22d2, "CapitalDifferentialD;":0x2145, | |
"Cayleys;":0x212d, "Ccaron;":0x10c, | |
"Ccedil;":0xc7, "Ccirc;":0x108, | |
"Cconint;":0x2230, "Cdot;":0x10a, | |
"Cedilla;":0xb8, "CenterDot;":0xb7, | |
"Cfr;":0x212d, "Chi;":0x3a7, | |
"CircleDot;":0x2299, "CircleMinus;":0x2296, | |
"CirclePlus;":0x2295, "CircleTimes;":0x2297, | |
"ClockwiseContourIntegral;":0x2232, "CloseCurlyDoubleQuote;":0x201d, | |
"CloseCurlyQuote;":0x2019, "Colon;":0x2237, | |
"Colone;":0x2a74, "Congruent;":0x2261, | |
"Conint;":0x222f, "ContourIntegral;":0x222e, | |
"Copf;":0x2102, "Coproduct;":0x2210, | |
"CounterClockwiseContourIntegral;":0x2233, "Cross;":0x2a2f, | |
"Cscr;":[0xd835,0xdc9e], "Cup;":0x22d3, | |
"CupCap;":0x224d, "DD;":0x2145, | |
"DDotrahd;":0x2911, "DJcy;":0x402, | |
"DScy;":0x405, "DZcy;":0x40f, | |
"Dagger;":0x2021, "Darr;":0x21a1, | |
"Dashv;":0x2ae4, "Dcaron;":0x10e, | |
"Dcy;":0x414, "Del;":0x2207, | |
"Delta;":0x394, "Dfr;":[0xd835,0xdd07], | |
"DiacriticalAcute;":0xb4, "DiacriticalDot;":0x2d9, | |
"DiacriticalDoubleAcute;":0x2dd, "DiacriticalGrave;":0x60, | |
"DiacriticalTilde;":0x2dc, "Diamond;":0x22c4, | |
"DifferentialD;":0x2146, "Dopf;":[0xd835,0xdd3b], | |
"Dot;":0xa8, "DotDot;":0x20dc, | |
"DotEqual;":0x2250, "DoubleContourIntegral;":0x222f, | |
"DoubleDot;":0xa8, "DoubleDownArrow;":0x21d3, | |
"DoubleLeftArrow;":0x21d0, "DoubleLeftRightArrow;":0x21d4, | |
"DoubleLeftTee;":0x2ae4, "DoubleLongLeftArrow;":0x27f8, | |
"DoubleLongLeftRightArrow;":0x27fa, "DoubleLongRightArrow;":0x27f9, | |
"DoubleRightArrow;":0x21d2, "DoubleRightTee;":0x22a8, | |
"DoubleUpArrow;":0x21d1, "DoubleUpDownArrow;":0x21d5, | |
"DoubleVerticalBar;":0x2225, "DownArrow;":0x2193, | |
"DownArrowBar;":0x2913, "DownArrowUpArrow;":0x21f5, | |
"DownBreve;":0x311, "DownLeftRightVector;":0x2950, | |
"DownLeftTeeVector;":0x295e, "DownLeftVector;":0x21bd, | |
"DownLeftVectorBar;":0x2956, "DownRightTeeVector;":0x295f, | |
"DownRightVector;":0x21c1, "DownRightVectorBar;":0x2957, | |
"DownTee;":0x22a4, "DownTeeArrow;":0x21a7, | |
"Downarrow;":0x21d3, "Dscr;":[0xd835,0xdc9f], | |
"Dstrok;":0x110, "ENG;":0x14a, | |
"ETH;":0xd0, "Eacute;":0xc9, | |
"Ecaron;":0x11a, "Ecirc;":0xca, | |
"Ecy;":0x42d, "Edot;":0x116, | |
"Efr;":[0xd835,0xdd08], "Egrave;":0xc8, | |
"Element;":0x2208, "Emacr;":0x112, | |
"EmptySmallSquare;":0x25fb, "EmptyVerySmallSquare;":0x25ab, | |
"Eogon;":0x118, "Eopf;":[0xd835,0xdd3c], | |
"Epsilon;":0x395, "Equal;":0x2a75, | |
"EqualTilde;":0x2242, "Equilibrium;":0x21cc, | |
"Escr;":0x2130, "Esim;":0x2a73, | |
"Eta;":0x397, "Euml;":0xcb, | |
"Exists;":0x2203, "ExponentialE;":0x2147, | |
"Fcy;":0x424, "Ffr;":[0xd835,0xdd09], | |
"FilledSmallSquare;":0x25fc, "FilledVerySmallSquare;":0x25aa, | |
"Fopf;":[0xd835,0xdd3d], "ForAll;":0x2200, | |
"Fouriertrf;":0x2131, "Fscr;":0x2131, | |
"GJcy;":0x403, "GT;":0x3e, | |
"Gamma;":0x393, "Gammad;":0x3dc, | |
"Gbreve;":0x11e, "Gcedil;":0x122, | |
"Gcirc;":0x11c, "Gcy;":0x413, | |
"Gdot;":0x120, "Gfr;":[0xd835,0xdd0a], | |
"Gg;":0x22d9, "Gopf;":[0xd835,0xdd3e], | |
"GreaterEqual;":0x2265, "GreaterEqualLess;":0x22db, | |
"GreaterFullEqual;":0x2267, "GreaterGreater;":0x2aa2, | |
"GreaterLess;":0x2277, "GreaterSlantEqual;":0x2a7e, | |
"GreaterTilde;":0x2273, "Gscr;":[0xd835,0xdca2], | |
"Gt;":0x226b, "HARDcy;":0x42a, | |
"Hacek;":0x2c7, "Hat;":0x5e, | |
"Hcirc;":0x124, "Hfr;":0x210c, | |
"HilbertSpace;":0x210b, "Hopf;":0x210d, | |
"HorizontalLine;":0x2500, "Hscr;":0x210b, | |
"Hstrok;":0x126, "HumpDownHump;":0x224e, | |
"HumpEqual;":0x224f, "IEcy;":0x415, | |
"IJlig;":0x132, "IOcy;":0x401, | |
"Iacute;":0xcd, "Icirc;":0xce, | |
"Icy;":0x418, "Idot;":0x130, | |
"Ifr;":0x2111, "Igrave;":0xcc, | |
"Im;":0x2111, "Imacr;":0x12a, | |
"ImaginaryI;":0x2148, "Implies;":0x21d2, | |
"Int;":0x222c, "Integral;":0x222b, | |
"Intersection;":0x22c2, "InvisibleComma;":0x2063, | |
"InvisibleTimes;":0x2062, "Iogon;":0x12e, | |
"Iopf;":[0xd835,0xdd40], "Iota;":0x399, | |
"Iscr;":0x2110, "Itilde;":0x128, | |
"Iukcy;":0x406, "Iuml;":0xcf, | |
"Jcirc;":0x134, "Jcy;":0x419, | |
"Jfr;":[0xd835,0xdd0d], "Jopf;":[0xd835,0xdd41], | |
"Jscr;":[0xd835,0xdca5], "Jsercy;":0x408, | |
"Jukcy;":0x404, "KHcy;":0x425, | |
"KJcy;":0x40c, "Kappa;":0x39a, | |
"Kcedil;":0x136, "Kcy;":0x41a, | |
"Kfr;":[0xd835,0xdd0e], "Kopf;":[0xd835,0xdd42], | |
"Kscr;":[0xd835,0xdca6], "LJcy;":0x409, | |
"LT;":0x3c, "Lacute;":0x139, | |
"Lambda;":0x39b, "Lang;":0x27ea, | |
"Laplacetrf;":0x2112, "Larr;":0x219e, | |
"Lcaron;":0x13d, "Lcedil;":0x13b, | |
"Lcy;":0x41b, "LeftAngleBracket;":0x27e8, | |
"LeftArrow;":0x2190, "LeftArrowBar;":0x21e4, | |
"LeftArrowRightArrow;":0x21c6, "LeftCeiling;":0x2308, | |
"LeftDoubleBracket;":0x27e6, "LeftDownTeeVector;":0x2961, | |
"LeftDownVector;":0x21c3, "LeftDownVectorBar;":0x2959, | |
"LeftFloor;":0x230a, "LeftRightArrow;":0x2194, | |
"LeftRightVector;":0x294e, "LeftTee;":0x22a3, | |
"LeftTeeArrow;":0x21a4, "LeftTeeVector;":0x295a, | |
"LeftTriangle;":0x22b2, "LeftTriangleBar;":0x29cf, | |
"LeftTriangleEqual;":0x22b4, "LeftUpDownVector;":0x2951, | |
"LeftUpTeeVector;":0x2960, "LeftUpVector;":0x21bf, | |
"LeftUpVectorBar;":0x2958, "LeftVector;":0x21bc, | |
"LeftVectorBar;":0x2952, "Leftarrow;":0x21d0, | |
"Leftrightarrow;":0x21d4, "LessEqualGreater;":0x22da, | |
"LessFullEqual;":0x2266, "LessGreater;":0x2276, | |
"LessLess;":0x2aa1, "LessSlantEqual;":0x2a7d, | |
"LessTilde;":0x2272, "Lfr;":[0xd835,0xdd0f], | |
"Ll;":0x22d8, "Lleftarrow;":0x21da, | |
"Lmidot;":0x13f, "LongLeftArrow;":0x27f5, | |
"LongLeftRightArrow;":0x27f7, "LongRightArrow;":0x27f6, | |
"Longleftarrow;":0x27f8, "Longleftrightarrow;":0x27fa, | |
"Longrightarrow;":0x27f9, "Lopf;":[0xd835,0xdd43], | |
"LowerLeftArrow;":0x2199, "LowerRightArrow;":0x2198, | |
"Lscr;":0x2112, "Lsh;":0x21b0, | |
"Lstrok;":0x141, "Lt;":0x226a, | |
"Map;":0x2905, "Mcy;":0x41c, | |
"MediumSpace;":0x205f, "Mellintrf;":0x2133, | |
"Mfr;":[0xd835,0xdd10], "MinusPlus;":0x2213, | |
"Mopf;":[0xd835,0xdd44], "Mscr;":0x2133, | |
"Mu;":0x39c, "NJcy;":0x40a, | |
"Nacute;":0x143, "Ncaron;":0x147, | |
"Ncedil;":0x145, "Ncy;":0x41d, | |
"NegativeMediumSpace;":0x200b, "NegativeThickSpace;":0x200b, | |
"NegativeThinSpace;":0x200b, "NegativeVeryThinSpace;":0x200b, | |
"NestedGreaterGreater;":0x226b, "NestedLessLess;":0x226a, | |
"NewLine;":0xa, "Nfr;":[0xd835,0xdd11], | |
"NoBreak;":0x2060, "NonBreakingSpace;":0xa0, | |
"Nopf;":0x2115, "Not;":0x2aec, | |
"NotCongruent;":0x2262, "NotCupCap;":0x226d, | |
"NotDoubleVerticalBar;":0x2226, "NotElement;":0x2209, | |
"NotEqual;":0x2260, "NotEqualTilde;":[0x2242,0x338], | |
"NotExists;":0x2204, "NotGreater;":0x226f, | |
"NotGreaterEqual;":0x2271, "NotGreaterFullEqual;":[0x2267,0x338], | |
"NotGreaterGreater;":[0x226b,0x338], "NotGreaterLess;":0x2279, | |
"NotGreaterSlantEqual;":[0x2a7e,0x338], "NotGreaterTilde;":0x2275, | |
"NotHumpDownHump;":[0x224e,0x338], "NotHumpEqual;":[0x224f,0x338], | |
"NotLeftTriangle;":0x22ea, "NotLeftTriangleBar;":[0x29cf,0x338], | |
"NotLeftTriangleEqual;":0x22ec, "NotLess;":0x226e, | |
"NotLessEqual;":0x2270, "NotLessGreater;":0x2278, | |
"NotLessLess;":[0x226a,0x338], "NotLessSlantEqual;":[0x2a7d,0x338], | |
"NotLessTilde;":0x2274, "NotNestedGreaterGreater;":[0x2aa2,0x338], | |
"NotNestedLessLess;":[0x2aa1,0x338], "NotPrecedes;":0x2280, | |
"NotPrecedesEqual;":[0x2aaf,0x338], "NotPrecedesSlantEqual;":0x22e0, | |
"NotReverseElement;":0x220c, "NotRightTriangle;":0x22eb, | |
"NotRightTriangleBar;":[0x29d0,0x338], "NotRightTriangleEqual;":0x22ed, | |
"NotSquareSubset;":[0x228f,0x338], "NotSquareSubsetEqual;":0x22e2, | |
"NotSquareSuperset;":[0x2290,0x338], "NotSquareSupersetEqual;":0x22e3, | |
"NotSubset;":[0x2282,0x20d2], "NotSubsetEqual;":0x2288, | |
"NotSucceeds;":0x2281, "NotSucceedsEqual;":[0x2ab0,0x338], | |
"NotSucceedsSlantEqual;":0x22e1, "NotSucceedsTilde;":[0x227f,0x338], | |
"NotSuperset;":[0x2283,0x20d2], "NotSupersetEqual;":0x2289, | |
"NotTilde;":0x2241, "NotTildeEqual;":0x2244, | |
"NotTildeFullEqual;":0x2247, "NotTildeTilde;":0x2249, | |
"NotVerticalBar;":0x2224, "Nscr;":[0xd835,0xdca9], | |
"Ntilde;":0xd1, "Nu;":0x39d, | |
"OElig;":0x152, "Oacute;":0xd3, | |
"Ocirc;":0xd4, "Ocy;":0x41e, | |
"Odblac;":0x150, "Ofr;":[0xd835,0xdd12], | |
"Ograve;":0xd2, "Omacr;":0x14c, | |
"Omega;":0x3a9, "Omicron;":0x39f, | |
"Oopf;":[0xd835,0xdd46], "OpenCurlyDoubleQuote;":0x201c, | |
"OpenCurlyQuote;":0x2018, "Or;":0x2a54, | |
"Oscr;":[0xd835,0xdcaa], "Oslash;":0xd8, | |
"Otilde;":0xd5, "Otimes;":0x2a37, | |
"Ouml;":0xd6, "OverBar;":0x203e, | |
"OverBrace;":0x23de, "OverBracket;":0x23b4, | |
"OverParenthesis;":0x23dc, "PartialD;":0x2202, | |
"Pcy;":0x41f, "Pfr;":[0xd835,0xdd13], | |
"Phi;":0x3a6, "Pi;":0x3a0, | |
"PlusMinus;":0xb1, "Poincareplane;":0x210c, | |
"Popf;":0x2119, "Pr;":0x2abb, | |
"Precedes;":0x227a, "PrecedesEqual;":0x2aaf, | |
"PrecedesSlantEqual;":0x227c, "PrecedesTilde;":0x227e, | |
"Prime;":0x2033, "Product;":0x220f, | |
"Proportion;":0x2237, "Proportional;":0x221d, | |
"Pscr;":[0xd835,0xdcab], "Psi;":0x3a8, | |
"QUOT;":0x22, "Qfr;":[0xd835,0xdd14], | |
"Qopf;":0x211a, "Qscr;":[0xd835,0xdcac], | |
"RBarr;":0x2910, "REG;":0xae, | |
"Racute;":0x154, "Rang;":0x27eb, | |
"Rarr;":0x21a0, "Rarrtl;":0x2916, | |
"Rcaron;":0x158, "Rcedil;":0x156, | |
"Rcy;":0x420, "Re;":0x211c, | |
"ReverseElement;":0x220b, "ReverseEquilibrium;":0x21cb, | |
"ReverseUpEquilibrium;":0x296f, "Rfr;":0x211c, | |
"Rho;":0x3a1, "RightAngleBracket;":0x27e9, | |
"RightArrow;":0x2192, "RightArrowBar;":0x21e5, | |
"RightArrowLeftArrow;":0x21c4, "RightCeiling;":0x2309, | |
"RightDoubleBracket;":0x27e7, "RightDownTeeVector;":0x295d, | |
"RightDownVector;":0x21c2, "RightDownVectorBar;":0x2955, | |
"RightFloor;":0x230b, "RightTee;":0x22a2, | |
"RightTeeArrow;":0x21a6, "RightTeeVector;":0x295b, | |
"RightTriangle;":0x22b3, "RightTriangleBar;":0x29d0, | |
"RightTriangleEqual;":0x22b5, "RightUpDownVector;":0x294f, | |
"RightUpTeeVector;":0x295c, "RightUpVector;":0x21be, | |
"RightUpVectorBar;":0x2954, "RightVector;":0x21c0, | |
"RightVectorBar;":0x2953, "Rightarrow;":0x21d2, | |
"Ropf;":0x211d, "RoundImplies;":0x2970, | |
"Rrightarrow;":0x21db, "Rscr;":0x211b, | |
"Rsh;":0x21b1, "RuleDelayed;":0x29f4, | |
"SHCHcy;":0x429, "SHcy;":0x428, | |
"SOFTcy;":0x42c, "Sacute;":0x15a, | |
"Sc;":0x2abc, "Scaron;":0x160, | |
"Scedil;":0x15e, "Scirc;":0x15c, | |
"Scy;":0x421, "Sfr;":[0xd835,0xdd16], | |
"ShortDownArrow;":0x2193, "ShortLeftArrow;":0x2190, | |
"ShortRightArrow;":0x2192, "ShortUpArrow;":0x2191, | |
"Sigma;":0x3a3, "SmallCircle;":0x2218, | |
"Sopf;":[0xd835,0xdd4a], "Sqrt;":0x221a, | |
"Square;":0x25a1, "SquareIntersection;":0x2293, | |
"SquareSubset;":0x228f, "SquareSubsetEqual;":0x2291, | |
"SquareSuperset;":0x2290, "SquareSupersetEqual;":0x2292, | |
"SquareUnion;":0x2294, "Sscr;":[0xd835,0xdcae], | |
"Star;":0x22c6, "Sub;":0x22d0, | |
"Subset;":0x22d0, "SubsetEqual;":0x2286, | |
"Succeeds;":0x227b, "SucceedsEqual;":0x2ab0, | |
"SucceedsSlantEqual;":0x227d, "SucceedsTilde;":0x227f, | |
"SuchThat;":0x220b, "Sum;":0x2211, | |
"Sup;":0x22d1, "Superset;":0x2283, | |
"SupersetEqual;":0x2287, "Supset;":0x22d1, | |
"THORN;":0xde, "TRADE;":0x2122, | |
"TSHcy;":0x40b, "TScy;":0x426, | |
"Tab;":0x9, "Tau;":0x3a4, | |
"Tcaron;":0x164, "Tcedil;":0x162, | |
"Tcy;":0x422, "Tfr;":[0xd835,0xdd17], | |
"Therefore;":0x2234, "Theta;":0x398, | |
"ThickSpace;":[0x205f,0x200a], "ThinSpace;":0x2009, | |
"Tilde;":0x223c, "TildeEqual;":0x2243, | |
"TildeFullEqual;":0x2245, "TildeTilde;":0x2248, | |
"Topf;":[0xd835,0xdd4b], "TripleDot;":0x20db, | |
"Tscr;":[0xd835,0xdcaf], "Tstrok;":0x166, | |
"Uacute;":0xda, "Uarr;":0x219f, | |
"Uarrocir;":0x2949, "Ubrcy;":0x40e, | |
"Ubreve;":0x16c, "Ucirc;":0xdb, | |
"Ucy;":0x423, "Udblac;":0x170, | |
"Ufr;":[0xd835,0xdd18], "Ugrave;":0xd9, | |
"Umacr;":0x16a, "UnderBar;":0x5f, | |