Browse Source

init

tags/v0.9.1
3136352472 5 years ago
parent
commit
ce7af9264d
  1. 579
      domino/CSSStyleDeclaration.js
  2. 120
      domino/CharacterData.js
  3. 119
      domino/ChildNode.js
  4. 39
      domino/Comment.js
  5. 80
      domino/ContainerNode.js
  6. 12
      domino/CustomEvent.js
  7. 134
      domino/DOMException.js
  8. 94
      domino/DOMImplementation.js
  9. 172
      domino/DOMTokenList.js
  10. 884
      domino/Document.js
  11. 68
      domino/DocumentFragment.js
  12. 36
      domino/DocumentType.js
  13. 1202
      domino/Element.js
  14. 66
      domino/Event.js
  15. 298
      domino/EventTarget.js
  16. 92
      domino/FilteredElementList.js
  17. 7264
      domino/HTMLParser.js
  18. 37
      domino/Leaf.js
  19. 44
      domino/LinkedList.js
  20. 56
      domino/Location.js
  21. 52
      domino/MouseEvent.js
  22. 9
      domino/MutationConstants.js
  23. 41
      domino/NamedNodeMap.js
  24. 17
      domino/NavigatorID.js
  25. 738
      domino/Node.js
  26. 24
      domino/NodeFilter.js
  27. 217
      domino/NodeIterator.js
  28. 15
      domino/NodeList.es5.js
  29. 12
      domino/NodeList.es6.js
  30. 13
      domino/NodeList.js
  31. 87
      domino/NodeTraversal.js
  32. 168
      domino/NodeUtils.js
  33. 26
      domino/NonDocumentTypeChildNode.js
  34. 43
      domino/ProcessingInstruction.js
  35. 74
      domino/Text.js
  36. 336
      domino/TreeWalker.js
  37. 19
      domino/UIEvent.js
  38. 194
      domino/URL.js
  39. 270
      domino/URLUtils.js
  40. 62
      domino/Window.js
  41. 11
      domino/WindowTimers.js
  42. 152
      domino/attributes.js
  43. 7
      domino/config.js
  44. 6654
      domino/cssparser.js
  45. 70
      domino/defineElement.js
  46. 7
      domino/events.js
  47. 1426
      domino/htmlelts.js
  48. 27
      domino/impl.js
  49. 5
      domino/index.d.ts
  50. 79
      domino/index.js
  51. 2938
      domino/jquery.js
  52. 933
      domino/select.js
  53. 24
      domino/sloppy.js
  54. 57
      domino/svg.js
  55. 85
      domino/utils.js
  56. 91
      domino/xmlnames.js
  57. 1535
      govue/axios.js
  58. 4
      govue/govue.js
  59. 28
      govue/init.js
  60. 1183
      govue/promise.js
  61. 80
      jsruntime/runtime.go
  62. 61
      main.go
  63. 14
      static/use.js

579
domino/CSSStyleDeclaration.js

@ -0,0 +1,579 @@
"use strict";
var parserlib = require('./cssparser.js');
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 = { property: Object.create(null), priority: Object.create(null) };
parser.addListener("property", function(e) {
if (e.invalid) return; // Skip errors
result.property[e.property.text] = e.value.text;
if (e.important) result.priority[e.property.text] = 'important';
});
s = (''+s).replace(/^;/, '');
parser.parseStyleAttribute(s);
return result;
}
var NO_CHANGE = {}; // Private marker object
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.property) {
if (s) s += " ";
s += name + ": " + styles.property[name];
if (styles.priority[name]) {
s += " !" + styles.priority[name];
}
s += ";";
}
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.property);
return this._names.length;
}},
item: { value: function(n) {
if (!this._names)
this._names = Object.getOwnPropertyNames(this._parsed.property);
return this._names[n];
}},
getPropertyValue: { value: function(property) {
property = property.toLowerCase();
return this._parsed.property[property] || "";
}},
getPropertyPriority: { value: function(property) {
property = property.toLowerCase();
return this._parsed.priority[property] || "";
}},
setProperty: { value: function(property, value, priority) {
property = property.toLowerCase();
if (value === null || value === undefined) {
value = "";
}
if (priority === null || priority === undefined) {
priority = "";
}
// String coercion
if (value !== NO_CHANGE) {
value = "" + value;
}
if (value === "") {
this.removeProperty(property);
return;
}
if (priority !== "" && priority !== NO_CHANGE &&
!/^important$/i.test(priority)) {
return;
}
var styles = this._parsed;
if (value === NO_CHANGE) {
if (!styles.property[property]) {
return; // Not a valid property name.
}
if (priority !== "") {
styles.priority[property] = "important";
} else {
delete styles.priority[property];
}
} else {
// 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 newprops = parseStyles(property + ":" + value);
if (Object.getOwnPropertyNames(newprops.property).length === 0) {
return; // no valid property found
}
if (Object.getOwnPropertyNames(newprops.priority).length !== 0) {
return; // if the value included '!important' it wasn't valid.
}
// XXX handle shorthand properties
for (var p in newprops.property) {
styles.property[p] = newprops.property[p];
if (priority === NO_CHANGE) {
continue;
} else if (priority !== "") {
styles.priority[p] = "important";
} else if (styles.priority[p]) {
delete styles.priority[p];
}
}
}
// Serialize and update cssText and element.style!
this._serialize();
}},
setPropertyValue: { value: function(property, value) {
return this.setProperty(property, value, NO_CHANGE);
}},
setPropertyPriority: { value: function(property, priority) {
return this.setProperty(property, NO_CHANGE, priority);
}},
removeProperty: { value: function(property) {
property = property.toLowerCase();
var styles = this._parsed;
if (property in styles.property) {
delete styles.property[property];
delete styles.priority[property];
// Serialize and update cssText and element.style!
this._serialize();
}
}},
});
var cssProperties = {
alignContent: "align-content",
alignItems: "align-items",
alignmentBaseline: "alignment-baseline",
alignSelf: "align-self",
animation: "animation",
animationDelay: "animation-delay",
animationDirection: "animation-direction",
animationDuration: "animation-duration",
animationFillMode: "animation-fill-mode",
animationIterationCount: "animation-iteration-count",
animationName: "animation-name",
animationPlayState: "animation-play-state",
animationTimingFunction: "animation-timing-function",
backfaceVisibility: "backface-visibility",
background: "background",
backgroundAttachment: "background-attachment",
backgroundClip: "background-clip",
backgroundColor: "background-color",
backgroundImage: "background-image",
backgroundOrigin: "background-origin",
backgroundPosition: "background-position",
backgroundPositionX: "background-position-x",
backgroundPositionY: "background-position-y",
backgroundRepeat: "background-repeat",
backgroundSize: "background-size",
baselineShift: "baseline-shift",
border: "border",
borderBottom: "border-bottom",
borderBottomColor: "border-bottom-color",
borderBottomLeftRadius: "border-bottom-left-radius",
borderBottomRightRadius: "border-bottom-right-radius",
borderBottomStyle: "border-bottom-style",
borderBottomWidth: "border-bottom-width",
borderCollapse: "border-collapse",
borderColor: "border-color",
borderImage: "border-image",
borderImageOutset: "border-image-outset",
borderImageRepeat: "border-image-repeat",
borderImageSlice: "border-image-slice",
borderImageSource: "border-image-source",
borderImageWidth: "border-image-width",
borderLeft: "border-left",
borderLeftColor: "border-left-color",
borderLeftStyle: "border-left-style",
borderLeftWidth: "border-left-width",
borderRadius: "border-radius",
borderRight: "border-right",
borderRightColor: "border-right-color",
borderRightStyle: "border-right-style",
borderRightWidth: "border-right-width",
borderSpacing: "border-spacing",
borderStyle: "border-style",
borderTop: "border-top",
borderTopColor: "border-top-color",
borderTopLeftRadius: "border-top-left-radius",
borderTopRightRadius: "border-top-right-radius",
borderTopStyle: "border-top-style",
borderTopWidth: "border-top-width",
borderWidth: "border-width",
bottom: "bottom",
boxShadow: "box-shadow",
boxSizing: "box-sizing",
breakAfter: "break-after",
breakBefore: "break-before",
breakInside: "break-inside",
captionSide: "caption-side",
clear: "clear",
clip: "clip",
clipPath: "clip-path",
clipRule: "clip-rule",
color: "color",
colorInterpolationFilters: "color-interpolation-filters",
columnCount: "column-count",
columnFill: "column-fill",
columnGap: "column-gap",
columnRule: "column-rule",
columnRuleColor: "column-rule-color",
columnRuleStyle: "column-rule-style",
columnRuleWidth: "column-rule-width",
columns: "columns",
columnSpan: "column-span",
columnWidth: "column-width",
content: "content",
counterIncrement: "counter-increment",
counterReset: "counter-reset",
cssFloat: "float",
cursor: "cursor",
direction: "direction",
display: "display",
dominantBaseline: "dominant-baseline",
emptyCells: "empty-cells",
enableBackground: "enable-background",
fill: "fill",
fillOpacity: "fill-opacity",
fillRule: "fill-rule",
filter: "filter",
flex: "flex",
flexBasis: "flex-basis",
flexDirection: "flex-direction",
flexFlow: "flex-flow",
flexGrow: "flex-grow",
flexShrink: "flex-shrink",
flexWrap: "flex-wrap",
floodColor: "flood-color",
floodOpacity: "flood-opacity",
font: "font",
fontFamily: "font-family",
fontFeatureSettings: "font-feature-settings",
fontSize: "font-size",
fontSizeAdjust: "font-size-adjust",
fontStretch: "font-stretch",
fontStyle: "font-style",
fontVariant: "font-variant",
fontWeight: "font-weight",
glyphOrientationHorizontal: "glyph-orientation-horizontal",
glyphOrientationVertical: "glyph-orientation-vertical",
grid: "grid",
gridArea: "grid-area",
gridAutoColumns: "grid-auto-columns",
gridAutoFlow: "grid-auto-flow",
gridAutoRows: "grid-auto-rows",
gridColumn: "grid-column",
gridColumnEnd: "grid-column-end",
gridColumnGap: "grid-column-gap",
gridColumnStart: "grid-column-start",
gridGap: "grid-gap",
gridRow: "grid-row",
gridRowEnd: "grid-row-end",
gridRowGap: "grid-row-gap",
gridRowStart: "grid-row-start",
gridTemplate: "grid-template",
gridTemplateAreas: "grid-template-areas",
gridTemplateColumns: "grid-template-columns",
gridTemplateRows: "grid-template-rows",
height: "height",
imeMode: "ime-mode",
justifyContent: "justify-content",
kerning: "kerning",
layoutGrid: "layout-grid",
layoutGridChar: "layout-grid-char",
layoutGridLine: "layout-grid-line",
layoutGridMode: "layout-grid-mode",
layoutGridType: "layout-grid-type",
left: "left",
letterSpacing: "letter-spacing",
lightingColor: "lighting-color",
lineBreak: "line-break",
lineHeight: "line-height",
listStyle: "list-style",
listStyleImage: "list-style-image",
listStylePosition: "list-style-position",
listStyleType: "list-style-type",
margin: "margin",
marginBottom: "margin-bottom",
marginLeft: "margin-left",
marginRight: "margin-right",
marginTop: "margin-top",
marker: "marker",
markerEnd: "marker-end",
markerMid: "marker-mid",
markerStart: "marker-start",
mask: "mask",
maxHeight: "max-height",
maxWidth: "max-width",
minHeight: "min-height",
minWidth: "min-width",
msContentZoomChaining: "-ms-content-zoom-chaining",
msContentZooming: "-ms-content-zooming",
msContentZoomLimit: "-ms-content-zoom-limit",
msContentZoomLimitMax: "-ms-content-zoom-limit-max",
msContentZoomLimitMin: "-ms-content-zoom-limit-min",
msContentZoomSnap: "-ms-content-zoom-snap",
msContentZoomSnapPoints: "-ms-content-zoom-snap-points",
msContentZoomSnapType: "-ms-content-zoom-snap-type",
msFlowFrom: "-ms-flow-from",
msFlowInto: "-ms-flow-into",
msFontFeatureSettings: "-ms-font-feature-settings",
msGridColumn: "-ms-grid-column",
msGridColumnAlign: "-ms-grid-column-align",
msGridColumns: "-ms-grid-columns",
msGridColumnSpan: "-ms-grid-column-span",
msGridRow: "-ms-grid-row",
msGridRowAlign: "-ms-grid-row-align",
msGridRows: "-ms-grid-rows",
msGridRowSpan: "-ms-grid-row-span",
msHighContrastAdjust: "-ms-high-contrast-adjust",
msHyphenateLimitChars: "-ms-hyphenate-limit-chars",
msHyphenateLimitLines: "-ms-hyphenate-limit-lines",
msHyphenateLimitZone: "-ms-hyphenate-limit-zone",
msHyphens: "-ms-hyphens",
msImeAlign: "-ms-ime-align",
msOverflowStyle: "-ms-overflow-style",
msScrollChaining: "-ms-scroll-chaining",
msScrollLimit: "-ms-scroll-limit",
msScrollLimitXMax: "-ms-scroll-limit-x-max",
msScrollLimitXMin: "-ms-scroll-limit-x-min",
msScrollLimitYMax: "-ms-scroll-limit-y-max",
msScrollLimitYMin: "-ms-scroll-limit-y-min",
msScrollRails: "-ms-scroll-rails",
msScrollSnapPointsX: "-ms-scroll-snap-points-x",
msScrollSnapPointsY: "-ms-scroll-snap-points-y",
msScrollSnapType: "-ms-scroll-snap-type",
msScrollSnapX: "-ms-scroll-snap-x",
msScrollSnapY: "-ms-scroll-snap-y",
msScrollTranslation: "-ms-scroll-translation",
msTextCombineHorizontal: "-ms-text-combine-horizontal",
msTextSizeAdjust: "-ms-text-size-adjust",
msTouchAction: "-ms-touch-action",
msTouchSelect: "-ms-touch-select",
msUserSelect: "-ms-user-select",
msWrapFlow: "-ms-wrap-flow",
msWrapMargin: "-ms-wrap-margin",
msWrapThrough: "-ms-wrap-through",
opacity: "opacity",
order: "order",
orphans: "orphans",
outline: "outline",
outlineColor: "outline-color",
outlineOffset: "outline-offset",
outlineStyle: "outline-style",
outlineWidth: "outline-width",
overflow: "overflow",
overflowX: "overflow-x",
overflowY: "overflow-y",
padding: "padding",
paddingBottom: "padding-bottom",
paddingLeft: "padding-left",
paddingRight: "padding-right",
paddingTop: "padding-top",
page: "page",
pageBreakAfter: "page-break-after",
pageBreakBefore: "page-break-before",
pageBreakInside: "page-break-inside",
perspective: "perspective",
perspectiveOrigin: "perspective-origin",
pointerEvents: "pointer-events",
position: "position",
quotes: "quotes",
right: "right",
rotate: "rotate",
rubyAlign: "ruby-align",
rubyOverhang: "ruby-overhang",
rubyPosition: "ruby-position",
scale: "scale",
size: "size",
stopColor: "stop-color",
stopOpacity: "stop-opacity",
stroke: "stroke",
strokeDasharray: "stroke-dasharray",
strokeDashoffset: "stroke-dashoffset",
strokeLinecap: "stroke-linecap",
strokeLinejoin: "stroke-linejoin",
strokeMiterlimit: "stroke-miterlimit",
strokeOpacity: "stroke-opacity",
strokeWidth: "stroke-width",
tableLayout: "table-layout",
textAlign: "text-align",
textAlignLast: "text-align-last",
textAnchor: "text-anchor",
textDecoration: "text-decoration",
textIndent: "text-indent",
textJustify: "text-justify",
textKashida: "text-kashida",
textKashidaSpace: "text-kashida-space",
textOverflow: "text-overflow",
textShadow: "text-shadow",
textTransform: "text-transform",
textUnderlinePosition: "text-underline-position",
top: "top",
touchAction: "touch-action",
transform: "transform",
transformOrigin: "transform-origin",
transformStyle: "transform-style",
transition: "transition",
transitionDelay: "transition-delay",
transitionDuration: "transition-duration",
transitionProperty: "transition-property",
transitionTimingFunction: "transition-timing-function",
translate: "translate",
unicodeBidi: "unicode-bidi",
verticalAlign: "vertical-align",
visibility: "visibility",
webkitAlignContent: "-webkit-align-content",
webkitAlignItems: "-webkit-align-items",
webkitAlignSelf: "-webkit-align-self",
webkitAnimation: "-webkit-animation",
webkitAnimationDelay: "-webkit-animation-delay",
webkitAnimationDirection: "-webkit-animation-direction",
webkitAnimationDuration: "-webkit-animation-duration",
webkitAnimationFillMode: "-webkit-animation-fill-mode",
webkitAnimationIterationCount: "-webkit-animation-iteration-count",
webkitAnimationName: "-webkit-animation-name",
webkitAnimationPlayState: "-webkit-animation-play-state",
webkitAnimationTimingFunction: "-webkit-animation-timing-funciton",
webkitAppearance: "-webkit-appearance",
webkitBackfaceVisibility: "-webkit-backface-visibility",
webkitBackgroundClip: "-webkit-background-clip",
webkitBackgroundOrigin: "-webkit-background-origin",
webkitBackgroundSize: "-webkit-background-size",
webkitBorderBottomLeftRadius: "-webkit-border-bottom-left-radius",
webkitBorderBottomRightRadius: "-webkit-border-bottom-right-radius",
webkitBorderImage: "-webkit-border-image",
webkitBorderRadius: "-webkit-border-radius",
webkitBorderTopLeftRadius: "-webkit-border-top-left-radius",
webkitBorderTopRightRadius: "-webkit-border-top-right-radius",
webkitBoxAlign: "-webkit-box-align",
webkitBoxDirection: "-webkit-box-direction",
webkitBoxFlex: "-webkit-box-flex",
webkitBoxOrdinalGroup: "-webkit-box-ordinal-group",
webkitBoxOrient: "-webkit-box-orient",
webkitBoxPack: "-webkit-box-pack",
webkitBoxSizing: "-webkit-box-sizing",
webkitColumnBreakAfter: "-webkit-column-break-after",
webkitColumnBreakBefore: "-webkit-column-break-before",
webkitColumnBreakInside: "-webkit-column-break-inside",
webkitColumnCount: "-webkit-column-count",
webkitColumnGap: "-webkit-column-gap",
webkitColumnRule: "-webkit-column-rule",
webkitColumnRuleColor: "-webkit-column-rule-color",
webkitColumnRuleStyle: "-webkit-column-rule-style",
webkitColumnRuleWidth: "-webkit-column-rule-width",
webkitColumns: "-webkit-columns",
webkitColumnSpan: "-webkit-column-span",
webkitColumnWidth: "-webkit-column-width",
webkitFilter: "-webkit-filter",
webkitFlex: "-webkit-flex",
webkitFlexBasis: "-webkit-flex-basis",
webkitFlexDirection: "-webkit-flex-direction",
webkitFlexFlow: "-webkit-flex-flow",
webkitFlexGrow: "-webkit-flex-grow",
webkitFlexShrink: "-webkit-flex-shrink",
webkitFlexWrap: "-webkit-flex-wrap",
webkitJustifyContent: "-webkit-justify-content",
webkitOrder: "-webkit-order",
webkitPerspective: "-webkit-perspective-origin",
webkitPerspectiveOrigin: "-webkit-perspective-origin",
webkitTapHighlightColor: "-webkit-tap-highlight-color",
webkitTextFillColor: "-webkit-text-fill-color",
webkitTextSizeAdjust: "-webkit-text-size-adjust",
webkitTextStroke: "-webkit-text-stroke",
webkitTextStrokeColor: "-webkit-text-stroke-color",
webkitTextStrokeWidth: "-webkit-text-stroke-width",
webkitTransform: "-webkit-transform",
webkitTransformOrigin: "-webkit-transform-origin",
webkitTransformStyle: "-webkit-transform-style",
webkitTransition: "-webkit-transition",
webkitTransitionDelay: "-webkit-transition-delay",
webkitTransitionDuration: "-webkit-transition-duration",
webkitTransitionProperty: "-webkit-transition-property",
webkitTransitionTimingFunction: "-webkit-transition-timing-function",
webkitUserModify: "-webkit-user-modify",
webkitUserSelect: "-webkit-user-select",
webkitWritingMode: "-webkit-writing-mode",
whiteSpace: "white-space",
widows: "widows",
width: "width",
wordBreak: "word-break",
wordSpacing: "word-spacing",
wordWrap: "word-wrap",
writingMode: "writing-mode",
zIndex: "z-index",
zoom: "zoom",
resize: "resize",
userSelect: "user-select",
};
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) {
this.setProperty(cssname, value);
}
});
if (!CSSStyleDeclaration.prototype.hasOwnProperty(cssname)) {
Object.defineProperty(CSSStyleDeclaration.prototype, cssname, {
get: function() {
return this.getPropertyValue(cssname);
},
set: function(value) {
this.setProperty(cssname, value);
}
});
}
}

120
domino/CharacterData.js

@ -0,0 +1,120 @@
/* jshint bitwise: false */
"use strict";
module.exports = CharacterData;
var Leaf = require('./Leaf.js');
var utils = require('./utils.js');
var ChildNode = require('./ChildNode.js');
var NonDocumentTypeChildNode = require('./NonDocumentTypeChildNode.js');
function CharacterData() {
Leaf.call(this);
}
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 (arguments.length < 2) { throw new TypeError("Not enough arguments"); }
// Convert arguments to WebIDL "unsigned long"
offset = offset >>> 0;
count = count >>> 0;
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) {
if (arguments.length < 1) { throw new TypeError("Not enough arguments"); }
this.data += String(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) {
return this.replaceData(offset, 0, data);
}},
// 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) {
return this.replaceData(offset, count, '');
}},
// 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;
// Convert arguments to correct WebIDL type
offset = offset >>> 0;
count = count >>> 0;
data = String(data);
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; }}
});
Object.defineProperties(CharacterData.prototype, ChildNode);
Object.defineProperties(CharacterData.prototype, NonDocumentTypeChildNode);

