Skip to content

Instantly share code, notes, and snippets.

@vectorsize
Last active June 13, 2019 04:18
Show Gist options
  • Save vectorsize/8053096 to your computer and use it in GitHub Desktop.
Save vectorsize/8053096 to your computer and use it in GitHub Desktop.
D3 in a virtual DOM proof of concept.
<
!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,