63 changed files with 29236 additions and 48 deletions
-
579domino/CSSStyleDeclaration.js
-
120domino/CharacterData.js
-
119domino/ChildNode.js
-
39domino/Comment.js
-
80domino/ContainerNode.js
-
12domino/CustomEvent.js
-
134domino/DOMException.js
-
94domino/DOMImplementation.js
-
172domino/DOMTokenList.js
-
884domino/Document.js
-
68domino/DocumentFragment.js
-
36domino/DocumentType.js
-
1202domino/Element.js
-
66domino/Event.js
-
298domino/EventTarget.js
-
92domino/FilteredElementList.js
-
7264domino/HTMLParser.js
-
37domino/Leaf.js
-
44domino/LinkedList.js
-
56domino/Location.js
-
52domino/MouseEvent.js
-
9domino/MutationConstants.js
-
41domino/NamedNodeMap.js
-
17domino/NavigatorID.js
-
738domino/Node.js
-
24domino/NodeFilter.js
-
217domino/NodeIterator.js
-
15domino/NodeList.es5.js
-
12domino/NodeList.es6.js
-
13domino/NodeList.js
-
87domino/NodeTraversal.js
-
168domino/NodeUtils.js
-
26domino/NonDocumentTypeChildNode.js
-
43domino/ProcessingInstruction.js
-
74domino/Text.js
-
336domino/TreeWalker.js
-
19domino/UIEvent.js
-
194domino/URL.js
-
270domino/URLUtils.js
-
62domino/Window.js
-
11domino/WindowTimers.js
-
152domino/attributes.js
-
7domino/config.js
-
6654domino/cssparser.js
-
70domino/defineElement.js
-
7domino/events.js
-
1426domino/htmlelts.js
-
27domino/impl.js
-
5domino/index.d.ts
-
79domino/index.js
-
2938domino/jquery.js
-
933domino/select.js
-
24domino/sloppy.js
-
57domino/svg.js
-
85domino/utils.js
-
91domino/xmlnames.js
-
1535govue/axios.js
-
4govue/govue.js
-
28govue/init.js
-
1183govue/promise.js
-
80jsruntime/runtime.go
-
61main.go
-
14static/use.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); |
|||
} |
|||
}); |
|||
} |
|||
} |
@ -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); |
@ -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; |
@ -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); |
|||
}}, |
|||
}); |
@ -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
|
|||
}}, |
|||
|
|||
}); |
@ -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 } |
|||
}); |
@ -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); |
|||
} |
@ -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, |
|||
}; |
@ -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; |
|||
}); |
|||
} |
|||
} |
@ -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; |
|||
}; |
@ -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 |
|||
}, |
|||
|
|||
}); |
@ -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
File diff suppressed because it is too large
View File
@ -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; |
|||
}}, |
|||
|
|||
}); |
@ -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; |
|||
} |
|||
|
|||
}; |
@ -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
File diff suppressed because it is too large
View File
@ -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; |
|||
}} |
|||
}); |
@ -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)); |
|||
} |
|||
}; |
@ -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; |
|||
}} |
|||
|
|||
}); |
@ -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; |
|||
} |
|||
}} |
|||
}); |
@ -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
|
|||
}; |
@ -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(); |
|||
} }, |
|||
}); |
@ -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; |
@ -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 }, |
|||
}); |
@ -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); |
@ -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]"; |
|||
} }, |
|||
}); |
@ -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; |
@ -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; } |
|||
}; |
@ -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; |
@ -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; |
|||
} |
@ -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 '&'; |
|||
case '<': return '<'; |
|||
case '>': return '>'; |
|||
case '\u00A0': return ' '; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
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 '&'; |
|||
case '"': return '"'; |
|||
case '\u00A0': return ' '; |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
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; |
|||
} |
@ -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; |
@ -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; |
|||
}} |
|||
|
|||
}); |
@ -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); |
|||
}}, |
|||
|
|||
}); |
@ -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]"; |
|||
}}, |
|||
}); |
@ -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; |
|||
}} |
|||
}); |
@ -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; |
|||
} |
|||
}, |
|||
}; |
@ -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); |
|||
}); |
|||
}; |
@ -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); |
@ -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; |
@ -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; |
|||
}; |
@ -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
File diff suppressed because it is too large
View File
@ -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); |
|||
}); |
|||
} |
@ -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
File diff suppressed because it is too large
View File
@ -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); |
@ -0,0 +1,5 @@ |
|||
declare module "domino" { |
|||
function createDOMImplementation(): DOMImplementation; |
|||
function createDocument(html?: string, force?: boolean): Document; |
|||
function createWindow(html?: string, address?: string): Window; |
|||
} |
@ -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
File diff suppressed because it is too large
View File
@ -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; |
|||
}; |
@ -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; }; |
|||
} |
|||
} |
|||
}; |
@ -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' |
|||
] |
|||
}); |
@ -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(); |
|||
}); |
|||
}; |
@ -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
File diff suppressed because it is too large
View File
@ -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
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save
Reference in new issue