119
domino/ChildNode.js

@ -0,0 +1,119 @@
"use strict";
var Node = require('./Node.js');
var LinkedList = require('./LinkedList.js');
var createDocumentFragmentFromArguments = function(document, args) {
var docFrag = document.createDocumentFragment();
for (var i=0; i<args.length; i++) {
var argItem = args[i];
var isNode = argItem instanceof Node;
docFrag.appendChild(isNode ? argItem :
document.createTextNode(String(argItem)));
}
return docFrag;
};
// The ChildNode interface contains methods that are particular to `Node`
// objects that can have a parent. It is implemented by `Element`,
// `DocumentType`, and `CharacterData` objects.
var ChildNode = {
// Inserts a set of Node or String objects in the children list of this
// ChildNode's parent, just after this ChildNode. String objects are
// inserted as the equivalent Text nodes.
after: { value: function after() {
var argArr = Array.prototype.slice.call(arguments);
var parentNode = this.parentNode, nextSibling = this.nextSibling;
if (parentNode === null) { return; }
// Find "viable next sibling"; that is, next one not in argArr
while (nextSibling && argArr.some(function(v) { return v===nextSibling; }))
nextSibling = nextSibling.nextSibling;
// ok, parent and sibling are saved away since this node could itself
// appear in argArr and we're about to move argArr to a document fragment.
var docFrag = createDocumentFragmentFromArguments(this.doc, argArr);
parentNode.insertBefore(docFrag, nextSibling);
}},
// Inserts a set of Node or String objects in the children list of this
// ChildNode's parent, just before this ChildNode. String objects are
// inserted as the equivalent Text nodes.
before: { value: function before() {
var argArr = Array.prototype.slice.call(arguments);
var parentNode = this.parentNode, prevSibling = this.previousSibling;
if (parentNode === null) { return; }
// Find "viable prev sibling"; that is, prev one not in argArr
while (prevSibling && argArr.some(function(v) { return v===prevSibling; }))
prevSibling = prevSibling.previousSibling;
// ok, parent and sibling are saved away since this node could itself
// appear in argArr and we're about to move argArr to a document fragment.
var docFrag = createDocumentFragmentFromArguments(this.doc, argArr);
var nextSibling =
prevSibling ? prevSibling.nextSibling : parentNode.firstChild;
parentNode.insertBefore(docFrag, nextSibling);
}},
// Remove this node from its parent
remove: { value: function remove() {
if (this.parentNode === null) return;
// Send mutation events if necessary
if (this.doc) {
this.doc._preremoveNodeIterators(this);
if (this.rooted) {
this.doc.mutateRemove(this);
}
}
// Remove this node from its parents array of children
// and update the structure id for all ancestors
this._remove();
// Forget this node's parent
this.parentNode = null;
}},
// Remove this node w/o uprooting or sending mutation events
// (But do update the structure id for all ancestors)
_remove: { value: function _remove() {
var parent = this.parentNode;
if (parent === null) return;
if (parent._childNodes) {
parent._childNodes.splice(this.index, 1);
} else if (parent._firstChild === this) {
if (this._nextSibling === this) {
parent._firstChild = null;
} else {
parent._firstChild = this._nextSibling;
}
}
LinkedList.remove(this);
parent.modify();
}},
// Replace this node with the nodes or strings provided as arguments.
replaceWith: { value: function replaceWith() {
var argArr = Array.prototype.slice.call(arguments);
var parentNode = this.parentNode, nextSibling = this.nextSibling;
if (parentNode === null) { return; }
// Find "viable next sibling"; that is, next one not in argArr
while (nextSibling && argArr.some(function(v) { return v===nextSibling; }))
nextSibling = nextSibling.nextSibling;
// ok, parent and sibling are saved away since this node could itself
// appear in argArr and we're about to move argArr to a document fragment.
var docFrag = createDocumentFragmentFromArguments(this.doc, argArr);
if (this.parentNode === parentNode) {
parentNode.replaceChild(docFrag, this);
} else {
// `this` was inserted into docFrag
parentNode.insertBefore(docFrag, nextSibling);
}
}},
};
module.exports = ChildNode;

39
domino/Comment.js

@ -0,0 +1,39 @@
"use strict";
module.exports = Comment;
var Node = require('./Node.js');
var CharacterData = require('./CharacterData.js');
function Comment(doc, data) {
CharacterData.call(this);
this.nodeType = Node.COMMENT_NODE;
this.ownerDocument = doc;
this._data = data;
}
var nodeValue = {
get: function() { return this._data; },
set: function(v) {
if (v === null || v === undefined) { v = ''; } else { v = String(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: {
get: nodeValue.get,
set: function(v) {
nodeValue.set.call(this, v===null ? '' : String(v));
},
},
// Utility methods
clone: { value: function clone() {
return new Comment(this.ownerDocument, this._data);
}},
});

80
domino/ContainerNode.js

@ -0,0 +1,80 @@
"use strict";
module.exports = ContainerNode;
var Node = require('./Node.js');
var NodeList = require('./NodeList.js');
// This class defines common functionality for node subtypes that
// can have children
function ContainerNode() {
Node.call(this);
this._firstChild = this._childNodes = null;
}
// Primary representation is a circular linked list of siblings
ContainerNode.prototype = Object.create(Node.prototype, {
hasChildNodes: { value: function() {
if (this._childNodes) {
return this._childNodes.length > 0;
}
return this._firstChild !== null;
}},
childNodes: { get: function() {
this._ensureChildNodes();
return this._childNodes;
}},
firstChild: { get: function() {
if (this._childNodes) {
return this._childNodes.length === 0 ? null : this._childNodes[0];
}
return this._firstChild;
}},
lastChild: { get: function() {
var kids = this._childNodes, first;
if (kids) {
return kids.length === 0 ? null: kids[kids.length-1];
}
first = this._firstChild;
if (first === null) { return null; }
return first._previousSibling; // circular linked list
}},
_ensureChildNodes: { value: function() {
if (this._childNodes) { return; }
var first = this._firstChild,
kid = first,
childNodes = this._childNodes = new NodeList();
if (first) do {
childNodes.push(kid);
kid = kid._nextSibling;
} while (kid !== first); // circular linked list
this._firstChild = null; // free memory
}},
// Remove all of this node's children. This is a minor
// optimization that only calls modify() once.
removeChildren: { value: function removeChildren() {
var root = this.rooted ? this.ownerDocument : null,
next = this.firstChild,
kid;
while (next !== null) {
kid = next;
next = kid.nextSibling;
if (root) root.mutateRemove(kid);
kid.parentNode = null;
}
if (this._childNodes) {
this._childNodes.length = 0;
} else {
this._firstChild = null;
}
this.modify(); // Update last modified type once only
}},
});

12
domino/CustomEvent.js

@ -0,0 +1,12 @@
"use strict";
module.exports = CustomEvent;
var Event = require('./Event.js');
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 }
});

134
domino/DOMException.js

@ -0,0 +1,134 @@
"use strict";
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',
'INUSE_ATTRIBUTE_ERR', // 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',
'INUSE_ATTRIBUTE_ERR (10): setAttributeNode called on owned Attribute',
'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, this.constructor);
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);
}

94
domino/DOMImplementation.js

@ -0,0 +1,94 @@
"use strict";
module.exports = DOMImplementation;
var Document = require('./Document.js');
var DocumentType = require('./DocumentType.js');
var HTMLParser = require('./HTMLParser.js');
var utils = require('./utils.js');
var xml = require('./xmlnames.js');
// Each document must have its own instance of the domimplementation object
function DOMImplementation(contextObject) {
this.contextObject = contextObject;
}
// 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.isValidQName(qualifiedName)) utils.InvalidCharacterError();
return new DocumentType(this.contextObject, 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) {
d.appendChild(doctype);
}
if (e) d.appendChild(e);
if (namespace === utils.NAMESPACE.HTML) {
d._contentType = 'application/xhtml+xml';
} else if (namespace === utils.NAMESPACE.SVG) {
d._contentType = 'image/svg+xml';
} else {
d._contentType = 'application/xml';
}
return d;
},
createHTMLDocument: function createHTMLDocument(titleText) {
var d = new Document(true, null);
d.appendChild(new DocumentType(d, 'html'));
var html = d.createElement('html');
d.appendChild(html);
var head = d.createElement('head');
html.appendChild(head);
if (titleText !== undefined) {
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,
};

172
domino/DOMTokenList.js

@ -0,0 +1,172 @@
"use strict";
// DOMTokenList implementation based on https://github.com/Raynos/DOM-shim
// XXX: should cache the getList(this) value more aggressively!
var utils = require('./utils.js');
module.exports = DOMTokenList;
function DOMTokenList(getter, setter) {
this._getString = getter;
this._setString = setter;
this._length = 0;
this._update();
}
Object.defineProperties(DOMTokenList.prototype, {
length: { get: function() { return this._length; } },
item: { value: function(index) {
var list = getList(this);
if (index < 0 || index >= list.length) {
return null;
}
return list[index];
}},
contains: { value: function(token) {
token = String(token); // no error checking for contains()
var list = getList(this);
return list.indexOf(token) > -1;
}},
add: { value: function() {
var list = getList(this);
for (var i = 0, len = arguments.length; i < len; i++) {
var token = handleErrors(arguments[i]);
if (list.indexOf(token) < 0) {
list.push(token);
}
}
// Note: as per spec, if handleErrors() throws any errors, we never
// make it here and none of the changes take effect.
// Also per spec: we run the "update steps" even if no change was
// made (ie, if the token already existed)
this._update(list);
}},
remove: { value: function() {
var list = getList(this);
for (var i = 0, len = arguments.length; i < len; i++) {
var token = handleErrors(arguments[i]);
var index = list.indexOf(token);
if (index > -1) {
list.splice(index, 1);
}
}
// Note: as per spec, if handleErrors() throws any errors, we never
// make it here and none of the changes take effect.
// Also per spec: we run the "update steps" even if no change was
// made (ie, if the token wasn't previously present)
this._update(list);
}},
toggle: { value: function toggle(token, force) {
token = handleErrors(token);
if (this.contains(token)) {
if (force === undefined || force === false) {
this.remove(token);
return false;
}
return true;
} else {
if (force === undefined || force === true) {
this.add(token);
return true;
}
return false;
}
}},
replace: { value: function replace(token, newToken) {
// weird corner case of spec: if `token` contains whitespace, but
// `newToken` is the empty string, we must throw SyntaxError not
// InvalidCharacterError (sigh)
if (String(newToken)==='') { utils.SyntaxError(); }
token = handleErrors(token);
newToken = handleErrors(newToken);
var list = getList(this);
var idx = list.indexOf(token);
if (idx < 0) {
// Note that, per spec, we do not run the update steps on this path.
return false;
}
var idx2 = list.indexOf(newToken);
if (idx2 < 0) {
list[idx] = newToken;
} else {
// "replace the first instance of either `token` or `newToken` with
// `newToken` and remove all other instances"
if (idx < idx2) {
list[idx] = newToken;
list.splice(idx2, 1);
} else {
// idx2 is already `newToken`
list.splice(idx, 1);
}
}
this._update(list);
return true;
}},
toString: { value: function() {
return this._getString();
}},
value: {
get: function() {
return this._getString();
},
set: function(v) {
this._setString(v);
this._update();
}
},
// Called when the setter is called from outside this interface.
_update: { value: function(list) {
if (list) {
fixIndex(this, list);
this._setString(list.join(" ").trim());
} else {
fixIndex(this, getList(this));
}
} },
});
function fixIndex(clist, list) {
var oldLength = clist._length;
var i;
clist._length = list.length;
for (i = 0; i < list.length; i++) {
clist[i] = list[i];
}
// Clear/free old entries.
for (; i < oldLength; i++) {
clist[i] = undefined;
}
}
function handleErrors(token) {
token = String(token);
if (token === "") {
utils.SyntaxError();
}
if (/[ \t\r\n\f]/.test(token)) {
utils.InvalidCharacterError();
}
return token;
}
function getList(clist) {
var str = clist._getString().replace(/(^[ \t\r\n\f]+)|([ \t\r\n\f]+$)/g, '');
if (str === "") {
return [];
} else {
var seen = Object.create(null);
return str.split(/[ \t\r\n\f]+/g).filter(function(n) {
var key = '$' + n;
if (seen[key]) { return false; }
seen[key] = true;
return true;
});
}
}

884
domino/Document.js

@ -0,0 +1,884 @@
"use strict";
module.exports = Document;
var Node = require('./Node.js');
var NodeList = require('./NodeList.js');
var ContainerNode = require('./ContainerNode.js');
var Element = require('./Element.js');
var Text = require('./Text.js');
var Comment = require('./Comment.js');
var Event = require('./Event.js');
var DocumentFragment = require('./DocumentFragment.js');
var ProcessingInstruction = require('./ProcessingInstruction.js');
var DOMImplementation = require('./DOMImplementation.js');
var TreeWalker = require('./TreeWalker.js');
var NodeIterator = require('./NodeIterator.js');
var NodeFilter = require('./NodeFilter.js');
var URL = require('./URL.js');
var select = require('./select.js');
var events = require('./events.js');
var xml = require('./xmlnames.js');
var html = require('./htmlelts.js');
var svg = require('./svg.js');
var utils = require('./utils.js');
var MUTATE = require('./MutationConstants.js');
var NAMESPACE = utils.NAMESPACE;
var isApiWritable = require("./config.js").isApiWritable;
function Document(isHTML, address) {
ContainerNode.call(this);
this.nodeType = Node.DOCUMENT_NODE;
this.isHTML = isHTML;
this._address = address || 'about:blank';
this.readyState = 'loading';
this.implementation = new DOMImplementation(this);
// DOMCore says that documents are always associated with themselves
this.ownerDocument = null; // ... but W3C tests expect null
this._contentType = isHTML ? 'text/html' : 'application/xml';
// 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;
// "Associated inert template document"
this._templateDocCache = null;
// List of active NodeIterators, see NodeIterator#_preremove()
this._nodeIterators = null;
// 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 = Object.create(null);
// 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'
};
var mirrorAttr = function(f, name, defaultValue) {
return {
get: function() {
var o = f.call(this);
if (o) { return o[name]; }
return defaultValue;
},
set: function(value) {
var o = f.call(this);
if (o) { o[name] = value; }
},
};
};
/** @spec https://dom.spec.whatwg.org/#validate-and-extract */
function validateAndExtract(namespace, qualifiedName) {
var prefix, localName, pos;
if (namespace==='') { namespace = null; }
// See https://github.com/whatwg/dom/issues/671
// and https://github.com/whatwg/dom/issues/319
if (!xml.isValidQName(qualifiedName)) {
utils.InvalidCharacterError();
}
prefix = null;
localName = qualifiedName;
pos = qualifiedName.indexOf(':');
if (pos >= 0) {
prefix = qualifiedName.substring(0, pos);
localName = qualifiedName.substring(pos+1);
}
if (prefix !== null && namespace === null) {
utils.NamespaceError();
}
if (prefix === 'xml' && namespace !== NAMESPACE.XML) {
utils.NamespaceError();
}
if ((prefix === 'xmlns' || qualifiedName === 'xmlns') &&
namespace !== NAMESPACE.XMLNS) {
utils.NamespaceError();
}
if (namespace === NAMESPACE.XMLNS && !(prefix==='xmlns' || qualifiedName==='xmlns')) {
utils.NamespaceError();
}
return { namespace: namespace, prefix: prefix, localName: localName };
}
Document.prototype = Object.create(ContainerNode.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: function() { return this._address; }, set: utils.nyi },
compatMode: { get: function() {
// The _quirks property is set by the HTML parser
return this._quirks ? 'BackCompat' : 'CSS1Compat';
}},
createTextNode: { value: function(data) {
return new Text(this, String(data));
}},
createComment: { value: function(data) {
return new Comment(this, data);
}},
createDocumentFragment: { value: function() {
return new DocumentFragment(this);
}},
createProcessingInstruction: { value: function(target, data) {
if (!xml.isValidName(target) || data.indexOf('?>') !== -1)
utils.InvalidCharacterError();
return new ProcessingInstruction(this, target, data);
}},
createAttribute: { value: function(localName) {
localName = String(localName);
if (!xml.isValidName(localName)) utils.InvalidCharacterError();
if (this.isHTML) {
localName = utils.toASCIILowerCase(localName);
}
return new Element._Attr(null, localName, null, null, '');
}},
createAttributeNS: { value: function(namespace, qualifiedName) {
// Convert parameter types according to WebIDL
namespace =
(namespace === null || namespace === undefined || namespace === '') ? null :
String(namespace);
qualifiedName = String(qualifiedName);
var ve = validateAndExtract(namespace, qualifiedName);
return new Element._Attr(null, ve.localName, ve.prefix, ve.namespace, '');
}},
createElement: { value: function(localName) {
localName = String(localName);
if (!xml.isValidName(localName)) utils.InvalidCharacterError();
// Per spec, namespace should be HTML namespace if "context object is
// an HTML document or context object's content type is
// "application/xhtml+xml", and null otherwise.
if (this.isHTML) {
if (/[A-Z]/.test(localName))
localName = utils.toASCIILowerCase(localName);
return html.createElement(this, localName, null);
} else if (this.contentType === 'application/xhtml+xml') {
return html.createElement(this, localName, null);
} else {
return new Element(this, localName, null, null);
}
}, writable: isApiWritable },
createElementNS: { value: function(namespace, qualifiedName) {
// Convert parameter types according to WebIDL
namespace =
(namespace === null || namespace === undefined || namespace === '') ? null :
String(namespace);
qualifiedName = String(qualifiedName);
var ve = validateAndExtract(namespace, qualifiedName);
return this._createElementNS(ve.localName, ve.namespace, ve.prefix);
}, writable: isApiWritable },
// This is used directly by HTML parser, which allows it to create
// elements with localNames containing ':' and non-default namespaces
_createElementNS: { value: function(localName, namespace, prefix) {
if (namespace === NAMESPACE.HTML) {
return html.createElement(this, localName, prefix);
}
else if (namespace === NAMESPACE.SVG) {
return svg.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) {
if (!root) { throw new TypeError("root argument is required"); }
if (!(root instanceof Node)) { throw new TypeError("root not a node"); }
whatToShow = whatToShow === undefined ? NodeFilter.SHOW_ALL : (+whatToShow);
filter = filter === undefined ? null : filter;
return new TreeWalker(root, whatToShow, filter);
}},
// See: http://www.w3.org/TR/dom/#dom-document-createnodeiterator
createNodeIterator: {value: function (root, whatToShow, filter) {
if (!root) { throw new TypeError("root argument is required"); }
if (!(root instanceof Node)) { throw new TypeError("root not a node"); }
whatToShow = whatToShow === undefined ? NodeFilter.SHOW_ALL : (+whatToShow);
filter = filter === undefined ? null : filter;
return new NodeIterator(root, whatToShow, filter);
}},
_attachNodeIterator: { value: function(ni) {
// XXX ideally this should be a weak reference from Document to NodeIterator
if (!this._nodeIterators) { this._nodeIterators = []; }
this._nodeIterators.push(ni);
}},
_detachNodeIterator: { value: function(ni) {
// ni should always be in list of node iterators
var idx = this._nodeIterators.indexOf(ni);
this._nodeIterators.splice(idx, 1);
}},
_preremoveNodeIterators: { value: function(toBeRemoved) {
if (this._nodeIterators) {
this._nodeIterators.forEach(function(ni) { ni._preremove(toBeRemoved); });
}
}},
// Maintain the documentElement and
// doctype properties of the document. Each of the following
// methods chains to the Node implementation of the method
// to do the actual inserting, removal or replacement.
_updateDocTypeElement: { value: function _updateDocTypeElement() {
this.doctype = this.documentElement = null;
for (var kid = this.firstChild; kid !== null; kid = kid.nextSibling) {
if (kid.nodeType === Node.DOCUMENT_TYPE_NODE)
this.doctype = kid;
else if (kid.nodeType === Node.ELEMENT_NODE)
this.documentElement = kid;
}
}},
insertBefore: { value: function insertBefore(child, refChild) {
Node.prototype.insertBefore.call(this, child, refChild);
this._updateDocTypeElement();
return child;
}},
replaceChild: { value: function replaceChild(node, child) {
Node.prototype.replaceChild.call(this, node, child);
this._updateDocTypeElement();
return child;
}},
removeChild: { value: function removeChild(child) {
Node.prototype.removeChild.call(this, child);
this._updateDocTypeElement();
return child;
}},
getElementById: { value: function(id) {
var n = this.byId[id];
if (!n) return null;
if (n instanceof MultiId) { // there was more than one element with this id
return n.getFirst();
}
return n;
}},
_hasMultipleElementsWithId: { value: function(id) {
// Used internally by querySelectorAll optimization
return (this.byId[id] instanceof MultiId);
}},
// Just copy this method from the Element prototype
getElementsByName: { value: Element.prototype.getElementsByName },
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) utils.NotSupportedError();
if (node.nodeType === Node.ATTRIBUTE_NODE) { return node; }
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(deep));
}, writable: isApiWritable },
// The following attributes and methods are from the HTML spec
origin: { get: function origin() { return null; } },
characterSet: { get: function characterSet() { return "UTF-8"; } },
contentType: { get: function contentType() { return this._contentType; } },
URL: { get: function URL() { return this._address; } },
domain: { get: utils.nyi, set: utils.nyi },
referrer: { get: utils.nyi },
cookie: { get: utils.nyi, set: utils.nyi },
lastModified: { get: utils.nyi },
location: {
get: function() {
return this.defaultView ? this.defaultView.location : null; // gh #75
},
set: utils.nyi
},
_titleElement: {
get: function() {
// The title element of a document is the first title element in the
// document in tree order, if there is one, or null otherwise.
return this.getElementsByTagName('title').item(0) || null;
}
},
title: {
get: function() {
var elt = this._titleElement;
// The child text content of the title element, or '' if null.
var value = elt ? elt.textContent : '';
// Strip and collapse whitespace in value
return value.replace(/[ \t\n\r\f]+/g, ' ').replace(/(^ )|( $)/g, '');
},
set: function(value) {
var elt = this._titleElement;
var head = this.head;
if (!elt && !head) { return; /* according to spec */ }
if (!elt) {
elt = this.createElement('title');
head.appendChild(elt);
}
elt.textContent = value;
}
},
dir: mirrorAttr(function() {
var htmlElement = this.documentElement;
if (htmlElement && htmlElement.tagName === 'HTML') { return htmlElement; }
}, 'dir', ''),
fgColor: mirrorAttr(function() { return this.body; }, 'text', ''),
linkColor: mirrorAttr(function() { return this.body; }, 'link', ''),
vlinkColor: mirrorAttr(function() { return this.body; }, 'vLink', ''),
alinkColor: mirrorAttr(function() { return this.body; }, 'aLink', ''),
bgColor: mirrorAttr(function() { return this.body; }, 'bgColor', ''),
// Historical aliases of Document#characterSet
charset: { get: function() { return this.characterSet; } },
inputEncoding: { get: function() { return this.characterSet; } },
scrollingElement: {
get: function() {
return this._quirks ? this.body : this.documentElement;
}
},
// 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 },
applets: { get: function() { return []; } },
activeElement: { get: function() { return null; } },
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 = 'interactive';
this._dispatchEvent(new Event('readystatechange'), true);
this._dispatchEvent(new Event('DOMContentLoaded'), true);
this.readyState = 'complete';
this._dispatchEvent(new Event('readystatechange'), true);
if (this.defaultView) {
this.defaultView._dispatchEvent(new Event('load'), true);
}
}},
// Utility methods
clone: { value: function clone() {
var d = new Document(this.isHTML, this._address);
d._quirks = this._quirks;
d._contentType = this._contentType;
return d;
}},
// We need to adopt the nodes if we do a deep clone
cloneNode: { value: function cloneNode(deep) {
var clone = Node.prototype.cloneNode.call(this, false);
if (deep) {
for (var kid = this.firstChild; kid !== null; kid = kid.nextSibling) {
clone._appendChild(clone.importNode(kid, true));
}
}
clone._updateDocTypeElement();
return clone;
}},
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 (!(val instanceof MultiId)) {
val = new MultiId(val);
this.byId[id] = val;
}
val.add(n);
}
}},
// 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 (val instanceof MultiId) {
val.del(n);
if (val.length === 1) { // convert back to a single node
this.byId[id] = val.downgrade();
}
}
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 = '/';
var base = this.querySelector('base[href]');
if (base) {
return new URL(url).resolve(base.getAttribute('href'));
}
return url;
// The document base URL of a Document object is the
// 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.
}},
_templateDoc: { get: function() {
if (!this._templateDocCache) {
// "associated inert template document"
var newDoc = new Document(this.isHTML, this._address);
this._templateDocCache = newDoc._templateDocCache = newDoc;
}
return this._templateDocCache;
}},
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) {
for (var kid = parent.firstChild; kid !== null; kid = kid.nextSibling) {
if (kid.nodeType === Node.ELEMENT_NODE &&
kid.localName === name &&
kid.namespaceURI === NAMESPACE.HTML) {
return kid;
}
}
}
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) {
for (var kid = node.firstChild; kid !== null; kid = kid.nextSibling)
recursivelyRoot(kid);
}
}
function recursivelyUproot(node) {
uproot(node);
for (var kid = node.firstChild; kid !== null; kid = kid.nextSibling)
recursivelyUproot(kid);
}
function recursivelySetOwner(node, owner) {
node.ownerDocument = owner;
node._lastModTime = undefined; // mod times are document-based
if (Object.prototype.hasOwnProperty.call(node, '_tagName')) {
node._tagName = undefined; // Element subclasses might need to change case
}
for (var kid = node.firstChild; kid !== null; kid = kid.nextSibling)
recursivelySetOwner(kid, owner);
}
// A class for storing multiple nodes with the same ID
function MultiId(node) {
this.nodes = Object.create(null);
this.nodes[node._nid] = node;
this.length = 1;
this.firstNode = undefined;
}
// Add a node to the list, with O(1) time
MultiId.prototype.add = function(node) {
if (!this.nodes[node._nid]) {
this.nodes[node._nid] = node;
this.length++;
this.firstNode = undefined;
}
};
// Remove a node from the list, with O(1) time
MultiId.prototype.del = function(node) {
if (this.nodes[node._nid]) {
delete this.nodes[node._nid];
this.length--;
this.firstNode = undefined;
}
};
// Get the first node from the list, in the document order
// Takes O(N) time in the size of the list, with a cache that is invalidated
// when the list is modified.
MultiId.prototype.getFirst = function() {
/* jshint bitwise: false */
if (!this.firstNode) {
var nid;
for (nid in this.nodes) {
if (this.firstNode === undefined ||
this.firstNode.compareDocumentPosition(this.nodes[nid]) & Node.DOCUMENT_POSITION_PRECEDING) {
this.firstNode = this.nodes[nid];
}
}
}
return this.firstNode;
};
// If there is only one node left, return it. Otherwise return "this".
MultiId.prototype.downgrade = function() {
if (this.length === 1) {
var nid;
for (nid in this.nodes) {
return this.nodes[nid];
}
}
return this;
};

68
domino/DocumentFragment.js

@ -0,0 +1,68 @@
"use strict";
module.exports = DocumentFragment;
var Node = require('./Node.js');
var NodeList = require('./NodeList.js');
var ContainerNode = require('./ContainerNode.js');
var Element = require('./Element.js');
var select = require('./select.js');
var utils = require('./utils.js');
function DocumentFragment(doc) {
ContainerNode.call(this);
this.nodeType = Node.DOCUMENT_FRAGMENT_NODE;
this.ownerDocument = doc;
}
DocumentFragment.prototype = Object.create(ContainerNode.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;
}},
// Non-standard, but useful (github issue #73)
innerHTML: {
get: function() { return this.serialize(); },
set: utils.nyi
},
outerHTML: {
get: function() { return this.serialize(); },
set: utils.nyi
},
});

36
domino/DocumentType.js

@ -0,0 +1,36 @@
"use strict";
module.exports = DocumentType;
var Node = require('./Node.js');
var Leaf = require('./Leaf.js');
var ChildNode = require('./ChildNode.js');
function DocumentType(ownerDocument, name, publicId, systemId) {
Leaf.call(this);
this.nodeType = Node.DOCUMENT_TYPE_NODE;
this.ownerDocument = 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() {
return new DocumentType(this.ownerDocument, this.name, this.publicId, this.systemId);
}},
isEqual: { value: function isEqual(n) {
return this.name === n.name &&
this.publicId === n.publicId &&
this.systemId === n.systemId;
}}
});
Object.defineProperties(DocumentType.prototype, ChildNode);

1202
domino/Element.js
File diff suppressed because it is too large
View File

66
domino/Event.js

@ -0,0 +1,66 @@
"use strict";
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;
}},
});

298
domino/EventTarget.js

@ -0,0 +1,298 @@
"use strict";
var Event = require('./Event.js');
var MouseEvent = require('./MouseEvent.js');
var utils = require('./utils.js');
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 = Object.create(null);
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 new 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
/* falls through */
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._immediatePropagationStopped) 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 new 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 ii = 0, nn = ancestors.length; ii < nn; ii++) {
invoke(ancestors[ii], 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 = Object.create(null);
this._handlers[type] = handler;
},
_getEventHandler: function _getEventHandler(type) {
return (this._handlers && this._handlers[type]) || null;
}
};

92
domino/FilteredElementList.js

@ -0,0 +1,92 @@
"use strict";
module.exports = FilteredElementList;
var Node = require('./Node.js');
//
// 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.
//
// XXX this should inherit from NodeList
function FilteredElementList(root, filter) {
this.root = root;
this.filter = filter;
this.lastModTime = root.lastModTime;
this.done = false;
this.cache = [];
this.traverse();
}
FilteredElementList.prototype = Object.create(Object.prototype, {
length: { get: function() {
this.checkcache();
if (!this.done) this.traverse();
return this.cache.length;
} },
item: { value: function(n) {
this.checkcache();
if (!this.done && n >= this.cache.length) {
// This can lead to O(N^2) behavior if we stop when we get to n
// and the caller is iterating through the items in order; so
// be sure to do the full traverse here.
this.traverse(/*n*/);
}
return this.cache[n];
} },
checkcache: { value: 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: { value: 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()) !== null) {
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: { value: 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;
} },
});

7264
domino/HTMLParser.js
File diff suppressed because it is too large
View File

37
domino/Leaf.js

@ -0,0 +1,37 @@
"use strict";
module.exports = Leaf;
var Node = require('./Node.js');
var NodeList = require('./NodeList.js');
var utils = require('./utils.js');
var HierarchyRequestError = utils.HierarchyRequestError;
var NotFoundError = utils.NotFoundError;
// This class defines common functionality for node subtypes that
// can never have children
function Leaf() {
Node.call(this);
}
Leaf.prototype = Object.create(Node.prototype, {
hasChildNodes: { value: function() { return false; }},
firstChild: { value: null },
lastChild: { value: null },
insertBefore: { value: function(node, child) {
if (!node.nodeType) throw new TypeError('not a node');
HierarchyRequestError();
}},
replaceChild: { value: function(node, child) {
if (!node.nodeType) throw new TypeError('not a node');
HierarchyRequestError();
}},
removeChild: { value: function(node) {
if (!node.nodeType) throw new TypeError('not a node');
NotFoundError();
}},
removeChildren: { value: function() { /* no op */ }},
childNodes: { get: function() {
if (!this._childNodes) this._childNodes = new NodeList();
return this._childNodes;
}}
});

44
domino/LinkedList.js

@ -0,0 +1,44 @@
"use strict";
var utils = require('./utils.js');
var LinkedList = module.exports = {
// basic validity tests on a circular linked list a
valid: function(a) {
utils.assert(a, "list falsy");
utils.assert(a._previousSibling, "previous falsy");
utils.assert(a._nextSibling, "next falsy");
// xxx check that list is actually circular
return true;
},
// insert a before b
insertBefore: function(a, b) {
utils.assert(LinkedList.valid(a) && LinkedList.valid(b));
var a_first = a, a_last = a._previousSibling;
var b_first = b, b_last = b._previousSibling;
a_first._previousSibling = b_last;
a_last._nextSibling = b_first;
b_last._nextSibling = a_first;
b_first._previousSibling = a_last;
utils.assert(LinkedList.valid(a) && LinkedList.valid(b));
},
// replace a single node a with a list b (which could be null)
replace: function(a, b) {
utils.assert(LinkedList.valid(a) && (b===null || LinkedList.valid(b)));
if (b!==null) {
LinkedList.insertBefore(b, a);
}
LinkedList.remove(a);
utils.assert(LinkedList.valid(a) && (b===null || LinkedList.valid(b)));
},
// remove single node a from its list
remove: function(a) {
utils.assert(LinkedList.valid(a));
var prev = a._previousSibling;
if (prev === a) { return; }
var next = a._nextSibling;
prev._nextSibling = next;
next._previousSibling = prev;
a._previousSibling = a._nextSibling = a;
utils.assert(LinkedList.valid(a));
}
};

56
domino/Location.js

@ -0,0 +1,56 @@
"use strict";
var URL = require('./URL.js');
var URLUtils = require('./URLUtils.js');
module.exports = Location;
function Location(window, href) {
this._window = window;
this._href = href;
}
Location.prototype = Object.create(URLUtils.prototype, {
constructor: { value: Location },
// Special behavior when href is set
href: {
get: function() { return this._href; },
set: function(v) { this.assign(v); }
},
assign: { value: function(url) {
// Resolve the new url against the current one
// XXX:
// This is not actually correct. It should be resolved against
// the URL of the document of the script. For now, though, I only
// support a single window and there is only one base url.
// So this is good enough for now.
var current = new URL(this._href);
var newurl = current.resolve(url);
// Save the new url
this._href = newurl;
// Start loading the new document!
// XXX
// This is just something hacked together.
// The real algorithm is: http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html#navigate
}},
replace: { value: function(url) {
// XXX
// Since we aren't tracking history yet, replace is the same as assign
this.assign(url);
}},
reload: { value: function() {
// XXX:
// Actually, the spec is a lot more complicated than this
this.assign(this.href);
}},
toString: { value: function() {
return this.href;
}}
});

52
domino/MouseEvent.js

@ -0,0 +1,52 @@
"use strict";
var UIEvent = require('./UIEvent.js');
module.exports = MouseEvent;
function MouseEvent() {
// Just use the superclass constructor to initialize
UIEvent.call(this);
this.screenX = this.screenY = this.clientX = this.clientY = 0;
this.ctrlKey = this.altKey = this.shiftKey = this.metaKey = false;
this.button = 0;
this.buttons = 1;
this.relatedTarget = null;
}
MouseEvent.prototype = Object.create(UIEvent.prototype, {
constructor: { value: MouseEvent },
initMouseEvent: { value: function(type, bubbles, cancelable,
view, detail,
screenX, screenY, clientX, clientY,
ctrlKey, altKey, shiftKey, metaKey,
button, relatedTarget) {
this.initEvent(type, bubbles, cancelable, view, detail);
this.screenX = screenX;
this.screenY = screenY;
this.clientX = clientX;
this.clientY = clientY;
this.ctrlKey = ctrlKey;
this.altKey = altKey;
this.shiftKey = shiftKey;
this.metaKey = metaKey;
this.button = button;
switch(button) {
case 0: this.buttons = 1; break;
case 1: this.buttons = 4; break;
case 2: this.buttons = 2; break;
default: this.buttons = 0; break;
}
this.relatedTarget = relatedTarget;
}},
getModifierState: { value: function(key) {
switch(key) {
case "Alt": return this.altKey;
case "Control": return this.ctrlKey;
case "Shift": return this.shiftKey;
case "Meta": return this.metaKey;
default: return false;
}
}}
});

9
domino/MutationConstants.js

@ -0,0 +1,9 @@
"use strict";
module.exports = {
VALUE: 1, // The value of a Text, Comment or PI node changed
ATTR: 2, // A new attribute was added or an attribute value and/or prefix changed
REMOVE_ATTR: 3, // An attribute was removed
REMOVE: 4, // A node was removed
MOVE: 5, // A node was moved
INSERT: 6 // A node (or a subtree of nodes) was inserted
};

41
domino/NamedNodeMap.js

@ -0,0 +1,41 @@
"use strict";
module.exports = NamedNodeMap;
var utils = require('./utils.js');
/* This is a hacky implementation of NamedNodeMap, intended primarily to
* satisfy clients (like dompurify and the web-platform-tests) which check
* to ensure that Node#attributes instanceof NamedNodeMap. */
function NamedNodeMap(element) {
this.element = element;
}
Object.defineProperties(NamedNodeMap.prototype, {
length: { get: utils.shouldOverride },
item: { value: utils.shouldOverride },
getNamedItem: { value: function getNamedItem(qualifiedName) {
return this.element.getAttributeNode(qualifiedName);
} },
getNamedItemNS: { value: function getNamedItemNS(namespace, localName) {
return this.element.getAttributeNodeNS(namespace, localName);
} },
setNamedItem: { value: utils.nyi },
setNamedItemNS: { value: utils.nyi },
removeNamedItem: { value: function removeNamedItem(qualifiedName) {
var attr = this.element.getAttributeNode(qualifiedName);
if (attr) {
this.element.removeAttribute(qualifiedName);
return attr;
}
utils.NotFoundError();
} },
removeNamedItemNS: { value: function removeNamedItemNS(ns, lname) {
var attr = this.element.getAttributeNodeNS(ns, lname);
if (attr) {
this.element.removeAttributeNS(ns, lname);
return attr;
}
utils.NotFoundError();
} },
});

17
domino/NavigatorID.js

@ -0,0 +1,17 @@
"use strict";
// https://html.spec.whatwg.org/multipage/webappapis.html#navigatorid
var NavigatorID = Object.create(null, {
appCodeName: { value: "Mozilla" },
appName: { value: "Netscape" },
appVersion: { value: "4.0" },
platform: { value: "" },
product: { value: "Gecko" },
productSub: { value: "20100101" },
userAgent: { value: "" },
vendor: { value: "" },
vendorSub: { value: "" },
taintEnabled: { value: function() { return false; } }
});
module.exports = NavigatorID;

738
domino/Node.js

@ -0,0 +1,738 @@
"use strict";
module.exports = Node;
var EventTarget = require('./EventTarget.js');
var LinkedList = require('./LinkedList.js');
var NodeUtils = require('./NodeUtils.js');
var utils = require('./utils.js');
// All nodes have a nodeType and an ownerDocument.
// Once inserted, they also have a parentNode.
// This is an abstract class; all nodes in a document are instances
// of a subtype, so all the properties are defined by more specific
// constructors.
function Node() {
EventTarget.call(this);
this.parentNode = null;
this._nextSibling = this._previousSibling = this;
this._index = undefined;
}
var ELEMENT_NODE = Node.ELEMENT_NODE = 1;
var ATTRIBUTE_NODE = Node.ATTRIBUTE_NODE = 2;
var TEXT_NODE = Node.TEXT_NODE = 3;
var CDATA_SECTION_NODE = Node.CDATA_SECTION_NODE = 4;
var ENTITY_REFERENCE_NODE = Node.ENTITY_REFERENCE_NODE = 5;
var ENTITY_NODE = Node.ENTITY_NODE = 6;
var PROCESSING_INSTRUCTION_NODE = Node.PROCESSING_INSTRUCTION_NODE = 7;
var COMMENT_NODE = Node.COMMENT_NODE = 8;
var DOCUMENT_NODE = Node.DOCUMENT_NODE = 9;
var DOCUMENT_TYPE_NODE = Node.DOCUMENT_TYPE_NODE = 10;
var DOCUMENT_FRAGMENT_NODE = Node.DOCUMENT_FRAGMENT_NODE = 11;
var NOTATION_NODE = Node.NOTATION_NODE = 12;
var DOCUMENT_POSITION_DISCONNECTED = Node.DOCUMENT_POSITION_DISCONNECTED = 0x01;
var DOCUMENT_POSITION_PRECEDING = Node.DOCUMENT_POSITION_PRECEDING = 0x02;
var DOCUMENT_POSITION_FOLLOWING = Node.DOCUMENT_POSITION_FOLLOWING = 0x04;
var DOCUMENT_POSITION_CONTAINS = Node.DOCUMENT_POSITION_CONTAINS = 0x08;
var DOCUMENT_POSITION_CONTAINED_BY = Node.DOCUMENT_POSITION_CONTAINED_BY = 0x10;
var DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20;
Node.prototype = Object.create(EventTarget.prototype, {
// Node that are not inserted into the tree inherit a null parent
// XXX: the baseURI attribute is defined by dom core, but
// a correct implementation of it requires HTML features, so
// we'll come back to this later.
baseURI: { get: utils.nyi },
parentElement: { get: function() {
return (this.parentNode && this.parentNode.nodeType===ELEMENT_NODE) ? this.parentNode : null;
}},
hasChildNodes: { value: utils.shouldOverride },
firstChild: { get: utils.shouldOverride },
lastChild: { get: utils.shouldOverride },
previousSibling: { get: function() {
var parent = this.parentNode;
if (!parent) return null;
if (this === parent.firstChild) return null;
return this._previousSibling;
}},
nextSibling: { get: function() {
var parent = this.parentNode, next = this._nextSibling;
if (!parent) return null;
if (next === parent.firstChild) return null;
return next;
}},
textContent: {
// Should override for DocumentFragment/Element/Attr/Text/PI/Comment
get: function() { return null; },
set: function(v) { /* do nothing */ },
},
_countChildrenOfType: { value: function(type) {
var sum = 0;
for (var kid = this.firstChild; kid !== null; kid = kid.nextSibling) {
if (kid.nodeType === type) sum++;
}
return sum;
}},
_ensureInsertValid: { value: function _ensureInsertValid(node, child, isPreinsert) {
var parent = this, i, kid;
if (!node.nodeType) throw new TypeError('not a node');
// 1. If parent is not a Document, DocumentFragment, or Element
// node, throw a HierarchyRequestError.
switch (parent.nodeType) {
case DOCUMENT_NODE:
case DOCUMENT_FRAGMENT_NODE:
case ELEMENT_NODE:
break;
default: utils.HierarchyRequestError();
}
// 2. If node is a host-including inclusive ancestor of parent,
// throw a HierarchyRequestError.
if (node.isAncestor(parent)) utils.HierarchyRequestError();
// 3. If child is not null and its parent is not parent, then
// throw a NotFoundError. (replaceChild omits the 'child is not null'
// and throws a TypeError here if child is null.)
if (child !== null || !isPreinsert) {
if (child.parentNode !== parent) utils.NotFoundError();
}
// 4. If node is not a DocumentFragment, DocumentType, Element,
// Text, ProcessingInstruction, or Comment node, throw a
// HierarchyRequestError.
switch (node.nodeType) {
case DOCUMENT_FRAGMENT_NODE:
case DOCUMENT_TYPE_NODE:
case ELEMENT_NODE:
case TEXT_NODE:
case PROCESSING_INSTRUCTION_NODE:
case COMMENT_NODE:
break;
default: utils.HierarchyRequestError();
}
// 5. If either node is a Text node and parent is a document, or
// node is a doctype and parent is not a document, throw a
// HierarchyRequestError.
// 6. If parent is a document, and any of the statements below, switched
// on node, are true, throw a HierarchyRequestError.
if (parent.nodeType === DOCUMENT_NODE) {
switch (node.nodeType) {
case TEXT_NODE:
utils.HierarchyRequestError();
break;
case DOCUMENT_FRAGMENT_NODE:
// 6a1. If node has more than one element child or has a Text
// node child.
if (node._countChildrenOfType(TEXT_NODE) > 0)
utils.HierarchyRequestError();
switch (node._countChildrenOfType(ELEMENT_NODE)) {
case 0:
break;
case 1:
// 6a2. Otherwise, if node has one element child and either
// parent has an element child, child is a doctype, or child
// is not null and a doctype is following child. [preinsert]
// 6a2. Otherwise, if node has one element child and either
// parent has an element child that is not child or a
// doctype is following child. [replaceWith]
if (child !== null /* always true here for replaceWith */) {
if (isPreinsert && child.nodeType === DOCUMENT_TYPE_NODE)
utils.HierarchyRequestError();
for (kid = child.nextSibling; kid !== null; kid = kid.nextSibling) {
if (kid.nodeType === DOCUMENT_TYPE_NODE)
utils.HierarchyRequestError();
}
}
i = parent._countChildrenOfType(ELEMENT_NODE);
if (isPreinsert) {
// "parent has an element child"
if (i > 0)
utils.HierarchyRequestError();
} else {
// "parent has an element child that is not child"
if (i > 1 || (i === 1 && child.nodeType !== ELEMENT_NODE))
utils.HierarchyRequestError();
}
break;
default: // 6a1, continued. (more than one Element child)
utils.HierarchyRequestError();
}
break;
case ELEMENT_NODE:
// 6b. parent has an element child, child is a doctype, or
// child is not null and a doctype is following child. [preinsert]
// 6b. parent has an element child that is not child or a
// doctype is following child. [replaceWith]
if (child !== null /* always true here for replaceWith */) {
if (isPreinsert && child.nodeType === DOCUMENT_TYPE_NODE)
utils.HierarchyRequestError();
for (kid = child.nextSibling; kid !== null; kid = kid.nextSibling) {
if (kid.nodeType === DOCUMENT_TYPE_NODE)
utils.HierarchyRequestError();
}
}
i = parent._countChildrenOfType(ELEMENT_NODE);
if (isPreinsert) {
// "parent has an element child"
if (i > 0)
utils.HierarchyRequestError();
} else {
// "parent has an element child that is not child"
if (i > 1 || (i === 1 && child.nodeType !== ELEMENT_NODE))
utils.HierarchyRequestError();
}
break;
case DOCUMENT_TYPE_NODE:
// 6c. parent has a doctype child, child is non-null and an
// element is preceding child, or child is null and parent has
// an element child. [preinsert]
// 6c. parent has a doctype child that is not child, or an
// element is preceding child. [replaceWith]
if (child === null) {
if (parent._countChildrenOfType(ELEMENT_NODE))
utils.HierarchyRequestError();
} else {
// child is always non-null for [replaceWith] case
for (kid = parent.firstChild; kid !== null; kid = kid.nextSibling) {
if (kid === child) break;
if (kid.nodeType === ELEMENT_NODE)
utils.HierarchyRequestError();
}
}
i = parent._countChildrenOfType(DOCUMENT_TYPE_NODE);
if (isPreinsert) {
// "parent has an doctype child"
if (i > 0)
utils.HierarchyRequestError();
} else {
// "parent has an doctype child that is not child"
if (i > 1 || (i === 1 && child.nodeType !== DOCUMENT_TYPE_NODE))
utils.HierarchyRequestError();
}
break;
}
} else {
// 5, continued: (parent is not a document)
if (node.nodeType === DOCUMENT_TYPE_NODE) utils.HierarchyRequestError();
}
}},
insertBefore: { value: function insertBefore(node, child) {
var parent = this;
// 1. Ensure pre-insertion validity
parent._ensureInsertValid(node, child, true);
// 2. Let reference child be child.
var refChild = child;
// 3. If reference child is node, set it to node's next sibling
if (refChild === node) { refChild = node.nextSibling; }
// 4. Adopt node into parent's node document.
parent.doc.adoptNode(node);
// 5. Insert node into parent before reference child.
node._insertOrReplace(parent, refChild, false);
// 6. Return node
return node;
}},
appendChild: { value: function(child) {
// This invokes _appendChild after doing validity checks.
return this.insertBefore(child, null);
}},
_appendChild: { value: function(child) {
child._insertOrReplace(this, null, false);
}},
removeChild: { value: function removeChild(child) {
var parent = this;
if (!child.nodeType) throw new TypeError('not a node');
if (child.parentNode !== parent) utils.NotFoundError();
child.remove();
return child;
}},
// To replace a `child` with `node` within a `parent` (this)
replaceChild: { value: function replaceChild(node, child) {
var parent = this;
// Ensure validity (slight differences from pre-insertion check)
parent._ensureInsertValid(node, child, false);
// Adopt node into parent's node document.
if (node.doc !== parent.doc) {
// XXX adoptNode has side-effect of removing node from its parent
// and generating a mutation event, thus causing the _insertOrReplace
// to generate two deletes and an insert instead of a 'move'
// event. It looks like the new MutationObserver stuff avoids
// this problem, but for now let's only adopt (ie, remove `node`
// from its parent) here if we need to.
parent.doc.adoptNode(node);
}
// Do the replace.
node._insertOrReplace(parent, child, true);
return child;
}},
// See: http://ejohn.org/blog/comparing-document-position/
contains: { value: function contains(node) {
if (node === null) { return false; }
if (this === node) { return true; /* inclusive descendant */ }
/* jshint bitwise: false */
return (this.compareDocumentPosition(node) &
DOCUMENT_POSITION_CONTAINED_BY) !== 0;
}},
compareDocumentPosition: { value: function compareDocumentPosition(that){
// Basic algorithm for finding the relative position of two nodes.
// Make a list the ancestors of each node, starting with the
// document element and proceeding down to the nodes themselves.
// Then, loop through the lists, looking for the first element
// that differs. The order of those two elements give the
// order of their descendant nodes. Or, if one list is a prefix
// of the other one, then that node contains the other.
if (this === that) return 0;
// If they're not owned by the same document or if one is rooted
// and one is not, then they're disconnected.
if (this.doc !== that.doc ||
this.rooted !== that.rooted)
return (DOCUMENT_POSITION_DISCONNECTED +
DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC);
// Get arrays of ancestors for this and that
var these = [], those = [];
for(var n = this; n !== null; n = n.parentNode) these.push(n);
for(n = that; n !== null; n = n.parentNode) those.push(n);
these.reverse(); // So we start with the outermost
those.reverse();
if (these[0] !== those[0]) // No common ancestor
return (DOCUMENT_POSITION_DISCONNECTED +
DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC);
n = Math.min(these.length, those.length);
for(var i = 1; i < n; i++) {
if (these[i] !== those[i]) {
// We found two different ancestors, so compare
// their positions
if (these[i].index < those[i].index)
return DOCUMENT_POSITION_FOLLOWING;
else
return DOCUMENT_POSITION_PRECEDING;
}
}
// If we get to here, then one of the nodes (the one with the
// shorter list of ancestors) contains the other one.
if (these.length < those.length)
return (DOCUMENT_POSITION_FOLLOWING +
DOCUMENT_POSITION_CONTAINED_BY);
else
return (DOCUMENT_POSITION_PRECEDING +
DOCUMENT_POSITION_CONTAINS);
}},
isSameNode: {value : function isSameNode(node) {
return this === node;
}},
// This method implements the generic parts of node equality testing
// and defers to the (non-recursive) type-specific isEqual() method
// defined by subclasses
isEqualNode: { value: function isEqualNode(node) {
if (!node) return false;
if (node.nodeType !== this.nodeType) return false;
// Check type-specific properties for equality
if (!this.isEqual(node)) return false;
// Now check children for number and equality
for (var c1 = this.firstChild, c2 = node.firstChild;
c1 && c2;
c1 = c1.nextSibling, c2 = c2.nextSibling) {
if (!c1.isEqualNode(c2)) return false;
}
return c1 === null && c2 === null;
}},
// This method delegates shallow cloning to a clone() method
// that each concrete subclass must implement
cloneNode: { value: function(deep) {
// Clone this node
var clone = this.clone();
// Handle the recursive case if necessary
if (deep) {
for (var kid = this.firstChild; kid !== null; kid = kid.nextSibling) {
clone._appendChild(kid.cloneNode(true));
}
}
return clone;
}},
lookupPrefix: { value: function lookupPrefix(ns) {
var e;
if (ns === '' || ns === null || ns === undefined) return null;
switch(this.nodeType) {
case ELEMENT_NODE:
return this._lookupNamespacePrefix(ns, this);
case DOCUMENT_NODE:
e = this.documentElement;
return e ? e.lookupPrefix(ns) : null;
case ENTITY_NODE:
case NOTATION_NODE:
case DOCUMENT_FRAGMENT_NODE:
case DOCUMENT_TYPE_NODE:
return null;
case ATTRIBUTE_NODE:
e = this.ownerElement;
return e ? e.lookupPrefix(ns) : null;
default:
e = this.parentElement;
return e ? e.lookupPrefix(ns) : null;
}
}},
lookupNamespaceURI: {value: function lookupNamespaceURI(prefix) {
if (prefix === '' || prefix === undefined) { prefix = null; }
var e;
switch(this.nodeType) {
case ELEMENT_NODE:
return utils.shouldOverride();
case DOCUMENT_NODE:
e = this.documentElement;
return e ? e.lookupNamespaceURI(prefix) : null;
case ENTITY_NODE:
case NOTATION_NODE:
case DOCUMENT_TYPE_NODE:
case DOCUMENT_FRAGMENT_NODE:
return null;
case ATTRIBUTE_NODE:
e = this.ownerElement;
return e ? e.lookupNamespaceURI(prefix) : null;
default:
e = this.parentElement;
return e ? e.lookupNamespaceURI(prefix) : null;
}
}},
isDefaultNamespace: { value: function isDefaultNamespace(ns) {
if (ns === '' || ns === undefined) { ns = null; }
var defaultNamespace = this.lookupNamespaceURI(null);
return (defaultNamespace === ns);
}},
// Utility methods for nodes. Not part of the DOM
// Return the index of this node in its parent.
// Throw if no parent, or if this node is not a child of its parent
index: { get: function() {
var parent = this.parentNode;
if (this === parent.firstChild) return 0; // fast case
var kids = parent.childNodes;
if (this._index === undefined || kids[this._index] !== this) {
// Ensure that we don't have an O(N^2) blowup if none of the
// kids have defined indices yet and we're traversing via
// nextSibling or previousSibling
for (var i=0; i<kids.length; i++) {
kids[i]._index = i;
}
utils.assert(kids[this._index] === this);
}
return this._index;
}},
// Return true if this node is equal to or is an ancestor of that node
// Note that nodes are considered to be ancestors of themselves
isAncestor: { value: function(that) {
// If they belong to different documents, then they're unrelated.
if (this.doc !== that.doc) return false;
// If one is rooted and one isn't then they're not related
if (this.rooted !== that.rooted) return false;
// Otherwise check by traversing the parentNode chain
for(var e = that; e; e = e.parentNode) {
if (e === this) return true;
}
return false;
}},
// DOMINO Changed the behavior to conform with the specs. See:
// https://groups.google.com/d/topic/mozilla.dev.platform/77sIYcpdDmc/discussion
ensureSameDoc: { value: function(that) {
if (that.ownerDocument === null) {
that.ownerDocument = this.doc;
}
else if(that.ownerDocument !== this.doc) {
utils.WrongDocumentError();
}
}},
removeChildren: { value: utils.shouldOverride },
// Insert this node as a child of parent before the specified child,
// or insert as the last child of parent if specified child is null,
// or replace the specified child with this node, firing mutation events as
// necessary
_insertOrReplace: { value: function _insertOrReplace(parent, before, isReplace) {
var child = this, before_index, i;
if (child.nodeType === DOCUMENT_FRAGMENT_NODE && child.rooted) {
utils.HierarchyRequestError();
}
/* Ensure index of `before` is cached before we (possibly) remove it. */
if (parent._childNodes) {
before_index = (before === null) ? parent._childNodes.length :
before.index; /* ensure _index is cached */
// If we are already a child of the specified parent, then
// the index may have to be adjusted.
if (child.parentNode === parent) {
var child_index = child.index;
// If the child is before the spot it is to be inserted at,
// then when it is removed, the index of that spot will be
// reduced.
if (child_index < before_index) {
before_index--;
}
}
}
// Delete the old child
if (isReplace) {
if (before.rooted) before.doc.mutateRemove(before);
before.parentNode = null;
}
var n = before;
if (n === null) { n = parent.firstChild; }
// If both the child and the parent are rooted, then we want to
// transplant the child without uprooting and rerooting it.
var bothRooted = child.rooted && parent.rooted;
if (child.nodeType === DOCUMENT_FRAGMENT_NODE) {
var spliceArgs = [0, isReplace ? 1 : 0], next;
for (var kid = child.firstChild; kid !== null; kid = next) {
next = kid.nextSibling;
spliceArgs.push(kid);
kid.parentNode = parent;
}
var len = spliceArgs.length;
// Add all nodes to the new parent, overwriting the old child
if (isReplace) {
LinkedList.replace(n, len > 2 ? spliceArgs[2] : null);
} else if (len > 2 && n !== null) {
LinkedList.insertBefore(spliceArgs[2], n);
}
if (parent._childNodes) {
spliceArgs[0] = (before === null) ?
parent._childNodes.length : before._index;
parent._childNodes.splice.apply(parent._childNodes, spliceArgs);
for (i=2; i<len; i++) {
spliceArgs[i]._index = spliceArgs[0] + (i - 2);
}
} else if (parent._firstChild === before) {
if (len > 2) {
parent._firstChild = spliceArgs[2];
} else if (isReplace) {
parent._firstChild = null;
}
}
// Remove all nodes from the document fragment
if (child._childNodes) {
child._childNodes.length = 0;
} else {
child._firstChild = null;
}
// Call the mutation handlers
// Use spliceArgs since the original array has been destroyed. The
// liveness guarantee requires us to clone the array so that
// references to the childNodes of the DocumentFragment will be empty
// when the insertion handlers are called.
if (parent.rooted) {
parent.modify();
for (i = 2; i < len; i++) {
parent.doc.mutateInsert(spliceArgs[i]);
}
}
} else {
if (before === child) { return; }
if (bothRooted) {
// Remove the child from its current position in the tree
// without calling remove(), since we don't want to uproot it.
child._remove();
} else if (child.parentNode) {
child.remove();
}
// Insert it as a child of its new parent
child.parentNode = parent;
if (isReplace) {
LinkedList.replace(n, child);
if (parent._childNodes) {
child._index = before_index;
parent._childNodes[before_index] = child;
} else if (parent._firstChild === before) {
parent._firstChild = child;
}
} else {
if (n !== null) {
LinkedList.insertBefore(child, n);
}
if (parent._childNodes) {
child._index = before_index;
parent._childNodes.splice(before_index, 0, child);
} else if (parent._firstChild === before) {
parent._firstChild = child;
}
}
if (bothRooted) {
parent.modify();
// Generate a move mutation event
parent.doc.mutateMove(child);
} else if (parent.rooted) {
parent.modify();
parent.doc.mutateInsert(child);
}
}
}},
// Return the lastModTime value for this node. (For use as a
// cache invalidation mechanism. If the node does not already
// have one, initialize it from the owner document's modclock
// property. (Note that modclock does not return the actual
// time; it is simply a counter incremented on each document
// modification)
lastModTime: { get: function() {
if (!this._lastModTime) {
this._lastModTime = this.doc.modclock;
}
return this._lastModTime;
}},
// Increment the owner document's modclock and use the new
// value to update the lastModTime value for this node and
// all of its ancestors. Nodes that have never had their
// lastModTime value queried do not need to have a
// lastModTime property set on them since there is no
// previously queried value to ever compare the new value
// against, so only update nodes that already have a
// _lastModTime property.
modify: { value: function() {
if (this.doc.modclock) { // Skip while doc.modclock == 0
var time = ++this.doc.modclock;
for(var n = this; n; n = n.parentElement) {
if (n._lastModTime) {
n._lastModTime = time;
}
}
}
}},
// This attribute is not part of the DOM but is quite helpful.
// It returns the document with which a node is associated. Usually
// this is the ownerDocument. But ownerDocument is null for the
// document object itself, so this is a handy way to get the document
// regardless of the node type
doc: { get: function() {
return this.ownerDocument || this;
}},
// If the node has a nid (node id), then it is rooted in a document
rooted: { get: function() {
return !!this._nid;
}},
normalize: { value: function() {
var next;
for (var child=this.firstChild; child !== null; child=next) {
next = child.nextSibling;
if (child.normalize) {
child.normalize();
}
if (child.nodeType !== Node.TEXT_NODE) {
continue;
}
if (child.nodeValue === "") {
this.removeChild(child);
continue;
}
var prevChild = child.previousSibling;
if (prevChild === null) {
continue;
} else if (prevChild.nodeType === Node.TEXT_NODE) {
// merge this with previous and remove the child
prevChild.appendData(child.nodeValue);
this.removeChild(child);
}
}
}},
// Convert the children of a node to an HTML string.
// This is used by the innerHTML getter
// The serialization spec is at:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#serializing-html-fragments
//
// The serialization logic is intentionally implemented in a separate
// `NodeUtils` helper instead of the more obvious choice of a private
// `_serializeOne()` method on the `Node.prototype` in order to avoid
// the megamorphic `this._serializeOne` property access, which reduces
// performance unnecessarily. If you need specialized behavior for a
// certain subclass, you'll need to implement that in `NodeUtils`.
// See https://github.com/fgnass/domino/pull/142 for more information.
serialize: { value: function() {
var s = '';
for (var kid = this.firstChild; kid !== null; kid = kid.nextSibling) {
s += NodeUtils.serializeOne(kid, this);
}
return s;
}},
// Non-standard, but often useful for debugging.
outerHTML: {
get: function() {
return NodeUtils.serializeOne(this, { nodeType: 0 });
},
set: utils.nyi,
},
// mirror node type properties in the prototype, so they are present
// in instances of Node (and subclasses)
ELEMENT_NODE: { value: ELEMENT_NODE },
ATTRIBUTE_NODE: { value: ATTRIBUTE_NODE },
TEXT_NODE: { value: TEXT_NODE },
CDATA_SECTION_NODE: { value: CDATA_SECTION_NODE },
ENTITY_REFERENCE_NODE: { value: ENTITY_REFERENCE_NODE },
ENTITY_NODE: { value: ENTITY_NODE },
PROCESSING_INSTRUCTION_NODE: { value: PROCESSING_INSTRUCTION_NODE },
COMMENT_NODE: { value: COMMENT_NODE },
DOCUMENT_NODE: { value: DOCUMENT_NODE },
DOCUMENT_TYPE_NODE: { value: DOCUMENT_TYPE_NODE },
DOCUMENT_FRAGMENT_NODE: { value: DOCUMENT_FRAGMENT_NODE },
NOTATION_NODE: { value: NOTATION_NODE },
DOCUMENT_POSITION_DISCONNECTED: { value: DOCUMENT_POSITION_DISCONNECTED },
DOCUMENT_POSITION_PRECEDING: { value: DOCUMENT_POSITION_PRECEDING },
DOCUMENT_POSITION_FOLLOWING: { value: DOCUMENT_POSITION_FOLLOWING },
DOCUMENT_POSITION_CONTAINS: { value: DOCUMENT_POSITION_CONTAINS },
DOCUMENT_POSITION_CONTAINED_BY: { value: DOCUMENT_POSITION_CONTAINED_BY },
DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: { value: DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC },
});

24
domino/NodeFilter.js

@ -0,0 +1,24 @@
"use strict";
var NodeFilter = {
// Constants for acceptNode()
FILTER_ACCEPT: 1,
FILTER_REJECT: 2,
FILTER_SKIP: 3,
// Constants for whatToShow
SHOW_ALL: 0xFFFFFFFF,
SHOW_ELEMENT: 0x1,
SHOW_ATTRIBUTE: 0x2, // historical
SHOW_TEXT: 0x4,
SHOW_CDATA_SECTION: 0x8, // historical
SHOW_ENTITY_REFERENCE: 0x10, // historical
SHOW_ENTITY: 0x20, // historical
SHOW_PROCESSING_INSTRUCTION: 0x40,
SHOW_COMMENT: 0x80,
SHOW_DOCUMENT: 0x100,
SHOW_DOCUMENT_TYPE: 0x200,
SHOW_DOCUMENT_FRAGMENT: 0x400,
SHOW_NOTATION: 0x800 // historical
};
module.exports = (NodeFilter.constructor = NodeFilter.prototype = NodeFilter);

217
domino/NodeIterator.js

@ -0,0 +1,217 @@
"use strict";
module.exports = NodeIterator;
var NodeFilter = require('./NodeFilter.js');
var NodeTraversal = require('./NodeTraversal.js');
var utils = require('./utils.js');
/* Private methods and helpers */
/**
* @based on WebKit's NodeIterator::moveToNext and NodeIterator::moveToPrevious
* https://trac.webkit.org/browser/trunk/Source/WebCore/dom/NodeIterator.cpp?rev=186279#L51
*/
function move(node, stayWithin, directionIsNext) {
if (directionIsNext) {
return NodeTraversal.next(node, stayWithin);
} else {
if (node === stayWithin) {
return null;
}
return NodeTraversal.previous(node, null);
}
}
function isInclusiveAncestor(node, possibleChild) {
for ( ; possibleChild; possibleChild = possibleChild.parentNode) {
if (node === possibleChild) { return true; }
}
return false;
}
/**
* @spec http://www.w3.org/TR/dom/#concept-nodeiterator-traverse
* @method
* @access private
* @param {NodeIterator} ni
* @param {string} direction One of 'next' or 'previous'.
* @return {Node|null}
*/
function traverse(ni, directionIsNext) {
var node, beforeNode;
node = ni._referenceNode;
beforeNode = ni._pointerBeforeReferenceNode;
while (true) {
if (beforeNode === directionIsNext) {
beforeNode = !beforeNode;
} else {
node = move(node, ni._root, directionIsNext);
if (node === null) {
return null;
}
}
var result = ni._internalFilter(node);
if (result === NodeFilter.FILTER_ACCEPT) {
break;
}
}
ni._referenceNode = node;
ni._pointerBeforeReferenceNode = beforeNode;
return node;
}
/* Public API */
/**
* Implemented version: http://www.w3.org/TR/2015/WD-dom-20150618/#nodeiterator
* Latest version: http://www.w3.org/TR/dom/#nodeiterator
*
* @constructor
* @param {Node} root
* @param {number} whatToShow [optional]
* @param {Function|NodeFilter} filter [optional]
* @throws Error
*/
function NodeIterator(root, whatToShow, filter) {
if (!root || !root.nodeType) {
utils.NotSupportedError();
}
// Read-only properties
this._root = root;
this._referenceNode = root;
this._pointerBeforeReferenceNode = true;
this._whatToShow = Number(whatToShow) || 0;
this._filter = filter || null;
this._active = false;
// Record active node iterators in the document, in order to perform
// "node iterator pre-removal steps".
root.doc._attachNodeIterator(this);
}
Object.defineProperties(NodeIterator.prototype, {
root: { get: function root() {
return this._root;
} },
referenceNode: { get: function referenceNode() {
return this._referenceNode;
} },
pointerBeforeReferenceNode: { get: function pointerBeforeReferenceNode() {
return this._pointerBeforeReferenceNode;
} },
whatToShow: { get: function whatToShow() {
return this._whatToShow;
} },
filter: { get: function filter() {
return this._filter;
} },
/**
* @method
* @param {Node} node
* @return {Number} Constant NodeFilter.FILTER_ACCEPT,
* NodeFilter.FILTER_REJECT or NodeFilter.FILTER_SKIP.
*/
_internalFilter: { value: function _internalFilter(node) {
/* jshint bitwise: false */
var result, filter;
if (this._active) {
utils.InvalidStateError();
}
// Maps nodeType to whatToShow
if (!(((1 << (node.nodeType - 1)) & this._whatToShow))) {
return NodeFilter.FILTER_SKIP;
}
filter = this._filter;
if (filter === null) {
result = NodeFilter.FILTER_ACCEPT;
} else {
this._active = true;
try {
if (typeof filter === 'function') {
result = filter(node);
} else {
result = filter.acceptNode(node);
}
} finally {
this._active = false;
}
}
// Note that coercing to a number means that
// `true` becomes `1` (which is NodeFilter.FILTER_ACCEPT)
// `false` becomes `0` (neither accept, reject, or skip)
return (+result);
} },
/**
* @spec https://dom.spec.whatwg.org/#nodeiterator-pre-removing-steps
* @method
* @return void
*/
_preremove: { value: function _preremove(toBeRemovedNode) {
if (isInclusiveAncestor(toBeRemovedNode, this._root)) { return; }
if (!isInclusiveAncestor(toBeRemovedNode, this._referenceNode)) { return; }
if (this._pointerBeforeReferenceNode) {
var next = toBeRemovedNode;
while (next.lastChild) {
next = next.lastChild;
}
next = NodeTraversal.next(next, this.root);
if (next) {
this._referenceNode = next;
return;
}
this._pointerBeforeReferenceNode = false;
// fall through
}
if (toBeRemovedNode.previousSibling === null) {
this._referenceNode = toBeRemovedNode.parentNode;
} else {
this._referenceNode = toBeRemovedNode.previousSibling;
var lastChild;
for (lastChild = this._referenceNode.lastChild;
lastChild;
lastChild = this._referenceNode.lastChild) {
this._referenceNode = lastChild;
}
}
} },
/**
* @spec http://www.w3.org/TR/dom/#dom-nodeiterator-nextnode
* @method
* @return {Node|null}
*/
nextNode: { value: function nextNode() {
return traverse(this, true);
} },
/**
* @spec http://www.w3.org/TR/dom/#dom-nodeiterator-previousnode
* @method
* @return {Node|null}
*/
previousNode: { value: function previousNode() {
return traverse(this, false);
} },
/**
* @spec http://www.w3.org/TR/dom/#dom-nodeiterator-detach
* @method
* @return void
*/
detach: { value: function detach() {
/* "The detach() method must do nothing.
* Its functionality (disabling a NodeIterator object) was removed,
* but the method itself is preserved for compatibility.
*/
} },
/** For compatibility with web-platform-tests. */
toString: { value: function toString() {
return "[object NodeIterator]";
} },
});

15
domino/NodeList.es5.js

@ -0,0 +1,15 @@
"use strict";
// No support for subclassing array, return an actual Array object.
function item(i) {
/* jshint validthis: true */
return this[i] || null;
}
function NodeList(a) {
if (!a) a = [];
a.item = item;
return a;
}
module.exports = NodeList;

12
domino/NodeList.es6.js

@ -0,0 +1,12 @@
/* jshint esversion: 6 */
"use strict";
module.exports = class NodeList extends Array {
constructor(a) {
super((a && a.length) || 0);
if (a) {
for (var idx in a) { this[idx] = a[idx]; }
}
}
item(i) { return this[i] || null; }
};

13
domino/NodeList.js

@ -0,0 +1,13 @@
"use strict";
var NodeList;
try {
// Attempt to use ES6-style Array subclass if possible.
NodeList = require('./NodeList.es6.js');
} catch (e) {
// No support for subclassing array, return an actual Array object.
NodeList = require('./NodeList.es5.js');
}
module.exports = NodeList;

87
domino/NodeTraversal.js

@ -0,0 +1,87 @@
"use strict";
/* exported NodeTraversal */
var NodeTraversal = module.exports = {
nextSkippingChildren: nextSkippingChildren,
nextAncestorSibling: nextAncestorSibling,
next: next,
previous: previous,
deepLastChild: deepLastChild
};
/**
* @based on WebKit's NodeTraversal::nextSkippingChildren
* https://trac.webkit.org/browser/trunk/Source/WebCore/dom/NodeTraversal.h?rev=179143#L109
*/
function nextSkippingChildren(node, stayWithin) {
if (node === stayWithin) {
return null;
}
if (node.nextSibling !== null) {
return node.nextSibling;
}
return nextAncestorSibling(node, stayWithin);
}
/**
* @based on WebKit's NodeTraversal::nextAncestorSibling
* https://trac.webkit.org/browser/trunk/Source/WebCore/dom/NodeTraversal.cpp?rev=179143#L93
*/
function nextAncestorSibling(node, stayWithin) {
for (node = node.parentNode; node !== null; node = node.parentNode) {
if (node === stayWithin) {
return null;
}
if (node.nextSibling !== null) {
return node.nextSibling;
}
}
return null;
}
/**
* @based on WebKit's NodeTraversal::next
* https://trac.webkit.org/browser/trunk/Source/WebCore/dom/NodeTraversal.h?rev=179143#L99
*/
function next(node, stayWithin) {
var n;
n = node.firstChild;
if (n !== null) {
return n;
}
if (node === stayWithin) {
return null;
}
n = node.nextSibling;
if (n !== null) {
return n;
}
return nextAncestorSibling(node, stayWithin);
}
/**
* @based on WebKit's NodeTraversal::deepLastChild
* https://trac.webkit.org/browser/trunk/Source/WebCore/dom/NodeTraversal.cpp?rev=179143#L116
*/
function deepLastChild(node) {
while (node.lastChild) {
node = node.lastChild;
}
return node;
}
/**
* @based on WebKit's NodeTraversal::previous
* https://trac.webkit.org/browser/trunk/Source/WebCore/dom/NodeTraversal.h?rev=179143#L121
*/
function previous(node, stayWithin) {
var p;
p = node.previousSibling;
if (p !== null) {
return deepLastChild(p);
}
p = node.parentNode;
if (p === stayWithin) {
return null;
}
return p;
}

168
domino/NodeUtils.js

@ -0,0 +1,168 @@
"use strict";
module.exports = {
// NOTE: The `serializeOne()` function used to live on the `Node.prototype`
// as a private method `Node#_serializeOne(child)`, however that requires
// a megamorphic property access `this._serializeOne` just to get to the
// method, and this is being done on lots of different `Node` subclasses,
// which puts a lot of pressure on V8's megamorphic stub cache. So by
// moving the helper off of the `Node.prototype` and into a separate
// function in this helper module, we get a monomorphic property access
// `NodeUtils.serializeOne` to get to the function and reduce pressure
// on the megamorphic stub cache.
// See https://github.com/fgnass/domino/pull/142 for more information.
serializeOne: serializeOne
};
var utils = require('./utils.js');
var NAMESPACE = utils.NAMESPACE;
var hasRawContent = {
STYLE: true,
SCRIPT: true,
XMP: true,
IFRAME: true,
NOEMBED: true,
NOFRAMES: true,
PLAINTEXT: true
};
var emptyElements = {
area: true,
base: true,
basefont: true,
bgsound: true,
br: true,
col: true,
embed: true,
frame: true,
hr: true,
img: true,
input: true,
keygen: true,
link: true,
meta: true,
param: true,
source: true,
track: true,
wbr: true
};
var extraNewLine = {
/* Removed in https://github.com/whatwg/html/issues/944
pre: true,
textarea: true,
listing: true
*/
};
function escape(s) {
return s.replace(/[&<>\u00A0]/g, function(c) {
switch(c) {
case '&': return '&amp;';
case '<': return '&lt;';
case '>': return '&gt;';
case '\u00A0': return '&nbsp;';
}
});
}
function escapeAttr(s) {
var toEscape = /[&"\u00A0]/g;
if (!toEscape.test(s)) {
// nothing to do, fast path
return s;
} else {
return s.replace(toEscape, function(c) {
switch(c) {
case '&': return '&amp;';
case '"': return '&quot;';
case '\u00A0': return '&nbsp;';
}
});
}
}
function attrname(a) {
var ns = a.namespaceURI;
if (!ns)
return a.localName;
if (ns === NAMESPACE.XML)
return 'xml:' + a.localName;
if (ns === NAMESPACE.XLINK)
return 'xlink:' + a.localName;
if (ns === NAMESPACE.XMLNS) {
if (a.localName === 'xmlns') return 'xmlns';
else return 'xmlns:' + a.localName;
}
return a.name;
}
function serializeOne(kid, parent) {
var s = '';
switch(kid.nodeType) {
case 1: //ELEMENT_NODE
var ns = kid.namespaceURI;
var html = ns === NAMESPACE.HTML;
var tagname = (html || ns === NAMESPACE.SVG || ns === NAMESPACE.MATHML) ? kid.localName : kid.tagName;
s += '<' + tagname;
for(var j = 0, k = kid._numattrs; j < k; j++) {
var a = kid._attr(j);
s += ' ' + attrname(a);
if (a.value !== undefined) s += '="' + escapeAttr(a.value) + '"';
}
s += '>';
if (!(html && emptyElements[tagname])) {
var ss = kid.serialize();
if (html && extraNewLine[tagname] && ss.charAt(0)==='\n') s += '\n';
// Serialize children and add end tag for all others
s += ss;
s += '</' + tagname + '>';
}
break;
case 3: //TEXT_NODE
case 4: //CDATA_SECTION_NODE
var parenttag;
if (parent.nodeType === 1 /*ELEMENT_NODE*/ &&
parent.namespaceURI === NAMESPACE.HTML)
parenttag = parent.tagName;
else
parenttag = '';
if (hasRawContent[parenttag] ||
(parenttag==='NOSCRIPT' && parent.ownerDocument._scripting_enabled)) {
s += kid.data;
} else {
s += escape(kid.data);
}
break;
case 8: //COMMENT_NODE
s += '<!--' + kid.data + '-->';
break;
case 7: //PROCESSING_INSTRUCTION_NODE
s += '<?' + kid.target + ' ' + kid.data + '?>';
break;
case 10: //DOCUMENT_TYPE_NODE
s += '<!DOCTYPE ' + kid.name;
if (false) {
// Latest HTML serialization spec omits the public/system ID
if (kid.publicID) {
s += ' PUBLIC "' + kid.publicId + '"';
}
if (kid.systemId) {
s += ' "' + kid.systemId + '"';
}
}
s += '>';
break;
default:
utils.InvalidStateError();
}
return s;
}

26
domino/NonDocumentTypeChildNode.js

@ -0,0 +1,26 @@
"use strict";
var Node = require('./Node.js');
var NonDocumentTypeChildNode = {
nextElementSibling: { get: function() {
if (this.parentNode) {
for (var kid = this.nextSibling; kid !== null; kid = kid.nextSibling) {
if (kid.nodeType === Node.ELEMENT_NODE) return kid;
}
}
return null;
}},
previousElementSibling: { get: function() {
if (this.parentNode) {
for (var kid = this.previousSibling; kid !== null; kid = kid.previousSibling) {
if (kid.nodeType === Node.ELEMENT_NODE) return kid;
}
}
return null;
}}
};
module.exports = NonDocumentTypeChildNode;

43
domino/ProcessingInstruction.js

@ -0,0 +1,43 @@
"use strict";
module.exports = ProcessingInstruction;
var Node = require('./Node.js');
var CharacterData = require('./CharacterData.js');
function ProcessingInstruction(doc, target, data) {
CharacterData.call(this);
this.nodeType = Node.PROCESSING_INSTRUCTION_NODE;
this.ownerDocument = doc;
this.target = target;
this._data = data;
}
var nodeValue = {
get: function() { return this._data; },
set: function(v) {
if (v === null || v === undefined) { v = ''; } else { v = String(v); }
this._data = v;
if (this.rooted) this.ownerDocument.mutateValue(this);
}
};
ProcessingInstruction.prototype = Object.create(CharacterData.prototype, {
nodeName: { get: function() { return this.target; }},
nodeValue: nodeValue,
textContent: nodeValue,
data: {
get: nodeValue.get,
set: function(v) {
nodeValue.set.call(this, v===null ? '' : String(v));
},
},
// Utility methods
clone: { value: function clone() {
return new ProcessingInstruction(this.ownerDocument, this.target, this._data);
}},
isEqual: { value: function isEqual(n) {
return this.target === n.target && this._data === n._data;
}}
});

74
domino/Text.js

@ -0,0 +1,74 @@
"use strict";
module.exports = Text;
var utils = require('./utils.js');
var Node = require('./Node.js');
var CharacterData = require('./CharacterData.js');
function Text(doc, data) {
CharacterData.call(this);
this.nodeType = Node.TEXT_NODE;
this.ownerDocument = doc;
this._data = data;
this._index = undefined;
}
var nodeValue = {
get: function() { return this._data; },
set: function(v) {
if (v === null || v === undefined) { v = ''; } else { v = String(v); }
if (v === this._data) return;
this._data = v;
if (this.rooted)
this.ownerDocument.mutateValue(this);
if (this.parentNode &&
this.parentNode._textchangehook)
this.parentNode._textchangehook(this);
}
};
Text.prototype = Object.create(CharacterData.prototype, {
nodeName: { value: "#text" },
// These three attributes are all the same.
// The data attribute has a [TreatNullAs=EmptyString] but we'll
// implement that at the interface level
nodeValue: nodeValue,
textContent: nodeValue,
data: {
get: nodeValue.get,
set: function(v) {
nodeValue.set.call(this, v===null ? '' : String(v));
},
},
splitText: { value: function splitText(offset) {
if (offset > this._data.length || offset < 0) utils.IndexSizeError();
var newdata = this._data.substring(offset),
newnode = this.ownerDocument.createTextNode(newdata);
this.data = this.data.substring(0, offset);
var parent = this.parentNode;
if (parent !== null)
parent.insertBefore(newnode, this.nextSibling);
return newnode;
}},
wholeText: { get: function wholeText() {
var result = this.textContent;
for (var next = this.nextSibling; next; next = next.nextSibling) {
if (next.nodeType !== Node.TEXT_NODE) { break; }
result += next.textContent;
}
return result;
}},
// Obsolete, removed from spec.
replaceWholeText: { value: utils.nyi },
// Utility methods
clone: { value: function clone() {
return new Text(this.ownerDocument, this._data);
}},
});

336
domino/TreeWalker.js

@ -0,0 +1,336 @@
"use strict";
module.exports = TreeWalker;
var Node = require('./Node.js');
var NodeFilter = require('./NodeFilter.js');
var NodeTraversal = require('./NodeTraversal.js');
var utils = require('./utils.js');
var mapChild = {
first: 'firstChild',
last: 'lastChild',
next: 'firstChild',
previous: 'lastChild'
};
var mapSibling = {
first: 'nextSibling',
last: 'previousSibling',
next: 'nextSibling',
previous: 'previousSibling'
};
/* Private methods and helpers */
/**
* @spec https://dom.spec.whatwg.org/#concept-traverse-children
* @method
* @access private
* @param {TreeWalker} tw
* @param {string} type One of 'first' or 'last'.
* @return {Node|null}
*/
function traverseChildren(tw, type) {
var child, node, parent, result, sibling;
node = tw._currentNode[mapChild[type]];
while (node !== null) {
result = tw._internalFilter(node);
if (result === NodeFilter.FILTER_ACCEPT) {
tw._currentNode = node;
return node;
}
if (result === NodeFilter.FILTER_SKIP) {
child = node[mapChild[type]];
if (child !== null) {
node = child;
continue;
}
}
while (node !== null) {
sibling = node[mapSibling[type]];
if (sibling !== null) {
node = sibling;
break;
}
parent = node.parentNode;
if (parent === null || parent === tw.root || parent === tw._currentNode) {
return null;
} else {
node = parent;
}
}
}
return null;
}
/**
* @spec https://dom.spec.whatwg.org/#concept-traverse-siblings
* @method
* @access private
* @param {TreeWalker} tw
* @param {TreeWalker} type One of 'next' or 'previous'.
* @return {Node|nul}
*/
function traverseSiblings(tw, type) {
var node, result, sibling;
node = tw._currentNode;
if (node === tw.root) {
return null;
}
while (true) {
sibling = node[mapSibling[type]];
while (sibling !== null) {
node = sibling;
result = tw._internalFilter(node);
if (result === NodeFilter.FILTER_ACCEPT) {
tw._currentNode = node;
return node;
}
sibling = node[mapChild[type]];
if (result === NodeFilter.FILTER_REJECT || sibling === null) {
sibling = node[mapSibling[type]];
}
}
node = node.parentNode;
if (node === null || node === tw.root) {
return null;
}
if (tw._internalFilter(node) === NodeFilter.FILTER_ACCEPT) {
return null;
}
}
}
/* Public API */
/**
* Latest version: https://dom.spec.whatwg.org/#treewalker
*
* @constructor
* @param {Node} root
* @param {number} whatToShow [optional]
* @param {Function|NodeFilter} filter [optional]
* @throws Error
*/
function TreeWalker(root, whatToShow, filter) {
if (!root || !root.nodeType) {
utils.NotSupportedError();
}
// Read-only properties
this._root = root;
this._whatToShow = Number(whatToShow) || 0;
this._filter = filter || null;
this._active = false;
// Read-write property
this._currentNode = root;
}
Object.defineProperties(TreeWalker.prototype, {
root: { get: function() { return this._root; } },
whatToShow: { get: function() { return this._whatToShow; } },
filter: { get: function() { return this._filter; } },
currentNode: {
get: function currentNode() {
return this._currentNode;
},
set: function setCurrentNode(v) {
if (!(v instanceof Node)) {
throw new TypeError("Not a Node"); // `null` is also not a node
}
this._currentNode = v;
},
},
/**
* @method
* @param {Node} node
* @return {Number} Constant NodeFilter.FILTER_ACCEPT,
* NodeFilter.FILTER_REJECT or NodeFilter.FILTER_SKIP.
*/
_internalFilter: { value: function _internalFilter(node) {
/* jshint bitwise: false */
var result, filter;
if (this._active) {
utils.InvalidStateError();
}
// Maps nodeType to whatToShow
if (!(((1 << (node.nodeType - 1)) & this._whatToShow))) {
return NodeFilter.FILTER_SKIP;
}
filter = this._filter;
if (filter === null) {
result = NodeFilter.FILTER_ACCEPT;
} else {
this._active = true;
try {
if (typeof filter === 'function') {
result = filter(node);
} else {
result = filter.acceptNode(node);
}
} finally {
this._active = false;
}
}
// Note that coercing to a number means that
// `true` becomes `1` (which is NodeFilter.FILTER_ACCEPT)
// `false` becomes `0` (neither accept, reject, or skip)
return (+result);
}},
/**
* @spec https://dom.spec.whatwg.org/#dom-treewalker-parentnode
* @based on WebKit's TreeWalker::parentNode
* https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/dom/TreeWalker.cpp?rev=220453#L50
* @method
* @return {Node|null}
*/
parentNode: { value: function parentNode() {
var node = this._currentNode;
while (node !== this.root) {
node = node.parentNode;
if (node === null) {
return null;
}
if (this._internalFilter(node) === NodeFilter.FILTER_ACCEPT) {
this._currentNode = node;
return node;
}
}
return null;
}},
/**
* @spec https://dom.spec.whatwg.org/#dom-treewalker-firstchild
* @method
* @return {Node|null}
*/
firstChild: { value: function firstChild() {
return traverseChildren(this, 'first');
}},
/**
* @spec https://dom.spec.whatwg.org/#dom-treewalker-lastchild
* @method
* @return {Node|null}
*/
lastChild: { value: function lastChild() {
return traverseChildren(this, 'last');
}},
/**
* @spec http://www.w3.org/TR/dom/#dom-treewalker-previoussibling
* @method
* @return {Node|null}
*/
previousSibling: { value: function previousSibling() {
return traverseSiblings(this, 'previous');
}},
/**
* @spec http://www.w3.org/TR/dom/#dom-treewalker-nextsibling
* @method
* @return {Node|null}
*/
nextSibling: { value: function nextSibling() {
return traverseSiblings(this, 'next');
}},
/**
* @spec https://dom.spec.whatwg.org/#dom-treewalker-previousnode
* @based on WebKit's TreeWalker::previousNode
* https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/dom/TreeWalker.cpp?rev=220453#L181
* @method
* @return {Node|null}
*/
previousNode: { value: function previousNode() {
var node, result, previousSibling, lastChild;
node = this._currentNode;
while (node !== this._root) {
for (previousSibling = node.previousSibling;
previousSibling;
previousSibling = node.previousSibling) {
node = previousSibling;
result = this._internalFilter(node);
if (result === NodeFilter.FILTER_REJECT) {
continue;
}
for (lastChild = node.lastChild;
lastChild;
lastChild = node.lastChild) {
node = lastChild;
result = this._internalFilter(node);
if (result === NodeFilter.FILTER_REJECT) {
break;
}
}
if (result === NodeFilter.FILTER_ACCEPT) {
this._currentNode = node;
return node;
}
}
if (node === this.root || node.parentNode === null) {
return null;
}
node = node.parentNode;
if (this._internalFilter(node) === NodeFilter.FILTER_ACCEPT) {
this._currentNode = node;
return node;
}
}
return null;
}},
/**
* @spec https://dom.spec.whatwg.org/#dom-treewalker-nextnode
* @based on WebKit's TreeWalker::nextNode
* https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/dom/TreeWalker.cpp?rev=220453#L228
* @method
* @return {Node|null}
*/
nextNode: { value: function nextNode() {
var node, result, firstChild, nextSibling;
node = this._currentNode;
result = NodeFilter.FILTER_ACCEPT;
CHILDREN:
while (true) {
for (firstChild = node.firstChild;
firstChild;
firstChild = node.firstChild) {
node = firstChild;
result = this._internalFilter(node);
if (result === NodeFilter.FILTER_ACCEPT) {
this._currentNode = node;
return node;
} else if (result === NodeFilter.FILTER_REJECT) {
break;
}
}
for (nextSibling = NodeTraversal.nextSkippingChildren(node, this.root);
nextSibling;
nextSibling = NodeTraversal.nextSkippingChildren(node, this.root)) {
node = nextSibling;
result = this._internalFilter(node);
if (result === NodeFilter.FILTER_ACCEPT) {
this._currentNode = node;
return node;
} else if (result === NodeFilter.FILTER_SKIP) {
continue CHILDREN;
}
}
return null;
}
}},
/** For compatibility with web-platform-tests. */
toString: { value: function toString() {
return "[object TreeWalker]";
}},
});

19
domino/UIEvent.js

@ -0,0 +1,19 @@
"use strict";
var Event = require('./Event.js');
module.exports = UIEvent;
function UIEvent() {
// Just use the superclass constructor to initialize
Event.call(this);
this.view = null; // FF uses the current window
this.detail = 0;
}
UIEvent.prototype = Object.create(Event.prototype, {
constructor: { value: UIEvent },
initUIEvent: { value: function(type, bubbles, cancelable, view, detail) {
this.initEvent(type, bubbles, cancelable);
this.view = view;
this.detail = detail;
}}
});

194
domino/URL.js

@ -0,0 +1,194 @@
"use strict";
module.exports = URL;
function URL(url) {
if (!url) return Object.create(URL.prototype);
// Can't use String.trim() since it defines whitespace differently than HTML
this.url = url.replace(/^[ \t\n\r\f]+|[ \t\n\r\f]+$/g, "");
// See http://tools.ietf.org/html/rfc3986#appendix-B
// and https://url.spec.whatwg.org/#parsing
var match = URL.pattern.exec(this.url);
if (match) {
if (match[2]) this.scheme = match[2];
if (match[4]) {
// parse username/password
var userinfo = match[4].match(URL.userinfoPattern);
if (userinfo) {
this.username = userinfo[1];
this.password = userinfo[3];
match[4] = match[4].substring(userinfo[0].length);
}
if (match[4].match(URL.portPattern)) {
var pos = match[4].lastIndexOf(':');
this.host = match[4].substring(0, pos);
this.port = match[4].substring(pos+1);
}
else {
this.host = match[4];
}
}
if (match[5]) this.path = match[5];
if (match[6]) this.query = match[7];
if (match[8]) this.fragment = match[9];
}
}
URL.pattern = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/;
URL.userinfoPattern = /^([^@:]*)(:([^@]*))?@/;
URL.portPattern = /:\d+$/;
URL.authorityPattern = /^[^:\/?#]+:\/\//;
URL.hierarchyPattern = /^[^:\/?#]+:\//;
// Return a percentEncoded version of s.
// S should be a single-character string
// XXX: needs to do utf-8 encoding?
URL.percentEncode = function percentEncode(s) {
var c = s.charCodeAt(0);
if (c < 256) return "%" + c.toString(16);
else throw Error("can't percent-encode codepoints > 255 yet");
};
URL.prototype = {
constructor: URL,
// XXX: not sure if this is the precise definition of absolute
isAbsolute: function() { return !!this.scheme; },
isAuthorityBased: function() {
return URL.authorityPattern.test(this.url);
},
isHierarchical: function() {
return URL.hierarchyPattern.test(this.url);
},
toString: function() {
var s = "";
if (this.scheme !== undefined) s += this.scheme + ":";
if (this.isAbsolute()) {
s += '//';
if (this.username || this.password) {
s += this.username || '';
if (this.password) {
s += ':' + this.password;
}
s += '@';
}
if (this.host) {
s += this.host;
}
}
if (this.port !== undefined) s += ":" + this.port;
if (this.path !== undefined) s += this.path;
if (this.query !== undefined) s += "?" + this.query;
if (this.fragment !== undefined) s += "#" + this.fragment;
return s;
},
// See: http://tools.ietf.org/html/rfc3986#section-5.2
// and https://url.spec.whatwg.org/#constructors
resolve: function(relative) {
var base = this; // The base url we're resolving against
var r = new URL(relative); // The relative reference url to resolve
var t = new URL(); // The absolute target url we will return
if (r.scheme !== undefined) {
t.scheme = r.scheme;
t.username = r.username;
t.password = r.password;
t.host = r.host;
t.port = r.port;
t.path = remove_dot_segments(r.path);
t.query = r.query;
}
else {
t.scheme = base.scheme;
if (r.host !== undefined) {
t.username = r.username;
t.password = r.password;
t.host = r.host;
t.port = r.port;
t.path = remove_dot_segments(r.path);
t.query = r.query;
}
else {
t.username = base.username;
t.password = base.password;
t.host = base.host;
t.port = base.port;
if (!r.path) { // undefined or empty
t.path = base.path;
if (r.query !== undefined)
t.query = r.query;
else
t.query = base.query;
}
else {
if (r.path.charAt(0) === "/") {
t.path = remove_dot_segments(r.path);
}
else {
t.path = merge(base.path, r.path);
t.path = remove_dot_segments(t.path);
}
t.query = r.query;
}
}
}
t.fragment = r.fragment;
return t.toString();
function merge(basepath, refpath) {
if (base.host !== undefined && !base.path)
return "/" + refpath;
var lastslash = basepath.lastIndexOf("/");
if (lastslash === -1)
return refpath;
else
return basepath.substring(0, lastslash+1) + refpath;
}
function remove_dot_segments(path) {
if (!path) return path; // For "" or undefined
var output = "";
while(path.length > 0) {
if (path === "." || path === "..") {
path = "";
break;
}
var twochars = path.substring(0,2);
var threechars = path.substring(0,3);
var fourchars = path.substring(0,4);
if (threechars === "../") {
path = path.substring(3);
}
else if (twochars === "./") {
path = path.substring(2);
}
else if (threechars === "/./") {
path = "/" + path.substring(3);
}
else if (twochars === "/." && path.length === 2) {
path = "/";
}
else if (fourchars === "/../" ||
(threechars === "/.." && path.length === 3)) {
path = "/" + path.substring(4);
output = output.replace(/\/?[^\/]*$/, "");
}
else {
var segment = path.match(/(\/?([^\/]*))/)[0];
output += segment;
path = path.substring(segment.length);
}
}
return output;
}
},
};

270
domino/URLUtils.js

@ -0,0 +1,270 @@
"use strict";
var URL = require('./URL.js');
module.exports = URLUtils;
// Allow the `x == null` pattern. This is eslint's "null: 'ignore'" option,
// but jshint doesn't support this.
/* jshint eqeqeq: false */
// This is an abstract superclass for Location, HTMLAnchorElement and
// other types that have the standard complement of "URL decomposition
// IDL attributes". This is now standardized as URLUtils, see:
// https://url.spec.whatwg.org/#urlutils
// Subclasses must define a getter/setter on href.
// The getter and setter methods parse and rebuild the URL on each
// invocation; there is no attempt to cache the value and be more efficient
function URLUtils() {}
URLUtils.prototype = Object.create(Object.prototype, {
_url: { get: function() {
// XXX: this should do the "Reinitialize url" steps, and "null" should
// be a valid return value.
return new URL(this.href);
} },
protocol: {
get: function() {
var url = this._url;
if (url && url.scheme) return url.scheme + ":";
else return ":";
},
set: function(v) {
var output = this.href;
var url = new URL(output);
if (url.isAbsolute()) {
v = v.replace(/:+$/, "");
v = v.replace(/[^-+\.a-zA-Z0-9]/g, URL.percentEncode);
if (v.length > 0) {
url.scheme = v;
output = url.toString();
}
}
this.href = output;
},
},
host: {
get: function() {
var url = this._url;
if (url.isAbsolute() && url.isAuthorityBased())
return url.host + (url.port ? (":" + url.port) : "");
else
return "";
},
set: function(v) {
var output = this.href;
var url = new URL(output);
if (url.isAbsolute() && url.isAuthorityBased()) {
v = v.replace(/[^-+\._~!$&'()*,;:=a-zA-Z0-9]/g, URL.percentEncode);
if (v.length > 0) {
url.host = v;
delete url.port;
output = url.toString();
}
}
this.href = output;
},
},
hostname: {
get: function() {
var url = this._url;
if (url.isAbsolute() && url.isAuthorityBased())
return url.host;
else
return "";
},
set: function(v) {
var output = this.href;
var url = new URL(output);
if (url.isAbsolute() && url.isAuthorityBased()) {
v = v.replace(/^\/+/, "");
v = v.replace(/[^-+\._~!$&'()*,;:=a-zA-Z0-9]/g, URL.percentEncode);
if (v.length > 0) {
url.host = v;
output = url.toString();
}
}
this.href = output;
},
},
port: {
get: function() {
var url = this._url;
if (url.isAbsolute() && url.isAuthorityBased() && url.port!==undefined)
return url.port;
else
return "";
},
set: function(v) {
var output = this.href;
var url = new URL(output);
if (url.isAbsolute() && url.isAuthorityBased()) {
v = '' + v;
v = v.replace(/[^0-9].*$/, "");
v = v.replace(/^0+/, "");
if (v.length === 0) v = "0";
if (parseInt(v, 10) <= 65535) {
url.port = v;
output = url.toString();
}
}
this.href = output;
},
},
pathname: {
get: function() {
var url = this._url;
if (url.isAbsolute() && url.isHierarchical())
return url.path;
else
return "";
},
set: function(v) {
var output = this.href;
var url = new URL(output);
if (url.isAbsolute() && url.isHierarchical()) {
if (v.charAt(0) !== "/")
v = "/" + v;
v = v.replace(/[^-+\._~!$&'()*,;:=@\/a-zA-Z0-9]/g, URL.percentEncode);
url.path = v;
output = url.toString();
}
this.href = output;
},
},
search: {
get: function() {
var url = this._url;
if (url.isAbsolute() && url.isHierarchical() && url.query!==undefined)
return "?" + url.query;
else
return "";
},
set: function(v) {
var output = this.href;
var url = new URL(output);
if (url.isAbsolute() && url.isHierarchical()) {
if (v.charAt(0) === "?") v = v.substring(1);
v = v.replace(/[^-+\._~!$&'()*,;:=@\/?a-zA-Z0-9]/g, URL.percentEncode);
url.query = v;
output = url.toString();
}
this.href = output;
},
},
hash: {
get: function() {
var url = this._url;
if (url == null || url.fragment == null || url.fragment === '') {
return "";
} else {
return "#" + url.fragment;
}
},
set: function(v) {
var output = this.href;
var url = new URL(output);
if (v.charAt(0) === "#") v = v.substring(1);
v = v.replace(/[^-+\._~!$&'()*,;:=@\/?a-zA-Z0-9]/g, URL.percentEncode);
url.fragment = v;
output = url.toString();
this.href = output;
},
},
username: {
get: function() {
var url = this._url;
return url.username || '';
},
set: function(v) {
var output = this.href;
var url = new URL(output);
if (url.isAbsolute()) {
v = v.replace(/[\x00-\x1F\x7F-\uFFFF "#<>?`\/@\\:]/g, URL.percentEncode);
url.username = v;
output = url.toString();
}
this.href = output;
},
},
password: {
get: function() {
var url = this._url;
return url.password || '';
},
set: function(v) {
var output = this.href;
var url = new URL(output);
if (url.isAbsolute()) {
if (v==='') {
url.password = null;
} else {
v = v.replace(/[\x00-\x1F\x7F-\uFFFF "#<>?`\/@\\]/g, URL.percentEncode);
url.password = v;
}
output = url.toString();
}
this.href = output;
},
},
origin: { get: function() {
var url = this._url;
if (url == null) { return ''; }
var originForPort = function(defaultPort) {
var origin = [url.scheme, url.host, +url.port || defaultPort];
// XXX should be "unicode serialization"
return origin[0] + '://' + origin[1] +
(origin[2] === defaultPort ? '' : (':' + origin[2]));
};
switch (url.scheme) {
case 'ftp':
return originForPort(21);
case 'gopher':
return originForPort(70);
case 'http':
case 'ws':
return originForPort(80);
case 'https':
case 'wss':
return originForPort(443);
default:
// this is what chrome does
return url.scheme + '://';
}
} },
/*
searchParams: {
get: function() {
var url = this._url;
// XXX
},
set: function(v) {
var output = this.href;
var url = new URL(output);
// XXX
this.href = output;
},
},
*/
});
URLUtils._inherit = function(proto) {
// copy getters/setters from URLUtils to o.
Object.getOwnPropertyNames(URLUtils.prototype).forEach(function(p) {
if (p==='constructor' || p==='href') { return; }
var desc = Object.getOwnPropertyDescriptor(URLUtils.prototype, p);
Object.defineProperty(proto, p, desc);
});
};

62
domino/Window.js

@ -0,0 +1,62 @@
"use strict";
var DOMImplementation = require('./DOMImplementation.js');
var EventTarget = require('./EventTarget.js');
var Location = require('./Location.js');
var sloppy = require('./sloppy.js');
var utils = require('./utils.js');
module.exports = Window;
function Window(document) {
this.document = document || new DOMImplementation(null).createHTMLDocument("");
this.document._scripting_enabled = true;
this.document.defaultView = this;
this.location = new Location(this, this.document._address || 'about:blank');
}
Window.prototype = Object.create(EventTarget.prototype, {
_run: { value: sloppy.Window_run },
console: { value: console },
history: { value: {
back: utils.nyi,
forward: utils.nyi,
go: utils.nyi
}},
navigator: { value: require("./NavigatorID.js") },
// Self-referential properties
window: { get: function() { return this; }},
self: { get: function() { return this; }},
frames: { get: function() { return this; }},
// Self-referential properties for a top-level window
parent: { get: function() { return this; }},
top: { get: function() { return this; }},
// We don't support any other windows for now
length: { value: 0 }, // no frames
frameElement: { value: null }, // not part of a frame
opener: { value: null }, // not opened by another window
// The onload event handler.
// XXX: need to support a bunch of other event types, too,
// and have them interoperate with document.body.
onload: {
get: function() {
return this._getEventHandler("load");
},
set: function(v) {
this._setEventHandler("load", v);
}
},
// XXX This is a completely broken implementation
getComputedStyle: { value: function getComputedStyle(elt) {
return elt.style;
}}
});
utils.expose(require('./WindowTimers.js'), Window);
utils.expose(require('./impl.js'), Window);

11
domino/WindowTimers.js

@ -0,0 +1,11 @@
"use strict";
// https://html.spec.whatwg.org/multipage/webappapis.html#windowtimers
var WindowTimers = {
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setInterval: setInterval,
clearInterval: clearInterval
};
module.exports = WindowTimers;

152
domino/attributes.js

@ -0,0 +1,152 @@
"use strict";
var utils = require('./utils.js');
exports.property = function(attr) {
if (Array.isArray(attr.type)) {
var valid = Object.create(null);
attr.type.forEach(function(val) {
valid[val.value || val] = val.alias || val;
});
var missingValueDefault = attr.missing;
if (missingValueDefault===undefined) { missingValueDefault = null; }
var invalidValueDefault = attr.invalid;
if (invalidValueDefault===undefined) { invalidValueDefault = missingValueDefault; }
return {
get: function() {
var v = this._getattr(attr.name);
if (v === null) return missingValueDefault;
v = valid[v.toLowerCase()];
if (v !== undefined) return v;
if (invalidValueDefault !== null) return invalidValueDefault;
return v;
},
set: function(v) {
this._setattr(attr.name, v);
}
};
}
else if (attr.type === Boolean) {
return {
get: function() {
return this.hasAttribute(attr.name);
},
set: function(v) {
if (v) {
this._setattr(attr.name, '');
}
else {
this.removeAttribute(attr.name);
}
}
};
}
else if (attr.type === Number ||
attr.type === "long" ||
attr.type === "unsigned long" ||
attr.type === "limited unsigned long with fallback") {
return numberPropDesc(attr);
}
else if (!attr.type || attr.type === String) {
return {
get: function() { return this._getattr(attr.name) || ''; },
set: function(v) {
if (attr.treatNullAsEmptyString && v === null) { v = ''; }
this._setattr(attr.name, v);
}
};
}
else if (typeof attr.type === 'function') {
return attr.type(attr.name, attr);
}
throw new Error('Invalid attribute definition');
};
// See http://www.whatwg.org/specs/web-apps/current-work/#reflect
//
// defval is the default value. If it is a function, then that function
// will be invoked as a method of the element to obtain the default.
// If no default is specified for a given attribute, then the default
// depends on the type of the attribute, but since this function handles
// 4 integer cases, you must specify the default value in each call
//
// min and max define a valid range for getting the attribute.
//
// setmin defines a minimum value when setting. If the value is less
// than that, then throw INDEX_SIZE_ERR.
//
// Conveniently, JavaScript's parseInt function appears to be
// compatible with HTML's 'rules for parsing integers'
function numberPropDesc(a) {
var def;
if(typeof a.default === 'function') {
def = a.default;
}
else if(typeof a.default === 'number') {
def = function() { return a.default; };
}
else {
def = function() { utils.assert(false, typeof a.default); };
}
var unsigned_long = (a.type === 'unsigned long');
var signed_long = (a.type === 'long');
var unsigned_fallback = (a.type === 'limited unsigned long with fallback');
var min = a.min, max = a.max, setmin = a.setmin;
if (min === undefined) {
if (unsigned_long) min = 0;
if (signed_long) min = -0x80000000;
if (unsigned_fallback) min = 1;
}
if (max === undefined) {
if (unsigned_long || signed_long || unsigned_fallback) max = 0x7FFFFFFF;
}
return {
get: function() {
var v = this._getattr(a.name);
var n = a.float ? parseFloat(v) : parseInt(v, 10);
if (v === null || !isFinite(n) || (min !== undefined && n < min) || (max !== undefined && n > max)) {
return def.call(this);
}
if (unsigned_long || signed_long || unsigned_fallback) {
if (!/^[ \t\n\f\r]*[-+]?[0-9]/.test(v)) { return def.call(this); }
n = n|0; // jshint ignore:line
}
return n;
},
set: function(v) {
if (!a.float) { v = Math.floor(v); }
if (setmin !== undefined && v < setmin) {
utils.IndexSizeError(a.name + ' set to ' + v);
}
if (unsigned_long) {
v = (v < 0 || v > 0x7FFFFFFF) ? def.call(this) :
(v|0); // jshint ignore:line
} else if (unsigned_fallback) {
v = (v < 1 || v > 0x7FFFFFFF) ? def.call(this) :
(v|0); // jshint ignore:line
} else if (signed_long) {
v = (v < -0x80000000 || v > 0x7FFFFFFF) ? def.call(this) :
(v|0); // jshint ignore:line
}
this._setattr(a.name, String(v));
}
};
}
// This is a utility function for setting up change handler functions
// for attributes like 'id' that require special handling when they change.
exports.registerChangeHandler = function(c, name, handler) {
var p = c.prototype;
// If p does not already have its own _attributeChangeHandlers
// then create one for it, inheriting from the inherited
// _attributeChangeHandlers. At the top (for the Element class) the
// _attributeChangeHandlers object will be created with a null prototype.
if (!Object.prototype.hasOwnProperty.call(p, '_attributeChangeHandlers')) {
p._attributeChangeHandlers =
Object.create(p._attributeChangeHandlers || null);
}
p._attributeChangeHandlers[name] = handler;
};

7
domino/config.js

@ -0,0 +1,7 @@
/*
* This file defines Domino behaviour that can be externally configured.
* To change these settings, set the relevant global property *before*
* you call `require("domino")`.
*/
exports.isApiWritable = false;

6654
domino/cssparser.js
File diff suppressed because it is too large
View File

70
domino/defineElement.js

@ -0,0 +1,70 @@
"use strict";
var attributes = require('./attributes.js');
var sloppy = require('./sloppy.js');
var isApiWritable = require("./config.js").isApiWritable;
module.exports = function(spec, defaultConstructor, tagList, tagNameToImpl) {
var c = spec.ctor;
if (c) {
var props = spec.props || {};
if (spec.attributes) {
for (var n in spec.attributes) {
var attr = spec.attributes[n];
if (typeof attr !== 'object' || Array.isArray(attr)) attr = {type: attr};
if (!attr.name) attr.name = n.toLowerCase();
props[n] = attributes.property(attr);
}
}
props.constructor = { value : c, writable: isApiWritable };
c.prototype = Object.create((spec.superclass || defaultConstructor).prototype, props);
if (spec.events) {
addEventHandlers(c, spec.events);
}
tagList[c.name] = c;
}
else {
c = defaultConstructor;
}
(spec.tags || spec.tag && [spec.tag] || []).forEach(function(tag) {
tagNameToImpl[tag] = c;
});
return c;
};
function EventHandlerBuilder(body, document, form, element) {
this.body = body;
this.document = document;
this.form = form;
this.element = element;
}
EventHandlerBuilder.prototype.build = sloppy.EventHandlerBuilder_build;
function EventHandlerChangeHandler(elt, name, oldval, newval) {
var doc = elt.ownerDocument || Object.create(null);
var form = elt.form || Object.create(null);
elt[name] = new EventHandlerBuilder(newval, doc, form, elt).build();
}
function addEventHandlers(c, eventHandlerTypes) {
var p = c.prototype;
eventHandlerTypes.forEach(function(type) {
// Define the event handler registration IDL attribute for this type
Object.defineProperty(p, "on" + type, {
get: function() {
return this._getEventHandler(type);
},
set: function(v) {
this._setEventHandler(type, v);
},
});
// Define special behavior for the content attribute as well
attributes.registerChangeHandler(c, "on" + type, EventHandlerChangeHandler);
});
}

7
domino/events.js

@ -0,0 +1,7 @@
"use strict";
module.exports = {
Event: require('./Event.js'),
UIEvent: require('./UIEvent.js'),
MouseEvent: require('./MouseEvent.js'),
CustomEvent: require('./CustomEvent.js')
};

1426
domino/htmlelts.js
File diff suppressed because it is too large
View File

27
domino/impl.js

@ -0,0 +1,27 @@
"use strict";
var utils = require('./utils.js');
exports = module.exports = {
CSSStyleDeclaration: require('./CSSStyleDeclaration.js'),
CharacterData: require('./CharacterData.js'),
Comment: require('./Comment.js'),
DOMException: require('./DOMException.js'),
DOMImplementation: require('./DOMImplementation.js'),
DOMTokenList: require('./DOMTokenList.js'),
Document: require('./Document.js'),
DocumentFragment: require('./DocumentFragment.js'),
DocumentType: require('./DocumentType.js'),
Element: require('./Element.js'),
HTMLParser: require('./HTMLParser.js'),
NamedNodeMap: require('./NamedNodeMap.js'),
Node: require('./Node.js'),
NodeList: require('./NodeList.js'),
NodeFilter: require('./NodeFilter.js'),
ProcessingInstruction: require('./ProcessingInstruction.js'),
Text: require('./Text.js'),
Window: require('./Window.js')
};
utils.merge(exports, require('./events.js'));
utils.merge(exports, require('./htmlelts.js').elements);
utils.merge(exports, require('./svg.js').elements);

5
domino/index.d.ts

@ -0,0 +1,5 @@
declare module "domino" {
function createDOMImplementation(): DOMImplementation;
function createDocument(html?: string, force?: boolean): Document;
function createWindow(html?: string, address?: string): Window;
}

79
domino/index.js

@ -0,0 +1,79 @@
"use strict";
var DOMImplementation = require('./DOMImplementation.js');
var HTMLParser = require('./HTMLParser.js');
var Window = require('./Window.js');
exports.createDOMImplementation = function() {
return new DOMImplementation(null);
};
exports.createDocument = function(html, force) {
// Previous API couldn't let you pass '' as a document, and that
// yields a slightly different document than createHTMLDocument('')
// does. The new `force` parameter lets you pass '' if you want to.
if (html || force) {
var parser = new HTMLParser();
parser.parse(html || '', true);
return parser.document();
}
return new DOMImplementation(null).createHTMLDocument("");
};
exports.createIncrementalHTMLParser = function() {
var parser = new HTMLParser();
/** API for incremental parser. */
return {
/** Provide an additional chunk of text to be parsed. */
write: function(s) {
if (s.length > 0) {
parser.parse(s, false, function() { return true; });
}
},
/**
* Signal that we are done providing input text, optionally
* providing one last chunk as a parameter.
*/
end: function(s) {
parser.parse(s || '', true, function() { return true; });
},
/**
* Performs a chunk of parsing work, returning at the end of
* the next token as soon as shouldPauseFunc() returns true.
* Returns true iff there is more work to do.
*
* For example:
* ```
* var incrParser = domino.createIncrementalHTMLParser();
* incrParser.end('...long html document...');
* while (true) {
* // Pause every 10ms
* var start = Date.now();
* var pauseIn10 = function() { return (Date.now() - start) >= 10; };
* if (!incrParser.process(pauseIn10)) {
* break;
* }
* ...yield to other tasks, do other housekeeping, etc...
* }
* ```
*/
process: function(shouldPauseFunc) {
return parser.parse('', false, shouldPauseFunc);
},
/**
* Returns the result of the incremental parse. Valid after
* `this.end()` has been called and `this.process()` has returned
* false.
*/
document: function() {
return parser.document();
},
};
};
exports.createWindow = function(html, address) {
var document = exports.createDocument(html);
if (address !== undefined) { document._address = address; }
return new Window(document);
};
exports.impl = require('./impl.js');

2938
domino/jquery.js
File diff suppressed because it is too large
View File

933
domino/select.js

@ -0,0 +1,933 @@
"use strict";
/* jshint eqnull: true */
/**
* Zest (https://github.com/chjj/zest)
* A css selector engine.
* Copyright (c) 2011-2012, Christopher Jeffrey. (MIT Licensed)
* Domino version based on Zest v0.1.3 with bugfixes applied.
*/
/**
* Helpers
*/
var window = Object.create(null, {
location: { get: function() {
throw new Error('window.location is not supported.');
} }
});
var compareDocumentPosition = function(a, b) {
return a.compareDocumentPosition(b);
};
var order = function(a, b) {
/* jshint bitwise: false */
return compareDocumentPosition(a, b) & 2 ? 1 : -1;
};
var next = function(el) {
while ((el = el.nextSibling)
&& el.nodeType !== 1);
return el;
};
var prev = function(el) {
while ((el = el.previousSibling)
&& el.nodeType !== 1);
return el;
};
var child = function(el) {
/*jshint -W084 */
if (el = el.firstChild) {
while (el.nodeType !== 1
&& (el = el.nextSibling));
}
return el;
};
var lastChild = function(el) {
/*jshint -W084 */
if (el = el.lastChild) {
while (el.nodeType !== 1
&& (el = el.previousSibling));
}
return el;
};
var parentIsElement = function(n) {
if (!n.parentNode) { return false; }
var nodeType = n.parentNode.nodeType;
// The root `html` element can be a first- or last-child, too.
return nodeType === 1 || nodeType === 9;
};
var unquote = function(str) {
if (!str) return str;
var ch = str[0];
if (ch === '"' || ch === '\'') {
if (str[str.length-1] === ch) {
str = str.slice(1, -1);
} else {
// bad string.
str = str.slice(1);
}
return str.replace(rules.str_escape, function(s) {
var m = /^\\(?:([0-9A-Fa-f]+)|([\r\n\f]+))/.exec(s);
if (!m) { return s.slice(1); }
if (m[2]) { return ''; /* escaped newlines are ignored in strings. */ }
var cp = parseInt(m[1], 16);
return String.fromCodePoint ? String.fromCodePoint(cp) :
// Not all JavaScript implementations have String.fromCodePoint yet.
String.fromCharCode(cp);
});
} else if (rules.ident.test(str)) {
return decodeid(str);
} else {
// NUMBER, PERCENTAGE, DIMENSION, etc
return str;
}
};
var decodeid = function(str) {
return str.replace(rules.escape, function(s) {
var m = /^\\([0-9A-Fa-f]+)/.exec(s);
if (!m) { return s[1]; }
var cp = parseInt(m[1], 16);
return String.fromCodePoint ? String.fromCodePoint(cp) :
// Not all JavaScript implementations have String.fromCodePoint yet.
String.fromCharCode(cp);
});
};
var indexOf = (function() {
if (Array.prototype.indexOf) {
return Array.prototype.indexOf;
}
return function(obj, item) {
var i = this.length;
while (i--) {
if (this[i] === item) return i;
}
return -1;
};
})();
var makeInside = function(start, end) {
var regex = rules.inside.source
.replace(/</g, start)
.replace(/>/g, end);
return new RegExp(regex);
};
var replace = function(regex, name, val) {
regex = regex.source;
regex = regex.replace(name, val.source || val);
return new RegExp(regex);
};
var truncateUrl = function(url, num) {
return url
.replace(/^(?:\w+:\/\/|\/+)/, '')
.replace(/(?:\/+|\/*#.*?)$/, '')
.split('/', num)
.join('/');
};
/**
* Handle `nth` Selectors
*/
var parseNth = function(param_, test) {
var param = param_.replace(/\s+/g, '')
, cap;
if (param === 'even') {
param = '2n+0';
} else if (param === 'odd') {
param = '2n+1';
} else if (param.indexOf('n') === -1) {
param = '0n' + param;
}
cap = /^([+-])?(\d+)?n([+-])?(\d+)?$/.exec(param);
return {
group: cap[1] === '-'
? -(cap[2] || 1)
: +(cap[2] || 1),
offset: cap[4]
? (cap[3] === '-' ? -cap[4] : +cap[4])
: 0
};
};
var nth = function(param_, test, last) {
var param = parseNth(param_)
, group = param.group
, offset = param.offset
, find = !last ? child : lastChild
, advance = !last ? next : prev;
return function(el) {
if (!parentIsElement(el)) return;
var rel = find(el.parentNode)
, pos = 0;
while (rel) {
if (test(rel, el)) pos++;
if (rel === el) {
pos -= offset;
return group && pos
? (pos % group) === 0 && (pos < 0 === group < 0)
: !pos;
}
rel = advance(rel);
}
};
};
/**
* Simple Selectors
*/
var selectors = {
'*': (function() {
if (false/*function() {
var el = document.createElement('div');
el.appendChild(document.createComment(''));
return !!el.getElementsByTagName('*')[0];
}()*/) {
return function(el) {
if (el.nodeType === 1) return true;
};
}
return function() {
return true;
};
})(),
'type': function(type) {
type = type.toLowerCase();
return function(el) {
return el.nodeName.toLowerCase() === type;
};
},
'attr': function(key, op, val, i) {
op = operators[op];
return function(el) {
var attr;
switch (key) {
case 'for':
attr = el.htmlFor;
break;
case 'class':
// className is '' when non-existent
// getAttribute('class') is null
attr = el.className;
if (attr === '' && el.getAttribute('class') == null) {
attr = null;
}
break;
case 'href':
case 'src':
attr = el.getAttribute(key, 2);
break;
case 'title':
// getAttribute('title') can be '' when non-existent sometimes?
attr = el.getAttribute('title') || null;
break;
// careful with attributes with special getter functions
case 'id':
case 'lang':
case 'dir':
case 'accessKey':
case 'hidden':
case 'tabIndex':
case 'style':
if (el.getAttribute) {
attr = el.getAttribute(key);
break;
}
/* falls through */
default:
if (el.hasAttribute && !el.hasAttribute(key)) {
break;
}
attr = el[key] != null
? el[key]
: el.getAttribute && el.getAttribute(key);
break;
}
if (attr == null) return;
attr = attr + '';
if (i) {
attr = attr.toLowerCase();
val = val.toLowerCase();
}
return op(attr, val);
};
},
':first-child': function(el) {
return !prev(el) && parentIsElement(el);
},
':last-child': function(el) {
return !next(el) && parentIsElement(el);
},
':only-child': function(el) {
return !prev(el) && !next(el) && parentIsElement(el);
},
':nth-child': function(param, last) {
return nth(param, function() {
return true;
}, last);
},
':nth-last-child': function(param) {
return selectors[':nth-child'](param, true);
},
':root': function(el) {
return el.ownerDocument.documentElement === el;
},
':empty': function(el) {
return !el.firstChild;
},
':not': function(sel) {
var test = compileGroup(sel);
return function(el) {
return !test(el);
};
},
':first-of-type': function(el) {
if (!parentIsElement(el)) return;
var type = el.nodeName;
/*jshint -W084 */
while (el = prev(el)) {
if (el.nodeName === type) return;
}
return true;
},
':last-of-type': function(el) {
if (!parentIsElement(el)) return;
var type = el.nodeName;
/*jshint -W084 */
while (el = next(el)) {
if (el.nodeName === type) return;
}
return true;
},
':only-of-type': function(el) {
return selectors[':first-of-type'](el)
&& selectors[':last-of-type'](el);
},
':nth-of-type': function(param, last) {
return nth(param, function(rel, el) {
return rel.nodeName === el.nodeName;
}, last);
},
':nth-last-of-type': function(param) {
return selectors[':nth-of-type'](param, true);
},
':checked': function(el) {
return !!(el.checked || el.selected);
},
':indeterminate': function(el) {
return !selectors[':checked'](el);
},
':enabled': function(el) {
return !el.disabled && el.type !== 'hidden';
},
':disabled': function(el) {
return !!el.disabled;
},
':target': function(el) {
return el.id === window.location.hash.substring(1);
},
':focus': function(el) {
return el === el.ownerDocument.activeElement;
},
':is': function(sel) {
return compileGroup(sel);
},
// :matches is an older name for :is; see
// https://github.com/w3c/csswg-drafts/issues/3258
':matches': function(sel) {
return selectors[':is'](sel);
},
':nth-match': function(param, last) {
var args = param.split(/\s*,\s*/)
, arg = args.shift()
, test = compileGroup(args.join(','));
return nth(arg, test, last);
},
':nth-last-match': function(param) {
return selectors[':nth-match'](param, true);
},
':links-here': function(el) {
return el + '' === window.location + '';
},
':lang': function(param) {
return function(el) {
while (el) {
if (el.lang) return el.lang.indexOf(param) === 0;
el = el.parentNode;
}
};
},
':dir': function(param) {
return function(el) {
while (el) {
if (el.dir) return el.dir === param;
el = el.parentNode;
}
};
},
':scope': function(el, con) {
var context = con || el.ownerDocument;
if (context.nodeType === 9) {
return el === context.documentElement;
}
return el === context;
},
':any-link': function(el) {
return typeof el.href === 'string';
},
':local-link': function(el) {
if (el.nodeName) {
return el.href && el.host === window.location.host;
}
var param = +el + 1;
return function(el) {
if (!el.href) return;
var url = window.location + ''
, href = el + '';
return truncateUrl(url, param) === truncateUrl(href, param);
};
},
':default': function(el) {
return !!el.defaultSelected;
},
':valid': function(el) {
return el.willValidate || (el.validity && el.validity.valid);
},
':invalid': function(el) {
return !selectors[':valid'](el);
},
':in-range': function(el) {
return el.value > el.min && el.value <= el.max;
},
':out-of-range': function(el) {
return !selectors[':in-range'](el);
},
':required': function(el) {
return !!el.required;
},
':optional': function(el) {
return !el.required;
},
':read-only': function(el) {
if (el.readOnly) return true;
var attr = el.getAttribute('contenteditable')
, prop = el.contentEditable
, name = el.nodeName.toLowerCase();
name = name !== 'input' && name !== 'textarea';
return (name || el.disabled) && attr == null && prop !== 'true';
},
':read-write': function(el) {
return !selectors[':read-only'](el);
},
':hover': function() {
throw new Error(':hover is not supported.');
},
':active': function() {
throw new Error(':active is not supported.');
},
':link': function() {
throw new Error(':link is not supported.');
},
':visited': function() {
throw new Error(':visited is not supported.');
},
':column': function() {
throw new Error(':column is not supported.');
},
':nth-column': function() {
throw new Error(':nth-column is not supported.');
},
':nth-last-column': function() {
throw new Error(':nth-last-column is not supported.');
},
':current': function() {
throw new Error(':current is not supported.');
},
':past': function() {
throw new Error(':past is not supported.');
},
':future': function() {
throw new Error(':future is not supported.');
},
// Non-standard, for compatibility purposes.
':contains': function(param) {
return function(el) {
var text = el.innerText || el.textContent || el.value || '';
return text.indexOf(param) !== -1;
};
},
':has': function(param) {
return function(el) {
return find(param, el).length > 0;
};
}
// Potentially add more pseudo selectors for
// compatibility with sizzle and most other
// selector engines (?).
};
/**
* Attribute Operators
*/
var operators = {
'-': function() {
return true;
},
'=': function(attr, val) {
return attr === val;
},
'*=': function(attr, val) {
return attr.indexOf(val) !== -1;
},
'~=': function(attr, val) {
var i
, s
, f
, l;
for (s = 0; true; s = i + 1) {
i = attr.indexOf(val, s);
if (i === -1) return false;
f = attr[i - 1];
l = attr[i + val.length];
if ((!f || f === ' ') && (!l || l === ' ')) return true;
}
},
'|=': function(attr, val) {
var i = attr.indexOf(val)
, l;
if (i !== 0) return;
l = attr[i + val.length];
return l === '-' || !l;
},
'^=': function(attr, val) {
return attr.indexOf(val) === 0;
},
'$=': function(attr, val) {
var i = attr.lastIndexOf(val);
return i !== -1 && i + val.length === attr.length;
},
// non-standard
'!=': function(attr, val) {
return attr !== val;
}
};
/**
* Combinator Logic
*/
var combinators = {
' ': function(test) {
return function(el) {
/*jshint -W084 */
while (el = el.parentNode) {
if (test(el)) return el;
}
};
},
'>': function(test) {
return function(el) {
/*jshint -W084 */
if (el = el.parentNode) {
return test(el) && el;
}
};
},
'+': function(test) {
return function(el) {
/*jshint -W084 */
if (el = prev(el)) {
return test(el) && el;
}
};
},
'~': function(test) {
return function(el) {
/*jshint -W084 */
while (el = prev(el)) {
if (test(el)) return el;
}
};
},
'noop': function(test) {
return function(el) {
return test(el) && el;
};
},
'ref': function(test, name) {
var node;
function ref(el) {
var doc = el.ownerDocument
, nodes = doc.getElementsByTagName('*')
, i = nodes.length;
while (i--) {
node = nodes[i];
if (ref.test(el)) {
node = null;
return true;
}
}
node = null;
}
ref.combinator = function(el) {
if (!node || !node.getAttribute) return;
var attr = node.getAttribute(name) || '';
if (attr[0] === '#') attr = attr.substring(1);
if (attr === el.id && test(node)) {
return node;
}
};
return ref;
}
};
/**
* Grammar
*/
var rules = {
escape: /\\(?:[^0-9A-Fa-f\r\n]|[0-9A-Fa-f]{1,6}[\r\n\t ]?)/g,
str_escape: /(escape)|\\(\n|\r\n?|\f)/g,
nonascii: /[\u00A0-\uFFFF]/,
cssid: /(?:(?!-?[0-9])(?:escape|nonascii|[-_a-zA-Z0-9])+)/,
qname: /^ *(cssid|\*)/,
simple: /^(?:([.#]cssid)|pseudo|attr)/,
ref: /^ *\/(cssid)\/ */,
combinator: /^(?: +([^ \w*.#\\]) +|( )+|([^ \w*.#\\]))(?! *$)/,
attr: /^\[(cssid)(?:([^\w]?=)(inside))?\]/,
pseudo: /^(:cssid)(?:\((inside)\))?/,
inside: /(?:"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|<[^"'>]*>|\\["'>]|[^"'>])*/,
ident: /^(cssid)$/
};
rules.cssid = replace(rules.cssid, 'nonascii', rules.nonascii);
rules.cssid = replace(rules.cssid, 'escape', rules.escape);
rules.qname = replace(rules.qname, 'cssid', rules.cssid);
rules.simple = replace(rules.simple, 'cssid', rules.cssid);
rules.ref = replace(rules.ref, 'cssid', rules.cssid);
rules.attr = replace(rules.attr, 'cssid', rules.cssid);
rules.pseudo = replace(rules.pseudo, 'cssid', rules.cssid);
rules.inside = replace(rules.inside, '[^"\'>]*', rules.inside);
rules.attr = replace(rules.attr, 'inside', makeInside('\\[', '\\]'));
rules.pseudo = replace(rules.pseudo, 'inside', makeInside('\\(', '\\)'));
rules.simple = replace(rules.simple, 'pseudo', rules.pseudo);
rules.simple = replace(rules.simple, 'attr', rules.attr);
rules.ident = replace(rules.ident, 'cssid', rules.cssid);
rules.str_escape = replace(rules.str_escape, 'escape', rules.escape);
/**
* Compiling
*/
var compile = function(sel_) {
var sel = sel_.replace(/^\s+|\s+$/g, '')
, test
, filter = []
, buff = []
, subject
, qname
, cap
, op
, ref;
/*jshint -W084 */
while (sel) {
if (cap = rules.qname.exec(sel)) {
sel = sel.substring(cap[0].length);
qname = decodeid(cap[1]);
buff.push(tok(qname, true));
} else if (cap = rules.simple.exec(sel)) {
sel = sel.substring(cap[0].length);
qname = '*';
buff.push(tok(qname, true));
buff.push(tok(cap));
} else {
throw new SyntaxError('Invalid selector.');
}
while (cap = rules.simple.exec(sel)) {
sel = sel.substring(cap[0].length);
buff.push(tok(cap));
}
if (sel[0] === '!') {
sel = sel.substring(1);
subject = makeSubject();
subject.qname = qname;
buff.push(subject.simple);
}
if (cap = rules.ref.exec(sel)) {
sel = sel.substring(cap[0].length);
ref = combinators.ref(makeSimple(buff), decodeid(cap[1]));
filter.push(ref.combinator);
buff = [];
continue;
}
if (cap = rules.combinator.exec(sel)) {
sel = sel.substring(cap[0].length);
op = cap[1] || cap[2] || cap[3];
if (op === ',') {
filter.push(combinators.noop(makeSimple(buff)));
break;
}
} else {
op = 'noop';
}
if (!combinators[op]) { throw new SyntaxError('Bad combinator.'); }
filter.push(combinators[op](makeSimple(buff)));
buff = [];
}
test = makeTest(filter);
test.qname = qname;
test.sel = sel;
if (subject) {
subject.lname = test.qname;
subject.test = test;
subject.qname = subject.qname;
subject.sel = test.sel;
test = subject;
}
if (ref) {
ref.test = test;
ref.qname = test.qname;
ref.sel = test.sel;
test = ref;
}
return test;
};
var tok = function(cap, qname) {
// qname
if (qname) {
return cap === '*'
? selectors['*']
: selectors.type(cap);
}
// class/id
if (cap[1]) {
return cap[1][0] === '.'
// XXX unescape here? or in attr?
? selectors.attr('class', '~=', decodeid(cap[1].substring(1)), false)
: selectors.attr('id', '=', decodeid(cap[1].substring(1)), false);
}
// pseudo-name
// inside-pseudo
if (cap[2]) {
return cap[3]
? selectors[decodeid(cap[2])](unquote(cap[3]))
: selectors[decodeid(cap[2])];
}
// attr name
// attr op
// attr value
if (cap[4]) {
var value = cap[6];
var i = /["'\s]\s*I$/i.test(value);
if (i) {
value = value.replace(/\s*I$/i, '');
}
return selectors.attr(decodeid(cap[4]), cap[5] || '-', unquote(value), i);
}
throw new SyntaxError('Unknown Selector.');
};
var makeSimple = function(func) {
var l = func.length
, i;
// Potentially make sure
// `el` is truthy.
if (l < 2) return func[0];
return function(el) {
if (!el) return;
for (i = 0; i < l; i++) {
if (!func[i](el)) return;
}
return true;
};
};
var makeTest = function(func) {
if (func.length < 2) {
return function(el) {
return !!func[0](el);
};
}
return function(el) {
var i = func.length;
while (i--) {
if (!(el = func[i](el))) return;
}
return true;
};
};
var makeSubject = function() {
var target;
function subject(el) {
var node = el.ownerDocument
, scope = node.getElementsByTagName(subject.lname)
, i = scope.length;
while (i--) {
if (subject.test(scope[i]) && target === el) {
target = null;
return true;
}
}
target = null;
}
subject.simple = function(el) {
target = el;
return true;
};
return subject;
};
var compileGroup = function(sel) {
var test = compile(sel)
, tests = [ test ];
while (test.sel) {
test = compile(test.sel);
tests.push(test);
}
if (tests.length < 2) return test;
return function(el) {
var l = tests.length
, i = 0;
for (; i < l; i++) {
if (tests[i](el)) return true;
}
};
};
/**
* Selection
*/
var find = function(sel, node) {
var results = []
, test = compile(sel)
, scope = node.getElementsByTagName(test.qname)
, i = 0
, el;
/*jshint -W084 */
while (el = scope[i++]) {
if (test(el)) results.push(el);
}
if (test.sel) {
while (test.sel) {
test = compile(test.sel);
scope = node.getElementsByTagName(test.qname);
i = 0;
/*jshint -W084 */
while (el = scope[i++]) {
if (test(el) && indexOf.call(results, el) === -1) {
results.push(el);
}
}
}
results.sort(order);
}
return results;
};
/**
* Expose
*/
module.exports = exports = function(sel, context) {
/* when context isn't a DocumentFragment and the selector is simple: */
var id, r;
if (context.nodeType !== 11 && sel.indexOf(' ') === -1) {
if (sel[0] === '#' && context.rooted && /^#[A-Z_][-A-Z0-9_]*$/i.test(sel)) {
if (context.doc._hasMultipleElementsWithId) {
id = sel.substring(1);
if (!context.doc._hasMultipleElementsWithId(id)) {
r = context.doc.getElementById(id);
return r ? [r] : [];
}
}
}
if (sel[0] === '.' && /^\.\w+$/.test(sel)) {
return context.getElementsByClassName(sel.substring(1));
}
if (/^\w+$/.test(sel)) {
return context.getElementsByTagName(sel);
}
}
/* do things the hard/slow way */
return find(sel, context);
};
exports.selectors = selectors;
exports.operators = operators;
exports.combinators = combinators;
exports.matches = function(el, sel) {
var test = { sel: sel };
do {
test = compile(test.sel);
if (test(el)) { return true; }
} while (test.sel);
return false;
};

24
domino/sloppy.js

@ -0,0 +1,24 @@
/* Domino uses sloppy-mode features (in particular, `with`) for a few
* minor things. This file encapsulates all the sloppiness; every
* other module should be strict. */
/* jshint strict: false */
/* jshint evil: true */
/* jshint -W085 */
module.exports = {
Window_run: function _run(code, file) {
if (file) code += '\n//@ sourceURL=' + file;
with(this) eval(code);
},
EventHandlerBuilder_build: function build() {
try {
with(this.document.defaultView || Object.create(null))
with(this.document)
with(this.form)
with(this.element)
return eval("(function(event){" + this.body + "})");
}
catch (err) {
return function() { throw err; };
}
}
};

57
domino/svg.js

@ -0,0 +1,57 @@
"use strict";
var Element = require('./Element.js');
var defineElement = require('./defineElement.js');
var utils = require('./utils.js');
var CSSStyleDeclaration = require('./CSSStyleDeclaration.js');
var svgElements = exports.elements = {};
var svgNameToImpl = Object.create(null);
exports.createElement = function(doc, localName, prefix) {
var impl = svgNameToImpl[localName] || SVGElement;
return new impl(doc, localName, prefix);
};
function define(spec) {
return defineElement(spec, SVGElement, svgElements, svgNameToImpl);
}
var SVGElement = define({
superclass: Element,
ctor: function SVGElement(doc, localName, prefix) {
Element.call(this, doc, localName, utils.NAMESPACE.SVG, prefix);
},
props: {
style: { get: function() {
if (!this._style)
this._style = new CSSStyleDeclaration(this);
return this._style;
}}
}
});
define({
ctor: function SVGSVGElement(doc, localName, prefix) {
SVGElement.call(this, doc, localName, prefix);
},
tag: 'svg',
props: {
createSVGRect: { value: function () {
return exports.createElement(this.ownerDocument, 'rect', null);
} }
}
});
define({
tags: [
'a', 'altGlyph', 'altGlyphDef', 'altGlyphItem', 'animate', 'animateColor', 'animateMotion', 'animateTransform',
'circle', 'clipPath', 'color-profile', 'cursor', 'defs', 'desc', 'ellipse', 'feBlend', 'feColorMatrix',
'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight',
'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode',
'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence', 'filter',
'font', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignObject', 'g',
'glyph', 'glyphRef', 'hkern', 'image', 'line', 'linearGradient', 'marker', 'mask', 'metadata', 'missing-glyph',
'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'script', 'set', 'stop', 'style',
'switch', 'symbol', 'text', 'textPath', 'title', 'tref', 'tspan', 'use', 'view', 'vkern'
]
});

85
domino/utils.js

@ -0,0 +1,85 @@
"use strict";
var DOMException = require('./DOMException.js');
var ERR = DOMException;
var isApiWritable = require("./config.js").isApiWritable;
exports.NAMESPACE = {
HTML: 'http://www.w3.org/1999/xhtml',
XML: 'http://www.w3.org/XML/1998/namespace',
XMLNS: 'http://www.w3.org/2000/xmlns/',
MATHML: 'http://www.w3.org/1998/Math/MathML',
SVG: 'http://www.w3.org/2000/svg',
XLINK: 'http://www.w3.org/1999/xlink'
};
//
// Shortcut functions for throwing errors of various types.
//
exports.IndexSizeError = function() { throw new DOMException(ERR.INDEX_SIZE_ERR); };
exports.HierarchyRequestError = function() { throw new DOMException(ERR.HIERARCHY_REQUEST_ERR); };
exports.WrongDocumentError = function() { throw new DOMException(ERR.WRONG_DOCUMENT_ERR); };
exports.InvalidCharacterError = function() { throw new DOMException(ERR.INVALID_CHARACTER_ERR); };
exports.NoModificationAllowedError = function() { throw new DOMException(ERR.NO_MODIFICATION_ALLOWED_ERR); };
exports.NotFoundError = function() { throw new DOMException(ERR.NOT_FOUND_ERR); };
exports.NotSupportedError = function() { throw new DOMException(ERR.NOT_SUPPORTED_ERR); };
exports.InvalidStateError = function() { throw new DOMException(ERR.INVALID_STATE_ERR); };
exports.SyntaxError = function() { throw new DOMException(ERR.SYNTAX_ERR); };
exports.InvalidModificationError = function() { throw new DOMException(ERR.INVALID_MODIFICATION_ERR); };
exports.NamespaceError = function() { throw new DOMException(ERR.NAMESPACE_ERR); };
exports.InvalidAccessError = function() { throw new DOMException(ERR.INVALID_ACCESS_ERR); };
exports.TypeMismatchError = function() { throw new DOMException(ERR.TYPE_MISMATCH_ERR); };
exports.SecurityError = function() { throw new DOMException(ERR.SECURITY_ERR); };
exports.NetworkError = function() { throw new DOMException(ERR.NETWORK_ERR); };
exports.AbortError = function() { throw new DOMException(ERR.ABORT_ERR); };
exports.UrlMismatchError = function() { throw new DOMException(ERR.URL_MISMATCH_ERR); };
exports.QuotaExceededError = function() { throw new DOMException(ERR.QUOTA_EXCEEDED_ERR); };
exports.TimeoutError = function() { throw new DOMException(ERR.TIMEOUT_ERR); };
exports.InvalidNodeTypeError = function() { throw new DOMException(ERR.INVALID_NODE_TYPE_ERR); };
exports.DataCloneError = function() { throw new DOMException(ERR.DATA_CLONE_ERR); };
exports.nyi = function() {
throw new Error("NotYetImplemented");
};
exports.shouldOverride = function() {
throw new Error("Abstract function; should be overriding in subclass.");
};
exports.assert = function(expr, msg) {
if (!expr) {
throw new Error("Assertion failed: " + (msg || "") + "\n" + new Error().stack);
}
};
exports.expose = function(src, c) {
for (var n in src) {
Object.defineProperty(c.prototype, n, { value: src[n], writable: isApiWritable });
}
};
exports.merge = function(a, b) {
for (var n in b) {
a[n] = b[n];
}
};
// Compare two nodes based on their document order. This function is intended
// to be passed to sort(). Assumes that the array being sorted does not
// contain duplicates. And that all nodes are connected and comparable.
// Clever code by ppk via jeresig.
exports.documentOrder = function(n,m) {
/* jshint bitwise: false */
return 3 - (n.compareDocumentPosition(m) & 6);
};
exports.toASCIILowerCase = function(s) {
return s.replace(/[A-Z]+/g, function(c) {
return c.toLowerCase();
});
};
exports.toASCIIUpperCase = function(s) {
return s.replace(/[a-z]+/g, function(c) {
return c.toUpperCase();
});
};

91
domino/xmlnames.js

@ -0,0 +1,91 @@
"use strict";
// This grammar is from the XML and XML Namespace specs. It specifies whether
// a string (such as an element or attribute name) is a valid Name or QName.
//
// Name ::= NameStartChar (NameChar)*
// NameStartChar ::= ":" | [A-Z] | "_" | [a-z] |
// [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] |
// [#x370-#x37D] | [#x37F-#x1FFF] |
// [#x200C-#x200D] | [#x2070-#x218F] |
// [#x2C00-#x2FEF] | [#x3001-#xD7FF] |
// [#xF900-#xFDCF] | [#xFDF0-#xFFFD] |
// [#x10000-#xEFFFF]
//
// NameChar ::= NameStartChar | "-" | "." | [0-9] |
// #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
//
// QName ::= PrefixedName| UnprefixedName
// PrefixedName ::= Prefix ':' LocalPart
// UnprefixedName ::= LocalPart
// Prefix ::= NCName
// LocalPart ::= NCName
// NCName ::= Name - (Char* ':' Char*)
// # An XML Name, minus the ":"
//
exports.isValidName = isValidName;
exports.isValidQName = isValidQName;
// Most names will be ASCII only. Try matching against simple regexps first
var simplename = /^[_:A-Za-z][-.:\w]+$/;
var simpleqname = /^([_A-Za-z][-.\w]+|[_A-Za-z][-.\w]+:[_A-Za-z][-.\w]+)$/;
// If the regular expressions above fail, try more complex ones that work
// for any identifiers using codepoints from the Unicode BMP
var ncnamestartchars = "_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02ff\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD";
var ncnamechars = "-._A-Za-z0-9\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02ff\u0300-\u037D\u037F-\u1FFF\u200C\u200D\u203f\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD";
var ncname = "[" + ncnamestartchars + "][" + ncnamechars + "]*";
var namestartchars = ncnamestartchars + ":";
var namechars = ncnamechars + ":";
var name = new RegExp("^[" + namestartchars + "]" + "[" + namechars + "]*$");
var qname = new RegExp("^(" + ncname + "|" + ncname + ":" + ncname + ")$");
// XML says that these characters are also legal:
// [#x10000-#xEFFFF]. So if the patterns above fail, and the
// target string includes surrogates, then try the following
// patterns that allow surrogates and then run an extra validation
// step to make sure that the surrogates are in valid pairs and in
// the right range. Note that since the characters \uf0000 to \u1f0000
// are not allowed, it means that the high surrogate can only go up to
// \uDB7f instead of \uDBFF.
var hassurrogates = /[\uD800-\uDB7F\uDC00-\uDFFF]/;
var surrogatechars = /[\uD800-\uDB7F\uDC00-\uDFFF]/g;
var surrogatepairs = /[\uD800-\uDB7F][\uDC00-\uDFFF]/g;
// Modify the variables above to allow surrogates
ncnamestartchars += "\uD800-\uDB7F\uDC00-\uDFFF";
ncnamechars += "\uD800-\uDB7F\uDC00-\uDFFF";
ncname = "[" + ncnamestartchars + "][" + ncnamechars + "]*";
namestartchars = ncnamestartchars + ":";
namechars = ncnamechars + ":";
// Build another set of regexps that include surrogates
var surrogatename = new RegExp("^[" + namestartchars + "]" + "[" + namechars + "]*$");
var surrogateqname = new RegExp("^(" + ncname + "|" + ncname + ":" + ncname + ")$");
function isValidName(s) {
if (simplename.test(s)) return true; // Plain ASCII
if (name.test(s)) return true; // Unicode BMP
// Maybe the tests above failed because s includes surrogate pairs
// Most likely, though, they failed for some more basic syntax problem
if (!hassurrogates.test(s)) return false;
// Is the string a valid name if we allow surrogates?
if (!surrogatename.test(s)) return false;
// Finally, are the surrogates all correctly paired up?
var chars = s.match(surrogatechars), pairs = s.match(surrogatepairs);
return pairs !== null && 2*pairs.length === chars.length;
}
function isValidQName(s) {
if (simpleqname.test(s)) return true; // Plain ASCII
if (qname.test(s)) return true; // Unicode BMP
if (!hassurrogates.test(s)) return false;
if (!surrogateqname.test(s)) return false;
var chars = s.match(surrogatechars), pairs = s.match(surrogatepairs);
return pairs !== null && 2*pairs.length === chars.length;
}

1535
govue/axios.js
File diff suppressed because it is too large
View File

4
govue/govue.js

@ -1,5 +1,7 @@
window = domino.createWindow(HTML_SRC, '127.0.0.1');
window = domino.createWindow(HTML_SRC, SELF_HREF);
document = window.document;
location = window.location;
GoVueUseCall(Vue);

28
govue/init.js

@ -0,0 +1,28 @@
window = domino.createWindow("", "http://127.0.0.1/");
document = window.document;
location = window.location;
XMLHttpRequest = function () {
console.log("XMLHttpRequest");
this.open = function (m, url) {
console.log("XMLHttpRequest.open");
this.m = m;
this.url = url;
this.onreadystatechange("", "", 0, 0)
};
this.send = function (data) {
this.onreadystatechange("", "", 1, 0);
this.onreadystatechange("", "", 2, 0);
this.onreadystatechange("", "", 3, 0);
var result = GO_AJAX(m, url, data);
result = JSON.parse(result);
this.onreadystatechange(result["data"], result["data"], 4, parseInt(result["code"]))
};
this.onreadystatechange = function (responseText, response, readyState, status) {
};
return this
};
navigator = {
"userAgent": ""
};

1183
govue/promise.js
File diff suppressed because it is too large
View File

80
jsruntime/runtime.go

@ -1,6 +1,8 @@
package jsruntime
import (
"encoding/json"
"git.ouxuan.net/hasaki-service/hasaki-sdk/hskhttpdo"
"github.com/dop251/goja"
"github.com/dop251/goja_nodejs/console"
"github.com/dop251/goja_nodejs/eventloop"
@ -13,9 +15,11 @@ type JsRuntime struct {
MainSrc string
UseSrc string
Relys Relys
TimeoutSec int64
runtime *goja.Runtime
registry *require.Registry
requireModule *require.RequireModule
loop *eventloop.EventLoop
}
type Rely struct {
@ -26,7 +30,8 @@ type Relys []Rely
func NewJsRuntime(mainFileName string, useFileName string, rels Relys) (*JsRuntime, error) {
jr := JsRuntime{
Relys: rels,
Relys: rels,
TimeoutSec: 8,
}
mainScript, err := ioutil.ReadFile(mainFileName)
if err != nil {
@ -34,14 +39,20 @@ func NewJsRuntime(mainFileName string, useFileName string, rels Relys) (*JsRunti
}
jr.MainSrc = string(mainScript)
jr.registry = new(require.Registry) // this can be shared by multiple runtimes
loop := eventloop.NewEventLoop()
loop.Run(func(r *goja.Runtime) {
jr.loop = eventloop.NewEventLoop()
jr.loop.Run(func(r *goja.Runtime) {
jr.runtime = r
})
jr.requireModule = jr.registry.Enable(jr.runtime)
console.Enable(jr.runtime)
jr.runtime.Set("GoVueUse", func(v interface{}) {
jr.runtime.Set("GoVueUseCall", v)
})
jr.setAjax()
for e := range rels {
v, err := jr.requireModule.Require(rels[e].FileName)
if err != nil {
@ -52,21 +63,70 @@ func NewJsRuntime(mainFileName string, useFileName string, rels Relys) (*JsRunti
go func() {
for {
useSrc, _ := ioutil.ReadFile(mainFileName)
useSrc, _ := ioutil.ReadFile(useFileName)
jr.UseSrc = string(useSrc)
time.Sleep(time.Second * 2)
}
}()
return &jr, nil
}
func (jr *JsRuntime) Render(tpl string) string {
jr.runtime.Set("HTML_SRC", tpl)
func (jr *JsRuntime) setAjax() {
jr.runtime.Set("GO_AJAX", func(call goja.FunctionCall) string {
m := call.Argument(0)
url := call.Argument(1)
data := call.Argument(3)
res, err := hskhttpdo.HttpDo{
Url: url.ToString().String(),
Raw: []byte(data.ToString().String()),
}.Request(m.ToString().String())
r := map[string]string{
"code": "200",
"data": string(res),
}
if err != nil {
r["code"] = "502"
}
rs, _ := json.Marshal(r)
return string(rs)
})
}
func (jr *JsRuntime) SetVariable(name string, value interface{}) {
jr.runtime.Set(name, value)
}
func (jr *JsRuntime) Render(href, tplSrc string) string {
jr.registry.Lock()
isLock := true
go func() {
time.Sleep(time.Duration(jr.TimeoutSec) * time.Second)
if isLock {
jr.runtime.Interrupt("timeout")
}
}()
defer func() {
isLock = false
jr.registry.Unlock()
}()
runtime.Set("HTML_OUT", func(data string) {
context.Writer.WriteHeader(200)
context.Writer.Write([]byte(data))
_, err := jr.runtime.RunString(jr.UseSrc)
if err != nil {
return err.Error()
}
jr.runtime.Set("HTML_SRC", tplSrc)
jr.runtime.Set("SELF_HREF", href)
return
result := ""
jr.runtime.Set("HTML_OUT", func(data string) {
result = data
})
_, err = jr.runtime.RunString(jr.MainSrc)
if err != nil {
return err.Error()
}
return result
}

61
main.go

@ -1,6 +1,7 @@
package main
import (
"fmt"
"git.ouxuan.net/3136352472/go-service-template/jsruntime"
"git.ouxuan.net/hasaki-service/hasaki-sdk/hskutils"
"github.com/gin-gonic/gin"
@ -24,6 +25,18 @@ func main() {
FileName: "./index.js",
},
jsruntime.Rely{
Variable: "init",
FileName: filepath.Join("..", "govue", "init.js"),
},
jsruntime.Rely{
Variable: "Promise",
FileName: filepath.Join("..", "govue", "promise.js"),
},
jsruntime.Rely{
Variable: "axios",
FileName: filepath.Join("..", "govue", "axios.js"),
},
jsruntime.Rely{
Variable: "Vue",
FileName: "./vue.js",
},
@ -32,12 +45,6 @@ func main() {
panic(err)
}
mainScript, err := ioutil.ReadFile(filepath.Join(hskutils.GetSelfFilePath(), "govue", "govue.js"))
if err != nil {
panic(err.Error())
return
}
r.NoRoute(func(context *gin.Context) {
path := filepath.Join(hskutils.GetSelfFilePath(), "static", context.Request.URL.Path)
fi, err := os.Stat(path)
@ -60,37 +67,17 @@ func main() {
return
}
htmlSrc := string(raw)
registry.Lock()
defer registry.Unlock()
useScript, err := ioutil.ReadFile(filepath.Join(hskutils.GetSelfFilePath(), "static", "use.js"))
if err != nil {
panic(err.Error())
return
}
runtime.Set("GoVueUse", func(v interface{}) {
runtime.Set("GoVueUseCall", v)
})
_, err = runtime.RunString(string(useScript))
runtime.Set("HTML_SRC", htmlSrc)
runtime.Set("HTML_OUT", func(data string) {
context.Writer.WriteHeader(200)
context.Writer.Write([]byte(data))
return
})
_, err = runtime.RunString(string(mainScript))
if err != nil {
log.Println(err)
}
context.Writer.Write([]byte(""))
log.Println(context.Request.URL.RawPath)
log.Println(context.Request.URL.RawQuery)
log.Println(context.Request.URL.RequestURI())
log.Println(context.Request.URL.String())
log.Println(context.Request.URL.Scheme)
log.Println(context.Request.Host)
log.Println(context.Request.RequestURI)
log.Println(context.Request.Proto)
result := render.Render(fmt.Sprintf("http://%s%s", context.Request.Host, context.Request.RequestURI), string(raw))
context.Writer.WriteHeader(200)
context.Writer.Write([]byte(result))
return
})

14
static/use.js

@ -1,5 +1,19 @@
GoVueUse(function (Vue) {
console.log("213");
console.log(location.href);
console.log(axios.defaults);
// for (var i in axios.defaults){
// console.log(i)
// console.log(axios.defaults[i])
// }
axios.defaults.baseURL = "http://127.0.0.1";
axios.get("/classifyer/all?parent_classifyer_id=%E7%90%83%E9%A6%86%E5%88%86%E7%B1%BB").then(function (r) {
console.log("get");
console.log(r);
}).catch(function (err) {
console.log("geterr");
console.log(err);
});
new Vue({
el: "#classifyer",
data: {

Loading…
Cancel
Save