shithub: opossum

Download patch

ref: 39318cd9ec341341102854546dcd54326d9ba4fe
author: Philip Silva <philip.silva@protonmail.com>
date: Sat Dec 5 09:12:53 EST 2020

First public commit

diff: cannot open b/browser//null: file does not exist: 'b/browser//null' diff: cannot open b/cmd/browse//null: file does not exist: 'b/cmd/browse//null' diff: cannot open b/cmd//null: file does not exist: 'b/cmd//null' diff: cannot open b/domino-lib//null: file does not exist: 'b/domino-lib//null' diff: cannot open b/domino//null: file does not exist: 'b/domino//null' diff: cannot open b/logger//null: file does not exist: 'b/logger//null' diff: cannot open b/nodes//null: file does not exist: 'b/nodes//null' diff: cannot open b/style//null: file does not exist: 'b/style//null'
--- /dev/null
+++ b/.gitignore
@@ -1,0 +1,14 @@
+/possum
+/opossum
+/possum-src
+/possum-plan9-amd64
+*.bin
+*.out
+*.pdf
+/-cpuprofile
+*.prof
+*.swp
+*.tgz
+~*
+.DS_Store
+/cmd/browse/browse
--- /dev/null
+++ b/LICENSE
@@ -1,0 +1,27 @@
+Copyright (c) 2020, Philip Silva <philip.silva@protonmail.com>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null
+++ b/README.md
@@ -1,0 +1,43 @@
+# Opossum Web Browser
+
+Basic portable Web browser; only needs a Go compiler to compile, no C dependencies
+
+Supported features:
+
+- rudimentary CSS/HTML5 support, large parts like float/flex layout are just stub implementations
+- Server-side rendered websites
+- Images (pre-loaded all at once though)
+- TLS
+- experimental JS/DOM without AJAX can be activated (basically script tags are evaluated)
+
+# Install
+
+## Plan 9
+
+You can download a tarball with the binary at http://psilva.sdf.org/opossum-plan9-amd64.tgz
+
+To compile the source Go 1.15 is needed. Probably `$GOPROXY` should be set to `https://proxy.golang.org`
+
+```
+cd cmd/browse
+go run .
+```
+
+## macOS
+
+Requirements:
+
+- Go
+- Plan9Port
+
+```
+cd cmd/browse
+go run .
+```
+
+## TODO
+
+- load images on the fly
+- implement more parts of HTML5 and CSS
+- create a widget for div/span
+- clean up code, support webfs, snarf, file downloads
\ No newline at end of file
--- /dev/null
+++ b/browser/browser.go
@@ -1,0 +1,1529 @@
+package browser
+
+import (
+	"9fans.net/go/draw"
+	"bytes"
+	"encoding/base64"
+	"errors"
+	"fmt"
+	"github.com/nfnt/resize"
+	"golang.org/x/net/html"
+	"golang.org/x/net/publicsuffix"
+	"image"
+	"image/jpeg"
+	"io/ioutil"
+	"net/http"
+	"net/http/cookiejar"
+	"net/url"
+	"opossum"
+	"opossum/domino"
+	"opossum/logger"
+	"opossum/nodes"
+	"opossum/style"
+	"strconv"
+	"strings"
+	"unicode"
+
+	"github.com/mjl-/duit"
+
+	_ "image/gif"
+	_ "image/jpeg"
+	_ "image/png"
+)
+
+const debugPrintHtml = false
+const stashElements = true
+const experimentalUseSlicedDrawing = false
+
+var DebugDumpCSS *bool
+var ExperimentalJsInsecure *bool
+
+var browser *Browser // TODO: limit global objects;
+//       at least put them in separate pkgs
+//       with good choiced private/public
+//       p
+var Style = style.Map{}
+var dui *duit.DUI
+var colorCache = make(map[draw.Color]*draw.Image)
+var cache = make(map[string]struct {
+	opossum.ContentType
+	buf []byte
+})
+var numElements int64
+var log *logger.Logger
+var scroller *duit.Scroll
+
+func SetLogger(l *logger.Logger) {
+	log = l
+}
+
+type ColoredLabel struct {
+	*duit.Label
+
+	Map style.Map
+}
+
+func (ui *ColoredLabel) Draw(dui *duit.DUI, self *duit.Kid, img *draw.Image, orig image.Point, m draw.Mouse, force bool) {
+	// TODO: hacky function, might lead to crashes and memory leaks
+	c := ui.Map.Color()
+	i, ok := colorCache[c]
+	if !ok {
+		var err error
+		i, err = dui.Display.AllocImage(image.Rect(0, 0, 10, 10), draw.ARGB32, true, c)
+		if err != nil {
+			panic(err.Error())
+		}
+		colorCache[c] = i
+	}
+	var swap *draw.Image = dui.Regular.Normal.Text
+	dui.Regular.Normal.Text = i
+	ui.Label.Draw(dui, self, img, orig, m, force)
+	dui.Regular.Normal.Text = swap
+}
+
+type CodeView struct {
+	duit.UI
+}
+
+func NewCodeView(s string, n style.Map) (cv *CodeView) {
+	log.Printf("NewCodeView(%+v)", s)
+	cv = &CodeView{}
+	edit := &duit.Edit{}
+	edit.Keys = func(k rune, m draw.Mouse) (e duit.Event) {
+		//log.Printf("k=%v (c %v    p %v)", k, unicode.IsControl(k), unicode.IsPrint(k))
+		if unicode.IsPrint(k) {
+			e.Consumed = true
+		}
+		return
+	}
+	formatted := ""
+	lines := strings.Split(s, "\n")
+	for _, line := range lines {
+		formatted += strings.TrimSpace(line) + "\n"
+	}
+	log.Printf("formatted=%+v", formatted)
+	edit.Append([]byte(formatted))
+	cv.UI = &duit.Box{
+		Kids:   duit.NewKids(edit),
+		Height: (int(n.FontSize()) + 4) * len(lines),
+	}
+	return
+}
+
+func (cv *CodeView) Mouse(dui *duit.DUI, self *duit.Kid, m draw.Mouse, origM draw.Mouse, orig image.Point) (r duit.Result) {
+	//log.Printf("m=%+v",m.Buttons)
+	if m.Buttons == 8 || m.Buttons == 16 {
+		//r.Consumed = true
+		return
+	}
+	return cv.UI.Mouse(dui, self, m, origM, orig)
+}
+
+type Image struct {
+	*duit.Image
+
+	src string
+}
+
+func NewImage(display *draw.Display, n nodes.Node) duit.UI {
+	img, err := newImage(display, n)
+	if err != nil {
+		log.Errorf("could not load image: %v", err)
+		return &duit.Label{}
+	}
+	return img
+}
+
+func parseDataUri(addr string) (data []byte, err error) {
+	if strings.Contains(addr, "charset=UTF-8") {
+		return nil, fmt.Errorf("cannot handle charset")
+	}
+	parts := strings.Split(addr, ",")
+	e := base64.RawStdEncoding
+	if strings.HasSuffix(addr, "=") {
+		e = base64.StdEncoding
+	}
+	if data, err = e.DecodeString(parts[1]); err != nil {
+		return nil, fmt.Errorf("decode %v src: %w", addr, err)
+	}
+	return
+}
+
+func newImage(display *draw.Display, n nodes.Node) (ui duit.UI, err error) {
+	src := attr(*n.DomSubtree, "src")
+	if src == "" {
+		return nil, fmt.Errorf("no src in %+v", n.Attr)
+	}
+	var imgUrl *url.URL
+	var data []byte
+	if strings.HasPrefix(src, "data:") {
+		if data, err = parseDataUri(src); err != nil {
+			return nil, fmt.Errorf("parse data uri %v: %w", src, err)
+		}
+	} else {
+		if imgUrl, err = browser.LinkedUrl(src); err != nil {
+			return nil, err
+		}
+		if data, _, err = browser.Get(*imgUrl); err != nil {
+			return nil, fmt.Errorf("get %v: %w", *imgUrl, err)
+		}
+	}
+
+	var w int
+	var h int
+	wStr, ok := n.Declarations["width"]
+	if ok {
+		w, _ = strconv.Atoi(strings.TrimSuffix(wStr.Value, "px"))
+	}
+	hStr, ok := n.Declarations["height"]
+	if ok {
+		h, _ = strconv.Atoi(strings.TrimSuffix(hStr.Value, "px"))
+	}
+	if w != 0 || h != 0 {
+		image, _, err := image.Decode(bytes.NewReader(data))
+		if err != nil {
+			return nil, fmt.Errorf("decode %v: %w", *imgUrl, err)
+		}
+		// check err
+
+		newImage := resize.Resize(uint(w), uint(h), image, resize.Lanczos3)
+
+		// Encode uses a Writer, use a Buffer if you need the raw []byte
+		buf := bytes.NewBufferString("")
+		if err = jpeg.Encode(buf, newImage, nil); err != nil {
+			return nil, fmt.Errorf("encode: %w", err)
+		}
+		data = buf.Bytes()
+	}
+	r := bytes.NewReader(data)
+	log.Printf("Read %v...", imgUrl)
+	img, err := duit.ReadImage(display, r)
+	if err != nil {
+		return nil, fmt.Errorf("duit read image: %w (len=%v)", err, len(data))
+	}
+	log.Printf("Done reading %v", imgUrl)
+	return &Element{
+		UI: &Image{
+			Image: &duit.Image{
+				Image: img,
+			},
+			src: src,
+		},
+	}, nil
+}
+
+type Element struct {
+	duit.UI
+	CS     style.Map
+	IsLink bool
+	Click  func() duit.Event
+}
+
+func NewElement(ui duit.UI, cs style.Map) *Element {
+	if ui == nil {
+		return nil
+	}
+	if cs.IsDisplayNone() {
+		return nil
+	}
+
+	if stashElements {
+		existingEl, ok := ui.(*Element)
+		if ok && existingEl != nil {
+			// TODO: check is cs and existingEl shouldn't be vice-versa
+			ccs := cs.ApplyChildStyle(existingEl.CS)
+			return &Element{
+				UI: existingEl.UI,
+				CS: ccs,
+			}
+		}
+	}
+	return &Element{
+		UI: ui,
+		CS: cs,
+	}
+}
+
+func NewBoxElement(ui duit.UI, cs style.Map) *Element {
+	if ui == nil {
+		return nil
+	}
+	if cs.IsDisplayNone() {
+		return nil
+	}
+	var w int
+	var h int
+	wStr, ok := cs.Declarations["width"]
+	if ok {
+		w, _ = strconv.Atoi(strings.TrimSuffix(wStr.Value, "px"))
+	}
+	hStr, ok := cs.Declarations["height"]
+	if ok {
+		h, _ = strconv.Atoi(strings.TrimSuffix(hStr.Value, "px"))
+	}
+	var i *draw.Image
+	var err error
+	if w == 0 && h == 0 {
+		return NewElement(ui, cs)
+	}
+	if i, err = cs.BoxBackground(); err != nil {
+		log.Printf("box background: %f", err)
+	}
+	box := &duit.Box{
+		Kids:       duit.NewKids(ui),
+		Width:      w,
+		Height:     h,
+		Background: i,
+	}
+	el := NewElement(box, cs)
+	return el
+}
+
+func (el *Element) Draw(dui *duit.DUI, self *duit.Kid, img *draw.Image, orig image.Point, m draw.Mouse, force bool) {
+	if el == nil {
+		return
+	}
+	if el.slicedDraw(dui, self, img, orig, m, force) {
+		return
+	}
+	box, ok := el.UI.(*duit.Box)
+	if ok && box.Width > 0 && box.Height > 0 {
+		uiSize := image.Point{X: box.Width, Y: box.Height}
+		duit.KidsDraw(dui, self, box.Kids, uiSize, box.Background, img, orig, m, force)
+	} else {
+		el.UI.Draw(dui, self, img, orig, m, force)
+	}
+}
+
+func (el *Element) Layout(dui *duit.DUI, self *duit.Kid, sizeAvail image.Point, force bool) {
+	if el == nil {
+		return
+	}
+	box, ok := el.UI.(*duit.Box)
+	if ok && box.Width > 0 && box.Height > 0 {
+		//dui.debugLayout(self)
+		//if ui.Image == nil {
+		//	self.R = image.ZR
+		//} else {
+		//	self.R = rect(ui.Image.R.Size())
+		//}
+		//duit.KidsLayout(dui, self, box.Kids, true)
+
+		el.UI.Layout(dui, self, sizeAvail, force)
+		self.R = image.Rect(0, 0, box.Width, box.Height)
+	} else {
+		el.UI.Layout(dui, self, sizeAvail, force)
+	}
+
+	return
+}
+
+func (el *Element) Mark(self *duit.Kid, o duit.UI, forLayout bool) (marked bool) {
+	if el != nil {
+		return el.UI.Mark(self, o, forLayout)
+	}
+	return
+}
+
+func NewSubmitButton(b *Browser, n *nodes.Node) *Element {
+	t := attr(*n.DomSubtree, "value")
+	if t == "" {
+		t = "Submit"
+	}
+	btn := &duit.Button{
+		Text: t,
+		Font: n.Font(),
+		Click: func() (r duit.Event) {
+			b.submit(n.ParentForm().DomSubtree)
+			//r.Consumed = true
+			return duit.Event{
+				Consumed:   true,
+				NeedLayout: true,
+				NeedDraw:   true,
+			}
+			//return
+		},
+	}
+	return &Element{
+		UI: btn,
+		CS: n.Map,
+	}
+}
+
+func NewInputField(n *nodes.Node) *Element {
+	t := attr(*n.DomSubtree, "type")
+	return &Element{
+		UI: &duit.Box{
+			Kids: duit.NewKids(&duit.Field{
+				Font:        n.Font(),
+				Placeholder: attr(*n.DomSubtree, "placeholder"),
+				Password:    t == "password",
+				Text:        attr(*n.DomSubtree, "value"),
+				Changed: func(t string) (r duit.Event) {
+					setAttr(n.DomSubtree, "value", t)
+					r.Consumed = true
+					return
+				},
+			}),
+			MaxWidth: 200,
+		},
+		CS: n.Map,
+	}
+}
+
+func (el *Element) Mouse(dui *duit.DUI, self *duit.Kid, m draw.Mouse, origM draw.Mouse, orig image.Point) (r duit.Result) {
+	if el == nil {
+		return
+	}
+	if m.Buttons == 1 {
+		if el.Click != nil {
+			el.Click()
+		}
+	}
+	x := m.Point.X
+	y := m.Point.Y
+	maxX := self.R.Dx()
+	maxY := self.R.Dy()
+	if 5 <= x && x <= (maxX-5) && 5 <= y && y <= (maxY-5) {
+		//log.Printf("Mouse %v    (m ~ %v); Kid.R.Dx/Dy=%v/%v\n", el.UI, m.Point, self.R.Dx(), self.R.Dy())
+		if el.IsLink {
+			//fmt.Printf("do hover\n")
+			// dui.Display.SetCursor(nil) // set to system cursor
+			yolo := [2 * 16]uint8{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 90, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
+			dui.Display.SetCursor(&draw.Cursor{
+				Set: yolo,
+			})
+			if m.Buttons == 0 {
+				r.Consumed = true
+				return r
+			}
+		}
+	} else {
+		if el.IsLink {
+			// this never hhappens :/
+			//fmt.Printf("unhover\n")
+			dui.Display.SetCursor(nil)
+		} else {
+			dui.Display.SetCursor(nil)
+		}
+	}
+	return el.UI.Mouse(dui, self, m, origM, orig)
+}
+
+func (el *Element) onClickLeafes(f func() duit.Event) {
+	PrintTree(el) // todo: debug remove me
+	TraverseTree(el, func(ui duit.UI) {
+		el, ok := ui.(*Element)
+		if ok && el != nil {
+			el.IsLink = true
+			el.Click = f
+			return
+		}
+		l, ok := ui.(*duit.Label)
+		if ok && l != nil {
+			l.Click = f
+			return
+		}
+	})
+}
+
+func attr(n html.Node, key string) (val string) {
+	for _, a := range n.Attr {
+		if a.Key == key {
+			return a.Val
+		}
+	}
+	return
+}
+
+func hasAttr(n html.Node, key string) bool {
+	for _, a := range n.Attr {
+		if a.Key == key {
+			return true
+		}
+	}
+	return false
+}
+
+func setAttr(n *html.Node, key, val string) {
+	newAttr := html.Attribute{
+		Key: key,
+		Val: val,
+	}
+	for i, a := range n.Attr {
+		if a.Key == key {
+			n.Attr[i] = newAttr
+			return
+		}
+	}
+	n.Attr = append(n.Attr, newAttr)
+}
+
+func formData(n html.Node) (data url.Values) {
+	data = make(url.Values)
+	if n.Data == "input" {
+		if k := attr(n, "name"); k != "" {
+			data.Set(k, attr(n, "value"))
+		}
+	}
+	for c := n.FirstChild; c != nil; c = c.NextSibling {
+		for k, vs := range formData(*c) {
+			data.Set(k, vs[0]) // TODO: what aboot the rest?
+		}
+	}
+	return
+}
+
+func (b *Browser) submit(form *html.Node) {
+	var err error
+	method := "GET" // TODO
+	if m := attr(*form, "method"); m != "" {
+		method = strings.ToUpper(m)
+	}
+	uri := b.URL
+	log.Printf("form = %+v", form)
+	if action := attr(*form, "action"); action != "" {
+		uri, err = b.LinkedUrl(action)
+		if err != nil {
+			log.Printf("error parsing %v", action)
+			return
+		}
+	}
+	var buf []byte
+	var contentType opossum.ContentType
+	if method == "GET" {
+		q := uri.Query()
+		for k, vs := range formData(*form) {
+			log.Printf("add query info %v => %v", k, vs[0])
+			q.Set(k, vs[0]) // TODO: what is with the rest?
+		}
+		uri.RawQuery = q.Encode()
+		log.Printf("uri raw query=%v", uri.RawQuery)
+		buf, contentType, err = b.get(*uri, true)
+		log.Printf("uri=%v", uri.String())
+	} else {
+		buf, contentType, err = b.PostForm(*uri, formData(*form))
+	}
+	if err == nil {
+		if contentType.IsHTML() {
+			b.render(buf)
+		} else {
+			log.Fatalf("post: unexpected %v", contentType)
+		}
+	} else {
+		log.Printf("post form: %v", err)
+	}
+}
+
+func Arrange(cs style.Map, elements ...*Element) *Element {
+	if cs.IsFlex() {
+		if cs.IsFlexDirectionRow() {
+			return NewElement(horizontalSequenceOf(true, elements), cs)
+		} else {
+			return NewElement(verticalSequenceOf(elements), cs)
+		}
+	}
+
+	rows := make([][]*Element, 0, 10)
+	currentRow := make([]*Element, 0, 10)
+	flushCurrentRow := func() {
+		if len(currentRow) > 0 {
+			rows = append(rows, currentRow)
+			currentRow = make([]*Element, 0, 10)
+		}
+	}
+
+	for _, e := range elements {
+		if !e.CS.IsInline() {
+			flushCurrentRow()
+		}
+		currentRow = append(currentRow, e)
+		if !e.CS.IsInline() {
+			flushCurrentRow()
+		}
+	}
+	flushCurrentRow()
+
+	if len(rows) == 0 {
+		return nil
+	} else if len(rows) == 1 {
+		if len(rows[0]) == 0 {
+			return nil
+		} else if len(rows[0]) == 1 {
+			return rows[0][0]
+		}
+		numElements++
+		return NewElement(horizontalSequenceOf(true, rows[0]), cs)
+	} else {
+		seqs := make([]*Element, 0, len(rows))
+		for _, row := range rows {
+			seq := horizontalSequenceOf(true, row)
+			numElements++
+			seqs = append(seqs, NewElement(seq, cs))
+		}
+		numElements++
+		return NewElement(verticalSequenceOf(seqs), cs)
+	}
+}
+
+func horizontalSequenceOf(wrap bool, es []*Element) duit.UI {
+	if len(es) == 0 {
+		return nil
+	} else if len(es) == 1 {
+		return es[0]
+	}
+
+	halign := make([]duit.Halign, 0, len(es))
+	valign := make([]duit.Valign, 0, len(es))
+
+	for i := 0; i < len(es); i++ {
+		halign = append(halign, duit.HalignLeft)
+		valign = append(valign, duit.ValignTop)
+	}
+
+	uis := make([]duit.UI, 0, len(es))
+	for _, e := range es {
+		uis = append(uis, e)
+	}
+
+	if wrap {
+		log.Printf("wrap")
+		finalUis := make([]duit.UI, 0, len(uis))
+		for _, ui := range uis {
+			log.Printf("wrap, tree:")
+			PrintTree(ui)
+			el, ok := ui.(*Element)
+			if ok {
+				label, isLabel := el.UI.(*duit.Label)
+				if isLabel {
+					tts := strings.Split(label.Text, " ")
+					for _, t := range tts {
+						finalUis = append(finalUis, NewElement(&duit.Label{
+							Text: t,
+							Font: label.Font,
+						}, el.CS))
+					}
+				} else {
+					finalUis = append(finalUis, ui)
+				}
+			} else {
+				finalUis = append(finalUis, ui)
+			}
+		}
+		return &duit.Box{
+			Padding: duit.SpaceXY(6, 4),
+			Margin:  image.Pt(6, 4),
+			Kids:    duit.NewKids(finalUis...),
+		}
+	} else {
+		return &duit.Grid{
+			Columns: len(es),
+			Padding: duit.NSpace(len(es), duit.SpaceXY(0, 3)),
+			Halign:  halign,
+			Valign:  valign,
+			Kids:    duit.NewKids(uis...),
+		}
+	}
+}
+
+func verticalSequenceOf(es []*Element) duit.UI {
+	if len(es) == 0 {
+		return nil
+	} else if len(es) == 1 {
+		return es[0]
+	}
+
+	uis := make([]duit.UI, 0, len(es))
+	for _, e := range es {
+		uis = append(uis, e)
+	}
+
+	return &duit.Grid{
+		Columns: 1,
+		Padding: duit.NSpace(1, duit.SpaceXY(0, 3)),
+		Halign:  []duit.Halign{duit.HalignLeft},
+		Valign:  []duit.Valign{duit.ValignTop},
+		Kids:    duit.NewKids(uis...),
+	}
+}
+
+func check(err error, msg string) {
+	if err != nil {
+		log.Fatalf("%s: %s\n", msg, err)
+	}
+}
+
+func RichInnerContentFrom(r int, b *Browser, display *draw.Display, n *nodes.Node) *Element {
+	childrenAsEls := make([]*Element, 0, 1)
+
+	for _, c := range n.Children {
+		tmp := NodeToBox(r+1, b, display, c)
+		if tmp != nil {
+			numElements++
+			el := NewElement(tmp, c.Map.ApplyChildStyle(style.TextNode))
+			childrenAsEls = append(childrenAsEls, el)
+		}
+	}
+	if len(childrenAsEls) == 0 {
+		return nil
+	} else if len(childrenAsEls) == 1 {
+		return childrenAsEls[0]
+	}
+	res := Arrange(n.Map, childrenAsEls...)
+	return res
+}
+
+type Table struct {
+	rows []*TableRow
+}
+
+func NewTable(n *nodes.Node) (t *Table) {
+	t = &Table{
+		rows: make([]*TableRow, 0, 10),
+	}
+
+	if n.Text != "" || n.DomSubtree.Data != "table" {
+		log.Printf("invalid table root")
+		return nil
+	}
+
+	trContainers := make([]*nodes.Node, 0, 2)
+	for _, c := range n.Children {
+		if c.DomSubtree.Data == "tbody" || c.DomSubtree.Data == "thead" {
+			trContainers = append(trContainers, c)
+		}
+	}
+	if len(trContainers) == 0 {
+		trContainers = []*nodes.Node{n}
+	}
+
+	for _, tc := range trContainers {
+		for _, c := range tc.Children {
+			if txt := c.Text; txt != "" && strings.TrimSpace(txt) == "" {
+				continue
+			}
+			if c.DomSubtree.Data == "tr" {
+				row := NewTableRow(c)
+				t.rows = append(t.rows, row)
+			} else {
+				log.Printf("unexpected row element '%v' (%v)", c.DomSubtree.Data, c.DomSubtree.Type)
+			}
+		}
+	}
+	return
+}
+
+func (t *Table) numColsMin() (min int) {
+	min = t.numColsMax()
+	for _, r := range t.rows {
+		if l := len(r.columns); l < min {
+			min = l
+		}
+	}
+	return
+}
+
+func (t *Table) numColsMax() (max int) {
+	for _, r := range t.rows {
+		if l := len(r.columns); l > max {
+			max = l
+		}
+	}
+	return
+}
+
+func (t *Table) Element(r int, b *Browser, display *draw.Display, cs style.Map) *Element {
+	numRows := len(t.rows)
+	numCols := t.numColsMax()
+	useOneGrid := t.numColsMin() == t.numColsMax()
+
+	if useOneGrid {
+		uis := make([]duit.UI, 0, numRows*numCols)
+		for _, row := range t.rows {
+			for _, td := range row.columns {
+				uis = append(uis, NodeToBox(r+1, b, display, td))
+			}
+		}
+
+		log.Printf("use on grid")
+		halign := make([]duit.Halign, 0, len(uis))
+		valign := make([]duit.Valign, 0, len(uis))
+
+		for i := 0; i < numCols; i++ {
+			halign = append(halign, duit.HalignLeft)
+			valign = append(valign, duit.ValignTop)
+		}
+
+		return NewElement(
+			&duit.Grid{
+				Columns: numCols,
+				Padding: duit.NSpace(numCols, duit.SpaceXY(0, 3)),
+				Halign:  halign,
+				Valign:  valign,
+				Kids:    duit.NewKids(uis...),
+			},
+			cs,
+		)
+	} else {
+		log.Printf("combine")
+
+		seqs := make([]*Element, 0, len(t.rows))
+		for _, row := range t.rows {
+			rowEls := make([]*Element, 0, len(row.columns))
+			for _, col := range row.columns {
+				ui := NodeToBox(r+1, b, display, col)
+				if ui != nil {
+					el := NewElement(ui, col.Map)
+					rowEls = append(rowEls, el)
+				}
+			}
+
+			log.Printf("len rowsEls=%v", len(rowEls))
+			if len(rowEls) > 0 {
+				seq := horizontalSequenceOf(false, rowEls)
+				numElements++
+				seqs = append(seqs, NewElement(seq, cs))
+			}
+		}
+		numElements++
+		return NewElement(verticalSequenceOf(seqs), cs)
+	}
+}
+
+type TableRow struct {
+	columns []*nodes.Node
+}
+
+func NewTableRow(n *nodes.Node) (tr *TableRow) {
+	tr = &TableRow{
+		columns: make([]*nodes.Node, 0, 5),
+	}
+
+	if n.Type() != html.ElementNode || n.Data() != "tr" {
+		log.Printf("invalid tr root")
+		return nil
+	}
+
+	for _, c := range n.Children {
+		if c.Type() == html.TextNode && strings.TrimSpace(c.Data()) == "" {
+			continue
+		}
+		if c.DomSubtree.Data == "td" || c.DomSubtree.Data == "th" {
+			tr.columns = append(tr.columns, c)
+		} else {
+			log.Printf("unexpected row element '%v' (%v)", c.Data(), c.Type())
+		}
+	}
+
+	return tr
+}
+
+func grepBody(n *html.Node) *html.Node {
+	var body *html.Node
+
+	if n.Type == html.ElementNode {
+		if n.Data == "body" {
+			return n
+		}
+	}
+
+	for c := n.FirstChild; c != nil; c = c.NextSibling {
+		res := grepBody(c)
+		if res != nil {
+			body = res
+		}
+	}
+
+	return body
+}
+
+func NodeToBox(r int, b *Browser, display *draw.Display, n *nodes.Node) *Element {
+	if attr(*n.DomSubtree, "aria-hidden") == "true" || hasAttr(*n.DomSubtree, "hidden") {
+		return nil
+	}
+	if n.IsDisplayNone() {
+		return nil
+	}
+
+	if n.Type() == html.ElementNode {
+		switch n.Data() {
+		case "style", "script", "svg", "template":
+			return nil
+		case "noscript":
+			if *ExperimentalJsInsecure {
+				return nil
+			}
+			fallthrough
+		case "input":
+			numElements++
+			t := attr(*n.DomSubtree, "type")
+			if isPw := t == "password"; t == "text" || t == "" || t == "search" || isPw {
+				return NewInputField(n)
+			} else if t == "submit" {
+				return NewSubmitButton(b, n)
+			} else {
+				return nil
+			}
+		case "button":
+			numElements++
+			if t := attr(*n.DomSubtree, "type"); t == "" || t == "submit" {
+				return NewSubmitButton(b, n)
+			} else {
+				btn := &duit.Button{
+					Text: nodes.ContentFrom(*n),
+					Font: n.Font(),
+				}
+				return NewElement(
+					btn,
+					n.Map,
+				)
+			}
+		case "table":
+			numElements++
+			return NewTable(n).Element(r+1, b, display, n.Map)
+		case "body", "p", "h1", "center", "nav", "article", "header", "div", "td":
+			var innerContent duit.UI
+			if nodes.IsPureTextContent(*n) {
+				t := strings.TrimSpace(nodes.ContentFrom(*n))
+				innerContent = &ColoredLabel{
+					Label: &duit.Label{
+						Text: t,
+						Font: n.Font(),
+					},
+					Map: n.Map.ApplyChildStyle(style.TextNode),
+				}
+			} else {
+				innerContent = RichInnerContentFrom(r+1, b, display, n)
+			}
+
+			numElements++
+			return NewBoxElement(
+				innerContent,
+				n.Map.ApplyChildStyle(style.TextNode),
+			)
+		case "img":
+			numElements++
+			return NewElement(
+				NewImage(display, *n),
+				n.Map,
+			)
+		case "pre":
+			numElements++
+			return NewElement(
+				NewCodeView(nodes.ContentFrom(*n), n.Map),
+				n.Map,
+			)
+		case "li":
+			var innerContent duit.UI
+			if nodes.IsPureTextContent(*n) {
+				t := nodes.ContentFrom(*n)
+				if s, ok := n.Map.Declarations["list-style"]; !ok || s.Value != "none" {
+					t = "• " + t
+				}
+				innerContent = &ColoredLabel{
+					Label: &duit.Label{
+						Text: t,
+						Font: n.Font(),
+					},
+					Map: n.Map,
+				}
+			} else {
+				innerContent = RichInnerContentFrom(r+1, b, display, n)
+			}
+
+			numElements++
+			return NewElement(
+				innerContent,
+				n.Map,
+			)
+		case "a":
+			var href string
+			for _, a := range n.Attr {
+				if a.Key == "href" {
+					href = a.Val
+				}
+			}
+			var innerContent duit.UI
+			if nodes.IsPureTextContent(*n) {
+				innerContent = &ColoredLabel{
+					Label: &duit.Label{
+						Text:  nodes.ContentFrom(*n),
+						Font:  n.Font(),
+						Click: browser.SetAndLoadUrl(href),
+					},
+					Map: n.Map, //.ApplyIsLink(),
+				}
+			} else {
+				// TODO: make blue borders and different
+				//       mouse cursor and actually clickable
+				innerContent = RichInnerContentFrom(r+1, b, display, n)
+			}
+			numElements++
+			if innerContent == nil {
+				return nil
+			}
+			el := NewElement(
+				innerContent,
+				n.Map,
+			)
+			el.IsLink = true // TODO make this cascading
+			//      also a way to bubble up
+			// will be needed eventually
+			el.onClickLeafes(browser.SetAndLoadUrl(href))
+			return el
+		default:
+			// Internal node object
+			els := make([]*Element, 0, 10)
+			for _, c := range n.Children {
+				el := NodeToBox(r+1, b, display, c)
+				if el != nil && !c.IsDisplayNone() {
+					els = append(els, el)
+				}
+			}
+
+			if len(els) == 0 {
+				return nil
+			} else if len(els) == 1 {
+				return els[0]
+			} else {
+				for _, e := range els {
+					_ = e
+				}
+				return Arrange(n.Map, els...)
+			}
+		}
+	} else if n.Type() == html.TextNode {
+		// Leaf text object
+
+		if text := strings.TrimSpace(nodes.ContentFrom(*n)); text != "" {
+			text = strings.ReplaceAll(text, "\n", "")
+			text = strings.ReplaceAll(text, "\t", "")
+			l := strings.Split(text, " ")
+			nn := make([]string, 0, len(l))
+			for _, w := range l {
+				if w != "" {
+					nn = append(nn, w)
+				}
+			}
+			text = strings.Join(nn, " ")
+			ui := &duit.Label{
+				Text: text,
+				Font: n.Font(),
+			}
+			numElements++
+			return NewElement(
+				ui,
+				style.TextNode,
+			)
+		} else {
+			return nil
+		}
+	} else {
+		return nil
+	}
+}
+
+type Website struct {
+	duit.UI
+}
+
+func TraverseTree(ui duit.UI, f func(ui duit.UI)) {
+	traverseTree(0, ui, f)
+}
+
+func traverseTree(r int, ui duit.UI, f func(ui duit.UI)) {
+	if ui == nil {
+		panic("null")
+		return
+	}
+	f(ui)
+	switch v := ui.(type) {
+	case nil:
+		panic("null")
+	case *duit.Scroll:
+		traverseTree(r+1, v.Kid.UI, f)
+	case *duit.Box:
+		for _, kid := range v.Kids {
+			traverseTree(r+1, kid.UI, f)
+		}
+	case *Element:
+		if v == nil {
+			// TODO: repair?!
+			//panic("null element")
+			return
+		}
+		traverseTree(r+1, v.UI, f)
+	case *duit.Grid:
+		for _, kid := range v.Kids {
+			traverseTree(r+1, kid.UI, f)
+		}
+	case *duit.Image:
+	case *duit.Label:
+	case *ColoredLabel:
+		traverseTree(r+1, v.Label, f)
+	case *duit.Button:
+	case *Image:
+		traverseTree(r+1, v.Image, f)
+	case *duit.Field:
+	case *CodeView:
+	default:
+		panic(fmt.Sprintf("unknown: %+v", v))
+	}
+}
+
+func PrintTree(ui duit.UI) {
+	if log.Debug {
+		printTree(0, ui)
+	}
+}
+
+func printTree(r int, ui duit.UI) {
+	for i := 0; i < r; i++ {
+		fmt.Printf("  ")
+	}
+	if ui == nil {
+		fmt.Printf("ui=nil\n")
+		return
+	}
+	switch v := ui.(type) {
+	case nil:
+		fmt.Printf("v=nil\n")
+		return
+	case *duit.Scroll:
+		fmt.Printf("duit.Scroll\n")
+		printTree(r+1, v.Kid.UI)
+	case *duit.Box:
+		fmt.Printf("duit.Box\n")
+		for _, kid := range v.Kids {
+			printTree(r+1, kid.UI)
+		}
+	case *Element:
+		if v == nil {
+			fmt.Printf("v:*Element=nil\n")
+			return
+		}
+		fmt.Printf("Element\n")
+		printTree(r+1, v.UI)
+	case *duit.Grid:
+		fmt.Printf("duit.Grid %vx%v\n", len(v.Kids)/v.Columns, v.Columns)
+		for _, kid := range v.Kids {
+			printTree(r+1, kid.UI)
+		}
+	case *duit.Image:
+		fmt.Printf("Image %v\n", v)
+	case *duit.Label:
+		t := v.Text
+		if len(t) > 20 {
+			t = t[:15] + "..."
+		}
+		fmt.Printf("Label %v\n", t)
+	case *ColoredLabel:
+		t := v.Text
+		if len(t) > 20 {
+			t = t[:15] + "..."
+		}
+		fmt.Printf("ColoredLabel %v\n", t)
+	default:
+		fmt.Printf("default :-) %+v\n", v)
+	}
+}
+
+type Browser struct {
+	dui       *duit.DUI
+	html      string
+	URL       *url.URL
+	Website   *Website
+	StatusBar *duit.Label
+	LocationField *duit.Field
+	client    *http.Client
+}
+
+func NewBrowser(_dui *duit.DUI, initUrl string) (b *Browser) {
+	jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	b = &Browser{
+		client: &http.Client{
+			Jar: jar,
+		},
+		dui: _dui,
+		StatusBar: &duit.Label{
+			Text: "",
+		},
+		Website: &Website{},
+	}
+
+	b.LocationField = &duit.Field{
+		Text:    initUrl,
+		Font:    Style.Font(),
+		Changed: b.SetUrl,
+	}
+
+	u, err := url.Parse(initUrl)
+	if err != nil {
+		log.Fatalf("parse: %v", err)
+	}
+	b.URL = u
+
+	buf, _, err := b.Get(*u)
+	if err != nil {
+		log.Fatalf("get: %v", err)
+	}
+	b.html = string(buf)
+
+	browser = b
+	style.SetFetcher(b)
+	dui = _dui
+
+	b.layoutWebsite()
+	b.SetUrl(initUrl)
+
+	return
+}
+
+func (b *Browser) LinkedUrl(addr string) (a *url.URL, err error) {
+	log.Printf("LinkedUrl: addr=%v, b.URL=%v", addr, b.URL)
+	if strings.HasPrefix(addr, "//") {
+		addr = b.URL.Scheme + ":" + addr
+	} else if strings.HasPrefix(addr, "/") {
+		addr = b.URL.Scheme + "://" + b.URL.Host + addr
+	} else if !strings.HasPrefix(addr, "http") {
+		if strings.HasSuffix(b.URL.Path, "/") {
+			log.Printf("A")
+			addr = "/" + b.URL.Path + "/" + addr
+		} else {
+			log.Printf("B")
+			m := strings.LastIndex(b.URL.Path, "/")
+			if m > 0 {
+				log.Printf("B.>")
+				folder := b.URL.Path[0:m]
+				addr = "/" + folder + "/" + addr
+			} else {
+				log.Printf("B.<=")
+				addr = "/" + addr
+			}
+		}
+		addr = strings.ReplaceAll(addr, "//", "/")
+		addr = b.URL.Scheme + "://" + b.URL.Host + addr
+	}
+	return url.Parse(addr)
+}
+
+func (b *Browser) SetAndLoadUrl(addr string) func() duit.Event {
+	a := addr
+	return func() duit.Event {
+		log.Printf("SetAndLoadUrl::callback: addr=%v", addr)
+		log.Printf("       b.URL=%v", b.URL)
+		url, err := b.LinkedUrl(addr)
+		if err == nil {
+			b.SetUrl(url.String())
+			b.LoadUrl()
+		} else {
+			log.Printf("parse url %v: %v", a, err)
+		}
+
+		return duit.Event{
+			Consumed: true,
+		}
+	}
+}
+
+func (b *Browser) SetUrl(addr string) (e duit.Event) {
+	log.Printf("SetUrl(%v)", addr)
+	var err error
+	if !strings.HasPrefix(addr, "http") {
+		addr = "https://" + addr
+	}
+	b.URL, err = url.Parse(addr)
+	if err != nil {
+		log.Printf("could not parse url: %v", err)
+	}
+	log.Printf("   b.Url=%v", b.URL)
+	return duit.Event{
+		Consumed: true,
+	}
+}
+
+func (b *Browser) showBodyMessage(msg string) {
+	b.Website.UI = &duit.Label{
+		Text: msg,
+		Font: style.Map{}.Font(),
+	}
+	dui.MarkLayout(dui.Top.UI)
+	dui.MarkDraw(dui.Top.UI)
+	dui.Render()
+}
+
+func (b *Browser) LoadUrl() (e duit.Event) {
+	if b.URL == nil {
+		e.Consumed = true
+		return
+	}
+	addr := b.URL.String()
+	log.Printf("Getting %v...", addr)
+	buf, contentType, err := b.get(*b.URL, true)
+	if err != nil {
+		log.Errorf("error loading %v: %v", addr, err)
+		err = errors.Unwrap(err)
+		if strings.Contains(err.Error(), "HTTP response to HTTPS client") {
+			b.SetUrl(strings.Replace(b.URL.String(), "https://", "http://", 1))
+			return b.LoadUrl()
+		}
+		b.showBodyMessage(err.Error())
+		return
+	}
+	if contentType.IsHTML() || contentType.IsPlain() {
+		b.render(buf)
+	} else {
+		log.Errorf("unhandled content type: %v", contentType)
+	}
+	return duit.Event{
+		Consumed:   true,
+		NeedLayout: true,
+		NeedDraw:   true,
+	}
+}
+
+func (b *Browser) render(buf []byte) {
+	b.html = string(buf) // TODO: correctly interpret UTF8
+	b.layoutWebsite()
+
+	dui.MarkLayout(dui.Top.UI)
+	dui.MarkDraw(dui.Top.UI)
+	TraverseTree(b.Website.UI, func(ui duit.UI) {
+		// just checking
+		if ui == nil {
+			panic("nil")
+		}
+	})
+	PrintTree(b.Website.UI)
+	//log.Printf("Empty image cache...")
+	//cache = make(map[string][]byte)
+	log.Printf("Render...")
+	dui.Render()
+	log.Printf("Rendering done")
+}
+
+func (b *Browser) Get(uri url.URL) (buf []byte, contentType opossum.ContentType, err error) {
+	c, ok := cache[uri.String()]
+	if ok {
+		log.Printf("use %v from cache", uri)
+	} else {
+		c.buf, c.ContentType, err = b.get(uri, false)
+		if err == nil {
+			cache[uri.String()] = c
+		}
+	}
+	return c.buf, c.ContentType, err
+}
+
+func (b *Browser) statusBarMsg(msg string, emptyBody bool) {
+	if dui == nil || dui.Top.UI == nil {
+		return
+	}
+	b.StatusBar.Text = msg
+	if emptyBody {
+		b.Website.UI = &duit.Label{}
+	}
+	dui.MarkLayout(dui.Top.UI)
+	dui.MarkDraw(dui.Top.UI)
+	dui.Render()
+}
+
+func (b *Browser) get(uri url.URL, isNewOrigin bool) (buf []byte, contentType opossum.ContentType, err error) {
+	msg := fmt.Sprintf("Get %v", uri.String())
+	log.Printf(msg)
+	b.statusBarMsg(msg, true)
+	defer func() {
+		b.statusBarMsg("", true)
+	}()
+	req, err := http.NewRequest("GET", uri.String(), nil)
+	if err != nil {
+		return
+	}
+	req.Header.Add("User-Agent", "opossum")
+	resp, err := b.client.Do(req)
+	if err != nil {
+		return nil, opossum.ContentType{}, fmt.Errorf("error loading %v: %w", uri, err)
+	}
+	defer resp.Body.Close()
+	if isNewOrigin {
+		b.URL = resp.Request.URL
+		b.LocationField.Text = b.URL.String()
+	}
+	buf, err = ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return nil, opossum.ContentType{}, fmt.Errorf("error reading")
+	}
+	contentType, err = opossum.NewContentType(resp.Header.Get("Content-Type"))
+	log.Printf("%v\n", resp.Header)
+	if err == nil && (contentType.IsHTML() || contentType.IsCSS() || contentType.IsPlain()) {
+		buf = contentType.Utf8(buf)
+	}
+	return
+}
+
+func (b *Browser) PostForm(uri url.URL, data url.Values) (buf []byte, contentType opossum.ContentType, err error) {
+	b.Website.UI = &duit.Label{Text: "Posting..."}
+	dui.MarkLayout(dui.Top.UI)
+	dui.MarkDraw(dui.Top.UI)
+	dui.Render()
+	req, err := http.NewRequest("POST", uri.String(), strings.NewReader(data.Encode()))
+	if err != nil {
+		return
+	}
+	req.Header.Add("User-Agent", "opossum")
+	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+	resp, err := b.client.Do(req)
+	if err != nil {
+		return nil, opossum.ContentType{}, fmt.Errorf("error loading %v: %w", uri, err)
+	}
+	defer resp.Body.Close()
+	b.URL = resp.Request.URL
+	buf, err = ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return nil, opossum.ContentType{}, fmt.Errorf("error reading")
+	}
+	contentType, err = opossum.NewContentType(resp.Header.Get("Content-Type"))
+	return
+}
+
+func (b *Browser) layoutWebsite() {
+	pass := func(htm string, csss ...string) (*html.Node, map[*html.Node]style.Map) {
+
+		if debugPrintHtml {
+			log.Printf("%v\n", htm)
+		}
+
+		var doc *html.Node
+		var err error
+		doc, err = html.ParseWithOptions(
+			strings.NewReader(htm),
+			html.ParseOptionEnableScripting(*ExperimentalJsInsecure),
+		)
+		if err != nil {
+			panic(err.Error())
+		}
+
+		log.Printf("Retrieving CSS Rules...")
+		var cssSize int
+		nodeMap := make(map[*html.Node]style.Map)
+		for i, css := range csss {
+
+			log.Printf("CSS size %v kB", cssSize/1024)
+
+			nm, err := style.FetchNodeMap(doc, css, 1280)
+			if err == nil {
+				log.Printf("[%v/%v] Fetch CSS Rules successful!", i+1, len(csss))
+				if debugPrintHtml {
+					log.Printf("%v", nm)
+				}
+				style.MergeNodeMaps(nodeMap, nm)
+			} else {
+				log.Errorf("Fetch CSS Rules failed: %v", err)
+				if *DebugDumpCSS {
+					ioutil.WriteFile("info.css", []byte(css), 0644)
+				}
+			}
+		}
+
+		return doc, nodeMap
+	}
+
+	log.Printf("1st pass")
+	doc, _ := pass(b.html)
+
+	log.Printf("2nd pass")
+	log.Printf("Download style...")
+	cssHrefs := style.Hrefs(doc)
+	csss := make([]string, 0, len(cssHrefs))
+	for _, href := range cssHrefs {
+		url, err := b.LinkedUrl(href)
+		if err != nil {
+			log.Printf("error parsing %v", href)
+			continue
+		}
+		log.Printf("Download %v", url)
+		buf, contentType, err := b.Get(*url)
+		if err != nil {
+			log.Printf("error downloading %v", url)
+			continue
+		}
+		if contentType.IsCSS() {
+			csss = append(csss, string(buf))
+		} else {
+			log.Printf("css: unexpected %v", contentType)
+		}
+	}
+	csss = append([]string{ /*string(revertCSS), */ style.AddOnCSS}, csss...)
+	doc, nodeMap := pass(b.html, csss...)
+
+	if *ExperimentalJsInsecure {
+		log.Printf("3rd pass")
+		if b.URL == nil {
+			b.SetUrl("opossum://go")
+		}
+		nt := nodes.NewNodeTree(doc, style.Map{}, nodeMap, nil)
+		jsSrcs := domino.Srcs(nt)
+		downloads := make(map[string]string)
+		for _, src := range jsSrcs {
+			url, err := b.LinkedUrl(src)
+			if err != nil {
+				log.Printf("error parsing %v", src)
+				continue
+			}
+			log.Printf("Download %v", url)
+			buf, _ /*contentType*/, err := b.Get(*url)
+			if err != nil {
+				log.Printf("error downloading %v", url)
+				continue
+			}
+			downloads[src] = string(buf)
+		}
+		codes := domino.Scripts(nt, downloads)
+		log.Infof("JS pipeline start")
+		jsProcessed, err := processJS2(b.html, nt, codes)
+		if err == nil {
+			if b.html != jsProcessed {
+				log.Infof("html changed")
+			}
+			b.html = jsProcessed
+			if debugPrintHtml {
+				log.Printf("%v\n", jsProcessed)
+			}
+			doc, nodeMap = pass(b.html, csss...)
+		} else {
+			log.Errorf("JS error: %v", err)
+		}
+		log.Infof("JS pipeline end")
+	}
+	var countHtmlNodes func(*html.Node) int
+	countHtmlNodes = func(n *html.Node) (num int) {
+		num++
+		for c := n.FirstChild; c != nil; c = c.NextSibling {
+			num += countHtmlNodes(c)
+		}
+		return
+	}
+	log.Printf("%v html nodes found...", countHtmlNodes(doc))
+
+	body := grepBody(doc)
+
+	log.Printf("Layout website...")
+	numElements = 0
+	scroller = duit.NewScroll(
+		NodeToBox(0, b, b.dui.Display, nodes.NewNodeTree(body, style.Map{}, nodeMap, nil)),
+	)
+	b.Website.UI = scroller
+	log.Printf("Layouting done (%v elements created)", numElements)
+	if numElements < 10 {
+		log.Errorf("Less than 10 elements layouted, seems css processing failed. Will layout without css")
+		scroller = duit.NewScroll(
+			NodeToBox(0, b, b.dui.Display, nodes.NewNodeTree(body, style.Map{}, make(map[*html.Node]style.Map), nil)),
+		)
+		b.Website.UI = scroller
+	}
+	log.Flush()
+}
--- /dev/null
+++ b/browser/browser_test.go
@@ -1,0 +1,62 @@
+package browser
+
+import (
+	"net/url"
+	"opossum/logger"
+	"testing"
+)
+
+func init() {
+	SetLogger(&logger.Logger{})
+}
+
+type item struct {
+	orig   string
+	href   string
+	expect string
+}
+
+func TestParseDataUri(t *testing.T) {
+	srcs := []string{"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP//yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
+		"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=",
+	}
+
+	for _, src := range srcs {
+		data, err := parseDataUri(src)
+		if err != nil {
+			t.Fatalf(err.Error())
+		}
+		t.Logf("%v", data)
+	}
+}
+
+func TestLinkedUrl(t *testing.T) {
+	items := []item{
+		item{
+			orig:   "https://news.ycombinator.com/item?id=24777268",
+			href:   "news",
+			expect: "https://news.ycombinator.com/news",
+		},
+	}
+
+	for _, i := range items {
+		b := Browser{}
+		origin, err := url.Parse(i.orig)
+		if err != nil {
+			panic(err.Error())
+		}
+		b.URL = origin
+		res, err := b.LinkedUrl(i.href)
+		if err != nil {
+			panic(err.Error())
+		}
+		if res.String() != i.expect {
+			t.Fatalf("got %v but expected %v", res, i.expect)
+		}
+		t.Logf("res=%v, i.expect=%v", res, i.expect)
+	}
+}
+
+func TestNilPanic(t *testing.T) {
+	//f, err := os.Open()
+}
--- /dev/null
+++ b/browser/experimental.go
@@ -1,0 +1,194 @@
+package browser
+
+import (
+	//"bytes"
+	"fmt"
+	"image"
+	//"io/ioutil"
+	//"net/http"
+	"strings"
+	"opossum/domino"
+	"opossum/nodes"
+	"time"
+
+	"9fans.net/go/draw"
+	"github.com/mjl-/duit"
+	//"opossum/nodes"
+)
+
+func (el *Element) slicedDraw(dui *duit.DUI, self *duit.Kid, img *draw.Image, orig image.Point, m draw.Mouse, force bool) bool {
+	//fmt.Printf("m.Point.y=%v\n", m.Point.Y)
+	if experimentalUseSlicedDrawing {
+		//offset := scroller.GetOffset()
+		offset := -1
+		panic("not implemented")
+		fmt.Printf("orig=%v    m.Point.y=%v   offset=%v\n", orig.Y,m.Point.Y,offset)
+		if (m.Point.Y-offset < -10 || m.Point.Y-offset > 1200) && isLeaf(el.UI) {
+			return true
+		}
+	}
+	return false
+}
+
+type AtomBox struct {
+	Left, Right, Bottom, Top int
+}
+
+// Atom is div/span with contentEditable=true/false, i.e. it should be able
+// to render practically anything
+type Atom struct {
+	// BackgroundImgSrc to read image from provided cache
+	// it's okay when the pointer is empty -> defered loading
+	BackgroundImgSrc string
+	BackgroundColor draw.Color
+	BorderWidths AtomBox
+	Color draw.Color
+	Margin AtomBox
+	Padding AtomBox
+	Wrap bool
+
+	// Children []*Atom TODO: future; at the same time rething where
+        //                                      to put Draw functions etc./if to rely on
+        //                                      type Kid
+	Text  string           // Text to draw, wrapped at glyph boundary.
+	Font  *draw.Font       `json:"-"` // For drawing text.
+	Click func()
+
+	lines []string
+	size  image.Point
+	m     draw.Mouse
+}
+
+func (ui *Atom) font(dui *duit.DUI) *draw.Font {
+	return dui.Font(ui.Font)
+}
+
+func (ui *Atom) Draw(dui *duit.DUI, self *duit.Kid, img *draw.Image, orig image.Point, m draw.Mouse, force bool) {
+	// dui.debugDraw(self)
+
+	p := orig
+	font := ui.font(dui)
+	for _, line := range ui.lines {
+		img.String(p, dui.Regular.Normal.Text, image.ZP, font, line)
+		p.Y += font.Height
+	}
+}
+
+func isLeaf(ui duit.UI) bool {
+	if ui == nil {
+		return true
+	}
+	switch /*v := */ui.(type) {
+	case nil:
+		return true
+	case *duit.Scroll:
+		return false
+	case *duit.Box:
+		return false
+	case *Element:
+		return false
+	case *duit.Grid:
+		return false
+	case *duit.Image:
+		return true
+	case *duit.Label:
+		return true
+	case *ColoredLabel:
+		return false
+	case *duit.Button:
+		return true
+	case *Image:
+		return false
+	case *duit.Field:
+		return true
+	case *CodeView:
+		return false
+	default:
+		return false
+	}
+}
+
+func CleanTree(ui duit.UI) {
+	if ui == nil {
+		panic("nil root")
+	}
+	TraverseTree(ui, func(ui duit.UI) {
+		if ui == nil {
+			panic("null")
+		}
+		switch v := ui.(type) {
+		case nil:
+			panic("null")
+		case *duit.Scroll:
+			panic("like nil root")
+		case *duit.Box:
+			//realKids := make([])
+		case *Element:
+			if v == nil {
+				panic("null element")
+			}
+		case *duit.Grid:
+		case *duit.Image:
+		case *duit.Label:
+		case *ColoredLabel:
+		case *duit.Button:
+		case *Image:
+		case *duit.Field:
+		case *CodeView:
+		default:
+			panic(fmt.Sprintf("unknown: %+v", v))
+		}
+	})
+}
+
+
+func processJS(htm string) (resHtm string, err error) {
+	_ = strings.Replace(htm, "window.", "", -1)
+	d := domino.NewDomino(htm)
+	d.Start()
+	if err = d.ExecInlinedScripts(); err != nil {
+		return "", fmt.Errorf("exec <script>s: %w", err)
+	}
+	time.Sleep(time.Second)
+	resHtm, changed, err := d.TrackChanges()
+	log.Infof("processJS: changes = %v", changed)
+	d.Stop()
+	return
+}
+
+func processJS2(htm string, doc *nodes.Node, scripts []string) (resHtm string, err error) {
+	//_ = strings.Replace(htm, "window.", "", -1)
+	d := domino.NewDomino(htm)
+	d.Start()
+	code := ""
+	for _, script := range scripts {
+		code += `
+			try {
+		` + script + `;
+		` + fmt.Sprintf(`
+			console.log('==============');
+			console.log('Success!');
+			console.log('==============');
+		`) + `
+			} catch(e) {
+				console.log('==============');
+				console.log('Catch:');
+				console.log(e);
+				console.log('==============');
+			}
+		`
+	}
+	log.Printf("code=%v\n", code)
+	if err = d.Exec/*6*/(code); err != nil {
+		return "", fmt.Errorf("exec <script>s: %w", err)
+	}
+	time.Sleep(time.Second)
+	resHtm, changed, err := d.TrackChanges()
+	if err != nil {
+		return "", fmt.Errorf("track changes: %w", err)
+	}
+	log.Printf("processJS: changes = %v", changed)
+	log.Printf("exp. resHtm=%v\n", resHtm)
+	d.Stop()
+	return
+}
--- /dev/null
+++ b/browser/experimental_test.go
@@ -1,0 +1,11 @@
+package browser
+
+import (
+	//"github.com/mjl-/duit"
+	"testing"
+)
+
+func TestAtom(t *testing.T) {
+	//var ui duit.UI
+	//ui = &Atom{}
+}
--- /dev/null
+++ b/cmd/browse/main.go
@@ -1,0 +1,123 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+
+	"os"
+	"opossum"
+	"opossum/browser"
+	"opossum/domino"
+	"opossum/logger"
+	"opossum/style"
+	"opossum/nodes"
+	"runtime/pprof"
+	"time"
+
+	"github.com/mjl-/duit"
+)
+
+const debugPrintHtml = false
+
+var dui *duit.DUI
+var log *logger.Logger
+
+var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
+var cssFonts = flag.Bool("cssFonts", true, "toggle css fonts (default true)")
+var experimentalUseBoxBackgrounds = flag.Bool("experimentalUseBoxBackgrounds", true, "show box BGs (default true)")
+var startPage = flag.String("startPage", "http://9p.io", "")
+var dbg = flag.Bool("debug", false, "show debug logs")
+
+func init() {
+	browser.DebugDumpCSS = flag.Bool("debugDumpCSS", false, "write css to info.css")
+	domino.DebugDumpJS = flag.Bool("debugDumpJS", false, "write js to main.js")
+	browser.ExperimentalJsInsecure = flag.Bool("experimentalJsInsecure", false, "DO NOT ACTIVATE UNLESS INSTRUCTED OTHERWISE")
+	logger.Quiet = flag.Bool("quiet", defaultQuietActive, "don't print info messages and non-fatal errors")
+}
+
+func Main() (err error) {
+	dui, err = duit.NewDUI("opossum", nil) // TODO: rm global var
+	if err != nil {
+		return fmt.Errorf("new dui: %w", err)
+	}
+
+	style.Init(dui, log)
+
+	w := dui.Display.Windows.Bounds().Dx()
+	log.Printf("w=%v", w)
+	log.Printf("w'=%v", dui.Scale(w))
+	log.Printf("kid=%v", dui.Top.R)
+	browser.SetLogger(log)
+	opossum.SetLogger(log)
+	nodes.SetLogger(log)
+	b := browser.NewBrowser(dui, *startPage)
+
+	dui.Top.UI = &duit.Box{
+		Kids: duit.NewKids(
+			&duit.Grid{
+				Columns: 2,
+				Padding: duit.NSpace(2, duit.SpaceXY(5, 3)),
+				Halign:  []duit.Halign{duit.HalignLeft, duit.HalignRight},
+				Valign:  []duit.Valign{duit.ValignMiddle, duit.ValignMiddle},
+				Kids: duit.NewKids(
+					&duit.Button{
+						Text:  "Load",
+						Font:  browser.Style.Font(),
+						Click: b.LoadUrl,
+					},
+					&duit.Box{
+						Kids: duit.NewKids(
+							b.LocationField,
+						),
+					},
+				),
+			},
+			b.StatusBar,
+			b.Website,
+		),
+	}
+	browser.PrintTree(b.Website.UI)
+	log.Printf("Render.....")
+	dui.Render()
+	log.Printf("Rendering done")
+
+	for {
+		select {
+		case e := <-dui.Inputs:
+			dui.Input(e)
+			//log.Printf("e=%+v", e)
+
+		case err, ok := <-dui.Error:
+			if !ok {
+				return nil
+			}
+			log.Printf("main: duit: %s\n", err)
+		}
+	}
+}
+
+func main() {
+	flag.Parse()
+	logger.Init()
+
+	if *cpuprofile != "" {
+		f, err := os.Create(*cpuprofile)
+		if err != nil {
+			log.Fatal(err)
+		}
+		pprof.StartCPUProfile(f)
+		go func() {
+			<-time.After(time.Minute)
+			pprof.StopCPUProfile()
+			os.Exit(2)
+		}()
+	}
+	os.Chdir("../..")
+	log = logger.Log
+	log.Debug = *dbg
+	style.CssFonts = *cssFonts
+	style.ExperimentalUseBoxBackgrounds = *experimentalUseBoxBackgrounds
+	if err := Main(); err != nil {
+		log.Fatalf("Main: %v", err)
+	}
+}
--- /dev/null
+++ b/cmd/browse/main_plan9.go
@@ -1,0 +1,3 @@
+package main
+
+const defaultQuietActive = true
--- /dev/null
+++ b/cmd/browse/main_test.go
@@ -1,0 +1,1 @@
+package main
--- /dev/null
+++ b/cmd/browse/main_unix.go
@@ -1,0 +1,5 @@
+// +build !plan9
+
+package main
+
+const defaultQuietActive = false
--- /dev/null
+++ b/domino-lib/CSSStyleDeclaration.js
@@ -1,0 +1,579 @@
+"use strict";
+var parserlib = require('./cssparser');
+
+module.exports = CSSStyleDeclaration;
+
+function CSSStyleDeclaration(elt) {
+  this._element = elt;
+}
+
+// Utility function for parsing style declarations
+// Pass in a string like "margin-left: 5px; border-style: solid"
+// and this function returns an object like
+// {"margin-left":"5px", "border-style":"solid"}
+function parseStyles(s) {
+  var parser = new parserlib.css.Parser();
+  var result = { 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);
+      }
+    });
+  }
+}
--- /dev/null
+++ b/domino-lib/CharacterData.js
@@ -1,0 +1,120 @@
+/* jshint bitwise: false */
+"use strict";
+module.exports = CharacterData;
+
+var Leaf = require('./Leaf');
+var utils = require('./utils');
+var ChildNode = require('./ChildNode');
+var NonDocumentTypeChildNode = require('./NonDocumentTypeChildNode');
+
+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);
--- /dev/null
+++ b/domino-lib/ChildNode.js
@@ -1,0 +1,119 @@
+"use strict";
+
+var Node = require('./Node');
+var LinkedList = require('./LinkedList');
+
+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;
--- /dev/null
+++ b/domino-lib/Comment.js
@@ -1,0 +1,39 @@
+"use strict";
+module.exports = Comment;
+
+var Node = require('./Node');
+var CharacterData = require('./CharacterData');
+
+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);
+  }},
+});
--- /dev/null
+++ b/domino-lib/ContainerNode.js
@@ -1,0 +1,80 @@
+"use strict";
+module.exports = ContainerNode;
+
+var Node = require('./Node');
+var NodeList = require('./NodeList');
+
+// 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
+  }},
+
+});
--- /dev/null
+++ b/domino-lib/CustomEvent.js
@@ -1,0 +1,12 @@
+"use strict";
+module.exports = CustomEvent;
+
+var Event = require('./Event');
+
+function CustomEvent(type, dictionary) {
+  // Just use the superclass constructor to initialize
+  Event.call(this, type, dictionary);
+}
+CustomEvent.prototype = Object.create(Event.prototype, {
+  constructor: { value: CustomEvent }
+});
--- /dev/null
+++ b/domino-lib/DOMException.js
@@ -1,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);
+}
--- /dev/null
+++ b/domino-lib/DOMImplementation.js
@@ -1,0 +1,94 @@
+"use strict";
+module.exports = DOMImplementation;
+
+var Document = require('./Document');
+var DocumentType = require('./DocumentType');
+var HTMLParser = require('./HTMLParser');
+var utils = require('./utils');
+var xml = require('./xmlnames');
+
+// Each document must have its own instance of the domimplementation object
+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,
+};
--- /dev/null
+++ b/domino-lib/DOMTokenList.js
@@ -1,0 +1,186 @@
+"use strict";
+// DOMTokenList implementation based on https://github.com/Raynos/DOM-shim
+var utils = require('./utils');
+
+module.exports = DOMTokenList;
+
+function DOMTokenList(getter, setter) {
+  this._getString = getter;
+  this._setString = setter;
+  this._length = 0;
+  this._lastStringValue = '';
+  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));
+    }
+    this._lastStringValue = this._getString();
+  } },
+});
+
+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 toArray(clist) {
+  var length = clist._length;
+  var arr = Array(length);
+  for (var i = 0; i < length; i++) {
+    arr[i] = clist[i];
+  }
+  return arr;
+}
+
+function getList(clist) {
+  var strProp = clist._getString();
+  if (strProp === clist._lastStringValue) {
+    return toArray(clist);
+  }
+  var str = strProp.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;
+    });
+  }
+}
--- /dev/null
+++ b/domino-lib/Document.js
@@ -1,0 +1,884 @@
+"use strict";
+module.exports = Document;
+
+var Node = require('./Node');
+var NodeList = require('./NodeList');
+var ContainerNode = require('./ContainerNode');
+var Element = require('./Element');
+var Text = require('./Text');
+var Comment = require('./Comment');
+var Event = require('./Event');
+var DocumentFragment = require('./DocumentFragment');
+var ProcessingInstruction = require('./ProcessingInstruction');
+var DOMImplementation = require('./DOMImplementation');
+var TreeWalker = require('./TreeWalker');
+var NodeIterator = require('./NodeIterator');
+var NodeFilter = require('./NodeFilter');
+var URL = require('./URL');
+var select = require('./select');
+var events = require('./events');
+var xml = require('./xmlnames');
+var html = require('./htmlelts');
+var svg = require('./svg');
+var utils = require('./utils');
+var MUTATE = require('./MutationConstants');
+var NAMESPACE = utils.NAMESPACE;
+var isApiWritable = require("./config").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;
+};
--- /dev/null
+++ b/domino-lib/DocumentFragment.js
@@ -1,0 +1,68 @@
+"use strict";
+module.exports =  DocumentFragment;
+
+var Node = require('./Node');
+var NodeList = require('./NodeList');
+var ContainerNode = require('./ContainerNode');
+var Element = require('./Element');
+var select = require('./select');
+var utils = require('./utils');
+
+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
+  },
+
+});
--- /dev/null
+++ b/domino-lib/DocumentType.js
@@ -1,0 +1,36 @@
+"use strict";
+module.exports = DocumentType;
+
+var Node = require('./Node');
+var Leaf = require('./Leaf');
+var ChildNode = require('./ChildNode');
+
+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);
--- /dev/null
+++ b/domino-lib/Element.js
@@ -1,0 +1,1202 @@
+"use strict";
+module.exports = Element;
+
+var xml = require('./xmlnames');
+var utils = require('./utils');
+var NAMESPACE = utils.NAMESPACE;
+var attributes = require('./attributes');
+var Node = require('./Node');
+var NodeList = require('./NodeList');
+var NodeUtils = require('./NodeUtils');
+var FilteredElementList = require('./FilteredElementList');
+var DOMException = require('./DOMException');
+var DOMTokenList = require('./DOMTokenList');
+var select = require('./select');
+var ContainerNode = require('./ContainerNode');
+var ChildNode = require('./ChildNode');
+var NonDocumentTypeChildNode = require('./NonDocumentTypeChildNode');
+var NamedNodeMap = require('./NamedNodeMap');
+
+var uppercaseCache = Object.create(null);
+
+function Element(doc, localName, namespaceURI, prefix) {
+  ContainerNode.call(this);
+  this.nodeType = Node.ELEMENT_NODE;
+  this.ownerDocument = doc;
+  this.localName = localName;
+  this.namespaceURI = namespaceURI;
+  this.prefix = prefix;
+  this._tagName = undefined;
+
+  // These properties maintain the set of attributes
+  this._attrsByQName = Object.create(null); // The qname->Attr map
+  this._attrsByLName = Object.create(null); // The ns|lname->Attr map
+  this._attrKeys = [];     // attr index -> ns|lname
+}
+
+function recursiveGetText(node, a) {
+  if (node.nodeType === Node.TEXT_NODE) {
+    a.push(node._data);
+  }
+  else {
+    for(var i = 0, n = node.childNodes.length;  i < n; i++)
+      recursiveGetText(node.childNodes[i], a);
+  }
+}
+
+Element.prototype = Object.create(ContainerNode.prototype, {
+  isHTML: { get: function isHTML() {
+    return this.namespaceURI === NAMESPACE.HTML && this.ownerDocument.isHTML;
+  }},
+  tagName: { get: function tagName() {
+    if (this._tagName === undefined) {
+      var tn;
+      if (this.prefix === null) {
+        tn = this.localName;
+      } else {
+        tn = this.prefix + ':' + this.localName;
+      }
+      if (this.isHTML) {
+        var up = uppercaseCache[tn];
+        if (!up) {
+          // Converting to uppercase can be slow, so cache the conversion.
+          uppercaseCache[tn] = up = utils.toASCIIUpperCase(tn);
+        }
+        tn = up;
+      }
+      this._tagName = tn;
+    }
+    return this._tagName;
+  }},
+  nodeName: { get: function() { return this.tagName; }},
+  nodeValue: {
+    get: function() {
+      return null;
+    },
+    set: function() {}
+  },
+  textContent: {
+    get: function() {
+      var strings = [];
+      recursiveGetText(this, strings);
+      return strings.join('');
+    },
+    set: function(newtext) {
+      this.removeChildren();
+      if (newtext !== null && newtext !== undefined && newtext !== '') {
+        this._appendChild(this.ownerDocument.createTextNode(newtext));
+      }
+    }
+  },
+  innerHTML: {
+    get: function() {
+      return this.serialize();
+    },
+    set: utils.nyi
+  },
+  outerHTML: {
+    get: function() {
+      // "the attribute must return the result of running the HTML fragment
+      // serialization algorithm on a fictional node whose only child is
+      // the context object"
+      //
+      // 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.
+      return NodeUtils.serializeOne(this, { nodeType: 0 });
+    },
+    set: function(v) {
+      var document = this.ownerDocument;
+      var parent = this.parentNode;
+      if (parent === null) { return; }
+      if (parent.nodeType === Node.DOCUMENT_NODE) {
+        utils.NoModificationAllowedError();
+      }
+      if (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
+        parent = parent.ownerDocument.createElement("body");
+      }
+      var parser = document.implementation.mozHTMLParser(
+        document._address,
+        parent
+      );
+      parser.parse(v===null?'':String(v), true);
+      this.replaceWith(parser._asDocumentFragment());
+    },
+  },
+
+  _insertAdjacent: { value: function _insertAdjacent(position, node) {
+    var first = false;
+    switch(position) {
+    case 'beforebegin':
+      first = true;
+      /* falls through */
+    case 'afterend':
+      var parent = this.parentNode;
+      if (parent === null) { return null; }
+      return parent.insertBefore(node, first ? this : this.nextSibling);
+    case 'afterbegin':
+      first = true;
+      /* falls through */
+    case 'beforeend':
+      return this.insertBefore(node, first ? this.firstChild : null);
+    default:
+      return utils.SyntaxError();
+    }
+  }},
+
+  insertAdjacentElement: { value: function insertAdjacentElement(position, element) {
+    if (element.nodeType !== Node.ELEMENT_NODE) {
+      throw new TypeError('not an element');
+    }
+    position = utils.toASCIILowerCase(String(position));
+    return this._insertAdjacent(position, element);
+  }},
+
+  insertAdjacentText: { value: function insertAdjacentText(position, data) {
+    var textNode = this.ownerDocument.createTextNode(data);
+    position = utils.toASCIILowerCase(String(position));
+    this._insertAdjacent(position, textNode);
+    // "This method returns nothing because it existed before we had a chance
+    // to design it."
+  }},
+
+  insertAdjacentHTML: { value: function insertAdjacentHTML(position, text) {
+    position = utils.toASCIILowerCase(String(position));
+    text = String(text);
+    var context;
+    switch(position) {
+    case 'beforebegin':
+    case 'afterend':
+      context = this.parentNode;
+      if (context === null || context.nodeType === Node.DOCUMENT_NODE) {
+        utils.NoModificationAllowedError();
+      }
+      break;
+    case 'afterbegin':
+    case 'beforeend':
+      context = this;
+      break;
+    default:
+      utils.SyntaxError();
+    }
+    if ( (!(context instanceof Element)) || (
+      context.ownerDocument.isHTML &&
+      context.localName === 'html' &&
+      context.namespaceURI === NAMESPACE.HTML
+    ) ) {
+      context = context.ownerDocument.createElementNS(NAMESPACE.HTML, 'body');
+    }
+    var parser = this.ownerDocument.implementation.mozHTMLParser(
+      this.ownerDocument._address, context
+    );
+    parser.parse(text, true);
+    this._insertAdjacent(position, parser._asDocumentFragment());
+  }},
+
+  children: { get: function() {
+    if (!this._children) {
+      this._children = new ChildrenCollection(this);
+    }
+    return this._children;
+  }},
+
+  attributes: { get: function() {
+    if (!this._attributes) {
+      this._attributes = new AttributesArray(this);
+    }
+    return this._attributes;
+  }},
+
+
+  firstElementChild: { get: function() {
+    for (var kid = this.firstChild; kid !== null; kid = kid.nextSibling) {
+      if (kid.nodeType === Node.ELEMENT_NODE) return kid;
+    }
+    return null;
+  }},
+
+  lastElementChild: { get: function() {
+    for (var kid = this.lastChild; kid !== null; kid = kid.previousSibling) {
+      if (kid.nodeType === Node.ELEMENT_NODE) return kid;
+    }
+    return null;
+  }},
+
+  childElementCount: { get: function() {
+    return this.children.length;
+  }},
+
+
+  // Return the next element, in source order, after this one or
+  // null if there are no more.  If root element is specified,
+  // then don't traverse beyond its subtree.
+  //
+  // This is not a DOM method, but is convenient for
+  // lazy traversals of the tree.
+  nextElement: { value: function(root) {
+    if (!root) root = this.ownerDocument.documentElement;
+    var next = this.firstElementChild;
+    if (!next) {
+      // don't use sibling if we're at root
+      if (this===root) return null;
+      next = this.nextElementSibling;
+    }
+    if (next) return next;
+
+    // If we can't go down or across, then we have to go up
+    // and across to the parent sibling or another ancestor's
+    // sibling.  Be careful, though: if we reach the root
+    // element, or if we reach the documentElement, then
+    // the traversal ends.
+    for(var parent = this.parentElement;
+      parent && parent !== root;
+      parent = parent.parentElement) {
+
+      next = parent.nextElementSibling;
+      if (next) return next;
+    }
+
+    return null;
+  }},
+
+  // XXX:
+  // Tests are currently failing for this function.
+  // Awaiting resolution of:
+  // http://lists.w3.org/Archives/Public/www-dom/2011JulSep/0016.html
+  getElementsByTagName: { value: function getElementsByTagName(lname) {
+    var filter;
+    if (!lname) return new NodeList();
+    if (lname === '*')
+      filter = function() { return true; };
+    else if (this.isHTML)
+      filter = htmlLocalNameElementFilter(lname);
+    else
+      filter = localNameElementFilter(lname);
+
+    return new FilteredElementList(this, filter);
+  }},
+
+  getElementsByTagNameNS: { value: function getElementsByTagNameNS(ns, lname){
+    var filter;
+    if (ns === '*' && lname === '*')
+      filter = function() { return true; };
+    else if (ns === '*')
+      filter = localNameElementFilter(lname);
+    else if (lname === '*')
+      filter = namespaceElementFilter(ns);
+    else
+      filter = namespaceLocalNameElementFilter(ns, lname);
+
+    return new FilteredElementList(this, filter);
+  }},
+
+  getElementsByClassName: { value: function getElementsByClassName(names){
+    names = String(names).trim();
+    if (names === '') {
+      var result = new NodeList(); // Empty node list
+      return result;
+    }
+    names = names.split(/[ \t\r\n\f]+/);  // Split on ASCII whitespace
+    return new FilteredElementList(this, classNamesElementFilter(names));
+  }},
+
+  getElementsByName: { value: function getElementsByName(name) {
+    return new FilteredElementList(this, elementNameFilter(String(name)));
+  }},
+
+  // Utility methods used by the public API methods above
+  clone: { value: function clone() {
+    var e;
+
+    // XXX:
+    // Modify this to use the constructor directly or
+    // avoid error checking in some other way. In case we try
+    // to clone an invalid node that the parser inserted.
+    //
+    if (this.namespaceURI !== NAMESPACE.HTML || this.prefix || !this.ownerDocument.isHTML) {
+      e = this.ownerDocument.createElementNS(
+        this.namespaceURI, (this.prefix !== null) ?
+          (this.prefix + ':' + this.localName) : this.localName
+      );
+    } else {
+      e = this.ownerDocument.createElement(this.localName);
+    }
+
+    for(var i = 0, n = this._attrKeys.length; i < n; i++) {
+      var lname = this._attrKeys[i];
+      var a = this._attrsByLName[lname];
+      var b = a.cloneNode();
+      b._setOwnerElement(e);
+      e._attrsByLName[lname] = b;
+      e._addQName(b);
+    }
+    e._attrKeys = this._attrKeys.concat();
+
+    return e;
+  }},
+
+  isEqual: { value: function isEqual(that) {
+    if (this.localName !== that.localName ||
+      this.namespaceURI !== that.namespaceURI ||
+      this.prefix !== that.prefix ||
+      this._numattrs !== that._numattrs)
+      return false;
+
+    // Compare the sets of attributes, ignoring order
+    // and ignoring attribute prefixes.
+    for(var i = 0, n = this._numattrs; i < n; i++) {
+      var a = this._attr(i);
+      if (!that.hasAttributeNS(a.namespaceURI, a.localName))
+        return false;
+      if (that.getAttributeNS(a.namespaceURI,a.localName) !== a.value)
+        return false;
+    }
+
+    return true;
+  }},
+
+  // This is the 'locate a namespace prefix' algorithm from the
+  // DOM specification.  It is used by Node.lookupPrefix()
+  // (Be sure to compare DOM3 and DOM4 versions of spec.)
+  _lookupNamespacePrefix: { value: function _lookupNamespacePrefix(ns, originalElement) {
+    if (
+      this.namespaceURI &&
+      this.namespaceURI === ns &&
+      this.prefix !== null &&
+      originalElement.lookupNamespaceURI(this.prefix) === ns
+    ) {
+      return this.prefix;
+    }
+
+    for(var i = 0, n = this._numattrs; i < n; i++) {
+      var a = this._attr(i);
+      if (
+        a.prefix === 'xmlns' &&
+        a.value === ns &&
+        originalElement.lookupNamespaceURI(a.localName) === ns
+      ) {
+        return a.localName;
+      }
+    }
+
+    var parent = this.parentElement;
+    return parent ? parent._lookupNamespacePrefix(ns, originalElement) : null;
+  }},
+
+  // This is the 'locate a namespace' algorithm for Element nodes
+  // from the DOM Core spec.  It is used by Node#lookupNamespaceURI()
+  lookupNamespaceURI: { value: function lookupNamespaceURI(prefix) {
+    if (prefix === '' || prefix === undefined) { prefix = null; }
+    if (this.namespaceURI !== null && this.prefix === prefix)
+      return this.namespaceURI;
+
+    for(var i = 0, n = this._numattrs; i < n; i++) {
+      var a = this._attr(i);
+      if (a.namespaceURI === NAMESPACE.XMLNS) {
+        if (
+          (a.prefix === 'xmlns' && a.localName === prefix) ||
+          (prefix === null && a.prefix === null && a.localName === 'xmlns')
+        ) {
+          return a.value || null;
+        }
+      }
+    }
+
+    var parent = this.parentElement;
+    return parent ? parent.lookupNamespaceURI(prefix) : null;
+  }},
+
+  //
+  // Attribute handling methods and utilities
+  //
+
+  /*
+   * Attributes in the DOM are tricky:
+   *
+   * - there are the 8 basic get/set/has/removeAttribute{NS} methods
+   *
+   * - but many HTML attributes are also 'reflected' through IDL
+   *   attributes which means that they can be queried and set through
+   *   regular properties of the element.  There is just one attribute
+   *   value, but two ways to get and set it.
+   *
+   * - Different HTML element types have different sets of reflected
+     attributes.
+   *
+   * - attributes can also be queried and set through the .attributes
+   *   property of an element.  This property behaves like an array of
+   *   Attr objects.  The value property of each Attr is writeable, so
+   *   this is a third way to read and write attributes.
+   *
+   * - for efficiency, we really want to store attributes in some kind
+   *   of name->attr map.  But the attributes[] array is an array, not a
+   *   map, which is kind of unnatural.
+   *
+   * - When using namespaces and prefixes, and mixing the NS methods
+   *   with the non-NS methods, it is apparently actually possible for
+   *   an attributes[] array to have more than one attribute with the
+   *   same qualified name.  And certain methods must operate on only
+   *   the first attribute with such a name.  So for these methods, an
+   *   inefficient array-like data structure would be easier to
+   *   implement.
+   *
+   * - The attributes[] array is live, not a snapshot, so changes to the
+   *   attributes must be immediately visible through existing arrays.
+   *
+   * - When attributes are queried and set through IDL properties
+   *   (instead of the get/setAttributes() method or the attributes[]
+   *   array) they may be subject to type conversions, URL
+   *   normalization, etc., so some extra processing is required in that
+   *   case.
+   *
+   * - But access through IDL properties is probably the most common
+   *   case, so we'd like that to be as fast as possible.
+   *
+   * - We can't just store attribute values in their parsed idl form,
+   *   because setAttribute() has to return whatever string is passed to
+   *   getAttribute even if it is not a legal, parseable value. So
+   *   attribute values must be stored in unparsed string form.
+   *
+   * - We need to be able to send change notifications or mutation
+   *   events of some sort to the renderer whenever an attribute value
+   *   changes, regardless of the way in which it changes.
+   *
+   * - Some attributes, such as id and class affect other parts of the
+   *   DOM API, like getElementById and getElementsByClassName and so
+   *   for efficiency, we need to specially track changes to these
+   *   special attributes.
+   *
+   * - Some attributes like class have different names (className) when
+   *   reflected.
+   *
+   * - Attributes whose names begin with the string 'data-' are treated
+     specially.
+   *
+   * - Reflected attributes that have a boolean type in IDL have special
+   *   behavior: setting them to false (in IDL) is the same as removing
+   *   them with removeAttribute()
+   *
+   * - numeric attributes (like HTMLElement.tabIndex) can have default
+   *   values that must be returned by the idl getter even if the
+   *   content attribute does not exist. (The default tabIndex value
+   *   actually varies based on the type of the element, so that is a
+   *   tricky one).
+   *
+   * See
+   * http://www.whatwg.org/specs/web-apps/current-work/multipage/urls.html#reflect
+   * for rules on how attributes are reflected.
+   *
+   */
+
+  getAttribute: { value: function getAttribute(qname) {
+    var attr = this.getAttributeNode(qname);
+    return attr ? attr.value : null;
+  }},
+
+  getAttributeNS: { value: function getAttributeNS(ns, lname) {
+    var attr = this.getAttributeNodeNS(ns, lname);
+    return attr ? attr.value : null;
+  }},
+
+  getAttributeNode: { value: function getAttributeNode(qname) {
+    qname = String(qname);
+    if (/[A-Z]/.test(qname) && this.isHTML)
+      qname = utils.toASCIILowerCase(qname);
+    var attr = this._attrsByQName[qname];
+    if (!attr) return null;
+
+    if (Array.isArray(attr))  // If there is more than one
+      attr = attr[0];         // use the first
+
+    return attr;
+  }},
+
+  getAttributeNodeNS: { value: function getAttributeNodeNS(ns, lname) {
+    ns = (ns === undefined || ns === null) ? '' : String(ns);
+    lname = String(lname);
+    var attr = this._attrsByLName[ns + '|' + lname];
+    return attr ? attr : null;
+  }},
+
+  hasAttribute: { value: function hasAttribute(qname) {
+    qname = String(qname);
+    if (/[A-Z]/.test(qname) && this.isHTML)
+      qname = utils.toASCIILowerCase(qname);
+    return this._attrsByQName[qname] !== undefined;
+  }},
+
+  hasAttributeNS: { value: function hasAttributeNS(ns, lname) {
+    ns = (ns === undefined || ns === null) ? '' : String(ns);
+    lname = String(lname);
+    var key = ns + '|' + lname;
+    return this._attrsByLName[key] !== undefined;
+  }},
+
+  hasAttributes: { value: function hasAttributes() {
+    return this._numattrs > 0;
+  }},
+
+  toggleAttribute: { value: function toggleAttribute(qname, force) {
+    qname = String(qname);
+    if (!xml.isValidName(qname)) utils.InvalidCharacterError();
+    if (/[A-Z]/.test(qname) && this.isHTML)
+      qname = utils.toASCIILowerCase(qname);
+    var a = this._attrsByQName[qname];
+    if (a === undefined) {
+      if (force === undefined || force === true) {
+        this._setAttribute(qname, '');
+        return true;
+      }
+      return false;
+    } else {
+      if (force === undefined || force === false) {
+        this.removeAttribute(qname);
+        return false;
+      }
+      return true;
+    }
+  }},
+
+  // Set the attribute without error checking. The parser uses this.
+  _setAttribute: { value: function _setAttribute(qname, value) {
+    // XXX: the spec says that this next search should be done
+    // on the local name, but I think that is an error.
+    // email pending on www-dom about it.
+    var attr = this._attrsByQName[qname];
+    var isnew;
+    if (!attr) {
+      attr = this._newattr(qname);
+      isnew = true;
+    }
+    else {
+      if (Array.isArray(attr)) attr = attr[0];
+    }
+
+    // Now set the attribute value on the new or existing Attr object.
+    // The Attr.value setter method handles mutation events, etc.
+    attr.value = value;
+    if (this._attributes) this._attributes[qname] = attr;
+    if (isnew && this._newattrhook) this._newattrhook(qname, value);
+  }},
+
+  // Check for errors, and then set the attribute
+  setAttribute: { value: function setAttribute(qname, value) {
+    qname = String(qname);
+    if (!xml.isValidName(qname)) utils.InvalidCharacterError();
+    if (/[A-Z]/.test(qname) && this.isHTML)
+      qname = utils.toASCIILowerCase(qname);
+    this._setAttribute(qname, String(value));
+  }},
+
+
+  // The version with no error checking used by the parser
+  _setAttributeNS: { value: function _setAttributeNS(ns, qname, value) {
+    var pos = qname.indexOf(':'), prefix, lname;
+    if (pos < 0) {
+      prefix = null;
+      lname = qname;
+    }
+    else {
+      prefix = qname.substring(0, pos);
+      lname = qname.substring(pos+1);
+    }
+
+    if (ns === '' || ns === undefined) ns = null;
+    var key = (ns === null ? '' : ns) + '|' + lname;
+
+    var attr = this._attrsByLName[key];
+    var isnew;
+    if (!attr) {
+      attr = new Attr(this, lname, prefix, ns);
+      isnew = true;
+      this._attrsByLName[key] = attr;
+      if (this._attributes) {
+        this._attributes[this._attrKeys.length] = attr;
+      }
+      this._attrKeys.push(key);
+
+      // We also have to make the attr searchable by qname.
+      // But we have to be careful because there may already
+      // be an attr with this qname.
+      this._addQName(attr);
+    }
+    else if (false /* changed in DOM 4 */) {
+      // Calling setAttributeNS() can change the prefix of an
+      // existing attribute in DOM 2/3.
+      if (attr.prefix !== prefix) {
+        // Unbind the old qname
+        this._removeQName(attr);
+        // Update the prefix
+        attr.prefix = prefix;
+        // Bind the new qname
+        this._addQName(attr);
+      }
+
+    }
+    attr.value = value; // Automatically sends mutation event
+    if (isnew && this._newattrhook) this._newattrhook(qname, value);
+  }},
+
+  // Do error checking then call _setAttributeNS
+  setAttributeNS: { value: function setAttributeNS(ns, qname, value) {
+    // Convert parameter types according to WebIDL
+    ns = (ns === null || ns === undefined || ns === '') ? null : String(ns);
+    qname = String(qname);
+    if (!xml.isValidQName(qname)) utils.InvalidCharacterError();
+
+    var pos = qname.indexOf(':');
+    var prefix = (pos < 0) ? null : qname.substring(0, pos);
+
+    if ((prefix !== null && ns === null) ||
+      (prefix === 'xml' && ns !== NAMESPACE.XML) ||
+      ((qname === 'xmlns' || prefix === 'xmlns') &&
+       (ns !== NAMESPACE.XMLNS)) ||
+      (ns === NAMESPACE.XMLNS &&
+       !(qname === 'xmlns' || prefix === 'xmlns')))
+      utils.NamespaceError();
+
+    this._setAttributeNS(ns, qname, String(value));
+  }},
+
+  setAttributeNode: { value: function setAttributeNode(attr) {
+    if (attr.ownerElement !== null && attr.ownerElement !== this) {
+      throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR);
+    }
+    var result = null;
+    var oldAttrs = this._attrsByQName[attr.name];
+    if (oldAttrs) {
+      if (!Array.isArray(oldAttrs)) { oldAttrs = [ oldAttrs ]; }
+      if (oldAttrs.some(function(a) { return a===attr; })) {
+        return attr;
+      } else if (attr.ownerElement !== null) {
+        throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR);
+      }
+      oldAttrs.forEach(function(a) { this.removeAttributeNode(a); }, this);
+      result = oldAttrs[0];
+    }
+    this.setAttributeNodeNS(attr);
+    return result;
+  }},
+
+  setAttributeNodeNS: { value: function setAttributeNodeNS(attr) {
+    if (attr.ownerElement !== null) {
+      throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR);
+    }
+    var ns = attr.namespaceURI;
+    var key = (ns === null ? '' : ns) + '|' + attr.localName;
+    var oldAttr = this._attrsByLName[key];
+    if (oldAttr) { this.removeAttributeNode(oldAttr); }
+    attr._setOwnerElement(this);
+    this._attrsByLName[key] = attr;
+    if (this._attributes) {
+      this._attributes[this._attrKeys.length] = attr;
+    }
+    this._attrKeys.push(key);
+    this._addQName(attr);
+    if (this._newattrhook) this._newattrhook(attr.name, attr.value);
+    return oldAttr || null;
+  }},
+
+  removeAttribute: { value: function removeAttribute(qname) {
+    qname = String(qname);
+    if (/[A-Z]/.test(qname) && this.isHTML)
+      qname = utils.toASCIILowerCase(qname);
+
+    var attr = this._attrsByQName[qname];
+    if (!attr) return;
+
+    // If there is more than one match for this qname
+    // so don't delete the qname mapping, just remove the first
+    // element from it.
+    if (Array.isArray(attr)) {
+      if (attr.length > 2) {
+        attr = attr.shift();  // remove it from the array
+      }
+      else {
+        this._attrsByQName[qname] = attr[1];
+        attr = attr[0];
+      }
+    }
+    else {
+      // only a single match, so remove the qname mapping
+      this._attrsByQName[qname] = undefined;
+    }
+
+    var ns = attr.namespaceURI;
+    // Now attr is the removed attribute.  Figure out its
+    // ns+lname key and remove it from the other mapping as well.
+    var key = (ns === null ? '' : ns) + '|' + attr.localName;
+    this._attrsByLName[key] = undefined;
+
+    var i = this._attrKeys.indexOf(key);
+    if (this._attributes) {
+      Array.prototype.splice.call(this._attributes, i, 1);
+      this._attributes[qname] = undefined;
+    }
+    this._attrKeys.splice(i, 1);
+
+    // Onchange handler for the attribute
+    var onchange = attr.onchange;
+    attr._setOwnerElement(null);
+    if (onchange) {
+      onchange.call(attr, this, attr.localName, attr.value, null);
+    }
+    // Mutation event
+    if (this.rooted) this.ownerDocument.mutateRemoveAttr(attr);
+  }},
+
+  removeAttributeNS: { value: function removeAttributeNS(ns, lname) {
+    ns = (ns === undefined || ns === null) ? '' : String(ns);
+    lname = String(lname);
+    var key = ns + '|' + lname;
+    var attr = this._attrsByLName[key];
+    if (!attr) return;
+
+    this._attrsByLName[key] = undefined;
+
+    var i = this._attrKeys.indexOf(key);
+    if (this._attributes) {
+      Array.prototype.splice.call(this._attributes, i, 1);
+    }
+    this._attrKeys.splice(i, 1);
+
+    // Now find the same Attr object in the qname mapping and remove it
+    // But be careful because there may be more than one match.
+    this._removeQName(attr);
+
+    // Onchange handler for the attribute
+    var onchange = attr.onchange;
+    attr._setOwnerElement(null);
+    if (onchange) {
+      onchange.call(attr, this, attr.localName, attr.value, null);
+    }
+    // Mutation event
+    if (this.rooted) this.ownerDocument.mutateRemoveAttr(attr);
+  }},
+
+  removeAttributeNode: { value: function removeAttributeNode(attr) {
+    var ns = attr.namespaceURI;
+    var key = (ns === null ? '' : ns) + '|' + attr.localName;
+    if (this._attrsByLName[key] !== attr) {
+      utils.NotFoundError();
+    }
+    this.removeAttributeNS(ns, attr.localName);
+    return attr;
+  }},
+
+  getAttributeNames: { value: function getAttributeNames() {
+    var elt = this;
+    return this._attrKeys.map(function(key) {
+      return elt._attrsByLName[key].name;
+    });
+  }},
+
+  // This 'raw' version of getAttribute is used by the getter functions
+  // of reflected attributes. It skips some error checking and
+  // namespace steps
+  _getattr: { value: function _getattr(qname) {
+    // Assume that qname is already lowercased, so don't do it here.
+    // Also don't check whether attr is an array: a qname with no
+    // prefix will never have two matching Attr objects (because
+    // setAttributeNS doesn't allow a non-null namespace with a
+    // null prefix.
+    var attr = this._attrsByQName[qname];
+    return attr ? attr.value : null;
+  }},
+
+  // The raw version of setAttribute for reflected idl attributes.
+  _setattr: { value: function _setattr(qname, value) {
+    var attr = this._attrsByQName[qname];
+    var isnew;
+    if (!attr) {
+      attr = this._newattr(qname);
+      isnew = true;
+    }
+    attr.value = String(value);
+    if (this._attributes) this._attributes[qname] = attr;
+    if (isnew && this._newattrhook) this._newattrhook(qname, value);
+  }},
+
+  // Create a new Attr object, insert it, and return it.
+  // Used by setAttribute() and by set()
+  _newattr: { value: function _newattr(qname) {
+    var attr = new Attr(this, qname, null, null);
+    var key = '|' + qname;
+    this._attrsByQName[qname] = attr;
+    this._attrsByLName[key] = attr;
+    if (this._attributes) {
+      this._attributes[this._attrKeys.length] = attr;
+    }
+    this._attrKeys.push(key);
+    return attr;
+  }},
+
+  // Add a qname->Attr mapping to the _attrsByQName object, taking into
+  // account that there may be more than one attr object with the
+  // same qname
+  _addQName: { value: function(attr) {
+    var qname = attr.name;
+    var existing = this._attrsByQName[qname];
+    if (!existing) {
+      this._attrsByQName[qname] = attr;
+    }
+    else if (Array.isArray(existing)) {
+      existing.push(attr);
+    }
+    else {
+      this._attrsByQName[qname] = [existing, attr];
+    }
+    if (this._attributes) this._attributes[qname] = attr;
+  }},
+
+  // Remove a qname->Attr mapping to the _attrsByQName object, taking into
+  // account that there may be more than one attr object with the
+  // same qname
+  _removeQName: { value: function(attr) {
+    var qname = attr.name;
+    var target = this._attrsByQName[qname];
+
+    if (Array.isArray(target)) {
+      var idx = target.indexOf(attr);
+      utils.assert(idx !== -1); // It must be here somewhere
+      if (target.length === 2) {
+        this._attrsByQName[qname] = target[1-idx];
+        if (this._attributes) {
+          this._attributes[qname] = this._attrsByQName[qname];
+        }
+      } else {
+        target.splice(idx, 1);
+        if (this._attributes && this._attributes[qname] === attr) {
+          this._attributes[qname] = target[0];
+        }
+      }
+    }
+    else {
+      utils.assert(target === attr);  // If only one, it must match
+      this._attrsByQName[qname] = undefined;
+      if (this._attributes) {
+        this._attributes[qname] = undefined;
+      }
+    }
+  }},
+
+  // Return the number of attributes
+  _numattrs: { get: function() { return this._attrKeys.length; }},
+  // Return the nth Attr object
+  _attr: { value: function(n) {
+    return this._attrsByLName[this._attrKeys[n]];
+  }},
+
+  // Define getters and setters for an 'id' property that reflects
+  // the content attribute 'id'.
+  id: attributes.property({name: 'id'}),
+
+  // Define getters and setters for a 'className' property that reflects
+  // the content attribute 'class'.
+  className: attributes.property({name: 'class'}),
+
+  classList: { get: function() {
+    var self = this;
+    if (this._classList) {
+      return this._classList;
+    }
+    var dtlist = new DOMTokenList(
+      function() {
+        return self.className || "";
+      },
+      function(v) {
+        self.className = v;
+      }
+    );
+    this._classList = dtlist;
+    return dtlist;
+  }, set: function(v) { this.className = v; }},
+
+  matches: { value: function(selector) {
+    return select.matches(this, selector);
+  }},
+
+  closest: { value: function(selector) {
+    var el = this;
+	do {
+	  if (el.matches && el.matches(selector)) { return el; }
+	  el = el.parentElement || el.parentNode;
+	} while (el !== null && el.nodeType === Node.ELEMENT_NODE);
+	return null;
+  }},
+
+  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);
+  }}
+
+});
+
+Object.defineProperties(Element.prototype, ChildNode);
+Object.defineProperties(Element.prototype, NonDocumentTypeChildNode);
+
+// Register special handling for the id attribute
+attributes.registerChangeHandler(Element, 'id',
+ function(element, lname, oldval, newval) {
+   if (element.rooted) {
+     if (oldval) {
+       element.ownerDocument.delId(oldval, element);
+     }
+     if (newval) {
+       element.ownerDocument.addId(newval, element);
+     }
+   }
+ }
+);
+attributes.registerChangeHandler(Element, 'class',
+ function(element, lname, oldval, newval) {
+   if (element._classList) { element._classList._update(); }
+ }
+);
+
+// The Attr class represents a single attribute.  The values in
+// _attrsByQName and _attrsByLName are instances of this class.
+function Attr(elt, lname, prefix, namespace, value) {
+  // localName and namespace are constant for any attr object.
+  // But value may change.  And so can prefix, and so, therefore can name.
+  this.localName = lname;
+  this.prefix = (prefix===null || prefix==='') ? null : ('' + prefix);
+  this.namespaceURI = (namespace===null || namespace==='') ? null : ('' + namespace);
+  this.data = value;
+  // Set ownerElement last to ensure it is hooked up to onchange handler
+  this._setOwnerElement(elt);
+}
+
+// In DOM 3 Attr was supposed to extend Node; in DOM 4 that was abandoned.
+Attr.prototype = Object.create(Object.prototype, {
+  ownerElement: {
+    get: function() { return this._ownerElement; },
+  },
+  _setOwnerElement: { value: function _setOwnerElement(elt) {
+    this._ownerElement = elt;
+    if (this.prefix === null && this.namespaceURI === null && elt) {
+      this.onchange = elt._attributeChangeHandlers[this.localName];
+    } else {
+      this.onchange = null;
+    }
+  }},
+
+  name: { get: function() {
+    return this.prefix ? this.prefix + ':' + this.localName : this.localName;
+  }},
+
+  specified: { get: function() {
+    // Deprecated
+    return true;
+  }},
+
+  value: {
+    get: function() {
+      return this.data;
+    },
+    set: function(value) {
+      var oldval = this.data;
+      value = (value === undefined) ? '' : value + '';
+      if (value === oldval) return;
+
+      this.data = value;
+
+      // Run the onchange hook for the attribute
+      // if there is one.
+      if (this.ownerElement) {
+        if (this.onchange)
+          this.onchange(this.ownerElement,this.localName, oldval, value);
+
+        // Generate a mutation event if the element is rooted
+        if (this.ownerElement.rooted)
+          this.ownerElement.ownerDocument.mutateAttr(this, oldval);
+      }
+    },
+  },
+
+  cloneNode: { value: function cloneNode(deep) {
+    // Both this method and Document#createAttribute*() create unowned Attrs
+    return new Attr(
+      null, this.localName, this.prefix, this.namespaceURI, this.data
+    );
+  }},
+
+  // Legacy aliases (see gh#70 and https://dom.spec.whatwg.org/#interface-attr)
+  nodeType: { get: function() { return Node.ATTRIBUTE_NODE; } },
+  nodeName: { get: function() { return this.name; } },
+  nodeValue: {
+    get: function() { return this.value; },
+    set: function(v) { this.value = v; },
+  },
+  textContent: {
+    get: function() { return this.value; },
+    set: function(v) {
+      if (v === null || v === undefined) { v = ''; }
+      this.value = v;
+    },
+  },
+});
+// Sneakily export this class for use by Document.createAttribute()
+Element._Attr = Attr;
+
+// The attributes property of an Element will be an instance of this class.
+// This class is really just a dummy, though. It only defines a length
+// property and an item() method. The AttrArrayProxy that
+// defines the public API just uses the Element object itself.
+function AttributesArray(elt) {
+  NamedNodeMap.call(this, elt);
+  for (var name in elt._attrsByQName) {
+    this[name] = elt._attrsByQName[name];
+  }
+  for (var i = 0; i < elt._attrKeys.length; i++) {
+    this[i] = elt._attrsByLName[elt._attrKeys[i]];
+  }
+}
+AttributesArray.prototype = Object.create(NamedNodeMap.prototype, {
+  length: { get: function() {
+    return this.element._attrKeys.length;
+  }, set: function() { /* ignore */ } },
+  item: { value: function(n) {
+    /* jshint bitwise: false */
+    n = n >>> 0;
+    if (n >= this.length) { return null; }
+    return this.element._attrsByLName[this.element._attrKeys[n]];
+    /* jshint bitwise: true */
+  } },
+});
+
+// We can't make direct array access work (without Proxies, node >=6)
+// but we can make `Array.from(node.attributes)` and for-of loops work.
+if (global.Symbol && global.Symbol.iterator) {
+    AttributesArray.prototype[global.Symbol.iterator] = function() {
+        var i=0, n=this.length, self=this;
+        return {
+            next: function() {
+                if (i<n) return { value: self.item(i++) };
+                return { done: true };
+            }
+        };
+    };
+}
+
+
+// The children property of an Element will be an instance of this class.
+// It defines length, item() and namedItem() and will be wrapped by an
+// HTMLCollection when exposed through the DOM.
+function ChildrenCollection(e) {
+  this.element = e;
+  this.updateCache();
+}
+
+ChildrenCollection.prototype = Object.create(Object.prototype, {
+  length: { get: function() {
+    this.updateCache();
+    return this.childrenByNumber.length;
+  } },
+  item: { value: function item(n) {
+    this.updateCache();
+    return this.childrenByNumber[n] || null;
+  } },
+
+  namedItem: { value: function namedItem(name) {
+    this.updateCache();
+    return this.childrenByName[name] || null;
+  } },
+
+  // This attribute returns the entire name->element map.
+  // It is not part of the HTMLCollection API, but we need it in
+  // src/HTMLCollectionProxy
+  namedItems: { get: function() {
+    this.updateCache();
+    return this.childrenByName;
+  } },
+
+  updateCache: { value: function updateCache() {
+    var namedElts = /^(a|applet|area|embed|form|frame|frameset|iframe|img|object)$/;
+    if (this.lastModTime !== this.element.lastModTime) {
+      this.lastModTime = this.element.lastModTime;
+
+      var n = this.childrenByNumber && this.childrenByNumber.length || 0;
+      for(var i = 0; i < n; i++) {
+        this[i] = undefined;
+      }
+
+      this.childrenByNumber = [];
+      this.childrenByName = Object.create(null);
+
+      for (var c = this.element.firstChild; c !== null; c = c.nextSibling) {
+        if (c.nodeType === Node.ELEMENT_NODE) {
+
+          this[this.childrenByNumber.length] = c;
+          this.childrenByNumber.push(c);
+
+          // XXX Are there any requirements about the namespace
+          // of the id property?
+          var id = c.getAttribute('id');
+
+          // If there is an id that is not already in use...
+          if (id && !this.childrenByName[id])
+            this.childrenByName[id] = c;
+
+          // For certain HTML elements we check the name attribute
+          var name = c.getAttribute('name');
+          if (name &&
+            this.element.namespaceURI === NAMESPACE.HTML &&
+            namedElts.test(this.element.localName) &&
+            !this.childrenByName[name])
+            this.childrenByName[id] = c;
+        }
+      }
+    }
+  } },
+});
+
+// These functions return predicates for filtering elements.
+// They're used by the Document and Element classes for methods like
+// getElementsByTagName and getElementsByClassName
+
+function localNameElementFilter(lname) {
+  return function(e) { return e.localName === lname; };
+}
+
+function htmlLocalNameElementFilter(lname) {
+  var lclname = utils.toASCIILowerCase(lname);
+  if (lclname === lname)
+    return localNameElementFilter(lname);
+
+  return function(e) {
+    return e.isHTML ? e.localName === lclname : e.localName === lname;
+  };
+}
+
+function namespaceElementFilter(ns) {
+  return function(e) { return e.namespaceURI === ns; };
+}
+
+function namespaceLocalNameElementFilter(ns, lname) {
+  return function(e) {
+    return e.namespaceURI === ns && e.localName === lname;
+  };
+}
+
+function classNamesElementFilter(names) {
+  return function(e) {
+    return names.every(function(n) { return e.classList.contains(n); });
+  };
+}
+
+function elementNameFilter(name) {
+  return function(e) {
+    // All the *HTML elements* in the document with the given name attribute
+    if (e.namespaceURI !== NAMESPACE.HTML) { return false; }
+    return e.getAttribute('name') === name;
+  };
+}
--- /dev/null
+++ b/domino-lib/Event.js
@@ -1,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;
+  }},
+
+});
--- /dev/null
+++ b/domino-lib/EventTarget.js
@@ -1,0 +1,298 @@
+"use strict";
+var Event = require('./Event');
+var MouseEvent = require('./MouseEvent');
+var utils = require('./utils');
+
+module.exports = EventTarget;
+
+function EventTarget() {}
+
+EventTarget.prototype = {
+  // XXX
+  // See WebIDL §4.8 for details on object event handlers
+  // and how they should behave.  We actually have to accept
+  // any object to addEventListener... Can't type check it.
+  // on registration.
+
+  // XXX:
+  // Capturing event listeners are sort of rare.  I think I can optimize
+  // them so that dispatchEvent can skip the capturing phase (or much of
+  // it).  Each time a capturing listener is added, increment a flag on
+  // the target node and each of its ancestors.  Decrement when removed.
+  // And update the counter when nodes are added and removed from the
+  // tree as well.  Then, in dispatch event, the capturing phase can
+  // abort if it sees any node with a zero count.
+  addEventListener: function addEventListener(type, listener, capture) {
+    if (!listener) return;
+    if (capture === undefined) capture = false;
+    if (!this._listeners) this._listeners = 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;
+  }
+
+};
--- /dev/null
+++ b/domino-lib/FilteredElementList.js
@@ -1,0 +1,92 @@
+"use strict";
+module.exports = FilteredElementList;
+
+var Node = require('./Node');
+
+//
+// This file defines node list implementation that lazily traverses
+// the document tree (or a subtree rooted at any element) and includes
+// only those elements for which a specified filter function returns true.
+// It is used to implement the
+// {Document,Element}.getElementsBy{TagName,ClassName}{,NS} methods.
+//
+// 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;
+  } },
+});
--- /dev/null
+++ b/domino-lib/HTMLParser.js
@@ -1,0 +1,7264 @@
+"use strict";
+module.exports = HTMLParser;
+
+var Document = require('./Document');
+var DocumentType = require('./DocumentType');
+var Node = require('./Node');
+var NAMESPACE = require('./utils').NAMESPACE;
+var html = require('./htmlelts');
+var impl = html.elements;
+
+var pushAll = Function.prototype.apply.bind(Array.prototype.push);
+
+/*
+ * This file contains an implementation of the HTML parsing algorithm.
+ * The algorithm and the implementation are complex because HTML
+ * explicitly defines how the parser should behave for all possible
+ * valid and invalid inputs.
+ *
+ * Usage:
+ *
+ * The file defines a single HTMLParser() function, which dom.js exposes
+ * publicly as document.implementation.mozHTMLParser(). This is a
+ * factory function, not a constructor.
+ *
+ * When you call document.implementation.mozHTMLParser(), it returns
+ * an object that has parse() and document() methods. To parse HTML text,
+ * pass the text (in one or more chunks) to the parse() method.  When
+ * you've passed all the text (on the last chunk, or afterward) pass
+ * true as the second argument to parse() to tell the parser that there
+ * is no more coming. Call document() to get the document object that
+ * the parser is parsing into.  You can call this at any time, before
+ * or after calling parse().
+ *
+ * The first argument to mozHTMLParser is the absolute URL of the document.
+ *
+ * The second argument is optional and is for internal use only.  Pass an
+ * element as the fragmentContext to do innerHTML parsing for the
+ * element.  To do innerHTML parsing on a document, pass null. Otherwise,
+ * omit the 2nd argument. See HTMLElement.innerHTML for an example.  Note
+ * that if you pass a context element, the end() method will return an
+ * unwrapped document instead of a wrapped one.
+ *
+ * Implementation details:
+ *
+ * This is a long file of almost 7000 lines. It is structured as one
+ * big function nested within another big function.  The outer
+ * function defines a bunch of constant data, utility functions
+ * that use that data, and a couple of classes used by the parser.
+ * The outer function also defines and returns the
+ * inner function. This inner function is the HTMLParser factory
+ * function that implements the parser and holds all the parser state
+ * as local variables.  The HTMLParser function is quite big because
+ * it defines many nested functions that use those local variables.
+ *
+ * There are three tightly coupled parser stages: a scanner, a
+ * tokenizer and a tree builder. In a (possibly misguided) attempt at
+ * efficiency, the stages are not implemented as separate classes:
+ * everything shares state and is (mostly) implemented in imperative
+ * (rather than OO) style.
+ *
+ * The stages of the parser work like this: When the client code calls
+ * the parser's parse() method, the specified string is passed to
+ * scanChars(). The scanner loops through that string and passes characters
+ * (sometimes one at a time, sometimes in chunks) to the tokenizer stage.
+ * The tokenizer groups the characters into tokens: tags, endtags, runs
+ * of text, comments, doctype declarations, and the end-of-file (EOF)
+ * token.  These tokens are then passed to the tree building stage via
+ * the insertToken() function.  The tree building stage builds up the
+ * document tree.
+ *
+ * The tokenizer stage is a finite state machine.  Each state is
+ * implemented as a function with a name that ends in "_state".  The
+ * initial state is data_state(). The current tokenizer state is stored
+ * in the variable 'tokenizer'.  Most state functions expect a single
+ * integer argument which represents a single UTF-16 codepoint.  Some
+ * states want more characters and set a lookahead property on
+ * themselves.  The scanChars() function in the scanner checks for this
+ * lookahead property.  If it doesn't exist, then scanChars() just passes
+ * the next input character to the current tokenizer state function.
+ * Otherwise, scanChars() looks ahead (a given # of characters, or for a
+ * matching string, or for a matching regexp) and passes a string of
+ * characters to the current tokenizer state function.
+ *
+ * As a shortcut, certain states of the tokenizer use regular expressions
+ * to look ahead in the scanner's input buffer for runs of text, simple
+ * tags and attributes.  For well-formed input, these shortcuts skip a
+ * lot of state transitions and speed things up a bit.
+ *
+ * When a tokenizer state function has consumed a complete token, it
+ * emits that token, by calling insertToken(), or by calling a utility
+ * function that itself calls insertToken().  These tokens are passed to
+ * the tree building stage, which is also a state machine.  Like the
+ * tokenizer, the tree building states are implemented as functions, and
+ * these functions have names that end with _mode (because the HTML spec
+ * refers to them as insertion modes). The current insertion mode is held
+ * by the 'parser' variable.  Each insertion mode function takes up to 4
+ * arguments.  The first is a token type, represented by the constants
+ * TAG, ENDTAG, TEXT, COMMENT, DOCTYPE and EOF.  The second argument is
+ * the value of the token: the text or comment data, or tagname or
+ * doctype.  For tags, the 3rd argument is an array of attributes.  For
+ * DOCTYPES it is the optional public id.  For tags, the 4th argument is
+ * true if the tag is self-closing. For doctypes, the 4th argument is the
+ * optional system id.
+ *
+ * Search for "***" to find the major sub-divisions in the code.
+ */
+
+
+/***
+ * Data prolog.  Lots of constants declared here, including some
+ * very large objects.  They're used throughout the code that follows
+ */
+// Token types for the tree builder.
+var EOF = -1;
+var TEXT = 1;
+var TAG = 2;
+var ENDTAG = 3;
+var COMMENT = 4;
+var DOCTYPE = 5;
+
+// A re-usable empty array
+var NOATTRS = [];
+
+// These DTD public ids put the browser in quirks mode
+var quirkyPublicIds = /^HTML$|^-\/\/W3O\/\/DTD W3 HTML Strict 3\.0\/\/EN\/\/$|^-\/W3C\/DTD HTML 4\.0 Transitional\/EN$|^\+\/\/Silmaril\/\/dtd html Pro v0r11 19970101\/\/|^-\/\/AdvaSoft Ltd\/\/DTD HTML 3\.0 asWedit \+ extensions\/\/|^-\/\/AS\/\/DTD HTML 3\.0 asWedit \+ extensions\/\/|^-\/\/IETF\/\/DTD HTML 2\.0 Level 1\/\/|^-\/\/IETF\/\/DTD HTML 2\.0 Level 2\/\/|^-\/\/IETF\/\/DTD HTML 2\.0 Strict Level 1\/\/|^-\/\/IETF\/\/DTD HTML 2\.0 Strict Level 2\/\/|^-\/\/IETF\/\/DTD HTML 2\.0 Strict\/\/|^-\/\/IETF\/\/DTD HTML 2\.0\/\/|^-\/\/IETF\/\/DTD HTML 2\.1E\/\/|^-\/\/IETF\/\/DTD HTML 3\.0\/\/|^-\/\/IETF\/\/DTD HTML 3\.2 Final\/\/|^-\/\/IETF\/\/DTD HTML 3\.2\/\/|^-\/\/IETF\/\/DTD HTML 3\/\/|^-\/\/IETF\/\/DTD HTML Level 0\/\/|^-\/\/IETF\/\/DTD HTML Level 1\/\/|^-\/\/IETF\/\/DTD HTML Level 2\/\/|^-\/\/IETF\/\/DTD HTML Level 3\/\/|^-\/\/IETF\/\/DTD HTML Strict Level 0\/\/|^-\/\/IETF\/\/DTD HTML Strict Level 1\/\/|^-\/\/IETF\/\/DTD HTML Strict Level 2\/\/|^-\/\/IETF\/\/DTD HTML Strict Level 3\/\/|^-\/\/IETF\/\/DTD HTML Strict\/\/|^-\/\/IETF\/\/DTD HTML\/\/|^-\/\/Metrius\/\/DTD Metrius Presentational\/\/|^-\/\/Microsoft\/\/DTD Internet Explorer 2\.0 HTML Strict\/\/|^-\/\/Microsoft\/\/DTD Internet Explorer 2\.0 HTML\/\/|^-\/\/Microsoft\/\/DTD Internet Explorer 2\.0 Tables\/\/|^-\/\/Microsoft\/\/DTD Internet Explorer 3\.0 HTML Strict\/\/|^-\/\/Microsoft\/\/DTD Internet Explorer 3\.0 HTML\/\/|^-\/\/Microsoft\/\/DTD Internet Explorer 3\.0 Tables\/\/|^-\/\/Netscape Comm\. Corp\.\/\/DTD HTML\/\/|^-\/\/Netscape Comm\. Corp\.\/\/DTD Strict HTML\/\/|^-\/\/O'Reilly and Associates\/\/DTD HTML 2\.0\/\/|^-\/\/O'Reilly and Associates\/\/DTD HTML Extended 1\.0\/\/|^-\/\/O'Reilly and Associates\/\/DTD HTML Extended Relaxed 1\.0\/\/|^-\/\/SoftQuad Software\/\/DTD HoTMetaL PRO 6\.0::19990601::extensions to HTML 4\.0\/\/|^-\/\/SoftQuad\/\/DTD HoTMetaL PRO 4\.0::19971010::extensions to HTML 4\.0\/\/|^-\/\/Spyglass\/\/DTD HTML 2\.0 Extended\/\/|^-\/\/SQ\/\/DTD HTML 2\.0 HoTMetaL \+ extensions\/\/|^-\/\/Sun Microsystems Corp\.\/\/DTD HotJava HTML\/\/|^-\/\/Sun Microsystems Corp\.\/\/DTD HotJava Strict HTML\/\/|^-\/\/W3C\/\/DTD HTML 3 1995-03-24\/\/|^-\/\/W3C\/\/DTD HTML 3\.2 Draft\/\/|^-\/\/W3C\/\/DTD HTML 3\.2 Final\/\/|^-\/\/W3C\/\/DTD HTML 3\.2\/\/|^-\/\/W3C\/\/DTD HTML 3\.2S Draft\/\/|^-\/\/W3C\/\/DTD HTML 4\.0 Frameset\/\/|^-\/\/W3C\/\/DTD HTML 4\.0 Transitional\/\/|^-\/\/W3C\/\/DTD HTML Experimental 19960712\/\/|^-\/\/W3C\/\/DTD HTML Experimental 970421\/\/|^-\/\/W3C\/\/DTD W3 HTML\/\/|^-\/\/W3O\/\/DTD W3 HTML 3\.0\/\/|^-\/\/WebTechs\/\/DTD Mozilla HTML 2\.0\/\/|^-\/\/WebTechs\/\/DTD Mozilla HTML\/\//i;
+
+var quirkySystemId = "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd";
+
+var conditionallyQuirkyPublicIds = /^-\/\/W3C\/\/DTD HTML 4\.01 Frameset\/\/|^-\/\/W3C\/\/DTD HTML 4\.01 Transitional\/\//i;
+
+// These DTD public ids put the browser in limited quirks mode
+var limitedQuirkyPublicIds = /^-\/\/W3C\/\/DTD XHTML 1\.0 Frameset\/\/|^-\/\/W3C\/\/DTD XHTML 1\.0 Transitional\/\//i;
+
+
+// Element sets below. See the isA() function for a way to test
+// whether an element is a member of a set
+var specialSet = Object.create(null);
+specialSet[NAMESPACE.HTML] = {
+  __proto__: null,
+  "address":true, "applet":true, "area":true, "article":true,
+  "aside":true, "base":true, "basefont":true, "bgsound":true,
+  "blockquote":true, "body":true, "br":true, "button":true,
+  "caption":true, "center":true, "col":true, "colgroup":true,
+  "dd":true, "details":true, "dir":true,
+  "div":true, "dl":true, "dt":true, "embed":true,
+  "fieldset":true, "figcaption":true, "figure":true, "footer":true,
+  "form":true, "frame":true, "frameset":true, "h1":true,
+  "h2":true, "h3":true, "h4":true, "h5":true,
+  "h6":true, "head":true, "header":true, "hgroup":true,
+  "hr":true, "html":true, "iframe":true, "img":true,
+  "input":true, "li":true, "link":true,
+  "listing":true, "main":true, "marquee":true, "menu":true, "meta":true,
+  "nav":true, "noembed":true, "noframes":true, "noscript":true,
+  "object":true, "ol":true, "p":true, "param":true,
+  "plaintext":true, "pre":true, "script":true, "section":true,
+  "select":true, "source":true, "style":true, "summary":true, "table":true,
+  "tbody":true, "td":true, "template":true, "textarea":true, "tfoot":true,
+  "th":true, "thead":true, "title":true, "tr":true, "track":true,
+  // Note that "xmp" was removed from the "special" set in the latest
+  // spec, apparently by accident; see
+  // https://github.com/whatwg/html/pull/1919
+  "ul":true, "wbr":true, "xmp":true
+};
+specialSet[NAMESPACE.SVG] = {
+  __proto__: null,
+  "foreignObject": true, "desc": true, "title": true
+};
+specialSet[NAMESPACE.MATHML] = {
+  __proto__: null,
+  "mi":true, "mo":true, "mn":true, "ms":true,
+  "mtext":true, "annotation-xml":true
+};
+
+// The set of address, div, and p HTML tags
+var addressdivpSet = Object.create(null);
+addressdivpSet[NAMESPACE.HTML] = {
+  __proto__: null,
+  "address":true, "div":true, "p":true
+};
+
+var dddtSet = Object.create(null);
+dddtSet[NAMESPACE.HTML] = {
+  __proto__: null,
+  "dd":true, "dt":true
+};
+
+var tablesectionrowSet = Object.create(null);
+tablesectionrowSet[NAMESPACE.HTML] = {
+  __proto__: null,
+  "table":true, "thead":true, "tbody":true, "tfoot":true, "tr":true
+};
+
+var impliedEndTagsSet = Object.create(null);
+impliedEndTagsSet[NAMESPACE.HTML] = {
+  __proto__: null,
+  "dd": true, "dt": true, "li": true, "menuitem": true, "optgroup": true,
+  "option": true, "p": true, "rb": true, "rp": true, "rt": true, "rtc": true
+};
+
+var thoroughImpliedEndTagsSet = Object.create(null);
+thoroughImpliedEndTagsSet[NAMESPACE.HTML] = {
+  __proto__: null,
+  "caption": true, "colgroup": true, "dd": true, "dt": true, "li": true,
+  "optgroup": true, "option": true, "p": true, "rb": true, "rp": true,
+  "rt": true, "rtc": true, "tbody": true, "td": true, "tfoot": true,
+  "th": true, "thead": true, "tr": true
+};
+
+var tableContextSet = Object.create(null);
+tableContextSet[NAMESPACE.HTML] = {
+  __proto__: null,
+  "table": true, "template": true, "html": true
+};
+
+var tableBodyContextSet = Object.create(null);
+tableBodyContextSet[NAMESPACE.HTML] = {
+  __proto__: null,
+  "tbody": true, "tfoot": true, "thead": true, "template": true, "html": true
+};
+
+var tableRowContextSet = Object.create(null);
+tableRowContextSet[NAMESPACE.HTML] = {
+  __proto__: null,
+  "tr": true, "template": true, "html": true
+};
+
+// See http://www.w3.org/TR/html5/forms.html#form-associated-element
+var formassociatedSet = Object.create(null);
+formassociatedSet[NAMESPACE.HTML] = {
+  __proto__: null,
+  "button": true, "fieldset": true, "input": true, "keygen": true,
+  "object": true, "output": true, "select": true, "textarea": true,
+  "img": true
+};
+
+var inScopeSet = Object.create(null);
+inScopeSet[NAMESPACE.HTML]= {
+  __proto__: null,
+  "applet":true, "caption":true, "html":true, "table":true,
+  "td":true, "th":true, "marquee":true, "object":true,
+  "template":true
+};
+inScopeSet[NAMESPACE.MATHML] = {
+  __proto__: null,
+  "mi":true, "mo":true, "mn":true, "ms":true,
+  "mtext":true, "annotation-xml":true
+};
+inScopeSet[NAMESPACE.SVG] = {
+  __proto__: null,
+  "foreignObject":true, "desc":true, "title":true
+};
+
+var inListItemScopeSet = Object.create(inScopeSet);
+inListItemScopeSet[NAMESPACE.HTML] =
+  Object.create(inScopeSet[NAMESPACE.HTML]);
+inListItemScopeSet[NAMESPACE.HTML].ol = true;
+inListItemScopeSet[NAMESPACE.HTML].ul = true;
+
+var inButtonScopeSet = Object.create(inScopeSet);
+inButtonScopeSet[NAMESPACE.HTML] =
+  Object.create(inScopeSet[NAMESPACE.HTML]);
+inButtonScopeSet[NAMESPACE.HTML].button = true;
+
+var inTableScopeSet = Object.create(null);
+inTableScopeSet[NAMESPACE.HTML] = {
+  __proto__: null,
+  "html":true, "table":true, "template":true
+};
+
+// The set of elements for select scope is the everything *except* these
+var invertedSelectScopeSet = Object.create(null);
+invertedSelectScopeSet[NAMESPACE.HTML] = {
+  __proto__: null,
+  "optgroup":true, "option":true
+};
+
+var mathmlTextIntegrationPointSet = Object.create(null);
+mathmlTextIntegrationPointSet[NAMESPACE.MATHML] = {
+  __proto__: null,
+  mi: true,
+  mo: true,
+  mn: true,
+  ms: true,
+  mtext: true
+};
+
+var htmlIntegrationPointSet = Object.create(null);
+htmlIntegrationPointSet[NAMESPACE.SVG] = {
+  __proto__: null,
+  foreignObject: true,
+  desc: true,
+  title: true
+};
+
+var foreignAttributes = {
+  __proto__: null,
+  "xlink:actuate": NAMESPACE.XLINK, "xlink:arcrole": NAMESPACE.XLINK,
+  "xlink:href":   NAMESPACE.XLINK,  "xlink:role":    NAMESPACE.XLINK,
+  "xlink:show":   NAMESPACE.XLINK,  "xlink:title":   NAMESPACE.XLINK,
+  "xlink:type":   NAMESPACE.XLINK,  "xml:base":      NAMESPACE.XML,
+  "xml:lang":     NAMESPACE.XML,    "xml:space":     NAMESPACE.XML,
+  "xmlns":        NAMESPACE.XMLNS,  "xmlns:xlink":   NAMESPACE.XMLNS
+};
+
+
+// Lowercase to mixed case mapping for SVG attributes and tagnames
+var svgAttrAdjustments = {
+  __proto__: null,
+  attributename: "attributeName", attributetype: "attributeType",
+  basefrequency: "baseFrequency", baseprofile: "baseProfile",
+  calcmode: "calcMode", clippathunits: "clipPathUnits",
+  diffuseconstant: "diffuseConstant",
+  edgemode: "edgeMode",
+  filterunits: "filterUnits",
+  glyphref: "glyphRef", gradienttransform: "gradientTransform",
+  gradientunits: "gradientUnits", kernelmatrix: "kernelMatrix",
+  kernelunitlength: "kernelUnitLength", keypoints: "keyPoints",
+  keysplines: "keySplines", keytimes: "keyTimes",
+  lengthadjust: "lengthAdjust", limitingconeangle: "limitingConeAngle",
+  markerheight: "markerHeight", markerunits: "markerUnits",
+  markerwidth: "markerWidth", maskcontentunits: "maskContentUnits",
+  maskunits: "maskUnits", numoctaves: "numOctaves",
+  pathlength: "pathLength", patterncontentunits: "patternContentUnits",
+  patterntransform: "patternTransform", patternunits: "patternUnits",
+  pointsatx: "pointsAtX", pointsaty: "pointsAtY",
+  pointsatz: "pointsAtZ", preservealpha: "preserveAlpha",
+  preserveaspectratio: "preserveAspectRatio",
+  primitiveunits: "primitiveUnits", refx: "refX",
+  refy: "refY", repeatcount: "repeatCount",
+  repeatdur: "repeatDur", requiredextensions: "requiredExtensions",
+  requiredfeatures: "requiredFeatures",
+  specularconstant: "specularConstant",
+  specularexponent: "specularExponent", spreadmethod: "spreadMethod",
+  startoffset: "startOffset", stddeviation: "stdDeviation",
+  stitchtiles: "stitchTiles", surfacescale: "surfaceScale",
+  systemlanguage: "systemLanguage", tablevalues: "tableValues",
+  targetx: "targetX", targety: "targetY",
+  textlength: "textLength", viewbox: "viewBox",
+  viewtarget: "viewTarget", xchannelselector: "xChannelSelector",
+  ychannelselector: "yChannelSelector", zoomandpan: "zoomAndPan"
+};
+
+var svgTagNameAdjustments = {
+  __proto__: null,
+  altglyph: "altGlyph", altglyphdef: "altGlyphDef",
+  altglyphitem: "altGlyphItem", animatecolor: "animateColor",
+  animatemotion: "animateMotion", animatetransform: "animateTransform",
+  clippath: "clipPath", feblend: "feBlend",
+  fecolormatrix: "feColorMatrix",
+  fecomponenttransfer: "feComponentTransfer", fecomposite: "feComposite",
+  feconvolvematrix: "feConvolveMatrix",
+  fediffuselighting: "feDiffuseLighting",
+  fedisplacementmap: "feDisplacementMap",
+  fedistantlight: "feDistantLight", feflood: "feFlood",
+  fefunca: "feFuncA", fefuncb: "feFuncB",
+  fefuncg: "feFuncG", fefuncr: "feFuncR",
+  fegaussianblur: "feGaussianBlur", feimage: "feImage",
+  femerge: "feMerge", femergenode: "feMergeNode",
+  femorphology: "feMorphology", feoffset: "feOffset",
+  fepointlight: "fePointLight", fespecularlighting: "feSpecularLighting",
+  fespotlight: "feSpotLight", fetile: "feTile",
+  feturbulence: "feTurbulence", foreignobject: "foreignObject",
+  glyphref: "glyphRef", lineargradient: "linearGradient",
+  radialgradient: "radialGradient", textpath: "textPath"
+};
+
+
+// Data for parsing numeric and named character references
+// These next 3 objects are direct translations of tables
+// in the HTML spec into JavaScript object format
+var numericCharRefReplacements = {
+  __proto__: null,
+  0x00:0xFFFD, 0x80:0x20AC, 0x82:0x201A, 0x83:0x0192, 0x84:0x201E,
+  0x85:0x2026, 0x86:0x2020, 0x87:0x2021, 0x88:0x02C6, 0x89:0x2030,
+  0x8A:0x0160, 0x8B:0x2039, 0x8C:0x0152, 0x8E:0x017D, 0x91:0x2018,
+  0x92:0x2019, 0x93:0x201C, 0x94:0x201D, 0x95:0x2022, 0x96:0x2013,
+  0x97:0x2014, 0x98:0x02DC, 0x99:0x2122, 0x9A:0x0161, 0x9B:0x203A,
+  0x9C:0x0153, 0x9E:0x017E, 0x9F:0x0178
+};
+
+/*
+ * This table is generated with test/tools/update-entities.js
+ */
+var namedCharRefs = {
+  __proto__: null,
+  "AElig":0xc6, "AElig;":0xc6,
+  "AMP":0x26, "AMP;":0x26,
+  "Aacute":0xc1, "Aacute;":0xc1,
+  "Abreve;":0x102, "Acirc":0xc2,
+  "Acirc;":0xc2, "Acy;":0x410,
+  "Afr;":[0xd835,0xdd04], "Agrave":0xc0,
+  "Agrave;":0xc0, "Alpha;":0x391,
+  "Amacr;":0x100, "And;":0x2a53,
+  "Aogon;":0x104, "Aopf;":[0xd835,0xdd38],
+  "ApplyFunction;":0x2061, "Aring":0xc5,
+  "Aring;":0xc5, "Ascr;":[0xd835,0xdc9c],
+  "Assign;":0x2254, "Atilde":0xc3,
+  "Atilde;":0xc3, "Auml":0xc4,
+  "Auml;":0xc4, "Backslash;":0x2216,
+  "Barv;":0x2ae7, "Barwed;":0x2306,
+  "Bcy;":0x411, "Because;":0x2235,
+  "Bernoullis;":0x212c, "Beta;":0x392,
+  "Bfr;":[0xd835,0xdd05], "Bopf;":[0xd835,0xdd39],
+  "Breve;":0x2d8, "Bscr;":0x212c,
+  "Bumpeq;":0x224e, "CHcy;":0x427,
+  "COPY":0xa9, "COPY;":0xa9,
+  "Cacute;":0x106, "Cap;":0x22d2,
+  "CapitalDifferentialD;":0x2145, "Cayleys;":0x212d,
+  "Ccaron;":0x10c, "Ccedil":0xc7,
+  "Ccedil;":0xc7, "Ccirc;":0x108,
+  "Cconint;":0x2230, "Cdot;":0x10a,
+  "Cedilla;":0xb8, "CenterDot;":0xb7,
+  "Cfr;":0x212d, "Chi;":0x3a7,
+  "CircleDot;":0x2299, "CircleMinus;":0x2296,
+  "CirclePlus;":0x2295, "CircleTimes;":0x2297,
+  "ClockwiseContourIntegral;":0x2232, "CloseCurlyDoubleQuote;":0x201d,
+  "CloseCurlyQuote;":0x2019, "Colon;":0x2237,
+  "Colone;":0x2a74, "Congruent;":0x2261,
+  "Conint;":0x222f, "ContourIntegral;":0x222e,
+  "Copf;":0x2102, "Coproduct;":0x2210,
+  "CounterClockwiseContourIntegral;":0x2233, "Cross;":0x2a2f,
+  "Cscr;":[0xd835,0xdc9e], "Cup;":0x22d3,
+  "CupCap;":0x224d, "DD;":0x2145,
+  "DDotrahd;":0x2911, "DJcy;":0x402,
+  "DScy;":0x405, "DZcy;":0x40f,
+  "Dagger;":0x2021, "Darr;":0x21a1,
+  "Dashv;":0x2ae4, "Dcaron;":0x10e,
+  "Dcy;":0x414, "Del;":0x2207,
+  "Delta;":0x394, "Dfr;":[0xd835,0xdd07],
+  "DiacriticalAcute;":0xb4, "DiacriticalDot;":0x2d9,
+  "DiacriticalDoubleAcute;":0x2dd, "DiacriticalGrave;":0x60,
+  "DiacriticalTilde;":0x2dc, "Diamond;":0x22c4,
+  "DifferentialD;":0x2146, "Dopf;":[0xd835,0xdd3b],
+  "Dot;":0xa8, "DotDot;":0x20dc,
+  "DotEqual;":0x2250, "DoubleContourIntegral;":0x222f,
+  "DoubleDot;":0xa8, "DoubleDownArrow;":0x21d3,
+  "DoubleLeftArrow;":0x21d0, "DoubleLeftRightArrow;":0x21d4,
+  "DoubleLeftTee;":0x2ae4, "DoubleLongLeftArrow;":0x27f8,
+  "DoubleLongLeftRightArrow;":0x27fa, "DoubleLongRightArrow;":0x27f9,
+  "DoubleRightArrow;":0x21d2, "DoubleRightTee;":0x22a8,
+  "DoubleUpArrow;":0x21d1, "DoubleUpDownArrow;":0x21d5,
+  "DoubleVerticalBar;":0x2225, "DownArrow;":0x2193,
+  "DownArrowBar;":0x2913, "DownArrowUpArrow;":0x21f5,
+  "DownBreve;":0x311, "DownLeftRightVector;":0x2950,
+  "DownLeftTeeVector;":0x295e, "DownLeftVector;":0x21bd,
+  "DownLeftVectorBar;":0x2956, "DownRightTeeVector;":0x295f,
+  "DownRightVector;":0x21c1, "DownRightVectorBar;":0x2957,
+  "DownTee;":0x22a4, "DownTeeArrow;":0x21a7,
+  "Downarrow;":0x21d3, "Dscr;":[0xd835,0xdc9f],
+  "Dstrok;":0x110, "ENG;":0x14a,
+  "ETH":0xd0, "ETH;":0xd0,
+  "Eacute":0xc9, "Eacute;":0xc9,
+  "Ecaron;":0x11a, "Ecirc":0xca,
+  "Ecirc;":0xca, "Ecy;":0x42d,
+  "Edot;":0x116, "Efr;":[0xd835,0xdd08],
+  "Egrave":0xc8, "Egrave;":0xc8,
+  "Element;":0x2208, "Emacr;":0x112,
+  "EmptySmallSquare;":0x25fb, "EmptyVerySmallSquare;":0x25ab,
+  "Eogon;":0x118, "Eopf;":[0xd835,0xdd3c],
+  "Epsilon;":0x395, "Equal;":0x2a75,
+  "EqualTilde;":0x2242, "Equilibrium;":0x21cc,
+  "Escr;":0x2130, "Esim;":0x2a73,
+  "Eta;":0x397, "Euml":0xcb,
+  "Euml;":0xcb, "Exists;":0x2203,
+  "ExponentialE;":0x2147, "Fcy;":0x424,
+  "Ffr;":[0xd835,0xdd09], "FilledSmallSquare;":0x25fc,
+  "FilledVerySmallSquare;":0x25aa, "Fopf;":[0xd835,0xdd3d],
+  "ForAll;":0x2200, "Fouriertrf;":0x2131,
+  "Fscr;":0x2131, "GJcy;":0x403,
+  "GT":0x3e, "GT;":0x3e,
+  "Gamma;":0x393, "Gammad;":0x3dc,
+  "Gbreve;":0x11e, "Gcedil;":0x122,
+  "Gcirc;":0x11c, "Gcy;":0x413,
+  "Gdot;":0x120, "Gfr;":[0xd835,0xdd0a],
+  "Gg;":0x22d9, "Gopf;":[0xd835,0xdd3e],
+  "GreaterEqual;":0x2265, "GreaterEqualLess;":0x22db,
+  "GreaterFullEqual;":0x2267, "GreaterGreater;":0x2aa2,
+  "GreaterLess;":0x2277, "GreaterSlantEqual;":0x2a7e,
+  "GreaterTilde;":0x2273, "Gscr;":[0xd835,0xdca2],
+  "Gt;":0x226b, "HARDcy;":0x42a,
+  "Hacek;":0x2c7, "Hat;":0x5e,
+  "Hcirc;":0x124, "Hfr;":0x210c,
+  "HilbertSpace;":0x210b, "Hopf;":0x210d,
+  "HorizontalLine;":0x2500, "Hscr;":0x210b,
+  "Hstrok;":0x126, "HumpDownHump;":0x224e,
+  "HumpEqual;":0x224f, "IEcy;":0x415,
+  "IJlig;":0x132, "IOcy;":0x401,
+  "Iacute":0xcd, "Iacute;":0xcd,
+  "Icirc":0xce, "Icirc;":0xce,
+  "Icy;":0x418, "Idot;":0x130,
+  "Ifr;":0x2111, "Igrave":0xcc,
+  "Igrave;":0xcc, "Im;":0x2111,
+  "Imacr;":0x12a, "ImaginaryI;":0x2148,
+  "Implies;":0x21d2, "Int;":0x222c,
+  "Integral;":0x222b, "Intersection;":0x22c2,
+  "InvisibleComma;":0x2063, "InvisibleTimes;":0x2062,
+  "Iogon;":0x12e, "Iopf;":[0xd835,0xdd40],
+  "Iota;":0x399, "Iscr;":0x2110,
+  "Itilde;":0x128, "Iukcy;":0x406,
+  "Iuml":0xcf, "Iuml;":0xcf,
+  "Jcirc;":0x134, "Jcy;":0x419,
+  "Jfr;":[0xd835,0xdd0d], "Jopf;":[0xd835,0xdd41],
+  "Jscr;":[0xd835,0xdca5], "Jsercy;":0x408,
+  "Jukcy;":0x404, "KHcy;":0x425,
+  "KJcy;":0x40c, "Kappa;":0x39a,
+  "Kcedil;":0x136, "Kcy;":0x41a,
+  "Kfr;":[0xd835,0xdd0e], "Kopf;":[0xd835,0xdd42],
+  "Kscr;":[0xd835,0xdca6], "LJcy;":0x409,
+  "LT":0x3c, "LT;":0x3c,
+  "Lacute;":0x139, "Lambda;":0x39b,
+  "Lang;":0x27ea, "Laplacetrf;":0x2112,
+  "Larr;":0x219e, "Lcaron;":0x13d,
+  "Lcedil;":0x13b, "Lcy;":0x41b,
+  "LeftAngleBracket;":0x27e8, "LeftArrow;":0x2190,
+  "LeftArrowBar;":0x21e4, "LeftArrowRightArrow;":0x21c6,
+  "LeftCeiling;":0x2308, "LeftDoubleBracket;":0x27e6,
+  "LeftDownTeeVector;":0x2961, "LeftDownVector;":0x21c3,
+  "LeftDownVectorBar;":0x2959, "LeftFloor;":0x230a,
+  "LeftRightArrow;":0x2194, "LeftRightVector;":0x294e,
+  "LeftTee;":0x22a3, "LeftTeeArrow;":0x21a4,
+  "LeftTeeVector;":0x295a, "LeftTriangle;":0x22b2,
+  "LeftTriangleBar;":0x29cf, "LeftTriangleEqual;":0x22b4,
+  "LeftUpDownVector;":0x2951, "LeftUpTeeVector;":0x2960,
+  "LeftUpVector;":0x21bf, "LeftUpVectorBar;":0x2958,
+  "LeftVector;":0x21bc, "LeftVectorBar;":0x2952,
+  "Leftarrow;":0x21d0, "Leftrightarrow;":0x21d4,
+  "LessEqualGreater;":0x22da, "LessFullEqual;":0x2266,
+  "LessGreater;":0x2276, "LessLess;":0x2aa1,
+  "LessSlantEqual;":0x2a7d, "LessTilde;":0x2272,
+  "Lfr;":[0xd835,0xdd0f], "Ll;":0x22d8,
+  "Lleftarrow;":0x21da, "Lmidot;":0x13f,
+  "LongLeftArrow;":0x27f5, "LongLeftRightArrow;":0x27f7,
+  "LongRightArrow;":0x27f6, "Longleftarrow;":0x27f8,
+  "Longleftrightarrow;":0x27fa, "Longrightarrow;":0x27f9,
+  "Lopf;":[0xd835,0xdd43], "LowerLeftArrow;":0x2199,
+  "LowerRightArrow;":0x2198, "Lscr;":0x2112,
+  "Lsh;":0x21b0, "Lstrok;":0x141,
+  "Lt;":0x226a, "Map;":0x2905,
+  "Mcy;":0x41c, "MediumSpace;":0x205f,
+  "Mellintrf;":0x2133, "Mfr;":[0xd835,0xdd10],
+  "MinusPlus;":0x2213, "Mopf;":[0xd835,0xdd44],
+  "Mscr;":0x2133, "Mu;":0x39c,
+  "NJcy;":0x40a, "Nacute;":0x143,
+  "Ncaron;":0x147, "Ncedil;":0x145,
+  "Ncy;":0x41d, "NegativeMediumSpace;":0x200b,
+  "NegativeThickSpace;":0x200b, "NegativeThinSpace;":0x200b,
+  "NegativeVeryThinSpace;":0x200b, "NestedGreaterGreater;":0x226b,
+  "NestedLessLess;":0x226a, "NewLine;":0xa,
+  "Nfr;":[0xd835,0xdd11], "NoBreak;":0x2060,
+  "NonBreakingSpace;":0xa0, "Nopf;":0x2115,
+  "Not;":0x2aec, "NotCongruent;":0x2262,
+  "NotCupCap;":0x226d, "NotDoubleVerticalBar;":0x2226,
+  "NotElement;":0x2209, "NotEqual;":0x2260,
+  "NotEqualTilde;":[0x2242,0x338], "NotExists;":0x2204,
+  "NotGreater;":0x226f, "NotGreaterEqual;":0x2271,
+  "NotGreaterFullEqual;":[0x2267,0x338], "NotGreaterGreater;":[0x226b,0x338],
+  "NotGreaterLess;":0x2279, "NotGreaterSlantEqual;":[0x2a7e,0x338],
+  "NotGreaterTilde;":0x2275, "NotHumpDownHump;":[0x224e,0x338],
+  "NotHumpEqual;":[0x224f,0x338], "NotLeftTriangle;":0x22ea,
+  "NotLeftTriangleBar;":[0x29cf,0x338], "NotLeftTriangleEqual;":0x22ec,
+  "NotLess;":0x226e, "NotLessEqual;":0x2270,
+  "NotLessGreater;":0x2278, "NotLessLess;":[0x226a,0x338],
+  "NotLessSlantEqual;":[0x2a7d,0x338], "NotLessTilde;":0x2274,
+  "NotNestedGreaterGreater;":[0x2aa2,0x338], "NotNestedLessLess;":[0x2aa1,0x338],
+  "NotPrecedes;":0x2280, "NotPrecedesEqual;":[0x2aaf,0x338],
+  "NotPrecedesSlantEqual;":0x22e0, "NotReverseElement;":0x220c,
+  "NotRightTriangle;":0x22eb, "NotRightTriangleBar;":[0x29d0,0x338],
+  "NotRightTriangleEqual;":0x22ed, "NotSquareSubset;":[0x228f,0x338],
+  "NotSquareSubsetEqual;":0x22e2, "NotSquareSuperset;":[0x2290,0x338],
+  "NotSquareSupersetEqual;":0x22e3, "NotSubset;":[0x2282,0x20d2],
+  "NotSubsetEqual;":0x2288, "NotSucceeds;":0x2281,
+  "NotSucceedsEqual;":[0x2ab0,0x338], "NotSucceedsSlantEqual;":0x22e1,
+  "NotSucceedsTilde;":[0x227f,0x338], "NotSuperset;":[0x2283,0x20d2],
+  "NotSupersetEqual;":0x2289, "NotTilde;":0x2241,
+  "NotTildeEqual;":0x2244, "NotTildeFullEqual;":0x2247,
+  "NotTildeTilde;":0x2249, "NotVerticalBar;":0x2224,
+  "Nscr;":[0xd835,0xdca9], "Ntilde":0xd1,
+  "Ntilde;":0xd1, "Nu;":0x39d,
+  "OElig;":0x152, "Oacute":0xd3,
+  "Oacute;":0xd3, "Ocirc":0xd4,
+  "Ocirc;":0xd4, "Ocy;":0x41e,
+  "Odblac;":0x150, "Ofr;":[0xd835,0xdd12],
+  "Ograve":0xd2, "Ograve;":0xd2,
+  "Omacr;":0x14c, "Omega;":0x3a9,
+  "Omicron;":0x39f, "Oopf;":[0xd835,0xdd46],
+  "OpenCurlyDoubleQuote;":0x201c, "OpenCurlyQuote;":0x2018,
+  "Or;":0x2a54, "Oscr;":[0xd835,0xdcaa],
+  "Oslash":0xd8, "Oslash;":0xd8,
+  "Otilde":0xd5, "Otilde;":0xd5,
+  "Otimes;":0x2a37, "Ouml":0xd6,
+  "Ouml;":0xd6, "OverBar;":0x203e,
+  "OverBrace;":0x23de, "OverBracket;":0x23b4,
+  "OverParenthesis;":0x23dc, "PartialD;":0x2202,
+  "Pcy;":0x41f, "Pfr;":[0xd835,0xdd13],
+  "Phi;":0x3a6, "Pi;":0x3a0,
+  "PlusMinus;":0xb1, "Poincareplane;":0x210c,
+  "Popf;":0x2119, "Pr;":0x2abb,
+  "Precedes;":0x227a, "PrecedesEqual;":0x2aaf,
+  "PrecedesSlantEqual;":0x227c, "PrecedesTilde;":0x227e,
+  "Prime;":0x2033, "Product;":0x220f,
+  "Proportion;":0x2237, "Proportional;":0x221d,
+  "Pscr;":[0xd835,0xdcab], "Psi;":0x3a8,
+  "QUOT":0x22, "QUOT;":0x22,
+  "Qfr;":[0xd835,0xdd14], "Qopf;":0x211a,
+  "Qscr;":[0xd835,0xdcac], "RBarr;":0x2910,
+  "REG":0xae, "REG;":0xae,
+  "Racute;":0x154, "Rang;":0x27eb,
+  "Rarr;":0x21a0, "Rarrtl;":0x2916,
+  "Rcaron;":0x158, "Rcedil;":0x156,
+  "Rcy;":0x420, "Re;":0x211c,
+  "ReverseElement;":0x220b, "ReverseEquilibrium;":0x21cb,
+  "ReverseUpEquilibrium;":0x296f, "Rfr;":0x211c,
+  "Rho;":0x3a1, "RightAngleBracket;":0x27e9,
+  "RightArrow;":0x2192, "RightArrowBar;":0x21e5,
+  "RightArrowLeftArrow;":0x21c4, "RightCeiling;":0x2309,
+  "RightDoubleBracket;":0x27e7, "RightDownTeeVector;":0x295d,
+  "RightDownVector;":0x21c2, "RightDownVectorBar;":0x2955,
+  "RightFloor;":0x230b, "RightTee;":0x22a2,
+  "RightTeeArrow;":0x21a6, "RightTeeVector;":0x295b,
+  "RightTriangle;":0x22b3, "RightTriangleBar;":0x29d0,
+  "RightTriangleEqual;":0x22b5, "RightUpDownVector;":0x294f,
+  "RightUpTeeVector;":0x295c, "RightUpVector;":0x21be,
+  "RightUpVectorBar;":0x2954, "RightVector;":0x21c0,
+  "RightVectorBar;":0x2953, "Rightarrow;":0x21d2,
+  "Ropf;":0x211d, "RoundImplies;":0x2970,
+  "Rrightarrow;":0x21db, "Rscr;":0x211b,
+  "Rsh;":0x21b1, "RuleDelayed;":0x29f4,
+  "SHCHcy;":0x429, "SHcy;":0x428,
+  "SOFTcy;":0x42c, "Sacute;":0x15a,
+  "Sc;":0x2abc, "Scaron;":0x160,
+  "Scedil;":0x15e, "Scirc;":0x15c,
+  "Scy;":0x421, "Sfr;":[0xd835,0xdd16],
+  "ShortDownArrow;":0x2193, "ShortLeftArrow;":0x2190,
+  "ShortRightArrow;":0x2192, "ShortUpArrow;":0x2191,
+  "Sigma;":0x3a3, "SmallCircle;":0x2218,
+  "Sopf;":[0xd835,0xdd4a], "Sqrt;":0x221a,
+  "Square;":0x25a1, "SquareIntersection;":0x2293,
+  "SquareSubset;":0x228f, "SquareSubsetEqual;":0x2291,
+  "SquareSuperset;":0x2290, "SquareSupersetEqual;":0x2292,
+  "SquareUnion;":0x2294, "Sscr;":[0xd835,0xdcae],
+  "Star;":0x22c6, "Sub;":0x22d0,
+  "Subset;":0x22d0, "SubsetEqual;":0x2286,
+  "Succeeds;":0x227b, "SucceedsEqual;":0x2ab0,
+  "SucceedsSlantEqual;":0x227d, "SucceedsTilde;":0x227f,
+  "SuchThat;":0x220b, "Sum;":0x2211,
+  "Sup;":0x22d1, "Superset;":0x2283,
+  "SupersetEqual;":0x2287, "Supset;":0x22d1,
+  "THORN":0xde, "THORN;":0xde,
+  "TRADE;":0x2122, "TSHcy;":0x40b,
+  "TScy;":0x426, "Tab;":0x9,
+  "Tau;":0x3a4, "Tcaron;":0x164,
+  "Tcedil;":0x162, "Tcy;":0x422,
+  "Tfr;":[0xd835,0xdd17], "Therefore;":0x2234,
+  "Theta;":0x398, "ThickSpace;":[0x205f,0x200a],
+  "ThinSpace;":0x2009, "Tilde;":0x223c,
+  "TildeEqual;":0x2243, "TildeFullEqual;":0x2245,
+  "TildeTilde;":0x2248, "Topf;":[0xd835,0xdd4b],
+  "TripleDot;":0x20db, "Tscr;":[0xd835,0xdcaf],
+  "Tstrok;":0x166, "Uacute":0xda,
+  "Uacute;":0xda, "Uarr;":0x219f,
+  "Uarrocir;":0x2949, "Ubrcy;":0x40e,
+  "Ubreve;":0x16c, "Ucirc":0xdb,
+  "Ucirc;":0xdb, "Ucy;":0x423,
+  "Udblac;":0x170, "Ufr;":[0xd835,0xdd18],
+  "Ugrave":0xd9, "Ugrave;":0xd9,
+  "Umacr;":0x16a, "UnderBar;":0x5f,
+  "UnderBrace;":0x23df, "UnderBracket;":0x23b5,
+  "UnderParenthesis;":0x23dd, "Union;":0x22c3,
+  "UnionPlus;":0x228e, "Uogon;":0x172,
+  "Uopf;":[0xd835,0xdd4c], "UpArrow;":0x2191,
+  "UpArrowBar;":0x2912, "UpArrowDownArrow;":0x21c5,
+  "UpDownArrow;":0x2195, "UpEquilibrium;":0x296e,
+  "UpTee;":0x22a5, "UpTeeArrow;":0x21a5,
+  "Uparrow;":0x21d1, "Updownarrow;":0x21d5,
+  "UpperLeftArrow;":0x2196, "UpperRightArrow;":0x2197,
+  "Upsi;":0x3d2, "Upsilon;":0x3a5,
+  "Uring;":0x16e, "Uscr;":[0xd835,0xdcb0],
+  "Utilde;":0x168, "Uuml":0xdc,
+  "Uuml;":0xdc, "VDash;":0x22ab,
+  "Vbar;":0x2aeb, "Vcy;":0x412,
+  "Vdash;":0x22a9, "Vdashl;":0x2ae6,
+  "Vee;":0x22c1, "Verbar;":0x2016,
+  "Vert;":0x2016, "VerticalBar;":0x2223,
+  "VerticalLine;":0x7c, "VerticalSeparator;":0x2758,
+  "VerticalTilde;":0x2240, "VeryThinSpace;":0x200a,
+  "Vfr;":[0xd835,0xdd19], "Vopf;":[0xd835,0xdd4d],
+  "Vscr;":[0xd835,0xdcb1], "Vvdash;":0x22aa,
+  "Wcirc;":0x174, "Wedge;":0x22c0,
+  "Wfr;":[0xd835,0xdd1a], "Wopf;":[0xd835,0xdd4e],
+  "Wscr;":[0xd835,0xdcb2], "Xfr;":[0xd835,0xdd1b],
+  "Xi;":0x39e, "Xopf;":[0xd835,0xdd4f],
+  "Xscr;":[0xd835,0xdcb3], "YAcy;":0x42f,
+  "YIcy;":0x407, "YUcy;":0x42e,
+  "Yacute":0xdd, "Yacute;":0xdd,
+  "Ycirc;":0x176, "Ycy;":0x42b,
+  "Yfr;":[0xd835,0xdd1c], "Yopf;":[0xd835,0xdd50],
+  "Yscr;":[0xd835,0xdcb4], "Yuml;":0x178,
+  "ZHcy;":0x416, "Zacute;":0x179,
+  "Zcaron;":0x17d, "Zcy;":0x417,
+  "Zdot;":0x17b, "ZeroWidthSpace;":0x200b,
+  "Zeta;":0x396, "Zfr;":0x2128,
+  "Zopf;":0x2124, "Zscr;":[0xd835,0xdcb5],
+  "aacute":0xe1, "aacute;":0xe1,
+  "abreve;":0x103, "ac;":0x223e,
+  "acE;":[0x223e,0x333], "acd;":0x223f,
+  "acirc":0xe2, "acirc;":0xe2,
+  "acute":0xb4, "acute;":0xb4,
+  "acy;":0x430, "aelig":0xe6,
+  "aelig;":0xe6, "af;":0x2061,
+  "afr;":[0xd835,0xdd1e], "agrave":0xe0,
+  "agrave;":0xe0, "alefsym;":0x2135,
+  "aleph;":0x2135, "alpha;":0x3b1,
+  "amacr;":0x101, "amalg;":0x2a3f,
+  "amp":0x26, "amp;":0x26,
+  "and;":0x2227, "andand;":0x2a55,
+  "andd;":0x2a5c, "andslope;":0x2a58,
+  "andv;":0x2a5a, "ang;":0x2220,
+  "ange;":0x29a4, "angle;":0x2220,
+  "angmsd;":0x2221, "angmsdaa;":0x29a8,
+  "angmsdab;":0x29a9, "angmsdac;":0x29aa,
+  "angmsdad;":0x29ab, "angmsdae;":0x29ac,
+  "angmsdaf;":0x29ad, "angmsdag;":0x29ae,
+  "angmsdah;":0x29af, "angrt;":0x221f,
+  "angrtvb;":0x22be, "angrtvbd;":0x299d,
+  "angsph;":0x2222, "angst;":0xc5,
+  "angzarr;":0x237c, "aogon;":0x105,
+  "aopf;":[0xd835,0xdd52], "ap;":0x2248,
+  "apE;":0x2a70, "apacir;":0x2a6f,
+  "ape;":0x224a, "apid;":0x224b,
+  "apos;":0x27, "approx;":0x2248,
+  "approxeq;":0x224a, "aring":0xe5,
+  "aring;":0xe5, "ascr;":[0xd835,0xdcb6],
+  "ast;":0x2a, "asymp;":0x2248,
+  "asympeq;":0x224d, "atilde":0xe3,
+  "atilde;":0xe3, "auml":0xe4,
+  "auml;":0xe4, "awconint;":0x2233,
+  "awint;":0x2a11, "bNot;":0x2aed,
+  "backcong;":0x224c, "backepsilon;":0x3f6,
+  "backprime;":0x2035, "backsim;":0x223d,
+  "backsimeq;":0x22cd, "barvee;":0x22bd,
+  "barwed;":0x2305, "barwedge;":0x2305,
+  "bbrk;":0x23b5, "bbrktbrk;":0x23b6,
+  "bcong;":0x224c, "bcy;":0x431,
+  "bdquo;":0x201e, "becaus;":0x2235,
+  "because;":0x2235, "bemptyv;":0x29b0,
+  "bepsi;":0x3f6, "bernou;":0x212c,
+  "beta;":0x3b2, "beth;":0x2136,
+  "between;":0x226c, "bfr;":[0xd835,0xdd1f],
+  "bigcap;":0x22c2, "bigcirc;":0x25ef,
+  "bigcup;":0x22c3, "bigodot;":0x2a00,
+  "bigoplus;":0x2a01, "bigotimes;":0x2a02,
+  "bigsqcup;":0x2a06, "bigstar;":0x2605,
+  "bigtriangledown;":0x25bd, "bigtriangleup;":0x25b3,
+  "biguplus;":0x2a04, "bigvee;":0x22c1,
+  "bigwedge;":0x22c0, "bkarow;":0x290d,
+  "blacklozenge;":0x29eb, "blacksquare;":0x25aa,
+  "blacktriangle;":0x25b4, "blacktriangledown;":0x25be,
+  "blacktriangleleft;":0x25c2, "blacktriangleright;":0x25b8,
+  "blank;":0x2423, "blk12;":0x2592,
+  "blk14;":0x2591, "blk34;":0x2593,
+  "block;":0x2588, "bne;":[0x3d,0x20e5],
+  "bnequiv;":[0x2261,0x20e5], "bnot;":0x2310,
+  "bopf;":[0xd835,0xdd53], "bot;":0x22a5,
+  "bottom;":0x22a5, "bowtie;":0x22c8,
+  "boxDL;":0x2557, "boxDR;":0x2554,
+  "boxDl;":0x2556, "boxDr;":0x2553,
+  "boxH;":0x2550, "boxHD;":0x2566,
+  "boxHU;":0x2569, "boxHd;":0x2564,
+  "boxHu;":0x2567, "boxUL;":0x255d,
+  "boxUR;":0x255a, "boxUl;":0x255c,
+  "boxUr;":0x2559, "boxV;":0x2551,
+  "boxVH;":0x256c, "boxVL;":0x2563,
+  "boxVR;":0x2560, "boxVh;":0x256b,
+  "boxVl;":0x2562, "boxVr;":0x255f,
+  "boxbox;":0x29c9, "boxdL;":0x2555,
+  "boxdR;":0x2552, "boxdl;":0x2510,
+  "boxdr;":0x250c, "boxh;":0x2500,
+  "boxhD;":0x2565, "boxhU;":0x2568,
+  "boxhd;":0x252c, "boxhu;":0x2534,
+  "boxminus;":0x229f, "boxplus;":0x229e,
+  "boxtimes;":0x22a0, "boxuL;":0x255b,
+  "boxuR;":0x2558, "boxul;":0x2518,
+  "boxur;":0x2514, "boxv;":0x2502,
+  "boxvH;":0x256a, "boxvL;":0x2561,
+  "boxvR;":0x255e, "boxvh;":0x253c,
+  "boxvl;":0x2524, "boxvr;":0x251c,
+  "bprime;":0x2035, "breve;":0x2d8,
+  "brvbar":0xa6, "brvbar;":0xa6,
+  "bscr;":[0xd835,0xdcb7], "bsemi;":0x204f,
+  "bsim;":0x223d, "bsime;":0x22cd,
+  "bsol;":0x5c, "bsolb;":0x29c5,
+  "bsolhsub;":0x27c8, "bull;":0x2022,
+  "bullet;":0x2022, "bump;":0x224e,
+  "bumpE;":0x2aae, "bumpe;":0x224f,
+  "bumpeq;":0x224f, "cacute;":0x107,
+  "cap;":0x2229, "capand;":0x2a44,
+  "capbrcup;":0x2a49, "capcap;":0x2a4b,
+  "capcup;":0x2a47, "capdot;":0x2a40,
+  "caps;":[0x2229,0xfe00], "caret;":0x2041,
+  "caron;":0x2c7, "ccaps;":0x2a4d,
+  "ccaron;":0x10d, "ccedil":0xe7,
+  "ccedil;":0xe7, "ccirc;":0x109,
+  "ccups;":0x2a4c, "ccupssm;":0x2a50,
+  "cdot;":0x10b, "cedil":0xb8,
+  "cedil;":0xb8, "cemptyv;":0x29b2,
+  "cent":0xa2, "cent;":0xa2,
+  "centerdot;":0xb7, "cfr;":[0xd835,0xdd20],
+  "chcy;":0x447, "check;":0x2713,
+  "checkmark;":0x2713, "chi;":0x3c7,
+  "cir;":0x25cb, "cirE;":0x29c3,
+  "circ;":0x2c6, "circeq;":0x2257,
+  "circlearrowleft;":0x21ba, "circlearrowright;":0x21bb,
+  "circledR;":0xae, "circledS;":0x24c8,
+  "circledast;":0x229b, "circledcirc;":0x229a,
+  "circleddash;":0x229d, "cire;":0x2257,
+  "cirfnint;":0x2a10, "cirmid;":0x2aef,
+  "cirscir;":0x29c2, "clubs;":0x2663,
+  "clubsuit;":0x2663, "colon;":0x3a,
+  "colone;":0x2254, "coloneq;":0x2254,
+  "comma;":0x2c, "commat;":0x40,
+  "comp;":0x2201, "compfn;":0x2218,
+  "complement;":0x2201, "complexes;":0x2102,
+  "cong;":0x2245, "congdot;":0x2a6d,
+  "conint;":0x222e, "copf;":[0xd835,0xdd54],
+  "coprod;":0x2210, "copy":0xa9,
+  "copy;":0xa9, "copysr;":0x2117,
+  "crarr;":0x21b5, "cross;":0x2717,
+  "cscr;":[0xd835,0xdcb8], "csub;":0x2acf,
+  "csube;":0x2ad1, "csup;":0x2ad0,
+  "csupe;":0x2ad2, "ctdot;":0x22ef,
+  "cudarrl;":0x2938, "cudarrr;":0x2935,
+  "cuepr;":0x22de, "cuesc;":0x22df,
+  "cularr;":0x21b6, "cularrp;":0x293d,
+  "cup;":0x222a, "cupbrcap;":0x2a48,
+  "cupcap;":0x2a46, "cupcup;":0x2a4a,
+  "cupdot;":0x228d, "cupor;":0x2a45,
+  "cups;":[0x222a,0xfe00], "curarr;":0x21b7,
+  "curarrm;":0x293c, "curlyeqprec;":0x22de,
+  "curlyeqsucc;":0x22df, "curlyvee;":0x22ce,
+  "curlywedge;":0x22cf, "curren":0xa4,
+  "curren;":0xa4, "curvearrowleft;":0x21b6,
+  "curvearrowright;":0x21b7, "cuvee;":0x22ce,
+  "cuwed;":0x22cf, "cwconint;":0x2232,
+  "cwint;":0x2231, "cylcty;":0x232d,
+  "dArr;":0x21d3, "dHar;":0x2965,
+  "dagger;":0x2020, "daleth;":0x2138,
+  "darr;":0x2193, "dash;":0x2010,
+  "dashv;":0x22a3, "dbkarow;":0x290f,
+  "dblac;":0x2dd, "dcaron;":0x10f,
+  "dcy;":0x434, "dd;":0x2146,
+  "ddagger;":0x2021, "ddarr;":0x21ca,
+  "ddotseq;":0x2a77, "deg":0xb0,
+  "deg;":0xb0, "delta;":0x3b4,
+  "demptyv;":0x29b1, "dfisht;":0x297f,
+  "dfr;":[0xd835,0xdd21], "dharl;":0x21c3,
+  "dharr;":0x21c2, "diam;":0x22c4,
+  "diamond;":0x22c4, "diamondsuit;":0x2666,
+  "diams;":0x2666, "die;":0xa8,
+  "digamma;":0x3dd, "disin;":0x22f2,
+  "div;":0xf7, "divide":0xf7,
+  "divide;":0xf7, "divideontimes;":0x22c7,
+  "divonx;":0x22c7, "djcy;":0x452,
+  "dlcorn;":0x231e, "dlcrop;":0x230d,
+  "dollar;":0x24, "dopf;":[0xd835,0xdd55],
+  "dot;":0x2d9, "doteq;":0x2250,
+  "doteqdot;":0x2251, "dotminus;":0x2238,
+  "dotplus;":0x2214, "dotsquare;":0x22a1,
+  "doublebarwedge;":0x2306, "downarrow;":0x2193,
+  "downdownarrows;":0x21ca, "downharpoonleft;":0x21c3,
+  "downharpoonright;":0x21c2, "drbkarow;":0x2910,
+  "drcorn;":0x231f, "drcrop;":0x230c,
+  "dscr;":[0xd835,0xdcb9], "dscy;":0x455,
+  "dsol;":0x29f6, "dstrok;":0x111,
+  "dtdot;":0x22f1, "dtri;":0x25bf,
+  "dtrif;":0x25be, "duarr;":0x21f5,
+  "duhar;":0x296f, "dwangle;":0x29a6,
+  "dzcy;":0x45f, "dzigrarr;":0x27ff,
+  "eDDot;":0x2a77, "eDot;":0x2251,
+  "eacute":0xe9, "eacute;":0xe9,
+  "easter;":0x2a6e, "ecaron;":0x11b,
+  "ecir;":0x2256, "ecirc":0xea,
+  "ecirc;":0xea, "ecolon;":0x2255,
+  "ecy;":0x44d, "edot;":0x117,
+  "ee;":0x2147, "efDot;":0x2252,
+  "efr;":[0xd835,0xdd22], "eg;":0x2a9a,
+  "egrave":0xe8, "egrave;":0xe8,
+  "egs;":0x2a96, "egsdot;":0x2a98,
+  "el;":0x2a99, "elinters;":0x23e7,
+  "ell;":0x2113, "els;":0x2a95,
+  "elsdot;":0x2a97, "emacr;":0x113,
+  "empty;":0x2205, "emptyset;":0x2205,
+  "emptyv;":0x2205, "emsp13;":0x2004,
+  "emsp14;":0x2005, "emsp;":0x2003,
+  "eng;":0x14b, "ensp;":0x2002,
+  "eogon;":0x119, "eopf;":[0xd835,0xdd56],
+  "epar;":0x22d5, "eparsl;":0x29e3,
+  "eplus;":0x2a71, "epsi;":0x3b5,
+  "epsilon;":0x3b5, "epsiv;":0x3f5,
+  "eqcirc;":0x2256, "eqcolon;":0x2255,
+  "eqsim;":0x2242, "eqslantgtr;":0x2a96,
+  "eqslantless;":0x2a95, "equals;":0x3d,
+  "equest;":0x225f, "equiv;":0x2261,
+  "equivDD;":0x2a78, "eqvparsl;":0x29e5,
+  "erDot;":0x2253, "erarr;":0x2971,
+  "escr;":0x212f, "esdot;":0x2250,
+  "esim;":0x2242, "eta;":0x3b7,
+  "eth":0xf0, "eth;":0xf0,
+  "euml":0xeb, "euml;":0xeb,
+  "euro;":0x20ac, "excl;":0x21,
+  "exist;":0x2203, "expectation;":0x2130,
+  "exponentiale;":0x2147, "fallingdotseq;":0x2252,
+  "fcy;":0x444, "female;":0x2640,
+  "ffilig;":0xfb03, "fflig;":0xfb00,
+  "ffllig;":0xfb04, "ffr;":[0xd835,0xdd23],
+  "filig;":0xfb01, "fjlig;":[0x66,0x6a],
+  "flat;":0x266d, "fllig;":0xfb02,
+  "fltns;":0x25b1, "fnof;":0x192,
+  "fopf;":[0xd835,0xdd57], "forall;":0x2200,
+  "fork;":0x22d4, "forkv;":0x2ad9,
+  "fpartint;":0x2a0d, "frac12":0xbd,
+  "frac12;":0xbd, "frac13;":0x2153,
+  "frac14":0xbc, "frac14;":0xbc,
+  "frac15;":0x2155, "frac16;":0x2159,
+  "frac18;":0x215b, "frac23;":0x2154,
+  "frac25;":0x2156, "frac34":0xbe,
+  "frac34;":0xbe, "frac35;":0x2157,
+  "frac38;":0x215c, "frac45;":0x2158,
+  "frac56;":0x215a, "frac58;":0x215d,
+  "frac78;":0x215e, "frasl;":0x2044,
+  "frown;":0x2322, "fscr;":[0xd835,0xdcbb],
+  "gE;":0x2267, "gEl;":0x2a8c,
+  "gacute;":0x1f5, "gamma;":0x3b3,
+  "gammad;":0x3dd, "gap;":0x2a86,
+  "gbreve;":0x11f, "gcirc;":0x11d,
+  "gcy;":0x433, "gdot;":0x121,
+  "ge;":0x2265, "gel;":0x22db,
+  "geq;":0x2265, "geqq;":0x2267,
+  "geqslant;":0x2a7e, "ges;":0x2a7e,
+  "gescc;":0x2aa9, "gesdot;":0x2a80,
+  "gesdoto;":0x2a82, "gesdotol;":0x2a84,
+  "gesl;":[0x22db,0xfe00], "gesles;":0x2a94,
+  "gfr;":[0xd835,0xdd24], "gg;":0x226b,
+  "ggg;":0x22d9, "gimel;":0x2137,
+  "gjcy;":0x453, "gl;":0x2277,
+  "glE;":0x2a92, "gla;":0x2aa5,
+  "glj;":0x2aa4, "gnE;":0x2269,
+  "gnap;":0x2a8a, "gnapprox;":0x2a8a,
+  "gne;":0x2a88, "gneq;":0x2a88,
+  "gneqq;":0x2269, "gnsim;":0x22e7,
+  "gopf;":[0xd835,0xdd58], "grave;":0x60,
+  "gscr;":0x210a, "gsim;":0x2273,
+  "gsime;":0x2a8e, "gsiml;":0x2a90,
+  "gt":0x3e, "gt;":0x3e,
+  "gtcc;":0x2aa7, "gtcir;":0x2a7a,
+  "gtdot;":0x22d7, "gtlPar;":0x2995,
+  "gtquest;":0x2a7c, "gtrapprox;":0x2a86,
+  "gtrarr;":0x2978, "gtrdot;":0x22d7,
+  "gtreqless;":0x22db, "gtreqqless;":0x2a8c,
+  "gtrless;":0x2277, "gtrsim;":0x2273,
+  "gvertneqq;":[0x2269,0xfe00], "gvnE;":[0x2269,0xfe00],
+  "hArr;":0x21d4, "hairsp;":0x200a,
+  "half;":0xbd, "hamilt;":0x210b,
+  "hardcy;":0x44a, "harr;":0x2194,
+  "harrcir;":0x2948, "harrw;":0x21ad,
+  "hbar;":0x210f, "hcirc;":0x125,
+  "hearts;":0x2665, "heartsuit;":0x2665,
+  "hellip;":0x2026, "hercon;":0x22b9,
+  "hfr;":[0xd835,0xdd25], "hksearow;":0x2925,
+  "hkswarow;":0x2926, "hoarr;":0x21ff,
+  "homtht;":0x223b, "hookleftarrow;":0x21a9,
+  "hookrightarrow;":0x21aa, "hopf;":[0xd835,0xdd59],
+  "horbar;":0x2015, "hscr;":[0xd835,0xdcbd],
+  "hslash;":0x210f, "hstrok;":0x127,
+  "hybull;":0x2043, "hyphen;":0x2010,
+  "iacute":0xed, "iacute;":0xed,
+  "ic;":0x2063, "icirc":0xee,
+  "icirc;":0xee, "icy;":0x438,
+  "iecy;":0x435, "iexcl":0xa1,
+  "iexcl;":0xa1, "iff;":0x21d4,
+  "ifr;":[0xd835,0xdd26], "igrave":0xec,
+  "igrave;":0xec, "ii;":0x2148,
+  "iiiint;":0x2a0c, "iiint;":0x222d,
+  "iinfin;":0x29dc, "iiota;":0x2129,
+  "ijlig;":0x133, "imacr;":0x12b,
+  "image;":0x2111, "imagline;":0x2110,
+  "imagpart;":0x2111, "imath;":0x131,
+  "imof;":0x22b7, "imped;":0x1b5,
+  "in;":0x2208, "incare;":0x2105,
+  "infin;":0x221e, "infintie;":0x29dd,
+  "inodot;":0x131, "int;":0x222b,
+  "intcal;":0x22ba, "integers;":0x2124,
+  "intercal;":0x22ba, "intlarhk;":0x2a17,
+  "intprod;":0x2a3c, "iocy;":0x451,
+  "iogon;":0x12f, "iopf;":[0xd835,0xdd5a],
+  "iota;":0x3b9, "iprod;":0x2a3c,
+  "iquest":0xbf, "iquest;":0xbf,
+  "iscr;":[0xd835,0xdcbe], "isin;":0x2208,
+  "isinE;":0x22f9, "isindot;":0x22f5,
+  "isins;":0x22f4, "isinsv;":0x22f3,
+  "isinv;":0x2208, "it;":0x2062,
+  "itilde;":0x129, "iukcy;":0x456,
+  "iuml":0xef, "iuml;":0xef,
+  "jcirc;":0x135, "jcy;":0x439,
+  "jfr;":[0xd835,0xdd27], "jmath;":0x237,
+  "jopf;":[0xd835,0xdd5b], "jscr;":[0xd835,0xdcbf],
+  "jsercy;":0x458, "jukcy;":0x454,
+  "kappa;":0x3ba, "kappav;":0x3f0,
+  "kcedil;":0x137, "kcy;":0x43a,
+  "kfr;":[0xd835,0xdd28], "kgreen;":0x138,
+  "khcy;":0x445, "kjcy;":0x45c,
+  "kopf;":[0xd835,0xdd5c], "kscr;":[0xd835,0xdcc0],
+  "lAarr;":0x21da, "lArr;":0x21d0,
+  "lAtail;":0x291b, "lBarr;":0x290e,
+  "lE;":0x2266, "lEg;":0x2a8b,
+  "lHar;":0x2962, "lacute;":0x13a,
+  "laemptyv;":0x29b4, "lagran;":0x2112,
+  "lambda;":0x3bb, "lang;":0x27e8,
+  "langd;":0x2991, "langle;":0x27e8,
+  "lap;":0x2a85, "laquo":0xab,
+  "laquo;":0xab, "larr;":0x2190,
+  "larrb;":0x21e4, "larrbfs;":0x291f,
+  "larrfs;":0x291d, "larrhk;":0x21a9,
+  "larrlp;":0x21ab, "larrpl;":0x2939,
+  "larrsim;":0x2973, "larrtl;":0x21a2,
+  "lat;":0x2aab, "latail;":0x2919,
+  "late;":0x2aad, "lates;":[0x2aad,0xfe00],
+  "lbarr;":0x290c, "lbbrk;":0x2772,
+  "lbrace;":0x7b, "lbrack;":0x5b,
+  "lbrke;":0x298b, "lbrksld;":0x298f,
+  "lbrkslu;":0x298d, "lcaron;":0x13e,
+  "lcedil;":0x13c, "lceil;":0x2308,
+  "lcub;":0x7b, "lcy;":0x43b,
+  "ldca;":0x2936, "ldquo;":0x201c,
+  "ldquor;":0x201e, "ldrdhar;":0x2967,
+  "ldrushar;":0x294b, "ldsh;":0x21b2,
+  "le;":0x2264, "leftarrow;":0x2190,
+  "leftarrowtail;":0x21a2, "leftharpoondown;":0x21bd,
+  "leftharpoonup;":0x21bc, "leftleftarrows;":0x21c7,
+  "leftrightarrow;":0x2194, "leftrightarrows;":0x21c6,
+  "leftrightharpoons;":0x21cb, "leftrightsquigarrow;":0x21ad,
+  "leftthreetimes;":0x22cb, "leg;":0x22da,
+  "leq;":0x2264, "leqq;":0x2266,
+  "leqslant;":0x2a7d, "les;":0x2a7d,
+  "lescc;":0x2aa8, "lesdot;":0x2a7f,
+  "lesdoto;":0x2a81, "lesdotor;":0x2a83,
+  "lesg;":[0x22da,0xfe00], "lesges;":0x2a93,
+  "lessapprox;":0x2a85, "lessdot;":0x22d6,
+  "lesseqgtr;":0x22da, "lesseqqgtr;":0x2a8b,
+  "lessgtr;":0x2276, "lesssim;":0x2272,
+  "lfisht;":0x297c, "lfloor;":0x230a,
+  "lfr;":[0xd835,0xdd29], "lg;":0x2276,
+  "lgE;":0x2a91, "lhard;":0x21bd,
+  "lharu;":0x21bc, "lharul;":0x296a,
+  "lhblk;":0x2584, "ljcy;":0x459,
+  "ll;":0x226a, "llarr;":0x21c7,
+  "llcorner;":0x231e, "llhard;":0x296b,
+  "lltri;":0x25fa, "lmidot;":0x140,
+  "lmoust;":0x23b0, "lmoustache;":0x23b0,
+  "lnE;":0x2268, "lnap;":0x2a89,
+  "lnapprox;":0x2a89, "lne;":0x2a87,
+  "lneq;":0x2a87, "lneqq;":0x2268,
+  "lnsim;":0x22e6, "loang;":0x27ec,
+  "loarr;":0x21fd, "lobrk;":0x27e6,
+  "longleftarrow;":0x27f5, "longleftrightarrow;":0x27f7,
+  "longmapsto;":0x27fc, "longrightarrow;":0x27f6,
+  "looparrowleft;":0x21ab, "looparrowright;":0x21ac,
+  "lopar;":0x2985, "lopf;":[0xd835,0xdd5d],
+  "loplus;":0x2a2d, "lotimes;":0x2a34,
+  "lowast;":0x2217, "lowbar;":0x5f,
+  "loz;":0x25ca, "lozenge;":0x25ca,
+  "lozf;":0x29eb, "lpar;":0x28,
+  "lparlt;":0x2993, "lrarr;":0x21c6,
+  "lrcorner;":0x231f, "lrhar;":0x21cb,
+  "lrhard;":0x296d, "lrm;":0x200e,
+  "lrtri;":0x22bf, "lsaquo;":0x2039,
+  "lscr;":[0xd835,0xdcc1], "lsh;":0x21b0,
+  "lsim;":0x2272, "lsime;":0x2a8d,
+  "lsimg;":0x2a8f, "lsqb;":0x5b,
+  "lsquo;":0x2018, "lsquor;":0x201a,
+  "lstrok;":0x142, "lt":0x3c,
+  "lt;":0x3c, "ltcc;":0x2aa6,
+  "ltcir;":0x2a79, "ltdot;":0x22d6,
+  "lthree;":0x22cb, "ltimes;":0x22c9,
+  "ltlarr;":0x2976, "ltquest;":0x2a7b,
+  "ltrPar;":0x2996, "ltri;":0x25c3,
+  "ltrie;":0x22b4, "ltrif;":0x25c2,
+  "lurdshar;":0x294a, "luruhar;":0x2966,
+  "lvertneqq;":[0x2268,0xfe00], "lvnE;":[0x2268,0xfe00],
+  "mDDot;":0x223a, "macr":0xaf,
+  "macr;":0xaf, "male;":0x2642,
+  "malt;":0x2720, "maltese;":0x2720,
+  "map;":0x21a6, "mapsto;":0x21a6,
+  "mapstodown;":0x21a7, "mapstoleft;":0x21a4,
+  "mapstoup;":0x21a5, "marker;":0x25ae,
+  "mcomma;":0x2a29, "mcy;":0x43c,
+  "mdash;":0x2014, "measuredangle;":0x2221,
+  "mfr;":[0xd835,0xdd2a], "mho;":0x2127,
+  "micro":0xb5, "micro;":0xb5,
+  "mid;":0x2223, "midast;":0x2a,
+  "midcir;":0x2af0, "middot":0xb7,
+  "middot;":0xb7, "minus;":0x2212,
+  "minusb;":0x229f, "minusd;":0x2238,
+  "minusdu;":0x2a2a, "mlcp;":0x2adb,
+  "mldr;":0x2026, "mnplus;":0x2213,
+  "models;":0x22a7, "mopf;":[0xd835,0xdd5e],
+  "mp;":0x2213, "mscr;":[0xd835,0xdcc2],
+  "mstpos;":0x223e, "mu;":0x3bc,
+  "multimap;":0x22b8, "mumap;":0x22b8,
+  "nGg;":[0x22d9,0x338], "nGt;":[0x226b,0x20d2],
+  "nGtv;":[0x226b,0x338], "nLeftarrow;":0x21cd,
+  "nLeftrightarrow;":0x21ce, "nLl;":[0x22d8,0x338],
+  "nLt;":[0x226a,0x20d2], "nLtv;":[0x226a,0x338],
+  "nRightarrow;":0x21cf, "nVDash;":0x22af,
+  "nVdash;":0x22ae, "nabla;":0x2207,
+  "nacute;":0x144, "nang;":[0x2220,0x20d2],
+  "nap;":0x2249, "napE;":[0x2a70,0x338],
+  "napid;":[0x224b,0x338], "napos;":0x149,
+  "napprox;":0x2249, "natur;":0x266e,
+  "natural;":0x266e, "naturals;":0x2115,
+  "nbsp":0xa0, "nbsp;":0xa0,
+  "nbump;":[0x224e,0x338], "nbumpe;":[0x224f,0x338],
+  "ncap;":0x2a43, "ncaron;":0x148,
+  "ncedil;":0x146, "ncong;":0x2247,
+  "ncongdot;":[0x2a6d,0x338], "ncup;":0x2a42,
+  "ncy;":0x43d, "ndash;":0x2013,
+  "ne;":0x2260, "neArr;":0x21d7,
+  "nearhk;":0x2924, "nearr;":0x2197,
+  "nearrow;":0x2197, "nedot;":[0x2250,0x338],
+  "nequiv;":0x2262, "nesear;":0x2928,
+  "nesim;":[0x2242,0x338], "nexist;":0x2204,
+  "nexists;":0x2204, "nfr;":[0xd835,0xdd2b],
+  "ngE;":[0x2267,0x338], "nge;":0x2271,
+  "ngeq;":0x2271, "ngeqq;":[0x2267,0x338],
+  "ngeqslant;":[0x2a7e,0x338], "nges;":[0x2a7e,0x338],
+  "ngsim;":0x2275, "ngt;":0x226f,
+  "ngtr;":0x226f, "nhArr;":0x21ce,
+  "nharr;":0x21ae, "nhpar;":0x2af2,
+  "ni;":0x220b, "nis;":0x22fc,
+  "nisd;":0x22fa, "niv;":0x220b,
+  "njcy;":0x45a, "nlArr;":0x21cd,
+  "nlE;":[0x2266,0x338], "nlarr;":0x219a,
+  "nldr;":0x2025, "nle;":0x2270,
+  "nleftarrow;":0x219a, "nleftrightarrow;":0x21ae,
+  "nleq;":0x2270, "nleqq;":[0x2266,0x338],
+  "nleqslant;":[0x2a7d,0x338], "nles;":[0x2a7d,0x338],
+  "nless;":0x226e, "nlsim;":0x2274,
+  "nlt;":0x226e, "nltri;":0x22ea,
+  "nltrie;":0x22ec, "nmid;":0x2224,
+  "nopf;":[0xd835,0xdd5f], "not":0xac,
+  "not;":0xac, "notin;":0x2209,
+  "notinE;":[0x22f9,0x338], "notindot;":[0x22f5,0x338],
+  "notinva;":0x2209, "notinvb;":0x22f7,
+  "notinvc;":0x22f6, "notni;":0x220c,
+  "notniva;":0x220c, "notnivb;":0x22fe,
+  "notnivc;":0x22fd, "npar;":0x2226,
+  "nparallel;":0x2226, "nparsl;":[0x2afd,0x20e5],
+  "npart;":[0x2202,0x338], "npolint;":0x2a14,
+  "npr;":0x2280, "nprcue;":0x22e0,
+  "npre;":[0x2aaf,0x338], "nprec;":0x2280,
+  "npreceq;":[0x2aaf,0x338], "nrArr;":0x21cf,
+  "nrarr;":0x219b, "nrarrc;":[0x2933,0x338],
+  "nrarrw;":[0x219d,0x338], "nrightarrow;":0x219b,
+  "nrtri;":0x22eb, "nrtrie;":0x22ed,
+  "nsc;":0x2281, "nsccue;":0x22e1,
+  "nsce;":[0x2ab0,0x338], "nscr;":[0xd835,0xdcc3],
+  "nshortmid;":0x2224, "nshortparallel;":0x2226,
+  "nsim;":0x2241, "nsime;":0x2244,
+  "nsimeq;":0x2244, "nsmid;":0x2224,
+  "nspar;":0x2226, "nsqsube;":0x22e2,
+  "nsqsupe;":0x22e3, "nsub;":0x2284,
+  "nsubE;":[0x2ac5,0x338], "nsube;":0x2288,
+  "nsubset;":[0x2282,0x20d2], "nsubseteq;":0x2288,
+  "nsubseteqq;":[0x2ac5,0x338], "nsucc;":0x2281,
+  "nsucceq;":[0x2ab0,0x338], "nsup;":0x2285,
+  "nsupE;":[0x2ac6,0x338], "nsupe;":0x2289,
+  "nsupset;":[0x2283,0x20d2], "nsupseteq;":0x2289,
+  "nsupseteqq;":[0x2ac6,0x338], "ntgl;":0x2279,
+  "ntilde":0xf1, "ntilde;":0xf1,
+  "ntlg;":0x2278, "ntriangleleft;":0x22ea,
+  "ntrianglelefteq;":0x22ec, "ntriangleright;":0x22eb,
+  "ntrianglerighteq;":0x22ed, "nu;":0x3bd,
+  "num;":0x23, "numero;":0x2116,
+  "numsp;":0x2007, "nvDash;":0x22ad,
+  "nvHarr;":0x2904, "nvap;":[0x224d,0x20d2],
+  "nvdash;":0x22ac, "nvge;":[0x2265,0x20d2],
+  "nvgt;":[0x3e,0x20d2], "nvinfin;":0x29de,
+  "nvlArr;":0x2902, "nvle;":[0x2264,0x20d2],
+  "nvlt;":[0x3c,0x20d2], "nvltrie;":[0x22b4,0x20d2],
+  "nvrArr;":0x2903, "nvrtrie;":[0x22b5,0x20d2],
+  "nvsim;":[0x223c,0x20d2], "nwArr;":0x21d6,
+  "nwarhk;":0x2923, "nwarr;":0x2196,
+  "nwarrow;":0x2196, "nwnear;":0x2927,
+  "oS;":0x24c8, "oacute":0xf3,
+  "oacute;":0xf3, "oast;":0x229b,
+  "ocir;":0x229a, "ocirc":0xf4,
+  "ocirc;":0xf4, "ocy;":0x43e,
+  "odash;":0x229d, "odblac;":0x151,
+  "odiv;":0x2a38, "odot;":0x2299,
+  "odsold;":0x29bc, "oelig;":0x153,
+  "ofcir;":0x29bf, "ofr;":[0xd835,0xdd2c],
+  "ogon;":0x2db, "ograve":0xf2,
+  "ograve;":0xf2, "ogt;":0x29c1,
+  "ohbar;":0x29b5, "ohm;":0x3a9,
+  "oint;":0x222e, "olarr;":0x21ba,
+  "olcir;":0x29be, "olcross;":0x29bb,
+  "oline;":0x203e, "olt;":0x29c0,
+  "omacr;":0x14d, "omega;":0x3c9,
+  "omicron;":0x3bf, "omid;":0x29b6,
+  "ominus;":0x2296, "oopf;":[0xd835,0xdd60],
+  "opar;":0x29b7, "operp;":0x29b9,
+  "oplus;":0x2295, "or;":0x2228,
+  "orarr;":0x21bb, "ord;":0x2a5d,
+  "order;":0x2134, "orderof;":0x2134,
+  "ordf":0xaa, "ordf;":0xaa,
+  "ordm":0xba, "ordm;":0xba,
+  "origof;":0x22b6, "oror;":0x2a56,
+  "orslope;":0x2a57, "orv;":0x2a5b,
+  "oscr;":0x2134, "oslash":0xf8,
+  "oslash;":0xf8, "osol;":0x2298,
+  "otilde":0xf5, "otilde;":0xf5,
+  "otimes;":0x2297, "otimesas;":0x2a36,
+  "ouml":0xf6, "ouml;":0xf6,
+  "ovbar;":0x233d, "par;":0x2225,
+  "para":0xb6, "para;":0xb6,
+  "parallel;":0x2225, "parsim;":0x2af3,
+  "parsl;":0x2afd, "part;":0x2202,
+  "pcy;":0x43f, "percnt;":0x25,
+  "period;":0x2e, "permil;":0x2030,
+  "perp;":0x22a5, "pertenk;":0x2031,
+  "pfr;":[0xd835,0xdd2d], "phi;":0x3c6,
+  "phiv;":0x3d5, "phmmat;":0x2133,
+  "phone;":0x260e, "pi;":0x3c0,
+  "pitchfork;":0x22d4, "piv;":0x3d6,
+  "planck;":0x210f, "planckh;":0x210e,
+  "plankv;":0x210f, "plus;":0x2b,
+  "plusacir;":0x2a23, "plusb;":0x229e,
+  "pluscir;":0x2a22, "plusdo;":0x2214,
+  "plusdu;":0x2a25, "pluse;":0x2a72,
+  "plusmn":0xb1, "plusmn;":0xb1,
+  "plussim;":0x2a26, "plustwo;":0x2a27,
+  "pm;":0xb1, "pointint;":0x2a15,
+  "popf;":[0xd835,0xdd61], "pound":0xa3,
+  "pound;":0xa3, "pr;":0x227a,
+  "prE;":0x2ab3, "prap;":0x2ab7,
+  "prcue;":0x227c, "pre;":0x2aaf,
+  "prec;":0x227a, "precapprox;":0x2ab7,
+  "preccurlyeq;":0x227c, "preceq;":0x2aaf,
+  "precnapprox;":0x2ab9, "precneqq;":0x2ab5,
+  "precnsim;":0x22e8, "precsim;":0x227e,
+  "prime;":0x2032, "primes;":0x2119,
+  "prnE;":0x2ab5, "prnap;":0x2ab9,
+  "prnsim;":0x22e8, "prod;":0x220f,
+  "profalar;":0x232e, "profline;":0x2312,
+  "profsurf;":0x2313, "prop;":0x221d,
+  "propto;":0x221d, "prsim;":0x227e,
+  "prurel;":0x22b0, "pscr;":[0xd835,0xdcc5],
+  "psi;":0x3c8, "puncsp;":0x2008,
+  "qfr;":[0xd835,0xdd2e], "qint;":0x2a0c,
+  "qopf;":[0xd835,0xdd62], "qprime;":0x2057,
+  "qscr;":[0xd835,0xdcc6], "quaternions;":0x210d,
+  "quatint;":0x2a16, "quest;":0x3f,
+  "questeq;":0x225f, "quot":0x22,
+  "quot;":0x22, "rAarr;":0x21db,
+  "rArr;":0x21d2, "rAtail;":0x291c,
+  "rBarr;":0x290f, "rHar;":0x2964,
+  "race;":[0x223d,0x331], "racute;":0x155,
+  "radic;":0x221a, "raemptyv;":0x29b3,
+  "rang;":0x27e9, "rangd;":0x2992,
+  "range;":0x29a5, "rangle;":0x27e9,
+  "raquo":0xbb, "raquo;":0xbb,
+  "rarr;":0x2192, "rarrap;":0x2975,
+  "rarrb;":0x21e5, "rarrbfs;":0x2920,
+  "rarrc;":0x2933, "rarrfs;":0x291e,
+  "rarrhk;":0x21aa, "rarrlp;":0x21ac,
+  "rarrpl;":0x2945, "rarrsim;":0x2974,
+  "rarrtl;":0x21a3, "rarrw;":0x219d,
+  "ratail;":0x291a, "ratio;":0x2236,
+  "rationals;":0x211a, "rbarr;":0x290d,
+  "rbbrk;":0x2773, "rbrace;":0x7d,
+  "rbrack;":0x5d, "rbrke;":0x298c,
+  "rbrksld;":0x298e, "rbrkslu;":0x2990,
+  "rcaron;":0x159, "rcedil;":0x157,
+  "rceil;":0x2309, "rcub;":0x7d,
+  "rcy;":0x440, "rdca;":0x2937,
+  "rdldhar;":0x2969, "rdquo;":0x201d,
+  "rdquor;":0x201d, "rdsh;":0x21b3,
+  "real;":0x211c, "realine;":0x211b,
+  "realpart;":0x211c, "reals;":0x211d,
+  "rect;":0x25ad, "reg":0xae,
+  "reg;":0xae, "rfisht;":0x297d,
+  "rfloor;":0x230b, "rfr;":[0xd835,0xdd2f],
+  "rhard;":0x21c1, "rharu;":0x21c0,
+  "rharul;":0x296c, "rho;":0x3c1,
+  "rhov;":0x3f1, "rightarrow;":0x2192,
+  "rightarrowtail;":0x21a3, "rightharpoondown;":0x21c1,
+  "rightharpoonup;":0x21c0, "rightleftarrows;":0x21c4,
+  "rightleftharpoons;":0x21cc, "rightrightarrows;":0x21c9,
+  "rightsquigarrow;":0x219d, "rightthreetimes;":0x22cc,
+  "ring;":0x2da, "risingdotseq;":0x2253,
+  "rlarr;":0x21c4, "rlhar;":0x21cc,
+  "rlm;":0x200f, "rmoust;":0x23b1,
+  "rmoustache;":0x23b1, "rnmid;":0x2aee,
+  "roang;":0x27ed, "roarr;":0x21fe,
+  "robrk;":0x27e7, "ropar;":0x2986,
+  "ropf;":[0xd835,0xdd63], "roplus;":0x2a2e,
+  "rotimes;":0x2a35, "rpar;":0x29,
+  "rpargt;":0x2994, "rppolint;":0x2a12,
+  "rrarr;":0x21c9, "rsaquo;":0x203a,
+  "rscr;":[0xd835,0xdcc7], "rsh;":0x21b1,
+  "rsqb;":0x5d, "rsquo;":0x2019,
+  "rsquor;":0x2019, "rthree;":0x22cc,
+  "rtimes;":0x22ca, "rtri;":0x25b9,
+  "rtrie;":0x22b5, "rtrif;":0x25b8,
+  "rtriltri;":0x29ce, "ruluhar;":0x2968,
+  "rx;":0x211e, "sacute;":0x15b,
+  "sbquo;":0x201a, "sc;":0x227b,
+  "scE;":0x2ab4, "scap;":0x2ab8,
+  "scaron;":0x161, "sccue;":0x227d,
+  "sce;":0x2ab0, "scedil;":0x15f,
+  "scirc;":0x15d, "scnE;":0x2ab6,
+  "scnap;":0x2aba, "scnsim;":0x22e9,
+  "scpolint;":0x2a13, "scsim;":0x227f,
+  "scy;":0x441, "sdot;":0x22c5,
+  "sdotb;":0x22a1, "sdote;":0x2a66,
+  "seArr;":0x21d8, "searhk;":0x2925,
+  "searr;":0x2198, "searrow;":0x2198,
+  "sect":0xa7, "sect;":0xa7,
+  "semi;":0x3b, "seswar;":0x2929,
+  "setminus;":0x2216, "setmn;":0x2216,
+  "sext;":0x2736, "sfr;":[0xd835,0xdd30],
+  "sfrown;":0x2322, "sharp;":0x266f,
+  "shchcy;":0x449, "shcy;":0x448,
+  "shortmid;":0x2223, "shortparallel;":0x2225,
+  "shy":0xad, "shy;":0xad,
+  "sigma;":0x3c3, "sigmaf;":0x3c2,
+  "sigmav;":0x3c2, "sim;":0x223c,
+  "simdot;":0x2a6a, "sime;":0x2243,
+  "simeq;":0x2243, "simg;":0x2a9e,
+  "simgE;":0x2aa0, "siml;":0x2a9d,
+  "simlE;":0x2a9f, "simne;":0x2246,
+  "simplus;":0x2a24, "simrarr;":0x2972,
+  "slarr;":0x2190, "smallsetminus;":0x2216,
+  "smashp;":0x2a33, "smeparsl;":0x29e4,
+  "smid;":0x2223, "smile;":0x2323,
+  "smt;":0x2aaa, "smte;":0x2aac,
+  "smtes;":[0x2aac,0xfe00], "softcy;":0x44c,
+  "sol;":0x2f, "solb;":0x29c4,
+  "solbar;":0x233f, "sopf;":[0xd835,0xdd64],
+  "spades;":0x2660, "spadesuit;":0x2660,
+  "spar;":0x2225, "sqcap;":0x2293,
+  "sqcaps;":[0x2293,0xfe00], "sqcup;":0x2294,
+  "sqcups;":[0x2294,0xfe00], "sqsub;":0x228f,
+  "sqsube;":0x2291, "sqsubset;":0x228f,
+  "sqsubseteq;":0x2291, "sqsup;":0x2290,
+  "sqsupe;":0x2292, "sqsupset;":0x2290,
+  "sqsupseteq;":0x2292, "squ;":0x25a1,
+  "square;":0x25a1, "squarf;":0x25aa,
+  "squf;":0x25aa, "srarr;":0x2192,
+  "sscr;":[0xd835,0xdcc8], "ssetmn;":0x2216,
+  "ssmile;":0x2323, "sstarf;":0x22c6,
+  "star;":0x2606, "starf;":0x2605,
+  "straightepsilon;":0x3f5, "straightphi;":0x3d5,
+  "strns;":0xaf, "sub;":0x2282,
+  "subE;":0x2ac5, "subdot;":0x2abd,
+  "sube;":0x2286, "subedot;":0x2ac3,
+  "submult;":0x2ac1, "subnE;":0x2acb,
+  "subne;":0x228a, "subplus;":0x2abf,
+  "subrarr;":0x2979, "subset;":0x2282,
+  "subseteq;":0x2286, "subseteqq;":0x2ac5,
+  "subsetneq;":0x228a, "subsetneqq;":0x2acb,
+  "subsim;":0x2ac7, "subsub;":0x2ad5,
+  "subsup;":0x2ad3, "succ;":0x227b,
+  "succapprox;":0x2ab8, "succcurlyeq;":0x227d,
+  "succeq;":0x2ab0, "succnapprox;":0x2aba,
+  "succneqq;":0x2ab6, "succnsim;":0x22e9,
+  "succsim;":0x227f, "sum;":0x2211,
+  "sung;":0x266a, "sup1":0xb9,
+  "sup1;":0xb9, "sup2":0xb2,
+  "sup2;":0xb2, "sup3":0xb3,
+  "sup3;":0xb3, "sup;":0x2283,
+  "supE;":0x2ac6, "supdot;":0x2abe,
+  "supdsub;":0x2ad8, "supe;":0x2287,
+  "supedot;":0x2ac4, "suphsol;":0x27c9,
+  "suphsub;":0x2ad7, "suplarr;":0x297b,
+  "supmult;":0x2ac2, "supnE;":0x2acc,
+  "supne;":0x228b, "supplus;":0x2ac0,
+  "supset;":0x2283, "supseteq;":0x2287,
+  "supseteqq;":0x2ac6, "supsetneq;":0x228b,
+  "supsetneqq;":0x2acc, "supsim;":0x2ac8,
+  "supsub;":0x2ad4, "supsup;":0x2ad6,
+  "swArr;":0x21d9, "swarhk;":0x2926,
+  "swarr;":0x2199, "swarrow;":0x2199,
+  "swnwar;":0x292a, "szlig":0xdf,
+  "szlig;":0xdf, "target;":0x2316,
+  "tau;":0x3c4, "tbrk;":0x23b4,
+  "tcaron;":0x165, "tcedil;":0x163,
+  "tcy;":0x442, "tdot;":0x20db,
+  "telrec;":0x2315, "tfr;":[0xd835,0xdd31],
+  "there4;":0x2234, "therefore;":0x2234,
+  "theta;":0x3b8, "thetasym;":0x3d1,
+  "thetav;":0x3d1, "thickapprox;":0x2248,
+  "thicksim;":0x223c, "thinsp;":0x2009,
+  "thkap;":0x2248, "thksim;":0x223c,
+  "thorn":0xfe, "thorn;":0xfe,
+  "tilde;":0x2dc, "times":0xd7,
+  "times;":0xd7, "timesb;":0x22a0,
+  "timesbar;":0x2a31, "timesd;":0x2a30,
+  "tint;":0x222d, "toea;":0x2928,
+  "top;":0x22a4, "topbot;":0x2336,
+  "topcir;":0x2af1, "topf;":[0xd835,0xdd65],
+  "topfork;":0x2ada, "tosa;":0x2929,
+  "tprime;":0x2034, "trade;":0x2122,
+  "triangle;":0x25b5, "triangledown;":0x25bf,
+  "triangleleft;":0x25c3, "trianglelefteq;":0x22b4,
+  "triangleq;":0x225c, "triangleright;":0x25b9,
+  "trianglerighteq;":0x22b5, "tridot;":0x25ec,
+  "trie;":0x225c, "triminus;":0x2a3a,
+  "triplus;":0x2a39, "trisb;":0x29cd,
+  "tritime;":0x2a3b, "trpezium;":0x23e2,
+  "tscr;":[0xd835,0xdcc9], "tscy;":0x446,
+  "tshcy;":0x45b, "tstrok;":0x167,
+  "twixt;":0x226c, "twoheadleftarrow;":0x219e,
+  "twoheadrightarrow;":0x21a0, "uArr;":0x21d1,
+  "uHar;":0x2963, "uacute":0xfa,
+  "uacute;":0xfa, "uarr;":0x2191,
+  "ubrcy;":0x45e, "ubreve;":0x16d,
+  "ucirc":0xfb, "ucirc;":0xfb,
+  "ucy;":0x443, "udarr;":0x21c5,
+  "udblac;":0x171, "udhar;":0x296e,
+  "ufisht;":0x297e, "ufr;":[0xd835,0xdd32],
+  "ugrave":0xf9, "ugrave;":0xf9,
+  "uharl;":0x21bf, "uharr;":0x21be,
+  "uhblk;":0x2580, "ulcorn;":0x231c,
+  "ulcorner;":0x231c, "ulcrop;":0x230f,
+  "ultri;":0x25f8, "umacr;":0x16b,
+  "uml":0xa8, "uml;":0xa8,
+  "uogon;":0x173, "uopf;":[0xd835,0xdd66],
+  "uparrow;":0x2191, "updownarrow;":0x2195,
+  "upharpoonleft;":0x21bf, "upharpoonright;":0x21be,
+  "uplus;":0x228e, "upsi;":0x3c5,
+  "upsih;":0x3d2, "upsilon;":0x3c5,
+  "upuparrows;":0x21c8, "urcorn;":0x231d,
+  "urcorner;":0x231d, "urcrop;":0x230e,
+  "uring;":0x16f, "urtri;":0x25f9,
+  "uscr;":[0xd835,0xdcca], "utdot;":0x22f0,
+  "utilde;":0x169, "utri;":0x25b5,
+  "utrif;":0x25b4, "uuarr;":0x21c8,
+  "uuml":0xfc, "uuml;":0xfc,
+  "uwangle;":0x29a7, "vArr;":0x21d5,
+  "vBar;":0x2ae8, "vBarv;":0x2ae9,
+  "vDash;":0x22a8, "vangrt;":0x299c,
+  "varepsilon;":0x3f5, "varkappa;":0x3f0,
+  "varnothing;":0x2205, "varphi;":0x3d5,
+  "varpi;":0x3d6, "varpropto;":0x221d,
+  "varr;":0x2195, "varrho;":0x3f1,
+  "varsigma;":0x3c2, "varsubsetneq;":[0x228a,0xfe00],
+  "varsubsetneqq;":[0x2acb,0xfe00], "varsupsetneq;":[0x228b,0xfe00],
+  "varsupsetneqq;":[0x2acc,0xfe00], "vartheta;":0x3d1,
+  "vartriangleleft;":0x22b2, "vartriangleright;":0x22b3,
+  "vcy;":0x432, "vdash;":0x22a2,
+  "vee;":0x2228, "veebar;":0x22bb,
+  "veeeq;":0x225a, "vellip;":0x22ee,
+  "verbar;":0x7c, "vert;":0x7c,
+  "vfr;":[0xd835,0xdd33], "vltri;":0x22b2,
+  "vnsub;":[0x2282,0x20d2], "vnsup;":[0x2283,0x20d2],
+  "vopf;":[0xd835,0xdd67], "vprop;":0x221d,
+  "vrtri;":0x22b3, "vscr;":[0xd835,0xdccb],
+  "vsubnE;":[0x2acb,0xfe00], "vsubne;":[0x228a,0xfe00],
+  "vsupnE;":[0x2acc,0xfe00], "vsupne;":[0x228b,0xfe00],
+  "vzigzag;":0x299a, "wcirc;":0x175,
+  "wedbar;":0x2a5f, "wedge;":0x2227,
+  "wedgeq;":0x2259, "weierp;":0x2118,
+  "wfr;":[0xd835,0xdd34], "wopf;":[0xd835,0xdd68],
+  "wp;":0x2118, "wr;":0x2240,
+  "wreath;":0x2240, "wscr;":[0xd835,0xdccc],
+  "xcap;":0x22c2, "xcirc;":0x25ef,
+  "xcup;":0x22c3, "xdtri;":0x25bd,
+  "xfr;":[0xd835,0xdd35], "xhArr;":0x27fa,
+  "xharr;":0x27f7, "xi;":0x3be,
+  "xlArr;":0x27f8, "xlarr;":0x27f5,
+  "xmap;":0x27fc, "xnis;":0x22fb,
+  "xodot;":0x2a00, "xopf;":[0xd835,0xdd69],
+  "xoplus;":0x2a01, "xotime;":0x2a02,
+  "xrArr;":0x27f9, "xrarr;":0x27f6,
+  "xscr;":[0xd835,0xdccd], "xsqcup;":0x2a06,
+  "xuplus;":0x2a04, "xutri;":0x25b3,
+  "xvee;":0x22c1, "xwedge;":0x22c0,
+  "yacute":0xfd, "yacute;":0xfd,
+  "yacy;":0x44f, "ycirc;":0x177,
+  "ycy;":0x44b, "yen":0xa5,
+  "yen;":0xa5, "yfr;":[0xd835,0xdd36],
+  "yicy;":0x457, "yopf;":[0xd835,0xdd6a],
+  "yscr;":[0xd835,0xdcce], "yucy;":0x44e,
+  "yuml":0xff, "yuml;":0xff,
+  "zacute;":0x17a, "zcaron;":0x17e,
+  "zcy;":0x437, "zdot;":0x17c,
+  "zeetrf;":0x2128, "zeta;":0x3b6,
+  "zfr;":[0xd835,0xdd37], "zhcy;":0x436,
+  "zigrarr;":0x21dd, "zopf;":[0xd835,0xdd6b],
+  "zscr;":[0xd835,0xdccf], "zwj;":0x200d,
+  "zwnj;":0x200c,
+};
+/*
+ * This regexp is generated with test/tools/update-entities.js
+ * It will always match at least one character -- but note that there
+ * are no entities whose names are a single character long.
+ */
+var NAMEDCHARREF = /(A(?:Elig;?|MP;?|acute;?|breve;|c(?:irc;?|y;)|fr;|grave;?|lpha;|macr;|nd;|o(?:gon;|pf;)|pplyFunction;|ring;?|s(?:cr;|sign;)|tilde;?|uml;?)|B(?:a(?:ckslash;|r(?:v;|wed;))|cy;|e(?:cause;|rnoullis;|ta;)|fr;|opf;|reve;|scr;|umpeq;)|C(?:Hcy;|OPY;?|a(?:cute;|p(?:;|italDifferentialD;)|yleys;)|c(?:aron;|edil;?|irc;|onint;)|dot;|e(?:dilla;|nterDot;)|fr;|hi;|ircle(?:Dot;|Minus;|Plus;|Times;)|lo(?:ckwiseContourIntegral;|seCurly(?:DoubleQuote;|Quote;))|o(?:lon(?:;|e;)|n(?:gruent;|int;|tourIntegral;)|p(?:f;|roduct;)|unterClockwiseContourIntegral;)|ross;|scr;|up(?:;|Cap;))|D(?:D(?:;|otrahd;)|Jcy;|Scy;|Zcy;|a(?:gger;|rr;|shv;)|c(?:aron;|y;)|el(?:;|ta;)|fr;|i(?:a(?:critical(?:Acute;|Do(?:t;|ubleAcute;)|Grave;|Tilde;)|mond;)|fferentialD;)|o(?:pf;|t(?:;|Dot;|Equal;)|uble(?:ContourIntegral;|Do(?:t;|wnArrow;)|L(?:eft(?:Arrow;|RightArrow;|Tee;)|ong(?:Left(?:Arrow;|RightArrow;)|RightArrow;))|Right(?:Arrow;|Tee;)|Up(?:Arrow;|DownArrow;)|VerticalBar;)|wn(?:Arrow(?:;|Bar;|UpArrow;)|Breve;|Left(?:RightVector;|TeeVector;|Vector(?:;|Bar;))|Right(?:TeeVector;|Vector(?:;|Bar;))|Tee(?:;|Arrow;)|arrow;))|s(?:cr;|trok;))|E(?:NG;|TH;?|acute;?|c(?:aron;|irc;?|y;)|dot;|fr;|grave;?|lement;|m(?:acr;|pty(?:SmallSquare;|VerySmallSquare;))|o(?:gon;|pf;)|psilon;|qu(?:al(?:;|Tilde;)|ilibrium;)|s(?:cr;|im;)|ta;|uml;?|x(?:ists;|ponentialE;))|F(?:cy;|fr;|illed(?:SmallSquare;|VerySmallSquare;)|o(?:pf;|rAll;|uriertrf;)|scr;)|G(?:Jcy;|T;?|amma(?:;|d;)|breve;|c(?:edil;|irc;|y;)|dot;|fr;|g;|opf;|reater(?:Equal(?:;|Less;)|FullEqual;|Greater;|Less;|SlantEqual;|Tilde;)|scr;|t;)|H(?:ARDcy;|a(?:cek;|t;)|circ;|fr;|ilbertSpace;|o(?:pf;|rizontalLine;)|s(?:cr;|trok;)|ump(?:DownHump;|Equal;))|I(?:Ecy;|Jlig;|Ocy;|acute;?|c(?:irc;?|y;)|dot;|fr;|grave;?|m(?:;|a(?:cr;|ginaryI;)|plies;)|n(?:t(?:;|e(?:gral;|rsection;))|visible(?:Comma;|Times;))|o(?:gon;|pf;|ta;)|scr;|tilde;|u(?:kcy;|ml;?))|J(?:c(?:irc;|y;)|fr;|opf;|s(?:cr;|ercy;)|ukcy;)|K(?:Hcy;|Jcy;|appa;|c(?:edil;|y;)|fr;|opf;|scr;)|L(?:Jcy;|T;?|a(?:cute;|mbda;|ng;|placetrf;|rr;)|c(?:aron;|edil;|y;)|e(?:ft(?:A(?:ngleBracket;|rrow(?:;|Bar;|RightArrow;))|Ceiling;|Do(?:ubleBracket;|wn(?:TeeVector;|Vector(?:;|Bar;)))|Floor;|Right(?:Arrow;|Vector;)|T(?:ee(?:;|Arrow;|Vector;)|riangle(?:;|Bar;|Equal;))|Up(?:DownVector;|TeeVector;|Vector(?:;|Bar;))|Vector(?:;|Bar;)|arrow;|rightarrow;)|ss(?:EqualGreater;|FullEqual;|Greater;|Less;|SlantEqual;|Tilde;))|fr;|l(?:;|eftarrow;)|midot;|o(?:ng(?:Left(?:Arrow;|RightArrow;)|RightArrow;|left(?:arrow;|rightarrow;)|rightarrow;)|pf;|wer(?:LeftArrow;|RightArrow;))|s(?:cr;|h;|trok;)|t;)|M(?:ap;|cy;|e(?:diumSpace;|llintrf;)|fr;|inusPlus;|opf;|scr;|u;)|N(?:Jcy;|acute;|c(?:aron;|edil;|y;)|e(?:gative(?:MediumSpace;|Thi(?:ckSpace;|nSpace;)|VeryThinSpace;)|sted(?:GreaterGreater;|LessLess;)|wLine;)|fr;|o(?:Break;|nBreakingSpace;|pf;|t(?:;|C(?:ongruent;|upCap;)|DoubleVerticalBar;|E(?:lement;|qual(?:;|Tilde;)|xists;)|Greater(?:;|Equal;|FullEqual;|Greater;|Less;|SlantEqual;|Tilde;)|Hump(?:DownHump;|Equal;)|Le(?:ftTriangle(?:;|Bar;|Equal;)|ss(?:;|Equal;|Greater;|Less;|SlantEqual;|Tilde;))|Nested(?:GreaterGreater;|LessLess;)|Precedes(?:;|Equal;|SlantEqual;)|R(?:everseElement;|ightTriangle(?:;|Bar;|Equal;))|S(?:quareSu(?:bset(?:;|Equal;)|perset(?:;|Equal;))|u(?:bset(?:;|Equal;)|cceeds(?:;|Equal;|SlantEqual;|Tilde;)|perset(?:;|Equal;)))|Tilde(?:;|Equal;|FullEqual;|Tilde;)|VerticalBar;))|scr;|tilde;?|u;)|O(?:Elig;|acute;?|c(?:irc;?|y;)|dblac;|fr;|grave;?|m(?:acr;|ega;|icron;)|opf;|penCurly(?:DoubleQuote;|Quote;)|r;|s(?:cr;|lash;?)|ti(?:lde;?|mes;)|uml;?|ver(?:B(?:ar;|rac(?:e;|ket;))|Parenthesis;))|P(?:artialD;|cy;|fr;|hi;|i;|lusMinus;|o(?:incareplane;|pf;)|r(?:;|ecedes(?:;|Equal;|SlantEqual;|Tilde;)|ime;|o(?:duct;|portion(?:;|al;)))|s(?:cr;|i;))|Q(?:UOT;?|fr;|opf;|scr;)|R(?:Barr;|EG;?|a(?:cute;|ng;|rr(?:;|tl;))|c(?:aron;|edil;|y;)|e(?:;|verse(?:E(?:lement;|quilibrium;)|UpEquilibrium;))|fr;|ho;|ight(?:A(?:ngleBracket;|rrow(?:;|Bar;|LeftArrow;))|Ceiling;|Do(?:ubleBracket;|wn(?:TeeVector;|Vector(?:;|Bar;)))|Floor;|T(?:ee(?:;|Arrow;|Vector;)|riangle(?:;|Bar;|Equal;))|Up(?:DownVector;|TeeVector;|Vector(?:;|Ba
\ No newline at end of file
+
+var NAMEDCHARREF_MAXLEN = 32;
+
+// Regular expression constants used by the tokenizer and parser
+
+// Note that \r is included in all of these regexps because it will need
+// to be converted to LF by the scanChars() function.
+var DBLQUOTEATTRVAL = /[^\r"&\u0000]+/g;
+var SINGLEQUOTEATTRVAL = /[^\r'&\u0000]+/g;
+var UNQUOTEDATTRVAL = /[^\r\t\n\f &>\u0000]+/g;
+var TAGNAME = /[^\r\t\n\f \/>A-Z\u0000]+/g;
+var ATTRNAME = /[^\r\t\n\f \/=>A-Z\u0000]+/g;
+
+var CDATATEXT = /[^\]\r\u0000\uffff]*/g;
+var DATATEXT = /[^&<\r\u0000\uffff]*/g;
+var RAWTEXT = /[^<\r\u0000\uffff]*/g;
+var PLAINTEXT = /[^\r\u0000\uffff]*/g;
+// Since we don't have the 'sticky tag', add '|.' to the end of SIMPLETAG
+// and SIMPLEATTR so that we are guaranteed to always match.  This prevents
+// us from scanning past the lastIndex set. (Note that the desired matches
+// are always greater than 1 char long, so longest-match will ensure that .
+// is not matched unless the desired match fails.)
+var SIMPLETAG = /(?:(\/)?([a-z]+)>)|[\s\S]/g;
+var SIMPLEATTR = /(?:([-a-z]+)[ \t\n\f]*=[ \t\n\f]*('[^'&\r\u0000]*'|"[^"&\r\u0000]*"|[^\t\n\r\f "&'\u0000>][^&> \t\n\r\f\u0000]*[ \t\n\f]))|[\s\S]/g;
+
+var NONWS = /[^\x09\x0A\x0C\x0D\x20]/;
+var ALLNONWS = /[^\x09\x0A\x0C\x0D\x20]/g; // like above, with g flag
+var NONWSNONNUL = /[^\x00\x09\x0A\x0C\x0D\x20]/; // don't allow NUL either
+var LEADINGWS = /^[\x09\x0A\x0C\x0D\x20]+/;
+var NULCHARS = /\x00/g;
+
+/***
+ * These are utility functions that don't use any of the parser's
+ * internal state.
+ */
+function buf2str(buf) {
+  var CHUNKSIZE=16384;
+  if (buf.length < CHUNKSIZE) {
+    return String.fromCharCode.apply(String, buf);
+  }
+  // special case for large strings, to avoid busting the stack.
+  var result = '';
+  for (var i = 0; i < buf.length; i += CHUNKSIZE) {
+    result += String.fromCharCode.apply(String, buf.slice(i, i+CHUNKSIZE));
+  }
+  return result;
+}
+
+function str2buf(s) {
+  var result = [];
+  for (var i=0; i<s.length; i++) {
+    result[i] = s.charCodeAt(i);
+  }
+  return result;
+}
+
+// Determine whether the element is a member of the set.
+// The set is an object that maps namespaces to objects. The objects
+// then map local tagnames to the value true if that tag is part of the set
+function isA(elt, set) {
+  if (typeof set === 'string') {
+    // convenience case for testing a particular HTML element
+    return elt.namespaceURI === NAMESPACE.HTML &&
+      elt.localName === set;
+  }
+  var tagnames = set[elt.namespaceURI];
+  return tagnames && tagnames[elt.localName];
+}
+
+function isMathmlTextIntegrationPoint(n) {
+  return isA(n, mathmlTextIntegrationPointSet);
+}
+
+function isHTMLIntegrationPoint(n) {
+  if (isA(n, htmlIntegrationPointSet)) return true;
+  if (n.namespaceURI === NAMESPACE.MATHML &&
+    n.localName === "annotation-xml") {
+    var encoding = n.getAttribute("encoding");
+    if (encoding) encoding = encoding.toLowerCase();
+    if (encoding === "text/html" ||
+      encoding === "application/xhtml+xml")
+      return true;
+  }
+  return false;
+}
+
+function adjustSVGTagName(name) {
+  if (name in svgTagNameAdjustments)
+    return svgTagNameAdjustments[name];
+  else
+    return name;
+}
+
+function adjustSVGAttributes(attrs) {
+  for(var i = 0, n = attrs.length; i < n; i++) {
+    if (attrs[i][0] in svgAttrAdjustments) {
+      attrs[i][0] = svgAttrAdjustments[attrs[i][0]];
+    }
+  }
+}
+
+function adjustMathMLAttributes(attrs) {
+  for(var i = 0, n = attrs.length; i < n; i++) {
+    if (attrs[i][0] === "definitionurl") {
+      attrs[i][0] = "definitionURL";
+      break;
+    }
+  }
+}
+
+function adjustForeignAttributes(attrs) {
+  for(var i = 0, n = attrs.length; i < n; i++) {
+    if (attrs[i][0] in foreignAttributes) {
+      // Attributes with namespaces get a 3rd element:
+      // [Qname, value, namespace]
+      attrs[i].push(foreignAttributes[attrs[i][0]]);
+    }
+  }
+}
+
+// For each attribute in attrs, if elt doesn't have an attribute
+// by that name, add the attribute to elt
+// XXX: I'm ignoring namespaces for now
+function transferAttributes(attrs, elt) {
+  for(var i = 0, n = attrs.length; i < n; i++) {
+    var name = attrs[i][0], value = attrs[i][1];
+    if (elt.hasAttribute(name)) continue;
+    elt._setAttribute(name, value);
+  }
+}
+
+/***
+ * The ElementStack class
+ */
+HTMLParser.ElementStack = function ElementStack() {
+  this.elements = [];
+  this.top = null; // stack.top is the "current node" in the spec
+};
+
+/*
+// This is for debugging only
+HTMLParser.ElementStack.prototype.toString = function(e) {
+  return "STACK: " +
+  this.elements.map(function(e) {return e.localName;}).join("-");
+}
+*/
+
+HTMLParser.ElementStack.prototype.push = function(e) {
+  this.elements.push(e);
+  this.top = e;
+};
+
+HTMLParser.ElementStack.prototype.pop = function(e) {
+  this.elements.pop();
+  this.top = this.elements[this.elements.length-1];
+};
+
+// Pop elements off the stack up to and including the first
+// element with the specified (HTML) tagname
+HTMLParser.ElementStack.prototype.popTag = function(tag) {
+  for(var i = this.elements.length-1; i > 0; i--) {
+    var e = this.elements[i];
+    if (isA(e, tag)) break;
+  }
+  this.elements.length = i;
+  this.top = this.elements[i-1];
+};
+
+// Pop elements off the stack up to and including the first
+// element that is an instance of the specified type
+HTMLParser.ElementStack.prototype.popElementType = function(type) {
+  for(var i = this.elements.length-1; i > 0; i--) {
+    if (this.elements[i] instanceof type) break;
+  }
+  this.elements.length = i;
+  this.top = this.elements[i-1];
+};
+
+// Pop elements off the stack up to and including the element e.
+// Note that this is very different from removeElement()
+// This requires that e is on the stack.
+HTMLParser.ElementStack.prototype.popElement = function(e) {
+  for(var i = this.elements.length-1; i > 0; i--) {
+    if (this.elements[i] === e) break;
+  }
+  this.elements.length = i;
+  this.top = this.elements[i-1];
+};
+
+// Remove a specific element from the stack.
+// Do nothing if the element is not on the stack
+HTMLParser.ElementStack.prototype.removeElement = function(e) {
+  if (this.top === e) this.pop();
+  else {
+    var idx = this.elements.lastIndexOf(e);
+    if (idx !== -1)
+      this.elements.splice(idx, 1);
+  }
+};
+
+HTMLParser.ElementStack.prototype.clearToContext = function(set) {
+  // Note that we don't loop to 0. Never pop the <html> elt off.
+  for(var i = this.elements.length-1; i > 0; i--) {
+    if (isA(this.elements[i], set)) break;
+  }
+  this.elements.length = i+1;
+  this.top = this.elements[i];
+};
+
+HTMLParser.ElementStack.prototype.contains = function(tag) {
+  return this.inSpecificScope(tag, Object.create(null));
+};
+
+HTMLParser.ElementStack.prototype.inSpecificScope = function(tag, set) {
+  for(var i = this.elements.length-1; i >= 0; i--) {
+    var elt = this.elements[i];
+    if (isA(elt, tag)) return true;
+    if (isA(elt, set)) return false;
+  }
+  return false;
+};
+
+// Like the above, but for a specific element, not a tagname
+HTMLParser.ElementStack.prototype.elementInSpecificScope = function(target, set) {
+  for(var i = this.elements.length-1; i >= 0; i--) {
+    var elt = this.elements[i];
+    if (elt === target) return true;
+    if (isA(elt, set)) return false;
+  }
+  return false;
+};
+
+// Like the above, but for an element interface, not a tagname
+HTMLParser.ElementStack.prototype.elementTypeInSpecificScope = function(target, set) {
+  for(var i = this.elements.length-1; i >= 0; i--) {
+    var elt = this.elements[i];
+    if (elt instanceof target) return true;
+    if (isA(elt, set)) return false;
+  }
+  return false;
+};
+
+HTMLParser.ElementStack.prototype.inScope = function(tag) {
+  return this.inSpecificScope(tag, inScopeSet);
+};
+
+HTMLParser.ElementStack.prototype.elementInScope = function(e) {
+  return this.elementInSpecificScope(e, inScopeSet);
+};
+
+HTMLParser.ElementStack.prototype.elementTypeInScope = function(type) {
+  return this.elementTypeInSpecificScope(type, inScopeSet);
+};
+
+HTMLParser.ElementStack.prototype.inButtonScope = function(tag) {
+  return this.inSpecificScope(tag, inButtonScopeSet);
+};
+
+HTMLParser.ElementStack.prototype.inListItemScope = function(tag) {
+  return this.inSpecificScope(tag, inListItemScopeSet);
+};
+
+HTMLParser.ElementStack.prototype.inTableScope = function(tag) {
+  return this.inSpecificScope(tag, inTableScopeSet);
+};
+
+HTMLParser.ElementStack.prototype.inSelectScope = function(tag) {
+  // Can't implement this one with inSpecificScope, since it involves
+  // a set defined by inverting another set. So implement manually.
+  for(var i = this.elements.length-1; i >= 0; i--) {
+    var elt = this.elements[i];
+    if (elt.namespaceURI !== NAMESPACE.HTML) return false;
+    var localname = elt.localName;
+    if (localname === tag) return true;
+    if (localname !== "optgroup" && localname !== "option")
+      return false;
+  }
+  return false;
+};
+
+HTMLParser.ElementStack.prototype.generateImpliedEndTags = function(butnot, thorough) {
+  var endTagSet = thorough ? thoroughImpliedEndTagsSet : impliedEndTagsSet;
+  for(var i = this.elements.length-1; i >= 0; i--) {
+    var e = this.elements[i];
+    if (butnot && isA(e, butnot)) break;
+    if (!isA(this.elements[i], endTagSet)) break;
+  }
+
+  this.elements.length = i+1;
+  this.top = this.elements[i];
+};
+
+/***
+ * The ActiveFormattingElements class
+ */
+HTMLParser.ActiveFormattingElements = function AFE() {
+  this.list = []; // elements
+  this.attrs = []; // attribute tokens for cloning
+};
+
+HTMLParser.ActiveFormattingElements.prototype.MARKER = { localName: "|" };
+
+/*
+// For debugging
+HTMLParser.ActiveFormattingElements.prototype.toString = function() {
+  return "AFE: " +
+  this.list.map(function(e) { return e.localName; }).join("-");
+}
+*/
+
+HTMLParser.ActiveFormattingElements.prototype.insertMarker = function() {
+  this.list.push(this.MARKER);
+  this.attrs.push(this.MARKER);
+};
+
+HTMLParser.ActiveFormattingElements.prototype.push = function(elt, attrs) {
+  // Scan backwards: if there are already 3 copies of this element
+  // before we encounter a marker, then drop the last one
+  var count = 0;
+  for(var i = this.list.length-1; i >= 0; i--) {
+    if (this.list[i] === this.MARKER) break;
+    // equal() is defined below
+    if (equal(elt, this.list[i], this.attrs[i])) {
+      count++;
+      if (count === 3) {
+        this.list.splice(i, 1);
+        this.attrs.splice(i, 1);
+        break;
+      }
+    }
+  }
+
+
+  // Now push the element onto the list
+  this.list.push(elt);
+
+  // Copy the attributes and push those on, too
+  var attrcopy = [];
+  for(var ii = 0; ii < attrs.length; ii++) {
+    attrcopy[ii] = attrs[ii];
+  }
+
+  this.attrs.push(attrcopy);
+
+  // This function defines equality of two elements for the purposes
+  // of the AFE list.  Note that it compares the new elements
+  // attributes to the saved array of attributes associated with
+  // the old element because a script could have changed the
+  // old element's set of attributes
+  function equal(newelt, oldelt, oldattrs) {
+    if (newelt.localName !== oldelt.localName) return false;
+    if (newelt._numattrs !== oldattrs.length) return false;
+    for(var i = 0, n = oldattrs.length; i < n; i++) {
+      var oldname = oldattrs[i][0];
+      var oldval = oldattrs[i][1];
+      if (!newelt.hasAttribute(oldname)) return false;
+      if (newelt.getAttribute(oldname) !== oldval) return false;
+    }
+    return true;
+  }
+};
+
+HTMLParser.ActiveFormattingElements.prototype.clearToMarker = function() {
+  for(var i = this.list.length-1; i >= 0; i--) {
+    if (this.list[i] === this.MARKER) break;
+  }
+  if (i < 0) i = 0;
+  this.list.length = i;
+  this.attrs.length = i;
+};
+
+// Find and return the last element with the specified tag between the
+// end of the list and the last marker on the list.
+// Used when parsing <a> in_body_mode()
+HTMLParser.ActiveFormattingElements.prototype.findElementByTag = function(tag) {
+  for(var i = this.list.length-1; i >= 0; i--) {
+    var elt = this.list[i];
+    if (elt === this.MARKER) break;
+    if (elt.localName === tag) return elt;
+  }
+  return null;
+};
+
+HTMLParser.ActiveFormattingElements.prototype.indexOf = function(e) {
+  return this.list.lastIndexOf(e);
+};
+
+// Find the element e in the list and remove it
+// Used when parsing <a> in_body()
+HTMLParser.ActiveFormattingElements.prototype.remove = function(e) {
+  var idx = this.list.lastIndexOf(e);
+  if (idx !== -1) {
+    this.list.splice(idx, 1);
+    this.attrs.splice(idx, 1);
+  }
+};
+
+// Find element a in the list and replace it with element b
+// XXX: Do I need to handle attributes here?
+HTMLParser.ActiveFormattingElements.prototype.replace = function(a, b, attrs) {
+  var idx = this.list.lastIndexOf(a);
+  if (idx !== -1) {
+    this.list[idx] = b;
+    this.attrs[idx] = attrs;
+  }
+};
+
+// Find a in the list and insert b after it
+// This is only used for insert a bookmark object, so the
+// attrs array doesn't really matter
+HTMLParser.ActiveFormattingElements.prototype.insertAfter = function(a,b) {
+  var idx = this.list.lastIndexOf(a);
+  if (idx !== -1) {
+    this.list.splice(idx, 0, b);
+    this.attrs.splice(idx, 0, b);
+  }
+};
+
+
+
+
+/***
+ * This is the parser factory function. It is the return value of
+ * the outer closure that it is defined within.  Most of the parser
+ * implementation details are inside this function.
+ */
+function HTMLParser(address, fragmentContext, options) {
+  /***
+   * These are the parser's state variables
+   */
+  // Scanner state
+  var chars = null;
+  var numchars = 0; // Length of chars
+  var nextchar = 0; // Index of next char
+  var input_complete = false; // Becomes true when end() called.
+  var scanner_skip_newline = false; // If previous char was CR
+  var reentrant_invocations = 0;
+  var saved_scanner_state = [];
+  var leftovers = "";
+  var first_batch = true;
+  var paused = 0; // Becomes non-zero while loading scripts
+
+
+  // Tokenizer state
+  var tokenizer = data_state; // Current tokenizer state
+  var return_state;
+  var character_reference_code;
+  var tagnamebuf = "";
+  var lasttagname = ""; // holds the target end tag for text states
+  var tempbuf = [];
+  var attrnamebuf = "";
+  var attrvaluebuf = "";
+  var commentbuf = [];
+  var doctypenamebuf = [];
+  var doctypepublicbuf = [];
+  var doctypesystembuf = [];
+  var attributes = [];
+  var is_end_tag = false;
+
+  // Tree builder state
+  var parser = initial_mode; // Current insertion mode
+  var originalInsertionMode = null; // A saved insertion mode
+  var templateInsertionModes = []; // Stack of template insertion modes.
+  var stack = new HTMLParser.ElementStack(); // Stack of open elements
+  var afe = new HTMLParser.ActiveFormattingElements(); // mis-nested tags
+  var fragment = (fragmentContext!==undefined); // For innerHTML, etc.
+  var head_element_pointer = null;
+  var form_element_pointer = null;
+  var scripting_enabled = true;
+  if (fragmentContext) {
+	scripting_enabled = fragmentContext.ownerDocument._scripting_enabled;
+  }
+  if (options && options.scripting_enabled === false)
+    scripting_enabled = false;
+  var frameset_ok = true;
+  var force_quirks = false;
+  var pending_table_text;
+  var text_integration_mode; // XXX a spec bug workaround?
+
+  // A single run of characters, buffered up to be sent to
+  // the parser as a single string.
+  var textrun = [];
+  var textIncludesNUL = false;
+  var ignore_linefeed = false;
+
+  /***
+   * This is the parser object that will be the return value of this
+   * factory function, which is some 5000 lines below.
+   * Note that the variable "parser" is the current state of the
+   * parser's state machine.  This variable "htmlparser" is the
+   * return value and defines the public API of the parser
+   */
+  var htmlparser = {
+    document: function() {
+      return doc;
+    },
+
+    // Convenience function for internal use. Can only be called once,
+    // as it removes the nodes from `doc` to add them to fragment.
+    _asDocumentFragment: function() {
+      var frag = doc.createDocumentFragment();
+      var root = doc.firstChild;
+      while(root.hasChildNodes()) {
+        frag.appendChild(root.firstChild);
+      }
+      return frag;
+    },
+
+    // Internal function used from HTMLScriptElement to pause the
+    // parser while a script is being loaded from the network
+    pause: function() {
+      // print("pausing parser");
+      paused++;
+    },
+
+    // Called when a script finishes loading
+    resume: function() {
+      // print("resuming parser");
+      paused--;
+      // XXX: added this to force a resumption.
+      // Is this the right thing to do?
+      this.parse("");
+    },
+
+    // Parse the HTML text s.
+    // The second argument should be true if there is no more
+    // text to be parsed, and should be false or omitted otherwise.
+    // The second argument must not be set for recursive invocations
+    // from document.write()
+    parse: function(s, end, shouldPauseFunc) {
+      var moreToDo;
+
+      // If we're paused, remember the text to parse, but
+      // don't parse it now.
+      // (Don't invoke shouldPauseFunc because we haven't handled 'end' yet.)
+      if (paused > 0) {
+        leftovers += s;
+        return true; // more to do
+      }
+
+
+      if (reentrant_invocations === 0) {
+        // A normal, top-level invocation
+        if (leftovers) {
+          s = leftovers + s;
+          leftovers = "";
+        }
+
+        // Add a special marker character to the end of
+        // the buffer.  If the scanner is at the end of
+        // the buffer and input_complete is set, then this
+        // character will transform into an EOF token.
+        // Having an actual character that represents EOF
+        // in the character buffer makes lookahead regexp
+        // matching work more easily, and this is
+        // important for character references.
+        if (end) {
+          s += "\uFFFF";
+          input_complete = true; // Makes scanChars() send EOF
+        }
+
+        chars = s;
+        numchars = s.length;
+        nextchar = 0;
+
+        if (first_batch) {
+          // We skip a leading Byte Order Mark (\uFEFF)
+          // on first batch of text we're given
+          first_batch = false;
+          if (chars.charCodeAt(0) === 0xFEFF) nextchar = 1;
+        }
+
+        reentrant_invocations++;
+        moreToDo = scanChars(shouldPauseFunc);
+        leftovers = chars.substring(nextchar, numchars);
+        reentrant_invocations--;
+      }
+      else {
+        // This is the re-entrant case, which we have to
+        // handle a little differently.
+        reentrant_invocations++;
+
+        // Save current scanner state
+        saved_scanner_state.push(chars, numchars, nextchar);
+
+        // Set new scanner state
+        chars = s;
+        numchars = s.length;
+        nextchar = 0;
+
+        // Now scan as many of these new chars as we can
+        scanChars();
+        moreToDo = false;
+
+        leftovers = chars.substring(nextchar, numchars);
+
+        // restore old scanner state
+        nextchar = saved_scanner_state.pop();
+        numchars = saved_scanner_state.pop();
+        chars = saved_scanner_state.pop();
+
+        // If there were leftover chars from this invocation
+        // insert them into the pending invocation's buffer
+        // and trim already processed chars at the same time
+        if (leftovers) {
+          chars = leftovers + chars.substring(nextchar);
+          numchars = chars.length;
+          nextchar = 0;
+          leftovers = "";
+        }
+
+        // Decrement the counter
+        reentrant_invocations--;
+      }
+      return moreToDo;
+    }
+  };
+
+
+  // This is the document we'll be building up
+  var doc = new Document(true, address);
+
+  // The document needs to know about the parser, for document.write().
+  // This _parser property will be deleted when we're done parsing.
+  doc._parser = htmlparser;
+
+  // XXX I think that any document we use this parser on should support
+  // scripts. But I may need to configure that through a parser parameter
+  // Only documents with windows ("browsing contexts" to be precise)
+  // allow scripting.
+  doc._scripting_enabled = scripting_enabled;
+
+
+  /***
+   * The actual code of the HTMLParser() factory function begins here.
+   */
+
+  if (fragmentContext) { // for innerHTML parsing
+    if (fragmentContext.ownerDocument._quirks)
+      doc._quirks = true;
+    if (fragmentContext.ownerDocument._limitedQuirks)
+      doc._limitedQuirks = true;
+
+    // Set the initial tokenizer state
+    if (fragmentContext.namespaceURI === NAMESPACE.HTML) {
+      switch(fragmentContext.localName) {
+      case "title":
+      case "textarea":
+        tokenizer = rcdata_state;
+        break;
+      case "style":
+      case "xmp":
+      case "iframe":
+      case "noembed":
+      case "noframes":
+      case "script":
+      case "plaintext":
+        tokenizer = plaintext_state;
+        break;
+      case "noscript":
+        if (scripting_enabled)
+          tokenizer = plaintext_state;
+      }
+    }
+
+    var root = doc.createElement("html");
+    doc._appendChild(root);
+    stack.push(root);
+    if (fragmentContext instanceof impl.HTMLTemplateElement) {
+      templateInsertionModes.push(in_template_mode);
+    }
+    resetInsertionMode();
+
+    for(var e = fragmentContext; e !== null; e = e.parentElement) {
+      if (e instanceof impl.HTMLFormElement) {
+        form_element_pointer = e;
+        break;
+      }
+    }
+  }
+
+  /***
+   * Scanner functions
+   */
+  // Loop through the characters in chars, and pass them one at a time
+  // to the tokenizer FSM. Return when no more characters can be processed
+  // (This may leave 1 or more characters in the buffer: like a CR
+  // waiting to see if the next char is LF, or for states that require
+  // lookahead...)
+  function scanChars(shouldPauseFunc) {
+    var codepoint, s, pattern, eof;
+
+    while(nextchar < numchars) {
+
+      // If we just tokenized a </script> tag, then the paused flag
+      // may have been set to tell us to stop tokenizing while
+      // the script is loading
+      if (paused > 0 || (shouldPauseFunc && shouldPauseFunc())) {
+        return true;
+      }
+
+
+      switch(typeof tokenizer.lookahead) {
+      case 'undefined':
+        codepoint = chars.charCodeAt(nextchar++);
+        if (scanner_skip_newline) {
+          scanner_skip_newline = false;
+          if (codepoint === 0x000A) {
+            nextchar++;
+            continue;
+          }
+        }
+        switch(codepoint) {
+        case 0x000D:
+          // CR always turns into LF, but if the next character
+          // is LF, then that second LF is skipped.
+          if (nextchar < numchars) {
+            if (chars.charCodeAt(nextchar) === 0x000A)
+              nextchar++;
+          }
+          else {
+            // We don't know the next char right now, so we
+            // can't check if it is a LF.  So set a flag
+            scanner_skip_newline = true;
+          }
+
+          // In either case, emit a LF
+          tokenizer(0x000A);
+
+          break;
+        case 0xFFFF:
+          if (input_complete && nextchar === numchars) {
+            tokenizer(EOF); // codepoint will be 0xFFFF here
+            break;
+          }
+          /* falls through */
+        default:
+          tokenizer(codepoint);
+          break;
+        }
+        break;
+
+      case 'number':
+        codepoint = chars.charCodeAt(nextchar);
+
+        // The only tokenizer states that require fixed lookahead
+        // only consume alphanum characters, so we don't have
+        // to worry about CR and LF in this case
+
+        // tokenizer wants n chars of lookahead
+        var n = tokenizer.lookahead;
+        var needsString = true;
+        if (n < 0) {
+          needsString = false;
+          n = -n;
+        }
+
+        if (n < numchars - nextchar) {
+          // If we can look ahead that far
+          s = needsString ? chars.substring(nextchar, nextchar+n) : null;
+          eof = false;
+        }
+        else { // if we don't have that many characters
+          if (input_complete) { // If no more are coming
+            // Just return what we have
+            s = needsString ? chars.substring(nextchar, numchars) : null;
+            eof = true;
+            if (codepoint === 0xFFFF && nextchar === numchars-1)
+              codepoint = EOF;
+          }
+          else {
+            // Return now and wait for more chars later
+            return true;
+          }
+        }
+        tokenizer(codepoint, s, eof);
+        break;
+      case 'string':
+        codepoint = chars.charCodeAt(nextchar);
+
+        // tokenizer wants characters up to a matching string
+        pattern = tokenizer.lookahead;
+        var pos = chars.indexOf(pattern, nextchar);
+        if (pos !== -1) {
+          s = chars.substring(nextchar, pos + pattern.length);
+          eof = false;
+        }
+        else {  // No match
+          // If more characters coming, wait for them
+          if (!input_complete) return true;
+
+          // Otherwise, we've got to return what we've got
+          s = chars.substring(nextchar, numchars);
+          if (codepoint === 0xFFFF && nextchar === numchars-1)
+            codepoint = EOF;
+          eof = true;
+        }
+
+        // The tokenizer states that require this kind of
+        // lookahead have to be careful to handle CR characters
+        // correctly
+        tokenizer(codepoint, s, eof);
+        break;
+      }
+    }
+    return false; // no more characters to scan!
+  }
+
+
+  /***
+   * Tokenizer utility functions
+   */
+  function addAttribute(name,value) {
+    // Make sure there isn't already an attribute with this name
+    // If there is, ignore this one.
+    for(var i = 0; i < attributes.length; i++) {
+      if (attributes[i][0] === name) return;
+    }
+
+    if (value !== undefined) {
+      attributes.push([name, value]);
+    }
+    else {
+      attributes.push([name]);
+    }
+  }
+
+  // Shortcut for simple attributes
+  function handleSimpleAttribute() {
+    SIMPLEATTR.lastIndex = nextchar-1;
+    var matched = SIMPLEATTR.exec(chars);
+    if (!matched) throw new Error("should never happen");
+    var name = matched[1];
+    if (!name) return false;
+    var value = matched[2];
+    var len = value.length;
+    switch(value[0]) {
+    case '"':
+    case "'":
+      value = value.substring(1, len-1);
+      nextchar += (matched[0].length-1);
+      tokenizer = after_attribute_value_quoted_state;
+      break;
+    default:
+      tokenizer = before_attribute_name_state;
+      nextchar += (matched[0].length-1);
+      value = value.substring(0, len-1);
+      break;
+    }
+
+    // Make sure there isn't already an attribute with this name
+    // If there is, ignore this one.
+    for(var i = 0; i < attributes.length; i++) {
+      if (attributes[i][0] === name) return true;
+    }
+
+    attributes.push([name, value]);
+    return true;
+  }
+
+  function beginTagName() {
+    is_end_tag = false;
+    tagnamebuf = "";
+    attributes.length = 0;
+  }
+  function beginEndTagName() {
+    is_end_tag = true;
+    tagnamebuf = "";
+    attributes.length = 0;
+  }
+
+  function beginTempBuf() { tempbuf.length = 0; }
+  function beginAttrName() { attrnamebuf = ""; }
+  function beginAttrValue() { attrvaluebuf = ""; }
+  function beginComment() { commentbuf.length = 0; }
+  function beginDoctype() {
+    doctypenamebuf.length = 0;
+    doctypepublicbuf = null;
+    doctypesystembuf = null;
+  }
+  function beginDoctypePublicId() { doctypepublicbuf = []; }
+  function beginDoctypeSystemId() { doctypesystembuf = []; }
+  function forcequirks() { force_quirks = true; }
+  function cdataAllowed() {
+    return stack.top &&
+      stack.top.namespaceURI !== "http://www.w3.org/1999/xhtml";
+  }
+
+  // Return true if the codepoints in the specified buffer match the
+  // characters of lasttagname
+  function appropriateEndTag(buf) {
+    return lasttagname === buf;
+  }
+
+  function flushText() {
+    if (textrun.length > 0) {
+      var s = buf2str(textrun);
+      textrun.length = 0;
+
+      if (ignore_linefeed) {
+        ignore_linefeed = false;
+        if (s[0] === "\n") s = s.substring(1);
+        if (s.length === 0) return;
+      }
+
+      insertToken(TEXT, s);
+      textIncludesNUL = false;
+    }
+    ignore_linefeed = false;
+  }
+
+  // Consume chars matched by the pattern and return them as a string. Starts
+  // matching at the current position, so users should drop the current char
+  // otherwise.
+  function getMatchingChars(pattern) {
+    pattern.lastIndex = nextchar - 1;
+    var match = pattern.exec(chars);
+    if (match && match.index === nextchar - 1) {
+      match = match[0];
+      nextchar += match.length - 1;
+      /* Careful!  Make sure we haven't matched the EOF character! */
+      if (input_complete && nextchar === numchars) {
+        // Oops, backup one.
+        match = match.slice(0, -1);
+        nextchar--;
+      }
+      return match;
+    } else {
+      throw new Error("should never happen");
+    }
+  }
+
+  // emit a string of chars that match a regexp
+  // Returns false if no chars matched.
+  function emitCharsWhile(pattern) {
+    pattern.lastIndex = nextchar-1;
+    var match = pattern.exec(chars)[0];
+    if (!match) return false;
+    emitCharString(match);
+    nextchar += match.length - 1;
+    return true;
+  }
+
+  // This is used by CDATA sections
+  function emitCharString(s) {
+    if (textrun.length > 0) flushText();
+
+    if (ignore_linefeed) {
+      ignore_linefeed = false;
+      if (s[0] === "\n") s = s.substring(1);
+      if (s.length === 0) return;
+    }
+
+    insertToken(TEXT, s);
+  }
+
+  function emitTag() {
+    if (is_end_tag) insertToken(ENDTAG, tagnamebuf);
+    else {
+      // Remember the last open tag we emitted
+      var tagname = tagnamebuf;
+      tagnamebuf = "";
+      lasttagname = tagname;
+      insertToken(TAG, tagname, attributes);
+    }
+  }
+
+
+  // A shortcut: look ahead and if this is a open or close tag
+  // in lowercase with no spaces and no attributes, just emit it now.
+  function emitSimpleTag() {
+    if (nextchar === numchars) { return false; /* not even 1 char left */ }
+    SIMPLETAG.lastIndex = nextchar;
+    var matched = SIMPLETAG.exec(chars);
+    if (!matched) throw new Error("should never happen");
+    var tagname = matched[2];
+    if (!tagname) return false;
+    var endtag = matched[1];
+    if (endtag) {
+      nextchar += (tagname.length+2);
+      insertToken(ENDTAG, tagname);
+    }
+    else {
+      nextchar += (tagname.length+1);
+      lasttagname = tagname;
+      insertToken(TAG, tagname, NOATTRS);
+    }
+    return true;
+  }
+
+  function emitSelfClosingTag() {
+    if (is_end_tag) insertToken(ENDTAG, tagnamebuf, null, true);
+    else {
+      insertToken(TAG, tagnamebuf, attributes, true);
+    }
+  }
+
+  function emitDoctype() {
+    insertToken(DOCTYPE,
+          buf2str(doctypenamebuf),
+          doctypepublicbuf ? buf2str(doctypepublicbuf) : undefined,
+          doctypesystembuf ? buf2str(doctypesystembuf) : undefined);
+  }
+
+  function emitEOF() {
+    flushText();
+    parser(EOF); // EOF never goes to insertForeignContent()
+    doc.modclock = 1; // Start tracking modifications
+  }
+
+  // Insert a token, either using the current parser insertion mode
+  // (for HTML stuff) or using the insertForeignToken() method.
+  var insertToken = htmlparser.insertToken = function insertToken(t, value, arg3, arg4) {
+    flushText();
+    var current = stack.top;
+
+    if (!current || current.namespaceURI === NAMESPACE.HTML) {
+      // This is the common case
+      parser(t, value, arg3, arg4);
+    }
+    else {
+      // Otherwise we may need to insert this token as foreign content
+      if (t !== TAG && t !== TEXT) {
+        insertForeignToken(t, value, arg3, arg4);
+      }
+      else {
+        // But in some cases we treat it as regular content
+        if ((isMathmlTextIntegrationPoint(current) &&
+           (t === TEXT ||
+            (t === TAG &&
+             value !== "mglyph" && value !== "malignmark"))) ||
+          (t === TAG &&
+           value === "svg" &&
+           current.namespaceURI === NAMESPACE.MATHML &&
+           current.localName === "annotation-xml") ||
+          isHTMLIntegrationPoint(current)) {
+
+          // XXX: the text_integration_mode stuff is an
+          // attempted bug workaround of mine
+          text_integration_mode = true;
+          parser(t, value, arg3, arg4);
+          text_integration_mode = false;
+        }
+        // Otherwise it is foreign content
+        else {
+          insertForeignToken(t, value, arg3, arg4);
+        }
+      }
+    }
+  };
+
+
+  /***
+   * Tree building utility functions
+   */
+  function insertComment(data) {
+    var parent = stack.top;
+    if (foster_parent_mode && isA(parent, tablesectionrowSet)) {
+      fosterParent(function(doc) { return doc.createComment(data); });
+    } else {
+      // "If the adjusted insertion location is inside a template element,
+      // let it instead be inside the template element's template contents"
+      if (parent instanceof impl.HTMLTemplateElement) {
+        parent = parent.content;
+      }
+      parent._appendChild(parent.ownerDocument.createComment(data));
+    }
+  }
+
+  function insertText(s) {
+    var parent = stack.top;
+    if (foster_parent_mode && isA(parent, tablesectionrowSet)) {
+      fosterParent(function(doc) { return doc.createTextNode(s); });
+    } else {
+      // "If the adjusted insertion location is inside a template element,
+      // let it instead be inside the template element's template contents"
+      if (parent instanceof impl.HTMLTemplateElement) {
+        parent = parent.content;
+      }
+      // "If there is a Text node immediately before the adjusted insertion
+      // location, then append data to that Text node's data."
+      var lastChild = parent.lastChild;
+      if (lastChild && lastChild.nodeType === Node.TEXT_NODE) {
+        lastChild.appendData(s);
+      } else {
+        parent._appendChild(parent.ownerDocument.createTextNode(s));
+      }
+    }
+  }
+
+  function createHTMLElt(doc, name, attrs) {
+    // Create the element this way, rather than with
+    // doc.createElement because createElement() does error
+    // checking on the element name that we need to avoid here.
+    var elt = html.createElement(doc, name, null);
+
+    if (attrs) {
+      for(var i = 0, n = attrs.length; i < n; i++) {
+        // Use the _ version to avoid testing the validity
+        // of the attribute name
+        elt._setAttribute(attrs[i][0], attrs[i][1]);
+      }
+    }
+    // XXX
+    // If the element is a resettable form element,
+    // run its reset algorithm now
+    // XXX
+    // handle case where form-element-pointer is not null
+    return elt;
+  }
+
+  // The in_table insertion mode turns on this flag, and that makes
+  // insertHTMLElement use the foster parenting algorithm for elements
+  // tags inside a table
+  var foster_parent_mode = false;
+
+  function insertHTMLElement(name, attrs) {
+    var elt = insertElement(function(doc) {
+      return createHTMLElt(doc, name, attrs);
+    });
+
+    // XXX
+    // If this is a form element, set its form attribute property here
+    if (isA(elt, formassociatedSet)) {
+      elt._form = form_element_pointer;
+    }
+
+    return elt;
+  }
+
+  // Insert the element into the open element or foster parent it
+  function insertElement(eltFunc) {
+    var elt;
+    if (foster_parent_mode && isA(stack.top, tablesectionrowSet)) {
+      elt = fosterParent(eltFunc);
+    }
+    else if (stack.top instanceof impl.HTMLTemplateElement) {
+      // "If the adjusted insertion location is inside a template element,
+      // let it instead be inside the template element's template contents"
+      elt = eltFunc(stack.top.content.ownerDocument);
+      stack.top.content._appendChild(elt);
+    } else {
+      elt = eltFunc(stack.top.ownerDocument);
+      stack.top._appendChild(elt);
+    }
+
+    stack.push(elt);
+    return elt;
+  }
+
+  function insertForeignElement(name, attrs, ns) {
+    return insertElement(function(doc) {
+      // We need to prevent createElementNS from trying to parse `name` as a
+      // `qname`, so use an internal Document#_createElementNS() interface.
+      var elt = doc._createElementNS(name, ns, null);
+      if (attrs) {
+        for(var i = 0, n = attrs.length; i < n; i++) {
+          var attr = attrs[i];
+          if (attr.length === 2)
+            elt._setAttribute(attr[0], attr[1]);
+          else {
+            elt._setAttributeNS(attr[2], attr[0], attr[1]);
+          }
+        }
+      }
+      return elt;
+    });
+  }
+
+  function lastElementOfType(type) {
+    for(var i = stack.elements.length-1; i >= 0; i--) {
+      if (stack.elements[i] instanceof type) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  function fosterParent(eltFunc) {
+    var parent, before, lastTable = -1, lastTemplate = -1, elt;
+
+    lastTable = lastElementOfType(impl.HTMLTableElement);
+    lastTemplate = lastElementOfType(impl.HTMLTemplateElement);
+
+    if (lastTemplate >= 0 && (lastTable < 0 || lastTemplate > lastTable)) {
+      parent = stack.elements[lastTemplate];
+    } else if (lastTable >= 0) {
+      parent = stack.elements[lastTable].parentNode;
+      if (parent) {
+        before = stack.elements[lastTable];
+      } else {
+        parent = stack.elements[lastTable - 1];
+      }
+    }
+    if (!parent) parent = stack.elements[0]; // the `html` element.
+
+    // "If the adjusted insertion location is inside a template element,
+    // let it instead be inside the template element's template contents"
+    if (parent instanceof impl.HTMLTemplateElement) {
+      parent = parent.content;
+    }
+    // Create element in the appropriate document.
+    elt = eltFunc(parent.ownerDocument);
+
+    if (elt.nodeType === Node.TEXT_NODE) {
+      var prev;
+      if (before) prev = before.previousSibling;
+      else prev = parent.lastChild;
+      if (prev && prev.nodeType === Node.TEXT_NODE) {
+        prev.appendData(elt.data);
+        return elt;
+      }
+    }
+    if (before)
+      parent.insertBefore(elt, before);
+    else
+      parent._appendChild(elt);
+    return elt;
+  }
+
+
+  function resetInsertionMode() {
+    var last = false;
+    for(var i = stack.elements.length-1; i >= 0; i--) {
+      var node = stack.elements[i];
+      if (i === 0) {
+        last = true;
+        if (fragment) {
+          node = fragmentContext;
+        }
+      }
+      if (node.namespaceURI === NAMESPACE.HTML) {
+        var tag = node.localName;
+        switch(tag) {
+        case "select":
+          for(var j = i; j > 0; ) {
+            var ancestor = stack.elements[--j];
+            if (ancestor instanceof impl.HTMLTemplateElement) {
+              break;
+            } else if (ancestor instanceof impl.HTMLTableElement) {
+              parser = in_select_in_table_mode;
+              return;
+            }
+          }
+          parser = in_select_mode;
+          return;
+        case "tr":
+          parser = in_row_mode;
+          return;
+        case "tbody":
+        case "tfoot":
+        case "thead":
+          parser = in_table_body_mode;
+          return;
+        case "caption":
+          parser = in_caption_mode;
+          return;
+        case "colgroup":
+          parser = in_column_group_mode;
+          return;
+        case "table":
+          parser = in_table_mode;
+          return;
+        case "template":
+          parser = templateInsertionModes[templateInsertionModes.length-1];
+          return;
+        case "body":
+          parser = in_body_mode;
+          return;
+        case "frameset":
+          parser = in_frameset_mode;
+          return;
+        case "html":
+          if (head_element_pointer === null) {
+            parser = before_head_mode;
+          } else {
+            parser = after_head_mode;
+          }
+          return;
+        default:
+          if (!last) {
+            if (tag === "head") {
+              parser = in_head_mode;
+              return;
+            }
+            if (tag === "td" || tag === "th") {
+              parser = in_cell_mode;
+              return;
+            }
+          }
+        }
+      }
+      if (last) {
+        parser = in_body_mode;
+        return;
+      }
+    }
+  }
+
+
+  function parseRawText(name, attrs) {
+    insertHTMLElement(name, attrs);
+    tokenizer = rawtext_state;
+    originalInsertionMode = parser;
+    parser = text_mode;
+  }
+
+  function parseRCDATA(name, attrs) {
+    insertHTMLElement(name, attrs);
+    tokenizer = rcdata_state;
+    originalInsertionMode = parser;
+    parser = text_mode;
+  }
+
+  // Make a copy of element i on the list of active formatting
+  // elements, using its original attributes, not current
+  // attributes (which may have been modified by a script)
+  function afeclone(doc, i) {
+    return {
+      elt: createHTMLElt(doc, afe.list[i].localName, afe.attrs[i]),
+      attrs: afe.attrs[i],
+    };
+  }
+
+
+  function afereconstruct() {
+    if (afe.list.length === 0) return;
+    var entry = afe.list[afe.list.length-1];
+    // If the last is a marker , do nothing
+    if (entry === afe.MARKER) return;
+    // Or if it is an open element, do nothing
+    if (stack.elements.lastIndexOf(entry) !== -1) return;
+
+    // Loop backward through the list until we find a marker or an
+    // open element, and then move forward one from there.
+    for(var i = afe.list.length-2; i >= 0; i--) {
+      entry = afe.list[i];
+      if (entry === afe.MARKER) break;
+      if (stack.elements.lastIndexOf(entry) !== -1) break;
+    }
+
+    // Now loop forward, starting from the element after the current
+    // one, recreating formatting elements and pushing them back onto
+    // the list of open elements
+    for(i = i+1; i < afe.list.length; i++) {
+      var newelt = insertElement(function(doc) { return afeclone(doc, i).elt; });
+      afe.list[i] = newelt;
+    }
+  }
+
+  // Used by the adoptionAgency() function
+  var BOOKMARK = {localName:"BM"};
+
+  function adoptionAgency(tag) {
+    // If the current node is an HTML element whose tag name is subject,
+    // and the current node is not in the list of active formatting
+    // elements, then pop the current node off the stack of open
+    // elements and abort these steps.
+    if (isA(stack.top, tag) && afe.indexOf(stack.top) === -1) {
+      stack.pop();
+      return true; // no more handling required
+    }
+
+    // Let outer loop counter be zero.
+    var outer = 0;
+
+    // Outer loop: If outer loop counter is greater than or
+    // equal to eight, then abort these steps.
+    while(outer < 8) {
+      // Increment outer loop counter by one.
+      outer++;
+
+      // Let the formatting element be the last element in the list
+      // of active formatting elements that: is between the end of
+      // the list and the last scope marker in the list, if any, or
+      // the start of the list otherwise, and has the same tag name
+      // as the token.
+      var fmtelt = afe.findElementByTag(tag);
+
+      // If there is no such node, then abort these steps and instead
+      // act as described in the "any other end tag" entry below.
+      if (!fmtelt) {
+        return false; // false means handle by the default case
+      }
+
+      // Otherwise, if there is such a node, but that node is not in
+      // the stack of open elements, then this is a parse error;
+      // remove the element from the list, and abort these steps.
+      var index = stack.elements.lastIndexOf(fmtelt);
+      if (index === -1) {
+        afe.remove(fmtelt);
+        return true;   // true means no more handling required
+      }
+
+      // Otherwise, if there is such a node, and that node is also in
+      // the stack of open elements, but the element is not in scope,
+      // then this is a parse error; ignore the token, and abort
+      // these steps.
+      if (!stack.elementInScope(fmtelt)) {
+        return true;
+      }
+
+      // Let the furthest block be the topmost node in the stack of
+      // open elements that is lower in the stack than the formatting
+      // element, and is an element in the special category. There
+      // might not be one.
+      var furthestblock = null, furthestblockindex;
+      for(var i = index+1; i < stack.elements.length; i++) {
+        if (isA(stack.elements[i], specialSet)) {
+          furthestblock = stack.elements[i];
+          furthestblockindex = i;
+          break;
+        }
+      }
+
+      // If there is no furthest block, then the UA must skip the
+      // subsequent steps and instead just pop all the nodes from the
+      // bottom of the stack of open elements, from the current node
+      // up to and including the formatting element, and remove the
+      // formatting element from the list of active formatting
+      // elements.
+      if (!furthestblock) {
+        stack.popElement(fmtelt);
+        afe.remove(fmtelt);
+        return true;
+      }
+      else {
+        // Let the common ancestor be the element immediately above
+        // the formatting element in the stack of open elements.
+        var ancestor = stack.elements[index-1];
+
+        // Let a bookmark note the position of the formatting
+        // element in the list of active formatting elements
+        // relative to the elements on either side of it in the
+        // list.
+        afe.insertAfter(fmtelt, BOOKMARK);
+
+        // Let node and last node be the furthest block.
+        var node = furthestblock;
+        var lastnode = furthestblock;
+        var nodeindex = furthestblockindex;
+        var nodeafeindex;
+
+        // Let inner loop counter be zero.
+        var inner = 0;
+
+        while (true) {
+
+          // Increment inner loop counter by one.
+          inner++;
+
+          // Let node be the element immediately above node in
+          // the stack of open elements, or if node is no longer
+          // in the stack of open elements (e.g. because it got
+          // removed by this algorithm), the element that was
+          // immediately above node in the stack of open elements
+          // before node was removed.
+          node = stack.elements[--nodeindex];
+
+          // If node is the formatting element, then go
+          // to the next step in the overall algorithm.
+          if (node === fmtelt) break;
+
+          // If the inner loop counter is greater than three and node
+          // is in the list of active formatting elements, then remove
+          // node from the list of active formatting elements.
+          nodeafeindex = afe.indexOf(node);
+          if (inner > 3 && nodeafeindex !== -1) {
+            afe.remove(node);
+            nodeafeindex = -1;
+          }
+
+          // If node is not in the list of active formatting
+          // elements, then remove node from the stack of open
+          // elements and then go back to the step labeled inner
+          // loop.
+          if (nodeafeindex === -1) {
+            stack.removeElement(node);
+            continue;
+          }
+
+          // Create an element for the token for which the
+          // element node was created with common ancestor as
+          // the intended parent, replace the entry for node
+          // in the list of active formatting elements with an
+          // entry for the new element, replace the entry for
+          // node in the stack of open elements with an entry for
+          // the new element, and let node be the new element.
+          var newelt = afeclone(ancestor.ownerDocument, nodeafeindex);
+          afe.replace(node, newelt.elt, newelt.attrs);
+          stack.elements[nodeindex] = newelt.elt;
+          node = newelt.elt;
+
+          // If last node is the furthest block, then move the
+          // aforementioned bookmark to be immediately after the
+          // new node in the list of active formatting elements.
+          if (lastnode === furthestblock) {
+            afe.remove(BOOKMARK);
+            afe.insertAfter(newelt.elt, BOOKMARK);
+          }
+
+          // Insert last node into node, first removing it from
+          // its previous parent node if any.
+          node._appendChild(lastnode);
+
+          // Let last node be node.
+          lastnode = node;
+        }
+
+        // If the common ancestor node is a table, tbody, tfoot,
+        // thead, or tr element, then, foster parent whatever last
+        // node ended up being in the previous step, first removing
+        // it from its previous parent node if any.
+        if (foster_parent_mode && isA(ancestor, tablesectionrowSet)) {
+          fosterParent(function() { return lastnode; });
+        }
+        // Otherwise, append whatever last node ended up being in
+        // the previous step to the common ancestor node, first
+        // removing it from its previous parent node if any.
+        else if (ancestor instanceof impl.HTMLTemplateElement) {
+          ancestor.content._appendChild(lastnode);
+        } else {
+          ancestor._appendChild(lastnode);
+        }
+
+        // Create an element for the token for which the
+        // formatting element was created, with furthest block
+        // as the intended parent.
+        var newelt2 = afeclone(furthestblock.ownerDocument, afe.indexOf(fmtelt));
+
+        // Take all of the child nodes of the furthest block and
+        // append them to the element created in the last step.
+        while(furthestblock.hasChildNodes()) {
+          newelt2.elt._appendChild(furthestblock.firstChild);
+        }
+
+        // Append that new element to the furthest block.
+        furthestblock._appendChild(newelt2.elt);
+
+        // Remove the formatting element from the list of active
+        // formatting elements, and insert the new element into the
+        // list of active formatting elements at the position of
+        // the aforementioned bookmark.
+        afe.remove(fmtelt);
+        afe.replace(BOOKMARK, newelt2.elt, newelt2.attrs);
+
+        // Remove the formatting element from the stack of open
+        // elements, and insert the new element into the stack of
+        // open elements immediately below the position of the
+        // furthest block in that stack.
+        stack.removeElement(fmtelt);
+        var pos = stack.elements.lastIndexOf(furthestblock);
+        stack.elements.splice(pos+1, 0, newelt2.elt);
+      }
+    }
+
+    return true;
+  }
+
+  // We do this when we get /script in in_text_mode
+  function handleScriptEnd() {
+    // XXX:
+    // This is just a stub implementation right now and doesn't run scripts.
+    // Getting this method right involves the event loop, URL resolution
+    // script fetching etc. For now I just want to be able to parse
+    // documents and test the parser.
+
+    //var script = stack.top;
+    stack.pop();
+    parser = originalInsertionMode;
+    //script._prepare();
+    return;
+
+    // XXX: here is what this method is supposed to do
+
+    // Provide a stable state.
+
+    // Let script be the current node (which will be a script
+    // element).
+
+    // Pop the current node off the stack of open elements.
+
+    // Switch the insertion mode to the original insertion mode.
+
+    // Let the old insertion point have the same value as the current
+    // insertion point. Let the insertion point be just before the
+    // next input character.
+
+    // Increment the parser's script nesting level by one.
+
+    // Prepare the script. This might cause some script to execute,
+    // which might cause new characters to be inserted into the
+    // tokenizer, and might cause the tokenizer to output more tokens,
+    // resulting in a reentrant invocation of the parser.
+
+    // Decrement the parser's script nesting level by one. If the
+    // parser's script nesting level is zero, then set the parser
+    // pause flag to false.
+
+    // Let the insertion point have the value of the old insertion
+    // point. (In other words, restore the insertion point to its
+    // previous value. This value might be the "undefined" value.)
+
+    // At this stage, if there is a pending parsing-blocking script,
+    // then:
+
+    // If the script nesting level is not zero:
+
+    //   Set the parser pause flag to true, and abort the processing
+    //   of any nested invocations of the tokenizer, yielding
+    //   control back to the caller. (Tokenization will resume when
+    //   the caller returns to the "outer" tree construction stage.)
+
+    //   The tree construction stage of this particular parser is
+    //   being called reentrantly, say from a call to
+    //   document.write().
+
+    // Otherwise:
+
+    //     Run these steps:
+
+    //       Let the script be the pending parsing-blocking
+    //       script. There is no longer a pending
+    //       parsing-blocking script.
+
+    //       Block the tokenizer for this instance of the HTML
+    //       parser, such that the event loop will not run tasks
+    //       that invoke the tokenizer.
+
+    //       If the parser's Document has a style sheet that is
+    //       blocking scripts or the script's "ready to be
+    //       parser-executed" flag is not set: spin the event
+    //       loop until the parser's Document has no style sheet
+    //       that is blocking scripts and the script's "ready to
+    //       be parser-executed" flag is set.
+
+    //       Unblock the tokenizer for this instance of the HTML
+    //       parser, such that tasks that invoke the tokenizer
+    //       can again be run.
+
+    //       Let the insertion point be just before the next
+    //       input character.
+
+    //       Increment the parser's script nesting level by one
+    //       (it should be zero before this step, so this sets
+    //       it to one).
+
+    //       Execute the script.
+
+    //       Decrement the parser's script nesting level by
+    //       one. If the parser's script nesting level is zero
+    //       (which it always should be at this point), then set
+    //       the parser pause flag to false.
+
+    //       Let the insertion point be undefined again.
+
+    //       If there is once again a pending parsing-blocking
+    //       script, then repeat these steps from step 1.
+
+
+  }
+
+  function stopParsing() {
+    // XXX This is just a temporary implementation to get the parser working.
+    // A full implementation involves scripts and events and the event loop.
+
+    // Remove the link from document to parser.
+    // This is instead of "set the insertion point to undefined".
+    // It means that document.write() can't write into the doc anymore.
+    delete doc._parser;
+
+    stack.elements.length = 0; // pop everything off
+
+    // If there is a window object associated with the document
+    // then trigger an load event on it
+    if (doc.defaultView) {
+      doc.defaultView.dispatchEvent(new impl.Event("load",{}));
+    }
+
+  }
+
+  /****
+   * Tokenizer states
+   */
+
+  /**
+   * This file was partially mechanically generated from
+   * http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html
+   *
+   * After mechanical conversion, it was further converted from
+   * prose to JS by hand, but the intent is that it is a very
+   * faithful rendering of the HTML tokenization spec in
+   * JavaScript.
+   *
+   * It is not a goal of this tokenizer to detect or report
+   * parse errors.
+   *
+   * XXX The tokenizer is supposed to work with straight UTF32
+   * codepoints. But I don't think it has any dependencies on
+   * any character outside of the BMP so I think it is safe to
+   * pass it UTF16 characters. I don't think it will ever change
+   * state in the middle of a surrogate pair.
+   */
+
+  /*
+   * Each state is represented by a function.  For most states, the
+   * scanner simply passes the next character (as an integer
+   * codepoint) to the current state function and automatically
+   * consumes the character.  If the state function can't process
+   * the character it can call pushback() to push it back to the
+   * scanner.
+   *
+   * Some states require lookahead, though.  If a state function has
+   * a lookahead property, then it is invoked differently.  In this
+   * case, the scanner invokes the function with 3 arguments: 1) the
+   * next codepoint 2) a string of lookahead text 3) a boolean that
+   * is true if the lookahead goes all the way to the EOF. (XXX
+   * actually maybe this third is not necessary... the lookahead
+   * could just include \uFFFF?)
+   *
+   * If the lookahead property of a state function is an integer, it
+   * specifies the number of characters required. If it is a string,
+   * then the scanner will scan for that string and return all
+   * characters up to and including that sequence, or up to EOF.  If
+   * the lookahead property is a regexp, then the scanner will match
+   * the regexp at the current point and return the matching string.
+   *
+   * States that require lookahead are responsible for explicitly
+   * consuming the characters they process. They do this by
+   * incrementing nextchar by the number of processed characters.
+   */
+  function reconsume(c, new_state) {
+    tokenizer = new_state;
+    nextchar--; // pushback
+  }
+
+  function data_state(c) {
+    switch(c) {
+    case 0x0026: // AMPERSAND
+      return_state = data_state;
+      tokenizer = character_reference_state;
+      break;
+    case 0x003C: // LESS-THAN SIGN
+      if (emitSimpleTag()) // Shortcut for <p>, <dl>, </div> etc.
+        break;
+      tokenizer = tag_open_state;
+      break;
+    case 0x0000: // NULL
+      // Usually null characters emitted by the tokenizer will be
+      // ignored by the tree builder, but sometimes they'll be
+      // converted to \uFFFD.  I don't want to have the search every
+      // string emitted to replace NULs, so I'll set a flag
+      // if I've emitted a NUL.
+      textrun.push(c);
+      textIncludesNUL = true;
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    default:
+      // Instead of just pushing a single character and then
+      // coming back to the very same place, lookahead and
+      // emit everything we can at once.
+      /*jshint -W030 */
+      emitCharsWhile(DATATEXT) || textrun.push(c);
+      break;
+    }
+  }
+
+  function rcdata_state(c) {
+    // Save the open tag so we can find a matching close tag
+    switch(c) {
+    case 0x0026: // AMPERSAND
+      return_state = rcdata_state;
+      tokenizer = character_reference_state;
+      break;
+    case 0x003C: // LESS-THAN SIGN
+      tokenizer = rcdata_less_than_sign_state;
+      break;
+    case 0x0000: // NULL
+      textrun.push(0xFFFD); // REPLACEMENT CHARACTER
+      textIncludesNUL = true;
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    default:
+      textrun.push(c);
+      break;
+    }
+  }
+
+  function rawtext_state(c) {
+    switch(c) {
+    case 0x003C: // LESS-THAN SIGN
+      tokenizer = rawtext_less_than_sign_state;
+      break;
+    case 0x0000: // NULL
+      textrun.push(0xFFFD); // REPLACEMENT CHARACTER
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    default:
+      /*jshint -W030 */
+      emitCharsWhile(RAWTEXT) || textrun.push(c);
+      break;
+    }
+  }
+
+  function script_data_state(c) {
+    switch(c) {
+    case 0x003C: // LESS-THAN SIGN
+      tokenizer = script_data_less_than_sign_state;
+      break;
+    case 0x0000: // NULL
+      textrun.push(0xFFFD); // REPLACEMENT CHARACTER
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    default:
+      /*jshint -W030 */
+      emitCharsWhile(RAWTEXT) || textrun.push(c);
+      break;
+    }
+  }
+
+  function plaintext_state(c) {
+    switch(c) {
+    case 0x0000: // NULL
+      textrun.push(0xFFFD); // REPLACEMENT CHARACTER
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    default:
+      /*jshint -W030 */
+      emitCharsWhile(PLAINTEXT) || textrun.push(c);
+      break;
+    }
+  }
+
+  function tag_open_state(c) {
+    switch(c) {
+    case 0x0021: // EXCLAMATION MARK
+      tokenizer = markup_declaration_open_state;
+      break;
+    case 0x002F: // SOLIDUS
+      tokenizer = end_tag_open_state;
+      break;
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+    case 0x0061:  // [a-z]
+    case 0x0062:case 0x0063:case 0x0064:case 0x0065:case 0x0066:
+    case 0x0067:case 0x0068:case 0x0069:case 0x006A:case 0x006B:
+    case 0x006C:case 0x006D:case 0x006E:case 0x006F:case 0x0070:
+    case 0x0071:case 0x0072:case 0x0073:case 0x0074:case 0x0075:
+    case 0x0076:case 0x0077:case 0x0078:case 0x0079:case 0x007A:
+      beginTagName();
+      reconsume(c, tag_name_state);
+      break;
+    case 0x003F: // QUESTION MARK
+      reconsume(c, bogus_comment_state);
+      break;
+    default:
+      textrun.push(0x003C); // LESS-THAN SIGN
+      reconsume(c, data_state);
+      break;
+    }
+  }
+
+  function end_tag_open_state(c) {
+    switch(c) {
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+    case 0x0061:  // [a-z]
+    case 0x0062:case 0x0063:case 0x0064:case 0x0065:case 0x0066:
+    case 0x0067:case 0x0068:case 0x0069:case 0x006A:case 0x006B:
+    case 0x006C:case 0x006D:case 0x006E:case 0x006F:case 0x0070:
+    case 0x0071:case 0x0072:case 0x0073:case 0x0074:case 0x0075:
+    case 0x0076:case 0x0077:case 0x0078:case 0x0079:case 0x007A:
+      beginEndTagName();
+      reconsume(c, tag_name_state);
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      tokenizer = data_state;
+      break;
+    case -1: // EOF
+      textrun.push(0x003C); // LESS-THAN SIGN
+      textrun.push(0x002F); // SOLIDUS
+      emitEOF();
+      break;
+    default:
+      reconsume(c, bogus_comment_state);
+      break;
+    }
+  }
+
+  function tag_name_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      tokenizer = before_attribute_name_state;
+      break;
+    case 0x002F: // SOLIDUS
+      tokenizer = self_closing_start_tag_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      tokenizer = data_state;
+      emitTag();
+      break;
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+      tagnamebuf += String.fromCharCode(c + 0x0020);
+      break;
+    case 0x0000: // NULL
+      tagnamebuf += String.fromCharCode(0xFFFD /* REPLACEMENT CHARACTER */);
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    default:
+      tagnamebuf += getMatchingChars(TAGNAME);
+      break;
+    }
+  }
+
+  function rcdata_less_than_sign_state(c) {
+    /* identical to the RAWTEXT less-than sign state, except s/RAWTEXT/RCDATA/g */
+    if (c === 0x002F) {  // SOLIDUS
+      beginTempBuf();
+      tokenizer = rcdata_end_tag_open_state;
+    }
+    else {
+      textrun.push(0x003C); // LESS-THAN SIGN
+      reconsume(c, rcdata_state);
+    }
+  }
+
+  function rcdata_end_tag_open_state(c) {
+    /* identical to the RAWTEXT (and Script data) end tag open state, except s/RAWTEXT/RCDATA/g */
+    switch(c) {
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+    case 0x0061:  // [a-z]
+    case 0x0062:case 0x0063:case 0x0064:case 0x0065:case 0x0066:
+    case 0x0067:case 0x0068:case 0x0069:case 0x006A:case 0x006B:
+    case 0x006C:case 0x006D:case 0x006E:case 0x006F:case 0x0070:
+    case 0x0071:case 0x0072:case 0x0073:case 0x0074:case 0x0075:
+    case 0x0076:case 0x0077:case 0x0078:case 0x0079:case 0x007A:
+      beginEndTagName();
+      reconsume(c, rcdata_end_tag_name_state);
+      break;
+    default:
+      textrun.push(0x003C); // LESS-THAN SIGN
+      textrun.push(0x002F); // SOLIDUS
+      reconsume(c, rcdata_state);
+      break;
+    }
+  }
+
+  function rcdata_end_tag_name_state(c) {
+    /* identical to the RAWTEXT (and Script data) end tag name state, except s/RAWTEXT/RCDATA/g */
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      if (appropriateEndTag(tagnamebuf)) {
+        tokenizer = before_attribute_name_state;
+        return;
+      }
+      break;
+    case 0x002F: // SOLIDUS
+      if (appropriateEndTag(tagnamebuf)) {
+        tokenizer = self_closing_start_tag_state;
+        return;
+      }
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      if (appropriateEndTag(tagnamebuf)) {
+        tokenizer = data_state;
+        emitTag();
+        return;
+      }
+      break;
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+
+      tagnamebuf += String.fromCharCode(c + 0x0020);
+      tempbuf.push(c);
+      return;
+    case 0x0061:  // [a-z]
+    case 0x0062:case 0x0063:case 0x0064:case 0x0065:case 0x0066:
+    case 0x0067:case 0x0068:case 0x0069:case 0x006A:case 0x006B:
+    case 0x006C:case 0x006D:case 0x006E:case 0x006F:case 0x0070:
+    case 0x0071:case 0x0072:case 0x0073:case 0x0074:case 0x0075:
+    case 0x0076:case 0x0077:case 0x0078:case 0x0079:case 0x007A:
+
+      tagnamebuf += String.fromCharCode(c);
+      tempbuf.push(c);
+      return;
+    default:
+      break;
+    }
+
+    // If we don't return in one of the cases above, then this was not
+    // an appropriately matching close tag, so back out by emitting all
+    // the characters as text
+    textrun.push(0x003C); // LESS-THAN SIGN
+    textrun.push(0x002F); // SOLIDUS
+    pushAll(textrun, tempbuf);
+    reconsume(c, rcdata_state);
+  }
+
+  function rawtext_less_than_sign_state(c) {
+    /* identical to the RCDATA less-than sign state, except s/RCDATA/RAWTEXT/g
+     */
+    if (c === 0x002F) { // SOLIDUS
+      beginTempBuf();
+      tokenizer = rawtext_end_tag_open_state;
+    }
+    else {
+      textrun.push(0x003C); // LESS-THAN SIGN
+      reconsume(c, rawtext_state);
+    }
+  }
+
+  function rawtext_end_tag_open_state(c) {
+    /* identical to the RCDATA (and Script data) end tag open state, except s/RCDATA/RAWTEXT/g */
+    switch(c) {
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+    case 0x0061:  // [a-z]
+    case 0x0062:case 0x0063:case 0x0064:case 0x0065:case 0x0066:
+    case 0x0067:case 0x0068:case 0x0069:case 0x006A:case 0x006B:
+    case 0x006C:case 0x006D:case 0x006E:case 0x006F:case 0x0070:
+    case 0x0071:case 0x0072:case 0x0073:case 0x0074:case 0x0075:
+    case 0x0076:case 0x0077:case 0x0078:case 0x0079:case 0x007A:
+      beginEndTagName();
+      reconsume(c, rawtext_end_tag_name_state);
+      break;
+    default:
+      textrun.push(0x003C); // LESS-THAN SIGN
+      textrun.push(0x002F); // SOLIDUS
+      reconsume(c, rawtext_state);
+      break;
+    }
+  }
+
+  function rawtext_end_tag_name_state(c) {
+    /* identical to the RCDATA (and Script data) end tag name state, except s/RCDATA/RAWTEXT/g */
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      if (appropriateEndTag(tagnamebuf)) {
+        tokenizer = before_attribute_name_state;
+        return;
+      }
+      break;
+    case 0x002F: // SOLIDUS
+      if (appropriateEndTag(tagnamebuf)) {
+        tokenizer = self_closing_start_tag_state;
+        return;
+      }
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      if (appropriateEndTag(tagnamebuf)) {
+        tokenizer = data_state;
+        emitTag();
+        return;
+      }
+      break;
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+      tagnamebuf += String.fromCharCode(c + 0x0020);
+      tempbuf.push(c);
+      return;
+    case 0x0061:  // [a-z]
+    case 0x0062:case 0x0063:case 0x0064:case 0x0065:case 0x0066:
+    case 0x0067:case 0x0068:case 0x0069:case 0x006A:case 0x006B:
+    case 0x006C:case 0x006D:case 0x006E:case 0x006F:case 0x0070:
+    case 0x0071:case 0x0072:case 0x0073:case 0x0074:case 0x0075:
+    case 0x0076:case 0x0077:case 0x0078:case 0x0079:case 0x007A:
+      tagnamebuf += String.fromCharCode(c);
+      tempbuf.push(c);
+      return;
+    default:
+      break;
+    }
+
+    // If we don't return in one of the cases above, then this was not
+    // an appropriately matching close tag, so back out by emitting all
+    // the characters as text
+    textrun.push(0x003C); // LESS-THAN SIGN
+    textrun.push(0x002F); // SOLIDUS
+    pushAll(textrun,tempbuf);
+    reconsume(c, rawtext_state);
+  }
+
+  function script_data_less_than_sign_state(c) {
+    switch(c) {
+    case 0x002F: // SOLIDUS
+      beginTempBuf();
+      tokenizer = script_data_end_tag_open_state;
+      break;
+    case 0x0021: // EXCLAMATION MARK
+      tokenizer = script_data_escape_start_state;
+      textrun.push(0x003C); // LESS-THAN SIGN
+      textrun.push(0x0021); // EXCLAMATION MARK
+      break;
+    default:
+      textrun.push(0x003C); // LESS-THAN SIGN
+      reconsume(c, script_data_state);
+      break;
+    }
+  }
+
+  function script_data_end_tag_open_state(c) {
+    /* identical to the RCDATA (and RAWTEXT) end tag open state, except s/RCDATA/Script data/g */
+    switch(c) {
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+    case 0x0061:  // [a-z]
+    case 0x0062:case 0x0063:case 0x0064:case 0x0065:case 0x0066:
+    case 0x0067:case 0x0068:case 0x0069:case 0x006A:case 0x006B:
+    case 0x006C:case 0x006D:case 0x006E:case 0x006F:case 0x0070:
+    case 0x0071:case 0x0072:case 0x0073:case 0x0074:case 0x0075:
+    case 0x0076:case 0x0077:case 0x0078:case 0x0079:case 0x007A:
+      beginEndTagName();
+      reconsume(c, script_data_end_tag_name_state);
+      break;
+    default:
+      textrun.push(0x003C); // LESS-THAN SIGN
+      textrun.push(0x002F); // SOLIDUS
+      reconsume(c, script_data_state);
+      break;
+    }
+  }
+
+  function script_data_end_tag_name_state(c) {
+    /* identical to the RCDATA (and RAWTEXT) end tag name state, except s/RCDATA/Script data/g */
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      if (appropriateEndTag(tagnamebuf)) {
+        tokenizer = before_attribute_name_state;
+        return;
+      }
+      break;
+    case 0x002F: // SOLIDUS
+      if (appropriateEndTag(tagnamebuf)) {
+        tokenizer = self_closing_start_tag_state;
+        return;
+      }
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      if (appropriateEndTag(tagnamebuf)) {
+        tokenizer = data_state;
+        emitTag();
+        return;
+      }
+      break;
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+
+      tagnamebuf += String.fromCharCode(c + 0x0020);
+      tempbuf.push(c);
+      return;
+    case 0x0061:  // [a-z]
+    case 0x0062:case 0x0063:case 0x0064:case 0x0065:case 0x0066:
+    case 0x0067:case 0x0068:case 0x0069:case 0x006A:case 0x006B:
+    case 0x006C:case 0x006D:case 0x006E:case 0x006F:case 0x0070:
+    case 0x0071:case 0x0072:case 0x0073:case 0x0074:case 0x0075:
+    case 0x0076:case 0x0077:case 0x0078:case 0x0079:case 0x007A:
+
+      tagnamebuf += String.fromCharCode(c);
+      tempbuf.push(c);
+      return;
+    default:
+      break;
+    }
+
+    // If we don't return in one of the cases above, then this was not
+    // an appropriately matching close tag, so back out by emitting all
+    // the characters as text
+    textrun.push(0x003C); // LESS-THAN SIGN
+    textrun.push(0x002F); // SOLIDUS
+    pushAll(textrun,tempbuf);
+    reconsume(c, script_data_state);
+  }
+
+  function script_data_escape_start_state(c) {
+    if (c === 0x002D) { // HYPHEN-MINUS
+      tokenizer = script_data_escape_start_dash_state;
+      textrun.push(0x002D); // HYPHEN-MINUS
+    }
+    else {
+      reconsume(c, script_data_state);
+    }
+  }
+
+  function script_data_escape_start_dash_state(c) {
+    if (c === 0x002D) { // HYPHEN-MINUS
+      tokenizer = script_data_escaped_dash_dash_state;
+      textrun.push(0x002D); // HYPHEN-MINUS
+    }
+    else {
+      reconsume(c, script_data_state);
+    }
+  }
+
+  function script_data_escaped_state(c) {
+    switch(c) {
+    case 0x002D: // HYPHEN-MINUS
+      tokenizer = script_data_escaped_dash_state;
+      textrun.push(0x002D); // HYPHEN-MINUS
+      break;
+    case 0x003C: // LESS-THAN SIGN
+      tokenizer = script_data_escaped_less_than_sign_state;
+      break;
+    case 0x0000: // NULL
+      textrun.push(0xFFFD); // REPLACEMENT CHARACTER
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    default:
+      textrun.push(c);
+      break;
+    }
+  }
+
+  function script_data_escaped_dash_state(c) {
+    switch(c) {
+    case 0x002D: // HYPHEN-MINUS
+      tokenizer = script_data_escaped_dash_dash_state;
+      textrun.push(0x002D); // HYPHEN-MINUS
+      break;
+    case 0x003C: // LESS-THAN SIGN
+      tokenizer = script_data_escaped_less_than_sign_state;
+      break;
+    case 0x0000: // NULL
+      tokenizer = script_data_escaped_state;
+      textrun.push(0xFFFD); // REPLACEMENT CHARACTER
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    default:
+      tokenizer = script_data_escaped_state;
+      textrun.push(c);
+      break;
+    }
+  }
+
+  function script_data_escaped_dash_dash_state(c) {
+    switch(c) {
+    case 0x002D: // HYPHEN-MINUS
+      textrun.push(0x002D); // HYPHEN-MINUS
+      break;
+    case 0x003C: // LESS-THAN SIGN
+      tokenizer = script_data_escaped_less_than_sign_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      tokenizer = script_data_state;
+      textrun.push(0x003E); // GREATER-THAN SIGN
+      break;
+    case 0x0000: // NULL
+      tokenizer = script_data_escaped_state;
+      textrun.push(0xFFFD); // REPLACEMENT CHARACTER
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    default:
+      tokenizer = script_data_escaped_state;
+      textrun.push(c);
+      break;
+    }
+  }
+
+  function script_data_escaped_less_than_sign_state(c) {
+    switch(c) {
+    case 0x002F: // SOLIDUS
+      beginTempBuf();
+      tokenizer = script_data_escaped_end_tag_open_state;
+      break;
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+    case 0x0061:  // [a-z]
+    case 0x0062:case 0x0063:case 0x0064:case 0x0065:case 0x0066:
+    case 0x0067:case 0x0068:case 0x0069:case 0x006A:case 0x006B:
+    case 0x006C:case 0x006D:case 0x006E:case 0x006F:case 0x0070:
+    case 0x0071:case 0x0072:case 0x0073:case 0x0074:case 0x0075:
+    case 0x0076:case 0x0077:case 0x0078:case 0x0079:case 0x007A:
+      beginTempBuf();
+      textrun.push(0x003C); // LESS-THAN SIGN
+      reconsume(c, script_data_double_escape_start_state);
+      break;
+    default:
+      textrun.push(0x003C); // LESS-THAN SIGN
+      reconsume(c, script_data_escaped_state);
+      break;
+    }
+  }
+
+  function script_data_escaped_end_tag_open_state(c) {
+    switch(c) {
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+    case 0x0061:  // [a-z]
+    case 0x0062:case 0x0063:case 0x0064:case 0x0065:case 0x0066:
+    case 0x0067:case 0x0068:case 0x0069:case 0x006A:case 0x006B:
+    case 0x006C:case 0x006D:case 0x006E:case 0x006F:case 0x0070:
+    case 0x0071:case 0x0072:case 0x0073:case 0x0074:case 0x0075:
+    case 0x0076:case 0x0077:case 0x0078:case 0x0079:case 0x007A:
+      beginEndTagName();
+      reconsume(c, script_data_escaped_end_tag_name_state);
+      break;
+    default:
+      textrun.push(0x003C); // LESS-THAN SIGN
+      textrun.push(0x002F); // SOLIDUS
+      reconsume(c, script_data_escaped_state);
+      break;
+    }
+  }
+
+  function script_data_escaped_end_tag_name_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      if (appropriateEndTag(tagnamebuf)) {
+        tokenizer = before_attribute_name_state;
+        return;
+      }
+      break;
+    case 0x002F: // SOLIDUS
+      if (appropriateEndTag(tagnamebuf)) {
+        tokenizer = self_closing_start_tag_state;
+        return;
+      }
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      if (appropriateEndTag(tagnamebuf)) {
+        tokenizer = data_state;
+        emitTag();
+        return;
+      }
+      break;
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+      tagnamebuf += String.fromCharCode(c + 0x0020);
+      tempbuf.push(c);
+      return;
+    case 0x0061:  // [a-z]
+    case 0x0062:case 0x0063:case 0x0064:case 0x0065:case 0x0066:
+    case 0x0067:case 0x0068:case 0x0069:case 0x006A:case 0x006B:
+    case 0x006C:case 0x006D:case 0x006E:case 0x006F:case 0x0070:
+    case 0x0071:case 0x0072:case 0x0073:case 0x0074:case 0x0075:
+    case 0x0076:case 0x0077:case 0x0078:case 0x0079:case 0x007A:
+      tagnamebuf += String.fromCharCode(c);
+      tempbuf.push(c);
+      return;
+    default:
+      break;
+    }
+
+    // We get here in the default case, and if the closing tagname
+    // is not an appropriate tagname.
+    textrun.push(0x003C); // LESS-THAN SIGN
+    textrun.push(0x002F); // SOLIDUS
+    pushAll(textrun,tempbuf);
+    reconsume(c, script_data_escaped_state);
+  }
+
+  function script_data_double_escape_start_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+    case 0x002F: // SOLIDUS
+    case 0x003E: // GREATER-THAN SIGN
+      if (buf2str(tempbuf) === "script") {
+        tokenizer = script_data_double_escaped_state;
+      }
+      else {
+        tokenizer = script_data_escaped_state;
+      }
+      textrun.push(c);
+      break;
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+      tempbuf.push(c + 0x0020);
+      textrun.push(c);
+      break;
+    case 0x0061:  // [a-z]
+    case 0x0062:case 0x0063:case 0x0064:case 0x0065:case 0x0066:
+    case 0x0067:case 0x0068:case 0x0069:case 0x006A:case 0x006B:
+    case 0x006C:case 0x006D:case 0x006E:case 0x006F:case 0x0070:
+    case 0x0071:case 0x0072:case 0x0073:case 0x0074:case 0x0075:
+    case 0x0076:case 0x0077:case 0x0078:case 0x0079:case 0x007A:
+      tempbuf.push(c);
+      textrun.push(c);
+      break;
+    default:
+      reconsume(c, script_data_escaped_state);
+      break;
+    }
+  }
+
+  function script_data_double_escaped_state(c) {
+    switch(c) {
+    case 0x002D: // HYPHEN-MINUS
+      tokenizer = script_data_double_escaped_dash_state;
+      textrun.push(0x002D); // HYPHEN-MINUS
+      break;
+    case 0x003C: // LESS-THAN SIGN
+      tokenizer = script_data_double_escaped_less_than_sign_state;
+      textrun.push(0x003C); // LESS-THAN SIGN
+      break;
+    case 0x0000: // NULL
+      textrun.push(0xFFFD); // REPLACEMENT CHARACTER
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    default:
+      textrun.push(c);
+      break;
+    }
+  }
+
+  function script_data_double_escaped_dash_state(c) {
+    switch(c) {
+    case 0x002D: // HYPHEN-MINUS
+      tokenizer = script_data_double_escaped_dash_dash_state;
+      textrun.push(0x002D); // HYPHEN-MINUS
+      break;
+    case 0x003C: // LESS-THAN SIGN
+      tokenizer = script_data_double_escaped_less_than_sign_state;
+      textrun.push(0x003C); // LESS-THAN SIGN
+      break;
+    case 0x0000: // NULL
+      tokenizer = script_data_double_escaped_state;
+      textrun.push(0xFFFD); // REPLACEMENT CHARACTER
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    default:
+      tokenizer = script_data_double_escaped_state;
+      textrun.push(c);
+      break;
+    }
+  }
+
+  function script_data_double_escaped_dash_dash_state(c) {
+    switch(c) {
+    case 0x002D: // HYPHEN-MINUS
+      textrun.push(0x002D); // HYPHEN-MINUS
+      break;
+    case 0x003C: // LESS-THAN SIGN
+      tokenizer = script_data_double_escaped_less_than_sign_state;
+      textrun.push(0x003C); // LESS-THAN SIGN
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      tokenizer = script_data_state;
+      textrun.push(0x003E); // GREATER-THAN SIGN
+      break;
+    case 0x0000: // NULL
+      tokenizer = script_data_double_escaped_state;
+      textrun.push(0xFFFD); // REPLACEMENT CHARACTER
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    default:
+      tokenizer = script_data_double_escaped_state;
+      textrun.push(c);
+      break;
+    }
+  }
+
+  function script_data_double_escaped_less_than_sign_state(c) {
+    if (c === 0x002F) { // SOLIDUS
+      beginTempBuf();
+      tokenizer = script_data_double_escape_end_state;
+      textrun.push(0x002F); // SOLIDUS
+    }
+    else {
+      reconsume(c, script_data_double_escaped_state);
+    }
+  }
+
+  function script_data_double_escape_end_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+    case 0x002F: // SOLIDUS
+    case 0x003E: // GREATER-THAN SIGN
+      if (buf2str(tempbuf) === "script") {
+        tokenizer = script_data_escaped_state;
+      }
+      else {
+        tokenizer = script_data_double_escaped_state;
+      }
+      textrun.push(c);
+      break;
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+      tempbuf.push(c + 0x0020);
+      textrun.push(c);
+      break;
+    case 0x0061:  // [a-z]
+    case 0x0062:case 0x0063:case 0x0064:case 0x0065:case 0x0066:
+    case 0x0067:case 0x0068:case 0x0069:case 0x006A:case 0x006B:
+    case 0x006C:case 0x006D:case 0x006E:case 0x006F:case 0x0070:
+    case 0x0071:case 0x0072:case 0x0073:case 0x0074:case 0x0075:
+    case 0x0076:case 0x0077:case 0x0078:case 0x0079:case 0x007A:
+      tempbuf.push(c);
+      textrun.push(c);
+      break;
+    default:
+      reconsume(c, script_data_double_escaped_state);
+      break;
+    }
+  }
+
+  function before_attribute_name_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      /* Ignore the character. */
+      break;
+    // For SOLIDUS, GREATER-THAN SIGN, and EOF, spec says "reconsume in
+    // the after attribute name state", but in our implementation that
+    // state always has an active attribute in attrnamebuf.  Just clone
+    // the rules here, without the addAttribute business.
+    case 0x002F: // SOLIDUS
+      tokenizer = self_closing_start_tag_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      tokenizer = data_state;
+      emitTag();
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    case 0x003D: // EQUALS SIGN
+      beginAttrName();
+      attrnamebuf += String.fromCharCode(c);
+      tokenizer = attribute_name_state;
+      break;
+    default:
+      if (handleSimpleAttribute()) break;
+      beginAttrName();
+      reconsume(c, attribute_name_state);
+      break;
+    }
+  }
+
+  // beginAttrName() must have been called before this point
+  // There is an active attribute in attrnamebuf (but not attrvaluebuf)
+  function attribute_name_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+    case 0x002F: // SOLIDUS
+    case 0x003E: // GREATER-THAN SIGN
+    case -1: // EOF
+      reconsume(c, after_attribute_name_state);
+      break;
+    case 0x003D: // EQUALS SIGN
+      tokenizer = before_attribute_value_state;
+      break;
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+      attrnamebuf += String.fromCharCode(c + 0x0020);
+      break;
+    case 0x0000: // NULL
+      attrnamebuf += String.fromCharCode(0xFFFD /* REPLACEMENT CHARACTER */);
+      break;
+    case 0x0022: // QUOTATION MARK
+    case 0x0027: // APOSTROPHE
+    case 0x003C: // LESS-THAN SIGN
+      /* falls through */
+    default:
+      attrnamebuf += getMatchingChars(ATTRNAME);
+      break;
+    }
+  }
+
+  // There is an active attribute in attrnamebuf, but not yet in attrvaluebuf.
+  function after_attribute_name_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      /* Ignore the character. */
+      break;
+    case 0x002F: // SOLIDUS
+      // Keep in sync with before_attribute_name_state.
+      addAttribute(attrnamebuf);
+      tokenizer = self_closing_start_tag_state;
+      break;
+    case 0x003D: // EQUALS SIGN
+      tokenizer = before_attribute_value_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      // Keep in sync with before_attribute_name_state.
+      tokenizer = data_state;
+      addAttribute(attrnamebuf);
+      emitTag();
+      break;
+    case -1: // EOF
+      // Keep in sync with before_attribute_name_state.
+      addAttribute(attrnamebuf);
+      emitEOF();
+      break;
+    default:
+      addAttribute(attrnamebuf);
+      beginAttrName();
+      reconsume(c, attribute_name_state);
+      break;
+    }
+  }
+
+  function before_attribute_value_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      /* Ignore the character. */
+      break;
+    case 0x0022: // QUOTATION MARK
+      beginAttrValue();
+      tokenizer = attribute_value_double_quoted_state;
+      break;
+    case 0x0027: // APOSTROPHE
+      beginAttrValue();
+      tokenizer = attribute_value_single_quoted_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      /* falls through */
+    default:
+      beginAttrValue();
+      reconsume(c, attribute_value_unquoted_state);
+      break;
+    }
+  }
+
+  function attribute_value_double_quoted_state(c) {
+    switch(c) {
+    case 0x0022: // QUOTATION MARK
+      addAttribute(attrnamebuf, attrvaluebuf);
+      tokenizer = after_attribute_value_quoted_state;
+      break;
+    case 0x0026: // AMPERSAND
+      return_state = attribute_value_double_quoted_state;
+      tokenizer = character_reference_state;
+      break;
+    case 0x0000: // NULL
+      attrvaluebuf += String.fromCharCode(0xFFFD /* REPLACEMENT CHARACTER */);
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    case 0x000A: // LF
+      // this could be a converted \r, so don't use getMatchingChars
+      attrvaluebuf += String.fromCharCode(c);
+      break;
+    default:
+      attrvaluebuf += getMatchingChars(DBLQUOTEATTRVAL);
+      break;
+    }
+  }
+
+  function attribute_value_single_quoted_state(c) {
+    switch(c) {
+    case 0x0027: // APOSTROPHE
+      addAttribute(attrnamebuf, attrvaluebuf);
+      tokenizer = after_attribute_value_quoted_state;
+      break;
+    case 0x0026: // AMPERSAND
+      return_state = attribute_value_single_quoted_state;
+      tokenizer = character_reference_state;
+      break;
+    case 0x0000: // NULL
+      attrvaluebuf += String.fromCharCode(0xFFFD /* REPLACEMENT CHARACTER */);
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    case 0x000A: // LF
+      // this could be a converted \r, so don't use getMatchingChars
+      attrvaluebuf += String.fromCharCode(c);
+      break;
+    default:
+      attrvaluebuf += getMatchingChars(SINGLEQUOTEATTRVAL);
+      break;
+    }
+  }
+
+  function attribute_value_unquoted_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      addAttribute(attrnamebuf, attrvaluebuf);
+      tokenizer = before_attribute_name_state;
+      break;
+    case 0x0026: // AMPERSAND
+      return_state = attribute_value_unquoted_state;
+      tokenizer = character_reference_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      addAttribute(attrnamebuf, attrvaluebuf);
+      tokenizer = data_state;
+      emitTag();
+      break;
+    case 0x0000: // NULL
+      attrvaluebuf += String.fromCharCode(0xFFFD /* REPLACEMENT CHARACTER */);
+      break;
+    case -1: // EOF
+      nextchar--; // pushback
+      tokenizer = data_state;
+      break;
+    case 0x0022: // QUOTATION MARK
+    case 0x0027: // APOSTROPHE
+    case 0x003C: // LESS-THAN SIGN
+    case 0x003D: // EQUALS SIGN
+    case 0x0060: // GRAVE ACCENT
+      /* falls through */
+    default:
+      attrvaluebuf += getMatchingChars(UNQUOTEDATTRVAL);
+      break;
+    }
+  }
+
+  function after_attribute_value_quoted_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      tokenizer = before_attribute_name_state;
+      break;
+    case 0x002F: // SOLIDUS
+      tokenizer = self_closing_start_tag_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      tokenizer = data_state;
+      emitTag();
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    default:
+      reconsume(c, before_attribute_name_state);
+      break;
+    }
+  }
+
+  function self_closing_start_tag_state(c) {
+    switch(c) {
+    case 0x003E: // GREATER-THAN SIGN
+      // Set the <i>self-closing flag</i> of the current tag token.
+      tokenizer = data_state;
+      emitSelfClosingTag(true);
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    default:
+      reconsume(c, before_attribute_name_state);
+      break;
+    }
+  }
+
+  function bogus_comment_state(c, lookahead, eof) {
+    var len = lookahead.length;
+
+    if (eof) {
+      nextchar += len-1; // don't consume the eof
+    }
+    else {
+      nextchar += len;
+    }
+
+    var comment = lookahead.substring(0, len-1);
+
+    comment = comment.replace(/\u0000/g,"\uFFFD");
+    comment = comment.replace(/\u000D\u000A/g,"\u000A");
+    comment = comment.replace(/\u000D/g,"\u000A");
+
+    insertToken(COMMENT, comment);
+    tokenizer = data_state;
+  }
+  bogus_comment_state.lookahead = ">";
+
+  function markup_declaration_open_state(c, lookahead, eof) {
+    if (lookahead[0] === "-" && lookahead[1] === "-") {
+      nextchar += 2;
+      beginComment();
+      tokenizer = comment_start_state;
+      return;
+    }
+
+    if (lookahead.toUpperCase() === "DOCTYPE") {
+      nextchar += 7;
+      tokenizer = doctype_state;
+    }
+    else if (lookahead === "[CDATA[" && cdataAllowed()) {
+      nextchar += 7;
+      tokenizer = cdata_section_state;
+    }
+    else {
+      tokenizer = bogus_comment_state;
+    }
+  }
+  markup_declaration_open_state.lookahead = 7;
+
+  function comment_start_state(c) {
+    beginComment();
+    switch(c) {
+    case 0x002D: // HYPHEN-MINUS
+      tokenizer = comment_start_dash_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      tokenizer = data_state;
+      insertToken(COMMENT, buf2str(commentbuf));
+      break; /* see comment in comment end state */
+    default:
+      reconsume(c, comment_state);
+      break;
+    }
+  }
+
+  function comment_start_dash_state(c) {
+    switch(c) {
+    case 0x002D: // HYPHEN-MINUS
+      tokenizer = comment_end_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      tokenizer = data_state;
+      insertToken(COMMENT, buf2str(commentbuf));
+      break;
+    case -1: // EOF
+      insertToken(COMMENT, buf2str(commentbuf));
+      emitEOF();
+      break; /* see comment in comment end state */
+    default:
+      commentbuf.push(0x002D /* HYPHEN-MINUS */);
+      reconsume(c, comment_state);
+      break;
+    }
+  }
+
+  function comment_state(c) {
+    switch(c) {
+    case 0x003C: // LESS-THAN SIGN
+      commentbuf.push(c);
+      tokenizer = comment_less_than_sign_state;
+      break;
+    case 0x002D: // HYPHEN-MINUS
+      tokenizer = comment_end_dash_state;
+      break;
+    case 0x0000: // NULL
+      commentbuf.push(0xFFFD /* REPLACEMENT CHARACTER */);
+      break;
+    case -1: // EOF
+      insertToken(COMMENT, buf2str(commentbuf));
+      emitEOF();
+      break; /* see comment in comment end state */
+    default:
+      commentbuf.push(c);
+      break;
+    }
+  }
+
+  function comment_less_than_sign_state(c) {
+    switch(c) {
+    case 0x0021: // EXCLAMATION MARK
+      commentbuf.push(c);
+      tokenizer = comment_less_than_sign_bang_state;
+      break;
+    case 0x003C: // LESS-THAN SIGN
+      commentbuf.push(c);
+      break;
+    default:
+      reconsume(c, comment_state);
+      break;
+    }
+  }
+
+  function comment_less_than_sign_bang_state(c) {
+    switch(c) {
+    case 0x002D: // HYPHEN-MINUS
+      tokenizer = comment_less_than_sign_bang_dash_state;
+      break;
+    default:
+      reconsume(c, comment_state);
+      break;
+    }
+  }
+
+  function comment_less_than_sign_bang_dash_state(c) {
+    switch(c) {
+    case 0x002D: // HYPHEN-MINUS
+      tokenizer = comment_less_than_sign_bang_dash_dash_state;
+      break;
+    default:
+      reconsume(c, comment_end_dash_state);
+      break;
+    }
+  }
+
+  function comment_less_than_sign_bang_dash_dash_state(c) {
+    switch(c) {
+    case 0x003E: // GREATER-THAN SIGN
+    case -1: // EOF
+      reconsume(c, comment_end_state);
+      break;
+    default:
+      // parse error
+      reconsume(c, comment_end_state);
+      break;
+    }
+  }
+
+  function comment_end_dash_state(c) {
+    switch(c) {
+    case 0x002D: // HYPHEN-MINUS
+      tokenizer = comment_end_state;
+      break;
+    case -1: // EOF
+      insertToken(COMMENT, buf2str(commentbuf));
+      emitEOF();
+      break; /* see comment in comment end state */
+    default:
+      commentbuf.push(0x002D /* HYPHEN-MINUS */);
+      reconsume(c, comment_state);
+      break;
+    }
+  }
+
+  function comment_end_state(c) {
+    switch(c) {
+    case 0x003E: // GREATER-THAN SIGN
+      tokenizer = data_state;
+      insertToken(COMMENT, buf2str(commentbuf));
+      break;
+    case 0x0021: // EXCLAMATION MARK
+      tokenizer = comment_end_bang_state;
+      break;
+    case 0x002D: // HYPHEN-MINUS
+      commentbuf.push(0x002D);
+      break;
+    case -1: // EOF
+      insertToken(COMMENT, buf2str(commentbuf));
+      emitEOF();
+      break; /* For security reasons: otherwise, hostile user could put a script in a comment e.g. in a blog comment and then DOS the server so that the end tag isn't read, and then the commented script tag would be treated as live code */
+    default:
+      commentbuf.push(0x002D);
+      commentbuf.push(0x002D);
+      reconsume(c, comment_state);
+      break;
+    }
+  }
+
+  function comment_end_bang_state(c) {
+    switch(c) {
+    case 0x002D: // HYPHEN-MINUS
+      commentbuf.push(0x002D);
+      commentbuf.push(0x002D);
+      commentbuf.push(0x0021);
+      tokenizer = comment_end_dash_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      tokenizer = data_state;
+      insertToken(COMMENT, buf2str(commentbuf));
+      break;
+    case -1: // EOF
+      insertToken(COMMENT, buf2str(commentbuf));
+      emitEOF();
+      break; /* see comment in comment end state */
+    default:
+      commentbuf.push(0x002D);
+      commentbuf.push(0x002D);
+      commentbuf.push(0x0021);
+      reconsume(c, comment_state);
+      break;
+    }
+  }
+
+  function doctype_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      tokenizer = before_doctype_name_state;
+      break;
+    case -1: // EOF
+      beginDoctype();
+      forcequirks();
+      emitDoctype();
+      emitEOF();
+      break;
+    default:
+      reconsume(c, before_doctype_name_state);
+      break;
+    }
+  }
+
+  function before_doctype_name_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      /* Ignore the character. */
+      break;
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+      beginDoctype();
+      doctypenamebuf.push(c + 0x0020);
+      tokenizer = doctype_name_state;
+      break;
+    case 0x0000: // NULL
+      beginDoctype();
+      doctypenamebuf.push(0xFFFD);
+      tokenizer = doctype_name_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      beginDoctype();
+      forcequirks();
+      tokenizer = data_state;
+      emitDoctype();
+      break;
+    case -1: // EOF
+      beginDoctype();
+      forcequirks();
+      emitDoctype();
+      emitEOF();
+      break;
+    default:
+      beginDoctype();
+      doctypenamebuf.push(c);
+      tokenizer = doctype_name_state;
+      break;
+    }
+  }
+
+  function doctype_name_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      tokenizer = after_doctype_name_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      tokenizer = data_state;
+      emitDoctype();
+      break;
+    case 0x0041:  // [A-Z]
+    case 0x0042:case 0x0043:case 0x0044:case 0x0045:case 0x0046:
+    case 0x0047:case 0x0048:case 0x0049:case 0x004A:case 0x004B:
+    case 0x004C:case 0x004D:case 0x004E:case 0x004F:case 0x0050:
+    case 0x0051:case 0x0052:case 0x0053:case 0x0054:case 0x0055:
+    case 0x0056:case 0x0057:case 0x0058:case 0x0059:case 0x005A:
+      doctypenamebuf.push(c + 0x0020);
+      break;
+    case 0x0000: // NULL
+      doctypenamebuf.push(0xFFFD /* REPLACEMENT CHARACTER */);
+      break;
+    case -1: // EOF
+      forcequirks();
+      emitDoctype();
+      emitEOF();
+      break;
+    default:
+      doctypenamebuf.push(c);
+      break;
+    }
+  }
+
+  function after_doctype_name_state(c, lookahead, eof) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      /* Ignore the character. */
+      nextchar += 1;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      tokenizer = data_state;
+      nextchar += 1;
+      emitDoctype();
+      break;
+    case -1: // EOF
+      forcequirks();
+      emitDoctype();
+      emitEOF();
+      break;
+    default:
+      lookahead = lookahead.toUpperCase();
+      if (lookahead === "PUBLIC") {
+        nextchar += 6;
+        tokenizer = after_doctype_public_keyword_state;
+      }
+      else if (lookahead === "SYSTEM") {
+        nextchar += 6;
+        tokenizer = after_doctype_system_keyword_state;
+      }
+      else {
+        forcequirks();
+        tokenizer = bogus_doctype_state;
+      }
+      break;
+    }
+  }
+  after_doctype_name_state.lookahead = 6;
+
+  function after_doctype_public_keyword_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      tokenizer = before_doctype_public_identifier_state;
+      break;
+    case 0x0022: // QUOTATION MARK
+      beginDoctypePublicId();
+      tokenizer = doctype_public_identifier_double_quoted_state;
+      break;
+    case 0x0027: // APOSTROPHE
+      beginDoctypePublicId();
+      tokenizer = doctype_public_identifier_single_quoted_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      forcequirks();
+      tokenizer = data_state;
+      emitDoctype();
+      break;
+    case -1: // EOF
+      forcequirks();
+      emitDoctype();
+      emitEOF();
+      break;
+    default:
+      forcequirks();
+      tokenizer = bogus_doctype_state;
+      break;
+    }
+  }
+
+  function before_doctype_public_identifier_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      /* Ignore the character. */
+      break;
+    case 0x0022: // QUOTATION MARK
+      beginDoctypePublicId();
+      tokenizer = doctype_public_identifier_double_quoted_state;
+      break;
+    case 0x0027: // APOSTROPHE
+      beginDoctypePublicId();
+      tokenizer = doctype_public_identifier_single_quoted_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      forcequirks();
+      tokenizer = data_state;
+      emitDoctype();
+      break;
+    case -1: // EOF
+      forcequirks();
+      emitDoctype();
+      emitEOF();
+      break;
+    default:
+      forcequirks();
+      tokenizer = bogus_doctype_state;
+      break;
+    }
+  }
+
+  function doctype_public_identifier_double_quoted_state(c) {
+    switch(c) {
+    case 0x0022: // QUOTATION MARK
+      tokenizer = after_doctype_public_identifier_state;
+      break;
+    case 0x0000: // NULL
+      doctypepublicbuf.push(0xFFFD /* REPLACEMENT CHARACTER */);
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      forcequirks();
+      tokenizer = data_state;
+      emitDoctype();
+      break;
+    case -1: // EOF
+      forcequirks();
+      emitDoctype();
+      emitEOF();
+      break;
+    default:
+      doctypepublicbuf.push(c);
+      break;
+    }
+  }
+
+  function doctype_public_identifier_single_quoted_state(c) {
+    switch(c) {
+    case 0x0027: // APOSTROPHE
+      tokenizer = after_doctype_public_identifier_state;
+      break;
+    case 0x0000: // NULL
+      doctypepublicbuf.push(0xFFFD /* REPLACEMENT CHARACTER */);
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      forcequirks();
+      tokenizer = data_state;
+      emitDoctype();
+      break;
+    case -1: // EOF
+      forcequirks();
+      emitDoctype();
+      emitEOF();
+      break;
+    default:
+      doctypepublicbuf.push(c);
+      break;
+    }
+  }
+
+  function after_doctype_public_identifier_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      tokenizer = between_doctype_public_and_system_identifiers_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      tokenizer = data_state;
+      emitDoctype();
+      break;
+    case 0x0022: // QUOTATION MARK
+      beginDoctypeSystemId();
+      tokenizer = doctype_system_identifier_double_quoted_state;
+      break;
+    case 0x0027: // APOSTROPHE
+      beginDoctypeSystemId();
+      tokenizer = doctype_system_identifier_single_quoted_state;
+      break;
+    case -1: // EOF
+      forcequirks();
+      emitDoctype();
+      emitEOF();
+      break;
+    default:
+      forcequirks();
+      tokenizer = bogus_doctype_state;
+      break;
+    }
+  }
+
+  function between_doctype_public_and_system_identifiers_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE Ignore the character.
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      tokenizer = data_state;
+      emitDoctype();
+      break;
+    case 0x0022: // QUOTATION MARK
+      beginDoctypeSystemId();
+      tokenizer = doctype_system_identifier_double_quoted_state;
+      break;
+    case 0x0027: // APOSTROPHE
+      beginDoctypeSystemId();
+      tokenizer = doctype_system_identifier_single_quoted_state;
+      break;
+    case -1: // EOF
+      forcequirks();
+      emitDoctype();
+      emitEOF();
+      break;
+    default:
+      forcequirks();
+      tokenizer = bogus_doctype_state;
+      break;
+    }
+  }
+
+  function after_doctype_system_keyword_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      tokenizer = before_doctype_system_identifier_state;
+      break;
+    case 0x0022: // QUOTATION MARK
+      beginDoctypeSystemId();
+      tokenizer = doctype_system_identifier_double_quoted_state;
+      break;
+    case 0x0027: // APOSTROPHE
+      beginDoctypeSystemId();
+      tokenizer = doctype_system_identifier_single_quoted_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      forcequirks();
+      tokenizer = data_state;
+      emitDoctype();
+      break;
+    case -1: // EOF
+      forcequirks();
+      emitDoctype();
+      emitEOF();
+      break;
+    default:
+      forcequirks();
+      tokenizer = bogus_doctype_state;
+      break;
+    }
+  }
+
+  function before_doctype_system_identifier_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE Ignore the character.
+      break;
+    case 0x0022: // QUOTATION MARK
+      beginDoctypeSystemId();
+      tokenizer = doctype_system_identifier_double_quoted_state;
+      break;
+    case 0x0027: // APOSTROPHE
+      beginDoctypeSystemId();
+      tokenizer = doctype_system_identifier_single_quoted_state;
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      forcequirks();
+      tokenizer = data_state;
+      emitDoctype();
+      break;
+    case -1: // EOF
+      forcequirks();
+      emitDoctype();
+      emitEOF();
+      break;
+    default:
+      forcequirks();
+      tokenizer = bogus_doctype_state;
+      break;
+    }
+  }
+
+  function doctype_system_identifier_double_quoted_state(c) {
+    switch(c) {
+    case 0x0022: // QUOTATION MARK
+      tokenizer = after_doctype_system_identifier_state;
+      break;
+    case 0x0000: // NULL
+      doctypesystembuf.push(0xFFFD /* REPLACEMENT CHARACTER */);
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      forcequirks();
+      tokenizer = data_state;
+      emitDoctype();
+      break;
+    case -1: // EOF
+      forcequirks();
+      emitDoctype();
+      emitEOF();
+      break;
+    default:
+      doctypesystembuf.push(c);
+      break;
+    }
+  }
+
+  function doctype_system_identifier_single_quoted_state(c) {
+    switch(c) {
+    case 0x0027: // APOSTROPHE
+      tokenizer = after_doctype_system_identifier_state;
+      break;
+    case 0x0000: // NULL
+      doctypesystembuf.push(0xFFFD /* REPLACEMENT CHARACTER */);
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      forcequirks();
+      tokenizer = data_state;
+      emitDoctype();
+      break;
+    case -1: // EOF
+      forcequirks();
+      emitDoctype();
+      emitEOF();
+      break;
+    default:
+      doctypesystembuf.push(c);
+      break;
+    }
+  }
+
+  function after_doctype_system_identifier_state(c) {
+    switch(c) {
+    case 0x0009: // CHARACTER TABULATION (tab)
+    case 0x000A: // LINE FEED (LF)
+    case 0x000C: // FORM FEED (FF)
+    case 0x0020: // SPACE
+      /* Ignore the character. */
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      tokenizer = data_state;
+      emitDoctype();
+      break;
+    case -1: // EOF
+      forcequirks();
+      emitDoctype();
+      emitEOF();
+      break;
+    default:
+      tokenizer = bogus_doctype_state;
+      /* This does *not* set the DOCTYPE token's force-quirks flag. */
+      break;
+    }
+  }
+
+  function bogus_doctype_state(c) {
+    switch(c) {
+    case 0x003E: // GREATER-THAN SIGN
+      tokenizer = data_state;
+      emitDoctype();
+      break;
+    case -1: // EOF
+      emitDoctype();
+      emitEOF();
+      break;
+    default:
+      /* Ignore the character. */
+      break;
+    }
+  }
+
+  function cdata_section_state(c) {
+    switch(c) {
+    case 0x005D: // RIGHT SQUARE BRACKET
+      tokenizer = cdata_section_bracket_state;
+      break;
+    case -1: // EOF
+      emitEOF();
+      break;
+    case 0x0000: // NULL
+      textIncludesNUL = true;
+      /* fall through */
+    default:
+      // Instead of just pushing a single character and then
+      // coming back to the very same place, lookahead and
+      // emit everything we can at once.
+      /*jshint -W030 */
+      emitCharsWhile(CDATATEXT) || textrun.push(c);
+      break;
+    }
+  }
+
+  function cdata_section_bracket_state(c) {
+    switch(c) {
+    case 0x005D: // RIGHT SQUARE BRACKET
+      tokenizer = cdata_section_end_state;
+      break;
+    default:
+      textrun.push(0x005D);
+      reconsume(c, cdata_section_state);
+      break;
+    }
+  }
+
+  function cdata_section_end_state(c) {
+    switch(c) {
+    case 0x005D: // RIGHT SQUARE BRACKET
+      textrun.push(0x005D);
+      break;
+    case 0x003E: // GREATER-THAN SIGN
+      flushText();
+      tokenizer = data_state;
+      break;
+    default:
+      textrun.push(0x005D);
+      textrun.push(0x005D);
+      reconsume(c, cdata_section_state);
+      break;
+    }
+  }
+
+  function character_reference_state(c) {
+    beginTempBuf();
+    tempbuf.push(0x0026);
+    switch(c) {
+    case 0x0009: // TAB
+    case 0x000A: // LINE FEED
+    case 0x000C: // FORM FEED
+    case 0x0020: // SPACE
+    case 0x003C: // LESS-THAN SIGN
+    case 0x0026: // AMPERSAND
+    case -1: // EOF
+      reconsume(c, character_reference_end_state);
+      break;
+    case 0x0023: // NUMBER SIGN
+      tempbuf.push(c);
+      tokenizer = numeric_character_reference_state;
+      break;
+    default:
+      reconsume(c, named_character_reference_state);
+      break;
+    }
+  }
+
+  function named_character_reference_state(c) {
+    NAMEDCHARREF.lastIndex = nextchar; // w/ lookahead no char has been consumed
+    var matched = NAMEDCHARREF.exec(chars);
+    if (!matched) throw new Error("should never happen");
+    var name = matched[1];
+    if (!name) {
+      // If no match can be made, switch to the character reference end state
+      tokenizer = character_reference_end_state;
+      return;
+    }
+
+    // Consume the matched characters and append them to temporary buffer
+    nextchar += name.length;
+    pushAll(tempbuf, str2buf(name));
+
+    switch(return_state) {
+    case attribute_value_double_quoted_state:
+    case attribute_value_single_quoted_state:
+    case attribute_value_unquoted_state:
+      // If the character reference was consumed as part of an attribute...
+      if (name[name.length-1] !== ';') { // ...and the last char is not ;
+        if (/[=A-Za-z0-9]/.test(chars[nextchar])) {
+          tokenizer = character_reference_end_state;
+          return;
+        }
+      }
+      break;
+    default:
+      break;
+    }
+
+    beginTempBuf();
+    var rv = namedCharRefs[name];
+    if (typeof rv === 'number') {
+      tempbuf.push(rv);
+    } else {
+      pushAll(tempbuf, rv);
+    }
+    tokenizer = character_reference_end_state;
+  }
+  // We might need to pause tokenization until we have enough characters
+  // in the buffer for longest possible character reference.
+  named_character_reference_state.lookahead = -NAMEDCHARREF_MAXLEN;
+
+  function numeric_character_reference_state(c) {
+    character_reference_code = 0;
+    switch(c) {
+    case 0x0078: // x
+    case 0x0058: // X
+      tempbuf.push(c);
+      tokenizer = hexadecimal_character_reference_start_state;
+      break;
+    default:
+      reconsume(c, decimal_character_reference_start_state);
+      break;
+    }
+  }
+
+  function hexadecimal_character_reference_start_state(c) {
+    switch(c) {
+    case 0x0030: case 0x0031: case 0x0032: case 0x0033: case 0x0034:
+    case 0x0035: case 0x0036: case 0x0037: case 0x0038: case 0x0039: // [0-9]
+    case 0x0041: case 0x0042: case 0x0043: case 0x0044: case 0x0045:
+    case 0x0046: // [A-F]
+    case 0x0061: case 0x0062: case 0x0063: case 0x0064: case 0x0065:
+    case 0x0066: // [a-f]
+      reconsume(c, hexadecimal_character_reference_state);
+      break;
+    default:
+      reconsume(c, character_reference_end_state);
+      break;
+    }
+  }
+
+  function decimal_character_reference_start_state(c) {
+    switch(c) {
+    case 0x0030: case 0x0031: case 0x0032: case 0x0033: case 0x0034:
+    case 0x0035: case 0x0036: case 0x0037: case 0x0038: case 0x0039: // [0-9]
+      reconsume(c, decimal_character_reference_state);
+      break;
+    default:
+      reconsume(c, character_reference_end_state);
+      break;
+    }
+  }
+
+  function hexadecimal_character_reference_state(c) {
+    switch(c) {
+    case 0x0041: case 0x0042: case 0x0043: case 0x0044: case 0x0045:
+    case 0x0046: // [A-F]
+      character_reference_code *= 16;
+      character_reference_code += (c - 0x0037);
+      break;
+    case 0x0061: case 0x0062: case 0x0063: case 0x0064: case 0x0065:
+    case 0x0066: // [a-f]
+      character_reference_code *= 16;
+      character_reference_code += (c - 0x0057);
+      break;
+    case 0x0030: case 0x0031: case 0x0032: case 0x0033: case 0x0034:
+    case 0x0035: case 0x0036: case 0x0037: case 0x0038: case 0x0039: // [0-9]
+      character_reference_code *= 16;
+      character_reference_code += (c - 0x0030);
+      break;
+    case 0x003B: // SEMICOLON
+      tokenizer = numeric_character_reference_end_state;
+      break;
+    default:
+      reconsume(c, numeric_character_reference_end_state);
+      break;
+    }
+  }
+
+  function decimal_character_reference_state(c) {
+    switch(c) {
+    case 0x0030: case 0x0031: case 0x0032: case 0x0033: case 0x0034:
+    case 0x0035: case 0x0036: case 0x0037: case 0x0038: case 0x0039: // [0-9]
+      character_reference_code *= 10;
+      character_reference_code += (c - 0x0030);
+      break;
+    case 0x003B: // SEMICOLON
+      tokenizer = numeric_character_reference_end_state;
+      break;
+    default:
+      reconsume(c, numeric_character_reference_end_state);
+      break;
+    }
+  }
+
+  function numeric_character_reference_end_state(c) {
+    if (character_reference_code in numericCharRefReplacements) {
+      character_reference_code = numericCharRefReplacements[character_reference_code];
+    } else if (character_reference_code > 0x10FFFF || (character_reference_code >= 0xD800 && character_reference_code < 0xE000)) {
+      character_reference_code = 0xFFFD;
+    }
+
+    beginTempBuf();
+    if (character_reference_code <= 0xFFFF) {
+      tempbuf.push(character_reference_code);
+    } else {
+      character_reference_code = character_reference_code - 0x10000;
+      /* jshint bitwise: false */
+      tempbuf.push(0xD800 + (character_reference_code >> 10));
+      tempbuf.push(0xDC00 + (character_reference_code & 0x03FF));
+    }
+    reconsume(c, character_reference_end_state);
+  }
+
+  function character_reference_end_state(c) {
+    switch(return_state) {
+    case attribute_value_double_quoted_state:
+    case attribute_value_single_quoted_state:
+    case attribute_value_unquoted_state:
+      // append each character to the current attribute's value
+      attrvaluebuf += buf2str(tempbuf);
+      break;
+    default:
+      pushAll(textrun, tempbuf);
+      break;
+    }
+    reconsume(c, return_state);
+  }
+
+  /***
+   * The tree builder insertion modes
+   */
+
+  // 11.2.5.4.1 The "initial" insertion mode
+  function initial_mode(t, value, arg3, arg4) {
+    switch(t) {
+    case 1: // TEXT
+      value = value.replace(LEADINGWS, ""); // Ignore spaces
+      if (value.length === 0) return; // Are we done?
+      break; // Handle anything non-space text below
+    case 4: // COMMENT
+      doc._appendChild(doc.createComment(value));
+      return;
+    case 5: // DOCTYPE
+      var name = value;
+      var publicid = arg3;
+      var systemid = arg4;
+      // Use the constructor directly instead of
+      // implementation.createDocumentType because the create
+      // function throws errors on invalid characters, and
+      // we don't want the parser to throw them.
+      doc.appendChild(new DocumentType(doc, name, publicid, systemid));
+
+      // Note that there is no public API for setting quirks mode We can
+      // do this here because we have access to implementation details
+      if (force_quirks ||
+        name.toLowerCase() !== "html" ||
+        quirkyPublicIds.test(publicid) ||
+        (systemid && systemid.toLowerCase() === quirkySystemId) ||
+        (systemid === undefined &&
+         conditionallyQuirkyPublicIds.test(publicid)))
+        doc._quirks = true;
+      else if (limitedQuirkyPublicIds.test(publicid) ||
+           (systemid !== undefined &&
+            conditionallyQuirkyPublicIds.test(publicid)))
+        doc._limitedQuirks = true;
+      parser = before_html_mode;
+      return;
+    }
+
+    // tags or non-whitespace text
+    doc._quirks = true;
+    parser = before_html_mode;
+    parser(t,value,arg3,arg4);
+  }
+
+  // 11.2.5.4.2 The "before html" insertion mode
+  function before_html_mode(t,value,arg3,arg4) {
+    var elt;
+    switch(t) {
+    case 1: // TEXT
+      value = value.replace(LEADINGWS, ""); // Ignore spaces
+      if (value.length === 0) return; // Are we done?
+      break; // Handle anything non-space text below
+    case 5: // DOCTYPE
+      /* ignore the token */
+      return;
+    case 4: // COMMENT
+      doc._appendChild(doc.createComment(value));
+      return;
+    case 2: // TAG
+      if (value === "html") {
+        elt = createHTMLElt(doc, value, arg3);
+        stack.push(elt);
+        doc.appendChild(elt);
+        // XXX: handle application cache here
+        parser = before_head_mode;
+        return;
+      }
+      break;
+    case 3: // ENDTAG
+      switch(value) {
+      case "html":
+      case "head":
+      case "body":
+      case "br":
+        break;  // fall through on these
+      default:
+        return; // ignore most end tags
+      }
+    }
+
+    // Anything that didn't get handled above is handled like this:
+    elt = createHTMLElt(doc, "html", null);
+    stack.push(elt);
+    doc.appendChild(elt);
+    // XXX: handle application cache here
+    parser = before_head_mode;
+    parser(t,value,arg3,arg4);
+  }
+
+  // 11.2.5.4.3 The "before head" insertion mode
+  function before_head_mode(t,value,arg3,arg4) {
+    switch(t) {
+    case 1: // TEXT
+      value = value.replace(LEADINGWS, "");  // Ignore spaces
+      if (value.length === 0) return; // Are we done?
+      break;  // Handle anything non-space text below
+    case 5: // DOCTYPE
+      /* ignore the token */
+      return;
+    case 4: // COMMENT
+      insertComment(value);
+      return;
+    case 2: // TAG
+      switch(value) {
+      case "html":
+        in_body_mode(t,value,arg3,arg4);
+        return;
+      case "head":
+        var elt = insertHTMLElement(value, arg3);
+        head_element_pointer = elt;
+        parser = in_head_mode;
+        return;
+      }
+      break;
+    case 3: // ENDTAG
+      switch(value) {
+      case "html":
+      case "head":
+      case "body":
+      case "br":
+        break;
+      default:
+        return; // ignore most end tags
+      }
+    }
+
+    // If not handled explicitly above
+    before_head_mode(TAG, "head", null); // create a head tag
+    parser(t, value, arg3, arg4); // then try again with this token
+  }
+
+  function in_head_mode(t, value, arg3, arg4) {
+    switch(t) {
+    case 1: // TEXT
+      var ws = value.match(LEADINGWS);
+      if (ws) {
+        insertText(ws[0]);
+        value = value.substring(ws[0].length);
+      }
+      if (value.length === 0) return;
+      break; // Handle non-whitespace below
+    case 4: // COMMENT
+      insertComment(value);
+      return;
+    case 5: // DOCTYPE
+      return;
+    case 2: // TAG
+      switch(value) {
+      case "html":
+        in_body_mode(t, value, arg3, arg4);
+        return;
+      case "meta":
+        // XXX:
+        // May need to change the encoding based on this tag
+        /* falls through */
+      case "base":
+      case "basefont":
+      case "bgsound":
+      case "link":
+        insertHTMLElement(value, arg3);
+        stack.pop();
+        return;
+      case "title":
+        parseRCDATA(value, arg3);
+        return;
+      case "noscript":
+        if (!scripting_enabled) {
+          insertHTMLElement(value, arg3);
+          parser = in_head_noscript_mode;
+          return;
+        }
+        // Otherwise, if scripting is enabled...
+        /* falls through */
+      case "noframes":
+      case "style":
+        parseRawText(value,arg3);
+        return;
+      case "script":
+        insertElement(function(doc) {
+          var elt = createHTMLElt(doc, value, arg3);
+          elt._parser_inserted = true;
+          elt._force_async = false;
+          if (fragment) elt._already_started = true;
+          flushText();
+          return elt;
+        });
+        tokenizer = script_data_state;
+        originalInsertionMode = parser;
+        parser = text_mode;
+        return;
+      case "template":
+        insertHTMLElement(value, arg3);
+        afe.insertMarker();
+        frameset_ok = false;
+        parser = in_template_mode;
+        templateInsertionModes.push(parser);
+        return;
+      case "head":
+        return; // ignore it
+      }
+      break;
+    case 3: // ENDTAG
+      switch(value) {
+      case "head":
+        stack.pop();
+        parser = after_head_mode;
+        return;
+      case "body":
+      case "html":
+      case "br":
+        break; // handle these at the bottom of the function
+      case "template":
+        if (!stack.contains("template")) {
+          return;
+        }
+        stack.generateImpliedEndTags(null, "thorough");
+        stack.popTag("template");
+        afe.clearToMarker();
+        templateInsertionModes.pop();
+        resetInsertionMode();
+        return;
+      default:
+        // ignore any other end tag
+        return;
+      }
+      break;
+    }
+
+    // If not handled above
+    in_head_mode(ENDTAG, "head", null);   // synthetic </head>
+    parser(t, value, arg3, arg4);   // Then redo this one
+  }
+
+  // 13.2.5.4.5 The "in head noscript" insertion mode
+  function in_head_noscript_mode(t, value, arg3, arg4) {
+    switch(t) {
+    case 5: // DOCTYPE
+      return;
+    case 4: // COMMENT
+      in_head_mode(t, value);
+      return;
+    case 1: // TEXT
+      var ws = value.match(LEADINGWS);
+      if (ws) {
+        in_head_mode(t, ws[0]);
+        value = value.substring(ws[0].length);
+      }
+      if (value.length === 0) return; // no more text
+      break; // Handle non-whitespace below
+    case 2: // TAG
+      switch(value) {
+      case "html":
+        in_body_mode(t, value, arg3, arg4);
+        return;
+      case "basefont":
+      case "bgsound":
+      case "link":
+      case "meta":
+      case "noframes":
+      case "style":
+        in_head_mode(t, value, arg3);
+        return;
+      case "head":
+      case "noscript":
+        return;
+      }
+      break;
+    case 3: // ENDTAG
+      switch(value) {
+      case "noscript":
+        stack.pop();
+        parser = in_head_mode;
+        return;
+      case "br":
+        break;  // goes to the outer default
+      default:
+        return; // ignore other end tags
+      }
+      break;
+    }
+
+    // If not handled above
+    in_head_noscript_mode(ENDTAG, "noscript", null);
+    parser(t, value, arg3, arg4);
+  }
+
+  function after_head_mode(t, value, arg3, arg4) {
+    switch(t) {
+    case 1: // TEXT
+      var ws = value.match(LEADINGWS);
+      if (ws) {
+        insertText(ws[0]);
+        value = value.substring(ws[0].length);
+      }
+      if (value.length === 0) return;
+      break; // Handle non-whitespace below
+    case 4: // COMMENT
+      insertComment(value);
+      return;
+    case 5: // DOCTYPE
+      return;
+    case 2: // TAG
+      switch(value) {
+      case "html":
+        in_body_mode(t, value, arg3, arg4);
+        return;
+      case "body":
+        insertHTMLElement(value, arg3);
+        frameset_ok = false;
+        parser = in_body_mode;
+        return;
+      case "frameset":
+        insertHTMLElement(value, arg3);
+        parser = in_frameset_mode;
+        return;
+      case "base":
+      case "basefont":
+      case "bgsound":
+      case "link":
+      case "meta":
+      case "noframes":
+      case "script":
+      case "style":
+      case "template":
+      case "title":
+        stack.push(head_element_pointer);
+        in_head_mode(TAG, value, arg3);
+        stack.removeElement(head_element_pointer);
+        return;
+      case "head":
+        return;
+      }
+      break;
+    case 3: // ENDTAG
+      switch(value) {
+      case "template":
+        return in_head_mode(t, value, arg3, arg4);
+      case "body":
+      case "html":
+      case "br":
+        break;
+      default:
+        return;  // ignore any other end tag
+      }
+      break;
+    }
+
+    after_head_mode(TAG, "body", null);
+    frameset_ok = true;
+    parser(t, value, arg3, arg4);
+  }
+
+  // 13.2.5.4.7 The "in body" insertion mode
+  function in_body_mode(t,value,arg3,arg4) {
+    var body, i, node, elt;
+    switch(t) {
+    case 1: // TEXT
+      if (textIncludesNUL) {
+        value = value.replace(NULCHARS, "");
+        if (value.length === 0) return;
+      }
+      // If any non-space characters
+      if (frameset_ok && NONWS.test(value))
+        frameset_ok = false;
+      afereconstruct();
+      insertText(value);
+      return;
+    case 5: // DOCTYPE
+      return;
+    case 4: // COMMENT
+      insertComment(value);
+      return;
+    case -1: // EOF
+      if (templateInsertionModes.length) {
+        return in_template_mode(t);
+      }
+      stopParsing();
+      return;
+    case 2: // TAG
+      switch(value) {
+      case "html":
+        if (stack.contains("template")) {
+          return;
+        }
+        transferAttributes(arg3, stack.elements[0]);
+        return;
+      case "base":
+      case "basefont":
+      case "bgsound":
+      case "link":
+      case "meta":
+      case "noframes":
+      case "script":
+      case "style":
+      case "template":
+      case "title":
+        in_head_mode(TAG, value, arg3);
+        return;
+      case "body":
+        body = stack.elements[1];
+        if (!body || !(body instanceof impl.HTMLBodyElement) ||
+            stack.contains("template"))
+          return;
+        frameset_ok = false;
+        transferAttributes(arg3, body);
+        return;
+      case "frameset":
+        if (!frameset_ok) return;
+        body = stack.elements[1];
+        if (!body || !(body instanceof impl.HTMLBodyElement))
+          return;
+        if (body.parentNode) body.parentNode.removeChild(body);
+        while(!(stack.top instanceof impl.HTMLHtmlElement))
+          stack.pop();
+        insertHTMLElement(value, arg3);
+        parser = in_frameset_mode;
+        return;
+
+      case "address":
+      case "article":
+      case "aside":
+      case "blockquote":
+      case "center":
+      case "details":
+      case "dialog":
+      case "dir":
+      case "div":
+      case "dl":
+      case "fieldset":
+      case "figcaption":
+      case "figure":
+      case "footer":
+      case "header":
+      case "hgroup":
+      case "main":
+      case "nav":
+      case "ol":
+      case "p":
+      case "section":
+      case "summary":
+      case "ul":
+        if (stack.inButtonScope("p")) in_body_mode(ENDTAG, "p");
+        insertHTMLElement(value, arg3);
+        return;
+
+      case "menu":
+        if (stack.inButtonScope("p")) in_body_mode(ENDTAG, "p");
+        if (isA(stack.top, 'menuitem')) {
+          stack.pop();
+        }
+        insertHTMLElement(value, arg3);
+        return;
+
+      case "h1":
+      case "h2":
+      case "h3":
+      case "h4":
+      case "h5":
+      case "h6":
+        if (stack.inButtonScope("p")) in_body_mode(ENDTAG, "p");
+        if (stack.top instanceof impl.HTMLHeadingElement)
+          stack.pop();
+        insertHTMLElement(value, arg3);
+        return;
+
+      case "pre":
+      case "listing":
+        if (stack.inButtonScope("p")) in_body_mode(ENDTAG, "p");
+        insertHTMLElement(value, arg3);
+        ignore_linefeed = true;
+        frameset_ok = false;
+        return;
+
+      case "form":
+        if (form_element_pointer && !stack.contains("template")) return;
+        if (stack.inButtonScope("p")) in_body_mode(ENDTAG, "p");
+        elt = insertHTMLElement(value, arg3);
+        if (!stack.contains("template"))
+          form_element_pointer = elt;
+        return;
+
+      case "li":
+        frameset_ok = false;
+        for(i = stack.elements.length-1; i >= 0; i--) {
+          node = stack.elements[i];
+          if (node instanceof impl.HTMLLIElement) {
+            in_body_mode(ENDTAG, "li");
+            break;
+          }
+          if (isA(node, specialSet) && !isA(node, addressdivpSet))
+            break;
+        }
+        if (stack.inButtonScope("p")) in_body_mode(ENDTAG, "p");
+        insertHTMLElement(value, arg3);
+        return;
+
+      case "dd":
+      case "dt":
+        frameset_ok = false;
+        for(i = stack.elements.length-1; i >= 0; i--) {
+          node = stack.elements[i];
+          if (isA(node, dddtSet)) {
+            in_body_mode(ENDTAG, node.localName);
+            break;
+          }
+          if (isA(node, specialSet) && !isA(node, addressdivpSet))
+            break;
+        }
+        if (stack.inButtonScope("p")) in_body_mode(ENDTAG, "p");
+        insertHTMLElement(value, arg3);
+        return;
+
+      case "plaintext":
+        if (stack.inButtonScope("p")) in_body_mode(ENDTAG, "p");
+        insertHTMLElement(value, arg3);
+        tokenizer = plaintext_state;
+        return;
+
+      case "button":
+        if (stack.inScope("button")) {
+          in_body_mode(ENDTAG, "button");
+          parser(t, value, arg3, arg4);
+        }
+        else {
+          afereconstruct();
+          insertHTMLElement(value, arg3);
+          frameset_ok = false;
+        }
+        return;
+
+      case "a":
+        var activeElement = afe.findElementByTag("a");
+        if (activeElement) {
+          in_body_mode(ENDTAG, value);
+          afe.remove(activeElement);
+          stack.removeElement(activeElement);
+        }
+        /* falls through */
+      case "b":
+      case "big":
+      case "code":
+      case "em":
+      case "font":
+      case "i":
+      case "s":
+      case "small":
+      case "strike":
+      case "strong":
+      case "tt":
+      case "u":
+        afereconstruct();
+        afe.push(insertHTMLElement(value,arg3), arg3);
+        return;
+
+      case "nobr":
+        afereconstruct();
+
+        if (stack.inScope(value)) {
+          in_body_mode(ENDTAG, value);
+          afereconstruct();
+        }
+        afe.push(insertHTMLElement(value,arg3), arg3);
+        return;
+
+      case "applet":
+      case "marquee":
+      case "object":
+        afereconstruct();
+        insertHTMLElement(value,arg3);
+        afe.insertMarker();
+        frameset_ok = false;
+        return;
+
+      case "table":
+        if (!doc._quirks && stack.inButtonScope("p")) {
+          in_body_mode(ENDTAG, "p");
+        }
+        insertHTMLElement(value,arg3);
+        frameset_ok = false;
+        parser = in_table_mode;
+        return;
+
+      case "area":
+      case "br":
+      case "embed":
+      case "img":
+      case "keygen":
+      case "wbr":
+        afereconstruct();
+        insertHTMLElement(value,arg3);
+        stack.pop();
+        frameset_ok = false;
+        return;
+
+      case "input":
+        afereconstruct();
+        elt = insertHTMLElement(value,arg3);
+        stack.pop();
+        var type = elt.getAttribute("type");
+        if (!type || type.toLowerCase() !== "hidden")
+          frameset_ok = false;
+        return;
+
+      case "param":
+      case "source":
+      case "track":
+        insertHTMLElement(value,arg3);
+        stack.pop();
+        return;
+
+      case "hr":
+        if (stack.inButtonScope("p")) in_body_mode(ENDTAG, "p");
+        if (isA(stack.top, 'menuitem')) {
+          stack.pop();
+        }
+        insertHTMLElement(value,arg3);
+        stack.pop();
+        frameset_ok = false;
+        return;
+
+      case "image":
+        in_body_mode(TAG, "img", arg3, arg4);
+        return;
+
+      case "textarea":
+        insertHTMLElement(value,arg3);
+        ignore_linefeed = true;
+        frameset_ok = false;
+        tokenizer = rcdata_state;
+        originalInsertionMode = parser;
+        parser = text_mode;
+        return;
+
+      case "xmp":
+        if (stack.inButtonScope("p")) in_body_mode(ENDTAG, "p");
+        afereconstruct();
+        frameset_ok = false;
+        parseRawText(value, arg3);
+        return;
+
+      case "iframe":
+        frameset_ok = false;
+        parseRawText(value, arg3);
+        return;
+
+      case "noembed":
+        parseRawText(value,arg3);
+        return;
+
+      case "noscript":
+        if (scripting_enabled) {
+          parseRawText(value,arg3);
+          return;
+        }
+        break;  // XXX Otherwise treat it as any other open tag?
+
+      case "select":
+        afereconstruct();
+        insertHTMLElement(value,arg3);
+        frameset_ok = false;
+        if (parser === in_table_mode ||
+          parser === in_caption_mode ||
+          parser === in_table_body_mode ||
+          parser === in_row_mode ||
+          parser === in_cell_mode)
+          parser = in_select_in_table_mode;
+        else
+          parser = in_select_mode;
+        return;
+
+      case "optgroup":
+      case "option":
+        if (stack.top instanceof impl.HTMLOptionElement) {
+          in_body_mode(ENDTAG, "option");
+        }
+        afereconstruct();
+        insertHTMLElement(value,arg3);
+        return;
+
+      case "menuitem":
+        if (isA(stack.top, 'menuitem')) {
+          stack.pop();
+        }
+        afereconstruct();
+        insertHTMLElement(value, arg3);
+        return;
+
+      case "rb":
+      case "rtc":
+        if (stack.inScope("ruby")) {
+          stack.generateImpliedEndTags();
+        }
+        insertHTMLElement(value,arg3);
+        return;
+
+      case "rp":
+      case "rt":
+        if (stack.inScope("ruby")) {
+          stack.generateImpliedEndTags("rtc");
+        }
+        insertHTMLElement(value,arg3);
+        return;
+
+      case "math":
+        afereconstruct();
+        adjustMathMLAttributes(arg3);
+        adjustForeignAttributes(arg3);
+        insertForeignElement(value, arg3, NAMESPACE.MATHML);
+        if (arg4) // self-closing flag
+          stack.pop();
+        return;
+
+      case "svg":
+        afereconstruct();
+        adjustSVGAttributes(arg3);
+        adjustForeignAttributes(arg3);
+        insertForeignElement(value, arg3, NAMESPACE.SVG);
+        if (arg4) // self-closing flag
+          stack.pop();
+        return;
+
+      case "caption":
+      case "col":
+      case "colgroup":
+      case "frame":
+      case "head":
+      case "tbody":
+      case "td":
+      case "tfoot":
+      case "th":
+      case "thead":
+      case "tr":
+        // Ignore table tags if we're not in_table mode
+        return;
+      }
+
+      // Handle any other start tag here
+      // (and also noscript tags when scripting is disabled)
+      afereconstruct();
+      insertHTMLElement(value,arg3);
+      return;
+
+    case 3: // ENDTAG
+      switch(value) {
+      case "template":
+        in_head_mode(ENDTAG, value, arg3);
+        return;
+      case "body":
+        if (!stack.inScope("body")) return;
+        parser = after_body_mode;
+        return;
+      case "html":
+        if (!stack.inScope("body")) return;
+        parser = after_body_mode;
+        parser(t, value, arg3);
+        return;
+
+      case "address":
+      case "article":
+      case "aside":
+      case "blockquote":
+      case "button":
+      case "center":
+      case "details":
+      case "dialog":
+      case "dir":
+      case "div":
+      case "dl":
+      case "fieldset":
+      case "figcaption":
+      case "figure":
+      case "footer":
+      case "header":
+      case "hgroup":
+      case "listing":
+      case "main":
+      case "menu":
+      case "nav":
+      case "ol":
+      case "pre":
+      case "section":
+      case "summary":
+      case "ul":
+        // Ignore if there is not a matching open tag
+        if (!stack.inScope(value)) return;
+        stack.generateImpliedEndTags();
+        stack.popTag(value);
+        return;
+
+      case "form":
+        if (!stack.contains("template")) {
+          var openform = form_element_pointer;
+          form_element_pointer = null;
+          if (!openform || !stack.elementInScope(openform)) return;
+          stack.generateImpliedEndTags();
+          stack.removeElement(openform);
+        } else {
+          if (!stack.inScope("form")) return;
+          stack.generateImpliedEndTags();
+          stack.popTag("form");
+        }
+        return;
+
+      case "p":
+        if (!stack.inButtonScope(value)) {
+          in_body_mode(TAG, value, null);
+          parser(t, value, arg3, arg4);
+        }
+        else {
+          stack.generateImpliedEndTags(value);
+          stack.popTag(value);
+        }
+        return;
+
+      case "li":
+        if (!stack.inListItemScope(value)) return;
+        stack.generateImpliedEndTags(value);
+        stack.popTag(value);
+        return;
+
+      case "dd":
+      case "dt":
+        if (!stack.inScope(value)) return;
+        stack.generateImpliedEndTags(value);
+        stack.popTag(value);
+        return;
+
+      case "h1":
+      case "h2":
+      case "h3":
+      case "h4":
+      case "h5":
+      case "h6":
+        if (!stack.elementTypeInScope(impl.HTMLHeadingElement)) return;
+        stack.generateImpliedEndTags();
+        stack.popElementType(impl.HTMLHeadingElement);
+        return;
+
+      case "sarcasm":
+        // Take a deep breath, and then:
+        break;
+
+      case "a":
+      case "b":
+      case "big":
+      case "code":
+      case "em":
+      case "font":
+      case "i":
+      case "nobr":
+      case "s":
+      case "small":
+      case "strike":
+      case "strong":
+      case "tt":
+      case "u":
+        var result = adoptionAgency(value);
+        if (result) return;  // If we did something we're done
+        break;         // Go to the "any other end tag" case
+
+      case "applet":
+      case "marquee":
+      case "object":
+        if (!stack.inScope(value)) return;
+        stack.generateImpliedEndTags();
+        stack.popTag(value);
+        afe.clearToMarker();
+        return;
+
+      case "br":
+        in_body_mode(TAG, value, null);  // Turn </br> into <br>
+        return;
+      }
+
+      // Any other end tag goes here
+      for(i = stack.elements.length-1; i >= 0; i--) {
+        node = stack.elements[i];
+        if (isA(node, value)) {
+          stack.generateImpliedEndTags(value);
+          stack.popElement(node);
+          break;
+        }
+        else if (isA(node, specialSet)) {
+          return;
+        }
+      }
+
+      return;
+    }
+  }
+
+  function text_mode(t, value, arg3, arg4) {
+    switch(t) {
+    case 1: // TEXT
+      insertText(value);
+      return;
+    case -1: // EOF
+      if (stack.top instanceof impl.HTMLScriptElement)
+        stack.top._already_started = true;
+      stack.pop();
+      parser = originalInsertionMode;
+      parser(t);
+      return;
+    case 3: // ENDTAG
+      if (value === "script") {
+        handleScriptEnd();
+      }
+      else {
+        stack.pop();
+        parser = originalInsertionMode;
+      }
+      return;
+    default:
+      // We should never get any other token types
+      return;
+    }
+  }
+
+  function in_table_mode(t, value, arg3, arg4) {
+    function getTypeAttr(attrs) {
+      for(var i = 0, n = attrs.length; i < n; i++) {
+        if (attrs[i][0] === "type")
+          return attrs[i][1].toLowerCase();
+      }
+      return null;
+    }
+
+    switch(t) {
+    case 1: // TEXT
+      // XXX the text_integration_mode stuff is
+      // just a hack I made up
+      if (text_integration_mode) {
+        in_body_mode(t, value, arg3, arg4);
+        return;
+      }
+      else if (isA(stack.top, tablesectionrowSet)) {
+        pending_table_text = [];
+        originalInsertionMode = parser;
+        parser = in_table_text_mode;
+        parser(t, value, arg3, arg4);
+        return;
+      }
+      break;
+    case 4: // COMMENT
+      insertComment(value);
+      return;
+    case 5: // DOCTYPE
+      return;
+    case 2: // TAG
+      switch(value) {
+      case "caption":
+        stack.clearToContext(tableContextSet);
+        afe.insertMarker();
+        insertHTMLElement(value,arg3);
+        parser = in_caption_mode;
+        return;
+      case "colgroup":
+        stack.clearToContext(tableContextSet);
+        insertHTMLElement(value,arg3);
+        parser = in_column_group_mode;
+        return;
+      case "col":
+        in_table_mode(TAG, "colgroup", null);
+        parser(t, value, arg3, arg4);
+        return;
+      case "tbody":
+      case "tfoot":
+      case "thead":
+        stack.clearToContext(tableContextSet);
+        insertHTMLElement(value,arg3);
+        parser = in_table_body_mode;
+        return;
+      case "td":
+      case "th":
+      case "tr":
+        in_table_mode(TAG, "tbody", null);
+        parser(t, value, arg3, arg4);
+        return;
+
+      case "table":
+        if (!stack.inTableScope(value)) {
+          return; // Ignore the token
+        }
+        in_table_mode(ENDTAG, value);
+        parser(t, value, arg3, arg4);
+        return;
+
+      case "style":
+      case "script":
+      case "template":
+        in_head_mode(t, value, arg3, arg4);
+        return;
+
+      case "input":
+        var type = getTypeAttr(arg3);
+        if (type !== "hidden") break;  // to the anything else case
+        insertHTMLElement(value,arg3);
+        stack.pop();
+        return;
+
+      case "form":
+        if (form_element_pointer || stack.contains("template")) return;
+        form_element_pointer = insertHTMLElement(value, arg3);
+        stack.popElement(form_element_pointer);
+        return;
+      }
+      break;
+    case 3: // ENDTAG
+      switch(value) {
+      case "table":
+        if (!stack.inTableScope(value)) return;
+        stack.popTag(value);
+        resetInsertionMode();
+        return;
+      case "body":
+      case "caption":
+      case "col":
+      case "colgroup":
+      case "html":
+      case "tbody":
+      case "td":
+      case "tfoot":
+      case "th":
+      case "thead":
+      case "tr":
+        return;
+      case "template":
+        in_head_mode(t, value, arg3, arg4);
+        return;
+      }
+
+      break;
+    case -1: // EOF
+      in_body_mode(t, value, arg3, arg4);
+      return;
+    }
+
+    // This is the anything else case
+    foster_parent_mode = true;
+    in_body_mode(t, value, arg3, arg4);
+    foster_parent_mode = false;
+  }
+
+  function in_table_text_mode(t, value, arg3, arg4) {
+    if (t === TEXT) {
+      if (textIncludesNUL) {
+        value = value.replace(NULCHARS, "");
+        if (value.length === 0) return;
+      }
+      pending_table_text.push(value);
+    }
+    else {
+      var s = pending_table_text.join("");
+      pending_table_text.length = 0;
+      if (NONWS.test(s)) { // If any non-whitespace characters
+        // This must be the same code as the "anything else"
+        // case of the in_table mode above.
+        foster_parent_mode = true;
+        in_body_mode(TEXT, s);
+        foster_parent_mode = false;
+      }
+      else {
+        insertText(s);
+      }
+      parser = originalInsertionMode;
+      parser(t, value, arg3, arg4);
+    }
+  }
+
+
+  function in_caption_mode(t, value, arg3, arg4) {
+    function end_caption() {
+      if (!stack.inTableScope("caption")) return false;
+      stack.generateImpliedEndTags();
+      stack.popTag("caption");
+      afe.clearToMarker();
+      parser = in_table_mode;
+      return true;
+    }
+
+    switch(t) {
+    case 2: // TAG
+      switch(value) {
+      case "caption":
+      case "col":
+      case "colgroup":
+      case "tbody":
+      case "td":
+      case "tfoot":
+      case "th":
+      case "thead":
+      case "tr":
+        if (end_caption()) parser(t, value, arg3, arg4);
+        return;
+      }
+      break;
+    case 3: // ENDTAG
+      switch(value) {
+      case "caption":
+        end_caption();
+        return;
+      case "table":
+        if (end_caption()) parser(t, value, arg3, arg4);
+        return;
+      case "body":
+      case "col":
+      case "colgroup":
+      case "html":
+      case "tbody":
+      case "td":
+      case "tfoot":
+      case "th":
+      case "thead":
+      case "tr":
+        return;
+      }
+      break;
+    }
+
+    // The Anything Else case
+    in_body_mode(t, value, arg3, arg4);
+  }
+
+  function in_column_group_mode(t, value, arg3, arg4) {
+    switch(t) {
+    case 1: // TEXT
+      var ws = value.match(LEADINGWS);
+      if (ws) {
+        insertText(ws[0]);
+        value = value.substring(ws[0].length);
+      }
+      if (value.length === 0) return;
+      break; // Handle non-whitespace below
+
+    case 4: // COMMENT
+      insertComment(value);
+      return;
+    case 5: // DOCTYPE
+      return;
+    case 2: // TAG
+      switch(value) {
+      case "html":
+        in_body_mode(t, value, arg3, arg4);
+        return;
+      case "col":
+        insertHTMLElement(value, arg3);
+        stack.pop();
+        return;
+      case "template":
+        in_head_mode(t, value, arg3, arg4);
+        return;
+      }
+      break;
+    case 3: // ENDTAG
+      switch(value) {
+      case "colgroup":
+        if (!isA(stack.top, 'colgroup')) {
+          return; // Ignore the token.
+        }
+        stack.pop();
+        parser = in_table_mode;
+        return;
+      case "col":
+        return;
+      case "template":
+        in_head_mode(t, value, arg3, arg4);
+        return;
+      }
+      break;
+    case -1: // EOF
+      in_body_mode(t, value, arg3, arg4);
+      return;
+    }
+
+    // Anything else
+    if (!isA(stack.top, 'colgroup')) {
+      return; // Ignore the token.
+    }
+    in_column_group_mode(ENDTAG, "colgroup");
+    parser(t, value, arg3, arg4);
+  }
+
+  function in_table_body_mode(t, value, arg3, arg4) {
+    function endsect() {
+      if (!stack.inTableScope("tbody") &&
+        !stack.inTableScope("thead") &&
+        !stack.inTableScope("tfoot"))
+        return;
+      stack.clearToContext(tableBodyContextSet);
+      in_table_body_mode(ENDTAG, stack.top.localName, null);
+      parser(t, value, arg3, arg4);
+    }
+
+    switch(t) {
+    case 2: // TAG
+      switch(value) {
+      case "tr":
+        stack.clearToContext(tableBodyContextSet);
+        insertHTMLElement(value, arg3);
+        parser = in_row_mode;
+        return;
+      case "th":
+      case "td":
+        in_table_body_mode(TAG, "tr", null);
+        parser(t, value, arg3, arg4);
+        return;
+      case "caption":
+      case "col":
+      case "colgroup":
+      case "tbody":
+      case "tfoot":
+      case "thead":
+        endsect();
+        return;
+      }
+      break;
+    case 3: // ENDTAG
+      switch(value) {
+      case "table":
+        endsect();
+        return;
+      case "tbody":
+      case "tfoot":
+      case "thead":
+        if (stack.inTableScope(value)) {
+          stack.clearToContext(tableBodyContextSet);
+          stack.pop();
+          parser = in_table_mode;
+        }
+        return;
+      case "body":
+      case "caption":
+      case "col":
+      case "colgroup":
+      case "html":
+      case "td":
+      case "th":
+      case "tr":
+        return;
+      }
+      break;
+    }
+
+    // Anything else:
+    in_table_mode(t, value, arg3, arg4);
+  }
+
+  function in_row_mode(t, value, arg3, arg4) {
+    function endrow() {
+      if (!stack.inTableScope("tr")) return false;
+      stack.clearToContext(tableRowContextSet);
+      stack.pop();
+      parser = in_table_body_mode;
+      return true;
+    }
+
+    switch(t) {
+    case 2: // TAG
+      switch(value) {
+      case "th":
+      case "td":
+        stack.clearToContext(tableRowContextSet);
+        insertHTMLElement(value, arg3);
+        parser = in_cell_mode;
+        afe.insertMarker();
+        return;
+      case "caption":
+      case "col":
+      case "colgroup":
+      case "tbody":
+      case "tfoot":
+      case "thead":
+      case "tr":
+        if (endrow()) parser(t, value, arg3, arg4);
+        return;
+      }
+      break;
+    case 3: // ENDTAG
+      switch(value) {
+      case "tr":
+        endrow();
+        return;
+      case "table":
+        if (endrow()) parser(t, value, arg3, arg4);
+        return;
+      case "tbody":
+      case "tfoot":
+      case "thead":
+        if (stack.inTableScope(value)) {
+          if (endrow()) parser(t, value, arg3, arg4);
+        }
+        return;
+      case "body":
+      case "caption":
+      case "col":
+      case "colgroup":
+      case "html":
+      case "td":
+      case "th":
+        return;
+      }
+      break;
+    }
+
+    // anything else
+    in_table_mode(t, value, arg3, arg4);
+  }
+
+  function in_cell_mode(t, value, arg3, arg4) {
+    switch(t) {
+    case 2: // TAG
+      switch(value) {
+      case "caption":
+      case "col":
+      case "colgroup":
+      case "tbody":
+      case "td":
+      case "tfoot":
+      case "th":
+      case "thead":
+      case "tr":
+        if (stack.inTableScope("td")) {
+          in_cell_mode(ENDTAG, "td");
+          parser(t, value, arg3, arg4);
+        }
+        else if (stack.inTableScope("th")) {
+          in_cell_mode(ENDTAG, "th");
+          parser(t, value, arg3, arg4);
+        }
+        return;
+      }
+      break;
+    case 3: // ENDTAG
+      switch(value) {
+      case "td":
+      case "th":
+        if (!stack.inTableScope(value)) return;
+        stack.generateImpliedEndTags();
+        stack.popTag(value);
+        afe.clearToMarker();
+        parser = in_row_mode;
+        return;
+
+      case "body":
+      case "caption":
+      case "col":
+      case "colgroup":
+      case "html":
+        return;
+
+      case "table":
+      case "tbody":
+      case "tfoot":
+      case "thead":
+      case "tr":
+        if (!stack.inTableScope(value)) return;
+        in_cell_mode(ENDTAG, stack.inTableScope("td") ? "td" : "th");
+        parser(t, value, arg3, arg4);
+        return;
+      }
+      break;
+    }
+
+    // anything else
+    in_body_mode(t, value, arg3, arg4);
+  }
+
+  function in_select_mode(t, value, arg3, arg4) {
+    switch(t) {
+    case 1: // TEXT
+      if (textIncludesNUL) {
+        value = value.replace(NULCHARS, "");
+        if (value.length === 0) return;
+      }
+      insertText(value);
+      return;
+    case 4: // COMMENT
+      insertComment(value);
+      return;
+    case 5: // DOCTYPE
+      return;
+    case -1: // EOF
+      in_body_mode(t, value, arg3, arg4);
+      return;
+    case 2: // TAG
+      switch(value) {
+      case "html":
+        in_body_mode(t, value, arg3, arg4);
+        return;
+      case "option":
+        if (stack.top instanceof impl.HTMLOptionElement)
+          in_select_mode(ENDTAG, value);
+        insertHTMLElement(value, arg3);
+        return;
+      case "optgroup":
+        if (stack.top instanceof impl.HTMLOptionElement)
+          in_select_mode(ENDTAG, "option");
+        if (stack.top instanceof impl.HTMLOptGroupElement)
+          in_select_mode(ENDTAG, value);
+        insertHTMLElement(value, arg3);
+        return;
+      case "select":
+        in_select_mode(ENDTAG, value); // treat it as a close tag
+        return;
+
+      case "input":
+      case "keygen":
+      case "textarea":
+        if (!stack.inSelectScope("select")) return;
+        in_select_mode(ENDTAG, "select");
+        parser(t, value, arg3, arg4);
+        return;
+
+      case "script":
+      case "template":
+        in_head_mode(t, value, arg3, arg4);
+        return;
+      }
+      break;
+    case 3: // ENDTAG
+      switch(value) {
+      case "optgroup":
+        if (stack.top instanceof impl.HTMLOptionElement &&
+          stack.elements[stack.elements.length-2] instanceof
+          impl.HTMLOptGroupElement) {
+          in_select_mode(ENDTAG, "option");
+        }
+        if (stack.top instanceof impl.HTMLOptGroupElement)
+          stack.pop();
+
+        return;
+
+      case "option":
+        if (stack.top instanceof impl.HTMLOptionElement)
+          stack.pop();
+        return;
+
+      case "select":
+        if (!stack.inSelectScope(value)) return;
+        stack.popTag(value);
+        resetInsertionMode();
+        return;
+
+      case "template":
+        in_head_mode(t, value, arg3, arg4);
+        return;
+      }
+
+      break;
+    }
+
+    // anything else: just ignore the token
+  }
+
+  function in_select_in_table_mode(t, value, arg3, arg4) {
+    switch(value) {
+    case "caption":
+    case "table":
+    case "tbody":
+    case "tfoot":
+    case "thead":
+    case "tr":
+    case "td":
+    case "th":
+      switch(t) {
+      case 2: // TAG
+        in_select_in_table_mode(ENDTAG, "select");
+        parser(t, value, arg3, arg4);
+        return;
+      case 3: // ENDTAG
+        if (stack.inTableScope(value)) {
+          in_select_in_table_mode(ENDTAG, "select");
+          parser(t, value, arg3, arg4);
+        }
+        return;
+      }
+    }
+
+    // anything else
+    in_select_mode(t, value, arg3, arg4);
+  }
+
+  function in_template_mode(t, value, arg3, arg4) {
+    function switchModeAndReprocess(mode) {
+      parser = mode;
+      templateInsertionModes[templateInsertionModes.length-1] = parser;
+      parser(t, value, arg3, arg4);
+    }
+    switch(t) {
+    case 1: // TEXT
+    case 4: // COMMENT
+    case 5: // DOCTYPE
+      in_body_mode(t, value, arg3, arg4);
+      return;
+    case -1: // EOF
+      if (!stack.contains("template")) {
+        stopParsing();
+      } else {
+        stack.popTag("template");
+        afe.clearToMarker();
+        templateInsertionModes.pop();
+        resetInsertionMode();
+        parser(t, value, arg3, arg4);
+      }
+      return;
+    case 2: // TAG
+      switch(value) {
+      case "base":
+      case "basefont":
+      case "bgsound":
+      case "link":
+      case "meta":
+      case "noframes":
+      case "script":
+      case "style":
+      case "template":
+      case "title":
+        in_head_mode(t, value, arg3, arg4);
+        return;
+      case "caption":
+      case "colgroup":
+      case "tbody":
+      case "tfoot":
+      case "thead":
+        switchModeAndReprocess(in_table_mode);
+        return;
+      case "col":
+        switchModeAndReprocess(in_column_group_mode);
+        return;
+      case "tr":
+        switchModeAndReprocess(in_table_body_mode);
+        return;
+      case "td":
+      case "th":
+        switchModeAndReprocess(in_row_mode);
+        return;
+      }
+      switchModeAndReprocess(in_body_mode);
+      return;
+    case 3: // ENDTAG
+      switch(value) {
+      case "template":
+        in_head_mode(t, value, arg3, arg4);
+        return;
+      default:
+        return;
+      }
+    }
+  }
+
+  function after_body_mode(t, value, arg3, arg4) {
+    switch(t) {
+    case 1: // TEXT
+      // If any non-space chars, handle below
+      if (NONWS.test(value)) break;
+      in_body_mode(t, value);
+      return;
+    case 4: // COMMENT
+      // Append it to the <html> element
+      stack.elements[0]._appendChild(doc.createComment(value));
+      return;
+    case 5: // DOCTYPE
+      return;
+    case -1: // EOF
+      stopParsing();
+      return;
+    case 2: // TAG
+      if (value === "html") {
+        in_body_mode(t, value, arg3, arg4);
+        return;
+      }
+      break; // for any other tags
+    case 3: // ENDTAG
+      if (value === "html") {
+        if (fragment) return;
+        parser = after_after_body_mode;
+        return;
+      }
+      break; // for any other tags
+    }
+
+    // anything else
+    parser = in_body_mode;
+    parser(t, value, arg3, arg4);
+  }
+
+  function in_frameset_mode(t, value, arg3, arg4) {
+    switch(t) {
+    case 1: // TEXT
+      // Ignore any non-space characters
+      value = value.replace(ALLNONWS, "");
+      if (value.length > 0) insertText(value);
+      return;
+    case 4: // COMMENT
+      insertComment(value);
+      return;
+    case 5: // DOCTYPE
+      return;
+    case -1: // EOF
+      stopParsing();
+      return;
+    case 2: // TAG
+      switch(value) {
+      case "html":
+        in_body_mode(t, value, arg3, arg4);
+        return;
+      case "frameset":
+        insertHTMLElement(value, arg3);
+        return;
+      case "frame":
+        insertHTMLElement(value, arg3);
+        stack.pop();
+        return;
+      case "noframes":
+        in_head_mode(t, value, arg3, arg4);
+        return;
+      }
+      break;
+    case 3: // ENDTAG
+      if (value === "frameset") {
+        if (fragment && stack.top instanceof impl.HTMLHtmlElement)
+          return;
+        stack.pop();
+        if (!fragment &&
+          !(stack.top instanceof impl.HTMLFrameSetElement))
+          parser = after_frameset_mode;
+        return;
+      }
+      break;
+    }
+
+    // ignore anything else
+  }
+
+  function after_frameset_mode(t, value, arg3, arg4) {
+    switch(t) {
+    case 1: // TEXT
+      // Ignore any non-space characters
+      value = value.replace(ALLNONWS, "");
+      if (value.length > 0) insertText(value);
+      return;
+    case 4: // COMMENT
+      insertComment(value);
+      return;
+    case 5: // DOCTYPE
+      return;
+    case -1: // EOF
+      stopParsing();
+      return;
+    case 2: // TAG
+      switch(value) {
+      case "html":
+        in_body_mode(t, value, arg3, arg4);
+        return;
+      case "noframes":
+        in_head_mode(t, value, arg3, arg4);
+        return;
+      }
+      break;
+    case 3: // ENDTAG
+      if (value === "html") {
+        parser = after_after_frameset_mode;
+        return;
+      }
+      break;
+    }
+
+    // ignore anything else
+  }
+
+  function after_after_body_mode(t, value, arg3, arg4) {
+    switch(t) {
+    case 1: // TEXT
+      // If any non-space chars, handle below
+      if (NONWS.test(value)) break;
+      in_body_mode(t, value, arg3, arg4);
+      return;
+    case 4: // COMMENT
+      doc._appendChild(doc.createComment(value));
+      return;
+    case 5: // DOCTYPE
+      in_body_mode(t, value, arg3, arg4);
+      return;
+    case -1: // EOF
+      stopParsing();
+      return;
+    case 2: // TAG
+      if (value === "html") {
+        in_body_mode(t, value, arg3, arg4);
+        return;
+      }
+      break;
+    }
+
+    // anything else
+    parser = in_body_mode;
+    parser(t, value, arg3, arg4);
+  }
+
+  function after_after_frameset_mode(t, value, arg3, arg4) {
+    switch(t) {
+    case 1: // TEXT
+      // Ignore any non-space characters
+      value = value.replace(ALLNONWS, "");
+      if (value.length > 0)
+        in_body_mode(t, value, arg3, arg4);
+      return;
+    case 4: // COMMENT
+      doc._appendChild(doc.createComment(value));
+      return;
+    case 5: // DOCTYPE
+      in_body_mode(t, value, arg3, arg4);
+      return;
+    case -1: // EOF
+      stopParsing();
+      return;
+    case 2: // TAG
+      switch(value) {
+      case "html":
+        in_body_mode(t, value, arg3, arg4);
+        return;
+      case "noframes":
+        in_head_mode(t, value, arg3, arg4);
+        return;
+      }
+      break;
+    }
+
+    // ignore anything else
+  }
+
+
+  // 13.2.5.5 The rules for parsing tokens in foreign content
+  //
+  // This is like one of the insertion modes above, but is
+  // invoked somewhat differently when the current token is not HTML.
+  // See the insertToken() function.
+  function insertForeignToken(t, value, arg3, arg4) {
+    // A <font> tag is an HTML font tag if it has a color, font, or size
+    // attribute.  Otherwise we assume it is foreign content
+    function isHTMLFont(attrs) {
+      for(var i = 0, n = attrs.length; i < n; i++) {
+        switch(attrs[i][0]) {
+        case "color":
+        case "face":
+        case "size":
+          return true;
+        }
+      }
+      return false;
+    }
+
+    var current;
+
+    switch(t) {
+    case 1: // TEXT
+      // If any non-space, non-nul characters
+      if (frameset_ok && NONWSNONNUL.test(value))
+        frameset_ok = false;
+      if (textIncludesNUL) {
+        value = value.replace(NULCHARS, "\uFFFD");
+      }
+      insertText(value);
+      return;
+    case 4: // COMMENT
+      insertComment(value);
+      return;
+    case 5: // DOCTYPE
+      // ignore it
+      return;
+    case 2: // TAG
+      switch(value) {
+      case "font":
+        if (!isHTMLFont(arg3)) break;
+        /* falls through */
+      case "b":
+      case "big":
+      case "blockquote":
+      case "body":
+      case "br":
+      case "center":
+      case "code":
+      case "dd":
+      case "div":
+      case "dl":
+      case "dt":
+      case "em":
+      case "embed":
+      case "h1":
+      case "h2":
+      case "h3":
+      case "h4":
+      case "h5":
+      case "h6":
+      case "head":
+      case "hr":
+      case "i":
+      case "img":
+      case "li":
+      case "listing":
+      case "menu":
+      case "meta":
+      case "nobr":
+      case "ol":
+      case "p":
+      case "pre":
+      case "ruby":
+      case "s":
+      case "small":
+      case "span":
+      case "strong":
+      case "strike":
+      case "sub":
+      case "sup":
+      case "table":
+      case "tt":
+      case "u":
+      case "ul":
+      case "var":
+        if (fragment) {
+          break;
+        }
+        do {
+          stack.pop();
+          current = stack.top;
+        } while(current.namespaceURI !== NAMESPACE.HTML &&
+            !isMathmlTextIntegrationPoint(current) &&
+            !isHTMLIntegrationPoint(current));
+
+        insertToken(t, value, arg3, arg4);  // reprocess
+        return;
+      }
+
+      // Any other start tag case goes here
+      current = (stack.elements.length===1 && fragment) ? fragmentContext :
+        stack.top;
+      if (current.namespaceURI === NAMESPACE.MATHML) {
+        adjustMathMLAttributes(arg3);
+      }
+      else if (current.namespaceURI === NAMESPACE.SVG) {
+        value = adjustSVGTagName(value);
+        adjustSVGAttributes(arg3);
+      }
+      adjustForeignAttributes(arg3);
+
+      insertForeignElement(value, arg3, current.namespaceURI);
+      if (arg4) { // the self-closing flag
+        if (value === 'script' && current.namespaceURI === NAMESPACE.SVG) {
+          // XXX deal with SVG scripts here
+        }
+        stack.pop();
+      }
+      return;
+
+    case 3: // ENDTAG
+      current = stack.top;
+      if (value === "script" &&
+        current.namespaceURI === NAMESPACE.SVG &&
+        current.localName === "script") {
+
+        stack.pop();
+
+        // XXX
+        // Deal with SVG scripts here
+      }
+      else {
+        // The any other end tag case
+        var i = stack.elements.length-1;
+        var node = stack.elements[i];
+        for(;;) {
+          if (node.localName.toLowerCase() === value) {
+            stack.popElement(node);
+            break;
+          }
+          node = stack.elements[--i];
+          // If non-html, keep looping
+          if (node.namespaceURI !== NAMESPACE.HTML)
+            continue;
+          // Otherwise process the end tag as html
+          parser(t, value, arg3, arg4);
+          break;
+        }
+      }
+      return;
+    }
+  }
+
+  /***
+   * Finally, this is the end of the HTMLParser() factory function.
+   * It returns the htmlparser object with the append() and end() methods.
+   */
+
+  // Sneak another method into the htmlparser object to allow us to run
+  // tokenizer tests.  This can be commented out in production code.
+  // This is a hook for testing the tokenizer. It has to be here
+  // because the tokenizer details are all hidden away within the closure.
+  // It should return an array of tokens generated while parsing the
+  // input string.
+  htmlparser.testTokenizer = function(input, initialState, lastStartTag, charbychar) {
+    var tokens = [];
+
+    switch(initialState) {
+    case "PCDATA state":
+      tokenizer = data_state;
+      break;
+    case "RCDATA state":
+      tokenizer = rcdata_state;
+      break;
+    case "RAWTEXT state":
+      tokenizer = rawtext_state;
+      break;
+    case "PLAINTEXT state":
+      tokenizer = plaintext_state;
+      break;
+    }
+
+    if (lastStartTag) {
+      lasttagname = lastStartTag;
+    }
+
+    insertToken = function(t, value, arg3, arg4) {
+      flushText();
+      switch(t) {
+      case 1: // TEXT
+        if (tokens.length > 0 &&
+          tokens[tokens.length-1][0] === "Character") {
+          tokens[tokens.length-1][1] += value;
+        }
+        else tokens.push(["Character", value]);
+        break;
+      case 4: // COMMENT
+        tokens.push(["Comment", value]);
+        break;
+      case 5: // DOCTYPE
+        tokens.push(["DOCTYPE", value,
+               arg3 === undefined ? null : arg3,
+               arg4 === undefined ? null : arg4,
+               !force_quirks]);
+        break;
+      case 2: // TAG
+        var attrs = Object.create(null);
+        for(var i = 0; i < arg3.length; i++) {
+          // XXX: does attribute order matter?
+          var a = arg3[i];
+          if (a.length === 1) {
+            attrs[a[0]] = "";
+          }
+          else {
+            attrs[a[0]] = a[1];
+          }
+        }
+        var token = ["StartTag", value, attrs];
+        if (arg4) token.push(true);
+        tokens.push(token);
+        break;
+      case 3: // ENDTAG
+        tokens.push(["EndTag", value]);
+        break;
+      case -1: // EOF
+        break;
+      }
+    };
+
+    if (!charbychar) {
+      this.parse(input, true);
+    }
+    else {
+      for(var i = 0; i < input.length; i++) {
+        this.parse(input[i]);
+      }
+      this.parse("", true);
+    }
+    return tokens;
+  };
+
+  // Return the parser object from the HTMLParser() factory function
+  return htmlparser;
+}
--- /dev/null
+++ b/domino-lib/LICENSE
@@ -1,0 +1,25 @@
+Copyright (c) 2011 The Mozilla Foundation.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+
+    Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null
+++ b/domino-lib/Leaf.js
@@ -1,0 +1,37 @@
+"use strict";
+module.exports = Leaf;
+
+var Node = require('./Node');
+var NodeList = require('./NodeList');
+var utils = require('./utils');
+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;
+  }}
+});
--- /dev/null
+++ b/domino-lib/LinkedList.js
@@ -1,0 +1,44 @@
+"use strict";
+var utils = require('./utils');
+
+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));
+    }
+};
--- /dev/null
+++ b/domino-lib/Location.js
@@ -1,0 +1,56 @@
+"use strict";
+var URL = require('./URL');
+var URLUtils = require('./URLUtils');
+
+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;
+  }}
+
+});
--- /dev/null
+++ b/domino-lib/MouseEvent.js
@@ -1,0 +1,52 @@
+"use strict";
+var UIEvent = require('./UIEvent');
+
+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;
+    }
+  }}
+});
--- /dev/null
+++ b/domino-lib/MutationConstants.js
@@ -1,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
+};
\ No newline at end of file
--- /dev/null
+++ b/domino-lib/NamedNodeMap.js
@@ -1,0 +1,41 @@
+"use strict";
+module.exports = NamedNodeMap;
+
+var utils = require('./utils');
+
+/* 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();
+  } },
+});
--- /dev/null
+++ b/domino-lib/NavigatorID.js
@@ -1,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;
--- /dev/null
+++ b/domino-lib/Node.js
@@ -1,0 +1,738 @@
+"use strict";
+module.exports = Node;
+
+var EventTarget = require('./EventTarget');
+var LinkedList = require('./LinkedList');
+var NodeUtils = require('./NodeUtils');
+var utils = require('./utils');
+
+// 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 },
+});
--- /dev/null
+++ b/domino-lib/NodeFilter.js
@@ -1,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);
--- /dev/null
+++ b/domino-lib/NodeIterator.js
@@ -1,0 +1,217 @@
+"use strict";
+module.exports = NodeIterator;
+
+var NodeFilter = require('./NodeFilter');
+var NodeTraversal = require('./NodeTraversal');
+var utils = require('./utils');
+
+/* 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]";
+  } },
+});
--- /dev/null
+++ b/domino-lib/NodeList.es5.js
@@ -1,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;
--- /dev/null
+++ b/domino-lib/NodeList.es6.js
@@ -1,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; }
+};
--- /dev/null
+++ b/domino-lib/NodeList.js
@@ -1,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;
--- /dev/null
+++ b/domino-lib/NodeTraversal.js
@@ -1,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;
+}
--- /dev/null
+++ b/domino-lib/NodeUtils.js
@@ -1,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');
+var NAMESPACE = utils.NAMESPACE;
+
+var hasRawContent = {
+  STYLE: true,
+  SCRIPT: true,
+  XMP: true,
+  IFRAME: true,
+  NOEMBED: true,
+  NOFRAMES: true,
+  PLAINTEXT: true
+};
+
+var emptyElements = {
+  area: true,
+  base: true,
+  basefont: true,
+  bgsound: true,
+  br: true,
+  col: true,
+  embed: true,
+  frame: true,
+  hr: true,
+  img: true,
+  input: true,
+  keygen: true,
+  link: true,
+  meta: true,
+  param: true,
+  source: true,
+  track: true,
+  wbr: true
+};
+
+var extraNewLine = {
+  /* Removed in https://github.com/whatwg/html/issues/944
+  pre: true,
+  textarea: true,
+  listing: true
+  */
+};
+
+function escape(s) {
+  return s.replace(/[&<>\u00A0]/g, function(c) {
+    switch(c) {
+    case '&': return '&amp;';
+    case '<': return '&lt;';
+    case '>': return '&gt;';
+    case '\u00A0': return '&nbsp;';
+    }
+  });
+}
+
+function escapeAttr(s) {
+  var toEscape = /[&"\u00A0]/g;
+  if (!toEscape.test(s)) {
+      // nothing to do, fast path
+      return s;
+  } else {
+      return s.replace(toEscape, function(c) {
+        switch(c) {
+        case '&': return '&amp;';
+        case '"': return '&quot;';
+        case '\u00A0': return '&nbsp;';
+        }
+      });
+  }
+}
+
+function attrname(a) {
+  var ns = a.namespaceURI;
+  if (!ns)
+    return a.localName;
+  if (ns === NAMESPACE.XML)
+    return 'xml:' + a.localName;
+  if (ns === NAMESPACE.XLINK)
+    return 'xlink:' + a.localName;
+
+  if (ns === NAMESPACE.XMLNS) {
+    if (a.localName === 'xmlns') return 'xmlns';
+    else return 'xmlns:' + a.localName;
+  }
+  return a.name;
+}
+
+function serializeOne(kid, parent) {
+  var s = '';
+  switch(kid.nodeType) {
+    case 1: //ELEMENT_NODE
+      var ns = kid.namespaceURI;
+      var html = ns === NAMESPACE.HTML;
+      var tagname = (html || ns === NAMESPACE.SVG || ns === NAMESPACE.MATHML) ? kid.localName : kid.tagName;
+
+      s += '<' + tagname;
+
+      for(var j = 0, k = kid._numattrs; j < k; j++) {
+        var a = kid._attr(j);
+        s += ' ' + attrname(a);
+        if (a.value !== undefined) s += '="' + escapeAttr(a.value) + '"';
+      }
+      s += '>';
+
+      if (!(html && emptyElements[tagname])) {
+        var ss = kid.serialize();
+        if (html && extraNewLine[tagname] && ss.charAt(0)==='\n') s += '\n';
+        // Serialize children and add end tag for all others
+        s += ss;
+        s += '</' + tagname + '>';
+      }
+      break;
+    case 3: //TEXT_NODE
+    case 4: //CDATA_SECTION_NODE
+      var parenttag;
+      if (parent.nodeType === 1 /*ELEMENT_NODE*/ &&
+        parent.namespaceURI === NAMESPACE.HTML)
+        parenttag = parent.tagName;
+      else
+        parenttag = '';
+
+      if (hasRawContent[parenttag] ||
+          (parenttag==='NOSCRIPT' && parent.ownerDocument._scripting_enabled)) {
+        s += kid.data;
+      } else {
+        s += escape(kid.data);
+      }
+      break;
+    case 8: //COMMENT_NODE
+      s += '<!--' + kid.data + '-->';
+      break;
+    case 7: //PROCESSING_INSTRUCTION_NODE
+      s += '<?' + kid.target + ' ' + kid.data + '?>';
+      break;
+    case 10: //DOCUMENT_TYPE_NODE
+      s += '<!DOCTYPE ' + kid.name;
+
+      if (false) {
+        // Latest HTML serialization spec omits the public/system ID
+        if (kid.publicID) {
+          s += ' PUBLIC "' + kid.publicId + '"';
+        }
+
+        if (kid.systemId) {
+          s += ' "' + kid.systemId + '"';
+        }
+      }
+
+      s += '>';
+      break;
+    default:
+      utils.InvalidStateError();
+  }
+  return s;
+}
--- /dev/null
+++ b/domino-lib/NonDocumentTypeChildNode.js
@@ -1,0 +1,26 @@
+"use strict";
+var Node = require('./Node');
+
+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;
--- /dev/null
+++ b/domino-lib/ProcessingInstruction.js
@@ -1,0 +1,43 @@
+"use strict";
+module.exports = ProcessingInstruction;
+
+var Node = require('./Node');
+var CharacterData = require('./CharacterData');
+
+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;
+  }}
+
+});
--- /dev/null
+++ b/domino-lib/Text.js
@@ -1,0 +1,74 @@
+"use strict";
+module.exports = Text;
+
+var utils = require('./utils');
+var Node = require('./Node');
+var CharacterData = require('./CharacterData');
+
+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);
+  }},
+
+});
--- /dev/null
+++ b/domino-lib/TreeWalker.js
@@ -1,0 +1,336 @@
+"use strict";
+module.exports = TreeWalker;
+
+var Node = require('./Node');
+var NodeFilter = require('./NodeFilter');
+var NodeTraversal = require('./NodeTraversal');
+var utils = require('./utils');
+
+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]";
+  }},
+});
--- /dev/null
+++ b/domino-lib/UIEvent.js
@@ -1,0 +1,19 @@
+"use strict";
+var Event = require('./Event');
+
+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;
+  }}
+});
--- /dev/null
+++ b/domino-lib/URL.js
@@ -1,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;
+    }
+  },
+};
--- /dev/null
+++ b/domino-lib/URLUtils.js
@@ -1,0 +1,270 @@
+"use strict";
+var URL = require('./URL');
+
+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);
+  });
+};
--- /dev/null
+++ b/domino-lib/Window.js
@@ -1,0 +1,62 @@
+"use strict";
+var DOMImplementation = require('./DOMImplementation');
+var EventTarget = require('./EventTarget');
+var Location = require('./Location');
+var sloppy = require('./sloppy');
+var utils = require('./utils');
+
+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") },
+
+  // 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'), Window);
+utils.expose(require('./impl'), Window);
--- /dev/null
+++ b/domino-lib/WindowTimers.js
@@ -1,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;
--- /dev/null
+++ b/domino-lib/attributes.js
@@ -1,0 +1,152 @@
+"use strict";
+var utils = require('./utils');
+
+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;
+};
--- /dev/null
+++ b/domino-lib/config.js
@@ -1,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 = !global.__domino_frozen__;
--- /dev/null
+++ b/domino-lib/cssparser.js
@@ -1,0 +1,6654 @@
+/* jshint node:true, latedef:false */
+"use strict"; // jshint ignore:line
+/*!
+Parser-Lib
+Copyright (c) 2009-2011 Nicholas C. Zakas. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+/* Version v0.2.5+domino1, Build time: 30-January-2016 05:13:03 */
+var parserlib = Object.create(null);
+(function(){
+
+/**
+ * A generic base to inherit from for any object
+ * that needs event handling.
+ * @class EventTarget
+ * @constructor
+ */
+function EventTarget(){
+
+    /**
+     * The array of listeners for various events.
+     * @type Object
+     * @property _listeners
+     * @private
+     */
+    this._listeners = Object.create(null);
+}
+
+EventTarget.prototype = {
+
+    //restore constructor
+    constructor: EventTarget,
+
+    /**
+     * Adds a listener for a given event type.
+     * @param {String} type The type of event to add a listener for.
+     * @param {Function} listener The function to call when the event occurs.
+     * @return {void}
+     * @method addListener
+     */
+    addListener: function(type, listener){
+        if (!this._listeners[type]){
+            this._listeners[type] = [];
+        }
+
+        this._listeners[type].push(listener);
+    },
+
+    /**
+     * Fires an event based on the passed-in object.
+     * @param {Object|String} event An object with at least a 'type' attribute
+     *      or a string indicating the event name.
+     * @return {void}
+     * @method fire
+     */
+    fire: function(event){
+        if (typeof event === "string"){
+            event = { type: event };
+        }
+        if (typeof event.target !== "undefined"){
+            event.target = this;
+        }
+
+        if (typeof event.type === "undefined"){
+            throw new Error("Event object missing 'type' property.");
+        }
+
+        if (this._listeners[event.type]){
+
+            //create a copy of the array and use that so listeners can't chane
+            var listeners = this._listeners[event.type].concat();
+            for (var i=0, len=listeners.length; i < len; i++){
+                listeners[i].call(this, event);
+            }
+        }
+    },
+
+    /**
+     * Removes a listener for a given event type.
+     * @param {String} type The type of event to remove a listener from.
+     * @param {Function} listener The function to remove from the event.
+     * @return {void}
+     * @method removeListener
+     */
+    removeListener: function(type, listener){
+        if (this._listeners[type]){
+            var listeners = this._listeners[type];
+            for (var i=0, len=listeners.length; i < len; i++){
+                if (listeners[i] === listener){
+                    listeners.splice(i, 1);
+                    break;
+                }
+            }
+
+
+        }
+    }
+};
+/**
+ * Convenient way to read through strings.
+ * @namespace parserlib.util
+ * @class StringReader
+ * @constructor
+ * @param {String} text The text to read.
+ */
+function StringReader(text){
+
+    /**
+     * The input text with line endings normalized.
+     * @property _input
+     * @type String
+     * @private
+     */
+    this._input = text.replace(/(\r|\n){1,2}/g, "\n");
+
+
+    /**
+     * The row for the character to be read next.
+     * @property _line
+     * @type int
+     * @private
+     */
+    this._line = 1;
+
+
+    /**
+     * The column for the character to be read next.
+     * @property _col
+     * @type int
+     * @private
+     */
+    this._col = 1;
+
+    /**
+     * The index of the character in the input to be read next.
+     * @property _cursor
+     * @type int
+     * @private
+     */
+    this._cursor = 0;
+}
+
+StringReader.prototype = {
+
+    //restore constructor
+    constructor: StringReader,
+
+    //-------------------------------------------------------------------------
+    // Position info
+    //-------------------------------------------------------------------------
+
+    /**
+     * Returns the column of the character to be read next.
+     * @return {int} The column of the character to be read next.
+     * @method getCol
+     */
+    getCol: function(){
+        return this._col;
+    },
+
+    /**
+     * Returns the row of the character to be read next.
+     * @return {int} The row of the character to be read next.
+     * @method getLine
+     */
+    getLine: function(){
+        return this._line ;
+    },
+
+    /**
+     * Determines if you're at the end of the input.
+     * @return {Boolean} True if there's no more input, false otherwise.
+     * @method eof
+     */
+    eof: function(){
+        return (this._cursor === this._input.length);
+    },
+
+    //-------------------------------------------------------------------------
+    // Basic reading
+    //-------------------------------------------------------------------------
+
+    /**
+     * Reads the next character without advancing the cursor.
+     * @param {int} count How many characters to look ahead (default is 1).
+     * @return {String} The next character or null if there is no next character.
+     * @method peek
+     */
+    peek: function(count){
+        var c = null;
+        count = (typeof count === "undefined" ? 1 : count);
+
+        //if we're not at the end of the input...
+        if (this._cursor < this._input.length){
+
+            //get character and increment cursor and column
+            c = this._input.charAt(this._cursor + count - 1);
+        }
+
+        return c;
+    },
+
+    /**
+     * Reads the next character from the input and adjusts the row and column
+     * accordingly.
+     * @return {String} The next character or null if there is no next character.
+     * @method read
+     */
+    read: function(){
+        var c = null;
+
+        //if we're not at the end of the input...
+        if (this._cursor < this._input.length){
+
+            //if the last character was a newline, increment row count
+            //and reset column count
+            if (this._input.charAt(this._cursor) === "\n"){
+                this._line++;
+                this._col=1;
+            } else {
+                this._col++;
+            }
+
+            //get character and increment cursor and column
+            c = this._input.charAt(this._cursor++);
+        }
+
+        return c;
+    },
+
+    //-------------------------------------------------------------------------
+    // Misc
+    //-------------------------------------------------------------------------
+
+    /**
+     * Saves the current location so it can be returned to later.
+     * @method mark
+     * @return {void}
+     */
+    mark: function(){
+        this._bookmark = {
+            cursor: this._cursor,
+            line:   this._line,
+            col:    this._col
+        };
+    },
+
+    reset: function(){
+        if (this._bookmark){
+            this._cursor = this._bookmark.cursor;
+            this._line = this._bookmark.line;
+            this._col = this._bookmark.col;
+            delete this._bookmark;
+        }
+    },
+
+    //-------------------------------------------------------------------------
+    // Advanced reading
+    //-------------------------------------------------------------------------
+
+    /**
+     * Reads up to and including the given string. Throws an error if that
+     * string is not found.
+     * @param {String} pattern The string to read.
+     * @return {String} The string when it is found.
+     * @throws Error when the string pattern is not found.
+     * @method readTo
+     */
+    readTo: function(pattern){
+
+        var buffer = "",
+            c;
+
+        /*
+         * First, buffer must be the same length as the pattern.
+         * Then, buffer must end with the pattern or else reach the
+         * end of the input.
+         */
+        while (buffer.length < pattern.length || buffer.lastIndexOf(pattern) !== buffer.length - pattern.length){
+            c = this.read();
+            if (c){
+                buffer += c;
+            } else {
+                throw new Error("Expected \"" + pattern + "\" at line " + this._line  + ", col " + this._col + ".");
+            }
+        }
+
+        return buffer;
+
+    },
+
+    /**
+     * Reads characters while each character causes the given
+     * filter function to return true. The function is passed
+     * in each character and either returns true to continue
+     * reading or false to stop.
+     * @param {Function} filter The function to read on each character.
+     * @return {String} The string made up of all characters that passed the
+     *      filter check.
+     * @method readWhile
+     */
+    readWhile: function(filter){
+
+        var buffer = "",
+            c = this.read();
+
+        while(c !== null && filter(c)){
+            buffer += c;
+            c = this.read();
+        }
+
+        return buffer;
+
+    },
+
+    /**
+     * Reads characters that match either text or a regular expression and
+     * returns those characters. If a match is found, the row and column
+     * are adjusted; if no match is found, the reader's state is unchanged.
+     * reading or false to stop.
+     * @param {String|RegExp} matchter If a string, then the literal string
+     *      value is searched for. If a regular expression, then any string
+     *      matching the pattern is search for.
+     * @return {String} The string made up of all characters that matched or
+     *      null if there was no match.
+     * @method readMatch
+     */
+    readMatch: function(matcher){
+
+        var source = this._input.substring(this._cursor),
+            value = null;
+
+        //if it's a string, just do a straight match
+        if (typeof matcher === "string"){
+            if (source.indexOf(matcher) === 0){
+                value = this.readCount(matcher.length);
+            }
+        } else if (matcher instanceof RegExp){
+            if (matcher.test(source)){
+                value = this.readCount(RegExp.lastMatch.length);
+            }
+        }
+
+        return value;
+    },
+
+
+    /**
+     * Reads a given number of characters. If the end of the input is reached,
+     * it reads only the remaining characters and does not throw an error.
+     * @param {int} count The number of characters to read.
+     * @return {String} The string made up the read characters.
+     * @method readCount
+     */
+    readCount: function(count){
+        var buffer = "";
+
+        while(count--){
+            buffer += this.read();
+        }
+
+        return buffer;
+    }
+
+};
+/**
+ * Type to use when a syntax error occurs.
+ * @class SyntaxError
+ * @namespace parserlib.util
+ * @constructor
+ * @param {String} message The error message.
+ * @param {int} line The line at which the error occurred.
+ * @param {int} col The column at which the error occurred.
+ */
+function SyntaxError(message, line, col){
+    Error.call(this);
+    this.name = this.constructor.name;
+
+    /**
+     * The column at which the error occurred.
+     * @type int
+     * @property col
+     */
+    this.col = col;
+
+    /**
+     * The line at which the error occurred.
+     * @type int
+     * @property line
+     */
+    this.line = line;
+
+    /**
+     * The text representation of the unit.
+     * @type String
+     * @property text
+     */
+    this.message = message;
+
+}
+
+//inherit from Error
+SyntaxError.prototype = Object.create(Error.prototype); // jshint ignore:line
+SyntaxError.prototype.constructor = SyntaxError; // jshint ignore:line
+/**
+ * Base type to represent a single syntactic unit.
+ * @class SyntaxUnit
+ * @namespace parserlib.util
+ * @constructor
+ * @param {String} text The text of the unit.
+ * @param {int} line The line of text on which the unit resides.
+ * @param {int} col The column of text on which the unit resides.
+ */
+function SyntaxUnit(text, line, col, type){
+
+
+    /**
+     * The column of text on which the unit resides.
+     * @type int
+     * @property col
+     */
+    this.col = col;
+
+    /**
+     * The line of text on which the unit resides.
+     * @type int
+     * @property line
+     */
+    this.line = line;
+
+    /**
+     * The text representation of the unit.
+     * @type String
+     * @property text
+     */
+    this.text = text;
+
+    /**
+     * The type of syntax unit.
+     * @type int
+     * @property type
+     */
+    this.type = type;
+}
+
+/**
+ * Create a new syntax unit based solely on the given token.
+ * Convenience method for creating a new syntax unit when
+ * it represents a single token instead of multiple.
+ * @param {Object} token The token object to represent.
+ * @return {parserlib.util.SyntaxUnit} The object representing the token.
+ * @static
+ * @method fromToken
+ */
+SyntaxUnit.fromToken = function(token){
+    return new SyntaxUnit(token.value, token.startLine, token.startCol);
+};
+
+SyntaxUnit.prototype = {
+
+    //restore constructor
+    constructor: SyntaxUnit,
+
+    /**
+     * Returns the text representation of the unit.
+     * @return {String} The text representation of the unit.
+     * @method valueOf
+     */
+    valueOf: function(){
+        return this.toString();
+    },
+
+    /**
+     * Returns the text representation of the unit.
+     * @return {String} The text representation of the unit.
+     * @method toString
+     */
+    toString: function(){
+        return this.text;
+    }
+
+};
+
+/**
+ * Generic TokenStream providing base functionality.
+ * @class TokenStreamBase
+ * @namespace parserlib.util
+ * @constructor
+ * @param {String|StringReader} input The text to tokenize or a reader from
+ *      which to read the input.
+ */
+function TokenStreamBase(input, tokenData){
+
+    /**
+     * The string reader for easy access to the text.
+     * @type StringReader
+     * @property _reader
+     * @private
+     */
+    this._reader = input ? new StringReader(input.toString()) : null;
+
+    /**
+     * Token object for the last consumed token.
+     * @type Token
+     * @property _token
+     * @private
+     */
+    this._token = null;
+
+    /**
+     * The array of token information.
+     * @type Array
+     * @property _tokenData
+     * @private
+     */
+    this._tokenData = tokenData;
+
+    /**
+     * Lookahead token buffer.
+     * @type Array
+     * @property _lt
+     * @private
+     */
+    this._lt = [];
+
+    /**
+     * Lookahead token buffer index.
+     * @type int
+     * @property _ltIndex
+     * @private
+     */
+    this._ltIndex = 0;
+
+    this._ltIndexCache = [];
+}
+
+/**
+ * Accepts an array of token information and outputs
+ * an array of token data containing key-value mappings
+ * and matching functions that the TokenStream needs.
+ * @param {Array} tokens An array of token descriptors.
+ * @return {Array} An array of processed token data.
+ * @method createTokenData
+ * @static
+ */
+TokenStreamBase.createTokenData = function(tokens){
+
+    var nameMap     = [],
+        typeMap     = Object.create(null),
+        tokenData     = tokens.concat([]),
+        i            = 0,
+        len            = tokenData.length+1;
+
+    tokenData.UNKNOWN = -1;
+    tokenData.unshift({name:"EOF"});
+
+    for (; i < len; i++){
+        nameMap.push(tokenData[i].name);
+        tokenData[tokenData[i].name] = i;
+        if (tokenData[i].text){
+            typeMap[tokenData[i].text] = i;
+        }
+    }
+
+    tokenData.name = function(tt){
+        return nameMap[tt];
+    };
+
+    tokenData.type = function(c){
+        return typeMap[c];
+    };
+
+    return tokenData;
+};
+
+TokenStreamBase.prototype = {
+
+    //restore constructor
+    constructor: TokenStreamBase,
+
+    //-------------------------------------------------------------------------
+    // Matching methods
+    //-------------------------------------------------------------------------
+
+    /**
+     * Determines if the next token matches the given token type.
+     * If so, that token is consumed; if not, the token is placed
+     * back onto the token stream. You can pass in any number of
+     * token types and this will return true if any of the token
+     * types is found.
+     * @param {int|int[]} tokenTypes Either a single token type or an array of
+     *      token types that the next token might be. If an array is passed,
+     *      it's assumed that the token can be any of these.
+     * @param {variant} channel (Optional) The channel to read from. If not
+     *      provided, reads from the default (unnamed) channel.
+     * @return {Boolean} True if the token type matches, false if not.
+     * @method match
+     */
+    match: function(tokenTypes, channel){
+
+        //always convert to an array, makes things easier
+        if (!(tokenTypes instanceof Array)){
+            tokenTypes = [tokenTypes];
+        }
+
+        var tt  = this.get(channel),
+            i   = 0,
+            len = tokenTypes.length;
+
+        while(i < len){
+            if (tt === tokenTypes[i++]){
+                return true;
+            }
+        }
+
+        //no match found, put the token back
+        this.unget();
+        return false;
+    },
+
+    /**
+     * Determines if the next token matches the given token type.
+     * If so, that token is consumed; if not, an error is thrown.
+     * @param {int|int[]} tokenTypes Either a single token type or an array of
+     *      token types that the next token should be. If an array is passed,
+     *      it's assumed that the token must be one of these.
+     * @param {variant} channel (Optional) The channel to read from. If not
+     *      provided, reads from the default (unnamed) channel.
+     * @return {void}
+     * @method mustMatch
+     */
+    mustMatch: function(tokenTypes, channel){
+
+        var token;
+
+        //always convert to an array, makes things easier
+        if (!(tokenTypes instanceof Array)){
+            tokenTypes = [tokenTypes];
+        }
+
+        if (!this.match.apply(this, arguments)){
+            token = this.LT(1);
+            throw new SyntaxError("Expected " + this._tokenData[tokenTypes[0]].name +
+                " at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
+        }
+    },
+
+    //-------------------------------------------------------------------------
+    // Consuming methods
+    //-------------------------------------------------------------------------
+
+    /**
+     * Keeps reading from the token stream until either one of the specified
+     * token types is found or until the end of the input is reached.
+     * @param {int|int[]} tokenTypes Either a single token type or an array of
+     *      token types that the next token should be. If an array is passed,
+     *      it's assumed that the token must be one of these.
+     * @param {variant} channel (Optional) The channel to read from. If not
+     *      provided, reads from the default (unnamed) channel.
+     * @return {void}
+     * @method advance
+     */
+    advance: function(tokenTypes, channel){
+
+        while(this.LA(0) !== 0 && !this.match(tokenTypes, channel)){
+            this.get();
+        }
+
+        return this.LA(0);
+    },
+
+    /**
+     * Consumes the next token from the token stream.
+     * @return {int} The token type of the token that was just consumed.
+     * @method get
+     */
+    get: function(channel){
+
+        var tokenInfo   = this._tokenData,
+            i           =0,
+            token,
+            info;
+
+        //check the lookahead buffer first
+        if (this._lt.length && this._ltIndex >= 0 && this._ltIndex < this._lt.length){
+
+            i++;
+            this._token = this._lt[this._ltIndex++];
+            info = tokenInfo[this._token.type];
+
+            //obey channels logic
+            while((info.channel !== undefined && channel !== info.channel) &&
+                    this._ltIndex < this._lt.length){
+                this._token = this._lt[this._ltIndex++];
+                info = tokenInfo[this._token.type];
+                i++;
+            }
+
+            //here be dragons
+            if ((info.channel === undefined || channel === info.channel) &&
+                    this._ltIndex <= this._lt.length){
+                this._ltIndexCache.push(i);
+                return this._token.type;
+            }
+        }
+
+        //call token retriever method
+        token = this._getToken();
+
+        //if it should be hidden, don't save a token
+        if (token.type > -1 && !tokenInfo[token.type].hide){
+
+            //apply token channel
+            token.channel = tokenInfo[token.type].channel;
+
+            //save for later
+            this._token = token;
+            this._lt.push(token);
+
+            //save space that will be moved (must be done before array is truncated)
+            this._ltIndexCache.push(this._lt.length - this._ltIndex + i);
+
+            //keep the buffer under 5 items
+            if (this._lt.length > 5){
+                this._lt.shift();
+            }
+
+            //also keep the shift buffer under 5 items
+            if (this._ltIndexCache.length > 5){
+                this._ltIndexCache.shift();
+            }
+
+            //update lookahead index
+            this._ltIndex = this._lt.length;
+        }
+
+        /*
+         * Skip to the next token if:
+         * 1. The token type is marked as hidden.
+         * 2. The token type has a channel specified and it isn't the current channel.
+         */
+        info = tokenInfo[token.type];
+        if (info &&
+                (info.hide ||
+                (info.channel !== undefined && channel !== info.channel))){
+            return this.get(channel);
+        } else {
+            //return just the type
+            return token.type;
+        }
+    },
+
+    /**
+     * Looks ahead a certain number of tokens and returns the token type at
+     * that position. This will throw an error if you lookahead past the
+     * end of input, past the size of the lookahead buffer, or back past
+     * the first token in the lookahead buffer.
+     * @param {int} The index of the token type to retrieve. 0 for the
+     *      current token, 1 for the next, -1 for the previous, etc.
+     * @return {int} The token type of the token in the given position.
+     * @method LA
+     */
+    LA: function(index){
+        var total = index,
+            tt;
+        if (index > 0){
+            //TODO: Store 5 somewhere
+            if (index > 5){
+                throw new Error("Too much lookahead.");
+            }
+
+            //get all those tokens
+            while(total){
+                tt = this.get();
+                total--;
+            }
+
+            //unget all those tokens
+            while(total < index){
+                this.unget();
+                total++;
+            }
+        } else if (index < 0){
+
+            if(this._lt[this._ltIndex+index]){
+                tt = this._lt[this._ltIndex+index].type;
+            } else {
+                throw new Error("Too much lookbehind.");
+            }
+
+        } else {
+            tt = this._token.type;
+        }
+
+        return tt;
+
+    },
+
+    /**
+     * Looks ahead a certain number of tokens and returns the token at
+     * that position. This will throw an error if you lookahead past the
+     * end of input, past the size of the lookahead buffer, or back past
+     * the first token in the lookahead buffer.
+     * @param {int} The index of the token type to retrieve. 0 for the
+     *      current token, 1 for the next, -1 for the previous, etc.
+     * @return {Object} The token of the token in the given position.
+     * @method LA
+     */
+    LT: function(index){
+
+        //lookahead first to prime the token buffer
+        this.LA(index);
+
+        //now find the token, subtract one because _ltIndex is already at the next index
+        return this._lt[this._ltIndex+index-1];
+    },
+
+    /**
+     * Returns the token type for the next token in the stream without
+     * consuming it.
+     * @return {int} The token type of the next token in the stream.
+     * @method peek
+     */
+    peek: function(){
+        return this.LA(1);
+    },
+
+    /**
+     * Returns the actual token object for the last consumed token.
+     * @return {Token} The token object for the last consumed token.
+     * @method token
+     */
+    token: function(){
+        return this._token;
+    },
+
+    /**
+     * Returns the name of the token for the given token type.
+     * @param {int} tokenType The type of token to get the name of.
+     * @return {String} The name of the token or "UNKNOWN_TOKEN" for any
+     *      invalid token type.
+     * @method tokenName
+     */
+    tokenName: function(tokenType){
+        if (tokenType < 0 || tokenType > this._tokenData.length){
+            return "UNKNOWN_TOKEN";
+        } else {
+            return this._tokenData[tokenType].name;
+        }
+    },
+
+    /**
+     * Returns the token type value for the given token name.
+     * @param {String} tokenName The name of the token whose value should be returned.
+     * @return {int} The token type value for the given token name or -1
+     *      for an unknown token.
+     * @method tokenName
+     */
+    tokenType: function(tokenName){
+        return this._tokenData[tokenName] || -1;
+    },
+
+    /**
+     * Returns the last consumed token to the token stream.
+     * @method unget
+     */
+    unget: function(){
+        //if (this._ltIndex > -1){
+        if (this._ltIndexCache.length){
+            this._ltIndex -= this._ltIndexCache.pop();//--;
+            this._token = this._lt[this._ltIndex - 1];
+        } else {
+            throw new Error("Too much lookahead.");
+        }
+    }
+
+};
+
+
+parserlib.util = {
+__proto__   : null,
+StringReader: StringReader,
+SyntaxError : SyntaxError,
+SyntaxUnit  : SyntaxUnit,
+EventTarget : EventTarget,
+TokenStreamBase : TokenStreamBase
+};
+})();
+/*
+Parser-Lib
+Copyright (c) 2009-2011 Nicholas C. Zakas. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+/* Version v0.2.5+domino1, Build time: 30-January-2016 05:13:03 */
+(function(){
+var EventTarget = parserlib.util.EventTarget,
+TokenStreamBase = parserlib.util.TokenStreamBase,
+StringReader = parserlib.util.StringReader, // jshint ignore:line
+SyntaxError = parserlib.util.SyntaxError,
+SyntaxUnit  = parserlib.util.SyntaxUnit;
+
+var Colors = {
+    __proto__       :null,
+    aliceblue       :"#f0f8ff",
+    antiquewhite    :"#faebd7",
+    aqua            :"#00ffff",
+    aquamarine      :"#7fffd4",
+    azure           :"#f0ffff",
+    beige           :"#f5f5dc",
+    bisque          :"#ffe4c4",
+    black           :"#000000",
+    blanchedalmond  :"#ffebcd",
+    blue            :"#0000ff",
+    blueviolet      :"#8a2be2",
+    brown           :"#a52a2a",
+    burlywood       :"#deb887",
+    cadetblue       :"#5f9ea0",
+    chartreuse      :"#7fff00",
+    chocolate       :"#d2691e",
+    coral           :"#ff7f50",
+    cornflowerblue  :"#6495ed",
+    cornsilk        :"#fff8dc",
+    crimson         :"#dc143c",
+    cyan            :"#00ffff",
+    darkblue        :"#00008b",
+    darkcyan        :"#008b8b",
+    darkgoldenrod   :"#b8860b",
+    darkgray        :"#a9a9a9",
+    darkgrey        :"#a9a9a9",
+    darkgreen       :"#006400",
+    darkkhaki       :"#bdb76b",
+    darkmagenta     :"#8b008b",
+    darkolivegreen  :"#556b2f",
+    darkorange      :"#ff8c00",
+    darkorchid      :"#9932cc",
+    darkred         :"#8b0000",
+    darksalmon      :"#e9967a",
+    darkseagreen    :"#8fbc8f",
+    darkslateblue   :"#483d8b",
+    darkslategray   :"#2f4f4f",
+    darkslategrey   :"#2f4f4f",
+    darkturquoise   :"#00ced1",
+    darkviolet      :"#9400d3",
+    deeppink        :"#ff1493",
+    deepskyblue     :"#00bfff",
+    dimgray         :"#696969",
+    dimgrey         :"#696969",
+    dodgerblue      :"#1e90ff",
+    firebrick       :"#b22222",
+    floralwhite     :"#fffaf0",
+    forestgreen     :"#228b22",
+    fuchsia         :"#ff00ff",
+    gainsboro       :"#dcdcdc",
+    ghostwhite      :"#f8f8ff",
+    gold            :"#ffd700",
+    goldenrod       :"#daa520",
+    gray            :"#808080",
+    grey            :"#808080",
+    green           :"#008000",
+    greenyellow     :"#adff2f",
+    honeydew        :"#f0fff0",
+    hotpink         :"#ff69b4",
+    indianred       :"#cd5c5c",
+    indigo          :"#4b0082",
+    ivory           :"#fffff0",
+    khaki           :"#f0e68c",
+    lavender        :"#e6e6fa",
+    lavenderblush   :"#fff0f5",
+    lawngreen       :"#7cfc00",
+    lemonchiffon    :"#fffacd",
+    lightblue       :"#add8e6",
+    lightcoral      :"#f08080",
+    lightcyan       :"#e0ffff",
+    lightgoldenrodyellow  :"#fafad2",
+    lightgray       :"#d3d3d3",
+    lightgrey       :"#d3d3d3",
+    lightgreen      :"#90ee90",
+    lightpink       :"#ffb6c1",
+    lightsalmon     :"#ffa07a",
+    lightseagreen   :"#20b2aa",
+    lightskyblue    :"#87cefa",
+    lightslategray  :"#778899",
+    lightslategrey  :"#778899",
+    lightsteelblue  :"#b0c4de",
+    lightyellow     :"#ffffe0",
+    lime            :"#00ff00",
+    limegreen       :"#32cd32",
+    linen           :"#faf0e6",
+    magenta         :"#ff00ff",
+    maroon          :"#800000",
+    mediumaquamarine:"#66cdaa",
+    mediumblue      :"#0000cd",
+    mediumorchid    :"#ba55d3",
+    mediumpurple    :"#9370d8",
+    mediumseagreen  :"#3cb371",
+    mediumslateblue :"#7b68ee",
+    mediumspringgreen   :"#00fa9a",
+    mediumturquoise :"#48d1cc",
+    mediumvioletred :"#c71585",
+    midnightblue    :"#191970",
+    mintcream       :"#f5fffa",
+    mistyrose       :"#ffe4e1",
+    moccasin        :"#ffe4b5",
+    navajowhite     :"#ffdead",
+    navy            :"#000080",
+    oldlace         :"#fdf5e6",
+    olive           :"#808000",
+    olivedrab       :"#6b8e23",
+    orange          :"#ffa500",
+    orangered       :"#ff4500",
+    orchid          :"#da70d6",
+    palegoldenrod   :"#eee8aa",
+    palegreen       :"#98fb98",
+    paleturquoise   :"#afeeee",
+    palevioletred   :"#d87093",
+    papayawhip      :"#ffefd5",
+    peachpuff       :"#ffdab9",
+    peru            :"#cd853f",
+    pink            :"#ffc0cb",
+    plum            :"#dda0dd",
+    powderblue      :"#b0e0e6",
+    purple          :"#800080",
+    red             :"#ff0000",
+    rosybrown       :"#bc8f8f",
+    royalblue       :"#4169e1",
+    saddlebrown     :"#8b4513",
+    salmon          :"#fa8072",
+    sandybrown      :"#f4a460",
+    seagreen        :"#2e8b57",
+    seashell        :"#fff5ee",
+    sienna          :"#a0522d",
+    silver          :"#c0c0c0",
+    skyblue         :"#87ceeb",
+    slateblue       :"#6a5acd",
+    slategray       :"#708090",
+    slategrey       :"#708090",
+    snow            :"#fffafa",
+    springgreen     :"#00ff7f",
+    steelblue       :"#4682b4",
+    tan             :"#d2b48c",
+    teal            :"#008080",
+    thistle         :"#d8bfd8",
+    tomato          :"#ff6347",
+    turquoise       :"#40e0d0",
+    violet          :"#ee82ee",
+    wheat           :"#f5deb3",
+    white           :"#ffffff",
+    whitesmoke      :"#f5f5f5",
+    yellow          :"#ffff00",
+    yellowgreen     :"#9acd32",
+    //'currentColor' color keyword http://www.w3.org/TR/css3-color/#currentcolor
+    currentColor        :"The value of the 'color' property.",
+    //CSS2 system colors http://www.w3.org/TR/css3-color/#css2-system
+    activeBorder        :"Active window border.",
+    activecaption       :"Active window caption.",
+    appworkspace        :"Background color of multiple document interface.",
+    background          :"Desktop background.",
+    buttonface          :"The face background color for 3-D elements that appear 3-D due to one layer of surrounding border.",
+    buttonhighlight     :"The color of the border facing the light source for 3-D elements that appear 3-D due to one layer of surrounding border.",
+    buttonshadow        :"The color of the border away from the light source for 3-D elements that appear 3-D due to one layer of surrounding border.",
+    buttontext          :"Text on push buttons.",
+    captiontext         :"Text in caption, size box, and scrollbar arrow box.",
+    graytext            :"Grayed (disabled) text. This color is set to #000 if the current display driver does not support a solid gray color.",
+    greytext            :"Greyed (disabled) text. This color is set to #000 if the current display driver does not support a solid grey color.",
+    highlight           :"Item(s) selected in a control.",
+    highlighttext       :"Text of item(s) selected in a control.",
+    inactiveborder      :"Inactive window border.",
+    inactivecaption     :"Inactive window caption.",
+    inactivecaptiontext :"Color of text in an inactive caption.",
+    infobackground      :"Background color for tooltip controls.",
+    infotext            :"Text color for tooltip controls.",
+    menu                :"Menu background.",
+    menutext            :"Text in menus.",
+    scrollbar           :"Scroll bar gray area.",
+    threeddarkshadow    :"The color of the darker (generally outer) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
+    threedface          :"The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
+    threedhighlight     :"The color of the lighter (generally outer) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
+    threedlightshadow   :"The color of the darker (generally inner) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
+    threedshadow        :"The color of the lighter (generally inner) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
+    window              :"Window background.",
+    windowframe         :"Window frame.",
+    windowtext          :"Text in windows."
+};
+/**
+ * Represents a selector combinator (whitespace, +, >).
+ * @namespace parserlib.css
+ * @class Combinator
+ * @extends parserlib.util.SyntaxUnit
+ * @constructor
+ * @param {String} text The text representation of the unit.
+ * @param {int} line The line of text on which the unit resides.
+ * @param {int} col The column of text on which the unit resides.
+ */
+function Combinator(text, line, col){
+
+    SyntaxUnit.call(this, text, line, col, Parser.COMBINATOR_TYPE);
+
+    /**
+     * The type of modifier.
+     * @type String
+     * @property type
+     */
+    this.type = "unknown";
+
+    //pretty simple
+    if (/^\s+$/.test(text)){
+        this.type = "descendant";
+    } else if (text === ">"){
+        this.type = "child";
+    } else if (text === "+"){
+        this.type = "adjacent-sibling";
+    } else if (text === "~"){
+        this.type = "sibling";
+    }
+
+}
+
+Combinator.prototype = new SyntaxUnit();
+Combinator.prototype.constructor = Combinator;
+
+/**
+ * Represents a media feature, such as max-width:500.
+ * @namespace parserlib.css
+ * @class MediaFeature
+ * @extends parserlib.util.SyntaxUnit
+ * @constructor
+ * @param {SyntaxUnit} name The name of the feature.
+ * @param {SyntaxUnit} value The value of the feature or null if none.
+ */
+function MediaFeature(name, value){
+
+    SyntaxUnit.call(this, "(" + name + (value !== null ? ":" + value : "") + ")", name.startLine, name.startCol, Parser.MEDIA_FEATURE_TYPE);
+
+    /**
+     * The name of the media feature
+     * @type String
+     * @property name
+     */
+    this.name = name;
+
+    /**
+     * The value for the feature or null if there is none.
+     * @type SyntaxUnit
+     * @property value
+     */
+    this.value = value;
+}
+
+MediaFeature.prototype = new SyntaxUnit();
+MediaFeature.prototype.constructor = MediaFeature;
+
+/**
+ * Represents an individual media query.
+ * @namespace parserlib.css
+ * @class MediaQuery
+ * @extends parserlib.util.SyntaxUnit
+ * @constructor
+ * @param {String} modifier The modifier "not" or "only" (or null).
+ * @param {String} mediaType The type of media (i.e., "print").
+ * @param {Array} parts Array of selectors parts making up this selector.
+ * @param {int} line The line of text on which the unit resides.
+ * @param {int} col The column of text on which the unit resides.
+ */
+function MediaQuery(modifier, mediaType, features, line, col){
+
+    SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType : "") + (mediaType && features.length > 0 ? " and " : "") + features.join(" and "), line, col, Parser.MEDIA_QUERY_TYPE);
+
+    /**
+     * The media modifier ("not" or "only")
+     * @type String
+     * @property modifier
+     */
+    this.modifier = modifier;
+
+    /**
+     * The mediaType (i.e., "print")
+     * @type String
+     * @property mediaType
+     */
+    this.mediaType = mediaType;
+
+    /**
+     * The parts that make up the selector.
+     * @type Array
+     * @property features
+     */
+    this.features = features;
+
+}
+
+MediaQuery.prototype = new SyntaxUnit();
+MediaQuery.prototype.constructor = MediaQuery;
+
+
+/**
+ * A CSS3 parser.
+ * @namespace parserlib.css
+ * @class Parser
+ * @constructor
+ * @param {Object} options (Optional) Various options for the parser:
+ *      starHack (true|false) to allow IE6 star hack as valid,
+ *      underscoreHack (true|false) to interpret leading underscores
+ *      as IE6-7 targeting for known properties, ieFilters (true|false)
+ *      to indicate that IE < 8 filters should be accepted and not throw
+ *      syntax errors.
+ */
+function Parser(options){
+
+    //inherit event functionality
+    EventTarget.call(this);
+
+
+    this.options = options || {};
+
+    this._tokenStream = null;
+}
+
+//Static constants
+Parser.DEFAULT_TYPE = 0;
+Parser.COMBINATOR_TYPE = 1;
+Parser.MEDIA_FEATURE_TYPE = 2;
+Parser.MEDIA_QUERY_TYPE = 3;
+Parser.PROPERTY_NAME_TYPE = 4;
+Parser.PROPERTY_VALUE_TYPE = 5;
+Parser.PROPERTY_VALUE_PART_TYPE = 6;
+Parser.SELECTOR_TYPE = 7;
+Parser.SELECTOR_PART_TYPE = 8;
+Parser.SELECTOR_SUB_PART_TYPE = 9;
+
+Parser.prototype = function(){
+
+    var proto = new EventTarget(),  //new prototype
+        prop,
+        additions =  {
+            __proto__: null,
+
+            //restore constructor
+            constructor: Parser,
+
+            //instance constants - yuck
+            DEFAULT_TYPE : 0,
+            COMBINATOR_TYPE : 1,
+            MEDIA_FEATURE_TYPE : 2,
+            MEDIA_QUERY_TYPE : 3,
+            PROPERTY_NAME_TYPE : 4,
+            PROPERTY_VALUE_TYPE : 5,
+            PROPERTY_VALUE_PART_TYPE : 6,
+            SELECTOR_TYPE : 7,
+            SELECTOR_PART_TYPE : 8,
+            SELECTOR_SUB_PART_TYPE : 9,
+
+            //-----------------------------------------------------------------
+            // Grammar
+            //-----------------------------------------------------------------
+
+            _stylesheet: function(){
+
+                /*
+                 * stylesheet
+                 *  : [ CHARSET_SYM S* STRING S* ';' ]?
+                 *    [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
+                 *    [ namespace [S|CDO|CDC]* ]*
+                 *    [ [ ruleset | media | page | font_face | keyframes ] [S|CDO|CDC]* ]*
+                 *  ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    count,
+                    token,
+                    tt;
+
+                this.fire("startstylesheet");
+
+                //try to read character set
+                this._charset();
+
+                this._skipCruft();
+
+                //try to read imports - may be more than one
+                while (tokenStream.peek() === Tokens.IMPORT_SYM){
+                    this._import();
+                    this._skipCruft();
+                }
+
+                //try to read namespaces - may be more than one
+                while (tokenStream.peek() === Tokens.NAMESPACE_SYM){
+                    this._namespace();
+                    this._skipCruft();
+                }
+
+                //get the next token
+                tt = tokenStream.peek();
+
+                //try to read the rest
+                while(tt > Tokens.EOF){
+
+                    try {
+
+                        switch(tt){
+                            case Tokens.MEDIA_SYM:
+                                this._media();
+                                this._skipCruft();
+                                break;
+                            case Tokens.PAGE_SYM:
+                                this._page();
+                                this._skipCruft();
+                                break;
+                            case Tokens.FONT_FACE_SYM:
+                                this._font_face();
+                                this._skipCruft();
+                                break;
+                            case Tokens.KEYFRAMES_SYM:
+                                this._keyframes();
+                                this._skipCruft();
+                                break;
+                            case Tokens.VIEWPORT_SYM:
+                                this._viewport();
+                                this._skipCruft();
+                                break;
+                            case Tokens.DOCUMENT_SYM:
+                                this._document();
+                                this._skipCruft();
+                                break;
+                            case Tokens.UNKNOWN_SYM:  //unknown @ rule
+                                tokenStream.get();
+                                if (!this.options.strict){
+
+                                    //fire error event
+                                    this.fire({
+                                        type:       "error",
+                                        error:      null,
+                                        message:    "Unknown @ rule: " + tokenStream.LT(0).value + ".",
+                                        line:       tokenStream.LT(0).startLine,
+                                        col:        tokenStream.LT(0).startCol
+                                    });
+
+                                    //skip braces
+                                    count=0;
+                                    while (tokenStream.advance([Tokens.LBRACE, Tokens.RBRACE]) === Tokens.LBRACE){
+                                        count++;    //keep track of nesting depth
+                                    }
+
+                                    while(count){
+                                        tokenStream.advance([Tokens.RBRACE]);
+                                        count--;
+                                    }
+
+                                } else {
+                                    //not a syntax error, rethrow it
+                                    throw new SyntaxError("Unknown @ rule.", tokenStream.LT(0).startLine, tokenStream.LT(0).startCol);
+                                }
+                                break;
+                            case Tokens.S:
+                                this._readWhitespace();
+                                break;
+                            default:
+                                if(!this._ruleset()){
+
+                                    //error handling for known issues
+                                    switch(tt){
+                                        case Tokens.CHARSET_SYM:
+                                            token = tokenStream.LT(1);
+                                            this._charset(false);
+                                            throw new SyntaxError("@charset not allowed here.", token.startLine, token.startCol);
+                                        case Tokens.IMPORT_SYM:
+                                            token = tokenStream.LT(1);
+                                            this._import(false);
+                                            throw new SyntaxError("@import not allowed here.", token.startLine, token.startCol);
+                                        case Tokens.NAMESPACE_SYM:
+                                            token = tokenStream.LT(1);
+                                            this._namespace(false);
+                                            throw new SyntaxError("@namespace not allowed here.", token.startLine, token.startCol);
+                                        default:
+                                            tokenStream.get();  //get the last token
+                                            this._unexpectedToken(tokenStream.token());
+                                    }
+
+                                }
+                        }
+                    } catch(ex) {
+                        if (ex instanceof SyntaxError && !this.options.strict){
+                            this.fire({
+                                type:       "error",
+                                error:      ex,
+                                message:    ex.message,
+                                line:       ex.line,
+                                col:        ex.col
+                            });
+                        } else {
+                            throw ex;
+                        }
+                    }
+
+                    tt = tokenStream.peek();
+                }
+
+                if (tt !== Tokens.EOF){
+                    this._unexpectedToken(tokenStream.token());
+                }
+
+                this.fire("endstylesheet");
+            },
+
+            _charset: function(emit){
+                var tokenStream = this._tokenStream,
+                    charset,
+                    token,
+                    line,
+                    col;
+
+                if (tokenStream.match(Tokens.CHARSET_SYM)){
+                    line = tokenStream.token().startLine;
+                    col = tokenStream.token().startCol;
+
+                    this._readWhitespace();
+                    tokenStream.mustMatch(Tokens.STRING);
+
+                    token = tokenStream.token();
+                    charset = token.value;
+
+                    this._readWhitespace();
+                    tokenStream.mustMatch(Tokens.SEMICOLON);
+
+                    if (emit !== false){
+                        this.fire({
+                            type:   "charset",
+                            charset:charset,
+                            line:   line,
+                            col:    col
+                        });
+                    }
+                }
+            },
+
+            _import: function(emit){
+                /*
+                 * import
+                 *   : IMPORT_SYM S*
+                 *    [STRING|URI] S* media_query_list? ';' S*
+                 */
+
+                var tokenStream = this._tokenStream,
+                    uri,
+                    importToken,
+                    mediaList   = [];
+
+                //read import symbol
+                tokenStream.mustMatch(Tokens.IMPORT_SYM);
+                importToken = tokenStream.token();
+                this._readWhitespace();
+
+                tokenStream.mustMatch([Tokens.STRING, Tokens.URI]);
+
+                //grab the URI value
+                uri = tokenStream.token().value.replace(/^(?:url\()?["']?([^"']+?)["']?\)?$/, "$1");
+
+                this._readWhitespace();
+
+                mediaList = this._media_query_list();
+
+                //must end with a semicolon
+                tokenStream.mustMatch(Tokens.SEMICOLON);
+                this._readWhitespace();
+
+                if (emit !== false){
+                    this.fire({
+                        type:   "import",
+                        uri:    uri,
+                        media:  mediaList,
+                        line:   importToken.startLine,
+                        col:    importToken.startCol
+                    });
+                }
+
+            },
+
+            _namespace: function(emit){
+                /*
+                 * namespace
+                 *   : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S*
+                 */
+
+                var tokenStream = this._tokenStream,
+                    line,
+                    col,
+                    prefix,
+                    uri;
+
+                //read import symbol
+                tokenStream.mustMatch(Tokens.NAMESPACE_SYM);
+                line = tokenStream.token().startLine;
+                col = tokenStream.token().startCol;
+                this._readWhitespace();
+
+                //it's a namespace prefix - no _namespace_prefix() method because it's just an IDENT
+                if (tokenStream.match(Tokens.IDENT)){
+                    prefix = tokenStream.token().value;
+                    this._readWhitespace();
+                }
+
+                tokenStream.mustMatch([Tokens.STRING, Tokens.URI]);
+                /*if (!tokenStream.match(Tokens.STRING)){
+                    tokenStream.mustMatch(Tokens.URI);
+                }*/
+
+                //grab the URI value
+                uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1");
+
+                this._readWhitespace();
+
+                //must end with a semicolon
+                tokenStream.mustMatch(Tokens.SEMICOLON);
+                this._readWhitespace();
+
+                if (emit !== false){
+                    this.fire({
+                        type:   "namespace",
+                        prefix: prefix,
+                        uri:    uri,
+                        line:   line,
+                        col:    col
+                    });
+                }
+
+            },
+
+            _media: function(){
+                /*
+                 * media
+                 *   : MEDIA_SYM S* media_query_list S* '{' S* ruleset* '}' S*
+                 *   ;
+                 */
+                var tokenStream     = this._tokenStream,
+                    line,
+                    col,
+                    mediaList;//       = [];
+
+                //look for @media
+                tokenStream.mustMatch(Tokens.MEDIA_SYM);
+                line = tokenStream.token().startLine;
+                col = tokenStream.token().startCol;
+
+                this._readWhitespace();
+
+                mediaList = this._media_query_list();
+
+                tokenStream.mustMatch(Tokens.LBRACE);
+                this._readWhitespace();
+
+                this.fire({
+                    type:   "startmedia",
+                    media:  mediaList,
+                    line:   line,
+                    col:    col
+                });
+
+                while(true) {
+                    if (tokenStream.peek() === Tokens.PAGE_SYM){
+                        this._page();
+                    } else if (tokenStream.peek() === Tokens.FONT_FACE_SYM){
+                        this._font_face();
+                    } else if (tokenStream.peek() === Tokens.VIEWPORT_SYM){
+                        this._viewport();
+                    } else if (tokenStream.peek() === Tokens.DOCUMENT_SYM){
+                        this._document();
+                    } else if (!this._ruleset()){
+                        break;
+                    }
+                }
+
+                tokenStream.mustMatch(Tokens.RBRACE);
+                this._readWhitespace();
+
+                this.fire({
+                    type:   "endmedia",
+                    media:  mediaList,
+                    line:   line,
+                    col:    col
+                });
+            },
+
+
+            //CSS3 Media Queries
+            _media_query_list: function(){
+                /*
+                 * media_query_list
+                 *   : S* [media_query [ ',' S* media_query ]* ]?
+                 *   ;
+                 */
+                var tokenStream = this._tokenStream,
+                    mediaList   = [];
+
+
+                this._readWhitespace();
+
+                if (tokenStream.peek() === Tokens.IDENT || tokenStream.peek() === Tokens.LPAREN){
+                    mediaList.push(this._media_query());
+                }
+
+                while(tokenStream.match(Tokens.COMMA)){
+                    this._readWhitespace();
+                    mediaList.push(this._media_query());
+                }
+
+                return mediaList;
+            },
+
+            /*
+             * Note: "expression" in the grammar maps to the _media_expression
+             * method.
+
+             */
+            _media_query: function(){
+                /*
+                 * media_query
+                 *   : [ONLY | NOT]? S* media_type S* [ AND S* expression ]*
+                 *   | expression [ AND S* expression ]*
+                 *   ;
+                 */
+                var tokenStream = this._tokenStream,
+                    type        = null,
+                    ident       = null,
+                    token       = null,
+                    expressions = [];
+
+                if (tokenStream.match(Tokens.IDENT)){
+                    ident = tokenStream.token().value.toLowerCase();
+
+                    //since there's no custom tokens for these, need to manually check
+                    if (ident !== "only" && ident !== "not"){
+                        tokenStream.unget();
+                        ident = null;
+                    } else {
+                        token = tokenStream.token();
+                    }
+                }
+
+                this._readWhitespace();
+
+                if (tokenStream.peek() === Tokens.IDENT){
+                    type = this._media_type();
+                    if (token === null){
+                        token = tokenStream.token();
+                    }
+                } else if (tokenStream.peek() === Tokens.LPAREN){
+                    if (token === null){
+                        token = tokenStream.LT(1);
+                    }
+                    expressions.push(this._media_expression());
+                }
+
+                if (type === null && expressions.length === 0){
+                    return null;
+                } else {
+                    this._readWhitespace();
+                    while (tokenStream.match(Tokens.IDENT)){
+                        if (tokenStream.token().value.toLowerCase() !== "and"){
+                            this._unexpectedToken(tokenStream.token());
+                        }
+
+                        this._readWhitespace();
+                        expressions.push(this._media_expression());
+                    }
+                }
+
+                return new MediaQuery(ident, type, expressions, token.startLine, token.startCol);
+            },
+
+            //CSS3 Media Queries
+            _media_type: function(){
+                /*
+                 * media_type
+                 *   : IDENT
+                 *   ;
+                 */
+                return this._media_feature();
+            },
+
+            /**
+             * Note: in CSS3 Media Queries, this is called "expression".
+             * Renamed here to avoid conflict with CSS3 Selectors
+             * definition of "expression". Also note that "expr" in the
+             * grammar now maps to "expression" from CSS3 selectors.
+             * @method _media_expression
+             * @private
+             */
+            _media_expression: function(){
+                /*
+                 * expression
+                 *  : '(' S* media_feature S* [ ':' S* expr ]? ')' S*
+                 *  ;
+                 */
+                var tokenStream = this._tokenStream,
+                    feature     = null,
+                    token,
+                    expression  = null;
+
+                tokenStream.mustMatch(Tokens.LPAREN);
+
+                feature = this._media_feature();
+                this._readWhitespace();
+
+                if (tokenStream.match(Tokens.COLON)){
+                    this._readWhitespace();
+                    token = tokenStream.LT(1);
+                    expression = this._expression();
+                }
+
+                tokenStream.mustMatch(Tokens.RPAREN);
+                this._readWhitespace();
+
+                return new MediaFeature(feature, (expression ? new SyntaxUnit(expression, token.startLine, token.startCol) : null));
+            },
+
+            //CSS3 Media Queries
+            _media_feature: function(){
+                /*
+                 * media_feature
+                 *   : IDENT
+                 *   ;
+                 */
+                var tokenStream = this._tokenStream;
+
+                this._readWhitespace();
+
+                tokenStream.mustMatch(Tokens.IDENT);
+
+                return SyntaxUnit.fromToken(tokenStream.token());
+            },
+
+            //CSS3 Paged Media
+            _page: function(){
+                /*
+                 * page:
+                 *    PAGE_SYM S* IDENT? pseudo_page? S*
+                 *    '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S*
+                 *    ;
+                 */
+                var tokenStream = this._tokenStream,
+                    line,
+                    col,
+                    identifier  = null,
+                    pseudoPage  = null;
+
+                //look for @page
+                tokenStream.mustMatch(Tokens.PAGE_SYM);
+                line = tokenStream.token().startLine;
+                col = tokenStream.token().startCol;
+
+                this._readWhitespace();
+
+                if (tokenStream.match(Tokens.IDENT)){
+                    identifier = tokenStream.token().value;
+
+                    //The value 'auto' may not be used as a page name and MUST be treated as a syntax error.
+                    if (identifier.toLowerCase() === "auto"){
+                        this._unexpectedToken(tokenStream.token());
+                    }
+                }
+
+                //see if there's a colon upcoming
+                if (tokenStream.peek() === Tokens.COLON){
+                    pseudoPage = this._pseudo_page();
+                }
+
+                this._readWhitespace();
+
+                this.fire({
+                    type:   "startpage",
+                    id:     identifier,
+                    pseudo: pseudoPage,
+                    line:   line,
+                    col:    col
+                });
+
+                this._readDeclarations(true, true);
+
+                this.fire({
+                    type:   "endpage",
+                    id:     identifier,
+                    pseudo: pseudoPage,
+                    line:   line,
+                    col:    col
+                });
+
+            },
+
+            //CSS3 Paged Media
+            _margin: function(){
+                /*
+                 * margin :
+                 *    margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S*
+                 *    ;
+                 */
+                var tokenStream = this._tokenStream,
+                    line,
+                    col,
+                    marginSym   = this._margin_sym();
+
+                if (marginSym){
+                    line = tokenStream.token().startLine;
+                    col = tokenStream.token().startCol;
+
+                    this.fire({
+                        type: "startpagemargin",
+                        margin: marginSym,
+                        line:   line,
+                        col:    col
+                    });
+
+                    this._readDeclarations(true);
+
+                    this.fire({
+                        type: "endpagemargin",
+                        margin: marginSym,
+                        line:   line,
+                        col:    col
+                    });
+                    return true;
+                } else {
+                    return false;
+                }
+            },
+
+            //CSS3 Paged Media
+            _margin_sym: function(){
+
+                /*
+                 * margin_sym :
+                 *    TOPLEFTCORNER_SYM |
+                 *    TOPLEFT_SYM |
+                 *    TOPCENTER_SYM |
+                 *    TOPRIGHT_SYM |
+                 *    TOPRIGHTCORNER_SYM |
+                 *    BOTTOMLEFTCORNER_SYM |
+                 *    BOTTOMLEFT_SYM |
+                 *    BOTTOMCENTER_SYM |
+                 *    BOTTOMRIGHT_SYM |
+                 *    BOTTOMRIGHTCORNER_SYM |
+                 *    LEFTTOP_SYM |
+                 *    LEFTMIDDLE_SYM |
+                 *    LEFTBOTTOM_SYM |
+                 *    RIGHTTOP_SYM |
+                 *    RIGHTMIDDLE_SYM |
+                 *    RIGHTBOTTOM_SYM
+                 *    ;
+                 */
+
+                var tokenStream = this._tokenStream;
+
+                if(tokenStream.match([Tokens.TOPLEFTCORNER_SYM, Tokens.TOPLEFT_SYM,
+                        Tokens.TOPCENTER_SYM, Tokens.TOPRIGHT_SYM, Tokens.TOPRIGHTCORNER_SYM,
+                        Tokens.BOTTOMLEFTCORNER_SYM, Tokens.BOTTOMLEFT_SYM,
+                        Tokens.BOTTOMCENTER_SYM, Tokens.BOTTOMRIGHT_SYM,
+                        Tokens.BOTTOMRIGHTCORNER_SYM, Tokens.LEFTTOP_SYM,
+                        Tokens.LEFTMIDDLE_SYM, Tokens.LEFTBOTTOM_SYM, Tokens.RIGHTTOP_SYM,
+                        Tokens.RIGHTMIDDLE_SYM, Tokens.RIGHTBOTTOM_SYM]))
+                {
+                    return SyntaxUnit.fromToken(tokenStream.token());
+                } else {
+                    return null;
+                }
+
+            },
+
+            _pseudo_page: function(){
+                /*
+                 * pseudo_page
+                 *   : ':' IDENT
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream;
+
+                tokenStream.mustMatch(Tokens.COLON);
+                tokenStream.mustMatch(Tokens.IDENT);
+
+                //TODO: CSS3 Paged Media says only "left", "center", and "right" are allowed
+
+                return tokenStream.token().value;
+            },
+
+            _font_face: function(){
+                /*
+                 * font_face
+                 *   : FONT_FACE_SYM S*
+                 *     '{' S* declaration [ ';' S* declaration ]* '}' S*
+                 *   ;
+                 */
+                var tokenStream = this._tokenStream,
+                    line,
+                    col;
+
+                //look for @page
+                tokenStream.mustMatch(Tokens.FONT_FACE_SYM);
+                line = tokenStream.token().startLine;
+                col = tokenStream.token().startCol;
+
+                this._readWhitespace();
+
+                this.fire({
+                    type:   "startfontface",
+                    line:   line,
+                    col:    col
+                });
+
+                this._readDeclarations(true);
+
+                this.fire({
+                    type:   "endfontface",
+                    line:   line,
+                    col:    col
+                });
+            },
+
+            _viewport: function(){
+                /*
+                 * viewport
+                 *   : VIEWPORT_SYM S*
+                 *     '{' S* declaration? [ ';' S* declaration? ]* '}' S*
+                 *   ;
+                 */
+                 var tokenStream = this._tokenStream,
+                    line,
+                    col;
+
+                    tokenStream.mustMatch(Tokens.VIEWPORT_SYM);
+                    line = tokenStream.token().startLine;
+                    col = tokenStream.token().startCol;
+
+                    this._readWhitespace();
+
+                    this.fire({
+                        type:   "startviewport",
+                        line:   line,
+                        col:    col
+                    });
+
+                    this._readDeclarations(true);
+
+                    this.fire({
+                        type:   "endviewport",
+                        line:   line,
+                        col:    col
+                    });
+
+            },
+
+            _document: function(){
+                /*
+                 * document
+                 *   : DOCUMENT_SYM S*
+                 *     _document_function [ ',' S* _document_function ]* S*
+                 *     '{' S* ruleset* '}'
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    token,
+                    functions = [],
+                    prefix = "";
+
+                tokenStream.mustMatch(Tokens.DOCUMENT_SYM);
+                token = tokenStream.token();
+                if (/^@\-([^\-]+)\-/.test(token.value)) {
+                    prefix = RegExp.$1;
+                }
+
+                this._readWhitespace();
+                functions.push(this._document_function());
+
+                while(tokenStream.match(Tokens.COMMA)) {
+                    this._readWhitespace();
+                    functions.push(this._document_function());
+                }
+
+                tokenStream.mustMatch(Tokens.LBRACE);
+                this._readWhitespace();
+
+                this.fire({
+                    type:      "startdocument",
+                    functions: functions,
+                    prefix:    prefix,
+                    line:      token.startLine,
+                    col:       token.startCol
+                });
+
+                while(true) {
+                    if (tokenStream.peek() === Tokens.PAGE_SYM){
+                        this._page();
+                    } else if (tokenStream.peek() === Tokens.FONT_FACE_SYM){
+                        this._font_face();
+                    } else if (tokenStream.peek() === Tokens.VIEWPORT_SYM){
+                        this._viewport();
+                    } else if (tokenStream.peek() === Tokens.MEDIA_SYM){
+                        this._media();
+                    } else if (!this._ruleset()){
+                        break;
+                    }
+                }
+
+                tokenStream.mustMatch(Tokens.RBRACE);
+                this._readWhitespace();
+
+                this.fire({
+                    type:      "enddocument",
+                    functions: functions,
+                    prefix:    prefix,
+                    line:      token.startLine,
+                    col:       token.startCol
+                });
+            },
+
+            _document_function: function(){
+                /*
+                 * document_function
+                 *   : function | URI S*
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    value;
+
+                if (tokenStream.match(Tokens.URI)) {
+                    value = tokenStream.token().value;
+                    this._readWhitespace();
+                } else {
+                    value = this._function();
+                }
+
+                return value;
+            },
+
+            _operator: function(inFunction){
+
+                /*
+                 * operator (outside function)
+                 *  : '/' S* | ',' S* | /( empty )/
+                 * operator (inside function)
+                 *  : '/' S* | '+' S* | '*' S* | '-' S* /( empty )/
+                 *  ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    token       = null;
+
+                if (tokenStream.match([Tokens.SLASH, Tokens.COMMA]) ||
+                    (inFunction && tokenStream.match([Tokens.PLUS, Tokens.STAR, Tokens.MINUS]))){
+                    token =  tokenStream.token();
+                    this._readWhitespace();
+                }
+                return token ? PropertyValuePart.fromToken(token) : null;
+
+            },
+
+            _combinator: function(){
+
+                /*
+                 * combinator
+                 *  : PLUS S* | GREATER S* | TILDE S* | S+
+                 *  ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    value       = null,
+                    token;
+
+                if(tokenStream.match([Tokens.PLUS, Tokens.GREATER, Tokens.TILDE])){
+                    token = tokenStream.token();
+                    value = new Combinator(token.value, token.startLine, token.startCol);
+                    this._readWhitespace();
+                }
+
+                return value;
+            },
+
+            _unary_operator: function(){
+
+                /*
+                 * unary_operator
+                 *  : '-' | '+'
+                 *  ;
+                 */
+
+                var tokenStream = this._tokenStream;
+
+                if (tokenStream.match([Tokens.MINUS, Tokens.PLUS])){
+                    return tokenStream.token().value;
+                } else {
+                    return null;
+                }
+            },
+
+            _property: function(){
+
+                /*
+                 * property
+                 *   : IDENT S*
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    value       = null,
+                    hack        = null,
+                    tokenValue,
+                    token,
+                    line,
+                    col;
+
+                //check for star hack - throws error if not allowed
+                if (tokenStream.peek() === Tokens.STAR && this.options.starHack){
+                    tokenStream.get();
+                    token = tokenStream.token();
+                    hack = token.value;
+                    line = token.startLine;
+                    col = token.startCol;
+                }
+
+                if(tokenStream.match(Tokens.IDENT)){
+                    token = tokenStream.token();
+                    tokenValue = token.value;
+
+                    //check for underscore hack - no error if not allowed because it's valid CSS syntax
+                    if (tokenValue.charAt(0) === "_" && this.options.underscoreHack){
+                        hack = "_";
+                        tokenValue = tokenValue.substring(1);
+                    }
+
+                    value = new PropertyName(tokenValue, hack, (line||token.startLine), (col||token.startCol));
+                    this._readWhitespace();
+                }
+
+                return value;
+            },
+
+            //Augmented with CSS3 Selectors
+            _ruleset: function(){
+                /*
+                 * ruleset
+                 *   : selectors_group
+                 *     '{' S* declaration? [ ';' S* declaration? ]* '}' S*
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    tt,
+                    selectors;
+
+
+                /*
+                 * Error Recovery: If even a single selector fails to parse,
+                 * then the entire ruleset should be thrown away.
+                 */
+                try {
+                    selectors = this._selectors_group();
+                } catch (ex){
+                    if (ex instanceof SyntaxError && !this.options.strict){
+
+                        //fire error event
+                        this.fire({
+                            type:       "error",
+                            error:      ex,
+                            message:    ex.message,
+                            line:       ex.line,
+                            col:        ex.col
+                        });
+
+                        //skip over everything until closing brace
+                        tt = tokenStream.advance([Tokens.RBRACE]);
+                        if (tt === Tokens.RBRACE){
+                            //if there's a right brace, the rule is finished so don't do anything
+                        } else {
+                            //otherwise, rethrow the error because it wasn't handled properly
+                            throw ex;
+                        }
+
+                    } else {
+                        //not a syntax error, rethrow it
+                        throw ex;
+                    }
+
+                    //trigger parser to continue
+                    return true;
+                }
+
+                //if it got here, all selectors parsed
+                if (selectors){
+
+                    this.fire({
+                        type:       "startrule",
+                        selectors:  selectors,
+                        line:       selectors[0].line,
+                        col:        selectors[0].col
+                    });
+
+                    this._readDeclarations(true);
+
+                    this.fire({
+                        type:       "endrule",
+                        selectors:  selectors,
+                        line:       selectors[0].line,
+                        col:        selectors[0].col
+                    });
+
+                }
+
+                return selectors;
+
+            },
+
+            //CSS3 Selectors
+            _selectors_group: function(){
+
+                /*
+                 * selectors_group
+                 *   : selector [ COMMA S* selector ]*
+                 *   ;
+                 */
+                var tokenStream = this._tokenStream,
+                    selectors   = [],
+                    selector;
+
+                selector = this._selector();
+                if (selector !== null){
+
+                    selectors.push(selector);
+                    while(tokenStream.match(Tokens.COMMA)){
+                        this._readWhitespace();
+                        selector = this._selector();
+                        if (selector !== null){
+                            selectors.push(selector);
+                        } else {
+                            this._unexpectedToken(tokenStream.LT(1));
+                        }
+                    }
+                }
+
+                return selectors.length ? selectors : null;
+            },
+
+            //CSS3 Selectors
+            _selector: function(){
+                /*
+                 * selector
+                 *   : simple_selector_sequence [ combinator simple_selector_sequence ]*
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    selector    = [],
+                    nextSelector = null,
+                    combinator  = null,
+                    ws          = null;
+
+                //if there's no simple selector, then there's no selector
+                nextSelector = this._simple_selector_sequence();
+                if (nextSelector === null){
+                    return null;
+                }
+
+                selector.push(nextSelector);
+
+                do {
+
+                    //look for a combinator
+                    combinator = this._combinator();
+
+                    if (combinator !== null){
+                        selector.push(combinator);
+                        nextSelector = this._simple_selector_sequence();
+
+                        //there must be a next selector
+                        if (nextSelector === null){
+                            this._unexpectedToken(tokenStream.LT(1));
+                        } else {
+
+                            //nextSelector is an instance of SelectorPart
+                            selector.push(nextSelector);
+                        }
+                    } else {
+
+                        //if there's not whitespace, we're done
+                        if (this._readWhitespace()){
+
+                            //add whitespace separator
+                            ws = new Combinator(tokenStream.token().value, tokenStream.token().startLine, tokenStream.token().startCol);
+
+                            //combinator is not required
+                            combinator = this._combinator();
+
+                            //selector is required if there's a combinator
+                            nextSelector = this._simple_selector_sequence();
+                            if (nextSelector === null){
+                                if (combinator !== null){
+                                    this._unexpectedToken(tokenStream.LT(1));
+                                }
+                            } else {
+
+                                if (combinator !== null){
+                                    selector.push(combinator);
+                                } else {
+                                    selector.push(ws);
+                                }
+
+                                selector.push(nextSelector);
+                            }
+                        } else {
+                            break;
+                        }
+
+                    }
+                } while(true);
+
+                return new Selector(selector, selector[0].line, selector[0].col);
+            },
+
+            //CSS3 Selectors
+            _simple_selector_sequence: function(){
+                /*
+                 * simple_selector_sequence
+                 *   : [ type_selector | universal ]
+                 *     [ HASH | class | attrib | pseudo | negation ]*
+                 *   | [ HASH | class | attrib | pseudo | negation ]+
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+
+                    //parts of a simple selector
+                    elementName = null,
+                    modifiers   = [],
+
+                    //complete selector text
+                    selectorText= "",
+
+                    //the different parts after the element name to search for
+                    components  = [
+                        //HASH
+                        function(){
+                            return tokenStream.match(Tokens.HASH) ?
+                                    new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) :
+                                    null;
+                        },
+                        this._class,
+                        this._attrib,
+                        this._pseudo,
+                        this._negation
+                    ],
+                    i           = 0,
+                    len         = components.length,
+                    component   = null,
+                    line,
+                    col;
+
+
+                //get starting line and column for the selector
+                line = tokenStream.LT(1).startLine;
+                col = tokenStream.LT(1).startCol;
+
+                elementName = this._type_selector();
+                if (!elementName){
+                    elementName = this._universal();
+                }
+
+                if (elementName !== null){
+                    selectorText += elementName;
+                }
+
+                while(true){
+
+                    //whitespace means we're done
+                    if (tokenStream.peek() === Tokens.S){
+                        break;
+                    }
+
+                    //check for each component
+                    while(i < len && component === null){
+                        component = components[i++].call(this);
+                    }
+
+                    if (component === null){
+
+                        //we don't have a selector
+                        if (selectorText === ""){
+                            return null;
+                        } else {
+                            break;
+                        }
+                    } else {
+                        i = 0;
+                        modifiers.push(component);
+                        selectorText += component.toString();
+                        component = null;
+                    }
+                }
+
+
+                return selectorText !== "" ?
+                        new SelectorPart(elementName, modifiers, selectorText, line, col) :
+                        null;
+            },
+
+            //CSS3 Selectors
+            _type_selector: function(){
+                /*
+                 * type_selector
+                 *   : [ namespace_prefix ]? element_name
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    ns          = this._namespace_prefix(),
+                    elementName = this._element_name();
+
+                if (!elementName){
+                    /*
+                     * Need to back out the namespace that was read due to both
+                     * type_selector and universal reading namespace_prefix
+                     * first. Kind of hacky, but only way I can figure out
+                     * right now how to not change the grammar.
+                     */
+                    if (ns){
+                        tokenStream.unget();
+                        if (ns.length > 1){
+                            tokenStream.unget();
+                        }
+                    }
+
+                    return null;
+                } else {
+                    if (ns){
+                        elementName.text = ns + elementName.text;
+                        elementName.col -= ns.length;
+                    }
+                    return elementName;
+                }
+            },
+
+            //CSS3 Selectors
+            _class: function(){
+                /*
+                 * class
+                 *   : '.' IDENT
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    token;
+
+                if (tokenStream.match(Tokens.DOT)){
+                    tokenStream.mustMatch(Tokens.IDENT);
+                    token = tokenStream.token();
+                    return new SelectorSubPart("." + token.value, "class", token.startLine, token.startCol - 1);
+                } else {
+                    return null;
+                }
+
+            },
+
+            //CSS3 Selectors
+            _element_name: function(){
+                /*
+                 * element_name
+                 *   : IDENT
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    token;
+
+                if (tokenStream.match(Tokens.IDENT)){
+                    token = tokenStream.token();
+                    return new SelectorSubPart(token.value, "elementName", token.startLine, token.startCol);
+
+                } else {
+                    return null;
+                }
+            },
+
+            //CSS3 Selectors
+            _namespace_prefix: function(){
+                /*
+                 * namespace_prefix
+                 *   : [ IDENT | '*' ]? '|'
+                 *   ;
+                 */
+                var tokenStream = this._tokenStream,
+                    value       = "";
+
+                //verify that this is a namespace prefix
+                if (tokenStream.LA(1) === Tokens.PIPE || tokenStream.LA(2) === Tokens.PIPE){
+
+                    if(tokenStream.match([Tokens.IDENT, Tokens.STAR])){
+                        value += tokenStream.token().value;
+                    }
+
+                    tokenStream.mustMatch(Tokens.PIPE);
+                    value += "|";
+
+                }
+
+                return value.length ? value : null;
+            },
+
+            //CSS3 Selectors
+            _universal: function(){
+                /*
+                 * universal
+                 *   : [ namespace_prefix ]? '*'
+                 *   ;
+                 */
+                var tokenStream = this._tokenStream,
+                    value       = "",
+                    ns;
+
+                ns = this._namespace_prefix();
+                if(ns){
+                    value += ns;
+                }
+
+                if(tokenStream.match(Tokens.STAR)){
+                    value += "*";
+                }
+
+                return value.length ? value : null;
+
+           },
+
+            //CSS3 Selectors
+            _attrib: function(){
+                /*
+                 * attrib
+                 *   : '[' S* [ namespace_prefix ]? IDENT S*
+                 *         [ [ PREFIXMATCH |
+                 *             SUFFIXMATCH |
+                 *             SUBSTRINGMATCH |
+                 *             '=' |
+                 *             INCLUDES |
+                 *             DASHMATCH ] S* [ IDENT | STRING ] S*
+                 *         ]? ']'
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    value       = null,
+                    ns,
+                    token;
+
+                if (tokenStream.match(Tokens.LBRACKET)){
+                    token = tokenStream.token();
+                    value = token.value;
+                    value += this._readWhitespace();
+
+                    ns = this._namespace_prefix();
+
+                    if (ns){
+                        value += ns;
+                    }
+
+                    tokenStream.mustMatch(Tokens.IDENT);
+                    value += tokenStream.token().value;
+                    value += this._readWhitespace();
+
+                    if(tokenStream.match([Tokens.PREFIXMATCH, Tokens.SUFFIXMATCH, Tokens.SUBSTRINGMATCH,
+                            Tokens.EQUALS, Tokens.INCLUDES, Tokens.DASHMATCH])){
+
+                        value += tokenStream.token().value;
+                        value += this._readWhitespace();
+
+                        tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]);
+                        value += tokenStream.token().value;
+                        value += this._readWhitespace();
+                    }
+
+                    tokenStream.mustMatch(Tokens.RBRACKET);
+
+                    return new SelectorSubPart(value + "]", "attribute", token.startLine, token.startCol);
+                } else {
+                    return null;
+                }
+            },
+
+            //CSS3 Selectors
+            _pseudo: function(){
+
+                /*
+                 * pseudo
+                 *   : ':' ':'? [ IDENT | functional_pseudo ]
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    pseudo      = null,
+                    colons      = ":",
+                    line,
+                    col;
+
+                if (tokenStream.match(Tokens.COLON)){
+
+                    if (tokenStream.match(Tokens.COLON)){
+                        colons += ":";
+                    }
+
+                    if (tokenStream.match(Tokens.IDENT)){
+                        pseudo = tokenStream.token().value;
+                        line = tokenStream.token().startLine;
+                        col = tokenStream.token().startCol - colons.length;
+                    } else if (tokenStream.peek() === Tokens.FUNCTION){
+                        line = tokenStream.LT(1).startLine;
+                        col = tokenStream.LT(1).startCol - colons.length;
+                        pseudo = this._functional_pseudo();
+                    }
+
+                    if (pseudo){
+                        pseudo = new SelectorSubPart(colons + pseudo, "pseudo", line, col);
+                    }
+                }
+
+                return pseudo;
+            },
+
+            //CSS3 Selectors
+            _functional_pseudo: function(){
+                /*
+                 * functional_pseudo
+                 *   : FUNCTION S* expression ')'
+                 *   ;
+                */
+
+                var tokenStream = this._tokenStream,
+                    value = null;
+
+                if(tokenStream.match(Tokens.FUNCTION)){
+                    value = tokenStream.token().value;
+                    value += this._readWhitespace();
+                    value += this._expression();
+                    tokenStream.mustMatch(Tokens.RPAREN);
+                    value += ")";
+                }
+
+                return value;
+            },
+
+            //CSS3 Selectors
+            _expression: function(){
+                /*
+                 * expression
+                 *   : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    value       = "";
+
+                while(tokenStream.match([Tokens.PLUS, Tokens.MINUS, Tokens.DIMENSION,
+                        Tokens.NUMBER, Tokens.STRING, Tokens.IDENT, Tokens.LENGTH,
+                        Tokens.FREQ, Tokens.ANGLE, Tokens.TIME,
+                        Tokens.RESOLUTION, Tokens.SLASH])){
+
+                    value += tokenStream.token().value;
+                    value += this._readWhitespace();
+                }
+
+                return value.length ? value : null;
+
+            },
+
+            //CSS3 Selectors
+            _negation: function(){
+                /*
+                 * negation
+                 *   : NOT S* negation_arg S* ')'
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    line,
+                    col,
+                    value       = "",
+                    arg,
+                    subpart     = null;
+
+                if (tokenStream.match(Tokens.NOT)){
+                    value = tokenStream.token().value;
+                    line = tokenStream.token().startLine;
+                    col = tokenStream.token().startCol;
+                    value += this._readWhitespace();
+                    arg = this._negation_arg();
+                    value += arg;
+                    value += this._readWhitespace();
+                    tokenStream.match(Tokens.RPAREN);
+                    value += tokenStream.token().value;
+
+                    subpart = new SelectorSubPart(value, "not", line, col);
+                    subpart.args.push(arg);
+                }
+
+                return subpart;
+            },
+
+            //CSS3 Selectors
+            _negation_arg: function(){
+                /*
+                 * negation_arg
+                 *   : type_selector | universal | HASH | class | attrib | pseudo
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    args        = [
+                        this._type_selector,
+                        this._universal,
+                        function(){
+                            return tokenStream.match(Tokens.HASH) ?
+                                    new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) :
+                                    null;
+                        },
+                        this._class,
+                        this._attrib,
+                        this._pseudo
+                    ],
+                    arg         = null,
+                    i           = 0,
+                    len         = args.length,
+                    line,
+                    col,
+                    part;
+
+                line = tokenStream.LT(1).startLine;
+                col = tokenStream.LT(1).startCol;
+
+                while(i < len && arg === null){
+
+                    arg = args[i].call(this);
+                    i++;
+                }
+
+                //must be a negation arg
+                if (arg === null){
+                    this._unexpectedToken(tokenStream.LT(1));
+                }
+
+                //it's an element name
+                if (arg.type === "elementName"){
+                    part = new SelectorPart(arg, [], arg.toString(), line, col);
+                } else {
+                    part = new SelectorPart(null, [arg], arg.toString(), line, col);
+                }
+
+                return part;
+            },
+
+            _declaration: function(){
+
+                /*
+                 * declaration
+                 *   : property ':' S* expr prio?
+                 *   | /( empty )/
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    property    = null,
+                    expr        = null,
+                    prio        = null,
+                    invalid     = null,
+                    propertyName= "";
+
+                property = this._property();
+                if (property !== null){
+
+                    tokenStream.mustMatch(Tokens.COLON);
+                    this._readWhitespace();
+
+                    expr = this._expr();
+
+                    //if there's no parts for the value, it's an error
+                    if (!expr || expr.length === 0){
+                        this._unexpectedToken(tokenStream.LT(1));
+                    }
+
+                    prio = this._prio();
+
+                    /*
+                     * If hacks should be allowed, then only check the root
+                     * property. If hacks should not be allowed, treat
+                     * _property or *property as invalid properties.
+                     */
+                    propertyName = property.toString();
+                    if (this.options.starHack && property.hack === "*" ||
+                            this.options.underscoreHack && property.hack === "_") {
+
+                        propertyName = property.text;
+                    }
+
+                    try {
+                        this._validateProperty(propertyName, expr);
+                    } catch (ex) {
+                        invalid = ex;
+                    }
+
+                    this.fire({
+                        type:       "property",
+                        property:   property,
+                        value:      expr,
+                        important:  prio,
+                        line:       property.line,
+                        col:        property.col,
+                        invalid:    invalid
+                    });
+
+                    return true;
+                } else {
+                    return false;
+                }
+            },
+
+            _prio: function(){
+                /*
+                 * prio
+                 *   : IMPORTANT_SYM S*
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    result      = tokenStream.match(Tokens.IMPORTANT_SYM);
+
+                this._readWhitespace();
+                return result;
+            },
+
+            _expr: function(inFunction){
+                /*
+                 * expr
+                 *   : term [ operator term ]*
+                 *   ;
+                 */
+
+                var values      = [],
+					//valueParts	= [],
+                    value       = null,
+                    operator    = null;
+
+                value = this._term(inFunction);
+                if (value !== null){
+
+                    values.push(value);
+
+                    do {
+                        operator = this._operator(inFunction);
+
+                        //if there's an operator, keep building up the value parts
+                        if (operator){
+                            values.push(operator);
+                        } /*else {
+                            //if there's not an operator, you have a full value
+							values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col));
+							valueParts = [];
+						}*/
+
+                        value = this._term(inFunction);
+
+                        if (value === null){
+                            break;
+                        } else {
+                            values.push(value);
+                        }
+                    } while(true);
+                }
+
+				//cleanup
+                /*if (valueParts.length){
+                    values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col));
+                }*/
+
+                return values.length > 0 ? new PropertyValue(values, values[0].line, values[0].col) : null;
+            },
+
+            _term: function(inFunction){
+
+                /*
+                 * term
+                 *   : unary_operator?
+                 *     [ NUMBER S* | PERCENTAGE S* | LENGTH S* | ANGLE S* |
+                 *       TIME S* | FREQ S* | function | ie_function ]
+                 *   | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    unary       = null,
+                    value       = null,
+                    endChar     = null,
+                    token,
+                    line,
+                    col;
+
+                //returns the operator or null
+                unary = this._unary_operator();
+                if (unary !== null){
+                    line = tokenStream.token().startLine;
+                    col = tokenStream.token().startCol;
+                }
+
+                //exception for IE filters
+                if (tokenStream.peek() === Tokens.IE_FUNCTION && this.options.ieFilters){
+
+                    value = this._ie_function();
+                    if (unary === null){
+                        line = tokenStream.token().startLine;
+                        col = tokenStream.token().startCol;
+                    }
+
+                //see if it's a simple block
+                } else if (inFunction && tokenStream.match([Tokens.LPAREN, Tokens.LBRACE, Tokens.LBRACKET])){
+
+                    token = tokenStream.token();
+                    endChar = token.endChar;
+                    value = token.value + this._expr(inFunction).text;
+                    if (unary === null){
+                        line = tokenStream.token().startLine;
+                        col = tokenStream.token().startCol;
+                    }
+                    tokenStream.mustMatch(Tokens.type(endChar));
+                    value += endChar;
+                    this._readWhitespace();
+
+                //see if there's a simple match
+                } else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH,
+                        Tokens.ANGLE, Tokens.TIME,
+                        Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])){
+
+                    value = tokenStream.token().value;
+                    if (unary === null){
+                        line = tokenStream.token().startLine;
+                        col = tokenStream.token().startCol;
+                    }
+                    this._readWhitespace();
+                } else {
+
+                    //see if it's a color
+                    token = this._hexcolor();
+                    if (token === null){
+
+                        //if there's no unary, get the start of the next token for line/col info
+                        if (unary === null){
+                            line = tokenStream.LT(1).startLine;
+                            col = tokenStream.LT(1).startCol;
+                        }
+
+                        //has to be a function
+                        if (value === null){
+
+                            /*
+                             * This checks for alpha(opacity=0) style of IE
+                             * functions. IE_FUNCTION only presents progid: style.
+                             */
+                            if (tokenStream.LA(3) === Tokens.EQUALS && this.options.ieFilters){
+                                value = this._ie_function();
+                            } else {
+                                value = this._function();
+                            }
+                        }
+
+                        /*if (value === null){
+                            return null;
+                            //throw new Error("Expected identifier at line " + tokenStream.token().startLine + ", character " +  tokenStream.token().startCol + ".");
+                        }*/
+
+                    } else {
+                        value = token.value;
+                        if (unary === null){
+                            line = token.startLine;
+                            col = token.startCol;
+                        }
+                    }
+
+                }
+
+                return value !== null ?
+                        new PropertyValuePart(unary !== null ? unary + value : value, line, col) :
+                        null;
+
+            },
+
+            _function: function(){
+
+                /*
+                 * function
+                 *   : FUNCTION S* expr ')' S*
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    functionText = null,
+                    expr        = null,
+                    lt;
+
+                if (tokenStream.match(Tokens.FUNCTION)){
+                    functionText = tokenStream.token().value;
+                    this._readWhitespace();
+                    expr = this._expr(true);
+                    functionText += expr;
+
+                    //START: Horrible hack in case it's an IE filter
+                    if (this.options.ieFilters && tokenStream.peek() === Tokens.EQUALS){
+                        do {
+
+                            if (this._readWhitespace()){
+                                functionText += tokenStream.token().value;
+                            }
+
+                            //might be second time in the loop
+                            if (tokenStream.LA(0) === Tokens.COMMA){
+                                functionText += tokenStream.token().value;
+                            }
+
+                            tokenStream.match(Tokens.IDENT);
+                            functionText += tokenStream.token().value;
+
+                            tokenStream.match(Tokens.EQUALS);
+                            functionText += tokenStream.token().value;
+
+                            //functionText += this._term();
+                            lt = tokenStream.peek();
+                            while(lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN){
+                                tokenStream.get();
+                                functionText += tokenStream.token().value;
+                                lt = tokenStream.peek();
+                            }
+                        } while(tokenStream.match([Tokens.COMMA, Tokens.S]));
+                    }
+
+                    //END: Horrible Hack
+
+                    tokenStream.match(Tokens.RPAREN);
+                    functionText += ")";
+                    this._readWhitespace();
+                }
+
+                return functionText;
+            },
+
+            _ie_function: function(){
+
+                /* (My own extension)
+                 * ie_function
+                 *   : IE_FUNCTION S* IDENT '=' term [S* ','? IDENT '=' term]+ ')' S*
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    functionText = null,
+                    lt;
+
+                //IE function can begin like a regular function, too
+                if (tokenStream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])){
+                    functionText = tokenStream.token().value;
+
+                    do {
+
+                        if (this._readWhitespace()){
+                            functionText += tokenStream.token().value;
+                        }
+
+                        //might be second time in the loop
+                        if (tokenStream.LA(0) === Tokens.COMMA){
+                            functionText += tokenStream.token().value;
+                        }
+
+                        tokenStream.match(Tokens.IDENT);
+                        functionText += tokenStream.token().value;
+
+                        tokenStream.match(Tokens.EQUALS);
+                        functionText += tokenStream.token().value;
+
+                        //functionText += this._term();
+                        lt = tokenStream.peek();
+                        while(lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN){
+                            tokenStream.get();
+                            functionText += tokenStream.token().value;
+                            lt = tokenStream.peek();
+                        }
+                    } while(tokenStream.match([Tokens.COMMA, Tokens.S]));
+
+                    tokenStream.match(Tokens.RPAREN);
+                    functionText += ")";
+                    this._readWhitespace();
+                }
+
+                return functionText;
+            },
+
+            _hexcolor: function(){
+                /*
+                 * There is a constraint on the color that it must
+                 * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
+                 * after the "#"; e.g., "#000" is OK, but "#abcd" is not.
+                 *
+                 * hexcolor
+                 *   : HASH S*
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    token = null,
+                    color;
+
+                if(tokenStream.match(Tokens.HASH)){
+
+                    //need to do some validation here
+
+                    token = tokenStream.token();
+                    color = token.value;
+                    if (!/#[a-f0-9]{3,6}/i.test(color)){
+                        throw new SyntaxError("Expected a hex color but found '" + color + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
+                    }
+                    this._readWhitespace();
+                }
+
+                return token;
+            },
+
+            //-----------------------------------------------------------------
+            // Animations methods
+            //-----------------------------------------------------------------
+
+            _keyframes: function(){
+
+                /*
+                 * keyframes:
+                 *   : KEYFRAMES_SYM S* keyframe_name S* '{' S* keyframe_rule* '}' {
+                 *   ;
+                 */
+                var tokenStream = this._tokenStream,
+                    token,
+                    tt,
+                    name,
+                    prefix = "";
+
+                tokenStream.mustMatch(Tokens.KEYFRAMES_SYM);
+                token = tokenStream.token();
+                if (/^@\-([^\-]+)\-/.test(token.value)) {
+                    prefix = RegExp.$1;
+                }
+
+                this._readWhitespace();
+                name = this._keyframe_name();
+
+                this._readWhitespace();
+                tokenStream.mustMatch(Tokens.LBRACE);
+
+                this.fire({
+                    type:   "startkeyframes",
+                    name:   name,
+                    prefix: prefix,
+                    line:   token.startLine,
+                    col:    token.startCol
+                });
+
+                this._readWhitespace();
+                tt = tokenStream.peek();
+
+                //check for key
+                while(tt === Tokens.IDENT || tt === Tokens.PERCENTAGE) {
+                    this._keyframe_rule();
+                    this._readWhitespace();
+                    tt = tokenStream.peek();
+                }
+
+                this.fire({
+                    type:   "endkeyframes",
+                    name:   name,
+                    prefix: prefix,
+                    line:   token.startLine,
+                    col:    token.startCol
+                });
+
+                this._readWhitespace();
+                tokenStream.mustMatch(Tokens.RBRACE);
+
+            },
+
+            _keyframe_name: function(){
+
+                /*
+                 * keyframe_name:
+                 *   : IDENT
+                 *   | STRING
+                 *   ;
+                 */
+                var tokenStream = this._tokenStream;
+
+                tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]);
+                return SyntaxUnit.fromToken(tokenStream.token());
+            },
+
+            _keyframe_rule: function(){
+
+                /*
+                 * keyframe_rule:
+                 *   : key_list S*
+                 *     '{' S* declaration [ ';' S* declaration ]* '}' S*
+                 *   ;
+                 */
+                var keyList = this._key_list();
+
+                this.fire({
+                    type:   "startkeyframerule",
+                    keys:   keyList,
+                    line:   keyList[0].line,
+                    col:    keyList[0].col
+                });
+
+                this._readDeclarations(true);
+
+                this.fire({
+                    type:   "endkeyframerule",
+                    keys:   keyList,
+                    line:   keyList[0].line,
+                    col:    keyList[0].col
+                });
+
+            },
+
+            _key_list: function(){
+
+                /*
+                 * key_list:
+                 *   : key [ S* ',' S* key]*
+                 *   ;
+                 */
+                var tokenStream = this._tokenStream,
+                    keyList = [];
+
+                //must be least one key
+                keyList.push(this._key());
+
+                this._readWhitespace();
+
+                while(tokenStream.match(Tokens.COMMA)){
+                    this._readWhitespace();
+                    keyList.push(this._key());
+                    this._readWhitespace();
+                }
+
+                return keyList;
+            },
+
+            _key: function(){
+                /*
+                 * There is a restriction that IDENT can be only "from" or "to".
+                 *
+                 * key
+                 *   : PERCENTAGE
+                 *   | IDENT
+                 *   ;
+                 */
+
+                var tokenStream = this._tokenStream,
+                    token;
+
+                if (tokenStream.match(Tokens.PERCENTAGE)){
+                    return SyntaxUnit.fromToken(tokenStream.token());
+                } else if (tokenStream.match(Tokens.IDENT)){
+                    token = tokenStream.token();
+
+                    if (/from|to/i.test(token.value)){
+                        return SyntaxUnit.fromToken(token);
+                    }
+
+                    tokenStream.unget();
+                }
+
+                //if it gets here, there wasn't a valid token, so time to explode
+                this._unexpectedToken(tokenStream.LT(1));
+            },
+
+            //-----------------------------------------------------------------
+            // Helper methods
+            //-----------------------------------------------------------------
+
+            /**
+             * Not part of CSS grammar, but useful for skipping over
+             * combination of white space and HTML-style comments.
+             * @return {void}
+             * @method _skipCruft
+             * @private
+             */
+            _skipCruft: function(){
+                while(this._tokenStream.match([Tokens.S, Tokens.CDO, Tokens.CDC])){
+                    //noop
+                }
+            },
+
+            /**
+             * Not part of CSS grammar, but this pattern occurs frequently
+             * in the official CSS grammar. Split out here to eliminate
+             * duplicate code.
+             * @param {Boolean} checkStart Indicates if the rule should check
+             *      for the left brace at the beginning.
+             * @param {Boolean} readMargins Indicates if the rule should check
+             *      for margin patterns.
+             * @return {void}
+             * @method _readDeclarations
+             * @private
+             */
+            _readDeclarations: function(checkStart, readMargins){
+                /*
+                 * Reads the pattern
+                 * S* '{' S* declaration [ ';' S* declaration ]* '}' S*
+                 * or
+                 * S* '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S*
+                 * Note that this is how it is described in CSS3 Paged Media, but is actually incorrect.
+                 * A semicolon is only necessary following a declaration if there's another declaration
+                 * or margin afterwards.
+                 */
+                var tokenStream = this._tokenStream,
+                    tt;
+
+
+                this._readWhitespace();
+
+                if (checkStart){
+                    tokenStream.mustMatch(Tokens.LBRACE);
+                }
+
+                this._readWhitespace();
+
+                try {
+
+                    while(true){
+                        if (tokenStream.match(Tokens.SEMICOLON) || (readMargins && this._margin())){
+                            //noop
+                        } else if (this._declaration()){
+                            if (!tokenStream.match(Tokens.SEMICOLON)){
+                                break;
+                            }
+                        } else {
+                            break;
+                        }
+
+
+                        //if ((!this._margin() && !this._declaration()) || !tokenStream.match(Tokens.SEMICOLON)){
+                        //    break;
+                        //}
+                        this._readWhitespace();
+                    }
+
+                    tokenStream.mustMatch(Tokens.RBRACE);
+                    this._readWhitespace();
+
+                } catch (ex) {
+                    if (ex instanceof SyntaxError && !this.options.strict){
+
+                        //fire error event
+                        this.fire({
+                            type:       "error",
+                            error:      ex,
+                            message:    ex.message,
+                            line:       ex.line,
+                            col:        ex.col
+                        });
+
+                        //see if there's another declaration
+                        tt = tokenStream.advance([Tokens.SEMICOLON, Tokens.RBRACE]);
+                        if (tt === Tokens.SEMICOLON){
+                            //if there's a semicolon, then there might be another declaration
+                            this._readDeclarations(false, readMargins);
+                        } else if (tt !== Tokens.RBRACE){
+                            //if there's a right brace, the rule is finished so don't do anything
+                            //otherwise, rethrow the error because it wasn't handled properly
+                            throw ex;
+                        }
+
+                    } else {
+                        //not a syntax error, rethrow it
+                        throw ex;
+                    }
+                }
+
+            },
+
+            /**
+             * In some cases, you can end up with two white space tokens in a
+             * row. Instead of making a change in every function that looks for
+             * white space, this function is used to match as much white space
+             * as necessary.
+             * @method _readWhitespace
+             * @return {String} The white space if found, empty string if not.
+             * @private
+             */
+            _readWhitespace: function(){
+
+                var tokenStream = this._tokenStream,
+                    ws = "";
+
+                while(tokenStream.match(Tokens.S)){
+                    ws += tokenStream.token().value;
+                }
+
+                return ws;
+            },
+
+
+            /**
+             * Throws an error when an unexpected token is found.
+             * @param {Object} token The token that was found.
+             * @method _unexpectedToken
+             * @return {void}
+             * @private
+             */
+            _unexpectedToken: function(token){
+                throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
+            },
+
+            /**
+             * Helper method used for parsing subparts of a style sheet.
+             * @return {void}
+             * @method _verifyEnd
+             * @private
+             */
+            _verifyEnd: function(){
+                if (this._tokenStream.LA(1) !== Tokens.EOF){
+                    this._unexpectedToken(this._tokenStream.LT(1));
+                }
+            },
+
+            //-----------------------------------------------------------------
+            // Validation methods
+            //-----------------------------------------------------------------
+            _validateProperty: function(property, value){
+                Validation.validate(property, value);
+            },
+
+            //-----------------------------------------------------------------
+            // Parsing methods
+            //-----------------------------------------------------------------
+
+            parse: function(input){
+                this._tokenStream = new TokenStream(input, Tokens);
+                this._stylesheet();
+            },
+
+            parseStyleSheet: function(input){
+                //just passthrough
+                return this.parse(input);
+            },
+
+            parseMediaQuery: function(input){
+                this._tokenStream = new TokenStream(input, Tokens);
+                var result = this._media_query();
+
+                //if there's anything more, then it's an invalid selector
+                this._verifyEnd();
+
+                //otherwise return result
+                return result;
+            },
+
+            /**
+             * Parses a property value (everything after the semicolon).
+             * @return {parserlib.css.PropertyValue} The property value.
+             * @throws parserlib.util.SyntaxError If an unexpected token is found.
+             * @method parserPropertyValue
+             */
+            parsePropertyValue: function(input){
+
+                this._tokenStream = new TokenStream(input, Tokens);
+                this._readWhitespace();
+
+                var result = this._expr();
+
+                //okay to have a trailing white space
+                this._readWhitespace();
+
+                //if there's anything more, then it's an invalid selector
+                this._verifyEnd();
+
+                //otherwise return result
+                return result;
+            },
+
+            /**
+             * Parses a complete CSS rule, including selectors and
+             * properties.
+             * @param {String} input The text to parser.
+             * @return {Boolean} True if the parse completed successfully, false if not.
+             * @method parseRule
+             */
+            parseRule: function(input){
+                this._tokenStream = new TokenStream(input, Tokens);
+
+                //skip any leading white space
+                this._readWhitespace();
+
+                var result = this._ruleset();
+
+                //skip any trailing white space
+                this._readWhitespace();
+
+                //if there's anything more, then it's an invalid selector
+                this._verifyEnd();
+
+                //otherwise return result
+                return result;
+            },
+
+            /**
+             * Parses a single CSS selector (no comma)
+             * @param {String} input The text to parse as a CSS selector.
+             * @return {Selector} An object representing the selector.
+             * @throws parserlib.util.SyntaxError If an unexpected token is found.
+             * @method parseSelector
+             */
+            parseSelector: function(input){
+
+                this._tokenStream = new TokenStream(input, Tokens);
+
+                //skip any leading white space
+                this._readWhitespace();
+
+                var result = this._selector();
+
+                //skip any trailing white space
+                this._readWhitespace();
+
+                //if there's anything more, then it's an invalid selector
+                this._verifyEnd();
+
+                //otherwise return result
+                return result;
+            },
+
+            /**
+             * Parses an HTML style attribute: a set of CSS declarations
+             * separated by semicolons.
+             * @param {String} input The text to parse as a style attribute
+             * @return {void}
+             * @method parseStyleAttribute
+             */
+            parseStyleAttribute: function(input){
+                input += "}"; // for error recovery in _readDeclarations()
+                this._tokenStream = new TokenStream(input, Tokens);
+                this._readDeclarations();
+            }
+        };
+
+    //copy over onto prototype
+    for (prop in additions){
+        if (Object.prototype.hasOwnProperty.call(additions, prop)){
+            proto[prop] = additions[prop];
+        }
+    }
+
+    return proto;
+}();
+
+
+/*
+nth
+  : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? |
+         ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S*
+  ;
+*/
+var Properties = {
+    __proto__: null,
+
+    //A
+    "align-items"                   : "flex-start | flex-end | center | baseline | stretch",
+    "align-content"                 : "flex-start | flex-end | center | space-between | space-around | stretch",
+    "align-self"                    : "auto | flex-start | flex-end | center | baseline | stretch",
+    "-webkit-align-items"           : "flex-start | flex-end | center | baseline | stretch",
+    "-webkit-align-content"         : "flex-start | flex-end | center | space-between | space-around | stretch",
+    "-webkit-align-self"            : "auto | flex-start | flex-end | center | baseline | stretch",
+    "alignment-adjust"              : "auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | <percentage> | <length>",
+    "alignment-baseline"            : "baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical",
+    "animation"                     : 1,
+    "animation-delay"               : { multi: "<time>", comma: true },
+    "animation-direction"           : { multi: "normal | alternate", comma: true },
+    "animation-duration"            : { multi: "<time>", comma: true },
+    "animation-fill-mode"           : { multi: "none | forwards | backwards | both", comma: true },
+    "animation-iteration-count"     : { multi: "<number> | infinite", comma: true },
+    "animation-name"                : { multi: "none | <ident>", comma: true },
+    "animation-play-state"          : { multi: "running | paused", comma: true },
+    "animation-timing-function"     : 1,
+
+    //vendor prefixed
+    "-moz-animation-delay"               : { multi: "<time>", comma: true },
+    "-moz-animation-direction"           : { multi: "normal | alternate", comma: true },
+    "-moz-animation-duration"            : { multi: "<time>", comma: true },
+    "-moz-animation-iteration-count"     : { multi: "<number> | infinite", comma: true },
+    "-moz-animation-name"                : { multi: "none | <ident>", comma: true },
+    "-moz-animation-play-state"          : { multi: "running | paused", comma: true },
+
+    "-ms-animation-delay"               : { multi: "<time>", comma: true },
+    "-ms-animation-direction"           : { multi: "normal | alternate", comma: true },
+    "-ms-animation-duration"            : { multi: "<time>", comma: true },
+    "-ms-animation-iteration-count"     : { multi: "<number> | infinite", comma: true },
+    "-ms-animation-name"                : { multi: "none | <ident>", comma: true },
+    "-ms-animation-play-state"          : { multi: "running | paused", comma: true },
+
+    "-webkit-animation-delay"               : { multi: "<time>", comma: true },
+    "-webkit-animation-direction"           : { multi: "normal | alternate", comma: true },
+    "-webkit-animation-duration"            : { multi: "<time>", comma: true },
+    "-webkit-animation-fill-mode"           : { multi: "none | forwards | backwards | both", comma: true },
+    "-webkit-animation-iteration-count"     : { multi: "<number> | infinite", comma: true },
+    "-webkit-animation-name"                : { multi: "none | <ident>", comma: true },
+    "-webkit-animation-play-state"          : { multi: "running | paused", comma: true },
+
+    "-o-animation-delay"               : { multi: "<time>", comma: true },
+    "-o-animation-direction"           : { multi: "normal | alternate", comma: true },
+    "-o-animation-duration"            : { multi: "<time>", comma: true },
+    "-o-animation-iteration-count"     : { multi: "<number> | infinite", comma: true },
+    "-o-animation-name"                : { multi: "none | <ident>", comma: true },
+    "-o-animation-play-state"          : { multi: "running | paused", comma: true },
+
+    "appearance"                    : "icon | window | desktop | workspace | document | tooltip | dialog | button | push-button | hyperlink | radio | radio-button | checkbox | menu-item | tab | menu | menubar | pull-down-menu | pop-up-menu | list-menu | radio-group | checkbox-group | outline-tree | range | field | combo-box | signature | password | normal | none | inherit",
+    "azimuth"                       : function (expression) {
+        var simple      = "<angle> | leftwards | rightwards | inherit",
+            direction   = "left-side | far-left | left | center-left | center | center-right | right | far-right | right-side",
+            behind      = false,
+            valid       = false,
+            part;
+
+        if (!ValidationTypes.isAny(expression, simple)) {
+            if (ValidationTypes.isAny(expression, "behind")) {
+                behind = true;
+                valid = true;
+            }
+
+            if (ValidationTypes.isAny(expression, direction)) {
+                valid = true;
+                if (!behind) {
+                    ValidationTypes.isAny(expression, "behind");
+                }
+            }
+        }
+
+        if (expression.hasNext()) {
+            part = expression.next();
+            if (valid) {
+                throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
+            } else {
+                throw new ValidationError("Expected (<'azimuth'>) but found '" + part + "'.", part.line, part.col);
+            }
+        }
+    },
+
+    //B
+    "backface-visibility"           : "visible | hidden",
+    "background"                    : 1,
+    "background-attachment"         : { multi: "<attachment>", comma: true },
+    "background-clip"               : { multi: "<box>", comma: true },
+    "background-color"              : "<color> | inherit",
+    "background-image"              : { multi: "<bg-image>", comma: true },
+    "background-origin"             : { multi: "<box>", comma: true },
+    "background-position"           : { multi: "<bg-position>", comma: true },
+    "background-repeat"             : { multi: "<repeat-style>" },
+    "background-size"               : { multi: "<bg-size>", comma: true },
+    "baseline-shift"                : "baseline | sub | super | <percentage> | <length>",
+    "behavior"                      : 1,
+    "binding"                       : 1,
+    "bleed"                         : "<length>",
+    "bookmark-label"                : "<content> | <attr> | <string>",
+    "bookmark-level"                : "none | <integer>",
+    "bookmark-state"                : "open | closed",
+    "bookmark-target"               : "none | <uri> | <attr>",
+    "border"                        : "<border-width> || <border-style> || <color>",
+    "border-bottom"                 : "<border-width> || <border-style> || <color>",
+    "border-bottom-color"           : "<color> | inherit",
+    "border-bottom-left-radius"     :  "<x-one-radius>",
+    "border-bottom-right-radius"    :  "<x-one-radius>",
+    "border-bottom-style"           : "<border-style>",
+    "border-bottom-width"           : "<border-width>",
+    "border-collapse"               : "collapse | separate | inherit",
+    "border-color"                  : { multi: "<color> | inherit", max: 4 },
+    "border-image"                  : 1,
+    "border-image-outset"           : { multi: "<length> | <number>", max: 4 },
+    "border-image-repeat"           : { multi: "stretch | repeat | round", max: 2 },
+    "border-image-slice"            : function(expression) {
+
+        var valid   = false,
+            numeric = "<number> | <percentage>",
+            fill    = false,
+            count   = 0,
+            max     = 4,
+            part;
+
+        if (ValidationTypes.isAny(expression, "fill")) {
+            fill = true;
+            valid = true;
+        }
+
+        while (expression.hasNext() && count < max) {
+            valid = ValidationTypes.isAny(expression, numeric);
+            if (!valid) {
+                break;
+            }
+            count++;
+        }
+
+
+        if (!fill) {
+            ValidationTypes.isAny(expression, "fill");
+        } else {
+            valid = true;
+        }
+
+        if (expression.hasNext()) {
+            part = expression.next();
+            if (valid) {
+                throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
+            } else {
+                throw new ValidationError("Expected ([<number> | <percentage>]{1,4} && fill?) but found '" + part + "'.", part.line, part.col);
+            }
+        }
+    },
+    "border-image-source"           : "<image> | none",
+    "border-image-width"            : { multi: "<length> | <percentage> | <number> | auto", max: 4 },
+    "border-left"                   : "<border-width> || <border-style> || <color>",
+    "border-left-color"             : "<color> | inherit",
+    "border-left-style"             : "<border-style>",
+    "border-left-width"             : "<border-width>",
+    "border-radius"                 : function(expression) {
+
+        var valid   = false,
+            simple = "<length> | <percentage> | inherit",
+            slash   = false,
+            count   = 0,
+            max     = 8,
+            part;
+
+        while (expression.hasNext() && count < max) {
+            valid = ValidationTypes.isAny(expression, simple);
+            if (!valid) {
+
+                if (String(expression.peek()) === "/" && count > 0 && !slash) {
+                    slash = true;
+                    max = count + 5;
+                    expression.next();
+                } else {
+                    break;
+                }
+            }
+            count++;
+        }
+
+        if (expression.hasNext()) {
+            part = expression.next();
+            if (valid) {
+                throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
+            } else {
+                throw new ValidationError("Expected (<'border-radius'>) but found '" + part + "'.", part.line, part.col);
+            }
+        }
+    },
+    "border-right"                  : "<border-width> || <border-style> || <color>",
+    "border-right-color"            : "<color> | inherit",
+    "border-right-style"            : "<border-style>",
+    "border-right-width"            : "<border-width>",
+    "border-spacing"                : { multi: "<length> | inherit", max: 2 },
+    "border-style"                  : { multi: "<border-style>", max: 4 },
+    "border-top"                    : "<border-width> || <border-style> || <color>",
+    "border-top-color"              : "<color> | inherit",
+    "border-top-left-radius"        : "<x-one-radius>",
+    "border-top-right-radius"       : "<x-one-radius>",
+    "border-top-style"              : "<border-style>",
+    "border-top-width"              : "<border-width>",
+    "border-width"                  : { multi: "<border-width>", max: 4 },
+    "bottom"                        : "<margin-width> | inherit",
+    "-moz-box-align"                : "start | end | center | baseline | stretch",
+    "-moz-box-decoration-break"     : "slice |clone",
+    "-moz-box-direction"            : "normal | reverse | inherit",
+    "-moz-box-flex"                 : "<number>",
+    "-moz-box-flex-group"           : "<integer>",
+    "-moz-box-lines"                : "single | multiple",
+    "-moz-box-ordinal-group"        : "<integer>",
+    "-moz-box-orient"               : "horizontal | vertical | inline-axis | block-axis | inherit",
+    "-moz-box-pack"                 : "start | end | center | justify",
+    "-o-box-decoration-break"       : "slice | clone",
+    "-webkit-box-align"             : "start | end | center | baseline | stretch",
+    "-webkit-box-decoration-break"  : "slice |clone",
+    "-webkit-box-direction"         : "normal | reverse | inherit",
+    "-webkit-box-flex"              : "<number>",
+    "-webkit-box-flex-group"        : "<integer>",
+    "-webkit-box-lines"             : "single | multiple",
+    "-webkit-box-ordinal-group"     : "<integer>",
+    "-webkit-box-orient"            : "horizontal | vertical | inline-axis | block-axis | inherit",
+    "-webkit-box-pack"              : "start | end | center | justify",
+    "box-decoration-break"          : "slice | clone",
+    "box-shadow"                    : function (expression) {
+        var part;
+
+        if (!ValidationTypes.isAny(expression, "none")) {
+            Validation.multiProperty("<shadow>", expression, true, Infinity);
+        } else {
+            if (expression.hasNext()) {
+                part = expression.next();
+                throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
+            }
+        }
+    },
+    "box-sizing"                    : "content-box | border-box | inherit",
+    "break-after"                   : "auto | always | avoid | left | right | page | column | avoid-page | avoid-column",
+    "break-before"                  : "auto | always | avoid | left | right | page | column | avoid-page | avoid-column",
+    "break-inside"                  : "auto | avoid | avoid-page | avoid-column",
+
+    //C
+    "caption-side"                  : "top | bottom | inherit",
+    "clear"                         : "none | right | left | both | inherit",
+    "clip"                          : 1,
+    "color"                         : "<color> | inherit",
+    "color-profile"                 : 1,
+    "column-count"                  : "<integer> | auto",                      //http://www.w3.org/TR/css3-multicol/
+    "column-fill"                   : "auto | balance",
+    "column-gap"                    : "<length> | normal",
+    "column-rule"                   : "<border-width> || <border-style> || <color>",
+    "column-rule-color"             : "<color>",
+    "column-rule-style"             : "<border-style>",
+    "column-rule-width"             : "<border-width>",
+    "column-span"                   : "none | all",
+    "column-width"                  : "<length> | auto",
+    "columns"                       : 1,
+    "content"                       : 1,
+    "counter-increment"             : 1,
+    "counter-reset"                 : 1,
+    "crop"                          : "<shape> | auto",
+    "cue"                           : "cue-after | cue-before | inherit",
+    "cue-after"                     : 1,
+    "cue-before"                    : 1,
+    "cursor"                        : 1,
+
+    //D
+    "direction"                     : "ltr | rtl | inherit",
+    "display"                       : "inline | block | list-item | inline-block | table | inline-table | table-row-group | table-header-group | table-footer-group | table-row | table-column-group | table-column | table-cell | table-caption | grid | inline-grid | run-in | ruby | ruby-base | ruby-text | ruby-base-container | ruby-text-container | contents | none | inherit | -moz-box | -moz-inline-block | -moz-inline-box | -moz-inline-grid | -moz-inline-stack | -moz-inline-table | -moz-grid | -moz-grid-group | -moz-grid-line | -moz-groupbox | -moz-deck | -moz-popup | -moz-stack | -moz-marker | -webkit-box | -webkit-inline-box | -ms-flexbox | -ms-inline-flexbox | flex | -webkit-flex | inline-flex | -webkit-inline-flex",
+    "dominant-baseline"             : 1,
+    "drop-initial-after-adjust"     : "central | middle | after-edge | text-after-edge | ideographic | alphabetic | mathematical | <percentage> | <length>",
+    "drop-initial-after-align"      : "baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical",
+    "drop-initial-before-adjust"    : "before-edge | text-before-edge | central | middle | hanging | mathematical | <percentage> | <length>",
+    "drop-initial-before-align"     : "caps-height | baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical",
+    "drop-initial-size"             : "auto | line | <length> | <percentage>",
+    "drop-initial-value"            : "initial | <integer>",
+
+    //E
+    "elevation"                     : "<angle> | below | level | above | higher | lower | inherit",
+    "empty-cells"                   : "show | hide | inherit",
+
+    //F
+    "filter"                        : 1,
+    "fit"                           : "fill | hidden | meet | slice",
+    "fit-position"                  : 1,
+    "flex"                          : "<flex>",
+    "flex-basis"                    : "<width>",
+    "flex-direction"                : "row | row-reverse | column | column-reverse",
+    "flex-flow"                     : "<flex-direction> || <flex-wrap>",
+    "flex-grow"                     : "<number>",
+    "flex-shrink"                   : "<number>",
+    "flex-wrap"                     : "nowrap | wrap | wrap-reverse",
+    "-webkit-flex"                  : "<flex>",
+    "-webkit-flex-basis"            : "<width>",
+    "-webkit-flex-direction"        : "row | row-reverse | column | column-reverse",
+    "-webkit-flex-flow"             : "<flex-direction> || <flex-wrap>",
+    "-webkit-flex-grow"             : "<number>",
+    "-webkit-flex-shrink"           : "<number>",
+    "-webkit-flex-wrap"             : "nowrap | wrap | wrap-reverse",
+    "-ms-flex"                      : "<flex>",
+    "-ms-flex-align"                : "start | end | center | stretch | baseline",
+    "-ms-flex-direction"            : "row | row-reverse | column | column-reverse | inherit",
+    "-ms-flex-order"                : "<number>",
+    "-ms-flex-pack"                 : "start | end | center | justify",
+    "-ms-flex-wrap"                 : "nowrap | wrap | wrap-reverse",
+    "float"                         : "left | right | none | inherit",
+    "float-offset"                  : 1,
+    "font"                          : 1,
+    "font-family"                   : 1,
+    "font-feature-settings"         : "<feature-tag-value> | normal | inherit",
+    "font-kerning"                  : "auto | normal | none | initial | inherit | unset",
+    "font-size"                     : "<absolute-size> | <relative-size> | <length> | <percentage> | inherit",
+    "font-size-adjust"              : "<number> | none | inherit",
+    "font-stretch"                  : "normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded | inherit",
+    "font-style"                    : "normal | italic | oblique | inherit",
+    "font-variant"                  : "normal | small-caps | inherit",
+    "font-variant-caps"             : "normal | small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps",
+    "font-variant-position"         : "normal | sub | super | inherit | initial | unset",
+    "font-weight"                   : "normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | inherit",
+
+    //G
+    "grid"                          : 1,
+    "grid-area"                     : 1,
+    "grid-auto-columns"             : 1,
+    "grid-auto-flow"                : 1,
+    "grid-auto-position"            : 1,
+    "grid-auto-rows"                : 1,
+    "grid-cell-stacking"            : "columns | rows | layer",
+    "grid-column"                   : 1,
+    "grid-columns"                  : 1,
+    "grid-column-align"             : "start | end | center | stretch",
+    "grid-column-sizing"            : 1,
+    "grid-column-start"             : 1,
+    "grid-column-end"               : 1,
+    "grid-column-span"              : "<integer>",
+    "grid-flow"                     : "none | rows | columns",
+    "grid-layer"                    : "<integer>",
+    "grid-row"                      : 1,
+    "grid-rows"                     : 1,
+    "grid-row-align"                : "start | end | center | stretch",
+    "grid-row-start"                : 1,
+    "grid-row-end"                  : 1,
+    "grid-row-span"                 : "<integer>",
+    "grid-row-sizing"               : 1,
+    "grid-template"                 : 1,
+    "grid-template-areas"           : 1,
+    "grid-template-columns"         : 1,
+    "grid-template-rows"            : 1,
+
+    //H
+    "hanging-punctuation"           : 1,
+    "height"                        : "<margin-width> | <content-sizing> | inherit",
+    "hyphenate-after"               : "<integer> | auto",
+    "hyphenate-before"              : "<integer> | auto",
+    "hyphenate-character"           : "<string> | auto",
+    "hyphenate-lines"               : "no-limit | <integer>",
+    "hyphenate-resource"            : 1,
+    "hyphens"                       : "none | manual | auto",
+
+    //I
+    "icon"                          : 1,
+    "image-orientation"             : "angle | auto",
+    "image-rendering"               : 1,
+    "image-resolution"              : 1,
+    "ime-mode"                      : "auto | normal | active | inactive | disabled | inherit",
+    "inline-box-align"              : "initial | last | <integer>",
+
+    //J
+    "justify-content"               : "flex-start | flex-end | center | space-between | space-around",
+    "-webkit-justify-content"       : "flex-start | flex-end | center | space-between | space-around",
+
+    //L
+    "left"                          : "<margin-width> | inherit",
+    "letter-spacing"                : "<length> | normal | inherit",
+    "line-height"                   : "<number> | <length> | <percentage> | normal | inherit",
+    "line-break"                    : "auto | loose | normal | strict",
+    "line-stacking"                 : 1,
+    "line-stacking-ruby"            : "exclude-ruby | include-ruby",
+    "line-stacking-shift"           : "consider-shifts | disregard-shifts",
+    "line-stacking-strategy"        : "inline-line-height | block-line-height | max-height | grid-height",
+    "list-style"                    : 1,
+    "list-style-image"              : "<uri> | none | inherit",
+    "list-style-position"           : "inside | outside | inherit",
+    "list-style-type"               : "disc | circle | square | decimal | decimal-leading-zero | lower-roman | upper-roman | lower-greek | lower-latin | upper-latin | armenian | georgian | lower-alpha | upper-alpha | none | inherit",
+
+    //M
+    "margin"                        : { multi: "<margin-width> | inherit", max: 4 },
+    "margin-bottom"                 : "<margin-width> | inherit",
+    "margin-left"                   : "<margin-width> | inherit",
+    "margin-right"                  : "<margin-width> | inherit",
+    "margin-top"                    : "<margin-width> | inherit",
+    "mark"                          : 1,
+    "mark-after"                    : 1,
+    "mark-before"                   : 1,
+    "marks"                         : 1,
+    "marquee-direction"             : 1,
+    "marquee-play-count"            : 1,
+    "marquee-speed"                 : 1,
+    "marquee-style"                 : 1,
+    "max-height"                    : "<length> | <percentage> | <content-sizing> | none | inherit",
+    "max-width"                     : "<length> | <percentage> | <content-sizing> | none | inherit",
+    "min-height"                    : "<length> | <percentage> | <content-sizing> | contain-floats | -moz-contain-floats | -webkit-contain-floats | inherit",
+    "min-width"                     : "<length> | <percentage> | <content-sizing> | contain-floats | -moz-contain-floats | -webkit-contain-floats | inherit",
+    "move-to"                       : 1,
+
+    //N
+    "nav-down"                      : 1,
+    "nav-index"                     : 1,
+    "nav-left"                      : 1,
+    "nav-right"                     : 1,
+    "nav-up"                        : 1,
+
+    //O
+    "object-fit"                    : "fill | contain | cover | none | scale-down",
+    "object-position"               : "<bg-position>",
+    "opacity"                       : "<number> | inherit",
+    "order"                         : "<integer>",
+    "-webkit-order"                 : "<integer>",
+    "orphans"                       : "<integer> | inherit",
+    "outline"                       : 1,
+    "outline-color"                 : "<color> | invert | inherit",
+    "outline-offset"                : 1,
+    "outline-style"                 : "<border-style> | inherit",
+    "outline-width"                 : "<border-width> | inherit",
+    "overflow"                      : "visible | hidden | scroll | auto | inherit",
+    "overflow-style"                : 1,
+    "overflow-wrap"                 : "normal | break-word",
+    "overflow-x"                    : 1,
+    "overflow-y"                    : 1,
+
+    //P
+    "padding"                       : { multi: "<padding-width> | inherit", max: 4 },
+    "padding-bottom"                : "<padding-width> | inherit",
+    "padding-left"                  : "<padding-width> | inherit",
+    "padding-right"                 : "<padding-width> | inherit",
+    "padding-top"                   : "<padding-width> | inherit",
+    "page"                          : 1,
+    "page-break-after"              : "auto | always | avoid | left | right | inherit",
+    "page-break-before"             : "auto | always | avoid | left | right | inherit",
+    "page-break-inside"             : "auto | avoid | inherit",
+    "page-policy"                   : 1,
+    "pause"                         : 1,
+    "pause-after"                   : 1,
+    "pause-before"                  : 1,
+    "perspective"                   : 1,
+    "perspective-origin"            : 1,
+    "phonemes"                      : 1,
+    "pitch"                         : 1,
+    "pitch-range"                   : 1,
+    "play-during"                   : 1,
+    "pointer-events"                : "auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all | inherit",
+    "position"                      : "static | relative | absolute | fixed | inherit",
+    "presentation-level"            : 1,
+    "punctuation-trim"              : 1,
+
+    //Q
+    "quotes"                        : 1,
+
+    //R
+    "rendering-intent"              : 1,
+    "resize"                        : 1,
+    "rest"                          : 1,
+    "rest-after"                    : 1,
+    "rest-before"                   : 1,
+    "richness"                      : 1,
+    "right"                         : "<margin-width> | inherit",
+    "rotation"                      : 1,
+    "rotation-point"                : 1,
+    "ruby-align"                    : 1,
+    "ruby-overhang"                 : 1,
+    "ruby-position"                 : 1,
+    "ruby-span"                     : 1,
+
+    //S
+    "size"                          : 1,
+    "speak"                         : "normal | none | spell-out | inherit",
+    "speak-header"                  : "once | always | inherit",
+    "speak-numeral"                 : "digits | continuous | inherit",
+    "speak-punctuation"             : "code | none | inherit",
+    "speech-rate"                   : 1,
+    "src"                           : 1,
+    "stress"                        : 1,
+    "string-set"                    : 1,
+
+    "table-layout"                  : "auto | fixed | inherit",
+    "tab-size"                      : "<integer> | <length>",
+    "target"                        : 1,
+    "target-name"                   : 1,
+    "target-new"                    : 1,
+    "target-position"               : 1,
+    "text-align"                    : "left | right | center | justify | match-parent | start | end | inherit" ,
+    "text-align-last"               : 1,
+    "text-decoration"               : 1,
+    "text-emphasis"                 : 1,
+    "text-height"                   : 1,
+    "text-indent"                   : "<length> | <percentage> | inherit",
+    "text-justify"                  : "auto | none | inter-word | inter-ideograph | inter-cluster | distribute | kashida",
+    "text-outline"                  : 1,
+    "text-overflow"                 : 1,
+    "text-rendering"                : "auto | optimizeSpeed | optimizeLegibility | geometricPrecision | inherit",
+    "text-shadow"                   : 1,
+    "text-transform"                : "capitalize | uppercase | lowercase | none | inherit",
+    "text-wrap"                     : "normal | none | avoid",
+    "top"                           : "<margin-width> | inherit",
+    "-ms-touch-action"              : "auto | none | pan-x | pan-y | pan-left | pan-right | pan-up | pan-down | manipulation",
+    "touch-action"                  : "auto | none | pan-x | pan-y | pan-left | pan-right | pan-up | pan-down | manipulation",
+    "transform"                     : 1,
+    "transform-origin"              : 1,
+    "transform-style"               : 1,
+    "transition"                    : 1,
+    "transition-delay"              : 1,
+    "transition-duration"           : 1,
+    "transition-property"           : 1,
+    "transition-timing-function"    : 1,
+
+    //U
+    "unicode-bidi"                  : "normal | embed | isolate | bidi-override | isolate-override | plaintext | inherit",
+    "user-modify"                   : "read-only | read-write | write-only | inherit",
+    "user-select"                   : "none | text | toggle | element | elements | all | inherit",
+
+    //V
+    "vertical-align"                : "auto | use-script | baseline | sub | super | top | text-top | central | middle | bottom | text-bottom | <percentage> | <length> | inherit",
+    "visibility"                    : "visible | hidden | collapse | inherit",
+    "voice-balance"                 : 1,
+    "voice-duration"                : 1,
+    "voice-family"                  : 1,
+    "voice-pitch"                   : 1,
+    "voice-pitch-range"             : 1,
+    "voice-rate"                    : 1,
+    "voice-stress"                  : 1,
+    "voice-volume"                  : 1,
+    "volume"                        : 1,
+
+    //W
+    "white-space"                   : "normal | pre | nowrap | pre-wrap | pre-line | inherit | -pre-wrap | -o-pre-wrap | -moz-pre-wrap | -hp-pre-wrap", //http://perishablepress.com/wrapping-content/
+    "white-space-collapse"          : 1,
+    "widows"                        : "<integer> | inherit",
+    "width"                         : "<length> | <percentage> | <content-sizing> | auto | inherit",
+    "will-change"                   : { multi: "<ident>", comma: true },
+    "word-break"                    : "normal | keep-all | break-all",
+    "word-spacing"                  : "<length> | normal | inherit",
+    "word-wrap"                     : "normal | break-word",
+    "writing-mode"                  : "horizontal-tb | vertical-rl | vertical-lr | lr-tb | rl-tb | tb-rl | bt-rl | tb-lr | bt-lr | lr-bt | rl-bt | lr | rl | tb | inherit",
+
+    //Z
+    "z-index"                       : "<integer> | auto | inherit",
+    "zoom"                          : "<number> | <percentage> | normal"
+};
+/**
+ * Represents a selector combinator (whitespace, +, >).
+ * @namespace parserlib.css
+ * @class PropertyName
+ * @extends parserlib.util.SyntaxUnit
+ * @constructor
+ * @param {String} text The text representation of the unit.
+ * @param {String} hack The type of IE hack applied ("*", "_", or null).
+ * @param {int} line The line of text on which the unit resides.
+ * @param {int} col The column of text on which the unit resides.
+ */
+function PropertyName(text, hack, line, col){
+
+    SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_NAME_TYPE);
+
+    /**
+     * The type of IE hack applied ("*", "_", or null).
+     * @type String
+     * @property hack
+     */
+    this.hack = hack;
+
+}
+
+PropertyName.prototype = new SyntaxUnit();
+PropertyName.prototype.constructor = PropertyName;
+PropertyName.prototype.toString = function(){
+    return (this.hack ? this.hack : "") + this.text;
+};
+/**
+ * Represents a single part of a CSS property value, meaning that it represents
+ * just everything single part between ":" and ";". If there are multiple values
+ * separated by commas, this type represents just one of the values.
+ * @param {String[]} parts An array of value parts making up this value.
+ * @param {int} line The line of text on which the unit resides.
+ * @param {int} col The column of text on which the unit resides.
+ * @namespace parserlib.css
+ * @class PropertyValue
+ * @extends parserlib.util.SyntaxUnit
+ * @constructor
+ */
+function PropertyValue(parts, line, col){
+
+    SyntaxUnit.call(this, parts.join(" "), line, col, Parser.PROPERTY_VALUE_TYPE);
+
+    /**
+     * The parts that make up the selector.
+     * @type Array
+     * @property parts
+     */
+    this.parts = parts;
+
+}
+
+PropertyValue.prototype = new SyntaxUnit();
+PropertyValue.prototype.constructor = PropertyValue;
+
+/**
+ * A utility class that allows for easy iteration over the various parts of a
+ * property value.
+ * @param {parserlib.css.PropertyValue} value The property value to iterate over.
+ * @namespace parserlib.css
+ * @class PropertyValueIterator
+ * @constructor
+ */
+function PropertyValueIterator(value){
+
+    /**
+     * Iterator value
+     * @type int
+     * @property _i
+     * @private
+     */
+    this._i = 0;
+
+    /**
+     * The parts that make up the value.
+     * @type Array
+     * @property _parts
+     * @private
+     */
+    this._parts = value.parts;
+
+    /**
+     * Keeps track of bookmarks along the way.
+     * @type Array
+     * @property _marks
+     * @private
+     */
+    this._marks = [];
+
+    /**
+     * Holds the original property value.
+     * @type parserlib.css.PropertyValue
+     * @property value
+     */
+    this.value = value;
+
+}
+
+/**
+ * Returns the total number of parts in the value.
+ * @return {int} The total number of parts in the value.
+ * @method count
+ */
+PropertyValueIterator.prototype.count = function(){
+    return this._parts.length;
+};
+
+/**
+ * Indicates if the iterator is positioned at the first item.
+ * @return {Boolean} True if positioned at first item, false if not.
+ * @method isFirst
+ */
+PropertyValueIterator.prototype.isFirst = function(){
+    return this._i === 0;
+};
+
+/**
+ * Indicates if there are more parts of the property value.
+ * @return {Boolean} True if there are more parts, false if not.
+ * @method hasNext
+ */
+PropertyValueIterator.prototype.hasNext = function(){
+    return (this._i < this._parts.length);
+};
+
+/**
+ * Marks the current spot in the iteration so it can be restored to
+ * later on.
+ * @return {void}
+ * @method mark
+ */
+PropertyValueIterator.prototype.mark = function(){
+    this._marks.push(this._i);
+};
+
+/**
+ * Returns the next part of the property value or null if there is no next
+ * part. Does not move the internal counter forward.
+ * @return {parserlib.css.PropertyValuePart} The next part of the property value or null if there is no next
+ * part.
+ * @method peek
+ */
+PropertyValueIterator.prototype.peek = function(count){
+    return this.hasNext() ? this._parts[this._i + (count || 0)] : null;
+};
+
+/**
+ * Returns the next part of the property value or null if there is no next
+ * part.
+ * @return {parserlib.css.PropertyValuePart} The next part of the property value or null if there is no next
+ * part.
+ * @method next
+ */
+PropertyValueIterator.prototype.next = function(){
+    return this.hasNext() ? this._parts[this._i++] : null;
+};
+
+/**
+ * Returns the previous part of the property value or null if there is no
+ * previous part.
+ * @return {parserlib.css.PropertyValuePart} The previous part of the
+ * property value or null if there is no previous part.
+ * @method previous
+ */
+PropertyValueIterator.prototype.previous = function(){
+    return this._i > 0 ? this._parts[--this._i] : null;
+};
+
+/**
+ * Restores the last saved bookmark.
+ * @return {void}
+ * @method restore
+ */
+PropertyValueIterator.prototype.restore = function(){
+    if (this._marks.length){
+        this._i = this._marks.pop();
+    }
+};
+
+/**
+ * Represents a single part of a CSS property value, meaning that it represents
+ * just one part of the data between ":" and ";".
+ * @param {String} text The text representation of the unit.
+ * @param {int} line The line of text on which the unit resides.
+ * @param {int} col The column of text on which the unit resides.
+ * @namespace parserlib.css
+ * @class PropertyValuePart
+ * @extends parserlib.util.SyntaxUnit
+ * @constructor
+ */
+function PropertyValuePart(text, line, col){
+    SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_VALUE_PART_TYPE);
+
+    /**
+     * Indicates the type of value unit.
+     * @type String
+     * @property type
+     */
+    this.type = "unknown";
+
+    //figure out what type of data it is
+
+    var temp;
+    //it is a measurement?
+    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Deprecated_and_obsolete_features#RegExp_Propertiesa
+    var match;
+    if (match = /^([+\-]?[\d\.]+)([a-z]+)$/i.exec(text)){  //dimension
+        this.type = "dimension";
+        this.value = +match[1];
+        this.units = match[2];
+
+        //try to narrow down
+        switch(this.units.toLowerCase()){
+
+            case "em":
+            case "rem":
+            case "ex":
+            case "px":
+            case "cm":
+            case "mm":
+            case "in":
+            case "pt":
+            case "pc":
+            case "ch":
+            case "vh":
+            case "vw":
+            case "vmax":
+            case "vmin":
+                this.type = "length";
+                break;
+                
+            case "fr":
+                this.type = "grid";
+                break;
+
+            case "deg":
+            case "rad":
+            case "grad":
+                this.type = "angle";
+                break;
+
+            case "ms":
+            case "s":
+                this.type = "time";
+                break;
+
+            case "hz":
+            case "khz":
+                this.type = "frequency";
+                break;
+
+            case "dpi":
+            case "dpcm":
+                this.type = "resolution";
+                break;
+
+            //default
+
+        }
+
+    } else if (/^([+\-]?[\d\.]+)%$/i.test(text)){  //percentage
+        this.type = "percentage";
+        this.value = +RegExp.$1;
+    } else if (/^([+\-]?\d+)$/i.test(text)){  //integer
+        this.type = "integer";
+        this.value = +RegExp.$1;
+    } else if (/^([+\-]?[\d\.]+)$/i.test(text)){  //number
+        this.type = "number";
+        this.value = +RegExp.$1;
+
+    } else if (/^#([a-f0-9]{3,6})/i.test(text)){  //hexcolor
+        this.type = "color";
+        temp = RegExp.$1;
+        if (temp.length === 3){
+            this.red    = parseInt(temp.charAt(0)+temp.charAt(0),16);
+            this.green  = parseInt(temp.charAt(1)+temp.charAt(1),16);
+            this.blue   = parseInt(temp.charAt(2)+temp.charAt(2),16);
+        } else {
+            this.red    = parseInt(temp.substring(0,2),16);
+            this.green  = parseInt(temp.substring(2,4),16);
+            this.blue   = parseInt(temp.substring(4,6),16);
+        }
+    } else if (/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i.test(text)){ //rgb() color with absolute numbers
+        this.type   = "color";
+        this.red    = +RegExp.$1;
+        this.green  = +RegExp.$2;
+        this.blue   = +RegExp.$3;
+    } else if (/^rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)){ //rgb() color with percentages
+        this.type   = "color";
+        this.red    = +RegExp.$1 * 255 / 100;
+        this.green  = +RegExp.$2 * 255 / 100;
+        this.blue   = +RegExp.$3 * 255 / 100;
+    } else if (/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d\.]+)\s*\)/i.test(text)){ //rgba() color with absolute numbers
+        this.type   = "color";
+        this.red    = +RegExp.$1;
+        this.green  = +RegExp.$2;
+        this.blue   = +RegExp.$3;
+        this.alpha  = +RegExp.$4;
+    } else if (/^rgba\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)){ //rgba() color with percentages
+        this.type   = "color";
+        this.red    = +RegExp.$1 * 255 / 100;
+        this.green  = +RegExp.$2 * 255 / 100;
+        this.blue   = +RegExp.$3 * 255 / 100;
+        this.alpha  = +RegExp.$4;
+    } else if (/^hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)){ //hsl()
+        this.type   = "color";
+        this.hue    = +RegExp.$1;
+        this.saturation = +RegExp.$2 / 100;
+        this.lightness  = +RegExp.$3 / 100;
+    } else if (/^hsla\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)){ //hsla() color with percentages
+        this.type   = "color";
+        this.hue    = +RegExp.$1;
+        this.saturation = +RegExp.$2 / 100;
+        this.lightness  = +RegExp.$3 / 100;
+        this.alpha  = +RegExp.$4;
+    } else if (/^url\(["']?([^\)"']+)["']?\)/i.test(text)){ //URI
+        this.type   = "uri";
+        this.uri    = RegExp.$1;
+    } else if (/^([^\(]+)\(/i.test(text)){
+        this.type   = "function";
+        this.name   = RegExp.$1;
+        this.value  = text;
+    } else if (/^"([^\n\r\f\\"]|\\\r\n|\\[^\r0-9a-f]|\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)*"/i.test(text)){    //double-quoted string
+        this.type   = "string";
+        this.value  = PropertyValuePart.parseString(text);
+    } else if (/^'([^\n\r\f\\']|\\\r\n|\\[^\r0-9a-f]|\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)*'/i.test(text)){    //single-quoted string
+        this.type   = "string";
+        this.value  = PropertyValuePart.parseString(text);
+    } else if (Colors[text.toLowerCase()]){  //named color
+        this.type   = "color";
+        temp        = Colors[text.toLowerCase()].substring(1);
+        this.red    = parseInt(temp.substring(0,2),16);
+        this.green  = parseInt(temp.substring(2,4),16);
+        this.blue   = parseInt(temp.substring(4,6),16);
+    } else if (/^[\,\/]$/.test(text)){
+        this.type   = "operator";
+        this.value  = text;
+    } else if (/^[a-z\-_\u0080-\uFFFF][a-z0-9\-_\u0080-\uFFFF]*$/i.test(text)){
+        this.type   = "identifier";
+        this.value  = text;
+    }
+
+}
+
+PropertyValuePart.prototype = new SyntaxUnit();
+PropertyValuePart.prototype.constructor = PropertyValuePart;
+
+/**
+ * Helper method to parse a CSS string.
+ */
+PropertyValuePart.parseString = function(str) {
+    str = str.slice(1, -1); // Strip surrounding single/double quotes
+    var replacer = function(match, esc) {
+        if (/^(\n|\r\n|\r|\f)$/.test(esc)) { return ''; }
+        var m = /^[0-9a-f]{1,6}/i.exec(esc);
+        if (m) {
+            var codePoint = parseInt(m[0], 16);
+            if (String.fromCodePoint) {
+                return String.fromCodePoint(codePoint);
+            } else {
+                // XXX No support for surrogates on old JavaScript engines.
+                return String.fromCharCode(codePoint);
+            }
+        }
+        return esc;
+    };
+    return str.replace(/\\(\r\n|[^\r0-9a-f]|[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)/ig,
+                       replacer);
+};
+
+/**
+ * Helper method to serialize a CSS string.
+ */
+PropertyValuePart.serializeString = function(value) {
+    var replacer = function(match, c) {
+        if (c === '"') {
+            return "\\" + c;
+        }
+        var cp = String.codePointAt ? String.codePointAt(0) :
+            // We only escape non-surrogate chars, so using charCodeAt
+            // is harmless here.
+            String.charCodeAt(0);
+        return "\\" + cp.toString(16) + " ";
+    };
+    return '"' + value.replace(/["\r\n\f]/g, replacer) + '"';
+};
+
+/**
+ * Create a new syntax unit based solely on the given token.
+ * Convenience method for creating a new syntax unit when
+ * it represents a single token instead of multiple.
+ * @param {Object} token The token object to represent.
+ * @return {parserlib.css.PropertyValuePart} The object representing the token.
+ * @static
+ * @method fromToken
+ */
+PropertyValuePart.fromToken = function(token){
+    return new PropertyValuePart(token.value, token.startLine, token.startCol);
+};
+var Pseudos = {
+    __proto__:       null,
+    ":first-letter": 1,
+    ":first-line":   1,
+    ":before":       1,
+    ":after":        1
+};
+
+Pseudos.ELEMENT = 1;
+Pseudos.CLASS = 2;
+
+Pseudos.isElement = function(pseudo){
+    return pseudo.indexOf("::") === 0 || Pseudos[pseudo.toLowerCase()] === Pseudos.ELEMENT;
+};
+/**
+ * Represents an entire single selector, including all parts but not
+ * including multiple selectors (those separated by commas).
+ * @namespace parserlib.css
+ * @class Selector
+ * @extends parserlib.util.SyntaxUnit
+ * @constructor
+ * @param {Array} parts Array of selectors parts making up this selector.
+ * @param {int} line The line of text on which the unit resides.
+ * @param {int} col The column of text on which the unit resides.
+ */
+function Selector(parts, line, col){
+
+    SyntaxUnit.call(this, parts.join(" "), line, col, Parser.SELECTOR_TYPE);
+
+    /**
+     * The parts that make up the selector.
+     * @type Array
+     * @property parts
+     */
+    this.parts = parts;
+
+    /**
+     * The specificity of the selector.
+     * @type parserlib.css.Specificity
+     * @property specificity
+     */
+    this.specificity = Specificity.calculate(this);
+
+}
+
+Selector.prototype = new SyntaxUnit();
+Selector.prototype.constructor = Selector;
+
+/**
+ * Represents a single part of a selector string, meaning a single set of
+ * element name and modifiers. This does not include combinators such as
+ * spaces, +, >, etc.
+ * @namespace parserlib.css
+ * @class SelectorPart
+ * @extends parserlib.util.SyntaxUnit
+ * @constructor
+ * @param {String} elementName The element name in the selector or null
+ *      if there is no element name.
+ * @param {Array} modifiers Array of individual modifiers for the element.
+ *      May be empty if there are none.
+ * @param {String} text The text representation of the unit.
+ * @param {int} line The line of text on which the unit resides.
+ * @param {int} col The column of text on which the unit resides.
+ */
+function SelectorPart(elementName, modifiers, text, line, col){
+
+    SyntaxUnit.call(this, text, line, col, Parser.SELECTOR_PART_TYPE);
+
+    /**
+     * The tag name of the element to which this part
+     * of the selector affects.
+     * @type String
+     * @property elementName
+     */
+    this.elementName = elementName;
+
+    /**
+     * The parts that come after the element name, such as class names, IDs,
+     * pseudo classes/elements, etc.
+     * @type Array
+     * @property modifiers
+     */
+    this.modifiers = modifiers;
+
+}
+
+SelectorPart.prototype = new SyntaxUnit();
+SelectorPart.prototype.constructor = SelectorPart;
+
+/**
+ * Represents a selector modifier string, meaning a class name, element name,
+ * element ID, pseudo rule, etc.
+ * @namespace parserlib.css
+ * @class SelectorSubPart
+ * @extends parserlib.util.SyntaxUnit
+ * @constructor
+ * @param {String} text The text representation of the unit.
+ * @param {String} type The type of selector modifier.
+ * @param {int} line The line of text on which the unit resides.
+ * @param {int} col The column of text on which the unit resides.
+ */
+function SelectorSubPart(text, type, line, col){
+
+    SyntaxUnit.call(this, text, line, col, Parser.SELECTOR_SUB_PART_TYPE);
+
+    /**
+     * The type of modifier.
+     * @type String
+     * @property type
+     */
+    this.type = type;
+
+    /**
+     * Some subparts have arguments, this represents them.
+     * @type Array
+     * @property args
+     */
+    this.args = [];
+
+}
+
+SelectorSubPart.prototype = new SyntaxUnit();
+SelectorSubPart.prototype.constructor = SelectorSubPart;
+
+/**
+ * Represents a selector's specificity.
+ * @namespace parserlib.css
+ * @class Specificity
+ * @constructor
+ * @param {int} a Should be 1 for inline styles, zero for stylesheet styles
+ * @param {int} b Number of ID selectors
+ * @param {int} c Number of classes and pseudo classes
+ * @param {int} d Number of element names and pseudo elements
+ */
+function Specificity(a, b, c, d){
+    this.a = a;
+    this.b = b;
+    this.c = c;
+    this.d = d;
+}
+
+Specificity.prototype = {
+    constructor: Specificity,
+
+    /**
+     * Compare this specificity to another.
+     * @param {Specificity} other The other specificity to compare to.
+     * @return {int} -1 if the other specificity is larger, 1 if smaller, 0 if equal.
+     * @method compare
+     */
+    compare: function(other){
+        var comps = ["a", "b", "c", "d"],
+            i, len;
+
+        for (i=0, len=comps.length; i < len; i++){
+            if (this[comps[i]] < other[comps[i]]){
+                return -1;
+            } else if (this[comps[i]] > other[comps[i]]){
+                return 1;
+            }
+        }
+
+        return 0;
+    },
+
+    /**
+     * Creates a numeric value for the specificity.
+     * @return {int} The numeric value for the specificity.
+     * @method valueOf
+     */
+    valueOf: function(){
+        return (this.a * 1000) + (this.b * 100) + (this.c * 10) + this.d;
+    },
+
+    /**
+     * Returns a string representation for specificity.
+     * @return {String} The string representation of specificity.
+     * @method toString
+     */
+    toString: function(){
+        return this.a + "," + this.b + "," + this.c + "," + this.d;
+    }
+
+};
+
+/**
+ * Calculates the specificity of the given selector.
+ * @param {parserlib.css.Selector} The selector to calculate specificity for.
+ * @return {parserlib.css.Specificity} The specificity of the selector.
+ * @static
+ * @method calculate
+ */
+Specificity.calculate = function(selector){
+
+    var i, len,
+        part,
+        b=0, c=0, d=0;
+
+    function updateValues(part){
+
+        var i, j, len, num,
+            elementName = part.elementName ? part.elementName.text : "",
+            modifier;
+
+        if (elementName && elementName.charAt(elementName.length-1) !== "*") {
+            d++;
+        }
+
+        for (i=0, len=part.modifiers.length; i < len; i++){
+            modifier = part.modifiers[i];
+            switch(modifier.type){
+                case "class":
+                case "attribute":
+                    c++;
+                    break;
+
+                case "id":
+                    b++;
+                    break;
+
+                case "pseudo":
+                    if (Pseudos.isElement(modifier.text)){
+                        d++;
+                    } else {
+                        c++;
+                    }
+                    break;
+
+                case "not":
+                    for (j=0, num=modifier.args.length; j < num; j++){
+                        updateValues(modifier.args[j]);
+                    }
+            }
+         }
+    }
+
+    for (i=0, len=selector.parts.length; i < len; i++){
+        part = selector.parts[i];
+
+        if (part instanceof SelectorPart){
+            updateValues(part);
+        }
+    }
+
+    return new Specificity(0, b, c, d);
+};
+
+var h = /^[0-9a-fA-F]$/,
+    //nonascii = /^[\u0080-\uFFFF]$/,
+    nl = /\n|\r\n|\r|\f/;
+
+//-----------------------------------------------------------------------------
+// Helper functions
+//-----------------------------------------------------------------------------
+
+
+function isHexDigit(c){
+    return c !== null && h.test(c);
+}
+
+function isDigit(c){
+    return c !== null && /\d/.test(c);
+}
+
+function isWhitespace(c){
+    return c !== null && /\s/.test(c);
+}
+
+function isNewLine(c){
+    return c !== null && nl.test(c);
+}
+
+function isNameStart(c){
+    return c !== null && (/[a-z_\u0080-\uFFFF\\]/i.test(c));
+}
+
+function isNameChar(c){
+    return c !== null && (isNameStart(c) || /[0-9\-\\]/.test(c));
+}
+
+function isIdentStart(c){
+    return c !== null && (isNameStart(c) || /\-\\/.test(c));
+}
+
+function mix(receiver, supplier){
+	for (var prop in supplier){
+		if (Object.prototype.hasOwnProperty.call(supplier, prop)){
+			receiver[prop] = supplier[prop];
+		}
+	}
+	return receiver;
+}
+
+//-----------------------------------------------------------------------------
+// CSS Token Stream
+//-----------------------------------------------------------------------------
+
+
+/**
+ * A token stream that produces CSS tokens.
+ * @param {String|Reader} input The source of text to tokenize.
+ * @constructor
+ * @class TokenStream
+ * @namespace parserlib.css
+ */
+function TokenStream(input){
+	TokenStreamBase.call(this, input, Tokens);
+}
+
+TokenStream.prototype = mix(new TokenStreamBase(), {
+
+    /**
+     * Overrides the TokenStreamBase method of the same name
+     * to produce CSS tokens.
+     * @param {variant} channel The name of the channel to use
+     *      for the next token.
+     * @return {Object} A token object representing the next token.
+     * @method _getToken
+     * @private
+     */
+    _getToken: function(channel){
+
+        var c,
+            reader = this._reader,
+            token   = null,
+            startLine   = reader.getLine(),
+            startCol    = reader.getCol();
+
+        c = reader.read();
+
+
+        while(c){
+            switch(c){
+
+                /*
+                 * Potential tokens:
+                 * - COMMENT
+                 * - SLASH
+                 * - CHAR
+                 */
+                case "/":
+
+                    if(reader.peek() === "*"){
+                        token = this.commentToken(c, startLine, startCol);
+                    } else {
+                        token = this.charToken(c, startLine, startCol);
+                    }
+                    break;
+
+                /*
+                 * Potential tokens:
+                 * - DASHMATCH
+                 * - INCLUDES
+                 * - PREFIXMATCH
+                 * - SUFFIXMATCH
+                 * - SUBSTRINGMATCH
+                 * - CHAR
+                 */
+                case "|":
+                case "~":
+                case "^":
+                case "$":
+                case "*":
+                    if(reader.peek() === "="){
+                        token = this.comparisonToken(c, startLine, startCol);
+                    } else {
+                        token = this.charToken(c, startLine, startCol);
+                    }
+                    break;
+
+                /*
+                 * Potential tokens:
+                 * - STRING
+                 * - INVALID
+                 */
+                case "\"":
+                case "'":
+                    token = this.stringToken(c, startLine, startCol);
+                    break;
+
+                /*
+                 * Potential tokens:
+                 * - HASH
+                 * - CHAR
+                 */
+                case "#":
+                    if (isNameChar(reader.peek())){
+                        token = this.hashToken(c, startLine, startCol);
+                    } else {
+                        token = this.charToken(c, startLine, startCol);
+                    }
+                    break;
+
+                /*
+                 * Potential tokens:
+                 * - DOT
+                 * - NUMBER
+                 * - DIMENSION
+                 * - PERCENTAGE
+                 */
+                case ".":
+                    if (isDigit(reader.peek())){
+                        token = this.numberToken(c, startLine, startCol);
+                    } else {
+                        token = this.charToken(c, startLine, startCol);
+                    }
+                    break;
+
+                /*
+                 * Potential tokens:
+                 * - CDC
+                 * - MINUS
+                 * - NUMBER
+                 * - DIMENSION
+                 * - PERCENTAGE
+                 */
+                case "-":
+                    if (reader.peek() === "-"){  //could be closing HTML-style comment
+                        token = this.htmlCommentEndToken(c, startLine, startCol);
+                    } else if (isNameStart(reader.peek())){
+                        token = this.identOrFunctionToken(c, startLine, startCol);
+                    } else {
+                        token = this.charToken(c, startLine, startCol);
+                    }
+                    break;
+
+                /*
+                 * Potential tokens:
+                 * - IMPORTANT_SYM
+                 * - CHAR
+                 */
+                case "!":
+                    token = this.importantToken(c, startLine, startCol);
+                    break;
+
+                /*
+                 * Any at-keyword or CHAR
+                 */
+                case "@":
+                    token = this.atRuleToken(c, startLine, startCol);
+                    break;
+
+                /*
+                 * Potential tokens:
+                 * - NOT
+                 * - CHAR
+                 */
+                case ":":
+                    token = this.notToken(c, startLine, startCol);
+                    break;
+
+                /*
+                 * Potential tokens:
+                 * - CDO
+                 * - CHAR
+                 */
+                case "<":
+                    token = this.htmlCommentStartToken(c, startLine, startCol);
+                    break;
+
+                /*
+                 * Potential tokens:
+                 * - UNICODE_RANGE
+                 * - URL
+                 * - CHAR
+                 */
+                case "U":
+                case "u":
+                    if (reader.peek() === "+"){
+                        token = this.unicodeRangeToken(c, startLine, startCol);
+                        break;
+                    }
+                    /* falls through */
+                default:
+
+                    /*
+                     * Potential tokens:
+                     * - NUMBER
+                     * - DIMENSION
+                     * - LENGTH
+                     * - FREQ
+                     * - TIME
+                     * - EMS
+                     * - EXS
+                     * - ANGLE
+                     */
+                    if (isDigit(c)){
+                        token = this.numberToken(c, startLine, startCol);
+                    } else
+
+                    /*
+                     * Potential tokens:
+                     * - S
+                     */
+                    if (isWhitespace(c)){
+                        token = this.whitespaceToken(c, startLine, startCol);
+                    } else
+
+                    /*
+                     * Potential tokens:
+                     * - IDENT
+                     */
+                    if (isIdentStart(c)){
+                        token = this.identOrFunctionToken(c, startLine, startCol);
+                    } else
+
+                    /*
+                     * Potential tokens:
+                     * - CHAR
+                     * - PLUS
+                     */
+                    {
+                        token = this.charToken(c, startLine, startCol);
+                    }
+
+
+
+
+
+
+            }
+
+            //make sure this token is wanted
+            //TODO: check channel
+            break;
+        }
+
+        if (!token && c === null){
+            token = this.createToken(Tokens.EOF,null,startLine,startCol);
+        }
+
+        return token;
+    },
+
+    //-------------------------------------------------------------------------
+    // Methods to create tokens
+    //-------------------------------------------------------------------------
+
+    /**
+     * Produces a token based on available data and the current
+     * reader position information. This method is called by other
+     * private methods to create tokens and is never called directly.
+     * @param {int} tt The token type.
+     * @param {String} value The text value of the token.
+     * @param {int} startLine The beginning line for the character.
+     * @param {int} startCol The beginning column for the character.
+     * @param {Object} options (Optional) Specifies a channel property
+     *      to indicate that a different channel should be scanned
+     *      and/or a hide property indicating that the token should
+     *      be hidden.
+     * @return {Object} A token object.
+     * @method createToken
+     */
+    createToken: function(tt, value, startLine, startCol, options){
+        var reader = this._reader;
+        options = options || {};
+
+        return {
+            value:      value,
+            type:       tt,
+            channel:    options.channel,
+            endChar:    options.endChar,
+            hide:       options.hide || false,
+            startLine:  startLine,
+            startCol:   startCol,
+            endLine:    reader.getLine(),
+            endCol:     reader.getCol()
+        };
+    },
+
+    //-------------------------------------------------------------------------
+    // Methods to create specific tokens
+    //-------------------------------------------------------------------------
+
+    /**
+     * Produces a token for any at-rule. If the at-rule is unknown, then
+     * the token is for a single "@" character.
+     * @param {String} first The first character for the token.
+     * @param {int} startLine The beginning line for the character.
+     * @param {int} startCol The beginning column for the character.
+     * @return {Object} A token object.
+     * @method atRuleToken
+     */
+    atRuleToken: function(first, startLine, startCol){
+        var rule    = first,
+            reader  = this._reader,
+            tt      = Tokens.CHAR,
+            ident;
+
+        /*
+         * First, mark where we are. There are only four @ rules,
+         * so anything else is really just an invalid token.
+         * Basically, if this doesn't match one of the known @
+         * rules, just return '@' as an unknown token and allow
+         * parsing to continue after that point.
+         */
+        reader.mark();
+
+        //try to find the at-keyword
+        ident = this.readName();
+        rule = first + ident;
+        tt = Tokens.type(rule.toLowerCase());
+
+        //if it's not valid, use the first character only and reset the reader
+        if (tt === Tokens.CHAR || tt === Tokens.UNKNOWN){
+            if (rule.length > 1){
+                tt = Tokens.UNKNOWN_SYM;
+            } else {
+                tt = Tokens.CHAR;
+                rule = first;
+                reader.reset();
+            }
+        }
+
+        return this.createToken(tt, rule, startLine, startCol);
+    },
+
+    /**
+     * Produces a character token based on the given character
+     * and location in the stream. If there's a special (non-standard)
+     * token name, this is used; otherwise CHAR is used.
+     * @param {String} c The character for the token.
+     * @param {int} startLine The beginning line for the character.
+     * @param {int} startCol The beginning column for the character.
+     * @return {Object} A token object.
+     * @method charToken
+     */
+    charToken: function(c, startLine, startCol){
+        var tt = Tokens.type(c);
+        var opts = {};
+
+        if (tt === -1){
+            tt = Tokens.CHAR;
+        } else {
+            opts.endChar = Tokens[tt].endChar;
+        }
+
+        return this.createToken(tt, c, startLine, startCol, opts);
+    },
+
+    /**
+     * Produces a character token based on the given character
+     * and location in the stream. If there's a special (non-standard)
+     * token name, this is used; otherwise CHAR is used.
+     * @param {String} first The first character for the token.
+     * @param {int} startLine The beginning line for the character.
+     * @param {int} startCol The beginning column for the character.
+     * @return {Object} A token object.
+     * @method commentToken
+     */
+    commentToken: function(first, startLine, startCol){
+        var comment = this.readComment(first);
+
+        return this.createToken(Tokens.COMMENT, comment, startLine, startCol);
+    },
+
+    /**
+     * Produces a comparison token based on the given character
+     * and location in the stream. The next character must be
+     * read and is already known to be an equals sign.
+     * @param {String} c The character for the token.
+     * @param {int} startLine The beginning line for the character.
+     * @param {int} startCol The beginning column for the character.
+     * @return {Object} A token object.
+     * @method comparisonToken
+     */
+    comparisonToken: function(c, startLine, startCol){
+        var reader  = this._reader,
+            comparison  = c + reader.read(),
+            tt      = Tokens.type(comparison) || Tokens.CHAR;
+
+        return this.createToken(tt, comparison, startLine, startCol);
+    },
+
+    /**
+     * Produces a hash token based on the specified information. The
+     * first character provided is the pound sign (#) and then this
+     * method reads a name afterward.
+     * @param {String} first The first character (#) in the hash name.
+     * @param {int} startLine The beginning line for the character.
+     * @param {int} startCol The beginning column for the character.
+     * @return {Object} A token object.
+     * @method hashToken
+     */
+    hashToken: function(first, startLine, startCol){
+        var name    = this.readName(first);
+
+        return this.createToken(Tokens.HASH, name, startLine, startCol);
+    },
+
+    /**
+     * Produces a CDO or CHAR token based on the specified information. The
+     * first character is provided and the rest is read by the function to determine
+     * the correct token to create.
+     * @param {String} first The first character in the token.
+     * @param {int} startLine The beginning line for the character.
+     * @param {int} startCol The beginning column for the character.
+     * @return {Object} A token object.
+     * @method htmlCommentStartToken
+     */
+    htmlCommentStartToken: function(first, startLine, startCol){
+        var reader      = this._reader,
+            text        = first;
+
+        reader.mark();
+        text += reader.readCount(3);
+
+        if (text === "<!--"){
+            return this.createToken(Tokens.CDO, text, startLine, startCol);
+        } else {
+            reader.reset();
+            return this.charToken(first, startLine, startCol);
+        }
+    },
+
+    /**
+     * Produces a CDC or CHAR token based on the specified information. The
+     * first character is provided and the rest is read by the function to determine
+     * the correct token to create.
+     * @param {String} first The first character in the token.
+     * @param {int} startLine The beginning line for the character.
+     * @param {int} startCol The beginning column for the character.
+     * @return {Object} A token object.
+     * @method htmlCommentEndToken
+     */
+    htmlCommentEndToken: function(first, startLine, startCol){
+        var reader      = this._reader,
+            text        = first;
+
+        reader.mark();
+        text += reader.readCount(2);
+
+        if (text === "-->"){
+            return this.createToken(Tokens.CDC, text, startLine, startCol);
+        } else {
+            reader.reset();
+            return this.charToken(first, startLine, startCol);
+        }
+    },
+
+    /**
+     * Produces an IDENT or FUNCTION token based on the specified information. The
+     * first character is provided and the rest is read by the function to determine
+     * the correct token to create.
+     * @param {String} first The first character in the identifier.
+     * @param {int} startLine The beginning line for the character.
+     * @param {int} startCol The beginning column for the character.
+     * @return {Object} A token object.
+     * @method identOrFunctionToken
+     */
+    identOrFunctionToken: function(first, startLine, startCol){
+        var reader  = this._reader,
+            ident   = this.readName(first),
+            tt      = Tokens.IDENT,
+            uriFns  = ["url(", "url-prefix(", "domain("];
+
+        //if there's a left paren immediately after, it's a URI or function
+        if (reader.peek() === "("){
+            ident += reader.read();
+            if (uriFns.indexOf(ident.toLowerCase()) > -1){
+                tt = Tokens.URI;
+                ident = this.readURI(ident);
+
+                //didn't find a valid URL or there's no closing paren
+                if (uriFns.indexOf(ident.toLowerCase()) > -1){
+                    tt = Tokens.FUNCTION;
+                }
+            } else {
+                tt = Tokens.FUNCTION;
+            }
+        } else if (reader.peek() === ":"){  //might be an IE function
+
+            //IE-specific functions always being with progid:
+            if (ident.toLowerCase() === "progid"){
+                ident += reader.readTo("(");
+                tt = Tokens.IE_FUNCTION;
+            }
+        }
+
+        return this.createToken(tt, ident, startLine, startCol);
+    },
+
+    /**
+     * Produces an IMPORTANT_SYM or CHAR token based on the specified information. The
+     * first character is provided and the rest is read by the function to determine
+     * the correct token to create.
+     * @param {String} first The first character in the token.
+     * @param {int} startLine The beginning line for the character.
+     * @param {int} startCol The beginning column for the character.
+     * @return {Object} A token object.
+     * @method importantToken
+     */
+    importantToken: function(first, startLine, startCol){
+        var reader      = this._reader,
+            important   = first,
+            tt          = Tokens.CHAR,
+            temp,
+            c;
+
+        reader.mark();
+        c = reader.read();
+
+        while(c){
+
+            //there can be a comment in here
+            if (c === "/"){
+
+                //if the next character isn't a star, then this isn't a valid !important token
+                if (reader.peek() !== "*"){
+                    break;
+                } else {
+                    temp = this.readComment(c);
+                    if (temp === ""){    //broken!
+                        break;
+                    }
+                }
+            } else if (isWhitespace(c)){
+                important += c + this.readWhitespace();
+            } else if (/i/i.test(c)){
+                temp = reader.readCount(8);
+                if (/mportant/i.test(temp)){
+                    important += c + temp;
+                    tt = Tokens.IMPORTANT_SYM;
+
+                }
+                break;  //we're done
+            } else {
+                break;
+            }
+
+            c = reader.read();
+        }
+
+        if (tt === Tokens.CHAR){
+            reader.reset();
+            return this.charToken(first, startLine, startCol);
+        } else {
+            return this.createToken(tt, important, startLine, startCol);
+        }
+
+
+    },
+
+    /**
+     * Produces a NOT or CHAR token based on the specified information. The
+     * first character is provided and the rest is read by the function to determine
+     * the correct token to create.
+     * @param {String} first The first character in the token.
+     * @param {int} startLine The beginning line for the character.
+     * @param {int} startCol The beginning column for the character.
+     * @return {Object} A token object.
+     * @method notToken
+     */
+    notToken: function(first, startLine, startCol){
+        var reader      = this._reader,
+            text        = first;
+
+        reader.mark();
+        text += reader.readCount(4);
+
+        if (text.toLowerCase() === ":not("){
+            return this.createToken(Tokens.NOT, text, startLine, startCol);
+        } else {
+            reader.reset();
+            return this.charToken(first, startLine, startCol);
+        }
+    },
+
+    /**
+     * Produces a number token based on the given character
+     * and location in the stream. This may return a token of
+     * NUMBER, EMS, EXS, LENGTH, ANGLE, TIME, FREQ, DIMENSION,
+     * or PERCENTAGE.
+     * @param {String} first The first character for the token.
+     * @param {int} startLine The beginning line for the character.
+     * @param {int} startCol The beginning column for the character.
+     * @return {Object} A token object.
+     * @method numberToken
+     */
+    numberToken: function(first, startLine, startCol){
+        var reader  = this._reader,
+            value   = this.readNumber(first),
+            ident,
+            tt      = Tokens.NUMBER,
+            c       = reader.peek();
+
+        if (isIdentStart(c)){
+            ident = this.readName(reader.read());
+            value += ident;
+
+            if (/^em$|^ex$|^px$|^gd$|^rem$|^vw$|^vh$|^vmax$|^vmin$|^ch$|^cm$|^mm$|^in$|^pt$|^pc$/i.test(ident)){
+                tt = Tokens.LENGTH;
+            } else if (/^deg|^rad$|^grad$/i.test(ident)){
+                tt = Tokens.ANGLE;
+            } else if (/^ms$|^s$/i.test(ident)){
+                tt = Tokens.TIME;
+            } else if (/^hz$|^khz$/i.test(ident)){
+                tt = Tokens.FREQ;
+            } else if (/^dpi$|^dpcm$/i.test(ident)){
+                tt = Tokens.RESOLUTION;
+            } else {
+                tt = Tokens.DIMENSION;
+            }
+
+        } else if (c === "%"){
+            value += reader.read();
+            tt = Tokens.PERCENTAGE;
+        }
+
+        return this.createToken(tt, value, startLine, startCol);
+    },
+
+    /**
+     * Produces a string token based on the given character
+     * and location in the stream. Since strings may be indicated
+     * by single or double quotes, a failure to match starting
+     * and ending quotes results in an INVALID token being generated.
+     * The first character in the string is passed in and then
+     * the rest are read up to and including the final quotation mark.
+     * @param {String} first The first character in the string.
+     * @param {int} startLine The beginning line for the character.
+     * @param {int} startCol The beginning column for the character.
+     * @return {Object} A token object.
+     * @method stringToken
+     */
+    stringToken: function(first, startLine, startCol){
+        var delim   = first,
+            string  = first,
+            reader  = this._reader,
+            prev    = first,
+            tt      = Tokens.STRING,
+            c       = reader.read();
+
+        while(c){
+            string += c;
+
+            //if the delimiter is found with an escapement, we're done.
+            if (c === delim && prev !== "\\"){
+                break;
+            }
+
+            //if there's a newline without an escapement, it's an invalid string
+            if (isNewLine(reader.peek()) && c !== "\\"){
+                tt = Tokens.INVALID;
+                break;
+            }
+
+            //save previous and get next
+            prev = c;
+            c = reader.read();
+        }
+
+        //if c is null, that means we're out of input and the string was never closed
+        if (c === null){
+            tt = Tokens.INVALID;
+        }
+
+        return this.createToken(tt, string, startLine, startCol);
+    },
+
+    unicodeRangeToken: function(first, startLine, startCol){
+        var reader  = this._reader,
+            value   = first,
+            temp,
+            tt      = Tokens.CHAR;
+
+        //then it should be a unicode range
+        if (reader.peek() === "+"){
+            reader.mark();
+            value += reader.read();
+            value += this.readUnicodeRangePart(true);
+
+            //ensure there's an actual unicode range here
+            if (value.length === 2){
+                reader.reset();
+            } else {
+
+                tt = Tokens.UNICODE_RANGE;
+
+                //if there's a ? in the first part, there can't be a second part
+                if (value.indexOf("?") === -1){
+
+                    if (reader.peek() === "-"){
+                        reader.mark();
+                        temp = reader.read();
+                        temp += this.readUnicodeRangePart(false);
+
+                        //if there's not another value, back up and just take the first
+                        if (temp.length === 1){
+                            reader.reset();
+                        } else {
+                            value += temp;
+                        }
+                    }
+
+                }
+            }
+        }
+
+        return this.createToken(tt, value, startLine, startCol);
+    },
+
+    /**
+     * Produces a S token based on the specified information. Since whitespace
+     * may have multiple characters, this consumes all whitespace characters
+     * into a single token.
+     * @param {String} first The first character in the token.
+     * @param {int} startLine The beginning line for the character.
+     * @param {int} startCol The beginning column for the character.
+     * @return {Object} A token object.
+     * @method whitespaceToken
+     */
+    whitespaceToken: function(first, startLine, startCol){
+        var value   = first + this.readWhitespace();
+        return this.createToken(Tokens.S, value, startLine, startCol);
+    },
+
+
+
+
+    //-------------------------------------------------------------------------
+    // Methods to read values from the string stream
+    //-------------------------------------------------------------------------
+
+    readUnicodeRangePart: function(allowQuestionMark){
+        var reader  = this._reader,
+            part = "",
+            c       = reader.peek();
+
+        //first read hex digits
+        while(isHexDigit(c) && part.length < 6){
+            reader.read();
+            part += c;
+            c = reader.peek();
+        }
+
+        //then read question marks if allowed
+        if (allowQuestionMark){
+            while(c === "?" && part.length < 6){
+                reader.read();
+                part += c;
+                c = reader.peek();
+            }
+        }
+
+        //there can't be any other characters after this point
+
+        return part;
+    },
+
+    readWhitespace: function(){
+        var reader  = this._reader,
+            whitespace = "",
+            c       = reader.peek();
+
+        while(isWhitespace(c)){
+            reader.read();
+            whitespace += c;
+            c = reader.peek();
+        }
+
+        return whitespace;
+    },
+    readNumber: function(first){
+        var reader  = this._reader,
+            number  = first,
+            hasDot  = (first === "."),
+            c       = reader.peek();
+
+
+        while(c){
+            if (isDigit(c)){
+                number += reader.read();
+            } else if (c === "."){
+                if (hasDot){
+                    break;
+                } else {
+                    hasDot = true;
+                    number += reader.read();
+                }
+            } else {
+                break;
+            }
+
+            c = reader.peek();
+        }
+
+        return number;
+    },
+    readString: function(){
+        var reader  = this._reader,
+            delim   = reader.read(),
+            string  = delim,
+            prev    = delim,
+            c       = reader.peek();
+
+        while(c){
+            c = reader.read();
+            string += c;
+
+            //if the delimiter is found with an escapement, we're done.
+            if (c === delim && prev !== "\\"){
+                break;
+            }
+
+            //if there's a newline without an escapement, it's an invalid string
+            if (isNewLine(reader.peek()) && c !== "\\"){
+                string = "";
+                break;
+            }
+
+            //save previous and get next
+            prev = c;
+            c = reader.peek();
+        }
+
+        //if c is null, that means we're out of input and the string was never closed
+        if (c === null){
+            string = "";
+        }
+
+        return string;
+    },
+    readURI: function(first){
+        var reader  = this._reader,
+            uri     = first,
+            inner   = "",
+            c       = reader.peek();
+
+        reader.mark();
+
+        //skip whitespace before
+        while(c && isWhitespace(c)){
+            reader.read();
+            c = reader.peek();
+        }
+
+        //it's a string
+        if (c === "'" || c === "\""){
+            inner = this.readString();
+        } else {
+            inner = this.readURL();
+        }
+
+        c = reader.peek();
+
+        //skip whitespace after
+        while(c && isWhitespace(c)){
+            reader.read();
+            c = reader.peek();
+        }
+
+        //if there was no inner value or the next character isn't closing paren, it's not a URI
+        if (inner === "" || c !== ")"){
+            uri = first;
+            reader.reset();
+        } else {
+            uri += inner + reader.read();
+        }
+
+        return uri;
+    },
+    readURL: function(){
+        var reader  = this._reader,
+            url     = "",
+            c       = reader.peek();
+
+        //TODO: Check for escape and nonascii
+        while (/^[!#$%&\\*-~]$/.test(c)){
+            url += reader.read();
+            c = reader.peek();
+        }
+
+        return url;
+
+    },
+    readName: function(first){
+        var reader  = this._reader,
+            ident   = first || "",
+            c       = reader.peek();
+
+        while(true){
+            if (c === "\\"){
+                ident += this.readEscape(reader.read());
+                c = reader.peek();
+            } else if(c && isNameChar(c)){
+                ident += reader.read();
+                c = reader.peek();
+            } else {
+                break;
+            }
+        }
+
+        return ident;
+    },
+
+    readEscape: function(first){
+        var reader  = this._reader,
+            cssEscape = first || "",
+            i       = 0,
+            c       = reader.peek();
+
+        if (isHexDigit(c)){
+            do {
+                cssEscape += reader.read();
+                c = reader.peek();
+            } while(c && isHexDigit(c) && ++i < 6);
+        }
+
+        if (cssEscape.length === 3 && /\s/.test(c) ||
+            cssEscape.length === 7 || cssEscape.length === 1){
+                reader.read();
+        } else {
+            c = "";
+        }
+
+        return cssEscape + c;
+    },
+
+    readComment: function(first){
+        var reader  = this._reader,
+            comment = first || "",
+            c       = reader.read();
+
+        if (c === "*"){
+            while(c){
+                comment += c;
+
+                //look for end of comment
+                if (comment.length > 2 && c === "*" && reader.peek() === "/"){
+                    comment += reader.read();
+                    break;
+                }
+
+                c = reader.read();
+            }
+
+            return comment;
+        } else {
+            return "";
+        }
+
+    }
+});
+
+var Tokens  = [
+
+    /*
+     * The following token names are defined in CSS3 Grammar: http://www.w3.org/TR/css3-syntax/#lexical
+     */
+
+    //HTML-style comments
+    { name: "CDO"},
+    { name: "CDC"},
+
+    //ignorables
+    { name: "S", whitespace: true/*, channel: "ws"*/},
+    { name: "COMMENT", comment: true, hide: true, channel: "comment" },
+
+    //attribute equality
+    { name: "INCLUDES", text: "~="},
+    { name: "DASHMATCH", text: "|="},
+    { name: "PREFIXMATCH", text: "^="},
+    { name: "SUFFIXMATCH", text: "$="},
+    { name: "SUBSTRINGMATCH", text: "*="},
+
+    //identifier types
+    { name: "STRING"},
+    { name: "IDENT"},
+    { name: "HASH"},
+
+    //at-keywords
+    { name: "IMPORT_SYM", text: "@import"},
+    { name: "PAGE_SYM", text: "@page"},
+    { name: "MEDIA_SYM", text: "@media"},
+    { name: "FONT_FACE_SYM", text: "@font-face"},
+    { name: "CHARSET_SYM", text: "@charset"},
+    { name: "NAMESPACE_SYM", text: "@namespace"},
+    { name: "VIEWPORT_SYM", text: ["@viewport", "@-ms-viewport", "@-o-viewport"]},
+    { name: "DOCUMENT_SYM", text: ["@document", "@-moz-document"]},
+    { name: "UNKNOWN_SYM" },
+    //{ name: "ATKEYWORD"},
+
+    //CSS3 animations
+    { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes", "@-o-keyframes" ] },
+
+    //important symbol
+    { name: "IMPORTANT_SYM"},
+
+    //measurements
+    { name: "LENGTH"},
+    { name: "ANGLE"},
+    { name: "TIME"},
+    { name: "FREQ"},
+    { name: "DIMENSION"},
+    { name: "PERCENTAGE"},
+    { name: "NUMBER"},
+
+    //functions
+    { name: "URI"},
+    { name: "FUNCTION"},
+
+    //Unicode ranges
+    { name: "UNICODE_RANGE"},
+
+    /*
+     * The following token names are defined in CSS3 Selectors: http://www.w3.org/TR/css3-selectors/#selector-syntax
+     */
+
+    //invalid string
+    { name: "INVALID"},
+
+    //combinators
+    { name: "PLUS", text: "+" },
+    { name: "GREATER", text: ">"},
+    { name: "COMMA", text: ","},
+    { name: "TILDE", text: "~"},
+
+    //modifier
+    { name: "NOT"},
+
+    /*
+     * Defined in CSS3 Paged Media
+     */
+    { name: "TOPLEFTCORNER_SYM", text: "@top-left-corner"},
+    { name: "TOPLEFT_SYM", text: "@top-left"},
+    { name: "TOPCENTER_SYM", text: "@top-center"},
+    { name: "TOPRIGHT_SYM", text: "@top-right"},
+    { name: "TOPRIGHTCORNER_SYM", text: "@top-right-corner"},
+    { name: "BOTTOMLEFTCORNER_SYM", text: "@bottom-left-corner"},
+    { name: "BOTTOMLEFT_SYM", text: "@bottom-left"},
+    { name: "BOTTOMCENTER_SYM", text: "@bottom-center"},
+    { name: "BOTTOMRIGHT_SYM", text: "@bottom-right"},
+    { name: "BOTTOMRIGHTCORNER_SYM", text: "@bottom-right-corner"},
+    { name: "LEFTTOP_SYM", text: "@left-top"},
+    { name: "LEFTMIDDLE_SYM", text: "@left-middle"},
+    { name: "LEFTBOTTOM_SYM", text: "@left-bottom"},
+    { name: "RIGHTTOP_SYM", text: "@right-top"},
+    { name: "RIGHTMIDDLE_SYM", text: "@right-middle"},
+    { name: "RIGHTBOTTOM_SYM", text: "@right-bottom"},
+
+    /*
+     * The following token names are defined in CSS3 Media Queries: http://www.w3.org/TR/css3-mediaqueries/#syntax
+     */
+    /*{ name: "MEDIA_ONLY", state: "media"},
+    { name: "MEDIA_NOT", state: "media"},
+    { name: "MEDIA_AND", state: "media"},*/
+    { name: "RESOLUTION", state: "media"},
+
+    /*
+     * The following token names are not defined in any CSS specification but are used by the lexer.
+     */
+
+    //not a real token, but useful for stupid IE filters
+    { name: "IE_FUNCTION" },
+
+    //part of CSS3 grammar but not the Flex code
+    { name: "CHAR" },
+
+    //TODO: Needed?
+    //Not defined as tokens, but might as well be
+    {
+        name: "PIPE",
+        text: "|"
+    },
+    {
+        name: "SLASH",
+        text: "/"
+    },
+    {
+        name: "MINUS",
+        text: "-"
+    },
+    {
+        name: "STAR",
+        text: "*"
+    },
+
+    {
+        name: "LBRACE",
+        endChar: "}",
+        text: "{"
+    },
+    {
+        name: "RBRACE",
+        text: "}"
+    },
+    {
+        name: "LBRACKET",
+        endChar: "]",
+        text: "["
+    },
+    {
+        name: "RBRACKET",
+        text: "]"
+    },
+    {
+        name: "EQUALS",
+        text: "="
+    },
+    {
+        name: "COLON",
+        text: ":"
+    },
+    {
+        name: "SEMICOLON",
+        text: ";"
+    },
+
+    {
+        name: "LPAREN",
+        endChar: ")",
+        text: "("
+    },
+    {
+        name: "RPAREN",
+        text: ")"
+    },
+    {
+        name: "DOT",
+        text: "."
+    }
+];
+
+(function(){
+
+    var nameMap = [],
+        typeMap = Object.create(null);
+
+    Tokens.UNKNOWN = -1;
+    Tokens.unshift({name:"EOF"});
+    for (var i=0, len = Tokens.length; i < len; i++){
+        nameMap.push(Tokens[i].name);
+        Tokens[Tokens[i].name] = i;
+        if (Tokens[i].text){
+            if (Tokens[i].text instanceof Array){
+                for (var j=0; j < Tokens[i].text.length; j++){
+                    typeMap[Tokens[i].text[j]] = i;
+                }
+            } else {
+                typeMap[Tokens[i].text] = i;
+            }
+        }
+    }
+
+    Tokens.name = function(tt){
+        return nameMap[tt];
+    };
+
+    Tokens.type = function(c){
+        return typeMap[c] || -1;
+    };
+
+})();
+
+
+
+//This file will likely change a lot! Very experimental!
+var Validation = {
+
+    validate: function(property, value){
+
+        //normalize name
+        var name        = property.toString().toLowerCase(),
+            expression  = new PropertyValueIterator(value),
+            spec        = Properties[name];
+
+        if (!spec) {
+            if (name.indexOf("-") !== 0){    //vendor prefixed are ok
+                throw new ValidationError("Unknown property '" + property + "'.", property.line, property.col);
+            }
+        } else if (typeof spec !== "number"){
+
+            //initialization
+            if (typeof spec === "string"){
+                if (spec.indexOf("||") > -1) {
+                    this.groupProperty(spec, expression);
+                } else {
+                    this.singleProperty(spec, expression, 1);
+                }
+
+            } else if (spec.multi) {
+                this.multiProperty(spec.multi, expression, spec.comma, spec.max || Infinity);
+            } else if (typeof spec === "function") {
+                spec(expression);
+            }
+
+        }
+
+    },
+
+    singleProperty: function(types, expression, max, partial) {
+
+        var result      = false,
+            value       = expression.value,
+            count       = 0,
+            part;
+
+        while (expression.hasNext() && count < max) {
+            result = ValidationTypes.isAny(expression, types);
+            if (!result) {
+                break;
+            }
+            count++;
+        }
+
+        if (!result) {
+            if (expression.hasNext() && !expression.isFirst()) {
+                part = expression.peek();
+                throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
+            } else {
+                 throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col);
+            }
+        } else if (expression.hasNext()) {
+            part = expression.next();
+            throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
+        }
+
+    },
+
+    multiProperty: function (types, expression, comma, max) {
+
+        var result      = false,
+            value       = expression.value,
+            count       = 0,
+            part;
+
+        while(expression.hasNext() && !result && count < max) {
+            if (ValidationTypes.isAny(expression, types)) {
+                count++;
+                if (!expression.hasNext()) {
+                    result = true;
+
+                } else if (comma) {
+                    if (String(expression.peek()) === ",") {
+                        part = expression.next();
+                    } else {
+                        break;
+                    }
+                }
+            } else {
+                break;
+
+            }
+        }
+
+        if (!result) {
+            if (expression.hasNext() && !expression.isFirst()) {
+                part = expression.peek();
+                throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
+            } else {
+                part = expression.previous();
+                if (comma && String(part) === ",") {
+                    throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
+                } else {
+                    throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col);
+                }
+            }
+
+        } else if (expression.hasNext()) {
+            part = expression.next();
+            throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
+        }
+
+    },
+
+    groupProperty: function (types, expression, comma) {
+
+        var result      = false,
+            value       = expression.value,
+            typeCount   = types.split("||").length,
+            groups      = { count: 0 },
+            partial     = false,
+            name,
+            part;
+
+        while(expression.hasNext() && !result) {
+            name = ValidationTypes.isAnyOfGroup(expression, types);
+            if (name) {
+
+                //no dupes
+                if (groups[name]) {
+                    break;
+                } else {
+                    groups[name] = 1;
+                    groups.count++;
+                    partial = true;
+
+                    if (groups.count === typeCount || !expression.hasNext()) {
+                        result = true;
+                    }
+                }
+            } else {
+                break;
+            }
+        }
+
+        if (!result) {
+            if (partial && expression.hasNext()) {
+                    part = expression.peek();
+                    throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
+            } else {
+                throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col);
+            }
+        } else if (expression.hasNext()) {
+            part = expression.next();
+            throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col);
+        }
+    }
+
+
+
+};
+/**
+ * Type to use when a validation error occurs.
+ * @class ValidationError
+ * @namespace parserlib.util
+ * @constructor
+ * @param {String} message The error message.
+ * @param {int} line The line at which the error occurred.
+ * @param {int} col The column at which the error occurred.
+ */
+function ValidationError(message, line, col){
+
+    /**
+     * The column at which the error occurred.
+     * @type int
+     * @property col
+     */
+    this.col = col;
+
+    /**
+     * The line at which the error occurred.
+     * @type int
+     * @property line
+     */
+    this.line = line;
+
+    /**
+     * The text representation of the unit.
+     * @type String
+     * @property text
+     */
+    this.message = message;
+
+}
+
+//inherit from Error
+ValidationError.prototype = new Error();
+//This file will likely change a lot! Very experimental!
+var ValidationTypes = {
+
+    isLiteral: function (part, literals) {
+        var text = part.text.toString().toLowerCase(),
+            args = literals.split(" | "),
+            i, len, found = false;
+
+        for (i=0,len=args.length; i < len && !found; i++){
+            if (text === args[i].toLowerCase()){
+                found = true;
+            }
+        }
+
+        return found;
+    },
+
+    isSimple: function(type) {
+        return !!this.simple[type];
+    },
+
+    isComplex: function(type) {
+        return !!this.complex[type];
+    },
+
+    /**
+     * Determines if the next part(s) of the given expression
+     * are any of the given types.
+     */
+    isAny: function (expression, types) {
+        var args = types.split(" | "),
+            i, len, found = false;
+
+        for (i=0,len=args.length; i < len && !found && expression.hasNext(); i++){
+            found = this.isType(expression, args[i]);
+        }
+
+        return found;
+    },
+
+    /**
+     * Determines if the next part(s) of the given expression
+     * are one of a group.
+     */
+    isAnyOfGroup: function(expression, types) {
+        var args = types.split(" || "),
+            i, len, found = false;
+
+        for (i=0,len=args.length; i < len && !found; i++){
+            found = this.isType(expression, args[i]);
+        }
+
+        return found ? args[i-1] : false;
+    },
+
+    /**
+     * Determines if the next part(s) of the given expression
+     * are of a given type.
+     */
+    isType: function (expression, type) {
+        var part = expression.peek(),
+            result = false;
+
+        if (type.charAt(0) !== "<") {
+            result = this.isLiteral(part, type);
+            if (result) {
+                expression.next();
+            }
+        } else if (this.simple[type]) {
+            result = this.simple[type](part);
+            if (result) {
+                expression.next();
+            }
+        } else {
+            result = this.complex[type](expression);
+        }
+
+        return result;
+    },
+
+
+
+    simple: {
+        __proto__: null,
+
+        "<absolute-size>": function(part){
+            return ValidationTypes.isLiteral(part, "xx-small | x-small | small | medium | large | x-large | xx-large");
+        },
+
+        "<attachment>": function(part){
+            return ValidationTypes.isLiteral(part, "scroll | fixed | local");
+        },
+
+        "<attr>": function(part){
+            return part.type === "function" && part.name === "attr";
+        },
+
+        "<bg-image>": function(part){
+            return this["<image>"](part) || this["<gradient>"](part) ||  String(part) === "none";
+        },
+
+        "<gradient>": function(part) {
+            return part.type === "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?(?:repeating\-)?(?:radial\-|linear\-)?gradient/i.test(part);
+        },
+
+        "<box>": function(part){
+            return ValidationTypes.isLiteral(part, "padding-box | border-box | content-box");
+        },
+
+        "<content>": function(part){
+            return part.type === "function" && part.name === "content";
+        },
+
+        "<relative-size>": function(part){
+            return ValidationTypes.isLiteral(part, "smaller | larger");
+        },
+
+        //any identifier
+        "<ident>": function(part){
+            return part.type === "identifier";
+        },
+
+        "<length>": function(part){
+            if (part.type === "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?calc/i.test(part)){
+                return true;
+            }else{
+                return part.type === "length" || part.type === "number" || part.type === "integer" || String(part) === "0";
+            }
+        },
+
+        "<color>": function(part){
+            return part.type === "color" || String(part) === "transparent" || String(part) === "currentColor";
+        },
+
+        "<number>": function(part){
+            return part.type === "number" || this["<integer>"](part);
+        },
+
+        "<integer>": function(part){
+            return part.type === "integer";
+        },
+
+        "<line>": function(part){
+            return part.type === "integer";
+        },
+
+        "<angle>": function(part){
+            return part.type === "angle";
+        },
+
+        "<uri>": function(part){
+            return part.type === "uri";
+        },
+
+        "<image>": function(part){
+            return this["<uri>"](part);
+        },
+
+        "<percentage>": function(part){
+            return part.type === "percentage" || String(part) === "0";
+        },
+
+        "<border-width>": function(part){
+            return this["<length>"](part) || ValidationTypes.isLiteral(part, "thin | medium | thick");
+        },
+
+        "<border-style>": function(part){
+            return ValidationTypes.isLiteral(part, "none | hidden | dotted | dashed | solid | double | groove | ridge | inset | outset");
+        },
+
+        "<content-sizing>": function(part){ // http://www.w3.org/TR/css3-sizing/#width-height-keywords
+            return ValidationTypes.isLiteral(part, "fill-available | -moz-available | -webkit-fill-available | max-content | -moz-max-content | -webkit-max-content | min-content | -moz-min-content | -webkit-min-content | fit-content | -moz-fit-content | -webkit-fit-content");
+        },
+
+        "<margin-width>": function(part){
+            return this["<length>"](part) || this["<percentage>"](part) || ValidationTypes.isLiteral(part, "auto");
+        },
+
+        "<padding-width>": function(part){
+            return this["<length>"](part) || this["<percentage>"](part);
+        },
+
+        "<shape>": function(part){
+            return part.type === "function" && (part.name === "rect" || part.name === "inset-rect");
+        },
+
+        "<time>": function(part) {
+            return part.type === "time";
+        },
+
+        "<flex-grow>": function(part){
+            return this["<number>"](part);
+        },
+
+        "<flex-shrink>": function(part){
+            return this["<number>"](part);
+        },
+
+        "<width>": function(part){
+            return this["<margin-width>"](part);
+        },
+
+        "<flex-basis>": function(part){
+            return this["<width>"](part);
+        },
+
+        "<flex-direction>": function(part){
+            return ValidationTypes.isLiteral(part, "row | row-reverse | column | column-reverse");
+        },
+
+        "<flex-wrap>": function(part){
+            return ValidationTypes.isLiteral(part, "nowrap | wrap | wrap-reverse");
+        },
+
+        "<feature-tag-value>": function(part){
+            return (part.type === "function" && /^[A-Z0-9]{4}$/i.test(part));
+        }
+    },
+
+    complex: {
+        __proto__: null,
+
+        "<bg-position>": function(expression){
+            var result  = false,
+                numeric = "<percentage> | <length>",
+                xDir    = "left | right",
+                yDir    = "top | bottom",
+                count = 0;
+
+            while (expression.peek(count) && expression.peek(count).text !== ",") {
+                count++;
+            }
+
+/*
+<position> = [
+  [ left | center | right | top | bottom | <percentage> | <length> ]
+|
+  [ left | center | right | <percentage> | <length> ]
+  [ top | center | bottom | <percentage> | <length> ]
+|
+  [ center | [ left | right ] [ <percentage> | <length> ]? ] &&
+  [ center | [ top | bottom ] [ <percentage> | <length> ]? ]
+]
+*/
+
+            if (count < 3) {
+                if (ValidationTypes.isAny(expression, xDir + " | center | " + numeric)) {
+                        result = true;
+                        ValidationTypes.isAny(expression, yDir + " | center | " + numeric);
+                } else if (ValidationTypes.isAny(expression, yDir)) {
+                        result = true;
+                        ValidationTypes.isAny(expression, xDir + " | center");
+                }
+            } else {
+                if (ValidationTypes.isAny(expression, xDir)) {
+                    if (ValidationTypes.isAny(expression, yDir)) {
+                        result = true;
+                        ValidationTypes.isAny(expression, numeric);
+                    } else if (ValidationTypes.isAny(expression, numeric)) {
+                        if (ValidationTypes.isAny(expression, yDir)) {
+                            result = true;
+                            ValidationTypes.isAny(expression, numeric);
+                        } else if (ValidationTypes.isAny(expression, "center")) {
+                            result = true;
+                        }
+                    }
+                } else if (ValidationTypes.isAny(expression, yDir)) {
+                    if (ValidationTypes.isAny(expression, xDir)) {
+                        result = true;
+                        ValidationTypes.isAny(expression, numeric);
+                    } else if (ValidationTypes.isAny(expression, numeric)) {
+                        if (ValidationTypes.isAny(expression, xDir)) {
+                                result = true;
+                                ValidationTypes.isAny(expression, numeric);
+                        } else if (ValidationTypes.isAny(expression, "center")) {
+                            result = true;
+                        }
+                    }
+                } else if (ValidationTypes.isAny(expression, "center")) {
+                    if (ValidationTypes.isAny(expression, xDir + " | " + yDir)) {
+                        result = true;
+                        ValidationTypes.isAny(expression, numeric);
+                    }
+                }
+            }
+
+            return result;
+        },
+
+        "<bg-size>": function(expression){
+            //<bg-size> = [ <length> | <percentage> | auto ]{1,2} | cover | contain
+            var result  = false,
+                numeric = "<percentage> | <length> | auto";
+
+            if (ValidationTypes.isAny(expression, "cover | contain")) {
+                result = true;
+            } else if (ValidationTypes.isAny(expression, numeric)) {
+                result = true;
+                ValidationTypes.isAny(expression, numeric);
+            }
+
+            return result;
+        },
+
+        "<repeat-style>": function(expression){
+            //repeat-x | repeat-y | [repeat | space | round | no-repeat]{1,2}
+            var result  = false,
+                values  = "repeat | space | round | no-repeat",
+                part;
+
+            if (expression.hasNext()){
+                part = expression.next();
+
+                if (ValidationTypes.isLiteral(part, "repeat-x | repeat-y")) {
+                    result = true;
+                } else if (ValidationTypes.isLiteral(part, values)) {
+                    result = true;
+
+                    if (expression.hasNext() && ValidationTypes.isLiteral(expression.peek(), values)) {
+                        expression.next();
+                    }
+                }
+            }
+
+            return result;
+
+        },
+
+        "<shadow>": function(expression) {
+            //inset? && [ <length>{2,4} && <color>? ]
+            var result  = false,
+                count   = 0,
+                inset   = false,
+                color   = false;
+
+            if (expression.hasNext()) {
+
+                if (ValidationTypes.isAny(expression, "inset")){
+                    inset = true;
+                }
+
+                if (ValidationTypes.isAny(expression, "<color>")) {
+                    color = true;
+                }
+
+                while (ValidationTypes.isAny(expression, "<length>") && count < 4) {
+                    count++;
+                }
+
+
+                if (expression.hasNext()) {
+                    if (!color) {
+                        ValidationTypes.isAny(expression, "<color>");
+                    }
+
+                    if (!inset) {
+                        ValidationTypes.isAny(expression, "inset");
+                    }
+
+                }
+
+                result = (count >= 2 && count <= 4);
+
+            }
+
+            return result;
+        },
+
+        "<x-one-radius>": function(expression) {
+            //[ <length> | <percentage> ] [ <length> | <percentage> ]?
+            var result  = false,
+                simple = "<length> | <percentage> | inherit";
+
+            if (ValidationTypes.isAny(expression, simple)){
+                result = true;
+                ValidationTypes.isAny(expression, simple);
+            }
+
+            return result;
+        },
+
+        "<flex>": function(expression) {
+            // http://www.w3.org/TR/2014/WD-css-flexbox-1-20140325/#flex-property
+            // none | [ <flex-grow> <flex-shrink>? || <flex-basis> ]
+            // Valid syntaxes, according to https://developer.mozilla.org/en-US/docs/Web/CSS/flex#Syntax
+            // * none
+            // * <flex-grow>
+            // * <flex-basis>
+            // * <flex-grow> <flex-basis>
+            // * <flex-grow> <flex-shrink>
+            // * <flex-grow> <flex-shrink> <flex-basis>
+            // * inherit
+            var part,
+                result = false;
+            if (ValidationTypes.isAny(expression, "none | inherit")) {
+                result = true;
+            } else {
+                if (ValidationTypes.isType(expression, "<flex-grow>")) {
+                    if (expression.peek()) {
+                        if (ValidationTypes.isType(expression, "<flex-shrink>")) {
+                            if (expression.peek()) {
+                                result = ValidationTypes.isType(expression, "<flex-basis>");
+                            } else {
+                                result = true;
+                            }
+                        } else if (ValidationTypes.isType(expression, "<flex-basis>")) {
+                            result = expression.peek() === null;
+                        }
+                    } else {
+                        result = true;
+                    }
+                } else if (ValidationTypes.isType(expression, "<flex-basis>")) {
+                    result = true;
+                }
+            }
+
+            if (!result) {
+                // Generate a more verbose error than "Expected <flex>..."
+                part = expression.peek();
+                throw new ValidationError("Expected (none | [ <flex-grow> <flex-shrink>? || <flex-basis> ]) but found '" + expression.value.text + "'.", part.line, part.col);
+            }
+
+            return result;
+        }
+    }
+};
+
+parserlib.css = {
+__proto__           :null,
+Colors              :Colors,
+Combinator          :Combinator,
+Parser              :Parser,
+PropertyName        :PropertyName,
+PropertyValue       :PropertyValue,
+PropertyValuePart   :PropertyValuePart,
+MediaFeature        :MediaFeature,
+MediaQuery          :MediaQuery,
+Selector            :Selector,
+SelectorPart        :SelectorPart,
+SelectorSubPart     :SelectorSubPart,
+Specificity         :Specificity,
+TokenStream         :TokenStream,
+Tokens              :Tokens,
+ValidationError     :ValidationError
+};
+})();
+
+(function(){
+/* jshint forin:false */
+for(var prop in parserlib){
+exports[prop] = parserlib[prop];
+}
+})();
--- /dev/null
+++ b/domino-lib/defineElement.js
@@ -1,0 +1,70 @@
+"use strict";
+
+var attributes = require('./attributes');
+var sloppy = require('./sloppy');
+var isApiWritable = require("./config").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);
+  });
+}
--- /dev/null
+++ b/domino-lib/events.js
@@ -1,0 +1,7 @@
+"use strict";
+module.exports = {
+  Event: require('./Event'),
+  UIEvent: require('./UIEvent'),
+  MouseEvent: require('./MouseEvent'),
+  CustomEvent: require('./CustomEvent')
+};
--- /dev/null
+++ b/domino-lib/htmlelts.js
@@ -1,0 +1,1426 @@
+"use strict";
+var Node = require('./Node');
+var Element = require('./Element');
+var CSSStyleDeclaration = require('./CSSStyleDeclaration');
+var utils = require('./utils');
+var URLUtils = require('./URLUtils');
+var defineElement = require('./defineElement');
+
+var htmlElements = exports.elements = {};
+var htmlNameToImpl = Object.create(null);
+
+exports.createElement = function(doc, localName, prefix) {
+  var impl = htmlNameToImpl[localName] || HTMLUnknownElement;
+  return new impl(doc, localName, prefix);
+};
+
+function define(spec) {
+  return defineElement(spec, HTMLElement, htmlElements, htmlNameToImpl);
+}
+
+function URL(attr) {
+  return {
+    get: function() {
+      var v = this._getattr(attr);
+      if (v === null) { return ''; }
+      var url = this.doc._resolve(v);
+      return (url === null) ? v : url;
+    },
+    set: function(value) {
+      this._setattr(attr, value);
+    }
+  };
+}
+
+function CORS(attr) {
+  return {
+    get: function() {
+      var v = this._getattr(attr);
+      if (v === null) { return null; }
+      if (v.toLowerCase() === 'use-credentials') { return 'use-credentials'; }
+      return 'anonymous';
+    },
+    set: function(value) {
+      if (value===null || value===undefined) {
+        this.removeAttribute(attr);
+      } else {
+        this._setattr(attr, value);
+      }
+    }
+  };
+}
+
+var REFERRER = {
+  type: ["", "no-referrer", "no-referrer-when-downgrade", "same-origin", "origin", "strict-origin", "origin-when-cross-origin", "strict-origin-when-cross-origin", "unsafe-url"],
+  missing: '',
+};
+
+
+// XXX: the default value for tabIndex should be 0 if the element is
+// focusable and -1 if it is not.  But the full definition of focusable
+// is actually hard to compute, so for now, I'll follow Firefox and
+// just base the default value on the type of the element.
+var focusableElements = {
+  "A":true, "LINK":true, "BUTTON":true, "INPUT":true,
+  "SELECT":true, "TEXTAREA":true, "COMMAND":true
+};
+
+var HTMLFormElement = function(doc, localName, prefix) {
+  HTMLElement.call(this, doc, localName, prefix);
+  this._form = null; // Prevent later deoptimization
+};
+
+var HTMLElement = exports.HTMLElement = define({
+  superclass: Element,
+  ctor: function HTMLElement(doc, localName, prefix) {
+    Element.call(this, doc, localName, utils.NAMESPACE.HTML, prefix);
+  },
+  props: {
+    innerHTML: {
+      get: function() {
+        return this.serialize();
+      },
+      set: function(v) {
+        var parser = this.ownerDocument.implementation.mozHTMLParser(
+          this.ownerDocument._address,
+          this);
+        parser.parse(v===null ? '' : String(v), true);
+
+        // Remove any existing children of this node
+        var target = (this instanceof htmlNameToImpl.template) ?
+            this.content : this;
+        while(target.hasChildNodes())
+          target.removeChild(target.firstChild);
+
+        // Now copy newly parsed children to this node
+        target.appendChild(parser._asDocumentFragment());
+      }
+    },
+    style: { get: function() {
+      if (!this._style)
+        this._style = new CSSStyleDeclaration(this);
+      return this._style;
+    }, set: function(v) {
+        if (v===null||v===undefined) { v = ''; }
+        this._setattr('style', String(v));
+    }},
+
+    // These can't really be implemented server-side in a reasonable way.
+    blur: { value: function() {}},
+    focus: { value: function() {}},
+    forceSpellCheck: { value: function() {}},
+
+    click: { value: function() {
+      if (this._click_in_progress) return;
+      this._click_in_progress = true;
+      try {
+        if (this._pre_click_activation_steps)
+          this._pre_click_activation_steps();
+
+        var event = this.ownerDocument.createEvent("MouseEvent");
+        event.initMouseEvent("click", true, true,
+          this.ownerDocument.defaultView, 1,
+          0, 0, 0, 0,
+          // These 4 should be initialized with
+          // the actually current keyboard state
+          // somehow...
+          false, false, false, false,
+          0, null
+        );
+
+        // Dispatch this as an untrusted event since it is synthetic
+        var success = this.dispatchEvent(event);
+
+        if (success) {
+          if (this._post_click_activation_steps)
+            this._post_click_activation_steps(event);
+        }
+        else {
+          if (this._cancelled_activation_steps)
+            this._cancelled_activation_steps();
+        }
+      }
+      finally {
+        this._click_in_progress = false;
+      }
+    }},
+    submit: { value: utils.nyi },
+  },
+  attributes: {
+    title: String,
+    lang: String,
+    dir: {type: ["ltr", "rtl", "auto"], missing: ''},
+    accessKey: String,
+    hidden: Boolean,
+    tabIndex: {type: "long", default: function() {
+      if (this.tagName in focusableElements ||
+        this.contentEditable)
+        return 0;
+      else
+        return -1;
+    }}
+  },
+  events: [
+    "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",
+
+    // These last 5 event types will be overriden by HTMLBodyElement
+    "blur", "error", "focus", "load", "scroll"
+  ]
+});
+
+
+// XXX: reflect contextmenu as contextMenu, with element type
+
+
+// style: the spec doesn't call this a reflected attribute.
+//   may want to handle it manually.
+
+// contentEditable: enumerated, not clear if it is actually
+// reflected or requires custom getter/setter. Not listed as
+// "limited to known values".  Raises syntax_err on bad setting,
+// so I think this is custom.
+
+// contextmenu: content is element id, idl type is an element
+// draggable: boolean, but not a reflected attribute
+// dropzone: reflected SettableTokenList, experimental, so don't
+//   implement it right away.
+
+// data-* attributes: need special handling in setAttribute?
+// Or maybe that isn't necessary. Can I just scan the attribute list
+// when building the dataset?  Liveness and caching issues?
+
+// microdata attributes: many are simple reflected attributes, but
+// I'm not going to implement this now.
+
+
+var HTMLUnknownElement = define({
+  ctor: function HTMLUnknownElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  }
+});
+
+
+var formAssociatedProps = {
+  // See http://www.w3.org/TR/html5/association-of-controls-and-forms.html#form-owner
+  form: { get: function() {
+    return this._form;
+  }}
+};
+
+define({
+  tag: 'a',
+  ctor: function HTMLAnchorElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  props: {
+    _post_click_activation_steps: { value: function(e) {
+      if (this.href) {
+        // Follow the link
+        // XXX: this is just a quick hack
+        // XXX: the HTML spec probably requires more than this
+        this.ownerDocument.defaultView.location = this.href;
+      }
+    }},
+  },
+  attributes: {
+    href: URL,
+    ping: String,
+    download: String,
+    target: String,
+    rel: String,
+    media: String,
+    hreflang: String,
+    type: String,
+    referrerPolicy: REFERRER,
+    // Obsolete
+    coords: String,
+    charset: String,
+    name: String,
+    rev: String,
+    shape: String,
+  }
+});
+// Latest WhatWG spec says these methods come via HTMLHyperlinkElementUtils
+URLUtils._inherit(htmlNameToImpl.a.prototype);
+
+define({
+  tag: 'area',
+  ctor: function HTMLAreaElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    alt: String,
+    target: String,
+    download: String,
+    rel: String,
+    media: String,
+    href: URL,
+    hreflang: String,
+    type: String,
+    shape: String,
+    coords: String,
+    ping: String,
+    // XXX: also reflect relList
+    referrerPolicy: REFERRER,
+    // Obsolete
+    noHref: Boolean,
+  }
+});
+// Latest WhatWG spec says these methods come via HTMLHyperlinkElementUtils
+URLUtils._inherit(htmlNameToImpl.area.prototype);
+
+define({
+  tag: 'br',
+  ctor: function HTMLBRElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // Obsolete
+    clear: String
+  },
+});
+
+define({
+  tag: 'base',
+  ctor: function HTMLBaseElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    "target": String
+  }
+});
+
+
+define({
+  tag: 'body',
+  ctor: function HTMLBodyElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  // Certain event handler attributes on a <body> tag actually set
+  // handlers for the window rather than just that element.  Define
+  // getters and setters for those here.  Note that some of these override
+  // properties on HTMLElement.prototype.
+  // XXX: If I add support for <frameset>, these have to go there, too
+  // XXX
+  // When the Window object is implemented, these attribute will have
+  // to work with the same-named attributes on the Window.
+  events: [
+    "afterprint", "beforeprint", "beforeunload", "blur", "error",
+    "focus","hashchange", "load", "message", "offline", "online",
+    "pagehide", "pageshow","popstate","resize","scroll","storage","unload",
+  ],
+  attributes: {
+    // Obsolete
+    text: { type: String, treatNullAsEmptyString: true },
+    link: { type: String, treatNullAsEmptyString: true },
+    vLink: { type: String, treatNullAsEmptyString: true },
+    aLink: { type: String, treatNullAsEmptyString: true },
+    bgColor: { type: String, treatNullAsEmptyString: true },
+    background: String,
+  }
+});
+
+define({
+  tag: 'button',
+  ctor: function HTMLButtonElement(doc, localName, prefix) {
+    HTMLFormElement.call(this, doc, localName, prefix);
+  },
+  props: formAssociatedProps,
+  attributes: {
+    name: String,
+    value: String,
+    disabled: Boolean,
+    autofocus: Boolean,
+    type: { type:["submit", "reset", "button", "menu"], missing: 'submit' },
+    formTarget: String,
+    formNoValidate: Boolean,
+    formMethod: { type: ["get", "post", "dialog"], invalid: 'get', missing: '' },
+    formEnctype: { type: ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"], invalid: "application/x-www-form-urlencoded", missing: '' },
+  }
+});
+
+define({
+  tag: 'dl',
+  ctor: function HTMLDListElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // Obsolete
+    compact: Boolean,
+  }
+});
+
+define({
+  tag: 'data',
+  ctor: function HTMLDataElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    value: String,
+  }
+});
+
+define({
+  tag: 'datalist',
+  ctor: function HTMLDataListElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  }
+});
+
+define({
+  tag: 'details',
+  ctor: function HTMLDetailsElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    "open": Boolean
+  }
+});
+
+define({
+  tag: 'div',
+  ctor: function HTMLDivElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // Obsolete
+    align: String
+  }
+});
+
+define({
+  tag: 'embed',
+  ctor: function HTMLEmbedElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    src: URL,
+    type: String,
+    width: String,
+    height: String,
+    // Obsolete
+    align: String,
+    name: String,
+  }
+});
+
+define({
+  tag: 'fieldset',
+  ctor: function HTMLFieldSetElement(doc, localName, prefix) {
+    HTMLFormElement.call(this, doc, localName, prefix);
+  },
+  props: formAssociatedProps,
+  attributes: {
+    disabled: Boolean,
+    name: String
+  }
+});
+
+define({
+  tag: 'form',
+  ctor: function HTMLFormElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    action: String,
+    autocomplete: {type:['on', 'off'], missing: 'on'},
+    name: String,
+    acceptCharset: {name: "accept-charset"},
+    target: String,
+    noValidate: Boolean,
+    method: { type: ["get", "post", "dialog"], invalid: 'get', missing: 'get' },
+    // Both enctype and encoding reflect the enctype content attribute
+    enctype: { type: ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"], invalid: "application/x-www-form-urlencoded", missing: "application/x-www-form-urlencoded" },
+    encoding: {name: 'enctype', type: ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"], invalid: "application/x-www-form-urlencoded", missing: "application/x-www-form-urlencoded" },
+  }
+});
+
+define({
+  tag: 'hr',
+  ctor: function HTMLHRElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // Obsolete
+    align: String,
+    color: String,
+    noShade: Boolean,
+    size: String,
+    width: String,
+  },
+});
+
+define({
+  tag: 'head',
+  ctor: function HTMLHeadElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  }
+});
+
+define({
+  tags: ['h1','h2','h3','h4','h5','h6'],
+  ctor: function HTMLHeadingElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // Obsolete
+    align: String,
+  },
+});
+
+define({
+  tag: 'html',
+  ctor: function HTMLHtmlElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // Obsolete
+    version: String
+  }
+});
+
+define({
+  tag: 'iframe',
+  ctor: function HTMLIFrameElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+    var Window = require('./Window'); // Avoid circular dependencies.
+    this._contentWindow = new Window();
+  },
+  props: {
+    contentWindow: { get: function() {
+      return this._contentWindow;
+    } },
+    contentDocument: { get: function() {
+      return this.contentWindow.document;
+    } },
+  },
+  attributes: {
+    src: URL,
+    srcdoc: String,
+    name: String,
+    width: String,
+    height: String,
+    // XXX: sandbox is a reflected settable token list
+    seamless: Boolean,
+    allowFullscreen: Boolean,
+    allowUserMedia: Boolean,
+    allowPaymentRequest: Boolean,
+    referrerPolicy: REFERRER,
+    // Obsolete
+    align: String,
+    scrolling: String,
+    frameBorder: String,
+    longDesc: URL,
+    marginHeight: { type: String, treatNullAsEmptyString: true },
+    marginWidth: { type: String, treatNullAsEmptyString: true },
+  }
+});
+
+define({
+  tag: 'img',
+  ctor: function HTMLImageElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    alt: String,
+    src: URL,
+    srcset: String,
+    crossOrigin: CORS,
+    useMap: String,
+    isMap: Boolean,
+    height: { type: "unsigned long", default: 0 },
+    width: { type: "unsigned long", default: 0 },
+    referrerPolicy: REFERRER,
+    // Obsolete:
+    name: String,
+    lowsrc: URL,
+    align: String,
+    hspace: { type: "unsigned long", default: 0 },
+    vspace: { type: "unsigned long", default: 0 },
+    longDesc: URL,
+    border: { type: String, treatNullAsEmptyString: true },
+  }
+});
+
+define({
+  tag: 'input',
+  ctor: function HTMLInputElement(doc, localName, prefix) {
+    HTMLFormElement.call(this, doc, localName, prefix);
+  },
+  props: {
+    form: formAssociatedProps.form,
+    _post_click_activation_steps: { value: function(e) {
+      if (this.type === 'checkbox') {
+        this.checked = !this.checked;
+      }
+      else if (this.type === 'radio') {
+        var group = this.form.getElementsByName(this.name);
+        for (var i=group.length-1; i >= 0; i--) {
+          var el = group[i];
+          el.checked = (el === this);
+        }
+      }
+    }},
+  },
+  attributes: {
+    name: String,
+    disabled: Boolean,
+    autofocus: Boolean,
+    accept: String,
+    alt: String,
+    max: String,
+    min: String,
+    pattern: String,
+    placeholder: String,
+    step: String,
+    dirName: String,
+    defaultValue: {name: 'value'},
+    multiple: Boolean,
+    required: Boolean,
+    readOnly: Boolean,
+    checked: Boolean,
+    value: String,
+    src: URL,
+    defaultChecked: {name: 'checked', type: Boolean},
+    size: {type: 'unsigned long', default: 20, min: 1, setmin: 1},
+    width: {type: 'unsigned long', min: 0, setmin: 0, default: 0},
+    height: {type: 'unsigned long', min: 0, setmin: 0, default: 0},
+    minLength: {type: 'unsigned long', min: 0, setmin: 0, default: -1},
+    maxLength: {type: 'unsigned long', min: 0, setmin: 0, default: -1},
+    autocomplete: String, // It's complicated
+    type: { type:
+            ["text", "hidden", "search", "tel", "url", "email", "password",
+             "datetime", "date", "month", "week", "time", "datetime-local",
+             "number", "range", "color", "checkbox", "radio", "file", "submit",
+             "image", "reset", "button"],
+            missing: 'text' },
+    formTarget: String,
+    formNoValidate: Boolean,
+    formMethod: { type: ["get", "post"], invalid: 'get', missing: '' },
+    formEnctype: { type: ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"], invalid: "application/x-www-form-urlencoded", missing: '' },
+    inputMode: { type: [ "verbatim", "latin", "latin-name", "latin-prose", "full-width-latin", "kana", "kana-name", "katakana", "numeric", "tel", "email", "url" ], missing: '' },
+    // Obsolete
+    align: String,
+    useMap: String,
+  }
+});
+
+define({
+  tag: 'keygen',
+  ctor: function HTMLKeygenElement(doc, localName, prefix) {
+    HTMLFormElement.call(this, doc, localName, prefix);
+  },
+  props: formAssociatedProps,
+  attributes: {
+    name: String,
+    disabled: Boolean,
+    autofocus: Boolean,
+    challenge: String,
+    keytype: { type:["rsa"], missing: '' },
+  }
+});
+
+define({
+  tag: 'li',
+  ctor: function HTMLLIElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    value: {type: "long", default: 0},
+    // Obsolete
+    type: String,
+  }
+});
+
+define({
+  tag: 'label',
+  ctor: function HTMLLabelElement(doc, localName, prefix) {
+    HTMLFormElement.call(this, doc, localName, prefix);
+  },
+  props: formAssociatedProps,
+  attributes: {
+    htmlFor: {name: 'for', type: String}
+  }
+});
+
+define({
+  tag: 'legend',
+  ctor: function HTMLLegendElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // Obsolete
+    align: String
+  },
+});
+
+define({
+  tag: 'link',
+  ctor: function HTMLLinkElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // XXX Reflect DOMSettableTokenList sizes also DOMTokenList relList
+    href: URL,
+    rel: String,
+    media: String,
+    hreflang: String,
+    type: String,
+    crossOrigin: CORS,
+    nonce: String,
+    integrity: String,
+    referrerPolicy: REFERRER,
+    // Obsolete
+    charset: String,
+    rev: String,
+    target: String,
+  }
+});
+
+define({
+  tag: 'map',
+  ctor: function HTMLMapElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    name: String
+  }
+});
+
+define({
+  tag: 'menu',
+  ctor: function HTMLMenuElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // XXX: not quite right, default should be popup if parent element is
+    // popup.
+    type: { type: [ 'context', 'popup', 'toolbar' ], missing: 'toolbar' },
+    label: String,
+    // Obsolete
+    compact: Boolean,
+  }
+});
+
+define({
+  tag: 'meta',
+  ctor: function HTMLMetaElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    name: String,
+    content: String,
+    httpEquiv: {name: 'http-equiv', type: String},
+    // Obsolete
+    scheme: String,
+  }
+});
+
+define({
+  tag: 'meter',
+  ctor: function HTMLMeterElement(doc, localName, prefix) {
+    HTMLFormElement.call(this, doc, localName, prefix);
+  },
+  props: formAssociatedProps
+});
+
+define({
+  tags: ['ins', 'del'],
+  ctor: function HTMLModElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    cite: URL,
+    dateTime: String
+  }
+});
+
+define({
+  tag: 'ol',
+  ctor: function HTMLOListElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  props: {
+    // Utility function (see the start attribute default value). Returns
+    // the number of <li> children of this element
+    _numitems: { get: function() {
+      var items = 0;
+      this.childNodes.forEach(function(n) {
+        if (n.nodeType === Node.ELEMENT_NODE && n.tagName === "LI")
+          items++;
+      });
+      return items;
+    }}
+  },
+  attributes: {
+    type: String,
+    reversed: Boolean,
+    start: {
+      type: "long",
+      default: function() {
+       // The default value of the start attribute is 1 unless the list is
+       // reversed. Then it is the # of li children
+       if (this.reversed)
+         return this._numitems;
+       else
+         return 1;
+      }
+    },
+    // Obsolete
+    compact: Boolean,
+  }
+});
+
+define({
+  tag: 'object',
+  ctor: function HTMLObjectElement(doc, localName, prefix) {
+    HTMLFormElement.call(this, doc, localName, prefix);
+  },
+  props: formAssociatedProps,
+  attributes: {
+    data: URL,
+    type: String,
+    name: String,
+    useMap: String,
+    typeMustMatch: Boolean,
+    width: String,
+    height: String,
+    // Obsolete
+    align: String,
+    archive: String,
+    code: String,
+    declare: Boolean,
+    hspace: { type: "unsigned long", default: 0 },
+    standby: String,
+    vspace: { type: "unsigned long", default: 0 },
+    codeBase: URL,
+    codeType: String,
+    border: { type: String, treatNullAsEmptyString: true },
+  }
+});
+
+define({
+  tag: 'optgroup',
+  ctor: function HTMLOptGroupElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    disabled: Boolean,
+    label: String
+  }
+});
+
+define({
+  tag: 'option',
+  ctor: function HTMLOptionElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  props: {
+    form: { get: function() {
+      var p = this.parentNode;
+      while (p && p.nodeType === Node.ELEMENT_NODE) {
+        if (p.localName === 'select') return p.form;
+        p = p.parentNode;
+      }
+    }},
+    value: {
+      get: function() { return this._getattr('value') || this.text; },
+      set: function(v) { this._setattr('value', v); },
+    },
+    text: {
+      get: function() {
+        // Strip and collapse whitespace
+        return this.textContent.replace(/[ \t\n\f\r]+/g, ' ').trim();
+      },
+      set: function(v) { this.textContent = v; },
+    },
+    // missing: index
+  },
+  attributes: {
+    disabled: Boolean,
+    defaultSelected: {name: 'selected', type: Boolean},
+    label: String,
+  }
+});
+
+define({
+  tag: 'output',
+  ctor: function HTMLOutputElement(doc, localName, prefix) {
+    HTMLFormElement.call(this, doc, localName, prefix);
+  },
+  props: formAssociatedProps,
+  attributes: {
+    // XXX Reflect for/htmlFor as a settable token list
+    name: String
+  }
+});
+
+define({
+  tag: 'p',
+  ctor: function HTMLParagraphElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // Obsolete
+    align: String
+  }
+});
+
+define({
+  tag: 'param',
+  ctor: function HTMLParamElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    name: String,
+    value: String,
+    // Obsolete
+    type: String,
+    valueType: String,
+  }
+});
+
+define({
+  tags: ['pre',/*legacy elements:*/'listing','xmp'],
+  ctor: function HTMLPreElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // Obsolete
+    width: { type: "long", default: 0 },
+  }
+});
+
+define({
+  tag: 'progress',
+  ctor: function HTMLProgressElement(doc, localName, prefix) {
+    HTMLFormElement.call(this, doc, localName, prefix);
+  },
+  props: formAssociatedProps,
+  attributes: {
+    max: {type: Number, float: true, default: 1.0, min: 0}
+  }
+});
+
+define({
+  tags: ['q', 'blockquote'],
+  ctor: function HTMLQuoteElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    cite: URL
+  }
+});
+
+define({
+  tag: 'script',
+  ctor: function HTMLScriptElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  props: {
+    text: {
+      get: function() {
+        var s = "";
+        for(var i = 0, n = this.childNodes.length; i < n; i++) {
+          var child = this.childNodes[i];
+          if (child.nodeType === Node.TEXT_NODE)
+            s += child._data;
+        }
+        return s;
+      },
+      set: function(value) {
+        this.removeChildren();
+        if (value !== null && value !== "") {
+          this.appendChild(this.ownerDocument.createTextNode(value));
+        }
+      }
+    }
+  },
+  attributes: {
+    src: URL,
+    type: String,
+    charset: String,
+    defer: Boolean,
+    async: Boolean,
+    crossOrigin: CORS,
+    nonce: String,
+    integrity: String,
+  }
+});
+
+define({
+  tag: 'select',
+  ctor: function HTMLSelectElement(doc, localName, prefix) {
+    HTMLFormElement.call(this, doc, localName, prefix);
+  },
+  props: {
+    form: formAssociatedProps.form,
+    options: { get: function() {
+      return this.getElementsByTagName('option');
+    }}
+  },
+  attributes: {
+    autocomplete: String, // It's complicated
+    name: String,
+    disabled: Boolean,
+    autofocus: Boolean,
+    multiple: Boolean,
+    required: Boolean,
+    size: {type: "unsigned long", default: 0}
+  }
+});
+
+define({
+  tag: 'source',
+  ctor: function HTMLSourceElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    src: URL,
+    type: String,
+    media: String
+  }
+});
+
+define({
+  tag: 'span',
+  ctor: function HTMLSpanElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  }
+});
+
+define({
+  tag: 'style',
+  ctor: function HTMLStyleElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    media: String,
+    type: String,
+    scoped: Boolean
+  }
+});
+
+define({
+  tag: 'caption',
+  ctor: function HTMLTableCaptionElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    // Obsolete
+    align: String,
+  }
+});
+
+
+define({
+  ctor: function HTMLTableCellElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    colSpan: {type: "unsigned long", default: 1},
+    rowSpan: {type: "unsigned long", default: 1},
+    //XXX Also reflect settable token list headers
+    scope: { type: ['row','col','rowgroup','colgroup'], missing: '' },
+    abbr: String,
+    // Obsolete
+    align: String,
+    axis: String,
+    height: String,
+    width: String,
+    ch: { name: 'char', type: String },
+    chOff: { name: 'charoff', type: String },
+    noWrap: Boolean,
+    vAlign: String,
+    bgColor: { type: String, treatNullAsEmptyString: true },
+  }
+});
+
+define({
+  tags: ['col', 'colgroup'],
+  ctor: function HTMLTableColElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    span: {type: 'limited unsigned long with fallback', default: 1, min: 1},
+    // Obsolete
+    align: String,
+    ch: { name: 'char', type: String },
+    chOff: { name: 'charoff', type: String },
+    vAlign: String,
+    width: String,
+  }
+});
+
+define({
+  tag: 'table',
+  ctor: function HTMLTableElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  props: {
+    rows: { get: function() {
+      return this.getElementsByTagName('tr');
+    }}
+  },
+  attributes: {
+    // Obsolete
+    align: String,
+    border: String,
+    frame: String,
+    rules: String,
+    summary: String,
+    width: String,
+    bgColor: { type: String, treatNullAsEmptyString: true },
+    cellPadding: { type: String, treatNullAsEmptyString: true },
+    cellSpacing: { type: String, treatNullAsEmptyString: true },
+  }
+});
+
+define({
+  tag: 'template',
+  ctor: function HTMLTemplateElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+    this._contentFragment = doc._templateDoc.createDocumentFragment();
+  },
+  props: {
+    content: { get: function() { return this._contentFragment; } },
+    serialize: { value: function() { return this.content.serialize(); } }
+  }
+});
+
+define({
+  tag: 'tr',
+  ctor: function HTMLTableRowElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  props: {
+    cells: { get: function() {
+      return this.querySelectorAll('td,th');
+    }}
+  },
+  attributes: {
+    // Obsolete
+    align: String,
+    ch: { name: 'char', type: String },
+    chOff: { name: 'charoff', type: String },
+    vAlign: String,
+    bgColor: { type: String, treatNullAsEmptyString: true },
+  },
+});
+
+define({
+  tags: ['thead', 'tfoot', 'tbody'],
+  ctor: function HTMLTableSectionElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  props: {
+    rows: { get: function() {
+      return this.getElementsByTagName('tr');
+    }}
+  },
+  attributes: {
+    // Obsolete
+    align: String,
+    ch: { name: 'char', type: String },
+    chOff: { name: 'charoff', type: String },
+    vAlign: String,
+  }
+});
+
+define({
+  tag: 'textarea',
+  ctor: function HTMLTextAreaElement(doc, localName, prefix) {
+    HTMLFormElement.call(this, doc, localName, prefix);
+  },
+  props: {
+    form: formAssociatedProps.form,
+    type: { get: function() { return 'textarea'; } },
+    defaultValue: {
+      get: function() { return this.textContent; },
+      set: function(v) { this.textContent = v; },
+    },
+    value: {
+      get: function() { return this.defaultValue; /* never dirty */ },
+      set: function(v) {
+        // This isn't completely correct: according to the spec, this
+        // should "dirty" the API value, and result in
+        // `this.value !== this.defaultValue`.  But for most of what
+        // folks want to do, this implementation should be fine:
+        this.defaultValue = v;
+      },
+    },
+    textLength: { get: function() { return this.value.length; } },
+  },
+  attributes: {
+    autocomplete: String, // It's complicated
+    name: String,
+    disabled: Boolean,
+    autofocus: Boolean,
+    placeholder: String,
+    wrap: String,
+    dirName: String,
+    required: Boolean,
+    readOnly: Boolean,
+    rows: {type: 'limited unsigned long with fallback', default: 2 },
+    cols: {type: 'limited unsigned long with fallback', default: 20 },
+    maxLength: {type: 'unsigned long', min: 0, setmin: 0, default: -1},
+    minLength: {type: 'unsigned long', min: 0, setmin: 0, default: -1},
+    inputMode: { type: [ "verbatim", "latin", "latin-name", "latin-prose", "full-width-latin", "kana", "kana-name", "katakana", "numeric", "tel", "email", "url" ], missing: '' },
+  }
+});
+
+define({
+  tag: 'time',
+  ctor: function HTMLTimeElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    dateTime: String,
+    pubDate: Boolean
+  }
+});
+
+define({
+  tag: 'title',
+  ctor: function HTMLTitleElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  props: {
+    text: { get: function() {
+      return this.textContent;
+    }}
+  }
+});
+
+define({
+  tag: 'ul',
+  ctor: function HTMLUListElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    type: String,
+    // Obsolete
+    compact: Boolean,
+  }
+});
+
+define({
+  ctor: function HTMLMediaElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    src: URL,
+    crossOrigin: CORS,
+    preload: { type:["metadata", "none", "auto", {value: "", alias: "auto"}], missing: 'auto' },
+    loop: Boolean,
+    autoplay: Boolean,
+    mediaGroup: String,
+    controls: Boolean,
+    defaultMuted: {name: "muted", type: Boolean}
+  }
+});
+
+define({
+  tag: 'audio',
+  superclass: htmlElements.HTMLMediaElement,
+  ctor: function HTMLAudioElement(doc, localName, prefix) {
+    htmlElements.HTMLMediaElement.call(this, doc, localName, prefix);
+  }
+});
+
+define({
+  tag: 'video',
+  superclass: htmlElements.HTMLMediaElement,
+  ctor: function HTMLVideoElement(doc, localName, prefix) {
+    htmlElements.HTMLMediaElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    poster: URL,
+    width: {type: "unsigned long", min: 0, default: 0 },
+    height: {type: "unsigned long", min: 0, default: 0 }
+  }
+});
+
+define({
+  tag: 'td',
+  superclass: htmlElements.HTMLTableCellElement,
+  ctor: function HTMLTableDataCellElement(doc, localName, prefix) {
+    htmlElements.HTMLTableCellElement.call(this, doc, localName, prefix);
+  }
+});
+
+define({
+  tag: 'th',
+  superclass: htmlElements.HTMLTableCellElement,
+  ctor: function HTMLTableHeaderCellElement(doc, localName, prefix) {
+    htmlElements.HTMLTableCellElement.call(this, doc, localName, prefix);
+  },
+});
+
+define({
+  tag: 'frameset',
+  ctor: function HTMLFrameSetElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  }
+});
+
+define({
+  tag: 'frame',
+  ctor: function HTMLFrameElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  }
+});
+
+define({
+  tag: 'canvas',
+  ctor: function HTMLCanvasElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  props: {
+    getContext: { value: utils.nyi },
+    probablySupportsContext: { value: utils.nyi },
+    setContext: { value: utils.nyi },
+    transferControlToProxy: { value: utils.nyi },
+    toDataURL: { value: utils.nyi },
+    toBlob: { value: utils.nyi }
+  },
+  attributes: {
+    width: { type: "unsigned long", default: 300},
+    height: { type: "unsigned long", default: 150}
+  }
+});
+
+define({
+  tag: 'dialog',
+  ctor: function HTMLDialogElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  props: {
+    show: { value: utils.nyi },
+    showModal: { value: utils.nyi },
+    close: { value: utils.nyi }
+  },
+  attributes: {
+    open: Boolean,
+    returnValue: String
+  }
+});
+
+define({
+  tag: 'menuitem',
+  ctor: function HTMLMenuItemElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  props: {
+    // The menuitem's label
+    _label: {
+      get: function() {
+        var val = this._getattr('label');
+        if (val !== null && val !== '') { return val; }
+        val = this.textContent;
+        // Strip and collapse whitespace
+        return val.replace(/[ \t\n\f\r]+/g, ' ').trim();
+      }
+    },
+    // The menuitem label IDL attribute
+    label: {
+      get: function() {
+        var val = this._getattr('label');
+        if (val !== null) { return val; }
+        return this._label;
+      },
+      set: function(v) {
+        this._setattr('label', v);
+      },
+    }
+  },
+  attributes: {
+    type: { type: ["command","checkbox","radio"], missing: 'command' },
+    icon: URL,
+    disabled: Boolean,
+    checked: Boolean,
+    radiogroup: String,
+    default: Boolean
+  }
+});
+
+define({
+  tag: 'source',
+  ctor: function HTMLSourceElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    srcset: String,
+    sizes: String,
+    media: String,
+    src: URL,
+    type: String
+  }
+});
+
+define({
+  tag: 'track',
+  ctor: function HTMLTrackElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    src: URL,
+    srclang: String,
+    label: String,
+    default: Boolean,
+    kind: { type: ["subtitles", "captions", "descriptions", "chapters", "metadata"], missing: 'subtitles', invalid: 'metadata' },
+  },
+  props: {
+    NONE: { get: function() { return 0; } },
+    LOADING: { get: function() { return 1; } },
+    LOADED: { get: function() { return 2; } },
+    ERROR: { get: function() { return 3; } },
+    readyState: { get: utils.nyi },
+    track: { get: utils.nyi }
+  }
+});
+
+define({
+  // obsolete
+  tag: 'font',
+  ctor: function HTMLFontElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    color: { type: String, treatNullAsEmptyString: true },
+    face: { type: String },
+    size: { type: String },
+  },
+});
+
+define({
+  // obsolete
+  tag: 'dir',
+  ctor: function HTMLDirectoryElement(doc, localName, prefix) {
+    HTMLElement.call(this, doc, localName, prefix);
+  },
+  attributes: {
+    compact: Boolean,
+  },
+});
+
+define({
+  tags: [
+    "abbr", "address", "article", "aside", "b", "bdi", "bdo",
+    "cite", "code", "dd", "dfn", "dt", "em", "figcaption", "figure",
+    "footer", "header", "hgroup", "i", "kbd", "main", "mark", "nav", "noscript",
+    "rb", "rp", "rt", "rtc", "ruby", "s", "samp", "section", "small", "strong",
+    "sub", "summary", "sup", "u", "var", "wbr",
+    // Legacy elements
+    "acronym", "basefont", "big", "center", "nobr", "noembed", "noframes",
+    "plaintext", "strike", "tt"
+  ]
+});
--- /dev/null
+++ b/domino-lib/impl.js
@@ -1,0 +1,27 @@
+"use strict";
+var utils = require('./utils');
+
+exports = module.exports = {
+  CSSStyleDeclaration: require('./CSSStyleDeclaration'),
+  CharacterData: require('./CharacterData'),
+  Comment: require('./Comment'),
+  DOMException: require('./DOMException'),
+  DOMImplementation: require('./DOMImplementation'),
+  DOMTokenList: require('./DOMTokenList'),
+  Document: require('./Document'),
+  DocumentFragment: require('./DocumentFragment'),
+  DocumentType: require('./DocumentType'),
+  Element: require('./Element'),
+  HTMLParser: require('./HTMLParser'),
+  NamedNodeMap: require('./NamedNodeMap'),
+  Node: require('./Node'),
+  NodeList: require('./NodeList'),
+  NodeFilter: require('./NodeFilter'),
+  ProcessingInstruction: require('./ProcessingInstruction'),
+  Text: require('./Text'),
+  Window: require('./Window')
+};
+
+utils.merge(exports, require('./events'));
+utils.merge(exports, require('./htmlelts').elements);
+utils.merge(exports, require('./svg').elements);
--- /dev/null
+++ b/domino-lib/index.d.ts
@@ -1,0 +1,5 @@
+declare module "domino" {
+  function createDOMImplementation(): DOMImplementation;
+  function createDocument(html?: string, force?: boolean): Document;
+  function createWindow(html?: string, address?: string): Window;
+}
\ No newline at end of file
--- /dev/null
+++ b/domino-lib/index.js
@@ -1,0 +1,79 @@
+"use strict";
+var DOMImplementation = require('./DOMImplementation');
+var HTMLParser = require('./HTMLParser');
+var Window = require('./Window');
+
+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');
--- /dev/null
+++ b/domino-lib/select.js
@@ -1,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;
+};
--- /dev/null
+++ b/domino-lib/sloppy.js
@@ -1,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; };
+    }
+  }
+};
--- /dev/null
+++ b/domino-lib/svg.js
@@ -1,0 +1,57 @@
+"use strict";
+var Element = require('./Element');
+var defineElement = require('./defineElement');
+var utils = require('./utils');
+var CSSStyleDeclaration = require('./CSSStyleDeclaration');
+
+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'
+  ]
+});
--- /dev/null
+++ b/domino-lib/utils.js
@@ -1,0 +1,85 @@
+"use strict";
+var DOMException = require('./DOMException');
+var ERR = DOMException;
+var isApiWritable = require("./config").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();
+  });
+};
--- /dev/null
+++ b/domino-lib/xmlnames.js
@@ -1,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;
+}
--- /dev/null
+++ b/domino/domino.go
@@ -1,0 +1,337 @@
+package domino
+
+import (
+	"fmt"
+	"github.com/dop251/goja"
+	"github.com/dop251/goja_nodejs/console"
+	"github.com/dop251/goja_nodejs/eventloop"
+	"github.com/dop251/goja_nodejs/require"
+	"github.com/jvatic/goja-babel"
+	"golang.org/x/net/html"
+	"io/ioutil"
+	"log"
+	"opossum"
+	"opossum/nodes"
+	"strconv"
+	"strings"
+	"time"
+)
+
+var DebugDumpJS *bool
+
+type Domino struct {
+	loop       *eventloop.EventLoop
+	vm         *goja.Runtime
+	html       string
+	outputHtml string
+	domChanged chan int
+}
+
+func NewDomino(html string) (d *Domino) {
+	d = &Domino{
+		html: html,
+	}
+	return
+}
+
+func (d *Domino) Start() {
+	log.Printf("Start event loop")
+	d.loop = eventloop.NewEventLoop()
+
+	d.loop.Start()
+	log.Printf("event loop started")
+}
+
+func (d *Domino) Stop() {
+	d.loop.Stop()
+}
+
+func IntrospectError(err error, script string) {
+	prefix := "Line "
+	i := strings.Index(err.Error(), prefix)
+	if i > 0 {
+		i += len(prefix)
+		s := err.Error()[i:]
+		yxStart := strings.Split(s, " ")[0]
+		yx := strings.Split(yxStart, ":")
+		y, _ := strconv.Atoi(yx[0])
+		x, _ := strconv.Atoi(yx[1])
+		log.Printf("line %v, column %v", y, x)
+		lines := strings.Split(script, "\n")
+
+		if wholeLine := lines[y-1]; len(wholeLine) > 100 {
+			from := x - 50
+			to := x + 50
+			if from < 0 {
+				from = 0
+			}
+			if to >= len(wholeLine) {
+				to = len(wholeLine) - 1
+			}
+			log.Printf("the line: %v", wholeLine[from:to])
+		} else {
+			if y > 0 && len(lines[y-1]) < 120 {
+				log.Printf("%v: %v", y-1, lines[y-1])
+			}
+			log.Printf("%v: %v", y, lines[y])
+			if y+1 < len(lines) && len(lines[y+1]) < 120 {
+				log.Printf("%v: %v", y+1, lines[y+1])
+			}
+		}
+	}
+}
+
+func (d *Domino) Exec(script string) (err error) {
+	script = strings.Replace(script, "const ", "var ", -1)
+	script = strings.Replace(script, "let ", "var ", -1)
+	script = strings.Replace(script, "<!--", "", -1)
+	SCRIPT := `
+	    global = {};
+	    //global.__domino_frozen__ = true; // Must precede any require('domino')
+	    var domino = require('domino-lib/index');
+	    var Element = domino.impl.Element; // etc
+
+	    // JSDOM also knows the style tag
+	    // https://github.com/jsdom/jsdom/issues/2485
+		Object.assign(this, domino.createWindow(s.html, 'http://example.com'));
+		window = this;
+		window.parent = window;
+		window.top = window;
+		window.self = window;
+		addEventListener = function() {};
+		window.location.href = 'http://example.com';
+		navigator = {};
+		HTMLElement = domino.impl.HTMLElement;
+	    // Fire DOMContentLoaded
+	    // to trigger $(document)readfy!!!!!!!
+	    document.close();
+	` + script
+	if *DebugDumpJS {
+		ioutil.WriteFile("main.js", []byte(SCRIPT), 0644)
+	}
+	prg, err := goja.Compile("main.js", SCRIPT, false)
+	if err != nil {
+		IntrospectError(err, SCRIPT)
+		return fmt.Errorf("compile: %w", err)
+	}
+	ready := make(chan int)
+	go func() {
+		d.loop.RunOnLoop(func(vm *goja.Runtime) {
+			log.Printf("RunOnLoop")
+			registry := require.NewRegistry(
+				require.WithGlobalFolders(".", ".."),
+			)
+			console.Enable(vm)
+			req := registry.Enable(vm)
+			_ = req
+
+			vm.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
+			type S struct {
+				Buf  string `json:"buf"`
+				HTML string `json:"html"`
+			}
+			d.vm = vm
+
+			vm.Set("s", S{
+				HTML: d.html,
+				Buf:  "yolo",
+			})
+			_, err := vm.RunProgram(prg)
+			if err != nil {
+				log.Printf("run program: %v", err)
+				IntrospectError(err, script)
+			}
+			ready <- 1
+		})
+	}()
+	<-ready
+	<-time.After(10 * time.Millisecond)
+	//res = fmt.Sprintf("%v", v.Export())
+	if _, _, err = d.TrackChanges(); err != nil {
+		return fmt.Errorf("track changes: %w", err)
+	}
+	return
+}
+
+func (d *Domino) Exec6(script string) (err error) {
+	babel.Init(4) // Setup 4 transformers (can be any number > 0)
+	r, err := babel.Transform(strings.NewReader(script), map[string]interface{}{
+		"plugins": []string{
+			"transform-react-jsx",
+			"transform-es2015-block-scoping",
+		},
+	})
+	if err != nil {
+		return fmt.Errorf("babel: %v", err)
+	}
+	buf, err := ioutil.ReadAll(r)
+	if err != nil {
+		return fmt.Errorf("read all: %v", err)
+	}
+	return d.Exec(string(buf))
+}
+
+func (d *Domino) Export(expr string) (res string, err error) {
+	v, err := d.vm.RunString(expr)
+	if err != nil {
+		return "", fmt.Errorf("export: %w", err)
+	}
+	if v != nil {
+		res = fmt.Sprintf("%v", v.Export())
+	}
+	return
+}
+
+// TriggerClick, and return the result html
+// ...then HTML5 parse it, diff the node tree
+// (probably faster and cleaner than anything else)
+func (d *Domino) TriggerClick(selector string) (newHTML string, ok bool, err error) {
+	res, err := d.vm.RunString(`
+		var sel = '` + selector + `';
+		console.log('sel=' + sel);
+		var sell = document.querySelector(sel);
+		console.log('sell=' + sell);
+		var selfn = sell.click.bind(sell);
+		console.log('selfn=' + selfn);
+		if (selfn) {
+			selfn();
+		}
+		!!selfn;
+	`)
+
+	ok = fmt.Sprintf("%v", res) == "true"
+
+	return
+}
+
+// Put change into html (e.g. from input field mutation)
+func (d *Domino) PutAttr(selector, attr, val string) (ok bool, err error) {
+	res, err := d.vm.RunString(`
+		var sel = '` + selector + `';
+		console.log('sel=' + sel);
+		var sell = document.querySelector(sel);
+		console.log('sell=' + sell);
+		sell.attr('` + attr + `', '` + val + `');
+		!!sell;
+	`)
+
+	ok = fmt.Sprintf("%v", res) == "true"
+
+	return
+}
+
+func (d *Domino) TrackChanges() (html string, changed bool, err error) {
+	html, err = d.Export("document.querySelector('html').innerHTML;")
+	if err != nil {
+		return
+	}
+	changed = d.outputHtml != html
+	d.outputHtml = html
+	return
+}
+
+// https://stackoverflow.com/a/26716182
+// TODO: eval is evil
+func (d *Domino) ExecInlinedScripts() (err error) {
+	return d.Exec(`
+	navigator = {};
+
+    var scripts = Array.prototype.slice.call(document.getElementsByTagName("script"));
+    for (var i = 0; i < scripts.length; i++) {
+        if (scripts[i].src != "") {
+            var tag = document.createElement("script");
+            tag.src = scripts[i].src;
+            document.getElementsByTagName("head")[0].appendChild(tag);
+        }
+        else {
+        	try {
+            	eval.call(window, scripts[i].innerHTML);
+            } catch(e) {
+            	console.log(e);
+            }
+        }
+    }
+	`)
+}
+
+func Srcs(doc *nodes.Node) (srcs []string) {
+	srcs = make([]string, 0, 3)
+
+	iterateJsElements(doc, func(src, inlineCode string) {
+		if src = strings.TrimSpace(src); src != "" && !blocked(src) {
+			srcs = append(srcs, src)
+		}
+	})
+
+	return
+}
+
+func blocked(src string) bool {
+	for _, s := range []string{"adsense", "adsystem", "adservice", "googletagservice", "googletagmanager", "script.ioam.de","googlesyndication","adserver", "nativeads", "prebid", ".ads."} {
+		if strings.Contains(src, s) {
+			return true
+		}
+	}
+	return false
+}
+
+func Scripts(doc *nodes.Node, downloads map[string]string) (codes []string) {
+	codes = make([]string, 0, 3)
+
+	iterateJsElements(doc, func(src, inlineCode string) {
+		if strings.TrimSpace(inlineCode) != "" {
+			codes = append(codes, inlineCode)
+		} else if c, ok := downloads[src]; ok {
+			codes = append(codes, c)
+		}
+	})
+
+	return
+}
+
+func iterateJsElements(doc *nodes.Node, fn func(src string, inlineCode string)) {
+	var f func(n *nodes.Node)
+	f = func(n *nodes.Node) {
+		if n.Type() == html.ElementNode && n.Data() == "script" {
+			isJS := true
+			src := ""
+
+			for _, a := range n.Attr {
+				switch strings.ToLower(a.Key) {
+				case "type":
+					t, err := opossum.NewContentType(a.Val)
+					if err != nil {
+						log.Printf("t: %v", err)
+					}
+					if a.Val == "" || t.IsJS() {
+						isJS = true
+					} else {
+						isJS = false
+					}
+				case "src":
+					src = a.Val
+				}
+			}
+
+			if isJS {
+				fn(src, nodes.ContentFrom(*n))
+			}
+		}
+		for _, c := range n.Children {
+			f(c)
+		}
+	}
+
+	f(doc)
+
+	return
+}
+
+// AJAX:
+// https://stackoverflow.com/questions/7086858/loading-ajax-app-with-jsdom
+
+// Babel on Goja:
+// https://github.com/dop251/goja/issues/5#issuecomment-259996573
+
+// Goja supports ES5.1 which is essentially JS assembly:
+// https://github.com/dop251/goja/issues/76#issuecomment-399253779
--- /dev/null
+++ b/domino/domino_test.go
@@ -1,0 +1,393 @@
+package domino
+
+import (
+	"io/ioutil"
+	"strings"
+	"testing"
+	"time"
+)
+
+const simpleHTML = `
+<html>
+<body>
+<h1 id="title">Hello</h1>
+</body>
+</html>
+`
+
+func init() {
+	t := true
+	DebugDumpJS = &t
+}
+
+func TestSimple(t *testing.T) {
+	d := NewDomino(simpleHTML)
+	d.Start()
+	script := `
+	console.log('Hello!!');
+	var numberOne = 1;
+	`
+	err := d.Exec(script)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	res, err := d.Export("numberOne+1")
+	t.Logf("res=%v", res)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	if res != "2" {
+		t.Fatal()
+	}
+	d.Stop()
+}
+
+func TestJQuery(t *testing.T) {
+	buf, err := ioutil.ReadFile("jquery-3.5.1.js")
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	d := NewDomino(simpleHTML)
+	d.Start()
+	script := `
+	console.log('Hello!!');
+	//console.log(window.jQuery);
+	console.log(this);
+	Object.assign(this, window);
+	//console.log(this.jQuery);
+	console.log($);
+	$(document).ready(function() {
+		gfgf
+		console.log('yolo');
+	});
+	setTimeout(function() {
+		console.log("ok");
+	}, 1000);
+	var numberOne = 1;
+	`
+	_=buf
+	err = d.Exec(string(buf) + ";" + script)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	res, err := d.Export("numberOne+1")
+	t.Logf("res=%v", res)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	if res != "2" {
+		t.Fatal()
+	}
+	time.Sleep(2 * time.Second)
+	d.Stop()
+}
+
+func TestRun(t *testing.T) {
+	jQuery, err := ioutil.ReadFile("jquery-3.5.1.js")
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	//t.Parallel()
+	SCRIPT := string(jQuery) + `
+    ;;;
+	setTimeout(function() {
+		console.log("ok :-)");
+		console.log(s.buf);
+		var h = document.querySelector('html');
+    	console.log(h.innerHTML);
+	}, 1000);
+	console.log("Started");
+	Object.assign(this, window);
+    $(document).ready(function() {
+    	console.log('READDDYYYYY!!!!!!!');
+    });
+    console.log('$:');
+    console.log($);
+    console.log('$h1:');
+    console.log($('h1').html());
+
+    //elem.dispatchEvent(event);
+    console.log(window.location.href);
+	`
+	d := NewDomino(simpleHTML)
+	d.Start()
+	err = d.Exec(SCRIPT)
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+
+	time.Sleep(2 * time.Second)
+	res, err := d.Export("$('h1').html()")
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	if res != "Hello" {
+		t.Fatalf(res)
+	}
+	d.Stop()
+}
+
+func TestTriggerClick(t *testing.T) {
+	jQuery, err := ioutil.ReadFile("jquery-3.5.1.js")
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	//t.Parallel()
+	SCRIPT := string(jQuery) + `
+    ;;;
+    Object.assign(this, window);
+	console.log("Started");
+	var clicked = false;
+    $(document).ready(function() {
+    	console.log('READDDYYYYY!!!!!!!');
+    	$('h1').click(function() {
+    		console.log('CLICKED!!!!');
+    		clicked = true;
+    	});
+    });
+	`
+	d := NewDomino(simpleHTML)
+	d.Start()
+	err = d.Exec(SCRIPT)
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+
+	//time.Sleep(2 * time.Second)
+	res, err := d.Export("$('h1').html()")
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	if res != "Hello" {
+		t.Fatalf(res)
+	}
+	_, ok, err := d.TriggerClick("h1")
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	if !ok {
+		t.Fatal()
+	}
+	res, err = d.Export("clicked")
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	if res != "true" {
+		t.Fatalf(res)
+	}
+	d.Stop()
+}
+
+func TestDomChanged(t *testing.T) {
+	jQuery, err := ioutil.ReadFile("jquery-3.5.1.js")
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	//t.Parallel()
+	SCRIPT := string(jQuery) + `
+    ;;;
+	setTimeout(function() {
+		console.log("ok :-)");
+		console.log(s.buf);
+		var h = document.querySelector('html');
+    	console.log(h.innerHTML);
+	}, 1000);
+	console.log("Started");
+	Object.assign(this, window);
+    $(document).ready(function() {
+    	console.log('READDDYYYYY!!!!!!!');
+    });
+    console.log('$:');
+    console.log($);
+    console.log('$h1:');
+    console.log($('h1').html());
+
+    //elem.dispatchEvent(event);
+    console.log(window.location.href);
+	`
+	d := NewDomino(simpleHTML)
+	d.Start()
+	err = d.Exec(SCRIPT)
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+
+	time.Sleep(2 * time.Second)
+	res, err := d.Export("$('h1').html()")
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	/*if res != "Hello" {
+		t.Fatalf(res)
+	}*/
+	_=res
+	res, err = d.Export("$('h1').html('minor updates :-)'); $('h1').html();")
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	t.Logf("new res=%v", res)
+	<-time.After(2*time.Second)
+	d.Stop()
+}
+
+func TestTrackChanges(t *testing.T) {
+	d := NewDomino(simpleHTML)
+	d.Start()
+	err := d.Exec(``)
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	// 1st time: no change
+	html, changed, err := d.TrackChanges()
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	if html == "" {
+		t.Fatalf(err.Error())
+	}
+	if changed == true {
+		t.Fatal()
+	}
+	// 2nd time: no change
+	html, changed, err = d.TrackChanges()
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	if html == "" {
+		t.Fatalf(err.Error())
+	}
+	if changed == true {
+		t.Fatal()
+	}
+	_, err = d.Export("document.getElementById('title').innerHTML='new title'; true;")
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	// 3rd time: yes change
+	html, changed, err = d.TrackChanges()
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	if html == "" {
+		t.Fatalf(err.Error())
+	}
+	if changed == false {
+		t.Fatal()
+	}
+	if !strings.Contains(html, "new title") {
+		t.Fatalf(html)
+	}
+	d.Stop()
+}
+
+func TestExecInlinedScripts(t *testing.T) {
+	const h = `
+	<html>
+	<body>
+	<h1 id="title">Hello</h1>
+	<script>
+	document.getElementById('title').innerHTML = 'Good day';
+	</script>
+	</body>
+	</html>
+	`
+	d := NewDomino(h)
+	d.Start()
+	err := d.ExecInlinedScripts()
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	res, err := d.Export("document.getElementById('title').innerHTML")
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	if !strings.Contains(res, "Good day") {
+		t.Fatalf(res)
+	}
+	d.Stop()
+}
+
+func TestWindowEqualsGlobal(t *testing.T) {
+	const h = `
+	<html>
+	<body>
+	<script>
+	a = 2;
+	window.b = 5;
+	</script>
+	<script>
+	console.log('window.a=', window.a);
+	console.log('wot');
+	console.log('window.b=', window.b);
+	console.log('wit');
+	window.a++;
+	b++;
+	</script>
+	</body>
+	</html>
+	`
+	d := NewDomino(h)
+	d.Start()
+	err := d.ExecInlinedScripts()
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	res, err := d.Export("window.a")
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	if !strings.Contains(res, "3") {
+		t.Fatalf(res)
+	}
+	res, err = d.Export("window.b")
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	if !strings.Contains(res, "6") {
+		t.Fatalf(res)
+	}
+	d.Stop()
+}
+
+func TestES6(t *testing.T) {
+	d := NewDomino(simpleHTML)
+	d.Start()
+	script := `
+	console.log('Hello!!');
+	const numberOne = 1;
+	`
+	err := d.Exec6(script)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	res, err := d.Export("numberOne+1")
+	t.Logf("res=%v", res)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	if res != "2" {
+		t.Fatal()
+	}
+	d.Stop()
+}
+
+func TestWindowParent(t *testing.T) {
+	d := NewDomino(simpleHTML)
+	d.Start()
+	script := `
+	console.log('Hello!!')
+	`
+	err := d.Exec(script)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	res, err := d.Export("window === window.parent")
+	t.Logf("res=%v", res)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	if res != "true" {
+		t.Fatal()
+	}
+	d.Stop()
+}
--- /dev/null
+++ b/domino/jquery-3.5.1.js
@@ -1,0 +1,10872 @@
+/*!
+ * jQuery JavaScript Library v3.5.1
+ * https://jquery.com/
+ *
+ * Includes Sizzle.js
+ * https://sizzlejs.com/
+ *
+ * Copyright JS Foundation and other contributors
+ * Released under the MIT license
+ * https://jquery.org/license
+ *
+ * Date: 2020-05-04T22:49Z
+ */
+( function( global, factory ) {
+
+	"use strict";
+
+	if ( typeof module === "object" && typeof module.exports === "object" ) {
+
+		// For CommonJS and CommonJS-like environments where a proper `window`
+		// is present, execute the factory and get jQuery.
+		// For environments that do not have a `window` with a `document`
+		// (such as Node.js), expose a factory as module.exports.
+		// This accentuates the need for the creation of a real `window`.
+		// e.g. var jQuery = require("jquery")(window);
+		// See ticket #14549 for more info.
+		module.exports = global.document ?
+			factory( global, true ) :
+			function( w ) {
+				if ( !w.document ) {
+					throw new Error( "jQuery requires a window with a document" );
+				}
+				return factory( w );
+			};
+	} else {
+		factory( global );
+	}
+
+// Pass this if window is not defined yet
+} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
+// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
+// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
+// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
+// enough that all such attempts are guarded in a try block.
+"use strict";
+
+var arr = [];
+
+var getProto = Object.getPrototypeOf;
+
+var slice = arr.slice;
+
+var flat = arr.flat ? function( array ) {
+	return arr.flat.call( array );
+} : function( array ) {
+	return arr.concat.apply( [], array );
+};
+
+
+var push = arr.push;
+
+var indexOf = arr.indexOf;
+
+var class2type = {};
+
+var toString = class2type.toString;
+
+var hasOwn = class2type.hasOwnProperty;
+
+var fnToString = hasOwn.toString;
+
+var ObjectFunctionString = fnToString.call( Object );
+
+var support = {};
+
+var isFunction = function isFunction( obj ) {
+
+      // Support: Chrome <=57, Firefox <=52
+      // In some browsers, typeof returns "function" for HTML <object> elements
+      // (i.e., `typeof document.createElement( "object" ) === "function"`).
+      // We don't want to classify *any* DOM node as a function.
+      return typeof obj === "function" && typeof obj.nodeType !== "number";
+  };
+
+
+var isWindow = function isWindow( obj ) {
+		return obj != null && obj === obj.window;
+	};
+
+
+var document = window.document;
+
+
+
+	var preservedScriptAttributes = {
+		type: true,
+		src: true,
+		nonce: true,
+		noModule: true
+	};
+
+	function DOMEval( code, node, doc ) {
+		doc = doc || document;
+
+		var i, val,
+			script = doc.createElement( "script" );
+
+		script.text = code;
+		if ( node ) {
+			for ( i in preservedScriptAttributes ) {
+
+				// Support: Firefox 64+, Edge 18+
+				// Some browsers don't support the "nonce" property on scripts.
+				// On the other hand, just using `getAttribute` is not enough as
+				// the `nonce` attribute is reset to an empty string whenever it
+				// becomes browsing-context connected.
+				// See https://github.com/whatwg/html/issues/2369
+				// See https://html.spec.whatwg.org/#nonce-attributes
+				// The `node.getAttribute` check was added for the sake of
+				// `jQuery.globalEval` so that it can fake a nonce-containing node
+				// via an object.
+				val = node[ i ] || node.getAttribute && node.getAttribute( i );
+				if ( val ) {
+					script.setAttribute( i, val );
+				}
+			}
+		}
+		doc.head.appendChild( script ).parentNode.removeChild( script );
+	}
+
+
+function toType( obj ) {
+	if ( obj == null ) {
+		return obj + "";
+	}
+
+	// Support: Android <=2.3 only (functionish RegExp)
+	return typeof obj === "object" || typeof obj === "function" ?
+		class2type[ toString.call( obj ) ] || "object" :
+		typeof obj;
+}
+/* global Symbol */
+// Defining this global in .eslintrc.json would create a danger of using the global
+// unguarded in another place, it seems safer to define global only for this module
+
+
+
+var
+	version = "3.5.1",
+
+	// Define a local copy of jQuery
+	jQuery = function( selector, context ) {
+
+		// The jQuery object is actually just the init constructor 'enhanced'
+		// Need init if jQuery is called (just allow error to be thrown if not included)
+		return new jQuery.fn.init( selector, context );
+	};
+
+jQuery.fn = jQuery.prototype = {
+
+	// The current version of jQuery being used
+	jquery: version,
+
+	constructor: jQuery,
+
+	// The default length of a jQuery object is 0
+	length: 0,
+
+	toArray: function() {
+		return slice.call( this );
+	},
+
+	// Get the Nth element in the matched element set OR
+	// Get the whole matched element set as a clean array
+	get: function( num ) {
+
+		// Return all the elements in a clean array
+		if ( num == null ) {
+			return slice.call( this );
+		}
+
+		// Return just the one element from the set
+		return num < 0 ? this[ num + this.length ] : this[ num ];
+	},
+
+	// Take an array of elements and push it onto the stack
+	// (returning the new matched element set)
+	pushStack: function( elems ) {
+
+		// Build a new jQuery matched element set
+		var ret = jQuery.merge( this.constructor(), elems );
+
+		// Add the old object onto the stack (as a reference)
+		ret.prevObject = this;
+
+		// Return the newly-formed element set
+		return ret;
+	},
+
+	// Execute a callback for every element in the matched set.
+	each: function( callback ) {
+		return jQuery.each( this, callback );
+	},
+
+	map: function( callback ) {
+		return this.pushStack( jQuery.map( this, function( elem, i ) {
+			return callback.call( elem, i, elem );
+		} ) );
+	},
+
+	slice: function() {
+		return this.pushStack( slice.apply( this, arguments ) );
+	},
+
+	first: function() {
+		return this.eq( 0 );
+	},
+
+	last: function() {
+		return this.eq( -1 );
+	},
+
+	even: function() {
+		return this.pushStack( jQuery.grep( this, function( _elem, i ) {
+			return ( i + 1 ) % 2;
+		} ) );
+	},
+
+	odd: function() {
+		return this.pushStack( jQuery.grep( this, function( _elem, i ) {
+			return i % 2;
+		} ) );
+	},
+
+	eq: function( i ) {
+		var len = this.length,
+			j = +i + ( i < 0 ? len : 0 );
+		return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
+	},
+
+	end: function() {
+		return this.prevObject || this.constructor();
+	},
+
+	// For internal use only.
+	// Behaves like an Array's method, not like a jQuery method.
+	push: push,
+	sort: arr.sort,
+	splice: arr.splice
+};
+
+jQuery.extend = jQuery.fn.extend = function() {
+	var options, name, src, copy, copyIsArray, clone,
+		target = arguments[ 0 ] || {},
+		i = 1,
+		length = arguments.length,
+		deep = false;
+
+	// Handle a deep copy situation
+	if ( typeof target === "boolean" ) {
+		deep = target;
+
+		// Skip the boolean and the target
+		target = arguments[ i ] || {};
+		i++;
+	}
+
+	// Handle case when target is a string or something (possible in deep copy)
+	if ( typeof target !== "object" && !isFunction( target ) ) {
+		target = {};
+	}
+
+	// Extend jQuery itself if only one argument is passed
+	if ( i === length ) {
+		target = this;
+		i--;
+	}
+
+	for ( ; i < length; i++ ) {
+
+		// Only deal with non-null/undefined values
+		if ( ( options = arguments[ i ] ) != null ) {
+
+			// Extend the base object
+			for ( name in options ) {
+				copy = options[ name ];
+
+				// Prevent Object.prototype pollution
+				// Prevent never-ending loop
+				if ( name === "__proto__" || target === copy ) {
+					continue;
+				}
+
+				// Recurse if we're merging plain objects or arrays
+				if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
+					( copyIsArray = Array.isArray( copy ) ) ) ) {
+					src = target[ name ];
+
+					// Ensure proper type for the source value
+					if ( copyIsArray && !Array.isArray( src ) ) {
+						clone = [];
+					} else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
+						clone = {};
+					} else {
+						clone = src;
+					}
+					copyIsArray = false;
+
+					// Never move original objects, clone them
+					target[ name ] = jQuery.extend( deep, clone, copy );
+
+				// Don't bring in undefined values
+				} else if ( copy !== undefined ) {
+					target[ name ] = copy;
+				}
+			}
+		}
+	}
+
+	// Return the modified object
+	return target;
+};
+
+jQuery.extend( {
+
+	// Unique for each copy of jQuery on the page
+	expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
+
+	// Assume jQuery is ready without the ready module
+	isReady: true,
+
+	error: function( msg ) {
+		throw new Error( msg );
+	},
+
+	noop: function() {},
+
+	isPlainObject: function( obj ) {
+		var proto, Ctor;
+
+		// Detect obvious negatives
+		// Use toString instead of jQuery.type to catch host objects
+		if ( !obj || toString.call( obj ) !== "[object Object]" ) {
+			return false;
+		}
+
+		proto = getProto( obj );
+
+		// Objects with no prototype (e.g., `Object.create( null )`) are plain
+		if ( !proto ) {
+			return true;
+		}
+
+		// Objects with prototype are plain iff they were constructed by a global Object function
+		Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
+		return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
+	},
+
+	isEmptyObject: function( obj ) {
+		var name;
+
+		for ( name in obj ) {
+			return false;
+		}
+		return true;
+	},
+
+	// Evaluates a script in a provided context; falls back to the global one
+	// if not specified.
+	globalEval: function( code, options, doc ) {
+		DOMEval( code, { nonce: options && options.nonce }, doc );
+	},
+
+	each: function( obj, callback ) {
+		var length, i = 0;
+
+		if ( isArrayLike( obj ) ) {
+			length = obj.length;
+			for ( ; i < length; i++ ) {
+				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+					break;
+				}
+			}
+		} else {
+			for ( i in obj ) {
+				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+					break;
+				}
+			}
+		}
+
+		return obj;
+	},
+
+	// results is for internal usage only
+	makeArray: function( arr, results ) {
+		var ret = results || [];
+
+		if ( arr != null ) {
+			if ( isArrayLike( Object( arr ) ) ) {
+				jQuery.merge( ret,
+					typeof arr === "string" ?
+					[ arr ] : arr
+				);
+			} else {
+				push.call( ret, arr );
+			}
+		}
+
+		return ret;
+	},
+
+	inArray: function( elem, arr, i ) {
+		return arr == null ? -1 : indexOf.call( arr, elem, i );
+	},
+
+	// Support: Android <=4.0 only, PhantomJS 1 only
+	// push.apply(_, arraylike) throws on ancient WebKit
+	merge: function( first, second ) {
+		var len = +second.length,
+			j = 0,
+			i = first.length;
+
+		for ( ; j < len; j++ ) {
+			first[ i++ ] = second[ j ];
+		}
+
+		first.length = i;
+
+		return first;
+	},
+
+	grep: function( elems, callback, invert ) {
+		var callbackInverse,
+			matches = [],
+			i = 0,
+			length = elems.length,
+			callbackExpect = !invert;
+
+		// Go through the array, only saving the items
+		// that pass the validator function
+		for ( ; i < length; i++ ) {
+			callbackInverse = !callback( elems[ i ], i );
+			if ( callbackInverse !== callbackExpect ) {
+				matches.push( elems[ i ] );
+			}
+		}
+
+		return matches;
+	},
+
+	// arg is for internal usage only
+	map: function( elems, callback, arg ) {
+		var length, value,
+			i = 0,
+			ret = [];
+
+		// Go through the array, translating each of the items to their new values
+		if ( isArrayLike( elems ) ) {
+			length = elems.length;
+			for ( ; i < length; i++ ) {
+				value = callback( elems[ i ], i, arg );
+
+				if ( value != null ) {
+					ret.push( value );
+				}
+			}
+
+		// Go through every key on the object,
+		} else {
+			for ( i in elems ) {
+				value = callback( elems[ i ], i, arg );
+
+				if ( value != null ) {
+					ret.push( value );
+				}
+			}
+		}
+
+		// Flatten any nested arrays
+		return flat( ret );
+	},
+
+	// A global GUID counter for objects
+	guid: 1,
+
+	// jQuery.support is not used in Core but other projects attach their
+	// properties to it so it needs to exist.
+	support: support
+} );
+
+if ( typeof Symbol === "function" ) {
+	jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
+}
+
+// Populate the class2type map
+jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
+function( _i, name ) {
+	class2type[ "[object " + name + "]" ] = name.toLowerCase();
+} );
+
+function isArrayLike( obj ) {
+
+	// Support: real iOS 8.2 only (not reproducible in simulator)
+	// `in` check used to prevent JIT error (gh-2145)
+	// hasOwn isn't used here due to false negatives
+	// regarding Nodelist length in IE
+	var length = !!obj && "length" in obj && obj.length,
+		type = toType( obj );
+
+	if ( isFunction( obj ) || isWindow( obj ) ) {
+		return false;
+	}
+
+	return type === "array" || length === 0 ||
+		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
+}
+var Sizzle =
+/*!
+ * Sizzle CSS Selector Engine v2.3.5
+ * https://sizzlejs.com/
+ *
+ * Copyright JS Foundation and other contributors
+ * Released under the MIT license
+ * https://js.foundation/
+ *
+ * Date: 2020-03-14
+ */
+( function( window ) {
+var i,
+	support,
+	Expr,
+	getText,
+	isXML,
+	tokenize,
+	compile,
+	select,
+	outermostContext,
+	sortInput,
+	hasDuplicate,
+
+	// Local document vars
+	setDocument,
+	document,
+	docElem,
+	documentIsHTML,
+	rbuggyQSA,
+	rbuggyMatches,
+	matches,
+	contains,
+
+	// Instance-specific data
+	expando = "sizzle" + 1 * new Date(),
+	preferredDoc = window.document,
+	dirruns = 0,
+	done = 0,
+	classCache = createCache(),
+	tokenCache = createCache(),
+	compilerCache = createCache(),
+	nonnativeSelectorCache = createCache(),
+	sortOrder = function( a, b ) {
+		if ( a === b ) {
+			hasDuplicate = true;
+		}
+		return 0;
+	},
+
+	// Instance methods
+	hasOwn = ( {} ).hasOwnProperty,
+	arr = [],
+	pop = arr.pop,
+	pushNative = arr.push,
+	push = arr.push,
+	slice = arr.slice,
+
+	// Use a stripped-down indexOf as it's faster than native
+	// https://jsperf.com/thor-indexof-vs-for/5
+	indexOf = function( list, elem ) {
+		var i = 0,
+			len = list.length;
+		for ( ; i < len; i++ ) {
+			if ( list[ i ] === elem ) {
+				return i;
+			}
+		}
+		return -1;
+	},
+
+	booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" +
+		"ismap|loop|multiple|open|readonly|required|scoped",
+
+	// Regular expressions
+
+	// http://www.w3.org/TR/css3-selectors/#whitespace
+	whitespace = "[\\x20\\t\\r\\n\\f]",
+
+	// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
+	identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace +
+		"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",
+
+	// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
+	attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
+
+		// Operator (capture 2)
+		"*([*^$|!~]?=)" + whitespace +
+
+		// "Attribute values must be CSS identifiers [capture 5]
+		// or strings [capture 3 or capture 4]"
+		"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" +
+		whitespace + "*\\]",
+
+	pseudos = ":(" + identifier + ")(?:\\((" +
+
+		// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
+		// 1. quoted (capture 3; capture 4 or capture 5)
+		"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
+
+		// 2. simple (capture 6)
+		"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
+
+		// 3. anything else (capture 2)
+		".*" +
+		")\\)|)",
+
+	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+	rwhitespace = new RegExp( whitespace + "+", "g" ),
+	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" +
+		whitespace + "+$", "g" ),
+
+	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+	rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace +
+		"*" ),
+	rdescend = new RegExp( whitespace + "|>" ),
+
+	rpseudo = new RegExp( pseudos ),
+	ridentifier = new RegExp( "^" + identifier + "$" ),
+
+	matchExpr = {
+		"ID": new RegExp( "^#(" + identifier + ")" ),
+		"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
+		"TAG": new RegExp( "^(" + identifier + "|[*])" ),
+		"ATTR": new RegExp( "^" + attributes ),
+		"PSEUDO": new RegExp( "^" + pseudos ),
+		"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" +
+			whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" +
+			whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+		"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
+
+		// For use in libraries implementing .is()
+		// We use this for POS matching in `select`
+		"needsContext": new RegExp( "^" + whitespace +
+			"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
+			"*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
+	},
+
+	rhtml = /HTML$/i,
+	rinputs = /^(?:input|select|textarea|button)$/i,
+	rheader = /^h\d$/i,
+
+	rnative = /^[^{]+\{\s*\[native \w/,
+
+	// Easily-parseable/retrievable ID or TAG or CLASS selectors
+	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+	rsibling = /[+~]/,
+
+	// CSS escapes
+	// http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+	runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ),
+	funescape = function( escape, nonHex ) {
+		var high = "0x" + escape.slice( 1 ) - 0x10000;
+
+		return nonHex ?
+
+			// Strip the backslash prefix from a non-hex escape sequence
+			nonHex :
+
+			// Replace a hexadecimal escape sequence with the encoded Unicode code point
+			// Support: IE <=11+
+			// For values outside the Basic Multilingual Plane (BMP), manually construct a
+			// surrogate pair
+			high < 0 ?
+				String.fromCharCode( high + 0x10000 ) :
+				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+	},
+
+	// CSS string/identifier serialization
+	// https://drafts.csswg.org/cssom/#common-serializing-idioms
+	rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,
+	fcssescape = function( ch, asCodePoint ) {
+		if ( asCodePoint ) {
+
+			// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
+			if ( ch === "\0" ) {
+				return "\uFFFD";
+			}
+
+			// Control characters and (dependent upon position) numbers get escaped as code points
+			return ch.slice( 0, -1 ) + "\\" +
+				ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
+		}
+
+		// Other potentially-special ASCII characters get backslash-escaped
+		return "\\" + ch;
+	},
+
+	// Used for iframes
+	// See setDocument()
+	// Removing the function wrapper causes a "Permission Denied"
+	// error in IE
+	unloadHandler = function() {
+		setDocument();
+	},
+
+	inDisabledFieldset = addCombinator(
+		function( elem ) {
+			return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset";
+		},
+		{ dir: "parentNode", next: "legend" }
+	);
+
+// Optimize for push.apply( _, NodeList )
+try {
+	push.apply(
+		( arr = slice.call( preferredDoc.childNodes ) ),
+		preferredDoc.childNodes
+	);
+
+	// Support: Android<4.0
+	// Detect silently failing push.apply
+	// eslint-disable-next-line no-unused-expressions
+	arr[ preferredDoc.childNodes.length ].nodeType;
+} catch ( e ) {
+	push = { apply: arr.length ?
+
+		// Leverage slice if possible
+		function( target, els ) {
+			pushNative.apply( target, slice.call( els ) );
+		} :
+
+		// Support: IE<9
+		// Otherwise append directly
+		function( target, els ) {
+			var j = target.length,
+				i = 0;
+
+			// Can't trust NodeList.length
+			while ( ( target[ j++ ] = els[ i++ ] ) ) {}
+			target.length = j - 1;
+		}
+	};
+}
+
+function Sizzle( selector, context, results, seed ) {
+	var m, i, elem, nid, match, groups, newSelector,
+		newContext = context && context.ownerDocument,
+
+		// nodeType defaults to 9, since context defaults to document
+		nodeType = context ? context.nodeType : 9;
+
+	results = results || [];
+
+	// Return early from calls with invalid selector or context
+	if ( typeof selector !== "string" || !selector ||
+		nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
+
+		return results;
+	}
+
+	// Try to shortcut find operations (as opposed to filters) in HTML documents
+	if ( !seed ) {
+		setDocument( context );
+		context = context || document;
+
+		if ( documentIsHTML ) {
+
+			// If the selector is sufficiently simple, try using a "get*By*" DOM method
+			// (excepting DocumentFragment context, where the methods don't exist)
+			if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) {
+
+				// ID selector
+				if ( ( m = match[ 1 ] ) ) {
+
+					// Document context
+					if ( nodeType === 9 ) {
+						if ( ( elem = context.getElementById( m ) ) ) {
+
+							// Support: IE, Opera, Webkit
+							// TODO: identify versions
+							// getElementById can match elements by name instead of ID
+							if ( elem.id === m ) {
+								results.push( elem );
+								return results;
+							}
+						} else {
+							return results;
+						}
+
+					// Element context
+					} else {
+
+						// Support: IE, Opera, Webkit
+						// TODO: identify versions
+						// getElementById can match elements by name instead of ID
+						if ( newContext && ( elem = newContext.getElementById( m ) ) &&
+							contains( context, elem ) &&
+							elem.id === m ) {
+
+							results.push( elem );
+							return results;
+						}
+					}
+
+				// Type selector
+				} else if ( match[ 2 ] ) {
+					push.apply( results, context.getElementsByTagName( selector ) );
+					return results;
+
+				// Class selector
+				} else if ( ( m = match[ 3 ] ) && support.getElementsByClassName &&
+					context.getElementsByClassName ) {
+
+					push.apply( results, context.getElementsByClassName( m ) );
+					return results;
+				}
+			}
+
+			// Take advantage of querySelectorAll
+			if ( support.qsa &&
+				!nonnativeSelectorCache[ selector + " " ] &&
+				( !rbuggyQSA || !rbuggyQSA.test( selector ) ) &&
+
+				// Support: IE 8 only
+				// Exclude object elements
+				( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) {
+
+				newSelector = selector;
+				newContext = context;
+
+				// qSA considers elements outside a scoping root when evaluating child or
+				// descendant combinators, which is not what we want.
+				// In such cases, we work around the behavior by prefixing every selector in the
+				// list with an ID selector referencing the scope context.
+				// The technique has to be used as well when a leading combinator is used
+				// as such selectors are not recognized by querySelectorAll.
+				// Thanks to Andrew Dupont for this technique.
+				if ( nodeType === 1 &&
+					( rdescend.test( selector ) || rcombinators.test( selector ) ) ) {
+
+					// Expand context for sibling selectors
+					newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
+						context;
+
+					// We can use :scope instead of the ID hack if the browser
+					// supports it & if we're not changing the context.
+					if ( newContext !== context || !support.scope ) {
+
+						// Capture the context ID, setting it first if necessary
+						if ( ( nid = context.getAttribute( "id" ) ) ) {
+							nid = nid.replace( rcssescape, fcssescape );
+						} else {
+							context.setAttribute( "id", ( nid = expando ) );
+						}
+					}
+
+					// Prefix every selector in the list
+					groups = tokenize( selector );
+					i = groups.length;
+					while ( i-- ) {
+						groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " +
+							toSelector( groups[ i ] );
+					}
+					newSelector = groups.join( "," );
+				}
+
+				try {
+					push.apply( results,
+						newContext.querySelectorAll( newSelector )
+					);
+					return results;
+				} catch ( qsaError ) {
+					nonnativeSelectorCache( selector, true );
+				} finally {
+					if ( nid === expando ) {
+						context.removeAttribute( "id" );
+					}
+				}
+			}
+		}
+	}
+
+	// All others
+	return select( selector.replace( rtrim, "$1" ), context, results, seed );
+}
+
+/**
+ * Create key-value caches of limited size
+ * @returns {function(string, object)} Returns the Object data after storing it on itself with
+ *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
+ *	deleting the oldest entry
+ */
+function createCache() {
+	var keys = [];
+
+	function cache( key, value ) {
+
+		// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
+		if ( keys.push( key + " " ) > Expr.cacheLength ) {
+
+			// Only keep the most recent entries
+			delete cache[ keys.shift() ];
+		}
+		return ( cache[ key + " " ] = value );
+	}
+	return cache;
+}
+
+/**
+ * Mark a function for special use by Sizzle
+ * @param {Function} fn The function to mark
+ */
+function markFunction( fn ) {
+	fn[ expando ] = true;
+	return fn;
+}
+
+/**
+ * Support testing using an element
+ * @param {Function} fn Passed the created element and returns a boolean result
+ */
+function assert( fn ) {
+	var el = document.createElement( "fieldset" );
+
+	try {
+		return !!fn( el );
+	} catch ( e ) {
+		return false;
+	} finally {
+
+		// Remove from its parent by default
+		if ( el.parentNode ) {
+			el.parentNode.removeChild( el );
+		}
+
+		// release memory in IE
+		el = null;
+	}
+}
+
+/**
+ * Adds the same handler for all of the specified attrs
+ * @param {String} attrs Pipe-separated list of attributes
+ * @param {Function} handler The method that will be applied
+ */
+function addHandle( attrs, handler ) {
+	var arr = attrs.split( "|" ),
+		i = arr.length;
+
+	while ( i-- ) {
+		Expr.attrHandle[ arr[ i ] ] = handler;
+	}
+}
+
+/**
+ * Checks document order of two siblings
+ * @param {Element} a
+ * @param {Element} b
+ * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
+ */
+function siblingCheck( a, b ) {
+	var cur = b && a,
+		diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
+			a.sourceIndex - b.sourceIndex;
+
+	// Use IE sourceIndex if available on both nodes
+	if ( diff ) {
+		return diff;
+	}
+
+	// Check if b follows a
+	if ( cur ) {
+		while ( ( cur = cur.nextSibling ) ) {
+			if ( cur === b ) {
+				return -1;
+			}
+		}
+	}
+
+	return a ? 1 : -1;
+}
+
+/**
+ * Returns a function to use in pseudos for input types
+ * @param {String} type
+ */
+function createInputPseudo( type ) {
+	return function( elem ) {
+		var name = elem.nodeName.toLowerCase();
+		return name === "input" && elem.type === type;
+	};
+}
+
+/**
+ * Returns a function to use in pseudos for buttons
+ * @param {String} type
+ */
+function createButtonPseudo( type ) {
+	return function( elem ) {
+		var name = elem.nodeName.toLowerCase();
+		return ( name === "input" || name === "button" ) && elem.type === type;
+	};
+}
+
+/**
+ * Returns a function to use in pseudos for :enabled/:disabled
+ * @param {Boolean} disabled true for :disabled; false for :enabled
+ */
+function createDisabledPseudo( disabled ) {
+
+	// Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
+	return function( elem ) {
+
+		// Only certain elements can match :enabled or :disabled
+		// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
+		// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
+		if ( "form" in elem ) {
+
+			// Check for inherited disabledness on relevant non-disabled elements:
+			// * listed form-associated elements in a disabled fieldset
+			//   https://html.spec.whatwg.org/multipage/forms.html#category-listed
+			//   https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
+			// * option elements in a disabled optgroup
+			//   https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
+			// All such elements have a "form" property.
+			if ( elem.parentNode && elem.disabled === false ) {
+
+				// Option elements defer to a parent optgroup if present
+				if ( "label" in elem ) {
+					if ( "label" in elem.parentNode ) {
+						return elem.parentNode.disabled === disabled;
+					} else {
+						return elem.disabled === disabled;
+					}
+				}
+
+				// Support: IE 6 - 11
+				// Use the isDisabled shortcut property to check for disabled fieldset ancestors
+				return elem.isDisabled === disabled ||
+
+					// Where there is no isDisabled, check manually
+					/* jshint -W018 */
+					elem.isDisabled !== !disabled &&
+					inDisabledFieldset( elem ) === disabled;
+			}
+
+			return elem.disabled === disabled;
+
+		// Try to winnow out elements that can't be disabled before trusting the disabled property.
+		// Some victims get caught in our net (label, legend, menu, track), but it shouldn't
+		// even exist on them, let alone have a boolean value.
+		} else if ( "label" in elem ) {
+			return elem.disabled === disabled;
+		}
+
+		// Remaining elements are neither :enabled nor :disabled
+		return false;
+	};
+}
+
+/**
+ * Returns a function to use in pseudos for positionals
+ * @param {Function} fn
+ */
+function createPositionalPseudo( fn ) {
+	return markFunction( function( argument ) {
+		argument = +argument;
+		return markFunction( function( seed, matches ) {
+			var j,
+				matchIndexes = fn( [], seed.length, argument ),
+				i = matchIndexes.length;
+
+			// Match elements found at the specified indexes
+			while ( i-- ) {
+				if ( seed[ ( j = matchIndexes[ i ] ) ] ) {
+					seed[ j ] = !( matches[ j ] = seed[ j ] );
+				}
+			}
+		} );
+	} );
+}
+
+/**
+ * Checks a node for validity as a Sizzle context
+ * @param {Element|Object=} context
+ * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
+ */
+function testContext( context ) {
+	return context && typeof context.getElementsByTagName !== "undefined" && context;
+}
+
+// Expose support vars for convenience
+support = Sizzle.support = {};
+
+/**
+ * Detects XML nodes
+ * @param {Element|Object} elem An element or a document
+ * @returns {Boolean} True iff elem is a non-HTML XML node
+ */
+isXML = Sizzle.isXML = function( elem ) {
+	var namespace = elem.namespaceURI,
+		docElem = ( elem.ownerDocument || elem ).documentElement;
+
+	// Support: IE <=8
+	// Assume HTML when documentElement doesn't yet exist, such as inside loading iframes
+	// https://bugs.jquery.com/ticket/4833
+	return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" );
+};
+
+/**
+ * Sets document-related variables once based on the current document
+ * @param {Element|Object} [doc] An element or document object to use to set the document
+ * @returns {Object} Returns the current document
+ */
+setDocument = Sizzle.setDocument = function( node ) {
+	var hasCompare, subWindow,
+		doc = node ? node.ownerDocument || node : preferredDoc;
+
+	// Return early if doc is invalid or already selected
+	// Support: IE 11+, Edge 17 - 18+
+	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+	// two documents; shallow comparisons work.
+	// eslint-disable-next-line eqeqeq
+	if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) {
+		return document;
+	}
+
+	// Update global variables
+	document = doc;
+	docElem = document.documentElement;
+	documentIsHTML = !isXML( document );
+
+	// Support: IE 9 - 11+, Edge 12 - 18+
+	// Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
+	// Support: IE 11+, Edge 17 - 18+
+	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+	// two documents; shallow comparisons work.
+	// eslint-disable-next-line eqeqeq
+	if ( preferredDoc != document &&
+		( subWindow = document.defaultView ) && subWindow.top !== subWindow ) {
+
+		// Support: IE 11, Edge
+		if ( subWindow.addEventListener ) {
+			subWindow.addEventListener( "unload", unloadHandler, false );
+
+		// Support: IE 9 - 10 only
+		} else if ( subWindow.attachEvent ) {
+			subWindow.attachEvent( "onunload", unloadHandler );
+		}
+	}
+
+	// Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only,
+	// Safari 4 - 5 only, Opera <=11.6 - 12.x only
+	// IE/Edge & older browsers don't support the :scope pseudo-class.
+	// Support: Safari 6.0 only
+	// Safari 6.0 supports :scope but it's an alias of :root there.
+	support.scope = assert( function( el ) {
+		docElem.appendChild( el ).appendChild( document.createElement( "div" ) );
+		return typeof el.querySelectorAll !== "undefined" &&
+			!el.querySelectorAll( ":scope fieldset div" ).length;
+	} );
+
+	/* Attributes
+	---------------------------------------------------------------------- */
+
+	// Support: IE<8
+	// Verify that getAttribute really returns attributes and not properties
+	// (excepting IE8 booleans)
+	support.attributes = assert( function( el ) {
+		el.className = "i";
+		return !el.getAttribute( "className" );
+	} );
+
+	/* getElement(s)By*
+	---------------------------------------------------------------------- */
+
+	// Check if getElementsByTagName("*") returns only elements
+	support.getElementsByTagName = assert( function( el ) {
+		el.appendChild( document.createComment( "" ) );
+		return !el.getElementsByTagName( "*" ).length;
+	} );
+
+	// Support: IE<9
+	support.getElementsByClassName = rnative.test( document.getElementsByClassName );
+
+	// Support: IE<10
+	// Check if getElementById returns elements by name
+	// The broken getElementById methods don't pick up programmatically-set names,
+	// so use a roundabout getElementsByName test
+	support.getById = assert( function( el ) {
+		docElem.appendChild( el ).id = expando;
+		return !document.getElementsByName || !document.getElementsByName( expando ).length;
+	} );
+
+	// ID filter and find
+	if ( support.getById ) {
+		Expr.filter[ "ID" ] = function( id ) {
+			var attrId = id.replace( runescape, funescape );
+			return function( elem ) {
+				return elem.getAttribute( "id" ) === attrId;
+			};
+		};
+		Expr.find[ "ID" ] = function( id, context ) {
+			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+				var elem = context.getElementById( id );
+				return elem ? [ elem ] : [];
+			}
+		};
+	} else {
+		Expr.filter[ "ID" ] =  function( id ) {
+			var attrId = id.replace( runescape, funescape );
+			return function( elem ) {
+				var node = typeof elem.getAttributeNode !== "undefined" &&
+					elem.getAttributeNode( "id" );
+				return node && node.value === attrId;
+			};
+		};
+
+		// Support: IE 6 - 7 only
+		// getElementById is not reliable as a find shortcut
+		Expr.find[ "ID" ] = function( id, context ) {
+			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+				var node, i, elems,
+					elem = context.getElementById( id );
+
+				if ( elem ) {
+
+					// Verify the id attribute
+					node = elem.getAttributeNode( "id" );
+					if ( node && node.value === id ) {
+						return [ elem ];
+					}
+
+					// Fall back on getElementsByName
+					elems = context.getElementsByName( id );
+					i = 0;
+					while ( ( elem = elems[ i++ ] ) ) {
+						node = elem.getAttributeNode( "id" );
+						if ( node && node.value === id ) {
+							return [ elem ];
+						}
+					}
+				}
+
+				return [];
+			}
+		};
+	}
+
+	// Tag
+	Expr.find[ "TAG" ] = support.getElementsByTagName ?
+		function( tag, context ) {
+			if ( typeof context.getElementsByTagName !== "undefined" ) {
+				return context.getElementsByTagName( tag );
+
+			// DocumentFragment nodes don't have gEBTN
+			} else if ( support.qsa ) {
+				return context.querySelectorAll( tag );
+			}
+		} :
+
+		function( tag, context ) {
+			var elem,
+				tmp = [],
+				i = 0,
+
+				// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
+				results = context.getElementsByTagName( tag );
+
+			// Filter out possible comments
+			if ( tag === "*" ) {
+				while ( ( elem = results[ i++ ] ) ) {
+					if ( elem.nodeType === 1 ) {
+						tmp.push( elem );
+					}
+				}
+
+				return tmp;
+			}
+			return results;
+		};
+
+	// Class
+	Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) {
+		if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
+			return context.getElementsByClassName( className );
+		}
+	};
+
+	/* QSA/matchesSelector
+	---------------------------------------------------------------------- */
+
+	// QSA and matchesSelector support
+
+	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+	rbuggyMatches = [];
+
+	// qSa(:focus) reports false when true (Chrome 21)
+	// We allow this because of a bug in IE8/9 that throws an error
+	// whenever `document.activeElement` is accessed on an iframe
+	// So, we allow :focus to pass through QSA all the time to avoid the IE error
+	// See https://bugs.jquery.com/ticket/13378
+	rbuggyQSA = [];
+
+	if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) {
+
+		// Build QSA regex
+		// Regex strategy adopted from Diego Perini
+		assert( function( el ) {
+
+			var input;
+
+			// Select is set to empty string on purpose
+			// This is to test IE's treatment of not explicitly
+			// setting a boolean content attribute,
+			// since its presence should be enough
+			// https://bugs.jquery.com/ticket/12359
+			docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" +
+				"<select id='" + expando + "-\r\\' msallowcapture=''>" +
+				"<option selected=''></option></select>";
+
+			// Support: IE8, Opera 11-12.16
+			// Nothing should be selected when empty strings follow ^= or $= or *=
+			// The test attribute must be unknown in Opera but "safe" for WinRT
+			// https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
+			if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) {
+				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
+			}
+
+			// Support: IE8
+			// Boolean attributes and "value" are not treated correctly
+			if ( !el.querySelectorAll( "[selected]" ).length ) {
+				rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+			}
+
+			// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
+			if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
+				rbuggyQSA.push( "~=" );
+			}
+
+			// Support: IE 11+, Edge 15 - 18+
+			// IE 11/Edge don't find elements on a `[name='']` query in some cases.
+			// Adding a temporary attribute to the document before the selection works
+			// around the issue.
+			// Interestingly, IE 10 & older don't seem to have the issue.
+			input = document.createElement( "input" );
+			input.setAttribute( "name", "" );
+			el.appendChild( input );
+			if ( !el.querySelectorAll( "[name='']" ).length ) {
+				rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" +
+					whitespace + "*(?:''|\"\")" );
+			}
+
+			// Webkit/Opera - :checked should return selected option elements
+			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+			// IE8 throws error here and will not see later tests
+			if ( !el.querySelectorAll( ":checked" ).length ) {
+				rbuggyQSA.push( ":checked" );
+			}
+
+			// Support: Safari 8+, iOS 8+
+			// https://bugs.webkit.org/show_bug.cgi?id=136851
+			// In-page `selector#id sibling-combinator selector` fails
+			if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
+				rbuggyQSA.push( ".#.+[+~]" );
+			}
+
+			// Support: Firefox <=3.6 - 5 only
+			// Old Firefox doesn't throw on a badly-escaped identifier.
+			el.querySelectorAll( "\\\f" );
+			rbuggyQSA.push( "[\\r\\n\\f]" );
+		} );
+
+		assert( function( el ) {
+			el.innerHTML = "<a href='' disabled='disabled'></a>" +
+				"<select disabled='disabled'><option/></select>";
+
+			// Support: Windows 8 Native Apps
+			// The type and name attributes are restricted during .innerHTML assignment
+			var input = document.createElement( "input" );
+			input.setAttribute( "type", "hidden" );
+			el.appendChild( input ).setAttribute( "name", "D" );
+
+			// Support: IE8
+			// Enforce case-sensitivity of name attribute
+			if ( el.querySelectorAll( "[name=d]" ).length ) {
+				rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
+			}
+
+			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+			// IE8 throws error here and will not see later tests
+			if ( el.querySelectorAll( ":enabled" ).length !== 2 ) {
+				rbuggyQSA.push( ":enabled", ":disabled" );
+			}
+
+			// Support: IE9-11+
+			// IE's :disabled selector does not pick up the children of disabled fieldsets
+			docElem.appendChild( el ).disabled = true;
+			if ( el.querySelectorAll( ":disabled" ).length !== 2 ) {
+				rbuggyQSA.push( ":enabled", ":disabled" );
+			}
+
+			// Support: Opera 10 - 11 only
+			// Opera 10-11 does not throw on post-comma invalid pseudos
+			el.querySelectorAll( "*,:x" );
+			rbuggyQSA.push( ",.*:" );
+		} );
+	}
+
+	if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches ||
+		docElem.webkitMatchesSelector ||
+		docElem.mozMatchesSelector ||
+		docElem.oMatchesSelector ||
+		docElem.msMatchesSelector ) ) ) ) {
+
+		assert( function( el ) {
+
+			// Check to see if it's possible to do matchesSelector
+			// on a disconnected node (IE 9)
+			support.disconnectedMatch = matches.call( el, "*" );
+
+			// This should fail with an exception
+			// Gecko does not error, returns false instead
+			matches.call( el, "[s!='']:x" );
+			rbuggyMatches.push( "!=", pseudos );
+		} );
+	}
+
+	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) );
+	rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) );
+
+	/* Contains
+	---------------------------------------------------------------------- */
+	hasCompare = rnative.test( docElem.compareDocumentPosition );
+
+	// Element contains another
+	// Purposefully self-exclusive
+	// As in, an element does not contain itself
+	contains = hasCompare || rnative.test( docElem.contains ) ?
+		function( a, b ) {
+			var adown = a.nodeType === 9 ? a.documentElement : a,
+				bup = b && b.parentNode;
+			return a === bup || !!( bup && bup.nodeType === 1 && (
+				adown.contains ?
+					adown.contains( bup ) :
+					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
+			) );
+		} :
+		function( a, b ) {
+			if ( b ) {
+				while ( ( b = b.parentNode ) ) {
+					if ( b === a ) {
+						return true;
+					}
+				}
+			}
+			return false;
+		};
+
+	/* Sorting
+	---------------------------------------------------------------------- */
+
+	// Document order sorting
+	sortOrder = hasCompare ?
+	function( a, b ) {
+
+		// Flag for duplicate removal
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+		}
+
+		// Sort on method existence if only one input has compareDocumentPosition
+		var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
+		if ( compare ) {
+			return compare;
+		}
+
+		// Calculate position if both inputs belong to the same document
+		// Support: IE 11+, Edge 17 - 18+
+		// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+		// two documents; shallow comparisons work.
+		// eslint-disable-next-line eqeqeq
+		compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ?
+			a.compareDocumentPosition( b ) :
+
+			// Otherwise we know they are disconnected
+			1;
+
+		// Disconnected nodes
+		if ( compare & 1 ||
+			( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) {
+
+			// Choose the first element that is related to our preferred document
+			// Support: IE 11+, Edge 17 - 18+
+			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+			// two documents; shallow comparisons work.
+			// eslint-disable-next-line eqeqeq
+			if ( a == document || a.ownerDocument == preferredDoc &&
+				contains( preferredDoc, a ) ) {
+				return -1;
+			}
+
+			// Support: IE 11+, Edge 17 - 18+
+			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+			// two documents; shallow comparisons work.
+			// eslint-disable-next-line eqeqeq
+			if ( b == document || b.ownerDocument == preferredDoc &&
+				contains( preferredDoc, b ) ) {
+				return 1;
+			}
+
+			// Maintain original order
+			return sortInput ?
+				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+				0;
+		}
+
+		return compare & 4 ? -1 : 1;
+	} :
+	function( a, b ) {
+
+		// Exit early if the nodes are identical
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+		}
+
+		var cur,
+			i = 0,
+			aup = a.parentNode,
+			bup = b.parentNode,
+			ap = [ a ],
+			bp = [ b ];
+
+		// Parentless nodes are either documents or disconnected
+		if ( !aup || !bup ) {
+
+			// Support: IE 11+, Edge 17 - 18+
+			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+			// two documents; shallow comparisons work.
+			/* eslint-disable eqeqeq */
+			return a == document ? -1 :
+				b == document ? 1 :
+				/* eslint-enable eqeqeq */
+				aup ? -1 :
+				bup ? 1 :
+				sortInput ?
+				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+				0;
+
+		// If the nodes are siblings, we can do a quick check
+		} else if ( aup === bup ) {
+			return siblingCheck( a, b );
+		}
+
+		// Otherwise we need full lists of their ancestors for comparison
+		cur = a;
+		while ( ( cur = cur.parentNode ) ) {
+			ap.unshift( cur );
+		}
+		cur = b;
+		while ( ( cur = cur.parentNode ) ) {
+			bp.unshift( cur );
+		}
+
+		// Walk down the tree looking for a discrepancy
+		while ( ap[ i ] === bp[ i ] ) {
+			i++;
+		}
+
+		return i ?
+
+			// Do a sibling check if the nodes have a common ancestor
+			siblingCheck( ap[ i ], bp[ i ] ) :
+
+			// Otherwise nodes in our document sort first
+			// Support: IE 11+, Edge 17 - 18+
+			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+			// two documents; shallow comparisons work.
+			/* eslint-disable eqeqeq */
+			ap[ i ] == preferredDoc ? -1 :
+			bp[ i ] == preferredDoc ? 1 :
+			/* eslint-enable eqeqeq */
+			0;
+	};
+
+	return document;
+};
+
+Sizzle.matches = function( expr, elements ) {
+	return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+	setDocument( elem );
+
+	if ( support.matchesSelector && documentIsHTML &&
+		!nonnativeSelectorCache[ expr + " " ] &&
+		( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
+		( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {
+
+		try {
+			var ret = matches.call( elem, expr );
+
+			// IE 9's matchesSelector returns false on disconnected nodes
+			if ( ret || support.disconnectedMatch ||
+
+				// As well, disconnected nodes are said to be in a document
+				// fragment in IE 9
+				elem.document && elem.document.nodeType !== 11 ) {
+				return ret;
+			}
+		} catch ( e ) {
+			nonnativeSelectorCache( expr, true );
+		}
+	}
+
+	return Sizzle( expr, document, null, [ elem ] ).length > 0;
+};
+
+Sizzle.contains = function( context, elem ) {
+
+	// Set document vars if needed
+	// Support: IE 11+, Edge 17 - 18+
+	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+	// two documents; shallow comparisons work.
+	// eslint-disable-next-line eqeqeq
+	if ( ( context.ownerDocument || context ) != document ) {
+		setDocument( context );
+	}
+	return contains( context, elem );
+};
+
+Sizzle.attr = function( elem, name ) {
+
+	// Set document vars if needed
+	// Support: IE 11+, Edge 17 - 18+
+	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+	// two documents; shallow comparisons work.
+	// eslint-disable-next-line eqeqeq
+	if ( ( elem.ownerDocument || elem ) != document ) {
+		setDocument( elem );
+	}
+
+	var fn = Expr.attrHandle[ name.toLowerCase() ],
+
+		// Don't get fooled by Object.prototype properties (jQuery #13807)
+		val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
+			fn( elem, name, !documentIsHTML ) :
+			undefined;
+
+	return val !== undefined ?
+		val :
+		support.attributes || !documentIsHTML ?
+			elem.getAttribute( name ) :
+			( val = elem.getAttributeNode( name ) ) && val.specified ?
+				val.value :
+				null;
+};
+
+Sizzle.escape = function( sel ) {
+	return ( sel + "" ).replace( rcssescape, fcssescape );
+};
+
+Sizzle.error = function( msg ) {
+	throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Document sorting and removing duplicates
+ * @param {ArrayLike} results
+ */
+Sizzle.uniqueSort = function( results ) {
+	var elem,
+		duplicates = [],
+		j = 0,
+		i = 0;
+
+	// Unless we *know* we can detect duplicates, assume their presence
+	hasDuplicate = !support.detectDuplicates;
+	sortInput = !support.sortStable && results.slice( 0 );
+	results.sort( sortOrder );
+
+	if ( hasDuplicate ) {
+		while ( ( elem = results[ i++ ] ) ) {
+			if ( elem === results[ i ] ) {
+				j = duplicates.push( i );
+			}
+		}
+		while ( j-- ) {
+			results.splice( duplicates[ j ], 1 );
+		}
+	}
+
+	// Clear input after sorting to release objects
+	// See https://github.com/jquery/sizzle/pull/225
+	sortInput = null;
+
+	return results;
+};
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+getText = Sizzle.getText = function( elem ) {
+	var node,
+		ret = "",
+		i = 0,
+		nodeType = elem.nodeType;
+
+	if ( !nodeType ) {
+
+		// If no nodeType, this is expected to be an array
+		while ( ( node = elem[ i++ ] ) ) {
+
+			// Do not traverse comment nodes
+			ret += getText( node );
+		}
+	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+
+		// Use textContent for elements
+		// innerText usage removed for consistency of new lines (jQuery #11153)
+		if ( typeof elem.textContent === "string" ) {
+			return elem.textContent;
+		} else {
+
+			// Traverse its children
+			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+				ret += getText( elem );
+			}
+		}
+	} else if ( nodeType === 3 || nodeType === 4 ) {
+		return elem.nodeValue;
+	}
+
+	// Do not include comment or processing instruction nodes
+
+	return ret;
+};
+
+Expr = Sizzle.selectors = {
+
+	// Can be adjusted by the user
+	cacheLength: 50,
+
+	createPseudo: markFunction,
+
+	match: matchExpr,
+
+	attrHandle: {},
+
+	find: {},
+
+	relative: {
+		">": { dir: "parentNode", first: true },
+		" ": { dir: "parentNode" },
+		"+": { dir: "previousSibling", first: true },
+		"~": { dir: "previousSibling" }
+	},
+
+	preFilter: {
+		"ATTR": function( match ) {
+			match[ 1 ] = match[ 1 ].replace( runescape, funescape );
+
+			// Move the given value to match[3] whether quoted or unquoted
+			match[ 3 ] = ( match[ 3 ] || match[ 4 ] ||
+				match[ 5 ] || "" ).replace( runescape, funescape );
+
+			if ( match[ 2 ] === "~=" ) {
+				match[ 3 ] = " " + match[ 3 ] + " ";
+			}
+
+			return match.slice( 0, 4 );
+		},
+
+		"CHILD": function( match ) {
+
+			/* matches from matchExpr["CHILD"]
+				1 type (only|nth|...)
+				2 what (child|of-type)
+				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+				4 xn-component of xn+y argument ([+-]?\d*n|)
+				5 sign of xn-component
+				6 x of xn-component
+				7 sign of y-component
+				8 y of y-component
+			*/
+			match[ 1 ] = match[ 1 ].toLowerCase();
+
+			if ( match[ 1 ].slice( 0, 3 ) === "nth" ) {
+
+				// nth-* requires argument
+				if ( !match[ 3 ] ) {
+					Sizzle.error( match[ 0 ] );
+				}
+
+				// numeric x and y parameters for Expr.filter.CHILD
+				// remember that false/true cast respectively to 0/1
+				match[ 4 ] = +( match[ 4 ] ?
+					match[ 5 ] + ( match[ 6 ] || 1 ) :
+					2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) );
+				match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" );
+
+				// other types prohibit arguments
+			} else if ( match[ 3 ] ) {
+				Sizzle.error( match[ 0 ] );
+			}
+
+			return match;
+		},
+
+		"PSEUDO": function( match ) {
+			var excess,
+				unquoted = !match[ 6 ] && match[ 2 ];
+
+			if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) {
+				return null;
+			}
+
+			// Accept quoted arguments as-is
+			if ( match[ 3 ] ) {
+				match[ 2 ] = match[ 4 ] || match[ 5 ] || "";
+
+			// Strip excess characters from unquoted arguments
+			} else if ( unquoted && rpseudo.test( unquoted ) &&
+
+				// Get excess from tokenize (recursively)
+				( excess = tokenize( unquoted, true ) ) &&
+
+				// advance to the next closing parenthesis
+				( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) {
+
+				// excess is a negative index
+				match[ 0 ] = match[ 0 ].slice( 0, excess );
+				match[ 2 ] = unquoted.slice( 0, excess );
+			}
+
+			// Return only captures needed by the pseudo filter method (type and argument)
+			return match.slice( 0, 3 );
+		}
+	},
+
+	filter: {
+
+		"TAG": function( nodeNameSelector ) {
+			var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+			return nodeNameSelector === "*" ?
+				function() {
+					return true;
+				} :
+				function( elem ) {
+					return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+				};
+		},
+
+		"CLASS": function( className ) {
+			var pattern = classCache[ className + " " ];
+
+			return pattern ||
+				( pattern = new RegExp( "(^|" + whitespace +
+					")" + className + "(" + whitespace + "|$)" ) ) && classCache(
+						className, function( elem ) {
+							return pattern.test(
+								typeof elem.className === "string" && elem.className ||
+								typeof elem.getAttribute !== "undefined" &&
+									elem.getAttribute( "class" ) ||
+								""
+							);
+				} );
+		},
+
+		"ATTR": function( name, operator, check ) {
+			return function( elem ) {
+				var result = Sizzle.attr( elem, name );
+
+				if ( result == null ) {
+					return operator === "!=";
+				}
+				if ( !operator ) {
+					return true;
+				}
+
+				result += "";
+
+				/* eslint-disable max-len */
+
+				return operator === "=" ? result === check :
+					operator === "!=" ? result !== check :
+					operator === "^=" ? check && result.indexOf( check ) === 0 :
+					operator === "*=" ? check && result.indexOf( check ) > -1 :
+					operator === "$=" ? check && result.slice( -check.length ) === check :
+					operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
+					operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
+					false;
+				/* eslint-enable max-len */
+
+			};
+		},
+
+		"CHILD": function( type, what, _argument, first, last ) {
+			var simple = type.slice( 0, 3 ) !== "nth",
+				forward = type.slice( -4 ) !== "last",
+				ofType = what === "of-type";
+
+			return first === 1 && last === 0 ?
+
+				// Shortcut for :nth-*(n)
+				function( elem ) {
+					return !!elem.parentNode;
+				} :
+
+				function( elem, _context, xml ) {
+					var cache, uniqueCache, outerCache, node, nodeIndex, start,
+						dir = simple !== forward ? "nextSibling" : "previousSibling",
+						parent = elem.parentNode,
+						name = ofType && elem.nodeName.toLowerCase(),
+						useCache = !xml && !ofType,
+						diff = false;
+
+					if ( parent ) {
+
+						// :(first|last|only)-(child|of-type)
+						if ( simple ) {
+							while ( dir ) {
+								node = elem;
+								while ( ( node = node[ dir ] ) ) {
+									if ( ofType ?
+										node.nodeName.toLowerCase() === name :
+										node.nodeType === 1 ) {
+
+										return false;
+									}
+								}
+
+								// Reverse direction for :only-* (if we haven't yet done so)
+								start = dir = type === "only" && !start && "nextSibling";
+							}
+							return true;
+						}
+
+						start = [ forward ? parent.firstChild : parent.lastChild ];
+
+						// non-xml :nth-child(...) stores cache data on `parent`
+						if ( forward && useCache ) {
+
+							// Seek `elem` from a previously-cached index
+
+							// ...in a gzip-friendly way
+							node = parent;
+							outerCache = node[ expando ] || ( node[ expando ] = {} );
+
+							// Support: IE <9 only
+							// Defend against cloned attroperties (jQuery gh-1709)
+							uniqueCache = outerCache[ node.uniqueID ] ||
+								( outerCache[ node.uniqueID ] = {} );
+
+							cache = uniqueCache[ type ] || [];
+							nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+							diff = nodeIndex && cache[ 2 ];
+							node = nodeIndex && parent.childNodes[ nodeIndex ];
+
+							while ( ( node = ++nodeIndex && node && node[ dir ] ||
+
+								// Fallback to seeking `elem` from the start
+								( diff = nodeIndex = 0 ) || start.pop() ) ) {
+
+								// When found, cache indexes on `parent` and break
+								if ( node.nodeType === 1 && ++diff && node === elem ) {
+									uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
+									break;
+								}
+							}
+
+						} else {
+
+							// Use previously-cached element index if available
+							if ( useCache ) {
+
+								// ...in a gzip-friendly way
+								node = elem;
+								outerCache = node[ expando ] || ( node[ expando ] = {} );
+
+								// Support: IE <9 only
+								// Defend against cloned attroperties (jQuery gh-1709)
+								uniqueCache = outerCache[ node.uniqueID ] ||
+									( outerCache[ node.uniqueID ] = {} );
+
+								cache = uniqueCache[ type ] || [];
+								nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+								diff = nodeIndex;
+							}
+
+							// xml :nth-child(...)
+							// or :nth-last-child(...) or :nth(-last)?-of-type(...)
+							if ( diff === false ) {
+
+								// Use the same loop as above to seek `elem` from the start
+								while ( ( node = ++nodeIndex && node && node[ dir ] ||
+									( diff = nodeIndex = 0 ) || start.pop() ) ) {
+
+									if ( ( ofType ?
+										node.nodeName.toLowerCase() === name :
+										node.nodeType === 1 ) &&
+										++diff ) {
+
+										// Cache the index of each encountered element
+										if ( useCache ) {
+											outerCache = node[ expando ] ||
+												( node[ expando ] = {} );
+
+											// Support: IE <9 only
+											// Defend against cloned attroperties (jQuery gh-1709)
+											uniqueCache = outerCache[ node.uniqueID ] ||
+												( outerCache[ node.uniqueID ] = {} );
+
+											uniqueCache[ type ] = [ dirruns, diff ];
+										}
+
+										if ( node === elem ) {
+											break;
+										}
+									}
+								}
+							}
+						}
+
+						// Incorporate the offset, then check against cycle size
+						diff -= last;
+						return diff === first || ( diff % first === 0 && diff / first >= 0 );
+					}
+				};
+		},
+
+		"PSEUDO": function( pseudo, argument ) {
+
+			// pseudo-class names are case-insensitive
+			// http://www.w3.org/TR/selectors/#pseudo-classes
+			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+			// Remember that setFilters inherits from pseudos
+			var args,
+				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+					Sizzle.error( "unsupported pseudo: " + pseudo );
+
+			// The user may use createPseudo to indicate that
+			// arguments are needed to create the filter function
+			// just as Sizzle does
+			if ( fn[ expando ] ) {
+				return fn( argument );
+			}
+
+			// But maintain support for old signatures
+			if ( fn.length > 1 ) {
+				args = [ pseudo, pseudo, "", argument ];
+				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+					markFunction( function( seed, matches ) {
+						var idx,
+							matched = fn( seed, argument ),
+							i = matched.length;
+						while ( i-- ) {
+							idx = indexOf( seed, matched[ i ] );
+							seed[ idx ] = !( matches[ idx ] = matched[ i ] );
+						}
+					} ) :
+					function( elem ) {
+						return fn( elem, 0, args );
+					};
+			}
+
+			return fn;
+		}
+	},
+
+	pseudos: {
+
+		// Potentially complex pseudos
+		"not": markFunction( function( selector ) {
+
+			// Trim the selector passed to compile
+			// to avoid treating leading and trailing
+			// spaces as combinators
+			var input = [],
+				results = [],
+				matcher = compile( selector.replace( rtrim, "$1" ) );
+
+			return matcher[ expando ] ?
+				markFunction( function( seed, matches, _context, xml ) {
+					var elem,
+						unmatched = matcher( seed, null, xml, [] ),
+						i = seed.length;
+
+					// Match elements unmatched by `matcher`
+					while ( i-- ) {
+						if ( ( elem = unmatched[ i ] ) ) {
+							seed[ i ] = !( matches[ i ] = elem );
+						}
+					}
+				} ) :
+				function( elem, _context, xml ) {
+					input[ 0 ] = elem;
+					matcher( input, null, xml, results );
+
+					// Don't keep the element (issue #299)
+					input[ 0 ] = null;
+					return !results.pop();
+				};
+		} ),
+
+		"has": markFunction( function( selector ) {
+			return function( elem ) {
+				return Sizzle( selector, elem ).length > 0;
+			};
+		} ),
+
+		"contains": markFunction( function( text ) {
+			text = text.replace( runescape, funescape );
+			return function( elem ) {
+				return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1;
+			};
+		} ),
+
+		// "Whether an element is represented by a :lang() selector
+		// is based solely on the element's language value
+		// being equal to the identifier C,
+		// or beginning with the identifier C immediately followed by "-".
+		// The matching of C against the element's language value is performed case-insensitively.
+		// The identifier C does not have to be a valid language name."
+		// http://www.w3.org/TR/selectors/#lang-pseudo
+		"lang": markFunction( function( lang ) {
+
+			// lang value must be a valid identifier
+			if ( !ridentifier.test( lang || "" ) ) {
+				Sizzle.error( "unsupported lang: " + lang );
+			}
+			lang = lang.replace( runescape, funescape ).toLowerCase();
+			return function( elem ) {
+				var elemLang;
+				do {
+					if ( ( elemLang = documentIsHTML ?
+						elem.lang :
+						elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) {
+
+						elemLang = elemLang.toLowerCase();
+						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+					}
+				} while ( ( elem = elem.parentNode ) && elem.nodeType === 1 );
+				return false;
+			};
+		} ),
+
+		// Miscellaneous
+		"target": function( elem ) {
+			var hash = window.location && window.location.hash;
+			return hash && hash.slice( 1 ) === elem.id;
+		},
+
+		"root": function( elem ) {
+			return elem === docElem;
+		},
+
+		"focus": function( elem ) {
+			return elem === document.activeElement &&
+				( !document.hasFocus || document.hasFocus() ) &&
+				!!( elem.type || elem.href || ~elem.tabIndex );
+		},
+
+		// Boolean properties
+		"enabled": createDisabledPseudo( false ),
+		"disabled": createDisabledPseudo( true ),
+
+		"checked": function( elem ) {
+
+			// In CSS3, :checked should return both checked and selected elements
+			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+			var nodeName = elem.nodeName.toLowerCase();
+			return ( nodeName === "input" && !!elem.checked ) ||
+				( nodeName === "option" && !!elem.selected );
+		},
+
+		"selected": function( elem ) {
+
+			// Accessing this property makes selected-by-default
+			// options in Safari work properly
+			if ( elem.parentNode ) {
+				// eslint-disable-next-line no-unused-expressions
+				elem.parentNode.selectedIndex;
+			}
+
+			return elem.selected === true;
+		},
+
+		// Contents
+		"empty": function( elem ) {
+
+			// http://www.w3.org/TR/selectors/#empty-pseudo
+			// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
+			//   but not by others (comment: 8; processing instruction: 7; etc.)
+			// nodeType < 6 works because attributes (2) do not appear as children
+			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+				if ( elem.nodeType < 6 ) {
+					return false;
+				}
+			}
+			return true;
+		},
+
+		"parent": function( elem ) {
+			return !Expr.pseudos[ "empty" ]( elem );
+		},
+
+		// Element/input types
+		"header": function( elem ) {
+			return rheader.test( elem.nodeName );
+		},
+
+		"input": function( elem ) {
+			return rinputs.test( elem.nodeName );
+		},
+
+		"button": function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return name === "input" && elem.type === "button" || name === "button";
+		},
+
+		"text": function( elem ) {
+			var attr;
+			return elem.nodeName.toLowerCase() === "input" &&
+				elem.type === "text" &&
+
+				// Support: IE<8
+				// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
+				( ( attr = elem.getAttribute( "type" ) ) == null ||
+					attr.toLowerCase() === "text" );
+		},
+
+		// Position-in-collection
+		"first": createPositionalPseudo( function() {
+			return [ 0 ];
+		} ),
+
+		"last": createPositionalPseudo( function( _matchIndexes, length ) {
+			return [ length - 1 ];
+		} ),
+
+		"eq": createPositionalPseudo( function( _matchIndexes, length, argument ) {
+			return [ argument < 0 ? argument + length : argument ];
+		} ),
+
+		"even": createPositionalPseudo( function( matchIndexes, length ) {
+			var i = 0;
+			for ( ; i < length; i += 2 ) {
+				matchIndexes.push( i );
+			}
+			return matchIndexes;
+		} ),
+
+		"odd": createPositionalPseudo( function( matchIndexes, length ) {
+			var i = 1;
+			for ( ; i < length; i += 2 ) {
+				matchIndexes.push( i );
+			}
+			return matchIndexes;
+		} ),
+
+		"lt": createPositionalPseudo( function( matchIndexes, length, argument ) {
+			var i = argument < 0 ?
+				argument + length :
+				argument > length ?
+					length :
+					argument;
+			for ( ; --i >= 0; ) {
+				matchIndexes.push( i );
+			}
+			return matchIndexes;
+		} ),
+
+		"gt": createPositionalPseudo( function( matchIndexes, length, argument ) {
+			var i = argument < 0 ? argument + length : argument;
+			for ( ; ++i < length; ) {
+				matchIndexes.push( i );
+			}
+			return matchIndexes;
+		} )
+	}
+};
+
+Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ];
+
+// Add button/input type pseudos
+for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
+	Expr.pseudos[ i ] = createInputPseudo( i );
+}
+for ( i in { submit: true, reset: true } ) {
+	Expr.pseudos[ i ] = createButtonPseudo( i );
+}
+
+// Easy API for creating new setFilters
+function setFilters() {}
+setFilters.prototype = Expr.filters = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
+tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
+	var matched, match, tokens, type,
+		soFar, groups, preFilters,
+		cached = tokenCache[ selector + " " ];
+
+	if ( cached ) {
+		return parseOnly ? 0 : cached.slice( 0 );
+	}
+
+	soFar = selector;
+	groups = [];
+	preFilters = Expr.preFilter;
+
+	while ( soFar ) {
+
+		// Comma and first run
+		if ( !matched || ( match = rcomma.exec( soFar ) ) ) {
+			if ( match ) {
+
+				// Don't consume trailing commas as valid
+				soFar = soFar.slice( match[ 0 ].length ) || soFar;
+			}
+			groups.push( ( tokens = [] ) );
+		}
+
+		matched = false;
+
+		// Combinators
+		if ( ( match = rcombinators.exec( soFar ) ) ) {
+			matched = match.shift();
+			tokens.push( {
+				value: matched,
+
+				// Cast descendant combinators to space
+				type: match[ 0 ].replace( rtrim, " " )
+			} );
+			soFar = soFar.slice( matched.length );
+		}
+
+		// Filters
+		for ( type in Expr.filter ) {
+			if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] ||
+				( match = preFilters[ type ]( match ) ) ) ) {
+				matched = match.shift();
+				tokens.push( {
+					value: matched,
+					type: type,
+					matches: match
+				} );
+				soFar = soFar.slice( matched.length );
+			}
+		}
+
+		if ( !matched ) {
+			break;
+		}
+	}
+
+	// Return the length of the invalid excess
+	// if we're just parsing
+	// Otherwise, throw an error or return tokens
+	return parseOnly ?
+		soFar.length :
+		soFar ?
+			Sizzle.error( selector ) :
+
+			// Cache the tokens
+			tokenCache( selector, groups ).slice( 0 );
+};
+
+function toSelector( tokens ) {
+	var i = 0,
+		len = tokens.length,
+		selector = "";
+	for ( ; i < len; i++ ) {
+		selector += tokens[ i ].value;
+	}
+	return selector;
+}
+
+function addCombinator( matcher, combinator, base ) {
+	var dir = combinator.dir,
+		skip = combinator.next,
+		key = skip || dir,
+		checkNonElements = base && key === "parentNode",
+		doneName = done++;
+
+	return combinator.first ?
+
+		// Check against closest ancestor/preceding element
+		function( elem, context, xml ) {
+			while ( ( elem = elem[ dir ] ) ) {
+				if ( elem.nodeType === 1 || checkNonElements ) {
+					return matcher( elem, context, xml );
+				}
+			}
+			return false;
+		} :
+
+		// Check against all ancestor/preceding elements
+		function( elem, context, xml ) {
+			var oldCache, uniqueCache, outerCache,
+				newCache = [ dirruns, doneName ];
+
+			// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
+			if ( xml ) {
+				while ( ( elem = elem[ dir ] ) ) {
+					if ( elem.nodeType === 1 || checkNonElements ) {
+						if ( matcher( elem, context, xml ) ) {
+							return true;
+						}
+					}
+				}
+			} else {
+				while ( ( elem = elem[ dir ] ) ) {
+					if ( elem.nodeType === 1 || checkNonElements ) {
+						outerCache = elem[ expando ] || ( elem[ expando ] = {} );
+
+						// Support: IE <9 only
+						// Defend against cloned attroperties (jQuery gh-1709)
+						uniqueCache = outerCache[ elem.uniqueID ] ||
+							( outerCache[ elem.uniqueID ] = {} );
+
+						if ( skip && skip === elem.nodeName.toLowerCase() ) {
+							elem = elem[ dir ] || elem;
+						} else if ( ( oldCache = uniqueCache[ key ] ) &&
+							oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
+
+							// Assign to newCache so results back-propagate to previous elements
+							return ( newCache[ 2 ] = oldCache[ 2 ] );
+						} else {
+
+							// Reuse newcache so results back-propagate to previous elements
+							uniqueCache[ key ] = newCache;
+
+							// A match means we're done; a fail means we have to keep checking
+							if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) {
+								return true;
+							}
+						}
+					}
+				}
+			}
+			return false;
+		};
+}
+
+function elementMatcher( matchers ) {
+	return matchers.length > 1 ?
+		function( elem, context, xml ) {
+			var i = matchers.length;
+			while ( i-- ) {
+				if ( !matchers[ i ]( elem, context, xml ) ) {
+					return false;
+				}
+			}
+			return true;
+		} :
+		matchers[ 0 ];
+}
+
+function multipleContexts( selector, contexts, results ) {
+	var i = 0,
+		len = contexts.length;
+	for ( ; i < len; i++ ) {
+		Sizzle( selector, contexts[ i ], results );
+	}
+	return results;
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+	var elem,
+		newUnmatched = [],
+		i = 0,
+		len = unmatched.length,
+		mapped = map != null;
+
+	for ( ; i < len; i++ ) {
+		if ( ( elem = unmatched[ i ] ) ) {
+			if ( !filter || filter( elem, context, xml ) ) {
+				newUnmatched.push( elem );
+				if ( mapped ) {
+					map.push( i );
+				}
+			}
+		}
+	}
+
+	return newUnmatched;
+}
+
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+	if ( postFilter && !postFilter[ expando ] ) {
+		postFilter = setMatcher( postFilter );
+	}
+	if ( postFinder && !postFinder[ expando ] ) {
+		postFinder = setMatcher( postFinder, postSelector );
+	}
+	return markFunction( function( seed, results, context, xml ) {
+		var temp, i, elem,
+			preMap = [],
+			postMap = [],
+			preexisting = results.length,
+
+			// Get initial elements from seed or context
+			elems = seed || multipleContexts(
+				selector || "*",
+				context.nodeType ? [ context ] : context,
+				[]
+			),
+
+			// Prefilter to get matcher input, preserving a map for seed-results synchronization
+			matcherIn = preFilter && ( seed || !selector ) ?
+				condense( elems, preMap, preFilter, context, xml ) :
+				elems,
+
+			matcherOut = matcher ?
+
+				// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+				postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+					// ...intermediate processing is necessary
+					[] :
+
+					// ...otherwise use results directly
+					results :
+				matcherIn;
+
+		// Find primary matches
+		if ( matcher ) {
+			matcher( matcherIn, matcherOut, context, xml );
+		}
+
+		// Apply postFilter
+		if ( postFilter ) {
+			temp = condense( matcherOut, postMap );
+			postFilter( temp, [], context, xml );
+
+			// Un-match failing elements by moving them back to matcherIn
+			i = temp.length;
+			while ( i-- ) {
+				if ( ( elem = temp[ i ] ) ) {
+					matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem );
+				}
+			}
+		}
+
+		if ( seed ) {
+			if ( postFinder || preFilter ) {
+				if ( postFinder ) {
+
+					// Get the final matcherOut by condensing this intermediate into postFinder contexts
+					temp = [];
+					i = matcherOut.length;
+					while ( i-- ) {
+						if ( ( elem = matcherOut[ i ] ) ) {
+
+							// Restore matcherIn since elem is not yet a final match
+							temp.push( ( matcherIn[ i ] = elem ) );
+						}
+					}
+					postFinder( null, ( matcherOut = [] ), temp, xml );
+				}
+
+				// Move matched elements from seed to results to keep them synchronized
+				i = matcherOut.length;
+				while ( i-- ) {
+					if ( ( elem = matcherOut[ i ] ) &&
+						( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) {
+
+						seed[ temp ] = !( results[ temp ] = elem );
+					}
+				}
+			}
+
+		// Add elements to results, through postFinder if defined
+		} else {
+			matcherOut = condense(
+				matcherOut === results ?
+					matcherOut.splice( preexisting, matcherOut.length ) :
+					matcherOut
+			);
+			if ( postFinder ) {
+				postFinder( null, results, matcherOut, xml );
+			} else {
+				push.apply( results, matcherOut );
+			}
+		}
+	} );
+}
+
+function matcherFromTokens( tokens ) {
+	var checkContext, matcher, j,
+		len = tokens.length,
+		leadingRelative = Expr.relative[ tokens[ 0 ].type ],
+		implicitRelative = leadingRelative || Expr.relative[ " " ],
+		i = leadingRelative ? 1 : 0,
+
+		// The foundational matcher ensures that elements are reachable from top-level context(s)
+		matchContext = addCombinator( function( elem ) {
+			return elem === checkContext;
+		}, implicitRelative, true ),
+		matchAnyContext = addCombinator( function( elem ) {
+			return indexOf( checkContext, elem ) > -1;
+		}, implicitRelative, true ),
+		matchers = [ function( elem, context, xml ) {
+			var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+				( checkContext = context ).nodeType ?
+					matchContext( elem, context, xml ) :
+					matchAnyContext( elem, context, xml ) );
+
+			// Avoid hanging onto element (issue #299)
+			checkContext = null;
+			return ret;
+		} ];
+
+	for ( ; i < len; i++ ) {
+		if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) {
+			matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
+		} else {
+			matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches );
+
+			// Return special upon seeing a positional matcher
+			if ( matcher[ expando ] ) {
+
+				// Find the next relative operator (if any) for proper handling
+				j = ++i;
+				for ( ; j < len; j++ ) {
+					if ( Expr.relative[ tokens[ j ].type ] ) {
+						break;
+					}
+				}
+				return setMatcher(
+					i > 1 && elementMatcher( matchers ),
+					i > 1 && toSelector(
+
+					// If the preceding token was a descendant combinator, insert an implicit any-element `*`
+					tokens
+						.slice( 0, i - 1 )
+						.concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } )
+					).replace( rtrim, "$1" ),
+					matcher,
+					i < j && matcherFromTokens( tokens.slice( i, j ) ),
+					j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ),
+					j < len && toSelector( tokens )
+				);
+			}
+			matchers.push( matcher );
+		}
+	}
+
+	return elementMatcher( matchers );
+}
+
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+	var bySet = setMatchers.length > 0,
+		byElement = elementMatchers.length > 0,
+		superMatcher = function( seed, context, xml, results, outermost ) {
+			var elem, j, matcher,
+				matchedCount = 0,
+				i = "0",
+				unmatched = seed && [],
+				setMatched = [],
+				contextBackup = outermostContext,
+
+				// We must always have either seed elements or outermost context
+				elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ),
+
+				// Use integer dirruns iff this is the outermost matcher
+				dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ),
+				len = elems.length;
+
+			if ( outermost ) {
+
+				// Support: IE 11+, Edge 17 - 18+
+				// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+				// two documents; shallow comparisons work.
+				// eslint-disable-next-line eqeqeq
+				outermostContext = context == document || context || outermost;
+			}
+
+			// Add elements passing elementMatchers directly to results
+			// Support: IE<9, Safari
+			// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
+			for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) {
+				if ( byElement && elem ) {
+					j = 0;
+
+					// Support: IE 11+, Edge 17 - 18+
+					// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+					// two documents; shallow comparisons work.
+					// eslint-disable-next-line eqeqeq
+					if ( !context && elem.ownerDocument != document ) {
+						setDocument( elem );
+						xml = !documentIsHTML;
+					}
+					while ( ( matcher = elementMatchers[ j++ ] ) ) {
+						if ( matcher( elem, context || document, xml ) ) {
+							results.push( elem );
+							break;
+						}
+					}
+					if ( outermost ) {
+						dirruns = dirrunsUnique;
+					}
+				}
+
+				// Track unmatched elements for set filters
+				if ( bySet ) {
+
+					// They will have gone through all possible matchers
+					if ( ( elem = !matcher && elem ) ) {
+						matchedCount--;
+					}
+
+					// Lengthen the array for every element, matched or not
+					if ( seed ) {
+						unmatched.push( elem );
+					}
+				}
+			}
+
+			// `i` is now the count of elements visited above, and adding it to `matchedCount`
+			// makes the latter nonnegative.
+			matchedCount += i;
+
+			// Apply set filters to unmatched elements
+			// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
+			// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
+			// no element matchers and no seed.
+			// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
+			// case, which will result in a "00" `matchedCount` that differs from `i` but is also
+			// numerically zero.
+			if ( bySet && i !== matchedCount ) {
+				j = 0;
+				while ( ( matcher = setMatchers[ j++ ] ) ) {
+					matcher( unmatched, setMatched, context, xml );
+				}
+
+				if ( seed ) {
+
+					// Reintegrate element matches to eliminate the need for sorting
+					if ( matchedCount > 0 ) {
+						while ( i-- ) {
+							if ( !( unmatched[ i ] || setMatched[ i ] ) ) {
+								setMatched[ i ] = pop.call( results );
+							}
+						}
+					}
+
+					// Discard index placeholder values to get only actual matches
+					setMatched = condense( setMatched );
+				}
+
+				// Add matches to results
+				push.apply( results, setMatched );
+
+				// Seedless set matches succeeding multiple successful matchers stipulate sorting
+				if ( outermost && !seed && setMatched.length > 0 &&
+					( matchedCount + setMatchers.length ) > 1 ) {
+
+					Sizzle.uniqueSort( results );
+				}
+			}
+
+			// Override manipulation of globals by nested matchers
+			if ( outermost ) {
+				dirruns = dirrunsUnique;
+				outermostContext = contextBackup;
+			}
+
+			return unmatched;
+		};
+
+	return bySet ?
+		markFunction( superMatcher ) :
+		superMatcher;
+}
+
+compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
+	var i,
+		setMatchers = [],
+		elementMatchers = [],
+		cached = compilerCache[ selector + " " ];
+
+	if ( !cached ) {
+
+		// Generate a function of recursive functions that can be used to check each element
+		if ( !match ) {
+			match = tokenize( selector );
+		}
+		i = match.length;
+		while ( i-- ) {
+			cached = matcherFromTokens( match[ i ] );
+			if ( cached[ expando ] ) {
+				setMatchers.push( cached );
+			} else {
+				elementMatchers.push( cached );
+			}
+		}
+
+		// Cache the compiled function
+		cached = compilerCache(
+			selector,
+			matcherFromGroupMatchers( elementMatchers, setMatchers )
+		);
+
+		// Save selector and tokenization
+		cached.selector = selector;
+	}
+	return cached;
+};
+
+/**
+ * A low-level selection function that works with Sizzle's compiled
+ *  selector functions
+ * @param {String|Function} selector A selector or a pre-compiled
+ *  selector function built with Sizzle.compile
+ * @param {Element} context
+ * @param {Array} [results]
+ * @param {Array} [seed] A set of elements to match against
+ */
+select = Sizzle.select = function( selector, context, results, seed ) {
+	var i, tokens, token, type, find,
+		compiled = typeof selector === "function" && selector,
+		match = !seed && tokenize( ( selector = compiled.selector || selector ) );
+
+	results = results || [];
+
+	// Try to minimize operations if there is only one selector in the list and no seed
+	// (the latter of which guarantees us context)
+	if ( match.length === 1 ) {
+
+		// Reduce context if the leading compound selector is an ID
+		tokens = match[ 0 ] = match[ 0 ].slice( 0 );
+		if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" &&
+			context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) {
+
+			context = ( Expr.find[ "ID" ]( token.matches[ 0 ]
+				.replace( runescape, funescape ), context ) || [] )[ 0 ];
+			if ( !context ) {
+				return results;
+
+			// Precompiled matchers will still verify ancestry, so step up a level
+			} else if ( compiled ) {
+				context = context.parentNode;
+			}
+
+			selector = selector.slice( tokens.shift().value.length );
+		}
+
+		// Fetch a seed set for right-to-left matching
+		i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length;
+		while ( i-- ) {
+			token = tokens[ i ];
+
+			// Abort if we hit a combinator
+			if ( Expr.relative[ ( type = token.type ) ] ) {
+				break;
+			}
+			if ( ( find = Expr.find[ type ] ) ) {
+
+				// Search, expanding context for leading sibling combinators
+				if ( ( seed = find(
+					token.matches[ 0 ].replace( runescape, funescape ),
+					rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) ||
+						context
+				) ) ) {
+
+					// If seed is empty or no tokens remain, we can return early
+					tokens.splice( i, 1 );
+					selector = seed.length && toSelector( tokens );
+					if ( !selector ) {
+						push.apply( results, seed );
+						return results;
+					}
+
+					break;
+				}
+			}
+		}
+	}
+
+	// Compile and execute a filtering function if one is not provided
+	// Provide `match` to avoid retokenization if we modified the selector above
+	( compiled || compile( selector, match ) )(
+		seed,
+		context,
+		!documentIsHTML,
+		results,
+		!context || rsibling.test( selector ) && testContext( context.parentNode ) || context
+	);
+	return results;
+};
+
+// One-time assignments
+
+// Sort stability
+support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando;
+
+// Support: Chrome 14-35+
+// Always assume duplicates if they aren't passed to the comparison function
+support.detectDuplicates = !!hasDuplicate;
+
+// Initialize against the default document
+setDocument();
+
+// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
+// Detached nodes confoundingly follow *each other*
+support.sortDetached = assert( function( el ) {
+
+	// Should return 1, but returns 4 (following)
+	return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1;
+} );
+
+// Support: IE<8
+// Prevent attribute/property "interpolation"
+// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+if ( !assert( function( el ) {
+	el.innerHTML = "<a href='#'></a>";
+	return el.firstChild.getAttribute( "href" ) === "#";
+} ) ) {
+	addHandle( "type|href|height|width", function( elem, name, isXML ) {
+		if ( !isXML ) {
+			return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
+		}
+	} );
+}
+
+// Support: IE<9
+// Use defaultValue in place of getAttribute("value")
+if ( !support.attributes || !assert( function( el ) {
+	el.innerHTML = "<input/>";
+	el.firstChild.setAttribute( "value", "" );
+	return el.firstChild.getAttribute( "value" ) === "";
+} ) ) {
+	addHandle( "value", function( elem, _name, isXML ) {
+		if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
+			return elem.defaultValue;
+		}
+	} );
+}
+
+// Support: IE<9
+// Use getAttributeNode to fetch booleans when getAttribute lies
+if ( !assert( function( el ) {
+	return el.getAttribute( "disabled" ) == null;
+} ) ) {
+	addHandle( booleans, function( elem, name, isXML ) {
+		var val;
+		if ( !isXML ) {
+			return elem[ name ] === true ? name.toLowerCase() :
+				( val = elem.getAttributeNode( name ) ) && val.specified ?
+					val.value :
+					null;
+		}
+	} );
+}
+
+return Sizzle;
+
+} )( window );
+
+
+
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+
+// Deprecated
+jQuery.expr[ ":" ] = jQuery.expr.pseudos;
+jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+jQuery.escapeSelector = Sizzle.escape;
+
+
+
+
+var dir = function( elem, dir, until ) {
+	var matched = [],
+		truncate = until !== undefined;
+
+	while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
+		if ( elem.nodeType === 1 ) {
+			if ( truncate && jQuery( elem ).is( until ) ) {
+				break;
+			}
+			matched.push( elem );
+		}
+	}
+	return matched;
+};
+
+
+var siblings = function( n, elem ) {
+	var matched = [];
+
+	for ( ; n; n = n.nextSibling ) {
+		if ( n.nodeType === 1 && n !== elem ) {
+			matched.push( n );
+		}
+	}
+
+	return matched;
+};
+
+
+var rneedsContext = jQuery.expr.match.needsContext;
+
+
+
+function nodeName( elem, name ) {
+
+  return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+
+};
+var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
+
+
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, not ) {
+	if ( isFunction( qualifier ) ) {
+		return jQuery.grep( elements, function( elem, i ) {
+			return !!qualifier.call( elem, i, elem ) !== not;
+		} );
+	}
+
+	// Single element
+	if ( qualifier.nodeType ) {
+		return jQuery.grep( elements, function( elem ) {
+			return ( elem === qualifier ) !== not;
+		} );
+	}
+
+	// Arraylike of elements (jQuery, arguments, Array)
+	if ( typeof qualifier !== "string" ) {
+		return jQuery.grep( elements, function( elem ) {
+			return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
+		} );
+	}
+
+	// Filtered directly for both simple and complex selectors
+	return jQuery.filter( qualifier, elements, not );
+}
+
+jQuery.filter = function( expr, elems, not ) {
+	var elem = elems[ 0 ];
+
+	if ( not ) {
+		expr = ":not(" + expr + ")";
+	}
+
+	if ( elems.length === 1 && elem.nodeType === 1 ) {
+		return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
+	}
+
+	return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+		return elem.nodeType === 1;
+	} ) );
+};
+
+jQuery.fn.extend( {
+	find: function( selector ) {
+		var i, ret,
+			len = this.length,
+			self = this;
+
+		if ( typeof selector !== "string" ) {
+			return this.pushStack( jQuery( selector ).filter( function() {
+				for ( i = 0; i < len; i++ ) {
+					if ( jQuery.contains( self[ i ], this ) ) {
+						return true;
+					}
+				}
+			} ) );
+		}
+
+		ret = this.pushStack( [] );
+
+		for ( i = 0; i < len; i++ ) {
+			jQuery.find( selector, self[ i ], ret );
+		}
+
+		return len > 1 ? jQuery.uniqueSort( ret ) : ret;
+	},
+	filter: function( selector ) {
+		return this.pushStack( winnow( this, selector || [], false ) );
+	},
+	not: function( selector ) {
+		return this.pushStack( winnow( this, selector || [], true ) );
+	},
+	is: function( selector ) {
+		return !!winnow(
+			this,
+
+			// If this is a positional/relative selector, check membership in the returned set
+			// so $("p:first").is("p:last") won't return true for a doc with two "p".
+			typeof selector === "string" && rneedsContext.test( selector ) ?
+				jQuery( selector ) :
+				selector || [],
+			false
+		).length;
+	}
+} );
+
+
+// Initialize a jQuery object
+
+
+// A central reference to the root jQuery(document)
+var rootjQuery,
+
+	// A simple way to check for HTML strings
+	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+	// Strict HTML recognition (#11290: must start with <)
+	// Shortcut simple #id case for speed
+	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
+
+	init = jQuery.fn.init = function( selector, context, root ) {
+		var match, elem;
+
+		// HANDLE: $(""), $(null), $(undefined), $(false)
+		if ( !selector ) {
+			return this;
+		}
+
+		// Method init() accepts an alternate rootjQuery
+		// so migrate can support jQuery.sub (gh-2101)
+		root = root || rootjQuery;
+
+		// Handle HTML strings
+		if ( typeof selector === "string" ) {
+			if ( selector[ 0 ] === "<" &&
+				selector[ selector.length - 1 ] === ">" &&
+				selector.length >= 3 ) {
+
+				// Assume that strings that start and end with <> are HTML and skip the regex check
+				match = [ null, selector, null ];
+
+			} else {
+				match = rquickExpr.exec( selector );
+			}
+
+			// Match html or make sure no context is specified for #id
+			if ( match && ( match[ 1 ] || !context ) ) {
+
+				// HANDLE: $(html) -> $(array)
+				if ( match[ 1 ] ) {
+					context = context instanceof jQuery ? context[ 0 ] : context;
+
+					// Option to run scripts is true for back-compat
+					// Intentionally let the error be thrown if parseHTML is not present
+					jQuery.merge( this, jQuery.parseHTML(
+						match[ 1 ],
+						context && context.nodeType ? context.ownerDocument || context : document,
+						true
+					) );
+
+					// HANDLE: $(html, props)
+					if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
+						for ( match in context ) {
+
+							// Properties of context are called as methods if possible
+							if ( isFunction( this[ match ] ) ) {
+								this[ match ]( context[ match ] );
+
+							// ...and otherwise set as attributes
+							} else {
+								this.attr( match, context[ match ] );
+							}
+						}
+					}
+
+					return this;
+
+				// HANDLE: $(#id)
+				} else {
+					elem = document.getElementById( match[ 2 ] );
+
+					if ( elem ) {
+
+						// Inject the element directly into the jQuery object
+						this[ 0 ] = elem;
+						this.length = 1;
+					}
+					return this;
+				}
+
+			// HANDLE: $(expr, $(...))
+			} else if ( !context || context.jquery ) {
+				return ( context || root ).find( selector );
+
+			// HANDLE: $(expr, context)
+			// (which is just equivalent to: $(context).find(expr)
+			} else {
+				return this.constructor( context ).find( selector );
+			}
+
+		// HANDLE: $(DOMElement)
+		} else if ( selector.nodeType ) {
+			this[ 0 ] = selector;
+			this.length = 1;
+			return this;
+
+		// HANDLE: $(function)
+		// Shortcut for document ready
+		} else if ( isFunction( selector ) ) {
+			return root.ready !== undefined ?
+				root.ready( selector ) :
+
+				// Execute immediately if ready is not present
+				selector( jQuery );
+		}
+
+		return jQuery.makeArray( selector, this );
+	};
+
+// Give the init function the jQuery prototype for later instantiation
+init.prototype = jQuery.fn;
+
+// Initialize central reference
+rootjQuery = jQuery( document );
+
+
+var rparentsprev = /^(?:parents|prev(?:Until|All))/,
+
+	// Methods guaranteed to produce a unique set when starting from a unique set
+	guaranteedUnique = {
+		children: true,
+		contents: true,
+		next: true,
+		prev: true
+	};
+
+jQuery.fn.extend( {
+	has: function( target ) {
+		var targets = jQuery( target, this ),
+			l = targets.length;
+
+		return this.filter( function() {
+			var i = 0;
+			for ( ; i < l; i++ ) {
+				if ( jQuery.contains( this, targets[ i ] ) ) {
+					return true;
+				}
+			}
+		} );
+	},
+
+	closest: function( selectors, context ) {
+		var cur,
+			i = 0,
+			l = this.length,
+			matched = [],
+			targets = typeof selectors !== "string" && jQuery( selectors );
+
+		// Positional selectors never match, since there's no _selection_ context
+		if ( !rneedsContext.test( selectors ) ) {
+			for ( ; i < l; i++ ) {
+				for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
+
+					// Always skip document fragments
+					if ( cur.nodeType < 11 && ( targets ?
+						targets.index( cur ) > -1 :
+
+						// Don't pass non-elements to Sizzle
+						cur.nodeType === 1 &&
+							jQuery.find.matchesSelector( cur, selectors ) ) ) {
+
+						matched.push( cur );
+						break;
+					}
+				}
+			}
+		}
+
+		return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
+	},
+
+	// Determine the position of an element within the set
+	index: function( elem ) {
+
+		// No argument, return index in parent
+		if ( !elem ) {
+			return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
+		}
+
+		// Index in selector
+		if ( typeof elem === "string" ) {
+			return indexOf.call( jQuery( elem ), this[ 0 ] );
+		}
+
+		// Locate the position of the desired element
+		return indexOf.call( this,
+
+			// If it receives a jQuery object, the first element is used
+			elem.jquery ? elem[ 0 ] : elem
+		);
+	},
+
+	add: function( selector, context ) {
+		return this.pushStack(
+			jQuery.uniqueSort(
+				jQuery.merge( this.get(), jQuery( selector, context ) )
+			)
+		);
+	},
+
+	addBack: function( selector ) {
+		return this.add( selector == null ?
+			this.prevObject : this.prevObject.filter( selector )
+		);
+	}
+} );
+
+function sibling( cur, dir ) {
+	while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
+	return cur;
+}
+
+jQuery.each( {
+	parent: function( elem ) {
+		var parent = elem.parentNode;
+		return parent && parent.nodeType !== 11 ? parent : null;
+	},
+	parents: function( elem ) {
+		return dir( elem, "parentNode" );
+	},
+	parentsUntil: function( elem, _i, until ) {
+		return dir( elem, "parentNode", until );
+	},
+	next: function( elem ) {
+		return sibling( elem, "nextSibling" );
+	},
+	prev: function( elem ) {
+		return sibling( elem, "previousSibling" );
+	},
+	nextAll: function( elem ) {
+		return dir( elem, "nextSibling" );
+	},
+	prevAll: function( elem ) {
+		return dir( elem, "previousSibling" );
+	},
+	nextUntil: function( elem, _i, until ) {
+		return dir( elem, "nextSibling", until );
+	},
+	prevUntil: function( elem, _i, until ) {
+		return dir( elem, "previousSibling", until );
+	},
+	siblings: function( elem ) {
+		return siblings( ( elem.parentNode || {} ).firstChild, elem );
+	},
+	children: function( elem ) {
+		return siblings( elem.firstChild );
+	},
+	contents: function( elem ) {
+		if ( elem.contentDocument != null &&
+
+			// Support: IE 11+
+			// <object> elements with no `data` attribute has an object
+			// `contentDocument` with a `null` prototype.
+			getProto( elem.contentDocument ) ) {
+
+			return elem.contentDocument;
+		}
+
+		// Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
+		// Treat the template element as a regular one in browsers that
+		// don't support it.
+		if ( nodeName( elem, "template" ) ) {
+			elem = elem.content || elem;
+		}
+
+		return jQuery.merge( [], elem.childNodes );
+	}
+}, function( name, fn ) {
+	jQuery.fn[ name ] = function( until, selector ) {
+		var matched = jQuery.map( this, fn, until );
+
+		if ( name.slice( -5 ) !== "Until" ) {
+			selector = until;
+		}
+
+		if ( selector && typeof selector === "string" ) {
+			matched = jQuery.filter( selector, matched );
+		}
+
+		if ( this.length > 1 ) {
+
+			// Remove duplicates
+			if ( !guaranteedUnique[ name ] ) {
+				jQuery.uniqueSort( matched );
+			}
+
+			// Reverse order for parents* and prev-derivatives
+			if ( rparentsprev.test( name ) ) {
+				matched.reverse();
+			}
+		}
+
+		return this.pushStack( matched );
+	};
+} );
+var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );
+
+
+
+// Convert String-formatted options into Object-formatted ones
+function createOptions( options ) {
+	var object = {};
+	jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
+		object[ flag ] = true;
+	} );
+	return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ *	options: an optional list of space-separated options that will change how
+ *			the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ *	once:			will ensure the callback list can only be fired once (like a Deferred)
+ *
+ *	memory:			will keep track of previous values and will call any callback added
+ *					after the list has been fired right away with the latest "memorized"
+ *					values (like a Deferred)
+ *
+ *	unique:			will ensure a callback can only be added once (no duplicate in the list)
+ *
+ *	stopOnFalse:	interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+	// Convert options from String-formatted to Object-formatted if needed
+	// (we check in cache first)
+	options = typeof options === "string" ?
+		createOptions( options ) :
+		jQuery.extend( {}, options );
+
+	var // Flag to know if list is currently firing
+		firing,
+
+		// Last fire value for non-forgettable lists
+		memory,
+
+		// Flag to know if list was already fired
+		fired,
+
+		// Flag to prevent firing
+		locked,
+
+		// Actual callback list
+		list = [],
+
+		// Queue of execution data for repeatable lists
+		queue = [],
+
+		// Index of currently firing callback (modified by add/remove as needed)
+		firingIndex = -1,
+
+		// Fire callbacks
+		fire = function() {
+
+			// Enforce single-firing
+			locked = locked || options.once;
+
+			// Execute callbacks for all pending executions,
+			// respecting firingIndex overrides and runtime changes
+			fired = firing = true;
+			for ( ; queue.length; firingIndex = -1 ) {
+				memory = queue.shift();
+				while ( ++firingIndex < list.length ) {
+
+					// Run callback and check for early termination
+					if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
+						options.stopOnFalse ) {
+
+						// Jump to end and forget the data so .add doesn't re-fire
+						firingIndex = list.length;
+						memory = false;
+					}
+				}
+			}
+
+			// Forget the data if we're done with it
+			if ( !options.memory ) {
+				memory = false;
+			}
+
+			firing = false;
+
+			// Clean up if we're done firing for good
+			if ( locked ) {
+
+				// Keep an empty list if we have data for future add calls
+				if ( memory ) {
+					list = [];
+
+				// Otherwise, this object is spent
+				} else {
+					list = "";
+				}
+			}
+		},
+
+		// Actual Callbacks object
+		self = {
+
+			// Add a callback or a collection of callbacks to the list
+			add: function() {
+				if ( list ) {
+
+					// If we have memory from a past run, we should fire after adding
+					if ( memory && !firing ) {
+						firingIndex = list.length - 1;
+						queue.push( memory );
+					}
+
+					( function add( args ) {
+						jQuery.each( args, function( _, arg ) {
+							if ( isFunction( arg ) ) {
+								if ( !options.unique || !self.has( arg ) ) {
+									list.push( arg );
+								}
+							} else if ( arg && arg.length && toType( arg ) !== "string" ) {
+
+								// Inspect recursively
+								add( arg );
+							}
+						} );
+					} )( arguments );
+
+					if ( memory && !firing ) {
+						fire();
+					}
+				}
+				return this;
+			},
+
+			// Remove a callback from the list
+			remove: function() {
+				jQuery.each( arguments, function( _, arg ) {
+					var index;
+					while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+						list.splice( index, 1 );
+
+						// Handle firing indexes
+						if ( index <= firingIndex ) {
+							firingIndex--;
+						}
+					}
+				} );
+				return this;
+			},
+
+			// Check if a given callback is in the list.
+			// If no argument is given, return whether or not list has callbacks attached.
+			has: function( fn ) {
+				return fn ?
+					jQuery.inArray( fn, list ) > -1 :
+					list.length > 0;
+			},
+
+			// Remove all callbacks from the list
+			empty: function() {
+				if ( list ) {
+					list = [];
+				}
+				return this;
+			},
+
+			// Disable .fire and .add
+			// Abort any current/pending executions
+			// Clear all callbacks and values
+			disable: function() {
+				locked = queue = [];
+				list = memory = "";
+				return this;
+			},
+			disabled: function() {
+				return !list;
+			},
+
+			// Disable .fire
+			// Also disable .add unless we have memory (since it would have no effect)
+			// Abort any pending executions
+			lock: function() {
+				locked = queue = [];
+				if ( !memory && !firing ) {
+					list = memory = "";
+				}
+				return this;
+			},
+			locked: function() {
+				return !!locked;
+			},
+
+			// Call all callbacks with the given context and arguments
+			fireWith: function( context, args ) {
+				if ( !locked ) {
+					args = args || [];
+					args = [ context, args.slice ? args.slice() : args ];
+					queue.push( args );
+					if ( !firing ) {
+						fire();
+					}
+				}
+				return this;
+			},
+
+			// Call all the callbacks with the given arguments
+			fire: function() {
+				self.fireWith( this, arguments );
+				return this;
+			},
+
+			// To know if the callbacks have already been called at least once
+			fired: function() {
+				return !!fired;
+			}
+		};
+
+	return self;
+};
+
+
+function Identity( v ) {
+	return v;
+}
+function Thrower( ex ) {
+	throw ex;
+}
+
+function adoptValue( value, resolve, reject, noValue ) {
+	var method;
+
+	try {
+
+		// Check for promise aspect first to privilege synchronous behavior
+		if ( value && isFunction( ( method = value.promise ) ) ) {
+			method.call( value ).done( resolve ).fail( reject );
+
+		// Other thenables
+		} else if ( value && isFunction( ( method = value.then ) ) ) {
+			method.call( value, resolve, reject );
+
+		// Other non-thenables
+		} else {
+
+			// Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
+			// * false: [ value ].slice( 0 ) => resolve( value )
+			// * true: [ value ].slice( 1 ) => resolve()
+			resolve.apply( undefined, [ value ].slice( noValue ) );
+		}
+
+	// For Promises/A+, convert exceptions into rejections
+	// Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
+	// Deferred#then to conditionally suppress rejection.
+	} catch ( value ) {
+
+		// Support: Android 4.0 only
+		// Strict mode functions invoked without .call/.apply get global-object context
+		reject.apply( undefined, [ value ] );
+	}
+}
+
+jQuery.extend( {
+
+	Deferred: function( func ) {
+		var tuples = [
+
+				// action, add listener, callbacks,
+				// ... .then handlers, argument index, [final state]
+				[ "notify", "progress", jQuery.Callbacks( "memory" ),
+					jQuery.Callbacks( "memory" ), 2 ],
+				[ "resolve", "done", jQuery.Callbacks( "once memory" ),
+					jQuery.Callbacks( "once memory" ), 0, "resolved" ],
+				[ "reject", "fail", jQuery.Callbacks( "once memory" ),
+					jQuery.Callbacks( "once memory" ), 1, "rejected" ]
+			],
+			state = "pending",
+			promise = {
+				state: function() {
+					return state;
+				},
+				always: function() {
+					deferred.done( arguments ).fail( arguments );
+					return this;
+				},
+				"catch": function( fn ) {
+					return promise.then( null, fn );
+				},
+
+				// Keep pipe for back-compat
+				pipe: function( /* fnDone, fnFail, fnProgress */ ) {
+					var fns = arguments;
+
+					return jQuery.Deferred( function( newDefer ) {
+						jQuery.each( tuples, function( _i, tuple ) {
+
+							// Map tuples (progress, done, fail) to arguments (done, fail, progress)
+							var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
+
+							// deferred.progress(function() { bind to newDefer or newDefer.notify })
+							// deferred.done(function() { bind to newDefer or newDefer.resolve })
+							// deferred.fail(function() { bind to newDefer or newDefer.reject })
+							deferred[ tuple[ 1 ] ]( function() {
+								var returned = fn && fn.apply( this, arguments );
+								if ( returned && isFunction( returned.promise ) ) {
+									returned.promise()
+										.progress( newDefer.notify )
+										.done( newDefer.resolve )
+										.fail( newDefer.reject );
+								} else {
+									newDefer[ tuple[ 0 ] + "With" ](
+										this,
+										fn ? [ returned ] : arguments
+									);
+								}
+							} );
+						} );
+						fns = null;
+					} ).promise();
+				},
+				then: function( onFulfilled, onRejected, onProgress ) {
+					var maxDepth = 0;
+					function resolve( depth, deferred, handler, special ) {
+						return function() {
+							var that = this,
+								args = arguments,
+								mightThrow = function() {
+									var returned, then;
+
+									// Support: Promises/A+ section 2.3.3.3.3
+									// https://promisesaplus.com/#point-59
+									// Ignore double-resolution attempts
+									if ( depth < maxDepth ) {
+										return;
+									}
+
+									returned = handler.apply( that, args );
+
+									// Support: Promises/A+ section 2.3.1
+									// https://promisesaplus.com/#point-48
+									if ( returned === deferred.promise() ) {
+										throw new TypeError( "Thenable self-resolution" );
+									}
+
+									// Support: Promises/A+ sections 2.3.3.1, 3.5
+									// https://promisesaplus.com/#point-54
+									// https://promisesaplus.com/#point-75
+									// Retrieve `then` only once
+									then = returned &&
+
+										// Support: Promises/A+ section 2.3.4
+										// https://promisesaplus.com/#point-64
+										// Only check objects and functions for thenability
+										( typeof returned === "object" ||
+											typeof returned === "function" ) &&
+										returned.then;
+
+									// Handle a returned thenable
+									if ( isFunction( then ) ) {
+
+										// Special processors (notify) just wait for resolution
+										if ( special ) {
+											then.call(
+												returned,
+												resolve( maxDepth, deferred, Identity, special ),
+												resolve( maxDepth, deferred, Thrower, special )
+											);
+
+										// Normal processors (resolve) also hook into progress
+										} else {
+
+											// ...and disregard older resolution values
+											maxDepth++;
+
+											then.call(
+												returned,
+												resolve( maxDepth, deferred, Identity, special ),
+												resolve( maxDepth, deferred, Thrower, special ),
+												resolve( maxDepth, deferred, Identity,
+													deferred.notifyWith )
+											);
+										}
+
+									// Handle all other returned values
+									} else {
+
+										// Only substitute handlers pass on context
+										// and multiple values (non-spec behavior)
+										if ( handler !== Identity ) {
+											that = undefined;
+											args = [ returned ];
+										}
+
+										// Process the value(s)
+										// Default process is resolve
+										( special || deferred.resolveWith )( that, args );
+									}
+								},
+
+								// Only normal processors (resolve) catch and reject exceptions
+								process = special ?
+									mightThrow :
+									function() {
+										try {
+											mightThrow();
+										} catch ( e ) {
+
+											if ( jQuery.Deferred.exceptionHook ) {
+												jQuery.Deferred.exceptionHook( e,
+													process.stackTrace );
+											}
+
+											// Support: Promises/A+ section 2.3.3.3.4.1
+											// https://promisesaplus.com/#point-61
+											// Ignore post-resolution exceptions
+											if ( depth + 1 >= maxDepth ) {
+
+												// Only substitute handlers pass on context
+												// and multiple values (non-spec behavior)
+												if ( handler !== Thrower ) {
+													that = undefined;
+													args = [ e ];
+												}
+
+												deferred.rejectWith( that, args );
+											}
+										}
+									};
+
+							// Support: Promises/A+ section 2.3.3.3.1
+							// https://promisesaplus.com/#point-57
+							// Re-resolve promises immediately to dodge false rejection from
+							// subsequent errors
+							if ( depth ) {
+								process();
+							} else {
+
+								// Call an optional hook to record the stack, in case of exception
+								// since it's otherwise lost when execution goes async
+								if ( jQuery.Deferred.getStackHook ) {
+									process.stackTrace = jQuery.Deferred.getStackHook();
+								}
+								window.setTimeout( process );
+							}
+						};
+					}
+
+					return jQuery.Deferred( function( newDefer ) {
+
+						// progress_handlers.add( ... )
+						tuples[ 0 ][ 3 ].add(
+							resolve(
+								0,
+								newDefer,
+								isFunction( onProgress ) ?
+									onProgress :
+									Identity,
+								newDefer.notifyWith
+							)
+						);
+
+						// fulfilled_handlers.add( ... )
+						tuples[ 1 ][ 3 ].add(
+							resolve(
+								0,
+								newDefer,
+								isFunction( onFulfilled ) ?
+									onFulfilled :
+									Identity
+							)
+						);
+
+						// rejected_handlers.add( ... )
+						tuples[ 2 ][ 3 ].add(
+							resolve(
+								0,
+								newDefer,
+								isFunction( onRejected ) ?
+									onRejected :
+									Thrower
+							)
+						);
+					} ).promise();
+				},
+
+				// Get a promise for this deferred
+				// If obj is provided, the promise aspect is added to the object
+				promise: function( obj ) {
+					return obj != null ? jQuery.extend( obj, promise ) : promise;
+				}
+			},
+			deferred = {};
+
+		// Add list-specific methods
+		jQuery.each( tuples, function( i, tuple ) {
+			var list = tuple[ 2 ],
+				stateString = tuple[ 5 ];
+
+			// promise.progress = list.add
+			// promise.done = list.add
+			// promise.fail = list.add
+			promise[ tuple[ 1 ] ] = list.add;
+
+			// Handle state
+			if ( stateString ) {
+				list.add(
+					function() {
+
+						// state = "resolved" (i.e., fulfilled)
+						// state = "rejected"
+						state = stateString;
+					},
+
+					// rejected_callbacks.disable
+					// fulfilled_callbacks.disable
+					tuples[ 3 - i ][ 2 ].disable,
+
+					// rejected_handlers.disable
+					// fulfilled_handlers.disable
+					tuples[ 3 - i ][ 3 ].disable,
+
+					// progress_callbacks.lock
+					tuples[ 0 ][ 2 ].lock,
+
+					// progress_handlers.lock
+					tuples[ 0 ][ 3 ].lock
+				);
+			}
+
+			// progress_handlers.fire
+			// fulfilled_handlers.fire
+			// rejected_handlers.fire
+			list.add( tuple[ 3 ].fire );
+
+			// deferred.notify = function() { deferred.notifyWith(...) }
+			// deferred.resolve = function() { deferred.resolveWith(...) }
+			// deferred.reject = function() { deferred.rejectWith(...) }
+			deferred[ tuple[ 0 ] ] = function() {
+				deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
+				return this;
+			};
+
+			// deferred.notifyWith = list.fireWith
+			// deferred.resolveWith = list.fireWith
+			// deferred.rejectWith = list.fireWith
+			deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
+		} );
+
+		// Make the deferred a promise
+		promise.promise( deferred );
+
+		// Call given func if any
+		if ( func ) {
+			func.call( deferred, deferred );
+		}
+
+		// All done!
+		return deferred;
+	},
+
+	// Deferred helper
+	when: function( singleValue ) {
+		var
+
+			// count of uncompleted subordinates
+			remaining = arguments.length,
+
+			// count of unprocessed arguments
+			i = remaining,
+
+			// subordinate fulfillment data
+			resolveContexts = Array( i ),
+			resolveValues = slice.call( arguments ),
+
+			// the master Deferred
+			master = jQuery.Deferred(),
+
+			// subordinate callback factory
+			updateFunc = function( i ) {
+				return function( value ) {
+					resolveContexts[ i ] = this;
+					resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+					if ( !( --remaining ) ) {
+						master.resolveWith( resolveContexts, resolveValues );
+					}
+				};
+			};
+
+		// Single- and empty arguments are adopted like Promise.resolve
+		if ( remaining <= 1 ) {
+			adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject,
+				!remaining );
+
+			// Use .then() to unwrap secondary thenables (cf. gh-3000)
+			if ( master.state() === "pending" ||
+				isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
+
+				return master.then();
+			}
+		}
+
+		// Multiple arguments are aggregated like Promise.all array elements
+		while ( i-- ) {
+			adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
+		}
+
+		return master.promise();
+	}
+} );
+
+
+// These usually indicate a programmer mistake during development,
+// warn about them ASAP rather than swallowing them by default.
+var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
+
+jQuery.Deferred.exceptionHook = function( error, stack ) {
+
+	// Support: IE 8 - 9 only
+	// Console exists when dev tools are open, which can happen at any time
+	if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
+		window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack );
+	}
+};
+
+
+
+
+jQuery.readyException = function( error ) {
+	window.setTimeout( function() {
+		throw error;
+	} );
+};
+
+
+
+
+// The deferred used on DOM ready
+var readyList = jQuery.Deferred();
+
+jQuery.fn.ready = function( fn ) {
+
+	readyList
+		.then( fn )
+
+		// Wrap jQuery.readyException in a function so that the lookup
+		// happens at the time of error handling instead of callback
+		// registration.
+		.catch( function( error ) {
+			jQuery.readyException( error );
+		} );
+
+	return this;
+};
+
+jQuery.extend( {
+
+	// Is the DOM ready to be used? Set to true once it occurs.
+	isReady: false,
+
+	// A counter to track how many items to wait for before
+	// the ready event fires. See #6781
+	readyWait: 1,
+
+	// Handle when the DOM is ready
+	ready: function( wait ) {
+
+		// Abort if there are pending holds or we're already ready
+		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+			return;
+		}
+
+		// Remember that the DOM is ready
+		jQuery.isReady = true;
+
+		// If a normal DOM Ready event fired, decrement, and wait if need be
+		if ( wait !== true && --jQuery.readyWait > 0 ) {
+			return;
+		}
+
+		// If there are functions bound, to execute
+		readyList.resolveWith( document, [ jQuery ] );
+	}
+} );
+
+jQuery.ready.then = readyList.then;
+
+// The ready event handler and self cleanup method
+function completed() {
+	document.removeEventListener( "DOMContentLoaded", completed );
+	window.removeEventListener( "load", completed );
+	jQuery.ready();
+}
+
+// Catch cases where $(document).ready() is called
+// after the browser event has already occurred.
+// Support: IE <=9 - 10 only
+// Older IE sometimes signals "interactive" too soon
+if ( document.readyState === "complete" ||
+	( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
+
+	// Handle it asynchronously to allow scripts the opportunity to delay ready
+	window.setTimeout( jQuery.ready );
+
+} else {
+
+	// Use the handy event callback
+	document.addEventListener( "DOMContentLoaded", completed );
+
+	// A fallback to window.onload, that will always work
+	window.addEventListener( "load", completed );
+}
+
+
+
+
+// Multifunctional method to get and set values of a collection
+// The value/s can optionally be executed if it's a function
+var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
+	var i = 0,
+		len = elems.length,
+		bulk = key == null;
+
+	// Sets many values
+	if ( toType( key ) === "object" ) {
+		chainable = true;
+		for ( i in key ) {
+			access( elems, fn, i, key[ i ], true, emptyGet, raw );
+		}
+
+	// Sets one value
+	} else if ( value !== undefined ) {
+		chainable = true;
+
+		if ( !isFunction( value ) ) {
+			raw = true;
+		}
+
+		if ( bulk ) {
+
+			// Bulk operations run against the entire set
+			if ( raw ) {
+				fn.call( elems, value );
+				fn = null;
+
+			// ...except when executing function values
+			} else {
+				bulk = fn;
+				fn = function( elem, _key, value ) {
+					return bulk.call( jQuery( elem ), value );
+				};
+			}
+		}
+
+		if ( fn ) {
+			for ( ; i < len; i++ ) {
+				fn(
+					elems[ i ], key, raw ?
+					value :
+					value.call( elems[ i ], i, fn( elems[ i ], key ) )
+				);
+			}
+		}
+	}
+
+	if ( chainable ) {
+		return elems;
+	}
+
+	// Gets
+	if ( bulk ) {
+		return fn.call( elems );
+	}
+
+	return len ? fn( elems[ 0 ], key ) : emptyGet;
+};
+
+
+// Matches dashed string for camelizing
+var rmsPrefix = /^-ms-/,
+	rdashAlpha = /-([a-z])/g;
+
+// Used by camelCase as callback to replace()
+function fcamelCase( _all, letter ) {
+	return letter.toUpperCase();
+}
+
+// Convert dashed to camelCase; used by the css and data modules
+// Support: IE <=9 - 11, Edge 12 - 15
+// Microsoft forgot to hump their vendor prefix (#9572)
+function camelCase( string ) {
+	return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+}
+var acceptData = function( owner ) {
+
+	// Accepts only:
+	//  - Node
+	//    - Node.ELEMENT_NODE
+	//    - Node.DOCUMENT_NODE
+	//  - Object
+	//    - Any
+	return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
+};
+
+
+
+
+function Data() {
+	this.expando = jQuery.expando + Data.uid++;
+}
+
+Data.uid = 1;
+
+Data.prototype = {
+
+	cache: function( owner ) {
+
+		// Check if the owner object already has a cache
+		var value = owner[ this.expando ];
+
+		// If not, create one
+		if ( !value ) {
+			value = {};
+
+			// We can accept data for non-element nodes in modern browsers,
+			// but we should not, see #8335.
+			// Always return an empty object.
+			if ( acceptData( owner ) ) {
+
+				// If it is a node unlikely to be stringify-ed or looped over
+				// use plain assignment
+				if ( owner.nodeType ) {
+					owner[ this.expando ] = value;
+
+				// Otherwise secure it in a non-enumerable property
+				// configurable must be true to allow the property to be
+				// deleted when data is removed
+				} else {
+					Object.defineProperty( owner, this.expando, {
+						value: value,
+						configurable: true
+					} );
+				}
+			}
+		}
+
+		return value;
+	},
+	set: function( owner, data, value ) {
+		var prop,
+			cache = this.cache( owner );
+
+		// Handle: [ owner, key, value ] args
+		// Always use camelCase key (gh-2257)
+		if ( typeof data === "string" ) {
+			cache[ camelCase( data ) ] = value;
+
+		// Handle: [ owner, { properties } ] args
+		} else {
+
+			// Copy the properties one-by-one to the cache object
+			for ( prop in data ) {
+				cache[ camelCase( prop ) ] = data[ prop ];
+			}
+		}
+		return cache;
+	},
+	get: function( owner, key ) {
+		return key === undefined ?
+			this.cache( owner ) :
+
+			// Always use camelCase key (gh-2257)
+			owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
+	},
+	access: function( owner, key, value ) {
+
+		// In cases where either:
+		//
+		//   1. No key was specified
+		//   2. A string key was specified, but no value provided
+		//
+		// Take the "read" path and allow the get method to determine
+		// which value to return, respectively either:
+		//
+		//   1. The entire cache object
+		//   2. The data stored at the key
+		//
+		if ( key === undefined ||
+				( ( key && typeof key === "string" ) && value === undefined ) ) {
+
+			return this.get( owner, key );
+		}
+
+		// When the key is not a string, or both a key and value
+		// are specified, set or extend (existing objects) with either:
+		//
+		//   1. An object of properties
+		//   2. A key and value
+		//
+		this.set( owner, key, value );
+
+		// Since the "set" path can have two possible entry points
+		// return the expected data based on which path was taken[*]
+		return value !== undefined ? value : key;
+	},
+	remove: function( owner, key ) {
+		var i,
+			cache = owner[ this.expando ];
+
+		if ( cache === undefined ) {
+			return;
+		}
+
+		if ( key !== undefined ) {
+
+			// Support array or space separated string of keys
+			if ( Array.isArray( key ) ) {
+
+				// If key is an array of keys...
+				// We always set camelCase keys, so remove that.
+				key = key.map( camelCase );
+			} else {
+				key = camelCase( key );
+
+				// If a key with the spaces exists, use it.
+				// Otherwise, create an array by matching non-whitespace
+				key = key in cache ?
+					[ key ] :
+					( key.match( rnothtmlwhite ) || [] );
+			}
+
+			i = key.length;
+
+			while ( i-- ) {
+				delete cache[ key[ i ] ];
+			}
+		}
+
+		// Remove the expando if there's no more data
+		if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
+
+			// Support: Chrome <=35 - 45
+			// Webkit & Blink performance suffers when deleting properties
+			// from DOM nodes, so set to undefined instead
+			// https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
+			if ( owner.nodeType ) {
+				owner[ this.expando ] = undefined;
+			} else {
+				delete owner[ this.expando ];
+			}
+		}
+	},
+	hasData: function( owner ) {
+		var cache = owner[ this.expando ];
+		return cache !== undefined && !jQuery.isEmptyObject( cache );
+	}
+};
+var dataPriv = new Data();
+
+var dataUser = new Data();
+
+
+
+//	Implementation Summary
+//
+//	1. Enforce API surface and semantic compatibility with 1.9.x branch
+//	2. Improve the module's maintainability by reducing the storage
+//		paths to a single mechanism.
+//	3. Use the same single mechanism to support "private" and "user" data.
+//	4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
+//	5. Avoid exposing implementation details on user objects (eg. expando properties)
+//	6. Provide a clear path for implementation upgrade to WeakMap in 2014
+
+var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
+	rmultiDash = /[A-Z]/g;
+
+function getData( data ) {
+	if ( data === "true" ) {
+		return true;
+	}
+
+	if ( data === "false" ) {
+		return false;
+	}
+
+	if ( data === "null" ) {
+		return null;
+	}
+
+	// Only convert to a number if it doesn't change the string
+	if ( data === +data + "" ) {
+		return +data;
+	}
+
+	if ( rbrace.test( data ) ) {
+		return JSON.parse( data );
+	}
+
+	return data;
+}
+
+function dataAttr( elem, key, data ) {
+	var name;
+
+	// If nothing was found internally, try to fetch any
+	// data from the HTML5 data-* attribute
+	if ( data === undefined && elem.nodeType === 1 ) {
+		name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
+		data = elem.getAttribute( name );
+
+		if ( typeof data === "string" ) {
+			try {
+				data = getData( data );
+			} catch ( e ) {}
+
+			// Make sure we set the data so it isn't changed later
+			dataUser.set( elem, key, data );
+		} else {
+			data = undefined;
+		}
+	}
+	return data;
+}
+
+jQuery.extend( {
+	hasData: function( elem ) {
+		return dataUser.hasData( elem ) || dataPriv.hasData( elem );
+	},
+
+	data: function( elem, name, data ) {
+		return dataUser.access( elem, name, data );
+	},
+
+	removeData: function( elem, name ) {
+		dataUser.remove( elem, name );
+	},
+
+	// TODO: Now that all calls to _data and _removeData have been replaced
+	// with direct calls to dataPriv methods, these can be deprecated.
+	_data: function( elem, name, data ) {
+		return dataPriv.access( elem, name, data );
+	},
+
+	_removeData: function( elem, name ) {
+		dataPriv.remove( elem, name );
+	}
+} );
+
+jQuery.fn.extend( {
+	data: function( key, value ) {
+		var i, name, data,
+			elem = this[ 0 ],
+			attrs = elem && elem.attributes;
+
+		// Gets all values
+		if ( key === undefined ) {
+			if ( this.length ) {
+				data = dataUser.get( elem );
+
+				if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
+					i = attrs.length;
+					while ( i-- ) {
+
+						// Support: IE 11 only
+						// The attrs elements can be null (#14894)
+						if ( attrs[ i ] ) {
+							name = attrs[ i ].name;
+							if ( name.indexOf( "data-" ) === 0 ) {
+								name = camelCase( name.slice( 5 ) );
+								dataAttr( elem, name, data[ name ] );
+							}
+						}
+					}
+					dataPriv.set( elem, "hasDataAttrs", true );
+				}
+			}
+
+			return data;
+		}
+
+		// Sets multiple values
+		if ( typeof key === "object" ) {
+			return this.each( function() {
+				dataUser.set( this, key );
+			} );
+		}
+
+		return access( this, function( value ) {
+			var data;
+
+			// The calling jQuery object (element matches) is not empty
+			// (and therefore has an element appears at this[ 0 ]) and the
+			// `value` parameter was not undefined. An empty jQuery object
+			// will result in `undefined` for elem = this[ 0 ] which will
+			// throw an exception if an attempt to read a data cache is made.
+			if ( elem && value === undefined ) {
+
+				// Attempt to get data from the cache
+				// The key will always be camelCased in Data
+				data = dataUser.get( elem, key );
+				if ( data !== undefined ) {
+					return data;
+				}
+
+				// Attempt to "discover" the data in
+				// HTML5 custom data-* attrs
+				data = dataAttr( elem, key );
+				if ( data !== undefined ) {
+					return data;
+				}
+
+				// We tried really hard, but the data doesn't exist.
+				return;
+			}
+
+			// Set the data...
+			this.each( function() {
+
+				// We always store the camelCased key
+				dataUser.set( this, key, value );
+			} );
+		}, null, value, arguments.length > 1, null, true );
+	},
+
+	removeData: function( key ) {
+		return this.each( function() {
+			dataUser.remove( this, key );
+		} );
+	}
+} );
+
+
+jQuery.extend( {
+	queue: function( elem, type, data ) {
+		var queue;
+
+		if ( elem ) {
+			type = ( type || "fx" ) + "queue";
+			queue = dataPriv.get( elem, type );
+
+			// Speed up dequeue by getting out quickly if this is just a lookup
+			if ( data ) {
+				if ( !queue || Array.isArray( data ) ) {
+					queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
+				} else {
+					queue.push( data );
+				}
+			}
+			return queue || [];
+		}
+	},
+
+	dequeue: function( elem, type ) {
+		type = type || "fx";
+
+		var queue = jQuery.queue( elem, type ),
+			startLength = queue.length,
+			fn = queue.shift(),
+			hooks = jQuery._queueHooks( elem, type ),
+			next = function() {
+				jQuery.dequeue( elem, type );
+			};
+
+		// If the fx queue is dequeued, always remove the progress sentinel
+		if ( fn === "inprogress" ) {
+			fn = queue.shift();
+			startLength--;
+		}
+
+		if ( fn ) {
+
+			// Add a progress sentinel to prevent the fx queue from being
+			// automatically dequeued
+			if ( type === "fx" ) {
+				queue.unshift( "inprogress" );
+			}
+
+			// Clear up the last queue stop function
+			delete hooks.stop;
+			fn.call( elem, next, hooks );
+		}
+
+		if ( !startLength && hooks ) {
+			hooks.empty.fire();
+		}
+	},
+
+	// Not public - generate a queueHooks object, or return the current one
+	_queueHooks: function( elem, type ) {
+		var key = type + "queueHooks";
+		return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
+			empty: jQuery.Callbacks( "once memory" ).add( function() {
+				dataPriv.remove( elem, [ type + "queue", key ] );
+			} )
+		} );
+	}
+} );
+
+jQuery.fn.extend( {
+	queue: function( type, data ) {
+		var setter = 2;
+
+		if ( typeof type !== "string" ) {
+			data = type;
+			type = "fx";
+			setter--;
+		}
+
+		if ( arguments.length < setter ) {
+			return jQuery.queue( this[ 0 ], type );
+		}
+
+		return data === undefined ?
+			this :
+			this.each( function() {
+				var queue = jQuery.queue( this, type, data );
+
+				// Ensure a hooks for this queue
+				jQuery._queueHooks( this, type );
+
+				if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
+					jQuery.dequeue( this, type );
+				}
+			} );
+	},
+	dequeue: function( type ) {
+		return this.each( function() {
+			jQuery.dequeue( this, type );
+		} );
+	},
+	clearQueue: function( type ) {
+		return this.queue( type || "fx", [] );
+	},
+
+	// Get a promise resolved when queues of a certain type
+	// are emptied (fx is the type by default)
+	promise: function( type, obj ) {
+		var tmp,
+			count = 1,
+			defer = jQuery.Deferred(),
+			elements = this,
+			i = this.length,
+			resolve = function() {
+				if ( !( --count ) ) {
+					defer.resolveWith( elements, [ elements ] );
+				}
+			};
+
+		if ( typeof type !== "string" ) {
+			obj = type;
+			type = undefined;
+		}
+		type = type || "fx";
+
+		while ( i-- ) {
+			tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
+			if ( tmp && tmp.empty ) {
+				count++;
+				tmp.empty.add( resolve );
+			}
+		}
+		resolve();
+		return defer.promise( obj );
+	}
+} );
+var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
+
+var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
+
+
+var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
+
+var documentElement = document.documentElement;
+
+
+
+	var isAttached = function( elem ) {
+			return jQuery.contains( elem.ownerDocument, elem );
+		},
+		composed = { composed: true };
+
+	// Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only
+	// Check attachment across shadow DOM boundaries when possible (gh-3504)
+	// Support: iOS 10.0-10.2 only
+	// Early iOS 10 versions support `attachShadow` but not `getRootNode`,
+	// leading to errors. We need to check for `getRootNode`.
+	if ( documentElement.getRootNode ) {
+		isAttached = function( elem ) {
+			return jQuery.contains( elem.ownerDocument, elem ) ||
+				elem.getRootNode( composed ) === elem.ownerDocument;
+		};
+	}
+var isHiddenWithinTree = function( elem, el ) {
+
+		// isHiddenWithinTree might be called from jQuery#filter function;
+		// in that case, element will be second argument
+		elem = el || elem;
+
+		// Inline style trumps all
+		return elem.style.display === "none" ||
+			elem.style.display === "" &&
+
+			// Otherwise, check computed style
+			// Support: Firefox <=43 - 45
+			// Disconnected elements can have computed display: none, so first confirm that elem is
+			// in the document.
+			isAttached( elem ) &&
+
+			jQuery.css( elem, "display" ) === "none";
+	};
+
+
+
+function adjustCSS( elem, prop, valueParts, tween ) {
+	var adjusted, scale,
+		maxIterations = 20,
+		currentValue = tween ?
+			function() {
+				return tween.cur();
+			} :
+			function() {
+				return jQuery.css( elem, prop, "" );
+			},
+		initial = currentValue(),
+		unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
+
+		// Starting value computation is required for potential unit mismatches
+		initialInUnit = elem.nodeType &&
+			( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
+			rcssNum.exec( jQuery.css( elem, prop ) );
+
+	if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
+
+		// Support: Firefox <=54
+		// Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)
+		initial = initial / 2;
+
+		// Trust units reported by jQuery.css
+		unit = unit || initialInUnit[ 3 ];
+
+		// Iteratively approximate from a nonzero starting point
+		initialInUnit = +initial || 1;
+
+		while ( maxIterations-- ) {
+
+			// Evaluate and update our best guess (doubling guesses that zero out).
+			// Finish if the scale equals or crosses 1 (making the old*new product non-positive).
+			jQuery.style( elem, prop, initialInUnit + unit );
+			if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) {
+				maxIterations = 0;
+			}
+			initialInUnit = initialInUnit / scale;
+
+		}
+
+		initialInUnit = initialInUnit * 2;
+		jQuery.style( elem, prop, initialInUnit + unit );
+
+		// Make sure we update the tween properties later on
+		valueParts = valueParts || [];
+	}
+
+	if ( valueParts ) {
+		initialInUnit = +initialInUnit || +initial || 0;
+
+		// Apply relative offset (+=/-=) if specified
+		adjusted = valueParts[ 1 ] ?
+			initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
+			+valueParts[ 2 ];
+		if ( tween ) {
+			tween.unit = unit;
+			tween.start = initialInUnit;
+			tween.end = adjusted;
+		}
+	}
+	return adjusted;
+}
+
+
+var defaultDisplayMap = {};
+
+function getDefaultDisplay( elem ) {
+	var temp,
+		doc = elem.ownerDocument,
+		nodeName = elem.nodeName,
+		display = defaultDisplayMap[ nodeName ];
+
+	if ( display ) {
+		return display;
+	}
+
+	temp = doc.body.appendChild( doc.createElement( nodeName ) );
+	display = jQuery.css( temp, "display" );
+
+	temp.parentNode.removeChild( temp );
+
+	if ( display === "none" ) {
+		display = "block";
+	}
+	defaultDisplayMap[ nodeName ] = display;
+
+	return display;
+}
+
+function showHide( elements, show ) {
+	var display, elem,
+		values = [],
+		index = 0,
+		length = elements.length;
+
+	// Determine new display value for elements that need to change
+	for ( ; index < length; index++ ) {
+		elem = elements[ index ];
+		if ( !elem.style ) {
+			continue;
+		}
+
+		display = elem.style.display;
+		if ( show ) {
+
+			// Since we force visibility upon cascade-hidden elements, an immediate (and slow)
+			// check is required in this first loop unless we have a nonempty display value (either
+			// inline or about-to-be-restored)
+			if ( display === "none" ) {
+				values[ index ] = dataPriv.get( elem, "display" ) || null;
+				if ( !values[ index ] ) {
+					elem.style.display = "";
+				}
+			}
+			if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
+				values[ index ] = getDefaultDisplay( elem );
+			}
+		} else {
+			if ( display !== "none" ) {
+				values[ index ] = "none";
+
+				// Remember what we're overwriting
+				dataPriv.set( elem, "display", display );
+			}
+		}
+	}
+
+	// Set the display of the elements in a second loop to avoid constant reflow
+	for ( index = 0; index < length; index++ ) {
+		if ( values[ index ] != null ) {
+			elements[ index ].style.display = values[ index ];
+		}
+	}
+
+	return elements;
+}
+
+jQuery.fn.extend( {
+	show: function() {
+		return showHide( this, true );
+	},
+	hide: function() {
+		return showHide( this );
+	},
+	toggle: function( state ) {
+		if ( typeof state === "boolean" ) {
+			return state ? this.show() : this.hide();
+		}
+
+		return this.each( function() {
+			if ( isHiddenWithinTree( this ) ) {
+				jQuery( this ).show();
+			} else {
+				jQuery( this ).hide();
+			}
+		} );
+	}
+} );
+var rcheckableType = ( /^(?:checkbox|radio)$/i );
+
+var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i );
+
+var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
+
+
+
+( function() {
+	var fragment = document.createDocumentFragment(),
+		div = fragment.appendChild( document.createElement( "div" ) ),
+		input = document.createElement( "input" );
+
+	// Support: Android 4.0 - 4.3 only
+	// Check state lost if the name is set (#11217)
+	// Support: Windows Web Apps (WWA)
+	// `name` and `type` must use .setAttribute for WWA (#14901)
+	input.setAttribute( "type", "radio" );
+	input.setAttribute( "checked", "checked" );
+	input.setAttribute( "name", "t" );
+
+	div.appendChild( input );
+
+	// Support: Android <=4.1 only
+	// Older WebKit doesn't clone checked state correctly in fragments
+	support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+	// Support: IE <=11 only
+	// Make sure textarea (and checkbox) defaultValue is properly cloned
+	div.innerHTML = "<textarea>x</textarea>";
+	support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
+
+	// Support: IE <=9 only
+	// IE <=9 replaces <option> tags with their contents when inserted outside of
+	// the select element.
+	div.innerHTML = "<option></option>";
+	support.option = !!div.lastChild;
+} )();
+
+
+// We have to close these tags to support XHTML (#13200)
+var wrapMap = {
+
+	// XHTML parsers do not magically insert elements in the
+	// same way that tag soup parsers do. So we cannot shorten
+	// this by omitting <tbody> or other required elements.
+	thead: [ 1, "<table>", "</table>" ],
+	col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
+	tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+	td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+
+	_default: [ 0, "", "" ]
+};
+
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// Support: IE <=9 only
+if ( !support.option ) {
+	wrapMap.optgroup = wrapMap.option = [ 1, "<select multiple='multiple'>", "</select>" ];
+}
+
+
+function getAll( context, tag ) {
+
+	// Support: IE <=9 - 11 only
+	// Use typeof to avoid zero-argument method invocation on host objects (#15151)
+	var ret;
+
+	if ( typeof context.getElementsByTagName !== "undefined" ) {
+		ret = context.getElementsByTagName( tag || "*" );
+
+	} else if ( typeof context.querySelectorAll !== "undefined" ) {
+		ret = context.querySelectorAll( tag || "*" );
+
+	} else {
+		ret = [];
+	}
+
+	if ( tag === undefined || tag && nodeName( context, tag ) ) {
+		return jQuery.merge( [ context ], ret );
+	}
+
+	return ret;
+}
+
+
+// Mark scripts as having already been evaluated
+function setGlobalEval( elems, refElements ) {
+	var i = 0,
+		l = elems.length;
+
+	for ( ; i < l; i++ ) {
+		dataPriv.set(
+			elems[ i ],
+			"globalEval",
+			!refElements || dataPriv.get( refElements[ i ], "globalEval" )
+		);
+	}
+}
+
+
+var rhtml = /<|&#?\w+;/;
+
+function buildFragment( elems, context, scripts, selection, ignored ) {
+	var elem, tmp, tag, wrap, attached, j,
+		fragment = context.createDocumentFragment(),
+		nodes = [],
+		i = 0,
+		l = elems.length;
+
+	for ( ; i < l; i++ ) {
+		elem = elems[ i ];
+
+		if ( elem || elem === 0 ) {
+
+			// Add nodes directly
+			if ( toType( elem ) === "object" ) {
+
+				// Support: Android <=4.0 only, PhantomJS 1 only
+				// push.apply(_, arraylike) throws on ancient WebKit
+				jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
+
+			// Convert non-html into a text node
+			} else if ( !rhtml.test( elem ) ) {
+				nodes.push( context.createTextNode( elem ) );
+
+			// Convert html into DOM nodes
+			} else {
+				tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
+
+				// Deserialize a standard representation
+				tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
+				wrap = wrapMap[ tag ] || wrapMap._default;
+				tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
+
+				// Descend through wrappers to the right content
+				j = wrap[ 0 ];
+				while ( j-- ) {
+					tmp = tmp.lastChild;
+				}
+
+				// Support: Android <=4.0 only, PhantomJS 1 only
+				// push.apply(_, arraylike) throws on ancient WebKit
+				jQuery.merge( nodes, tmp.childNodes );
+
+				// Remember the top-level container
+				tmp = fragment.firstChild;
+
+				// Ensure the created nodes are orphaned (#12392)
+				tmp.textContent = "";
+			}
+		}
+	}
+
+	// Remove wrapper from fragment
+	fragment.textContent = "";
+
+	i = 0;
+	while ( ( elem = nodes[ i++ ] ) ) {
+
+		// Skip elements already in the context collection (trac-4087)
+		if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
+			if ( ignored ) {
+				ignored.push( elem );
+			}
+			continue;
+		}
+
+		attached = isAttached( elem );
+
+		// Append to fragment
+		tmp = getAll( fragment.appendChild( elem ), "script" );
+
+		// Preserve script evaluation history
+		if ( attached ) {
+			setGlobalEval( tmp );
+		}
+
+		// Capture executables
+		if ( scripts ) {
+			j = 0;
+			while ( ( elem = tmp[ j++ ] ) ) {
+				if ( rscriptType.test( elem.type || "" ) ) {
+					scripts.push( elem );
+				}
+			}
+		}
+	}
+
+	return fragment;
+}
+
+
+var
+	rkeyEvent = /^key/,
+	rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
+	rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
+
+function returnTrue() {
+	return true;
+}
+
+function returnFalse() {
+	return false;
+}
+
+// Support: IE <=9 - 11+
+// focus() and blur() are asynchronous, except when they are no-op.
+// So expect focus to be synchronous when the element is already active,
+// and blur to be synchronous when the element is not already active.
+// (focus and blur are always synchronous in other supported browsers,
+// this just defines when we can count on it).
+function expectSync( elem, type ) {
+	return ( elem === safeActiveElement() ) === ( type === "focus" );
+}
+
+// Support: IE <=9 only
+// Accessing document.activeElement can throw unexpectedly
+// https://bugs.jquery.com/ticket/13393
+function safeActiveElement() {
+	try {
+		return document.activeElement;
+	} catch ( err ) { }
+}
+
+function on( elem, types, selector, data, fn, one ) {
+	var origFn, type;
+
+	// Types can be a map of types/handlers
+	if ( typeof types === "object" ) {
+
+		// ( types-Object, selector, data )
+		if ( typeof selector !== "string" ) {
+
+			// ( types-Object, data )
+			data = data || selector;
+			selector = undefined;
+		}
+		for ( type in types ) {
+			on( elem, type, selector, data, types[ type ], one );
+		}
+		return elem;
+	}
+
+	if ( data == null && fn == null ) {
+
+		// ( types, fn )
+		fn = selector;
+		data = selector = undefined;
+	} else if ( fn == null ) {
+		if ( typeof selector === "string" ) {
+
+			// ( types, selector, fn )
+			fn = data;
+			data = undefined;
+		} else {
+
+			// ( types, data, fn )
+			fn = data;
+			data = selector;
+			selector = undefined;
+		}
+	}
+	if ( fn === false ) {
+		fn = returnFalse;
+	} else if ( !fn ) {
+		return elem;
+	}
+
+	if ( one === 1 ) {
+		origFn = fn;
+		fn = function( event ) {
+
+			// Can use an empty set, since event contains the info
+			jQuery().off( event );
+			return origFn.apply( this, arguments );
+		};
+
+		// Use same guid so caller can remove using origFn
+		fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+	}
+	return elem.each( function() {
+		jQuery.event.add( this, types, fn, data, selector );
+	} );
+}
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+	global: {},
+
+	add: function( elem, types, handler, data, selector ) {
+
+		var handleObjIn, eventHandle, tmp,
+			events, t, handleObj,
+			special, handlers, type, namespaces, origType,
+			elemData = dataPriv.get( elem );
+
+		// Only attach events to objects that accept data
+		if ( !acceptData( elem ) ) {
+			return;
+		}
+
+		// Caller can pass in an object of custom data in lieu of the handler
+		if ( handler.handler ) {
+			handleObjIn = handler;
+			handler = handleObjIn.handler;
+			selector = handleObjIn.selector;
+		}
+
+		// Ensure that invalid selectors throw exceptions at attach time
+		// Evaluate against documentElement in case elem is a non-element node (e.g., document)
+		if ( selector ) {
+			jQuery.find.matchesSelector( documentElement, selector );
+		}
+
+		// Make sure that the handler has a unique ID, used to find/remove it later
+		if ( !handler.guid ) {
+			handler.guid = jQuery.guid++;
+		}
+
+		// Init the element's event structure and main handler, if this is the first
+		if ( !( events = elemData.events ) ) {
+			events = elemData.events = Object.create( null );
+		}
+		if ( !( eventHandle = elemData.handle ) ) {
+			eventHandle = elemData.handle = function( e ) {
+
+				// Discard the second event of a jQuery.event.trigger() and
+				// when an event is called after a page has unloaded
+				return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
+					jQuery.event.dispatch.apply( elem, arguments ) : undefined;
+			};
+		}
+
+		// Handle multiple events separated by a space
+		types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
+		t = types.length;
+		while ( t-- ) {
+			tmp = rtypenamespace.exec( types[ t ] ) || [];
+			type = origType = tmp[ 1 ];
+			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
+
+			// There *must* be a type, no attaching namespace-only handlers
+			if ( !type ) {
+				continue;
+			}
+
+			// If event changes its type, use the special event handlers for the changed type
+			special = jQuery.event.special[ type ] || {};
+
+			// If selector defined, determine special event api type, otherwise given type
+			type = ( selector ? special.delegateType : special.bindType ) || type;
+
+			// Update special based on newly reset type
+			special = jQuery.event.special[ type ] || {};
+
+			// handleObj is passed to all event handlers
+			handleObj = jQuery.extend( {
+				type: type,
+				origType: origType,
+				data: data,
+				handler: handler,
+				guid: handler.guid,
+				selector: selector,
+				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+				namespace: namespaces.join( "." )
+			}, handleObjIn );
+
+			// Init the event handler queue if we're the first
+			if ( !( handlers = events[ type ] ) ) {
+				handlers = events[ type ] = [];
+				handlers.delegateCount = 0;
+
+				// Only use addEventListener if the special events handler returns false
+				if ( !special.setup ||
+					special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+
+					if ( elem.addEventListener ) {
+						elem.addEventListener( type, eventHandle );
+					}
+				}
+			}
+
+			if ( special.add ) {
+				special.add.call( elem, handleObj );
+
+				if ( !handleObj.handler.guid ) {
+					handleObj.handler.guid = handler.guid;
+				}
+			}
+
+			// Add to the element's handler list, delegates in front
+			if ( selector ) {
+				handlers.splice( handlers.delegateCount++, 0, handleObj );
+			} else {
+				handlers.push( handleObj );
+			}
+
+			// Keep track of which events have ever been used, for event optimization
+			jQuery.event.global[ type ] = true;
+		}
+
+	},
+
+	// Detach an event or set of events from an element
+	remove: function( elem, types, handler, selector, mappedTypes ) {
+
+		var j, origCount, tmp,
+			events, t, handleObj,
+			special, handlers, type, namespaces, origType,
+			elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );
+
+		if ( !elemData || !( events = elemData.events ) ) {
+			return;
+		}
+
+		// Once for each type.namespace in types; type may be omitted
+		types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
+		t = types.length;
+		while ( t-- ) {
+			tmp = rtypenamespace.exec( types[ t ] ) || [];
+			type = origType = tmp[ 1 ];
+			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
+
+			// Unbind all events (on this namespace, if provided) for the element
+			if ( !type ) {
+				for ( type in events ) {
+					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+				}
+				continue;
+			}
+
+			special = jQuery.event.special[ type ] || {};
+			type = ( selector ? special.delegateType : special.bindType ) || type;
+			handlers = events[ type ] || [];
+			tmp = tmp[ 2 ] &&
+				new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );
+
+			// Remove matching events
+			origCount = j = handlers.length;
+			while ( j-- ) {
+				handleObj = handlers[ j ];
+
+				if ( ( mappedTypes || origType === handleObj.origType ) &&
+					( !handler || handler.guid === handleObj.guid ) &&
+					( !tmp || tmp.test( handleObj.namespace ) ) &&
+					( !selector || selector === handleObj.selector ||
+						selector === "**" && handleObj.selector ) ) {
+					handlers.splice( j, 1 );
+
+					if ( handleObj.selector ) {
+						handlers.delegateCount--;
+					}
+					if ( special.remove ) {
+						special.remove.call( elem, handleObj );
+					}
+				}
+			}
+
+			// Remove generic event handler if we removed something and no more handlers exist
+			// (avoids potential for endless recursion during removal of special event handlers)
+			if ( origCount && !handlers.length ) {
+				if ( !special.teardown ||
+					special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+
+					jQuery.removeEvent( elem, type, elemData.handle );
+				}
+
+				delete events[ type ];
+			}
+		}
+
+		// Remove data and the expando if it's no longer used
+		if ( jQuery.isEmptyObject( events ) ) {
+			dataPriv.remove( elem, "handle events" );
+		}
+	},
+
+	dispatch: function( nativeEvent ) {
+
+		var i, j, ret, matched, handleObj, handlerQueue,
+			args = new Array( arguments.length ),
+
+			// Make a writable jQuery.Event from the native event object
+			event = jQuery.event.fix( nativeEvent ),
+
+			handlers = (
+					dataPriv.get( this, "events" ) || Object.create( null )
+				)[ event.type ] || [],
+			special = jQuery.event.special[ event.type ] || {};
+
+		// Use the fix-ed jQuery.Event rather than the (read-only) native event
+		args[ 0 ] = event;
+
+		for ( i = 1; i < arguments.length; i++ ) {
+			args[ i ] = arguments[ i ];
+		}
+
+		event.delegateTarget = this;
+
+		// Call the preDispatch hook for the mapped type, and let it bail if desired
+		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+			return;
+		}
+
+		// Determine handlers
+		handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+		// Run delegates first; they may want to stop propagation beneath us
+		i = 0;
+		while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
+			event.currentTarget = matched.elem;
+
+			j = 0;
+			while ( ( handleObj = matched.handlers[ j++ ] ) &&
+				!event.isImmediatePropagationStopped() ) {
+
+				// If the event is namespaced, then each handler is only invoked if it is
+				// specially universal or its namespaces are a superset of the event's.
+				if ( !event.rnamespace || handleObj.namespace === false ||
+					event.rnamespace.test( handleObj.namespace ) ) {
+
+					event.handleObj = handleObj;
+					event.data = handleObj.data;
+
+					ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
+						handleObj.handler ).apply( matched.elem, args );
+
+					if ( ret !== undefined ) {
+						if ( ( event.result = ret ) === false ) {
+							event.preventDefault();
+							event.stopPropagation();
+						}
+					}
+				}
+			}
+		}
+
+		// Call the postDispatch hook for the mapped type
+		if ( special.postDispatch ) {
+			special.postDispatch.call( this, event );
+		}
+
+		return event.result;
+	},
+
+	handlers: function( event, handlers ) {
+		var i, handleObj, sel, matchedHandlers, matchedSelectors,
+			handlerQueue = [],
+			delegateCount = handlers.delegateCount,
+			cur = event.target;
+
+		// Find delegate handlers
+		if ( delegateCount &&
+
+			// Support: IE <=9
+			// Black-hole SVG <use> instance trees (trac-13180)
+			cur.nodeType &&
+
+			// Support: Firefox <=42
+			// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
+			// https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
+			// Support: IE 11 only
+			// ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
+			!( event.type === "click" && event.button >= 1 ) ) {
+
+			for ( ; cur !== this; cur = cur.parentNode || this ) {
+
+				// Don't check non-elements (#13208)
+				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
+				if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
+					matchedHandlers = [];
+					matchedSelectors = {};
+					for ( i = 0; i < delegateCount; i++ ) {
+						handleObj = handlers[ i ];
+
+						// Don't conflict with Object.prototype properties (#13203)
+						sel = handleObj.selector + " ";
+
+						if ( matchedSelectors[ sel ] === undefined ) {
+							matchedSelectors[ sel ] = handleObj.needsContext ?
+								jQuery( sel, this ).index( cur ) > -1 :
+								jQuery.find( sel, this, null, [ cur ] ).length;
+						}
+						if ( matchedSelectors[ sel ] ) {
+							matchedHandlers.push( handleObj );
+						}
+					}
+					if ( matchedHandlers.length ) {
+						handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
+					}
+				}
+			}
+		}
+
+		// Add the remaining (directly-bound) handlers
+		cur = this;
+		if ( delegateCount < handlers.length ) {
+			handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
+		}
+
+		return handlerQueue;
+	},
+
+	addProp: function( name, hook ) {
+		Object.defineProperty( jQuery.Event.prototype, name, {
+			enumerable: true,
+			configurable: true,
+
+			get: isFunction( hook ) ?
+				function() {
+					if ( this.originalEvent ) {
+							return hook( this.originalEvent );
+					}
+				} :
+				function() {
+					if ( this.originalEvent ) {
+							return this.originalEvent[ name ];
+					}
+				},
+
+			set: function( value ) {
+				Object.defineProperty( this, name, {
+					enumerable: true,
+					configurable: true,
+					writable: true,
+					value: value
+				} );
+			}
+		} );
+	},
+
+	fix: function( originalEvent ) {
+		return originalEvent[ jQuery.expando ] ?
+			originalEvent :
+			new jQuery.Event( originalEvent );
+	},
+
+	special: {
+		load: {
+
+			// Prevent triggered image.load events from bubbling to window.load
+			noBubble: true
+		},
+		click: {
+
+			// Utilize native event to ensure correct state for checkable inputs
+			setup: function( data ) {
+
+				// For mutual compressibility with _default, replace `this` access with a local var.
+				// `|| data` is dead code meant only to preserve the variable through minification.
+				var el = this || data;
+
+				// Claim the first handler
+				if ( rcheckableType.test( el.type ) &&
+					el.click && nodeName( el, "input" ) ) {
+
+					// dataPriv.set( el, "click", ... )
+					leverageNative( el, "click", returnTrue );
+				}
+
+				// Return false to allow normal processing in the caller
+				return false;
+			},
+			trigger: function( data ) {
+
+				// For mutual compressibility with _default, replace `this` access with a local var.
+				// `|| data` is dead code meant only to preserve the variable through minification.
+				var el = this || data;
+
+				// Force setup before triggering a click
+				if ( rcheckableType.test( el.type ) &&
+					el.click && nodeName( el, "input" ) ) {
+
+					leverageNative( el, "click" );
+				}
+
+				// Return non-false to allow normal event-path propagation
+				return true;
+			},
+
+			// For cross-browser consistency, suppress native .click() on links
+			// Also prevent it if we're currently inside a leveraged native-event stack
+			_default: function( event ) {
+				var target = event.target;
+				return rcheckableType.test( target.type ) &&
+					target.click && nodeName( target, "input" ) &&
+					dataPriv.get( target, "click" ) ||
+					nodeName( target, "a" );
+			}
+		},
+
+		beforeunload: {
+			postDispatch: function( event ) {
+
+				// Support: Firefox 20+
+				// Firefox doesn't alert if the returnValue field is not set.
+				if ( event.result !== undefined && event.originalEvent ) {
+					event.originalEvent.returnValue = event.result;
+				}
+			}
+		}
+	}
+};
+
+// Ensure the presence of an event listener that handles manually-triggered
+// synthetic events by interrupting progress until reinvoked in response to
+// *native* events that it fires directly, ensuring that state changes have
+// already occurred before other listeners are invoked.
+function leverageNative( el, type, expectSync ) {
+
+	// Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add
+	if ( !expectSync ) {
+		if ( dataPriv.get( el, type ) === undefined ) {
+			jQuery.event.add( el, type, returnTrue );
+		}
+		return;
+	}
+
+	// Register the controller as a special universal handler for all event namespaces
+	dataPriv.set( el, type, false );
+	jQuery.event.add( el, type, {
+		namespace: false,
+		handler: function( event ) {
+			var notAsync, result,
+				saved = dataPriv.get( this, type );
+
+			if ( ( event.isTrigger & 1 ) && this[ type ] ) {
+
+				// Interrupt processing of the outer synthetic .trigger()ed event
+				// Saved data should be false in such cases, but might be a leftover capture object
+				// from an async native handler (gh-4350)
+				if ( !saved.length ) {
+
+					// Store arguments for use when handling the inner native event
+					// There will always be at least one argument (an event object), so this array
+					// will not be confused with a leftover capture object.
+					saved = slice.call( arguments );
+					dataPriv.set( this, type, saved );
+
+					// Trigger the native event and capture its result
+					// Support: IE <=9 - 11+
+					// focus() and blur() are asynchronous
+					notAsync = expectSync( this, type );
+					this[ type ]();
+					result = dataPriv.get( this, type );
+					if ( saved !== result || notAsync ) {
+						dataPriv.set( this, type, false );
+					} else {
+						result = {};
+					}
+					if ( saved !== result ) {
+
+						// Cancel the outer synthetic event
+						event.stopImmediatePropagation();
+						event.preventDefault();
+						return result.value;
+					}
+
+				// If this is an inner synthetic event for an event with a bubbling surrogate
+				// (focus or blur), assume that the surrogate already propagated from triggering the
+				// native event and prevent that from happening again here.
+				// This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the
+				// bubbling surrogate propagates *after* the non-bubbling base), but that seems
+				// less bad than duplication.
+				} else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) {
+					event.stopPropagation();
+				}
+
+			// If this is a native event triggered above, everything is now in order
+			// Fire an inner synthetic event with the original arguments
+			} else if ( saved.length ) {
+
+				// ...and capture the result
+				dataPriv.set( this, type, {
+					value: jQuery.event.trigger(
+
+						// Support: IE <=9 - 11+
+						// Extend with the prototype to reset the above stopImmediatePropagation()
+						jQuery.extend( saved[ 0 ], jQuery.Event.prototype ),
+						saved.slice( 1 ),
+						this
+					)
+				} );
+
+				// Abort handling of the native event
+				event.stopImmediatePropagation();
+			}
+		}
+	} );
+}
+
+jQuery.removeEvent = function( elem, type, handle ) {
+
+	// This "if" is needed for plain objects
+	if ( elem.removeEventListener ) {
+		elem.removeEventListener( type, handle );
+	}
+};
+
+jQuery.Event = function( src, props ) {
+
+	// Allow instantiation without the 'new' keyword
+	if ( !( this instanceof jQuery.Event ) ) {
+		return new jQuery.Event( src, props );
+	}
+
+	// Event object
+	if ( src && src.type ) {
+		this.originalEvent = src;
+		this.type = src.type;
+
+		// Events bubbling up the document may have been marked as prevented
+		// by a handler lower down the tree; reflect the correct value.
+		this.isDefaultPrevented = src.defaultPrevented ||
+				src.defaultPrevented === undefined &&
+
+				// Support: Android <=2.3 only
+				src.returnValue === false ?
+			returnTrue :
+			returnFalse;
+
+		// Create target properties
+		// Support: Safari <=6 - 7 only
+		// Target should not be a text node (#504, #13143)
+		this.target = ( src.target && src.target.nodeType === 3 ) ?
+			src.target.parentNode :
+			src.target;
+
+		this.currentTarget = src.currentTarget;
+		this.relatedTarget = src.relatedTarget;
+
+	// Event type
+	} else {
+		this.type = src;
+	}
+
+	// Put explicitly provided properties onto the event object
+	if ( props ) {
+		jQuery.extend( this, props );
+	}
+
+	// Create a timestamp if incoming event doesn't have one
+	this.timeStamp = src && src.timeStamp || Date.now();
+
+	// Mark it as fixed
+	this[ jQuery.expando ] = true;
+};
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+	constructor: jQuery.Event,
+	isDefaultPrevented: returnFalse,
+	isPropagationStopped: returnFalse,
+	isImmediatePropagationStopped: returnFalse,
+	isSimulated: false,
+
+	preventDefault: function() {
+		var e = this.originalEvent;
+
+		this.isDefaultPrevented = returnTrue;
+
+		if ( e && !this.isSimulated ) {
+			e.preventDefault();
+		}
+	},
+	stopPropagation: function() {
+		var e = this.originalEvent;
+
+		this.isPropagationStopped = returnTrue;
+
+		if ( e && !this.isSimulated ) {
+			e.stopPropagation();
+		}
+	},
+	stopImmediatePropagation: function() {
+		var e = this.originalEvent;
+
+		this.isImmediatePropagationStopped = returnTrue;
+
+		if ( e && !this.isSimulated ) {
+			e.stopImmediatePropagation();
+		}
+
+		this.stopPropagation();
+	}
+};
+
+// Includes all common event props including KeyEvent and MouseEvent specific props
+jQuery.each( {
+	altKey: true,
+	bubbles: true,
+	cancelable: true,
+	changedTouches: true,
+	ctrlKey: true,
+	detail: true,
+	eventPhase: true,
+	metaKey: true,
+	pageX: true,
+	pageY: true,
+	shiftKey: true,
+	view: true,
+	"char": true,
+	code: true,
+	charCode: true,
+	key: true,
+	keyCode: true,
+	button: true,
+	buttons: true,
+	clientX: true,
+	clientY: true,
+	offsetX: true,
+	offsetY: true,
+	pointerId: true,
+	pointerType: true,
+	screenX: true,
+	screenY: true,
+	targetTouches: true,
+	toElement: true,
+	touches: true,
+
+	which: function( event ) {
+		var button = event.button;
+
+		// Add which for key events
+		if ( event.which == null && rkeyEvent.test( event.type ) ) {
+			return event.charCode != null ? event.charCode : event.keyCode;
+		}
+
+		// Add which for click: 1 === left; 2 === middle; 3 === right
+		if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) {
+			if ( button & 1 ) {
+				return 1;
+			}
+
+			if ( button & 2 ) {
+				return 3;
+			}
+
+			if ( button & 4 ) {
+				return 2;
+			}
+
+			return 0;
+		}
+
+		return event.which;
+	}
+}, jQuery.event.addProp );
+
+jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) {
+	jQuery.event.special[ type ] = {
+
+		// Utilize native event if possible so blur/focus sequence is correct
+		setup: function() {
+
+			// Claim the first handler
+			// dataPriv.set( this, "focus", ... )
+			// dataPriv.set( this, "blur", ... )
+			leverageNative( this, type, expectSync );
+
+			// Return false to allow normal processing in the caller
+			return false;
+		},
+		trigger: function() {
+
+			// Force setup before trigger
+			leverageNative( this, type );
+
+			// Return non-false to allow normal event-path propagation
+			return true;
+		},
+
+		delegateType: delegateType
+	};
+} );
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+// so that event delegation works in jQuery.
+// Do the same for pointerenter/pointerleave and pointerover/pointerout
+//
+// Support: Safari 7 only
+// Safari sends mouseenter too often; see:
+// https://bugs.chromium.org/p/chromium/issues/detail?id=470258
+// for the description of the bug (it existed in older Chrome versions as well).
+jQuery.each( {
+	mouseenter: "mouseover",
+	mouseleave: "mouseout",
+	pointerenter: "pointerover",
+	pointerleave: "pointerout"
+}, function( orig, fix ) {
+	jQuery.event.special[ orig ] = {
+		delegateType: fix,
+		bindType: fix,
+
+		handle: function( event ) {
+			var ret,
+				target = this,
+				related = event.relatedTarget,
+				handleObj = event.handleObj;
+
+			// For mouseenter/leave call the handler if related is outside the target.
+			// NB: No relatedTarget if the mouse left/entered the browser window
+			if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
+				event.type = handleObj.origType;
+				ret = handleObj.handler.apply( this, arguments );
+				event.type = fix;
+			}
+			return ret;
+		}
+	};
+} );
+
+jQuery.fn.extend( {
+
+	on: function( types, selector, data, fn ) {
+		return on( this, types, selector, data, fn );
+	},
+	one: function( types, selector, data, fn ) {
+		return on( this, types, selector, data, fn, 1 );
+	},
+	off: function( types, selector, fn ) {
+		var handleObj, type;
+		if ( types && types.preventDefault && types.handleObj ) {
+
+			// ( event )  dispatched jQuery.Event
+			handleObj = types.handleObj;
+			jQuery( types.delegateTarget ).off(
+				handleObj.namespace ?
+					handleObj.origType + "." + handleObj.namespace :
+					handleObj.origType,
+				handleObj.selector,
+				handleObj.handler
+			);
+			return this;
+		}
+		if ( typeof types === "object" ) {
+
+			// ( types-object [, selector] )
+			for ( type in types ) {
+				this.off( type, selector, types[ type ] );
+			}
+			return this;
+		}
+		if ( selector === false || typeof selector === "function" ) {
+
+			// ( types [, fn] )
+			fn = selector;
+			selector = undefined;
+		}
+		if ( fn === false ) {
+			fn = returnFalse;
+		}
+		return this.each( function() {
+			jQuery.event.remove( this, types, fn, selector );
+		} );
+	}
+} );
+
+
+var
+
+	// Support: IE <=10 - 11, Edge 12 - 13 only
+	// In IE/Edge using regex groups here causes severe slowdowns.
+	// See https://connect.microsoft.com/IE/feedback/details/1736512/
+	rnoInnerhtml = /<script|<style|<link/i,
+
+	// checked="checked" or checked
+	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
+
+// Prefer a tbody over its parent table for containing new rows
+function manipulationTarget( elem, content ) {
+	if ( nodeName( elem, "table" ) &&
+		nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {
+
+		return jQuery( elem ).children( "tbody" )[ 0 ] || elem;
+	}
+
+	return elem;
+}
+
+// Replace/restore the type attribute of script elements for safe DOM manipulation
+function disableScript( elem ) {
+	elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;
+	return elem;
+}
+function restoreScript( elem ) {
+	if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) {
+		elem.type = elem.type.slice( 5 );
+	} else {
+		elem.removeAttribute( "type" );
+	}
+
+	return elem;
+}
+
+function cloneCopyEvent( src, dest ) {
+	var i, l, type, pdataOld, udataOld, udataCur, events;
+
+	if ( dest.nodeType !== 1 ) {
+		return;
+	}
+
+	// 1. Copy private data: events, handlers, etc.
+	if ( dataPriv.hasData( src ) ) {
+		pdataOld = dataPriv.get( src );
+		events = pdataOld.events;
+
+		if ( events ) {
+			dataPriv.remove( dest, "handle events" );
+
+			for ( type in events ) {
+				for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+					jQuery.event.add( dest, type, events[ type ][ i ] );
+				}
+			}
+		}
+	}
+
+	// 2. Copy user data
+	if ( dataUser.hasData( src ) ) {
+		udataOld = dataUser.access( src );
+		udataCur = jQuery.extend( {}, udataOld );
+
+		dataUser.set( dest, udataCur );
+	}
+}
+
+// Fix IE bugs, see support tests
+function fixInput( src, dest ) {
+	var nodeName = dest.nodeName.toLowerCase();
+
+	// Fails to persist the checked state of a cloned checkbox or radio button.
+	if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
+		dest.checked = src.checked;
+
+	// Fails to return the selected option to the default selected state when cloning options
+	} else if ( nodeName === "input" || nodeName === "textarea" ) {
+		dest.defaultValue = src.defaultValue;
+	}
+}
+
+function domManip( collection, args, callback, ignored ) {
+
+	// Flatten any nested arrays
+	args = flat( args );
+
+	var fragment, first, scripts, hasScripts, node, doc,
+		i = 0,
+		l = collection.length,
+		iNoClone = l - 1,
+		value = args[ 0 ],
+		valueIsFunction = isFunction( value );
+
+	// We can't cloneNode fragments that contain checked, in WebKit
+	if ( valueIsFunction ||
+			( l > 1 && typeof value === "string" &&
+				!support.checkClone && rchecked.test( value ) ) ) {
+		return collection.each( function( index ) {
+			var self = collection.eq( index );
+			if ( valueIsFunction ) {
+				args[ 0 ] = value.call( this, index, self.html() );
+			}
+			domManip( self, args, callback, ignored );
+		} );
+	}
+
+	if ( l ) {
+		fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
+		first = fragment.firstChild;
+
+		if ( fragment.childNodes.length === 1 ) {
+			fragment = first;
+		}
+
+		// Require either new content or an interest in ignored elements to invoke the callback
+		if ( first || ignored ) {
+			scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
+			hasScripts = scripts.length;
+
+			// Use the original fragment for the last item
+			// instead of the first because it can end up
+			// being emptied incorrectly in certain situations (#8070).
+			for ( ; i < l; i++ ) {
+				node = fragment;
+
+				if ( i !== iNoClone ) {
+					node = jQuery.clone( node, true, true );
+
+					// Keep references to cloned scripts for later restoration
+					if ( hasScripts ) {
+
+						// Support: Android <=4.0 only, PhantomJS 1 only
+						// push.apply(_, arraylike) throws on ancient WebKit
+						jQuery.merge( scripts, getAll( node, "script" ) );
+					}
+				}
+
+				callback.call( collection[ i ], node, i );
+			}
+
+			if ( hasScripts ) {
+				doc = scripts[ scripts.length - 1 ].ownerDocument;
+
+				// Reenable scripts
+				jQuery.map( scripts, restoreScript );
+
+				// Evaluate executable scripts on first document insertion
+				for ( i = 0; i < hasScripts; i++ ) {
+					node = scripts[ i ];
+					if ( rscriptType.test( node.type || "" ) &&
+						!dataPriv.access( node, "globalEval" ) &&
+						jQuery.contains( doc, node ) ) {
+
+						if ( node.src && ( node.type || "" ).toLowerCase()  !== "module" ) {
+
+							// Optional AJAX dependency, but won't run scripts if not present
+							if ( jQuery._evalUrl && !node.noModule ) {
+								jQuery._evalUrl( node.src, {
+									nonce: node.nonce || node.getAttribute( "nonce" )
+								}, doc );
+							}
+						} else {
+							DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc );
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return collection;
+}
+
+function remove( elem, selector, keepData ) {
+	var node,
+		nodes = selector ? jQuery.filter( selector, elem ) : elem,
+		i = 0;
+
+	for ( ; ( node = nodes[ i ] ) != null; i++ ) {
+		if ( !keepData && node.nodeType === 1 ) {
+			jQuery.cleanData( getAll( node ) );
+		}
+
+		if ( node.parentNode ) {
+			if ( keepData && isAttached( node ) ) {
+				setGlobalEval( getAll( node, "script" ) );
+			}
+			node.parentNode.removeChild( node );
+		}
+	}
+
+	return elem;
+}
+
+jQuery.extend( {
+	htmlPrefilter: function( html ) {
+		return html;
+	},
+
+	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+		var i, l, srcElements, destElements,
+			clone = elem.cloneNode( true ),
+			inPage = isAttached( elem );
+
+		// Fix IE cloning issues
+		if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
+				!jQuery.isXMLDoc( elem ) ) {
+
+			// We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2
+			destElements = getAll( clone );
+			srcElements = getAll( elem );
+
+			for ( i = 0, l = srcElements.length; i < l; i++ ) {
+				fixInput( srcElements[ i ], destElements[ i ] );
+			}
+		}
+
+		// Copy the events from the original to the clone
+		if ( dataAndEvents ) {
+			if ( deepDataAndEvents ) {
+				srcElements = srcElements || getAll( elem );
+				destElements = destElements || getAll( clone );
+
+				for ( i = 0, l = srcElements.length; i < l; i++ ) {
+					cloneCopyEvent( srcElements[ i ], destElements[ i ] );
+				}
+			} else {
+				cloneCopyEvent( elem, clone );
+			}
+		}
+
+		// Preserve script evaluation history
+		destElements = getAll( clone, "script" );
+		if ( destElements.length > 0 ) {
+			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
+		}
+
+		// Return the cloned set
+		return clone;
+	},
+
+	cleanData: function( elems ) {
+		var data, elem, type,
+			special = jQuery.event.special,
+			i = 0;
+
+		for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
+			if ( acceptData( elem ) ) {
+				if ( ( data = elem[ dataPriv.expando ] ) ) {
+					if ( data.events ) {
+						for ( type in data.events ) {
+							if ( special[ type ] ) {
+								jQuery.event.remove( elem, type );
+
+							// This is a shortcut to avoid jQuery.event.remove's overhead
+							} else {
+								jQuery.removeEvent( elem, type, data.handle );
+							}
+						}
+					}
+
+					// Support: Chrome <=35 - 45+
+					// Assign undefined instead of using delete, see Data#remove
+					elem[ dataPriv.expando ] = undefined;
+				}
+				if ( elem[ dataUser.expando ] ) {
+
+					// Support: Chrome <=35 - 45+
+					// Assign undefined instead of using delete, see Data#remove
+					elem[ dataUser.expando ] = undefined;
+				}
+			}
+		}
+	}
+} );
+
+jQuery.fn.extend( {
+	detach: function( selector ) {
+		return remove( this, selector, true );
+	},
+
+	remove: function( selector ) {
+		return remove( this, selector );
+	},
+
+	text: function( value ) {
+		return access( this, function( value ) {
+			return value === undefined ?
+				jQuery.text( this ) :
+				this.empty().each( function() {
+					if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+						this.textContent = value;
+					}
+				} );
+		}, null, value, arguments.length );
+	},
+
+	append: function() {
+		return domManip( this, arguments, function( elem ) {
+			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+				var target = manipulationTarget( this, elem );
+				target.appendChild( elem );
+			}
+		} );
+	},
+
+	prepend: function() {
+		return domManip( this, arguments, function( elem ) {
+			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+				var target = manipulationTarget( this, elem );
+				target.insertBefore( elem, target.firstChild );
+			}
+		} );
+	},
+
+	before: function() {
+		return domManip( this, arguments, function( elem ) {
+			if ( this.parentNode ) {
+				this.parentNode.insertBefore( elem, this );
+			}
+		} );
+	},
+
+	after: function() {
+		return domManip( this, arguments, function( elem ) {
+			if ( this.parentNode ) {
+				this.parentNode.insertBefore( elem, this.nextSibling );
+			}
+		} );
+	},
+
+	empty: function() {
+		var elem,
+			i = 0;
+
+		for ( ; ( elem = this[ i ] ) != null; i++ ) {
+			if ( elem.nodeType === 1 ) {
+
+				// Prevent memory leaks
+				jQuery.cleanData( getAll( elem, false ) );
+
+				// Remove any remaining nodes
+				elem.textContent = "";
+			}
+		}
+
+		return this;
+	},
+
+	clone: function( dataAndEvents, deepDataAndEvents ) {
+		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+		return this.map( function() {
+			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+		} );
+	},
+
+	html: function( value ) {
+		return access( this, function( value ) {
+			var elem = this[ 0 ] || {},
+				i = 0,
+				l = this.length;
+
+			if ( value === undefined && elem.nodeType === 1 ) {
+				return elem.innerHTML;
+			}
+
+			// See if we can take a shortcut and just use innerHTML
+			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+				!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
+
+				value = jQuery.htmlPrefilter( value );
+
+				try {
+					for ( ; i < l; i++ ) {
+						elem = this[ i ] || {};
+
+						// Remove element nodes and prevent memory leaks
+						if ( elem.nodeType === 1 ) {
+							jQuery.cleanData( getAll( elem, false ) );
+							elem.innerHTML = value;
+						}
+					}
+
+					elem = 0;
+
+				// If using innerHTML throws an exception, use the fallback method
+				} catch ( e ) {}
+			}
+
+			if ( elem ) {
+				this.empty().append( value );
+			}
+		}, null, value, arguments.length );
+	},
+
+	replaceWith: function() {
+		var ignored = [];
+
+		// Make the changes, replacing each non-ignored context element with the new content
+		return domManip( this, arguments, function( elem ) {
+			var parent = this.parentNode;
+
+			if ( jQuery.inArray( this, ignored ) < 0 ) {
+				jQuery.cleanData( getAll( this ) );
+				if ( parent ) {
+					parent.replaceChild( elem, this );
+				}
+			}
+
+		// Force callback invocation
+		}, ignored );
+	}
+} );
+
+jQuery.each( {
+	appendTo: "append",
+	prependTo: "prepend",
+	insertBefore: "before",
+	insertAfter: "after",
+	replaceAll: "replaceWith"
+}, function( name, original ) {
+	jQuery.fn[ name ] = function( selector ) {
+		var elems,
+			ret = [],
+			insert = jQuery( selector ),
+			last = insert.length - 1,
+			i = 0;
+
+		for ( ; i <= last; i++ ) {
+			elems = i === last ? this : this.clone( true );
+			jQuery( insert[ i ] )[ original ]( elems );
+
+			// Support: Android <=4.0 only, PhantomJS 1 only
+			// .get() because push.apply(_, arraylike) throws on ancient WebKit
+			push.apply( ret, elems.get() );
+		}
+
+		return this.pushStack( ret );
+	};
+} );
+var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
+
+var getStyles = function( elem ) {
+
+		// Support: IE <=11 only, Firefox <=30 (#15098, #14150)
+		// IE throws on elements created in popups
+		// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
+		var view = elem.ownerDocument.defaultView;
+
+		if ( !view || !view.opener ) {
+			view = window;
+		}
+
+		return view.getComputedStyle( elem );
+	};
+
+var swap = function( elem, options, callback ) {
+	var ret, name,
+		old = {};
+
+	// Remember the old values, and insert the new ones
+	for ( name in options ) {
+		old[ name ] = elem.style[ name ];
+		elem.style[ name ] = options[ name ];
+	}
+
+	ret = callback.call( elem );
+
+	// Revert the old values
+	for ( name in options ) {
+		elem.style[ name ] = old[ name ];
+	}
+
+	return ret;
+};
+
+
+var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
+
+
+
+( function() {
+
+	// Executing both pixelPosition & boxSizingReliable tests require only one layout
+	// so they're executed at the same time to save the second computation.
+	function computeStyleTests() {
+
+		// This is a singleton, we need to execute it only once
+		if ( !div ) {
+			return;
+		}
+
+		container.style.cssText = "position:absolute;left:-11111px;width:60px;" +
+			"margin-top:1px;padding:0;border:0";
+		div.style.cssText =
+			"position:relative;display:block;box-sizing:border-box;overflow:scroll;" +
+			"margin:auto;border:1px;padding:1px;" +
+			"width:60%;top:1%";
+		documentElement.appendChild( container ).appendChild( div );
+
+		var divStyle = window.getComputedStyle( div );
+		pixelPositionVal = divStyle.top !== "1%";
+
+		// Support: Android 4.0 - 4.3 only, Firefox <=3 - 44
+		reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12;
+
+		// Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3
+		// Some styles come back with percentage values, even though they shouldn't
+		div.style.right = "60%";
+		pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36;
+
+		// Support: IE 9 - 11 only
+		// Detect misreporting of content dimensions for box-sizing:border-box elements
+		boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36;
+
+		// Support: IE 9 only
+		// Detect overflow:scroll screwiness (gh-3699)
+		// Support: Chrome <=64
+		// Don't get tricked when zoom affects offsetWidth (gh-4029)
+		div.style.position = "absolute";
+		scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12;
+
+		documentElement.removeChild( container );
+
+		// Nullify the div so it wouldn't be stored in the memory and
+		// it will also be a sign that checks already performed
+		div = null;
+	}
+
+	function roundPixelMeasures( measure ) {
+		return Math.round( parseFloat( measure ) );
+	}
+
+	var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal,
+		reliableTrDimensionsVal, reliableMarginLeftVal,
+		container = document.createElement( "div" ),
+		div = document.createElement( "div" );
+
+	// Finish early in limited (non-browser) environments
+	if ( !div.style ) {
+		return;
+	}
+
+	// Support: IE <=9 - 11 only
+	// Style of cloned element affects source element cloned (#8908)
+	div.style.backgroundClip = "content-box";
+	div.cloneNode( true ).style.backgroundClip = "";
+	support.clearCloneStyle = div.style.backgroundClip === "content-box";
+
+	jQuery.extend( support, {
+		boxSizingReliable: function() {
+			computeStyleTests();
+			return boxSizingReliableVal;
+		},
+		pixelBoxStyles: function() {
+			computeStyleTests();
+			return pixelBoxStylesVal;
+		},
+		pixelPosition: function() {
+			computeStyleTests();
+			return pixelPositionVal;
+		},
+		reliableMarginLeft: function() {
+			computeStyleTests();
+			return reliableMarginLeftVal;
+		},
+		scrollboxSize: function() {
+			computeStyleTests();
+			return scrollboxSizeVal;
+		},
+
+		// Support: IE 9 - 11+, Edge 15 - 18+
+		// IE/Edge misreport `getComputedStyle` of table rows with width/height
+		// set in CSS while `offset*` properties report correct values.
+		// Behavior in IE 9 is more subtle than in newer versions & it passes
+		// some versions of this test; make sure not to make it pass there!
+		reliableTrDimensions: function() {
+			var table, tr, trChild, trStyle;
+			if ( reliableTrDimensionsVal == null ) {
+				table = document.createElement( "table" );
+				tr = document.createElement( "tr" );
+				trChild = document.createElement( "div" );
+
+				table.style.cssText = "position:absolute;left:-11111px";
+				tr.style.height = "1px";
+				trChild.style.height = "9px";
+
+				documentElement
+					.appendChild( table )
+					.appendChild( tr )
+					.appendChild( trChild );
+
+				trStyle = window.getComputedStyle( tr );
+				reliableTrDimensionsVal = parseInt( trStyle.height ) > 3;
+
+				documentElement.removeChild( table );
+			}
+			return reliableTrDimensionsVal;
+		}
+	} );
+} )();
+
+
+function curCSS( elem, name, computed ) {
+	var width, minWidth, maxWidth, ret,
+
+		// Support: Firefox 51+
+		// Retrieving style before computed somehow
+		// fixes an issue with getting wrong values
+		// on detached elements
+		style = elem.style;
+
+	computed = computed || getStyles( elem );
+
+	// getPropertyValue is needed for:
+	//   .css('filter') (IE 9 only, #12537)
+	//   .css('--customProperty) (#3144)
+	if ( computed ) {
+		ret = computed.getPropertyValue( name ) || computed[ name ];
+
+		if ( ret === "" && !isAttached( elem ) ) {
+			ret = jQuery.style( elem, name );
+		}
+
+		// A tribute to the "awesome hack by Dean Edwards"
+		// Android Browser returns percentage for some values,
+		// but width seems to be reliably pixels.
+		// This is against the CSSOM draft spec:
+		// https://drafts.csswg.org/cssom/#resolved-values
+		if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) {
+
+			// Remember the original values
+			width = style.width;
+			minWidth = style.minWidth;
+			maxWidth = style.maxWidth;
+
+			// Put in the new values to get a computed value out
+			style.minWidth = style.maxWidth = style.width = ret;
+			ret = computed.width;
+
+			// Revert the changed values
+			style.width = width;
+			style.minWidth = minWidth;
+			style.maxWidth = maxWidth;
+		}
+	}
+
+	return ret !== undefined ?
+
+		// Support: IE <=9 - 11 only
+		// IE returns zIndex value as an integer.
+		ret + "" :
+		ret;
+}
+
+
+function addGetHookIf( conditionFn, hookFn ) {
+
+	// Define the hook, we'll check on the first run if it's really needed.
+	return {
+		get: function() {
+			if ( conditionFn() ) {
+
+				// Hook not needed (or it's not possible to use it due
+				// to missing dependency), remove it.
+				delete this.get;
+				return;
+			}
+
+			// Hook needed; redefine it so that the support test is not executed again.
+			return ( this.get = hookFn ).apply( this, arguments );
+		}
+	};
+}
+
+
+var cssPrefixes = [ "Webkit", "Moz", "ms" ],
+	emptyStyle = document.createElement( "div" ).style,
+	vendorProps = {};
+
+// Return a vendor-prefixed property or undefined
+function vendorPropName( name ) {
+
+	// Check for vendor prefixed names
+	var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
+		i = cssPrefixes.length;
+
+	while ( i-- ) {
+		name = cssPrefixes[ i ] + capName;
+		if ( name in emptyStyle ) {
+			return name;
+		}
+	}
+}
+
+// Return a potentially-mapped jQuery.cssProps or vendor prefixed property
+function finalPropName( name ) {
+	var final = jQuery.cssProps[ name ] || vendorProps[ name ];
+
+	if ( final ) {
+		return final;
+	}
+	if ( name in emptyStyle ) {
+		return name;
+	}
+	return vendorProps[ name ] = vendorPropName( name ) || name;
+}
+
+
+var
+
+	// Swappable if display is none or starts with table
+	// except "table", "table-cell", or "table-caption"
+	// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+	rcustomProp = /^--/,
+	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+	cssNormalTransform = {
+		letterSpacing: "0",
+		fontWeight: "400"
+	};
+
+function setPositiveNumber( _elem, value, subtract ) {
+
+	// Any relative (+/-) values have already been
+	// normalized at this point
+	var matches = rcssNum.exec( value );
+	return matches ?
+
+		// Guard against undefined "subtract", e.g., when used as in cssHooks
+		Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
+		value;
+}
+
+function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
+	var i = dimension === "width" ? 1 : 0,
+		extra = 0,
+		delta = 0;
+
+	// Adjustment may not be necessary
+	if ( box === ( isBorderBox ? "border" : "content" ) ) {
+		return 0;
+	}
+
+	for ( ; i < 4; i += 2 ) {
+
+		// Both box models exclude margin
+		if ( box === "margin" ) {
+			delta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
+		}
+
+		// If we get here with a content-box, we're seeking "padding" or "border" or "margin"
+		if ( !isBorderBox ) {
+
+			// Add padding
+			delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+
+			// For "border" or "margin", add border
+			if ( box !== "padding" ) {
+				delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+
+			// But still keep track of it otherwise
+			} else {
+				extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+			}
+
+		// If we get here with a border-box (content + padding + border), we're seeking "content" or
+		// "padding" or "margin"
+		} else {
+
+			// For "content", subtract padding
+			if ( box === "content" ) {
+				delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+			}
+
+			// For "content" or "padding", subtract border
+			if ( box !== "margin" ) {
+				delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+			}
+		}
+	}
+
+	// Account for positive content-box scroll gutter when requested by providing computedVal
+	if ( !isBorderBox && computedVal >= 0 ) {
+
+		// offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
+		// Assuming integer scroll gutter, subtract the rest and round down
+		delta += Math.max( 0, Math.ceil(
+			elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
+			computedVal -
+			delta -
+			extra -
+			0.5
+
+		// If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter
+		// Use an explicit zero to avoid NaN (gh-3964)
+		) ) || 0;
+	}
+
+	return delta;
+}
+
+function getWidthOrHeight( elem, dimension, extra ) {
+
+	// Start with computed style
+	var styles = getStyles( elem ),
+
+		// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322).
+		// Fake content-box until we know it's needed to know the true value.
+		boxSizingNeeded = !support.boxSizingReliable() || extra,
+		isBorderBox = boxSizingNeeded &&
+			jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
+		valueIsBorderBox = isBorderBox,
+
+		val = curCSS( elem, dimension, styles ),
+		offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 );
+
+	// Support: Firefox <=54
+	// Return a confounding non-pixel value or feign ignorance, as appropriate.
+	if ( rnumnonpx.test( val ) ) {
+		if ( !extra ) {
+			return val;
+		}
+		val = "auto";
+	}
+
+
+	// Support: IE 9 - 11 only
+	// Use offsetWidth/offsetHeight for when box sizing is unreliable.
+	// In those cases, the computed value can be trusted to be border-box.
+	if ( ( !support.boxSizingReliable() && isBorderBox ||
+
+		// Support: IE 10 - 11+, Edge 15 - 18+
+		// IE/Edge misreport `getComputedStyle` of table rows with width/height
+		// set in CSS while `offset*` properties report correct values.
+		// Interestingly, in some cases IE 9 doesn't suffer from this issue.
+		!support.reliableTrDimensions() && nodeName( elem, "tr" ) ||
+
+		// Fall back to offsetWidth/offsetHeight when value is "auto"
+		// This happens for inline elements with no explicit setting (gh-3571)
+		val === "auto" ||
+
+		// Support: Android <=4.1 - 4.3 only
+		// Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)
+		!parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) &&
+
+		// Make sure the element is visible & connected
+		elem.getClientRects().length ) {
+
+		isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
+
+		// Where available, offsetWidth/offsetHeight approximate border box dimensions.
+		// Where not available (e.g., SVG), assume unreliable box-sizing and interpret the
+		// retrieved value as a content box dimension.
+		valueIsBorderBox = offsetProp in elem;
+		if ( valueIsBorderBox ) {
+			val = elem[ offsetProp ];
+		}
+	}
+
+	// Normalize "" and auto
+	val = parseFloat( val ) || 0;
+
+	// Adjust for the element's box model
+	return ( val +
+		boxModelAdjustment(
+			elem,
+			dimension,
+			extra || ( isBorderBox ? "border" : "content" ),
+			valueIsBorderBox,
+			styles,
+
+			// Provide the current computed size to request scroll gutter calculation (gh-3589)
+			val
+		)
+	) + "px";
+}
+
+jQuery.extend( {
+
+	// Add in style property hooks for overriding the default
+	// behavior of getting and setting a style property
+	cssHooks: {
+		opacity: {
+			get: function( elem, computed ) {
+				if ( computed ) {
+
+					// We should always get a number back from opacity
+					var ret = curCSS( elem, "opacity" );
+					return ret === "" ? "1" : ret;
+				}
+			}
+		}
+	},
+
+	// Don't automatically add "px" to these possibly-unitless properties
+	cssNumber: {
+		"animationIterationCount": true,
+		"columnCount": true,
+		"fillOpacity": true,
+		"flexGrow": true,
+		"flexShrink": true,
+		"fontWeight": true,
+		"gridArea": true,
+		"gridColumn": true,
+		"gridColumnEnd": true,
+		"gridColumnStart": true,
+		"gridRow": true,
+		"gridRowEnd": true,
+		"gridRowStart": true,
+		"lineHeight": true,
+		"opacity": true,
+		"order": true,
+		"orphans": true,
+		"widows": true,
+		"zIndex": true,
+		"zoom": true
+	},
+
+	// Add in properties whose names you wish to fix before
+	// setting or getting the value
+	cssProps: {},
+
+	// Get and set the style property on a DOM Node
+	style: function( elem, name, value, extra ) {
+
+		// Don't set styles on text and comment nodes
+		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+			return;
+		}
+
+		// Make sure that we're working with the right name
+		var ret, type, hooks,
+			origName = camelCase( name ),
+			isCustomProp = rcustomProp.test( name ),
+			style = elem.style;
+
+		// Make sure that we're working with the right name. We don't
+		// want to query the value if it is a CSS custom property
+		// since they are user-defined.
+		if ( !isCustomProp ) {
+			name = finalPropName( origName );
+		}
+
+		// Gets hook for the prefixed version, then unprefixed version
+		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+		// Check if we're setting a value
+		if ( value !== undefined ) {
+			type = typeof value;
+
+			// Convert "+=" or "-=" to relative numbers (#7345)
+			if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
+				value = adjustCSS( elem, name, ret );
+
+				// Fixes bug #9237
+				type = "number";
+			}
+
+			// Make sure that null and NaN values aren't set (#7116)
+			if ( value == null || value !== value ) {
+				return;
+			}
+
+			// If a number was passed in, add the unit (except for certain CSS properties)
+			// The isCustomProp check can be removed in jQuery 4.0 when we only auto-append
+			// "px" to a few hardcoded values.
+			if ( type === "number" && !isCustomProp ) {
+				value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
+			}
+
+			// background-* props affect original clone's values
+			if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
+				style[ name ] = "inherit";
+			}
+
+			// If a hook was provided, use that value, otherwise just set the specified value
+			if ( !hooks || !( "set" in hooks ) ||
+				( value = hooks.set( elem, value, extra ) ) !== undefined ) {
+
+				if ( isCustomProp ) {
+					style.setProperty( name, value );
+				} else {
+					style[ name ] = value;
+				}
+			}
+
+		} else {
+
+			// If a hook was provided get the non-computed value from there
+			if ( hooks && "get" in hooks &&
+				( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
+
+				return ret;
+			}
+
+			// Otherwise just get the value from the style object
+			return style[ name ];
+		}
+	},
+
+	css: function( elem, name, extra, styles ) {
+		var val, num, hooks,
+			origName = camelCase( name ),
+			isCustomProp = rcustomProp.test( name );
+
+		// Make sure that we're working with the right name. We don't
+		// want to modify the value if it is a CSS custom property
+		// since they are user-defined.
+		if ( !isCustomProp ) {
+			name = finalPropName( origName );
+		}
+
+		// Try prefixed name followed by the unprefixed name
+		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+		// If a hook was provided get the computed value from there
+		if ( hooks && "get" in hooks ) {
+			val = hooks.get( elem, true, extra );
+		}
+
+		// Otherwise, if a way to get the computed value exists, use that
+		if ( val === undefined ) {
+			val = curCSS( elem, name, styles );
+		}
+
+		// Convert "normal" to computed value
+		if ( val === "normal" && name in cssNormalTransform ) {
+			val = cssNormalTransform[ name ];
+		}
+
+		// Make numeric if forced or a qualifier was provided and val looks numeric
+		if ( extra === "" || extra ) {
+			num = parseFloat( val );
+			return extra === true || isFinite( num ) ? num || 0 : val;
+		}
+
+		return val;
+	}
+} );
+
+jQuery.each( [ "height", "width" ], function( _i, dimension ) {
+	jQuery.cssHooks[ dimension ] = {
+		get: function( elem, computed, extra ) {
+			if ( computed ) {
+
+				// Certain elements can have dimension info if we invisibly show them
+				// but it must have a current display style that would benefit
+				return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
+
+					// Support: Safari 8+
+					// Table columns in Safari have non-zero offsetWidth & zero
+					// getBoundingClientRect().width unless display is changed.
+					// Support: IE <=11 only
+					// Running getBoundingClientRect on a disconnected node
+					// in IE throws an error.
+					( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
+						swap( elem, cssShow, function() {
+							return getWidthOrHeight( elem, dimension, extra );
+						} ) :
+						getWidthOrHeight( elem, dimension, extra );
+			}
+		},
+
+		set: function( elem, value, extra ) {
+			var matches,
+				styles = getStyles( elem ),
+
+				// Only read styles.position if the test has a chance to fail
+				// to avoid forcing a reflow.
+				scrollboxSizeBuggy = !support.scrollboxSize() &&
+					styles.position === "absolute",
+
+				// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991)
+				boxSizingNeeded = scrollboxSizeBuggy || extra,
+				isBorderBox = boxSizingNeeded &&
+					jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
+				subtract = extra ?
+					boxModelAdjustment(
+						elem,
+						dimension,
+						extra,
+						isBorderBox,
+						styles
+					) :
+					0;
+
+			// Account for unreliable border-box dimensions by comparing offset* to computed and
+			// faking a content-box to get border and padding (gh-3699)
+			if ( isBorderBox && scrollboxSizeBuggy ) {
+				subtract -= Math.ceil(
+					elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
+					parseFloat( styles[ dimension ] ) -
+					boxModelAdjustment( elem, dimension, "border", false, styles ) -
+					0.5
+				);
+			}
+
+			// Convert to pixels if value adjustment is needed
+			if ( subtract && ( matches = rcssNum.exec( value ) ) &&
+				( matches[ 3 ] || "px" ) !== "px" ) {
+
+				elem.style[ dimension ] = value;
+				value = jQuery.css( elem, dimension );
+			}
+
+			return setPositiveNumber( elem, value, subtract );
+		}
+	};
+} );
+
+jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
+	function( elem, computed ) {
+		if ( computed ) {
+			return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
+				elem.getBoundingClientRect().left -
+					swap( elem, { marginLeft: 0 }, function() {
+						return elem.getBoundingClientRect().left;
+					} )
+				) + "px";
+		}
+	}
+);
+
+// These hooks are used by animate to expand properties
+jQuery.each( {
+	margin: "",
+	padding: "",
+	border: "Width"
+}, function( prefix, suffix ) {
+	jQuery.cssHooks[ prefix + suffix ] = {
+		expand: function( value ) {
+			var i = 0,
+				expanded = {},
+
+				// Assumes a single number if not a string
+				parts = typeof value === "string" ? value.split( " " ) : [ value ];
+
+			for ( ; i < 4; i++ ) {
+				expanded[ prefix + cssExpand[ i ] + suffix ] =
+					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+			}
+
+			return expanded;
+		}
+	};
+
+	if ( prefix !== "margin" ) {
+		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+	}
+} );
+
+jQuery.fn.extend( {
+	css: function( name, value ) {
+		return access( this, function( elem, name, value ) {
+			var styles, len,
+				map = {},
+				i = 0;
+
+			if ( Array.isArray( name ) ) {
+				styles = getStyles( elem );
+				len = name.length;
+
+				for ( ; i < len; i++ ) {
+					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
+				}
+
+				return map;
+			}
+
+			return value !== undefined ?
+				jQuery.style( elem, name, value ) :
+				jQuery.css( elem, name );
+		}, name, value, arguments.length > 1 );
+	}
+} );
+
+
+function Tween( elem, options, prop, end, easing ) {
+	return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+Tween.prototype = {
+	constructor: Tween,
+	init: function( elem, options, prop, end, easing, unit ) {
+		this.elem = elem;
+		this.prop = prop;
+		this.easing = easing || jQuery.easing._default;
+		this.options = options;
+		this.start = this.now = this.cur();
+		this.end = end;
+		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+	},
+	cur: function() {
+		var hooks = Tween.propHooks[ this.prop ];
+
+		return hooks && hooks.get ?
+			hooks.get( this ) :
+			Tween.propHooks._default.get( this );
+	},
+	run: function( percent ) {
+		var eased,
+			hooks = Tween.propHooks[ this.prop ];
+
+		if ( this.options.duration ) {
+			this.pos = eased = jQuery.easing[ this.easing ](
+				percent, this.options.duration * percent, 0, 1, this.options.duration
+			);
+		} else {
+			this.pos = eased = percent;
+		}
+		this.now = ( this.end - this.start ) * eased + this.start;
+
+		if ( this.options.step ) {
+			this.options.step.call( this.elem, this.now, this );
+		}
+
+		if ( hooks && hooks.set ) {
+			hooks.set( this );
+		} else {
+			Tween.propHooks._default.set( this );
+		}
+		return this;
+	}
+};
+
+Tween.prototype.init.prototype = Tween.prototype;
+
+Tween.propHooks = {
+	_default: {
+		get: function( tween ) {
+			var result;
+
+			// Use a property on the element directly when it is not a DOM element,
+			// or when there is no matching style property that exists.
+			if ( tween.elem.nodeType !== 1 ||
+				tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {
+				return tween.elem[ tween.prop ];
+			}
+
+			// Passing an empty string as a 3rd parameter to .css will automatically
+			// attempt a parseFloat and fallback to a string if the parse fails.
+			// Simple values such as "10px" are parsed to Float;
+			// complex values such as "rotate(1rad)" are returned as-is.
+			result = jQuery.css( tween.elem, tween.prop, "" );
+
+			// Empty strings, null, undefined and "auto" are converted to 0.
+			return !result || result === "auto" ? 0 : result;
+		},
+		set: function( tween ) {
+
+			// Use step hook for back compat.
+			// Use cssHook if its there.
+			// Use .style if available and use plain properties where available.
+			if ( jQuery.fx.step[ tween.prop ] ) {
+				jQuery.fx.step[ tween.prop ]( tween );
+			} else if ( tween.elem.nodeType === 1 && (
+					jQuery.cssHooks[ tween.prop ] ||
+					tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) {
+				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+			} else {
+				tween.elem[ tween.prop ] = tween.now;
+			}
+		}
+	}
+};
+
+// Support: IE <=9 only
+// Panic based approach to setting things on disconnected nodes
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+	set: function( tween ) {
+		if ( tween.elem.nodeType && tween.elem.parentNode ) {
+			tween.elem[ tween.prop ] = tween.now;
+		}
+	}
+};
+
+jQuery.easing = {
+	linear: function( p ) {
+		return p;
+	},
+	swing: function( p ) {
+		return 0.5 - Math.cos( p * Math.PI ) / 2;
+	},
+	_default: "swing"
+};
+
+jQuery.fx = Tween.prototype.init;
+
+// Back compat <1.8 extension point
+jQuery.fx.step = {};
+
+
+
+
+var
+	fxNow, inProgress,
+	rfxtypes = /^(?:toggle|show|hide)$/,
+	rrun = /queueHooks$/;
+
+function schedule() {
+	if ( inProgress ) {
+		if ( document.hidden === false && window.requestAnimationFrame ) {
+			window.requestAnimationFrame( schedule );
+		} else {
+			window.setTimeout( schedule, jQuery.fx.interval );
+		}
+
+		jQuery.fx.tick();
+	}
+}
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+	window.setTimeout( function() {
+		fxNow = undefined;
+	} );
+	return ( fxNow = Date.now() );
+}
+
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+	var which,
+		i = 0,
+		attrs = { height: type };
+
+	// If we include width, step value is 1 to do all cssExpand values,
+	// otherwise step value is 2 to skip over Left and Right
+	includeWidth = includeWidth ? 1 : 0;
+	for ( ; i < 4; i += 2 - includeWidth ) {
+		which = cssExpand[ i ];
+		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+	}
+
+	if ( includeWidth ) {
+		attrs.opacity = attrs.width = type;
+	}
+
+	return attrs;
+}
+
+function createTween( value, prop, animation ) {
+	var tween,
+		collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
+		index = 0,
+		length = collection.length;
+	for ( ; index < length; index++ ) {
+		if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {
+
+			// We're done with this property
+			return tween;
+		}
+	}
+}
+
+function defaultPrefilter( elem, props, opts ) {
+	var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,
+		isBox = "width" in props || "height" in props,
+		anim = this,
+		orig = {},
+		style = elem.style,
+		hidden = elem.nodeType && isHiddenWithinTree( elem ),
+		dataShow = dataPriv.get( elem, "fxshow" );
+
+	// Queue-skipping animations hijack the fx hooks
+	if ( !opts.queue ) {
+		hooks = jQuery._queueHooks( elem, "fx" );
+		if ( hooks.unqueued == null ) {
+			hooks.unqueued = 0;
+			oldfire = hooks.empty.fire;
+			hooks.empty.fire = function() {
+				if ( !hooks.unqueued ) {
+					oldfire();
+				}
+			};
+		}
+		hooks.unqueued++;
+
+		anim.always( function() {
+
+			// Ensure the complete handler is called before this completes
+			anim.always( function() {
+				hooks.unqueued--;
+				if ( !jQuery.queue( elem, "fx" ).length ) {
+					hooks.empty.fire();
+				}
+			} );
+		} );
+	}
+
+	// Detect show/hide animations
+	for ( prop in props ) {
+		value = props[ prop ];
+		if ( rfxtypes.test( value ) ) {
+			delete props[ prop ];
+			toggle = toggle || value === "toggle";
+			if ( value === ( hidden ? "hide" : "show" ) ) {
+
+				// Pretend to be hidden if this is a "show" and
+				// there is still data from a stopped show/hide
+				if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
+					hidden = true;
+
+				// Ignore all other no-op show/hide data
+				} else {
+					continue;
+				}
+			}
+			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
+		}
+	}
+
+	// Bail out if this is a no-op like .hide().hide()
+	propTween = !jQuery.isEmptyObject( props );
+	if ( !propTween && jQuery.isEmptyObject( orig ) ) {
+		return;
+	}
+
+	// Restrict "overflow" and "display" styles during box animations
+	if ( isBox && elem.nodeType === 1 ) {
+
+		// Support: IE <=9 - 11, Edge 12 - 15
+		// Record all 3 overflow attributes because IE does not infer the shorthand
+		// from identically-valued overflowX and overflowY and Edge just mirrors
+		// the overflowX value there.
+		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+		// Identify a display type, preferring old show/hide data over the CSS cascade
+		restoreDisplay = dataShow && dataShow.display;
+		if ( restoreDisplay == null ) {
+			restoreDisplay = dataPriv.get( elem, "display" );
+		}
+		display = jQuery.css( elem, "display" );
+		if ( display === "none" ) {
+			if ( restoreDisplay ) {
+				display = restoreDisplay;
+			} else {
+
+				// Get nonempty value(s) by temporarily forcing visibility
+				showHide( [ elem ], true );
+				restoreDisplay = elem.style.display || restoreDisplay;
+				display = jQuery.css( elem, "display" );
+				showHide( [ elem ] );
+			}
+		}
+
+		// Animate inline elements as inline-block
+		if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) {
+			if ( jQuery.css( elem, "float" ) === "none" ) {
+
+				// Restore the original display value at the end of pure show/hide animations
+				if ( !propTween ) {
+					anim.done( function() {
+						style.display = restoreDisplay;
+					} );
+					if ( restoreDisplay == null ) {
+						display = style.display;
+						restoreDisplay = display === "none" ? "" : display;
+					}
+				}
+				style.display = "inline-block";
+			}
+		}
+	}
+
+	if ( opts.overflow ) {
+		style.overflow = "hidden";
+		anim.always( function() {
+			style.overflow = opts.overflow[ 0 ];
+			style.overflowX = opts.overflow[ 1 ];
+			style.overflowY = opts.overflow[ 2 ];
+		} );
+	}
+
+	// Implement show/hide animations
+	propTween = false;
+	for ( prop in orig ) {
+
+		// General show/hide setup for this element animation
+		if ( !propTween ) {
+			if ( dataShow ) {
+				if ( "hidden" in dataShow ) {
+					hidden = dataShow.hidden;
+				}
+			} else {
+				dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } );
+			}
+
+			// Store hidden/visible for toggle so `.stop().toggle()` "reverses"
+			if ( toggle ) {
+				dataShow.hidden = !hidden;
+			}
+
+			// Show elements before animating them
+			if ( hidden ) {
+				showHide( [ elem ], true );
+			}
+
+			/* eslint-disable no-loop-func */
+
+			anim.done( function() {
+
+			/* eslint-enable no-loop-func */
+
+				// The final step of a "hide" animation is actually hiding the element
+				if ( !hidden ) {
+					showHide( [ elem ] );
+				}
+				dataPriv.remove( elem, "fxshow" );
+				for ( prop in orig ) {
+					jQuery.style( elem, prop, orig[ prop ] );
+				}
+			} );
+		}
+
+		// Per-property setup
+		propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
+		if ( !( prop in dataShow ) ) {
+			dataShow[ prop ] = propTween.start;
+			if ( hidden ) {
+				propTween.end = propTween.start;
+				propTween.start = 0;
+			}
+		}
+	}
+}
+
+function propFilter( props, specialEasing ) {
+	var index, name, easing, value, hooks;
+
+	// camelCase, specialEasing and expand cssHook pass
+	for ( index in props ) {
+		name = camelCase( index );
+		easing = specialEasing[ name ];
+		value = props[ index ];
+		if ( Array.isArray( value ) ) {
+			easing = value[ 1 ];
+			value = props[ index ] = value[ 0 ];
+		}
+
+		if ( index !== name ) {
+			props[ name ] = value;
+			delete props[ index ];
+		}
+
+		hooks = jQuery.cssHooks[ name ];
+		if ( hooks && "expand" in hooks ) {
+			value = hooks.expand( value );
+			delete props[ name ];
+
+			// Not quite $.extend, this won't overwrite existing keys.
+			// Reusing 'index' because we have the correct "name"
+			for ( index in value ) {
+				if ( !( index in props ) ) {
+					props[ index ] = value[ index ];
+					specialEasing[ index ] = easing;
+				}
+			}
+		} else {
+			specialEasing[ name ] = easing;
+		}
+	}
+}
+
+function Animation( elem, properties, options ) {
+	var result,
+		stopped,
+		index = 0,
+		length = Animation.prefilters.length,
+		deferred = jQuery.Deferred().always( function() {
+
+			// Don't match elem in the :animated selector
+			delete tick.elem;
+		} ),
+		tick = function() {
+			if ( stopped ) {
+				return false;
+			}
+			var currentTime = fxNow || createFxNow(),
+				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+
+				// Support: Android 2.3 only
+				// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
+				temp = remaining / animation.duration || 0,
+				percent = 1 - temp,
+				index = 0,
+				length = animation.tweens.length;
+
+			for ( ; index < length; index++ ) {
+				animation.tweens[ index ].run( percent );
+			}
+
+			deferred.notifyWith( elem, [ animation, percent, remaining ] );
+
+			// If there's more to do, yield
+			if ( percent < 1 && length ) {
+				return remaining;
+			}
+
+			// If this was an empty animation, synthesize a final progress notification
+			if ( !length ) {
+				deferred.notifyWith( elem, [ animation, 1, 0 ] );
+			}
+
+			// Resolve the animation and report its conclusion
+			deferred.resolveWith( elem, [ animation ] );
+			return false;
+		},
+		animation = deferred.promise( {
+			elem: elem,
+			props: jQuery.extend( {}, properties ),
+			opts: jQuery.extend( true, {
+				specialEasing: {},
+				easing: jQuery.easing._default
+			}, options ),
+			originalProperties: properties,
+			originalOptions: options,
+			startTime: fxNow || createFxNow(),
+			duration: options.duration,
+			tweens: [],
+			createTween: function( prop, end ) {
+				var tween = jQuery.Tween( elem, animation.opts, prop, end,
+						animation.opts.specialEasing[ prop ] || animation.opts.easing );
+				animation.tweens.push( tween );
+				return tween;
+			},
+			stop: function( gotoEnd ) {
+				var index = 0,
+
+					// If we are going to the end, we want to run all the tweens
+					// otherwise we skip this part
+					length = gotoEnd ? animation.tweens.length : 0;
+				if ( stopped ) {
+					return this;
+				}
+				stopped = true;
+				for ( ; index < length; index++ ) {
+					animation.tweens[ index ].run( 1 );
+				}
+
+				// Resolve when we played the last frame; otherwise, reject
+				if ( gotoEnd ) {
+					deferred.notifyWith( elem, [ animation, 1, 0 ] );
+					deferred.resolveWith( elem, [ animation, gotoEnd ] );
+				} else {
+					deferred.rejectWith( elem, [ animation, gotoEnd ] );
+				}
+				return this;
+			}
+		} ),
+		props = animation.props;
+
+	propFilter( props, animation.opts.specialEasing );
+
+	for ( ; index < length; index++ ) {
+		result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
+		if ( result ) {
+			if ( isFunction( result.stop ) ) {
+				jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
+					result.stop.bind( result );
+			}
+			return result;
+		}
+	}
+
+	jQuery.map( props, createTween, animation );
+
+	if ( isFunction( animation.opts.start ) ) {
+		animation.opts.start.call( elem, animation );
+	}
+
+	// Attach callbacks from options
+	animation
+		.progress( animation.opts.progress )
+		.done( animation.opts.done, animation.opts.complete )
+		.fail( animation.opts.fail )
+		.always( animation.opts.always );
+
+	jQuery.fx.timer(
+		jQuery.extend( tick, {
+			elem: elem,
+			anim: animation,
+			queue: animation.opts.queue
+		} )
+	);
+
+	return animation;
+}
+
+jQuery.Animation = jQuery.extend( Animation, {
+
+	tweeners: {
+		"*": [ function( prop, value ) {
+			var tween = this.createTween( prop, value );
+			adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
+			return tween;
+		} ]
+	},
+
+	tweener: function( props, callback ) {
+		if ( isFunction( props ) ) {
+			callback = props;
+			props = [ "*" ];
+		} else {
+			props = props.match( rnothtmlwhite );
+		}
+
+		var prop,
+			index = 0,
+			length = props.length;
+
+		for ( ; index < length; index++ ) {
+			prop = props[ index ];
+			Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
+			Animation.tweeners[ prop ].unshift( callback );
+		}
+	},
+
+	prefilters: [ defaultPrefilter ],
+
+	prefilter: function( callback, prepend ) {
+		if ( prepend ) {
+			Animation.prefilters.unshift( callback );
+		} else {
+			Animation.prefilters.push( callback );
+		}
+	}
+} );
+
+jQuery.speed = function( speed, easing, fn ) {
+	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+		complete: fn || !fn && easing ||
+			isFunction( speed ) && speed,
+		duration: speed,
+		easing: fn && easing || easing && !isFunction( easing ) && easing
+	};
+
+	// Go to the end state if fx are off
+	if ( jQuery.fx.off ) {
+		opt.duration = 0;
+
+	} else {
+		if ( typeof opt.duration !== "number" ) {
+			if ( opt.duration in jQuery.fx.speeds ) {
+				opt.duration = jQuery.fx.speeds[ opt.duration ];
+
+			} else {
+				opt.duration = jQuery.fx.speeds._default;
+			}
+		}
+	}
+
+	// Normalize opt.queue - true/undefined/null -> "fx"
+	if ( opt.queue == null || opt.queue === true ) {
+		opt.queue = "fx";
+	}
+
+	// Queueing
+	opt.old = opt.complete;
+
+	opt.complete = function() {
+		if ( isFunction( opt.old ) ) {
+			opt.old.call( this );
+		}
+
+		if ( opt.queue ) {
+			jQuery.dequeue( this, opt.queue );
+		}
+	};
+
+	return opt;
+};
+
+jQuery.fn.extend( {
+	fadeTo: function( speed, to, easing, callback ) {
+
+		// Show any hidden elements after setting opacity to 0
+		return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show()
+
+			// Animate to the value specified
+			.end().animate( { opacity: to }, speed, easing, callback );
+	},
+	animate: function( prop, speed, easing, callback ) {
+		var empty = jQuery.isEmptyObject( prop ),
+			optall = jQuery.speed( speed, easing, callback ),
+			doAnimation = function() {
+
+				// Operate on a copy of prop so per-property easing won't be lost
+				var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+
+				// Empty animations, or finishing resolves immediately
+				if ( empty || dataPriv.get( this, "finish" ) ) {
+					anim.stop( true );
+				}
+			};
+			doAnimation.finish = doAnimation;
+
+		return empty || optall.queue === false ?
+			this.each( doAnimation ) :
+			this.queue( optall.queue, doAnimation );
+	},
+	stop: function( type, clearQueue, gotoEnd ) {
+		var stopQueue = function( hooks ) {
+			var stop = hooks.stop;
+			delete hooks.stop;
+			stop( gotoEnd );
+		};
+
+		if ( typeof type !== "string" ) {
+			gotoEnd = clearQueue;
+			clearQueue = type;
+			type = undefined;
+		}
+		if ( clearQueue ) {
+			this.queue( type || "fx", [] );
+		}
+
+		return this.each( function() {
+			var dequeue = true,
+				index = type != null && type + "queueHooks",
+				timers = jQuery.timers,
+				data = dataPriv.get( this );
+
+			if ( index ) {
+				if ( data[ index ] && data[ index ].stop ) {
+					stopQueue( data[ index ] );
+				}
+			} else {
+				for ( index in data ) {
+					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+						stopQueue( data[ index ] );
+					}
+				}
+			}
+
+			for ( index = timers.length; index--; ) {
+				if ( timers[ index ].elem === this &&
+					( type == null || timers[ index ].queue === type ) ) {
+
+					timers[ index ].anim.stop( gotoEnd );
+					dequeue = false;
+					timers.splice( index, 1 );
+				}
+			}
+
+			// Start the next in the queue if the last step wasn't forced.
+			// Timers currently will call their complete callbacks, which
+			// will dequeue but only if they were gotoEnd.
+			if ( dequeue || !gotoEnd ) {
+				jQuery.dequeue( this, type );
+			}
+		} );
+	},
+	finish: function( type ) {
+		if ( type !== false ) {
+			type = type || "fx";
+		}
+		return this.each( function() {
+			var index,
+				data = dataPriv.get( this ),
+				queue = data[ type + "queue" ],
+				hooks = data[ type + "queueHooks" ],
+				timers = jQuery.timers,
+				length = queue ? queue.length : 0;
+
+			// Enable finishing flag on private data
+			data.finish = true;
+
+			// Empty the queue first
+			jQuery.queue( this, type, [] );
+
+			if ( hooks && hooks.stop ) {
+				hooks.stop.call( this, true );
+			}
+
+			// Look for any active animations, and finish them
+			for ( index = timers.length; index--; ) {
+				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
+					timers[ index ].anim.stop( true );
+					timers.splice( index, 1 );
+				}
+			}
+
+			// Look for any animations in the old queue and finish them
+			for ( index = 0; index < length; index++ ) {
+				if ( queue[ index ] && queue[ index ].finish ) {
+					queue[ index ].finish.call( this );
+				}
+			}
+
+			// Turn off finishing flag
+			delete data.finish;
+		} );
+	}
+} );
+
+jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) {
+	var cssFn = jQuery.fn[ name ];
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return speed == null || typeof speed === "boolean" ?
+			cssFn.apply( this, arguments ) :
+			this.animate( genFx( name, true ), speed, easing, callback );
+	};
+} );
+
+// Generate shortcuts for custom animations
+jQuery.each( {
+	slideDown: genFx( "show" ),
+	slideUp: genFx( "hide" ),
+	slideToggle: genFx( "toggle" ),
+	fadeIn: { opacity: "show" },
+	fadeOut: { opacity: "hide" },
+	fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return this.animate( props, speed, easing, callback );
+	};
+} );
+
+jQuery.timers = [];
+jQuery.fx.tick = function() {
+	var timer,
+		i = 0,
+		timers = jQuery.timers;
+
+	fxNow = Date.now();
+
+	for ( ; i < timers.length; i++ ) {
+		timer = timers[ i ];
+
+		// Run the timer and safely remove it when done (allowing for external removal)
+		if ( !timer() && timers[ i ] === timer ) {
+			timers.splice( i--, 1 );
+		}
+	}
+
+	if ( !timers.length ) {
+		jQuery.fx.stop();
+	}
+	fxNow = undefined;
+};
+
+jQuery.fx.timer = function( timer ) {
+	jQuery.timers.push( timer );
+	jQuery.fx.start();
+};
+
+jQuery.fx.interval = 13;
+jQuery.fx.start = function() {
+	if ( inProgress ) {
+		return;
+	}
+
+	inProgress = true;
+	schedule();
+};
+
+jQuery.fx.stop = function() {
+	inProgress = null;
+};
+
+jQuery.fx.speeds = {
+	slow: 600,
+	fast: 200,
+
+	// Default speed
+	_default: 400
+};
+
+
+// Based off of the plugin by Clint Helfers, with permission.
+// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
+jQuery.fn.delay = function( time, type ) {
+	time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+	type = type || "fx";
+
+	return this.queue( type, function( next, hooks ) {
+		var timeout = window.setTimeout( next, time );
+		hooks.stop = function() {
+			window.clearTimeout( timeout );
+		};
+	} );
+};
+
+
+( function() {
+	var input = document.createElement( "input" ),
+		select = document.createElement( "select" ),
+		opt = select.appendChild( document.createElement( "option" ) );
+
+	input.type = "checkbox";
+
+	// Support: Android <=4.3 only
+	// Default value for a checkbox should be "on"
+	support.checkOn = input.value !== "";
+
+	// Support: IE <=11 only
+	// Must access selectedIndex to make default options select
+	support.optSelected = opt.selected;
+
+	// Support: IE <=11 only
+	// An input loses its value after becoming a radio
+	input = document.createElement( "input" );
+	input.value = "t";
+	input.type = "radio";
+	support.radioValue = input.value === "t";
+} )();
+
+
+var boolHook,
+	attrHandle = jQuery.expr.attrHandle;
+
+jQuery.fn.extend( {
+	attr: function( name, value ) {
+		return access( this, jQuery.attr, name, value, arguments.length > 1 );
+	},
+
+	removeAttr: function( name ) {
+		return this.each( function() {
+			jQuery.removeAttr( this, name );
+		} );
+	}
+} );
+
+jQuery.extend( {
+	attr: function( elem, name, value ) {
+		var ret, hooks,
+			nType = elem.nodeType;
+
+		// Don't get/set attributes on text, comment and attribute nodes
+		if ( nType === 3 || nType === 8 || nType === 2 ) {
+			return;
+		}
+
+		// Fallback to prop when attributes are not supported
+		if ( typeof elem.getAttribute === "undefined" ) {
+			return jQuery.prop( elem, name, value );
+		}
+
+		// Attribute hooks are determined by the lowercase version
+		// Grab necessary hook if one is defined
+		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+			hooks = jQuery.attrHooks[ name.toLowerCase() ] ||
+				( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );
+		}
+
+		if ( value !== undefined ) {
+			if ( value === null ) {
+				jQuery.removeAttr( elem, name );
+				return;
+			}
+
+			if ( hooks && "set" in hooks &&
+				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
+				return ret;
+			}
+
+			elem.setAttribute( name, value + "" );
+			return value;
+		}
+
+		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
+			return ret;
+		}
+
+		ret = jQuery.find.attr( elem, name );
+
+		// Non-existent attributes return null, we normalize to undefined
+		return ret == null ? undefined : ret;
+	},
+
+	attrHooks: {
+		type: {
+			set: function( elem, value ) {
+				if ( !support.radioValue && value === "radio" &&
+					nodeName( elem, "input" ) ) {
+					var val = elem.value;
+					elem.setAttribute( "type", value );
+					if ( val ) {
+						elem.value = val;
+					}
+					return value;
+				}
+			}
+		}
+	},
+
+	removeAttr: function( elem, value ) {
+		var name,
+			i = 0,
+
+			// Attribute names can contain non-HTML whitespace characters
+			// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
+			attrNames = value && value.match( rnothtmlwhite );
+
+		if ( attrNames && elem.nodeType === 1 ) {
+			while ( ( name = attrNames[ i++ ] ) ) {
+				elem.removeAttribute( name );
+			}
+		}
+	}
+} );
+
+// Hooks for boolean attributes
+boolHook = {
+	set: function( elem, value, name ) {
+		if ( value === false ) {
+
+			// Remove boolean attributes when set to false
+			jQuery.removeAttr( elem, name );
+		} else {
+			elem.setAttribute( name, name );
+		}
+		return name;
+	}
+};
+
+jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) {
+	var getter = attrHandle[ name ] || jQuery.find.attr;
+
+	attrHandle[ name ] = function( elem, name, isXML ) {
+		var ret, handle,
+			lowercaseName = name.toLowerCase();
+
+		if ( !isXML ) {
+
+			// Avoid an infinite loop by temporarily removing this function from the getter
+			handle = attrHandle[ lowercaseName ];
+			attrHandle[ lowercaseName ] = ret;
+			ret = getter( elem, name, isXML ) != null ?
+				lowercaseName :
+				null;
+			attrHandle[ lowercaseName ] = handle;
+		}
+		return ret;
+	};
+} );
+
+
+
+
+var rfocusable = /^(?:input|select|textarea|button)$/i,
+	rclickable = /^(?:a|area)$/i;
+
+jQuery.fn.extend( {
+	prop: function( name, value ) {
+		return access( this, jQuery.prop, name, value, arguments.length > 1 );
+	},
+
+	removeProp: function( name ) {
+		return this.each( function() {
+			delete this[ jQuery.propFix[ name ] || name ];
+		} );
+	}
+} );
+
+jQuery.extend( {
+	prop: function( elem, name, value ) {
+		var ret, hooks,
+			nType = elem.nodeType;
+
+		// Don't get/set properties on text, comment and attribute nodes
+		if ( nType === 3 || nType === 8 || nType === 2 ) {
+			return;
+		}
+
+		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+
+			// Fix name and attach hooks
+			name = jQuery.propFix[ name ] || name;
+			hooks = jQuery.propHooks[ name ];
+		}
+
+		if ( value !== undefined ) {
+			if ( hooks && "set" in hooks &&
+				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
+				return ret;
+			}
+
+			return ( elem[ name ] = value );
+		}
+
+		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
+			return ret;
+		}
+
+		return elem[ name ];
+	},
+
+	propHooks: {
+		tabIndex: {
+			get: function( elem ) {
+
+				// Support: IE <=9 - 11 only
+				// elem.tabIndex doesn't always return the
+				// correct value when it hasn't been explicitly set
+				// https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+				// Use proper attribute retrieval(#12072)
+				var tabindex = jQuery.find.attr( elem, "tabindex" );
+
+				if ( tabindex ) {
+					return parseInt( tabindex, 10 );
+				}
+
+				if (
+					rfocusable.test( elem.nodeName ) ||
+					rclickable.test( elem.nodeName ) &&
+					elem.href
+				) {
+					return 0;
+				}
+
+				return -1;
+			}
+		}
+	},
+
+	propFix: {
+		"for": "htmlFor",
+		"class": "className"
+	}
+} );
+
+// Support: IE <=11 only
+// Accessing the selectedIndex property
+// forces the browser to respect setting selected
+// on the option
+// The getter ensures a default option is selected
+// when in an optgroup
+// eslint rule "no-unused-expressions" is disabled for this code
+// since it considers such accessions noop
+if ( !support.optSelected ) {
+	jQuery.propHooks.selected = {
+		get: function( elem ) {
+
+			/* eslint no-unused-expressions: "off" */
+
+			var parent = elem.parentNode;
+			if ( parent && parent.parentNode ) {
+				parent.parentNode.selectedIndex;
+			}
+			return null;
+		},
+		set: function( elem ) {
+
+			/* eslint no-unused-expressions: "off" */
+
+			var parent = elem.parentNode;
+			if ( parent ) {
+				parent.selectedIndex;
+
+				if ( parent.parentNode ) {
+					parent.parentNode.selectedIndex;
+				}
+			}
+		}
+	};
+}
+
+jQuery.each( [
+	"tabIndex",
+	"readOnly",
+	"maxLength",
+	"cellSpacing",
+	"cellPadding",
+	"rowSpan",
+	"colSpan",
+	"useMap",
+	"frameBorder",
+	"contentEditable"
+], function() {
+	jQuery.propFix[ this.toLowerCase() ] = this;
+} );
+
+
+
+
+	// Strip and collapse whitespace according to HTML spec
+	// https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
+	function stripAndCollapse( value ) {
+		var tokens = value.match( rnothtmlwhite ) || [];
+		return tokens.join( " " );
+	}
+
+
+function getClass( elem ) {
+	return elem.getAttribute && elem.getAttribute( "class" ) || "";
+}
+
+function classesToArray( value ) {
+	if ( Array.isArray( value ) ) {
+		return value;
+	}
+	if ( typeof value === "string" ) {
+		return value.match( rnothtmlwhite ) || [];
+	}
+	return [];
+}
+
+jQuery.fn.extend( {
+	addClass: function( value ) {
+		var classes, elem, cur, curValue, clazz, j, finalValue,
+			i = 0;
+
+		if ( isFunction( value ) ) {
+			return this.each( function( j ) {
+				jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
+			} );
+		}
+
+		classes = classesToArray( value );
+
+		if ( classes.length ) {
+			while ( ( elem = this[ i++ ] ) ) {
+				curValue = getClass( elem );
+				cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
+
+				if ( cur ) {
+					j = 0;
+					while ( ( clazz = classes[ j++ ] ) ) {
+						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
+							cur += clazz + " ";
+						}
+					}
+
+					// Only assign if different to avoid unneeded rendering.
+					finalValue = stripAndCollapse( cur );
+					if ( curValue !== finalValue ) {
+						elem.setAttribute( "class", finalValue );
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	removeClass: function( value ) {
+		var classes, elem, cur, curValue, clazz, j, finalValue,
+			i = 0;
+
+		if ( isFunction( value ) ) {
+			return this.each( function( j ) {
+				jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
+			} );
+		}
+
+		if ( !arguments.length ) {
+			return this.attr( "class", "" );
+		}
+
+		classes = classesToArray( value );
+
+		if ( classes.length ) {
+			while ( ( elem = this[ i++ ] ) ) {
+				curValue = getClass( elem );
+
+				// This expression is here for better compressibility (see addClass)
+				cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
+
+				if ( cur ) {
+					j = 0;
+					while ( ( clazz = classes[ j++ ] ) ) {
+
+						// Remove *all* instances
+						while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
+							cur = cur.replace( " " + clazz + " ", " " );
+						}
+					}
+
+					// Only assign if different to avoid unneeded rendering.
+					finalValue = stripAndCollapse( cur );
+					if ( curValue !== finalValue ) {
+						elem.setAttribute( "class", finalValue );
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	toggleClass: function( value, stateVal ) {
+		var type = typeof value,
+			isValidValue = type === "string" || Array.isArray( value );
+
+		if ( typeof stateVal === "boolean" && isValidValue ) {
+			return stateVal ? this.addClass( value ) : this.removeClass( value );
+		}
+
+		if ( isFunction( value ) ) {
+			return this.each( function( i ) {
+				jQuery( this ).toggleClass(
+					value.call( this, i, getClass( this ), stateVal ),
+					stateVal
+				);
+			} );
+		}
+
+		return this.each( function() {
+			var className, i, self, classNames;
+
+			if ( isValidValue ) {
+
+				// Toggle individual class names
+				i = 0;
+				self = jQuery( this );
+				classNames = classesToArray( value );
+
+				while ( ( className = classNames[ i++ ] ) ) {
+
+					// Check each className given, space separated list
+					if ( self.hasClass( className ) ) {
+						self.removeClass( className );
+					} else {
+						self.addClass( className );
+					}
+				}
+
+			// Toggle whole class name
+			} else if ( value === undefined || type === "boolean" ) {
+				className = getClass( this );
+				if ( className ) {
+
+					// Store className if set
+					dataPriv.set( this, "__className__", className );
+				}
+
+				// If the element has a class name or if we're passed `false`,
+				// then remove the whole classname (if there was one, the above saved it).
+				// Otherwise bring back whatever was previously saved (if anything),
+				// falling back to the empty string if nothing was stored.
+				if ( this.setAttribute ) {
+					this.setAttribute( "class",
+						className || value === false ?
+						"" :
+						dataPriv.get( this, "__className__" ) || ""
+					);
+				}
+			}
+		} );
+	},
+
+	hasClass: function( selector ) {
+		var className, elem,
+			i = 0;
+
+		className = " " + selector + " ";
+		while ( ( elem = this[ i++ ] ) ) {
+			if ( elem.nodeType === 1 &&
+				( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
+					return true;
+			}
+		}
+
+		return false;
+	}
+} );
+
+
+
+
+var rreturn = /\r/g;
+
+jQuery.fn.extend( {
+	val: function( value ) {
+		var hooks, ret, valueIsFunction,
+			elem = this[ 0 ];
+
+		if ( !arguments.length ) {
+			if ( elem ) {
+				hooks = jQuery.valHooks[ elem.type ] ||
+					jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+				if ( hooks &&
+					"get" in hooks &&
+					( ret = hooks.get( elem, "value" ) ) !== undefined
+				) {
+					return ret;
+				}
+
+				ret = elem.value;
+
+				// Handle most common string cases
+				if ( typeof ret === "string" ) {
+					return ret.replace( rreturn, "" );
+				}
+
+				// Handle cases where value is null/undef or number
+				return ret == null ? "" : ret;
+			}
+
+			return;
+		}
+
+		valueIsFunction = isFunction( value );
+
+		return this.each( function( i ) {
+			var val;
+
+			if ( this.nodeType !== 1 ) {
+				return;
+			}
+
+			if ( valueIsFunction ) {
+				val = value.call( this, i, jQuery( this ).val() );
+			} else {
+				val = value;
+			}
+
+			// Treat null/undefined as ""; convert numbers to string
+			if ( val == null ) {
+				val = "";
+
+			} else if ( typeof val === "number" ) {
+				val += "";
+
+			} else if ( Array.isArray( val ) ) {
+				val = jQuery.map( val, function( value ) {
+					return value == null ? "" : value + "";
+				} );
+			}
+
+			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+			// If set returns undefined, fall back to normal setting
+			if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
+				this.value = val;
+			}
+		} );
+	}
+} );
+
+jQuery.extend( {
+	valHooks: {
+		option: {
+			get: function( elem ) {
+
+				var val = jQuery.find.attr( elem, "value" );
+				return val != null ?
+					val :
+
+					// Support: IE <=10 - 11 only
+					// option.text throws exceptions (#14686, #14858)
+					// Strip and collapse whitespace
+					// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
+					stripAndCollapse( jQuery.text( elem ) );
+			}
+		},
+		select: {
+			get: function( elem ) {
+				var value, option, i,
+					options = elem.options,
+					index = elem.selectedIndex,
+					one = elem.type === "select-one",
+					values = one ? null : [],
+					max = one ? index + 1 : options.length;
+
+				if ( index < 0 ) {
+					i = max;
+
+				} else {
+					i = one ? index : 0;
+				}
+
+				// Loop through all the selected options
+				for ( ; i < max; i++ ) {
+					option = options[ i ];
+
+					// Support: IE <=9 only
+					// IE8-9 doesn't update selected after form reset (#2551)
+					if ( ( option.selected || i === index ) &&
+
+							// Don't return options that are disabled or in a disabled optgroup
+							!option.disabled &&
+							( !option.parentNode.disabled ||
+								!nodeName( option.parentNode, "optgroup" ) ) ) {
+
+						// Get the specific value for the option
+						value = jQuery( option ).val();
+
+						// We don't need an array for one selects
+						if ( one ) {
+							return value;
+						}
+
+						// Multi-Selects return an array
+						values.push( value );
+					}
+				}
+
+				return values;
+			},
+
+			set: function( elem, value ) {
+				var optionSet, option,
+					options = elem.options,
+					values = jQuery.makeArray( value ),
+					i = options.length;
+
+				while ( i-- ) {
+					option = options[ i ];
+
+					/* eslint-disable no-cond-assign */
+
+					if ( option.selected =
+						jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
+					) {
+						optionSet = true;
+					}
+
+					/* eslint-enable no-cond-assign */
+				}
+
+				// Force browsers to behave consistently when non-matching value is set
+				if ( !optionSet ) {
+					elem.selectedIndex = -1;
+				}
+				return values;
+			}
+		}
+	}
+} );
+
+// Radios and checkboxes getter/setter
+jQuery.each( [ "radio", "checkbox" ], function() {
+	jQuery.valHooks[ this ] = {
+		set: function( elem, value ) {
+			if ( Array.isArray( value ) ) {
+				return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
+			}
+		}
+	};
+	if ( !support.checkOn ) {
+		jQuery.valHooks[ this ].get = function( elem ) {
+			return elem.getAttribute( "value" ) === null ? "on" : elem.value;
+		};
+	}
+} );
+
+
+
+
+// Return jQuery for attributes-only inclusion
+
+
+support.focusin = "onfocusin" in window;
+
+
+var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+	stopPropagationCallback = function( e ) {
+		e.stopPropagation();
+	};
+
+jQuery.extend( jQuery.event, {
+
+	trigger: function( event, data, elem, onlyHandlers ) {
+
+		var i, cur, tmp, bubbleType, ontype, handle, special, lastElement,
+			eventPath = [ elem || document ],
+			type = hasOwn.call( event, "type" ) ? event.type : event,
+			namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];
+
+		cur = lastElement = tmp = elem = elem || document;
+
+		// Don't do events on text and comment nodes
+		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+			return;
+		}
+
+		// focus/blur morphs to focusin/out; ensure we're not firing them right now
+		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+			return;
+		}
+
+		if ( type.indexOf( "." ) > -1 ) {
+
+			// Namespaced trigger; create a regexp to match event type in handle()
+			namespaces = type.split( "." );
+			type = namespaces.shift();
+			namespaces.sort();
+		}
+		ontype = type.indexOf( ":" ) < 0 && "on" + type;
+
+		// Caller can pass in a jQuery.Event object, Object, or just an event type string
+		event = event[ jQuery.expando ] ?
+			event :
+			new jQuery.Event( type, typeof event === "object" && event );
+
+		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
+		event.isTrigger = onlyHandlers ? 2 : 3;
+		event.namespace = namespaces.join( "." );
+		event.rnamespace = event.namespace ?
+			new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
+			null;
+
+		// Clean up the event in case it is being reused
+		event.result = undefined;
+		if ( !event.target ) {
+			event.target = elem;
+		}
+
+		// Clone any incoming data and prepend the event, creating the handler arg list
+		data = data == null ?
+			[ event ] :
+			jQuery.makeArray( data, [ event ] );
+
+		// Allow special events to draw outside the lines
+		special = jQuery.event.special[ type ] || {};
+		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+			return;
+		}
+
+		// Determine event propagation path in advance, per W3C events spec (#9951)
+		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+		if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {
+
+			bubbleType = special.delegateType || type;
+			if ( !rfocusMorph.test( bubbleType + type ) ) {
+				cur = cur.parentNode;
+			}
+			for ( ; cur; cur = cur.parentNode ) {
+				eventPath.push( cur );
+				tmp = cur;
+			}
+
+			// Only add window if we got to document (e.g., not plain obj or detached DOM)
+			if ( tmp === ( elem.ownerDocument || document ) ) {
+				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
+			}
+		}
+
+		// Fire handlers on the event path
+		i = 0;
+		while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
+			lastElement = cur;
+			event.type = i > 1 ?
+				bubbleType :
+				special.bindType || type;
+
+			// jQuery handler
+			handle = (
+					dataPriv.get( cur, "events" ) || Object.create( null )
+				)[ event.type ] &&
+				dataPriv.get( cur, "handle" );
+			if ( handle ) {
+				handle.apply( cur, data );
+			}
+
+			// Native handler
+			handle = ontype && cur[ ontype ];
+			if ( handle && handle.apply && acceptData( cur ) ) {
+				event.result = handle.apply( cur, data );
+				if ( event.result === false ) {
+					event.preventDefault();
+				}
+			}
+		}
+		event.type = type;
+
+		// If nobody prevented the default action, do it now
+		if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+			if ( ( !special._default ||
+				special._default.apply( eventPath.pop(), data ) === false ) &&
+				acceptData( elem ) ) {
+
+				// Call a native DOM method on the target with the same name as the event.
+				// Don't do default actions on window, that's where global variables be (#6170)
+				if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {
+
+					// Don't re-trigger an onFOO event when we call its FOO() method
+					tmp = elem[ ontype ];
+
+					if ( tmp ) {
+						elem[ ontype ] = null;
+					}
+
+					// Prevent re-triggering of the same event, since we already bubbled it above
+					jQuery.event.triggered = type;
+
+					if ( event.isPropagationStopped() ) {
+						lastElement.addEventListener( type, stopPropagationCallback );
+					}
+
+					elem[ type ]();
+
+					if ( event.isPropagationStopped() ) {
+						lastElement.removeEventListener( type, stopPropagationCallback );
+					}
+
+					jQuery.event.triggered = undefined;
+
+					if ( tmp ) {
+						elem[ ontype ] = tmp;
+					}
+				}
+			}
+		}
+
+		return event.result;
+	},
+
+	// Piggyback on a donor event to simulate a different one
+	// Used only for `focus(in | out)` events
+	simulate: function( type, elem, event ) {
+		var e = jQuery.extend(
+			new jQuery.Event(),
+			event,
+			{
+				type: type,
+				isSimulated: true
+			}
+		);
+
+		jQuery.event.trigger( e, null, elem );
+	}
+
+} );
+
+jQuery.fn.extend( {
+
+	trigger: function( type, data ) {
+		return this.each( function() {
+			jQuery.event.trigger( type, data, this );
+		} );
+	},
+	triggerHandler: function( type, data ) {
+		var elem = this[ 0 ];
+		if ( elem ) {
+			return jQuery.event.trigger( type, data, elem, true );
+		}
+	}
+} );
+
+
+// Support: Firefox <=44
+// Firefox doesn't have focus(in | out) events
+// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
+//
+// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1
+// focus(in | out) events fire after focus & blur events,
+// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
+// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857
+if ( !support.focusin ) {
+	jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+		// Attach a single capturing handler on the document while someone wants focusin/focusout
+		var handler = function( event ) {
+			jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );
+		};
+
+		jQuery.event.special[ fix ] = {
+			setup: function() {
+
+				// Handle: regular nodes (via `this.ownerDocument`), window
+				// (via `this.document`) & document (via `this`).
+				var doc = this.ownerDocument || this.document || this,
+					attaches = dataPriv.access( doc, fix );
+
+				if ( !attaches ) {
+					doc.addEventListener( orig, handler, true );
+				}
+				dataPriv.access( doc, fix, ( attaches || 0 ) + 1 );
+			},
+			teardown: function() {
+				var doc = this.ownerDocument || this.document || this,
+					attaches = dataPriv.access( doc, fix ) - 1;
+
+				if ( !attaches ) {
+					doc.removeEventListener( orig, handler, true );
+					dataPriv.remove( doc, fix );
+
+				} else {
+					dataPriv.access( doc, fix, attaches );
+				}
+			}
+		};
+	} );
+}
+var location = window.location;
+
+var nonce = { guid: Date.now() };
+
+var rquery = ( /\?/ );
+
+
+
+// Cross-browser xml parsing
+jQuery.parseXML = function( data ) {
+	var xml;
+	if ( !data || typeof data !== "string" ) {
+		return null;
+	}
+
+	// Support: IE 9 - 11 only
+	// IE throws on parseFromString with invalid input.
+	try {
+		xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
+	} catch ( e ) {
+		xml = undefined;
+	}
+
+	if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
+		jQuery.error( "Invalid XML: " + data );
+	}
+	return xml;
+};
+
+
+var
+	rbracket = /\[\]$/,
+	rCRLF = /\r?\n/g,
+	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
+	rsubmittable = /^(?:input|select|textarea|keygen)/i;
+
+function buildParams( prefix, obj, traditional, add ) {
+	var name;
+
+	if ( Array.isArray( obj ) ) {
+
+		// Serialize array item.
+		jQuery.each( obj, function( i, v ) {
+			if ( traditional || rbracket.test( prefix ) ) {
+
+				// Treat each array item as a scalar.
+				add( prefix, v );
+
+			} else {
+
+				// Item is non-scalar (array or object), encode its numeric index.
+				buildParams(
+					prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
+					v,
+					traditional,
+					add
+				);
+			}
+		} );
+
+	} else if ( !traditional && toType( obj ) === "object" ) {
+
+		// Serialize object item.
+		for ( name in obj ) {
+			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+		}
+
+	} else {
+
+		// Serialize scalar item.
+		add( prefix, obj );
+	}
+}
+
+// Serialize an array of form elements or a set of
+// key/values into a query string
+jQuery.param = function( a, traditional ) {
+	var prefix,
+		s = [],
+		add = function( key, valueOrFunction ) {
+
+			// If value is a function, invoke it and use its return value
+			var value = isFunction( valueOrFunction ) ?
+				valueOrFunction() :
+				valueOrFunction;
+
+			s[ s.length ] = encodeURIComponent( key ) + "=" +
+				encodeURIComponent( value == null ? "" : value );
+		};
+
+	if ( a == null ) {
+		return "";
+	}
+
+	// If an array was passed in, assume that it is an array of form elements.
+	if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+
+		// Serialize the form elements
+		jQuery.each( a, function() {
+			add( this.name, this.value );
+		} );
+
+	} else {
+
+		// If traditional, encode the "old" way (the way 1.3.2 or older
+		// did it), otherwise encode params recursively.
+		for ( prefix in a ) {
+			buildParams( prefix, a[ prefix ], traditional, add );
+		}
+	}
+
+	// Return the resulting serialization
+	return s.join( "&" );
+};
+
+jQuery.fn.extend( {
+	serialize: function() {
+		return jQuery.param( this.serializeArray() );
+	},
+	serializeArray: function() {
+		return this.map( function() {
+
+			// Can add propHook for "elements" to filter or add form elements
+			var elements = jQuery.prop( this, "elements" );
+			return elements ? jQuery.makeArray( elements ) : this;
+		} )
+		.filter( function() {
+			var type = this.type;
+
+			// Use .is( ":disabled" ) so that fieldset[disabled] works
+			return this.name && !jQuery( this ).is( ":disabled" ) &&
+				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
+				( this.checked || !rcheckableType.test( type ) );
+		} )
+		.map( function( _i, elem ) {
+			var val = jQuery( this ).val();
+
+			if ( val == null ) {
+				return null;
+			}
+
+			if ( Array.isArray( val ) ) {
+				return jQuery.map( val, function( val ) {
+					return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+				} );
+			}
+
+			return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+		} ).get();
+	}
+} );
+
+
+var
+	r20 = /%20/g,
+	rhash = /#.*$/,
+	rantiCache = /([?&])_=[^&]*/,
+	rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
+
+	// #7653, #8125, #8152: local protocol detection
+	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
+	rnoContent = /^(?:GET|HEAD)$/,
+	rprotocol = /^\/\//,
+
+	/* Prefilters
+	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+	 * 2) These are called:
+	 *    - BEFORE asking for a transport
+	 *    - AFTER param serialization (s.data is a string if s.processData is true)
+	 * 3) key is the dataType
+	 * 4) the catchall symbol "*" can be used
+	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+	 */
+	prefilters = {},
+
+	/* Transports bindings
+	 * 1) key is the dataType
+	 * 2) the catchall symbol "*" can be used
+	 * 3) selection will start with transport dataType and THEN go to "*" if needed
+	 */
+	transports = {},
+
+	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+	allTypes = "*/".concat( "*" ),
+
+	// Anchor tag for parsing the document origin
+	originAnchor = document.createElement( "a" );
+	originAnchor.href = location.href;
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+	// dataTypeExpression is optional and defaults to "*"
+	return function( dataTypeExpression, func ) {
+
+		if ( typeof dataTypeExpression !== "string" ) {
+			func = dataTypeExpression;
+			dataTypeExpression = "*";
+		}
+
+		var dataType,
+			i = 0,
+			dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || [];
+
+		if ( isFunction( func ) ) {
+
+			// For each dataType in the dataTypeExpression
+			while ( ( dataType = dataTypes[ i++ ] ) ) {
+
+				// Prepend if requested
+				if ( dataType[ 0 ] === "+" ) {
+					dataType = dataType.slice( 1 ) || "*";
+					( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );
+
+				// Otherwise append
+				} else {
+					( structure[ dataType ] = structure[ dataType ] || [] ).push( func );
+				}
+			}
+		}
+	};
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
+
+	var inspected = {},
+		seekingTransport = ( structure === transports );
+
+	function inspect( dataType ) {
+		var selected;
+		inspected[ dataType ] = true;
+		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
+			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
+			if ( typeof dataTypeOrTransport === "string" &&
+				!seekingTransport && !inspected[ dataTypeOrTransport ] ) {
+
+				options.dataTypes.unshift( dataTypeOrTransport );
+				inspect( dataTypeOrTransport );
+				return false;
+			} else if ( seekingTransport ) {
+				return !( selected = dataTypeOrTransport );
+			}
+		} );
+		return selected;
+	}
+
+	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+	var key, deep,
+		flatOptions = jQuery.ajaxSettings.flatOptions || {};
+
+	for ( key in src ) {
+		if ( src[ key ] !== undefined ) {
+			( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+		}
+	}
+	if ( deep ) {
+		jQuery.extend( true, target, deep );
+	}
+
+	return target;
+}
+
+/* Handles responses to an ajax request:
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+	var ct, type, finalDataType, firstDataType,
+		contents = s.contents,
+		dataTypes = s.dataTypes;
+
+	// Remove auto dataType and get content-type in the process
+	while ( dataTypes[ 0 ] === "*" ) {
+		dataTypes.shift();
+		if ( ct === undefined ) {
+			ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" );
+		}
+	}
+
+	// Check if we're dealing with a known content-type
+	if ( ct ) {
+		for ( type in contents ) {
+			if ( contents[ type ] && contents[ type ].test( ct ) ) {
+				dataTypes.unshift( type );
+				break;
+			}
+		}
+	}
+
+	// Check to see if we have a response for the expected dataType
+	if ( dataTypes[ 0 ] in responses ) {
+		finalDataType = dataTypes[ 0 ];
+	} else {
+
+		// Try convertible dataTypes
+		for ( type in responses ) {
+			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) {
+				finalDataType = type;
+				break;
+			}
+			if ( !firstDataType ) {
+				firstDataType = type;
+			}
+		}
+
+		// Or just use first one
+		finalDataType = finalDataType || firstDataType;
+	}
+
+	// If we found a dataType
+	// We add the dataType to the list if needed
+	// and return the corresponding response
+	if ( finalDataType ) {
+		if ( finalDataType !== dataTypes[ 0 ] ) {
+			dataTypes.unshift( finalDataType );
+		}
+		return responses[ finalDataType ];
+	}
+}
+
+/* Chain conversions given the request and the original response
+ * Also sets the responseXXX fields on the jqXHR instance
+ */
+function ajaxConvert( s, response, jqXHR, isSuccess ) {
+	var conv2, current, conv, tmp, prev,
+		converters = {},
+
+		// Work with a copy of dataTypes in case we need to modify it for conversion
+		dataTypes = s.dataTypes.slice();
+
+	// Create converters map with lowercased keys
+	if ( dataTypes[ 1 ] ) {
+		for ( conv in s.converters ) {
+			converters[ conv.toLowerCase() ] = s.converters[ conv ];
+		}
+	}
+
+	current = dataTypes.shift();
+
+	// Convert to each sequential dataType
+	while ( current ) {
+
+		if ( s.responseFields[ current ] ) {
+			jqXHR[ s.responseFields[ current ] ] = response;
+		}
+
+		// Apply the dataFilter if provided
+		if ( !prev && isSuccess && s.dataFilter ) {
+			response = s.dataFilter( response, s.dataType );
+		}
+
+		prev = current;
+		current = dataTypes.shift();
+
+		if ( current ) {
+
+			// There's only work to do if current dataType is non-auto
+			if ( current === "*" ) {
+
+				current = prev;
+
+			// Convert response if prev dataType is non-auto and differs from current
+			} else if ( prev !== "*" && prev !== current ) {
+
+				// Seek a direct converter
+				conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+				// If none found, seek a pair
+				if ( !conv ) {
+					for ( conv2 in converters ) {
+
+						// If conv2 outputs current
+						tmp = conv2.split( " " );
+						if ( tmp[ 1 ] === current ) {
+
+							// If prev can be converted to accepted input
+							conv = converters[ prev + " " + tmp[ 0 ] ] ||
+								converters[ "* " + tmp[ 0 ] ];
+							if ( conv ) {
+
+								// Condense equivalence converters
+								if ( conv === true ) {
+									conv = converters[ conv2 ];
+
+								// Otherwise, insert the intermediate dataType
+								} else if ( converters[ conv2 ] !== true ) {
+									current = tmp[ 0 ];
+									dataTypes.unshift( tmp[ 1 ] );
+								}
+								break;
+							}
+						}
+					}
+				}
+
+				// Apply converter (if not an equivalence)
+				if ( conv !== true ) {
+
+					// Unless errors are allowed to bubble, catch and return them
+					if ( conv && s.throws ) {
+						response = conv( response );
+					} else {
+						try {
+							response = conv( response );
+						} catch ( e ) {
+							return {
+								state: "parsererror",
+								error: conv ? e : "No conversion from " + prev + " to " + current
+							};
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return { state: "success", data: response };
+}
+
+jQuery.extend( {
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Last-Modified header cache for next request
+	lastModified: {},
+	etag: {},
+
+	ajaxSettings: {
+		url: location.href,
+		type: "GET",
+		isLocal: rlocalProtocol.test( location.protocol ),
+		global: true,
+		processData: true,
+		async: true,
+		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+
+		/*
+		timeout: 0,
+		data: null,
+		dataType: null,
+		username: null,
+		password: null,
+		cache: null,
+		throws: false,
+		traditional: false,
+		headers: {},
+		*/
+
+		accepts: {
+			"*": allTypes,
+			text: "text/plain",
+			html: "text/html",
+			xml: "application/xml, text/xml",
+			json: "application/json, text/javascript"
+		},
+
+		contents: {
+			xml: /\bxml\b/,
+			html: /\bhtml/,
+			json: /\bjson\b/
+		},
+
+		responseFields: {
+			xml: "responseXML",
+			text: "responseText",
+			json: "responseJSON"
+		},
+
+		// Data converters
+		// Keys separate source (or catchall "*") and destination types with a single space
+		converters: {
+
+			// Convert anything to text
+			"* text": String,
+
+			// Text to html (true = no transformation)
+			"text html": true,
+
+			// Evaluate text as a json expression
+			"text json": JSON.parse,
+
+			// Parse text as xml
+			"text xml": jQuery.parseXML
+		},
+
+		// For options that shouldn't be deep extended:
+		// you can add your own custom options here if
+		// and when you create one that shouldn't be
+		// deep extended (see ajaxExtend)
+		flatOptions: {
+			url: true,
+			context: true
+		}
+	},
+
+	// Creates a full fledged settings object into target
+	// with both ajaxSettings and settings fields.
+	// If target is omitted, writes into ajaxSettings.
+	ajaxSetup: function( target, settings ) {
+		return settings ?
+
+			// Building a settings object
+			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
+
+			// Extending ajaxSettings
+			ajaxExtend( jQuery.ajaxSettings, target );
+	},
+
+	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+	ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+	// Main method
+	ajax: function( url, options ) {
+
+		// If url is an object, simulate pre-1.5 signature
+		if ( typeof url === "object" ) {
+			options = url;
+			url = undefined;
+		}
+
+		// Force options to be an object
+		options = options || {};
+
+		var transport,
+
+			// URL without anti-cache param
+			cacheURL,
+
+			// Response headers
+			responseHeadersString,
+			responseHeaders,
+
+			// timeout handle
+			timeoutTimer,
+
+			// Url cleanup var
+			urlAnchor,
+
+			// Request state (becomes false upon send and true upon completion)
+			completed,
+
+			// To know if global events are to be dispatched
+			fireGlobals,
+
+			// Loop variable
+			i,
+
+			// uncached part of the url
+			uncached,
+
+			// Create the final options object
+			s = jQuery.ajaxSetup( {}, options ),
+
+			// Callbacks context
+			callbackContext = s.context || s,
+
+			// Context for global events is callbackContext if it is a DOM node or jQuery collection
+			globalEventContext = s.context &&
+				( callbackContext.nodeType || callbackContext.jquery ) ?
+					jQuery( callbackContext ) :
+					jQuery.event,
+
+			// Deferreds
+			deferred = jQuery.Deferred(),
+			completeDeferred = jQuery.Callbacks( "once memory" ),
+
+			// Status-dependent callbacks
+			statusCode = s.statusCode || {},
+
+			// Headers (they are sent all at once)
+			requestHeaders = {},
+			requestHeadersNames = {},
+
+			// Default abort message
+			strAbort = "canceled",
+
+			// Fake xhr
+			jqXHR = {
+				readyState: 0,
+
+				// Builds headers hashtable if needed
+				getResponseHeader: function( key ) {
+					var match;
+					if ( completed ) {
+						if ( !responseHeaders ) {
+							responseHeaders = {};
+							while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
+								responseHeaders[ match[ 1 ].toLowerCase() + " " ] =
+									( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] )
+										.concat( match[ 2 ] );
+							}
+						}
+						match = responseHeaders[ key.toLowerCase() + " " ];
+					}
+					return match == null ? null : match.join( ", " );
+				},
+
+				// Raw string
+				getAllResponseHeaders: function() {
+					return completed ? responseHeadersString : null;
+				},
+
+				// Caches the header
+				setRequestHeader: function( name, value ) {
+					if ( completed == null ) {
+						name = requestHeadersNames[ name.toLowerCase() ] =
+							requestHeadersNames[ name.toLowerCase() ] || name;
+						requestHeaders[ name ] = value;
+					}
+					return this;
+				},
+
+				// Overrides response content-type header
+				overrideMimeType: function( type ) {
+					if ( completed == null ) {
+						s.mimeType = type;
+					}
+					return this;
+				},
+
+				// Status-dependent callbacks
+				statusCode: function( map ) {
+					var code;
+					if ( map ) {
+						if ( completed ) {
+
+							// Execute the appropriate callbacks
+							jqXHR.always( map[ jqXHR.status ] );
+						} else {
+
+							// Lazy-add the new callbacks in a way that preserves old ones
+							for ( code in map ) {
+								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
+							}
+						}
+					}
+					return this;
+				},
+
+				// Cancel the request
+				abort: function( statusText ) {
+					var finalText = statusText || strAbort;
+					if ( transport ) {
+						transport.abort( finalText );
+					}
+					done( 0, finalText );
+					return this;
+				}
+			};
+
+		// Attach deferreds
+		deferred.promise( jqXHR );
+
+		// Add protocol if not provided (prefilters might expect it)
+		// Handle falsy url in the settings object (#10093: consistency with old signature)
+		// We also use the url parameter if available
+		s.url = ( ( url || s.url || location.href ) + "" )
+			.replace( rprotocol, location.protocol + "//" );
+
+		// Alias method option to type as per ticket #12004
+		s.type = options.method || options.type || s.method || s.type;
+
+		// Extract dataTypes list
+		s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ];
+
+		// A cross-domain request is in order when the origin doesn't match the current origin.
+		if ( s.crossDomain == null ) {
+			urlAnchor = document.createElement( "a" );
+
+			// Support: IE <=8 - 11, Edge 12 - 15
+			// IE throws exception on accessing the href property if url is malformed,
+			// e.g. http://example.com:80x/
+			try {
+				urlAnchor.href = s.url;
+
+				// Support: IE <=8 - 11 only
+				// Anchor's host property isn't correctly set when s.url is relative
+				urlAnchor.href = urlAnchor.href;
+				s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !==
+					urlAnchor.protocol + "//" + urlAnchor.host;
+			} catch ( e ) {
+
+				// If there is an error parsing the URL, assume it is crossDomain,
+				// it can be rejected by the transport if it is invalid
+				s.crossDomain = true;
+			}
+		}
+
+		// Convert data if not already a string
+		if ( s.data && s.processData && typeof s.data !== "string" ) {
+			s.data = jQuery.param( s.data, s.traditional );
+		}
+
+		// Apply prefilters
+		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+		// If request was aborted inside a prefilter, stop there
+		if ( completed ) {
+			return jqXHR;
+		}
+
+		// We can fire global events as of now if asked to
+		// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
+		fireGlobals = jQuery.event && s.global;
+
+		// Watch for a new set of requests
+		if ( fireGlobals && jQuery.active++ === 0 ) {
+			jQuery.event.trigger( "ajaxStart" );
+		}
+
+		// Uppercase the type
+		s.type = s.type.toUpperCase();
+
+		// Determine if request has content
+		s.hasContent = !rnoContent.test( s.type );
+
+		// Save the URL in case we're toying with the If-Modified-Since
+		// and/or If-None-Match header later on
+		// Remove hash to simplify url manipulation
+		cacheURL = s.url.replace( rhash, "" );
+
+		// More options handling for requests with no content
+		if ( !s.hasContent ) {
+
+			// Remember the hash so we can put it back
+			uncached = s.url.slice( cacheURL.length );
+
+			// If data is available and should be processed, append data to url
+			if ( s.data && ( s.processData || typeof s.data === "string" ) ) {
+				cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data;
+
+				// #9682: remove data so that it's not used in an eventual retry
+				delete s.data;
+			}
+
+			// Add or update anti-cache param if needed
+			if ( s.cache === false ) {
+				cacheURL = cacheURL.replace( rantiCache, "$1" );
+				uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) +
+					uncached;
+			}
+
+			// Put hash and anti-cache on the URL that will be requested (gh-1732)
+			s.url = cacheURL + uncached;
+
+		// Change '%20' to '+' if this is encoded form body content (gh-2658)
+		} else if ( s.data && s.processData &&
+			( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) {
+			s.data = s.data.replace( r20, "+" );
+		}
+
+		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+		if ( s.ifModified ) {
+			if ( jQuery.lastModified[ cacheURL ] ) {
+				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
+			}
+			if ( jQuery.etag[ cacheURL ] ) {
+				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
+			}
+		}
+
+		// Set the correct header, if data is being sent
+		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+			jqXHR.setRequestHeader( "Content-Type", s.contentType );
+		}
+
+		// Set the Accepts header for the server, depending on the dataType
+		jqXHR.setRequestHeader(
+			"Accept",
+			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
+				s.accepts[ s.dataTypes[ 0 ] ] +
+					( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+				s.accepts[ "*" ]
+		);
+
+		// Check for headers option
+		for ( i in s.headers ) {
+			jqXHR.setRequestHeader( i, s.headers[ i ] );
+		}
+
+		// Allow custom headers/mimetypes and early abort
+		if ( s.beforeSend &&
+			( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) {
+
+			// Abort if not done already and return
+			return jqXHR.abort();
+		}
+
+		// Aborting is no longer a cancellation
+		strAbort = "abort";
+
+		// Install callbacks on deferreds
+		completeDeferred.add( s.complete );
+		jqXHR.done( s.success );
+		jqXHR.fail( s.error );
+
+		// Get transport
+		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+		// If no transport, we auto-abort
+		if ( !transport ) {
+			done( -1, "No Transport" );
+		} else {
+			jqXHR.readyState = 1;
+
+			// Send global event
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+			}
+
+			// If request was aborted inside ajaxSend, stop there
+			if ( completed ) {
+				return jqXHR;
+			}
+
+			// Timeout
+			if ( s.async && s.timeout > 0 ) {
+				timeoutTimer = window.setTimeout( function() {
+					jqXHR.abort( "timeout" );
+				}, s.timeout );
+			}
+
+			try {
+				completed = false;
+				transport.send( requestHeaders, done );
+			} catch ( e ) {
+
+				// Rethrow post-completion exceptions
+				if ( completed ) {
+					throw e;
+				}
+
+				// Propagate others as results
+				done( -1, e );
+			}
+		}
+
+		// Callback for when everything is done
+		function done( status, nativeStatusText, responses, headers ) {
+			var isSuccess, success, error, response, modified,
+				statusText = nativeStatusText;
+
+			// Ignore repeat invocations
+			if ( completed ) {
+				return;
+			}
+
+			completed = true;
+
+			// Clear timeout if it exists
+			if ( timeoutTimer ) {
+				window.clearTimeout( timeoutTimer );
+			}
+
+			// Dereference transport for early garbage collection
+			// (no matter how long the jqXHR object will be used)
+			transport = undefined;
+
+			// Cache response headers
+			responseHeadersString = headers || "";
+
+			// Set readyState
+			jqXHR.readyState = status > 0 ? 4 : 0;
+
+			// Determine if successful
+			isSuccess = status >= 200 && status < 300 || status === 304;
+
+			// Get response data
+			if ( responses ) {
+				response = ajaxHandleResponses( s, jqXHR, responses );
+			}
+
+			// Use a noop converter for missing script
+			if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) {
+				s.converters[ "text script" ] = function() {};
+			}
+
+			// Convert no matter what (that way responseXXX fields are always set)
+			response = ajaxConvert( s, response, jqXHR, isSuccess );
+
+			// If successful, handle type chaining
+			if ( isSuccess ) {
+
+				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+				if ( s.ifModified ) {
+					modified = jqXHR.getResponseHeader( "Last-Modified" );
+					if ( modified ) {
+						jQuery.lastModified[ cacheURL ] = modified;
+					}
+					modified = jqXHR.getResponseHeader( "etag" );
+					if ( modified ) {
+						jQuery.etag[ cacheURL ] = modified;
+					}
+				}
+
+				// if no content
+				if ( status === 204 || s.type === "HEAD" ) {
+					statusText = "nocontent";
+
+				// if not modified
+				} else if ( status === 304 ) {
+					statusText = "notmodified";
+
+				// If we have data, let's convert it
+				} else {
+					statusText = response.state;
+					success = response.data;
+					error = response.error;
+					isSuccess = !error;
+				}
+			} else {
+
+				// Extract error from statusText and normalize for non-aborts
+				error = statusText;
+				if ( status || !statusText ) {
+					statusText = "error";
+					if ( status < 0 ) {
+						status = 0;
+					}
+				}
+			}
+
+			// Set data for the fake xhr object
+			jqXHR.status = status;
+			jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+			// Success/Error
+			if ( isSuccess ) {
+				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+			} else {
+				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+			}
+
+			// Status-dependent callbacks
+			jqXHR.statusCode( statusCode );
+			statusCode = undefined;
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
+					[ jqXHR, s, isSuccess ? success : error ] );
+			}
+
+			// Complete
+			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+
+				// Handle the global AJAX counter
+				if ( !( --jQuery.active ) ) {
+					jQuery.event.trigger( "ajaxStop" );
+				}
+			}
+		}
+
+		return jqXHR;
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get( url, data, callback, "json" );
+	},
+
+	getScript: function( url, callback ) {
+		return jQuery.get( url, undefined, callback, "script" );
+	}
+} );
+
+jQuery.each( [ "get", "post" ], function( _i, method ) {
+	jQuery[ method ] = function( url, data, callback, type ) {
+
+		// Shift arguments if data argument was omitted
+		if ( isFunction( data ) ) {
+			type = type || callback;
+			callback = data;
+			data = undefined;
+		}
+
+		// The url can be an options object (which then must have .url)
+		return jQuery.ajax( jQuery.extend( {
+			url: url,
+			type: method,
+			dataType: type,
+			data: data,
+			success: callback
+		}, jQuery.isPlainObject( url ) && url ) );
+	};
+} );
+
+jQuery.ajaxPrefilter( function( s ) {
+	var i;
+	for ( i in s.headers ) {
+		if ( i.toLowerCase() === "content-type" ) {
+			s.contentType = s.headers[ i ] || "";
+		}
+	}
+} );
+
+
+jQuery._evalUrl = function( url, options, doc ) {
+	return jQuery.ajax( {
+		url: url,
+
+		// Make this explicit, since user can override this through ajaxSetup (#11264)
+		type: "GET",
+		dataType: "script",
+		cache: true,
+		async: false,
+		global: false,
+
+		// Only evaluate the response if it is successful (gh-4126)
+		// dataFilter is not invoked for failure responses, so using it instead
+		// of the default converter is kludgy but it works.
+		converters: {
+			"text script": function() {}
+		},
+		dataFilter: function( response ) {
+			jQuery.globalEval( response, options, doc );
+		}
+	} );
+};
+
+
+jQuery.fn.extend( {
+	wrapAll: function( html ) {
+		var wrap;
+
+		if ( this[ 0 ] ) {
+			if ( isFunction( html ) ) {
+				html = html.call( this[ 0 ] );
+			}
+
+			// The elements to wrap the target around
+			wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
+
+			if ( this[ 0 ].parentNode ) {
+				wrap.insertBefore( this[ 0 ] );
+			}
+
+			wrap.map( function() {
+				var elem = this;
+
+				while ( elem.firstElementChild ) {
+					elem = elem.firstElementChild;
+				}
+
+				return elem;
+			} ).append( this );
+		}
+
+		return this;
+	},
+
+	wrapInner: function( html ) {
+		if ( isFunction( html ) ) {
+			return this.each( function( i ) {
+				jQuery( this ).wrapInner( html.call( this, i ) );
+			} );
+		}
+
+		return this.each( function() {
+			var self = jQuery( this ),
+				contents = self.contents();
+
+			if ( contents.length ) {
+				contents.wrapAll( html );
+
+			} else {
+				self.append( html );
+			}
+		} );
+	},
+
+	wrap: function( html ) {
+		var htmlIsFunction = isFunction( html );
+
+		return this.each( function( i ) {
+			jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html );
+		} );
+	},
+
+	unwrap: function( selector ) {
+		this.parent( selector ).not( "body" ).each( function() {
+			jQuery( this ).replaceWith( this.childNodes );
+		} );
+		return this;
+	}
+} );
+
+
+jQuery.expr.pseudos.hidden = function( elem ) {
+	return !jQuery.expr.pseudos.visible( elem );
+};
+jQuery.expr.pseudos.visible = function( elem ) {
+	return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
+};
+
+
+
+
+jQuery.ajaxSettings.xhr = function() {
+	try {
+		return new window.XMLHttpRequest();
+	} catch ( e ) {}
+};
+
+var xhrSuccessStatus = {
+
+		// File protocol always yields status code 0, assume 200
+		0: 200,
+
+		// Support: IE <=9 only
+		// #1450: sometimes IE returns 1223 when it should be 204
+		1223: 204
+	},
+	xhrSupported = jQuery.ajaxSettings.xhr();
+
+support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
+support.ajax = xhrSupported = !!xhrSupported;
+
+jQuery.ajaxTransport( function( options ) {
+	var callback, errorCallback;
+
+	// Cross domain only allowed if supported through XMLHttpRequest
+	if ( support.cors || xhrSupported && !options.crossDomain ) {
+		return {
+			send: function( headers, complete ) {
+				var i,
+					xhr = options.xhr();
+
+				xhr.open(
+					options.type,
+					options.url,
+					options.async,
+					options.username,
+					options.password
+				);
+
+				// Apply custom fields if provided
+				if ( options.xhrFields ) {
+					for ( i in options.xhrFields ) {
+						xhr[ i ] = options.xhrFields[ i ];
+					}
+				}
+
+				// Override mime type if needed
+				if ( options.mimeType && xhr.overrideMimeType ) {
+					xhr.overrideMimeType( options.mimeType );
+				}
+
+				// X-Requested-With header
+				// For cross-domain requests, seeing as conditions for a preflight are
+				// akin to a jigsaw puzzle, we simply never set it to be sure.
+				// (it can always be set on a per-request basis or even using ajaxSetup)
+				// For same-domain requests, won't change header if already provided.
+				if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
+					headers[ "X-Requested-With" ] = "XMLHttpRequest";
+				}
+
+				// Set headers
+				for ( i in headers ) {
+					xhr.setRequestHeader( i, headers[ i ] );
+				}
+
+				// Callback
+				callback = function( type ) {
+					return function() {
+						if ( callback ) {
+							callback = errorCallback = xhr.onload =
+								xhr.onerror = xhr.onabort = xhr.ontimeout =
+									xhr.onreadystatechange = null;
+
+							if ( type === "abort" ) {
+								xhr.abort();
+							} else if ( type === "error" ) {
+
+								// Support: IE <=9 only
+								// On a manual native abort, IE9 throws
+								// errors on any property access that is not readyState
+								if ( typeof xhr.status !== "number" ) {
+									complete( 0, "error" );
+								} else {
+									complete(
+
+										// File: protocol always yields status 0; see #8605, #14207
+										xhr.status,
+										xhr.statusText
+									);
+								}
+							} else {
+								complete(
+									xhrSuccessStatus[ xhr.status ] || xhr.status,
+									xhr.statusText,
+
+									// Support: IE <=9 only
+									// IE9 has no XHR2 but throws on binary (trac-11426)
+									// For XHR2 non-text, let the caller handle it (gh-2498)
+									( xhr.responseType || "text" ) !== "text"  ||
+									typeof xhr.responseText !== "string" ?
+										{ binary: xhr.response } :
+										{ text: xhr.responseText },
+									xhr.getAllResponseHeaders()
+								);
+							}
+						}
+					};
+				};
+
+				// Listen to events
+				xhr.onload = callback();
+				errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" );
+
+				// Support: IE 9 only
+				// Use onreadystatechange to replace onabort
+				// to handle uncaught aborts
+				if ( xhr.onabort !== undefined ) {
+					xhr.onabort = errorCallback;
+				} else {
+					xhr.onreadystatechange = function() {
+
+						// Check readyState before timeout as it changes
+						if ( xhr.readyState === 4 ) {
+
+							// Allow onerror to be called first,
+							// but that will not handle a native abort
+							// Also, save errorCallback to a variable
+							// as xhr.onerror cannot be accessed
+							window.setTimeout( function() {
+								if ( callback ) {
+									errorCallback();
+								}
+							} );
+						}
+					};
+				}
+
+				// Create the abort callback
+				callback = callback( "abort" );
+
+				try {
+
+					// Do send the request (this may raise an exception)
+					xhr.send( options.hasContent && options.data || null );
+				} catch ( e ) {
+
+					// #14683: Only rethrow if this hasn't been notified as an error yet
+					if ( callback ) {
+						throw e;
+					}
+				}
+			},
+
+			abort: function() {
+				if ( callback ) {
+					callback();
+				}
+			}
+		};
+	}
+} );
+
+
+
+
+// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432)
+jQuery.ajaxPrefilter( function( s ) {
+	if ( s.crossDomain ) {
+		s.contents.script = false;
+	}
+} );
+
+// Install script dataType
+jQuery.ajaxSetup( {
+	accepts: {
+		script: "text/javascript, application/javascript, " +
+			"application/ecmascript, application/x-ecmascript"
+	},
+	contents: {
+		script: /\b(?:java|ecma)script\b/
+	},
+	converters: {
+		"text script": function( text ) {
+			jQuery.globalEval( text );
+			return text;
+		}
+	}
+} );
+
+// Handle cache's special case and crossDomain
+jQuery.ajaxPrefilter( "script", function( s ) {
+	if ( s.cache === undefined ) {
+		s.cache = false;
+	}
+	if ( s.crossDomain ) {
+		s.type = "GET";
+	}
+} );
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function( s ) {
+
+	// This transport only deals with cross domain or forced-by-attrs requests
+	if ( s.crossDomain || s.scriptAttrs ) {
+		var script, callback;
+		return {
+			send: function( _, complete ) {
+				script = jQuery( "<script>" )
+					.attr( s.scriptAttrs || {} )
+					.prop( { charset: s.scriptCharset, src: s.url } )
+					.on( "load error", callback = function( evt ) {
+						script.remove();
+						callback = null;
+						if ( evt ) {
+							complete( evt.type === "error" ? 404 : 200, evt.type );
+						}
+					} );
+
+				// Use native DOM manipulation to avoid our domManip AJAX trickery
+				document.head.appendChild( script[ 0 ] );
+			},
+			abort: function() {
+				if ( callback ) {
+					callback();
+				}
+			}
+		};
+	}
+} );
+
+
+
+
+var oldCallbacks = [],
+	rjsonp = /(=)\?(?=&|$)|\?\?/;
+
+// Default jsonp settings
+jQuery.ajaxSetup( {
+	jsonp: "callback",
+	jsonpCallback: function() {
+		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce.guid++ ) );
+		this[ callback ] = true;
+		return callback;
+	}
+} );
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+	var callbackName, overwritten, responseContainer,
+		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
+			"url" :
+			typeof s.data === "string" &&
+				( s.contentType || "" )
+					.indexOf( "application/x-www-form-urlencoded" ) === 0 &&
+				rjsonp.test( s.data ) && "data"
+		);
+
+	// Handle iff the expected data type is "jsonp" or we have a parameter to set
+	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
+
+		// Get callback name, remembering preexisting value associated with it
+		callbackName = s.jsonpCallback = isFunction( s.jsonpCallback ) ?
+			s.jsonpCallback() :
+			s.jsonpCallback;
+
+		// Insert callback into url or form data
+		if ( jsonProp ) {
+			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
+		} else if ( s.jsonp !== false ) {
+			s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+		}
+
+		// Use data converter to retrieve json after script execution
+		s.converters[ "script json" ] = function() {
+			if ( !responseContainer ) {
+				jQuery.error( callbackName + " was not called" );
+			}
+			return responseContainer[ 0 ];
+		};
+
+		// Force json dataType
+		s.dataTypes[ 0 ] = "json";
+
+		// Install callback
+		overwritten = window[ callbackName ];
+		window[ callbackName ] = function() {
+			responseContainer = arguments;
+		};
+
+		// Clean-up function (fires after converters)
+		jqXHR.always( function() {
+
+			// If previous value didn't exist - remove it
+			if ( overwritten === undefined ) {
+				jQuery( window ).removeProp( callbackName );
+
+			// Otherwise restore preexisting value
+			} else {
+				window[ callbackName ] = overwritten;
+			}
+
+			// Save back as free
+			if ( s[ callbackName ] ) {
+
+				// Make sure that re-using the options doesn't screw things around
+				s.jsonpCallback = originalSettings.jsonpCallback;
+
+				// Save the callback name for future use
+				oldCallbacks.push( callbackName );
+			}
+
+			// Call if it was a function and we have a response
+			if ( responseContainer && isFunction( overwritten ) ) {
+				overwritten( responseContainer[ 0 ] );
+			}
+
+			responseContainer = overwritten = undefined;
+		} );
+
+		// Delegate to script
+		return "script";
+	}
+} );
+
+
+
+
+// Support: Safari 8 only
+// In Safari 8 documents created via document.implementation.createHTMLDocument
+// collapse sibling forms: the second one becomes a child of the first one.
+// Because of that, this security measure has to be disabled in Safari 8.
+// https://bugs.webkit.org/show_bug.cgi?id=137337
+support.createHTMLDocument = ( function() {
+	var body = document.implementation.createHTMLDocument( "" ).body;
+	body.innerHTML = "<form></form><form></form>";
+	return body.childNodes.length === 2;
+} )();
+
+
+// Argument "data" should be string of html
+// context (optional): If specified, the fragment will be created in this context,
+// defaults to document
+// keepScripts (optional): If true, will include scripts passed in the html string
+jQuery.parseHTML = function( data, context, keepScripts ) {
+	if ( typeof data !== "string" ) {
+		return [];
+	}
+	if ( typeof context === "boolean" ) {
+		keepScripts = context;
+		context = false;
+	}
+
+	var base, parsed, scripts;
+
+	if ( !context ) {
+
+		// Stop scripts or inline event handlers from being executed immediately
+		// by using document.implementation
+		if ( support.createHTMLDocument ) {
+			context = document.implementation.createHTMLDocument( "" );
+
+			// Set the base href for the created document
+			// so any parsed elements with URLs
+			// are based on the document's URL (gh-2965)
+			base = context.createElement( "base" );
+			base.href = document.location.href;
+			context.head.appendChild( base );
+		} else {
+			context = document;
+		}
+	}
+
+	parsed = rsingleTag.exec( data );
+	scripts = !keepScripts && [];
+
+	// Single tag
+	if ( parsed ) {
+		return [ context.createElement( parsed[ 1 ] ) ];
+	}
+
+	parsed = buildFragment( [ data ], context, scripts );
+
+	if ( scripts && scripts.length ) {
+		jQuery( scripts ).remove();
+	}
+
+	return jQuery.merge( [], parsed.childNodes );
+};
+
+
+/**
+ * Load a url into a page
+ */
+jQuery.fn.load = function( url, params, callback ) {
+	var selector, type, response,
+		self = this,
+		off = url.indexOf( " " );
+
+	if ( off > -1 ) {
+		selector = stripAndCollapse( url.slice( off ) );
+		url = url.slice( 0, off );
+	}
+
+	// If it's a function
+	if ( isFunction( params ) ) {
+
+		// We assume that it's the callback
+		callback = params;
+		params = undefined;
+
+	// Otherwise, build a param string
+	} else if ( params && typeof params === "object" ) {
+		type = "POST";
+	}
+
+	// If we have elements to modify, make the request
+	if ( self.length > 0 ) {
+		jQuery.ajax( {
+			url: url,
+
+			// If "type" variable is undefined, then "GET" method will be used.
+			// Make value of this field explicit since
+			// user can override it through ajaxSetup method
+			type: type || "GET",
+			dataType: "html",
+			data: params
+		} ).done( function( responseText ) {
+
+			// Save response for use in complete callback
+			response = arguments;
+
+			self.html( selector ?
+
+				// If a selector was specified, locate the right elements in a dummy div
+				// Exclude scripts to avoid IE 'Permission Denied' errors
+				jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :
+
+				// Otherwise use the full result
+				responseText );
+
+		// If the request succeeds, this function gets "data", "status", "jqXHR"
+		// but they are ignored because response was set above.
+		// If it fails, this function gets "jqXHR", "status", "error"
+		} ).always( callback && function( jqXHR, status ) {
+			self.each( function() {
+				callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );
+			} );
+		} );
+	}
+
+	return this;
+};
+
+
+
+
+jQuery.expr.pseudos.animated = function( elem ) {
+	return jQuery.grep( jQuery.timers, function( fn ) {
+		return elem === fn.elem;
+	} ).length;
+};
+
+
+
+
+jQuery.offset = {
+	setOffset: function( elem, options, i ) {
+		var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
+			position = jQuery.css( elem, "position" ),
+			curElem = jQuery( elem ),
+			props = {};
+
+		// Set position first, in-case top/left are set even on static elem
+		if ( position === "static" ) {
+			elem.style.position = "relative";
+		}
+
+		curOffset = curElem.offset();
+		curCSSTop = jQuery.css( elem, "top" );
+		curCSSLeft = jQuery.css( elem, "left" );
+		calculatePosition = ( position === "absolute" || position === "fixed" ) &&
+			( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;
+
+		// Need to be able to calculate position if either
+		// top or left is auto and position is either absolute or fixed
+		if ( calculatePosition ) {
+			curPosition = curElem.position();
+			curTop = curPosition.top;
+			curLeft = curPosition.left;
+
+		} else {
+			curTop = parseFloat( curCSSTop ) || 0;
+			curLeft = parseFloat( curCSSLeft ) || 0;
+		}
+
+		if ( isFunction( options ) ) {
+
+			// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
+			options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
+		}
+
+		if ( options.top != null ) {
+			props.top = ( options.top - curOffset.top ) + curTop;
+		}
+		if ( options.left != null ) {
+			props.left = ( options.left - curOffset.left ) + curLeft;
+		}
+
+		if ( "using" in options ) {
+			options.using.call( elem, props );
+
+		} else {
+			if ( typeof props.top === "number" ) {
+				props.top += "px";
+			}
+			if ( typeof props.left === "number" ) {
+				props.left += "px";
+			}
+			curElem.css( props );
+		}
+	}
+};
+
+jQuery.fn.extend( {
+
+	// offset() relates an element's border box to the document origin
+	offset: function( options ) {
+
+		// Preserve chaining for setter
+		if ( arguments.length ) {
+			return options === undefined ?
+				this :
+				this.each( function( i ) {
+					jQuery.offset.setOffset( this, options, i );
+				} );
+		}
+
+		var rect, win,
+			elem = this[ 0 ];
+
+		if ( !elem ) {
+			return;
+		}
+
+		// Return zeros for disconnected and hidden (display: none) elements (gh-2310)
+		// Support: IE <=11 only
+		// Running getBoundingClientRect on a
+		// disconnected node in IE throws an error
+		if ( !elem.getClientRects().length ) {
+			return { top: 0, left: 0 };
+		}
+
+		// Get document-relative position by adding viewport scroll to viewport-relative gBCR
+		rect = elem.getBoundingClientRect();
+		win = elem.ownerDocument.defaultView;
+		return {
+			top: rect.top + win.pageYOffset,
+			left: rect.left + win.pageXOffset
+		};
+	},
+
+	// position() relates an element's margin box to its offset parent's padding box
+	// This corresponds to the behavior of CSS absolute positioning
+	position: function() {
+		if ( !this[ 0 ] ) {
+			return;
+		}
+
+		var offsetParent, offset, doc,
+			elem = this[ 0 ],
+			parentOffset = { top: 0, left: 0 };
+
+		// position:fixed elements are offset from the viewport, which itself always has zero offset
+		if ( jQuery.css( elem, "position" ) === "fixed" ) {
+
+			// Assume position:fixed implies availability of getBoundingClientRect
+			offset = elem.getBoundingClientRect();
+
+		} else {
+			offset = this.offset();
+
+			// Account for the *real* offset parent, which can be the document or its root element
+			// when a statically positioned element is identified
+			doc = elem.ownerDocument;
+			offsetParent = elem.offsetParent || doc.documentElement;
+			while ( offsetParent &&
+				( offsetParent === doc.body || offsetParent === doc.documentElement ) &&
+				jQuery.css( offsetParent, "position" ) === "static" ) {
+
+				offsetParent = offsetParent.parentNode;
+			}
+			if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) {
+
+				// Incorporate borders into its offset, since they are outside its content origin
+				parentOffset = jQuery( offsetParent ).offset();
+				parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true );
+				parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true );
+			}
+		}
+
+		// Subtract parent offsets and element margins
+		return {
+			top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
+			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
+		};
+	},
+
+	// This method will return documentElement in the following cases:
+	// 1) For the element inside the iframe without offsetParent, this method will return
+	//    documentElement of the parent window
+	// 2) For the hidden or detached element
+	// 3) For body or html element, i.e. in case of the html node - it will return itself
+	//
+	// but those exceptions were never presented as a real life use-cases
+	// and might be considered as more preferable results.
+	//
+	// This logic, however, is not guaranteed and can change at any point in the future
+	offsetParent: function() {
+		return this.map( function() {
+			var offsetParent = this.offsetParent;
+
+			while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
+				offsetParent = offsetParent.offsetParent;
+			}
+
+			return offsetParent || documentElement;
+		} );
+	}
+} );
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
+	var top = "pageYOffset" === prop;
+
+	jQuery.fn[ method ] = function( val ) {
+		return access( this, function( elem, method, val ) {
+
+			// Coalesce documents and windows
+			var win;
+			if ( isWindow( elem ) ) {
+				win = elem;
+			} else if ( elem.nodeType === 9 ) {
+				win = elem.defaultView;
+			}
+
+			if ( val === undefined ) {
+				return win ? win[ prop ] : elem[ method ];
+			}
+
+			if ( win ) {
+				win.scrollTo(
+					!top ? val : win.pageXOffset,
+					top ? val : win.pageYOffset
+				);
+
+			} else {
+				elem[ method ] = val;
+			}
+		}, method, val, arguments.length );
+	};
+} );
+
+// Support: Safari <=7 - 9.1, Chrome <=37 - 49
+// Add the top/left cssHooks using jQuery.fn.position
+// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347
+// getComputedStyle returns percent when specified for top/left/bottom/right;
+// rather than make the css module depend on the offset module, just check for it here
+jQuery.each( [ "top", "left" ], function( _i, prop ) {
+	jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
+		function( elem, computed ) {
+			if ( computed ) {
+				computed = curCSS( elem, prop );
+
+				// If curCSS returns percentage, fallback to offset
+				return rnumnonpx.test( computed ) ?
+					jQuery( elem ).position()[ prop ] + "px" :
+					computed;
+			}
+		}
+	);
+} );
+
+
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
+		function( defaultExtra, funcName ) {
+
+		// Margin is only for outerHeight, outerWidth
+		jQuery.fn[ funcName ] = function( margin, value ) {
+			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+			return access( this, function( elem, type, value ) {
+				var doc;
+
+				if ( isWindow( elem ) ) {
+
+					// $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
+					return funcName.indexOf( "outer" ) === 0 ?
+						elem[ "inner" + name ] :
+						elem.document.documentElement[ "client" + name ];
+				}
+
+				// Get document width or height
+				if ( elem.nodeType === 9 ) {
+					doc = elem.documentElement;
+
+					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
+					// whichever is greatest
+					return Math.max(
+						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+						elem.body[ "offset" + name ], doc[ "offset" + name ],
+						doc[ "client" + name ]
+					);
+				}
+
+				return value === undefined ?
+
+					// Get width or height on the element, requesting but not forcing parseFloat
+					jQuery.css( elem, type, extra ) :
+
+					// Set width or height on the element
+					jQuery.style( elem, type, value, extra );
+			}, type, chainable ? margin : undefined, chainable );
+		};
+	} );
+} );
+
+
+jQuery.each( [
+	"ajaxStart",
+	"ajaxStop",
+	"ajaxComplete",
+	"ajaxError",
+	"ajaxSuccess",
+	"ajaxSend"
+], function( _i, type ) {
+	jQuery.fn[ type ] = function( fn ) {
+		return this.on( type, fn );
+	};
+} );
+
+
+
+
+jQuery.fn.extend( {
+
+	bind: function( types, data, fn ) {
+		return this.on( types, null, data, fn );
+	},
+	unbind: function( types, fn ) {
+		return this.off( types, null, fn );
+	},
+
+	delegate: function( selector, types, data, fn ) {
+		return this.on( types, selector, data, fn );
+	},
+	undelegate: function( selector, types, fn ) {
+
+		// ( namespace ) or ( selector, types [, fn] )
+		return arguments.length === 1 ?
+			this.off( selector, "**" ) :
+			this.off( types, selector || "**", fn );
+	},
+
+	hover: function( fnOver, fnOut ) {
+		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+	}
+} );
+
+jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
+	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+	"change select submit keydown keypress keyup contextmenu" ).split( " " ),
+	function( _i, name ) {
+
+		// Handle event binding
+		jQuery.fn[ name ] = function( data, fn ) {
+			return arguments.length > 0 ?
+				this.on( name, null, data, fn ) :
+				this.trigger( name );
+		};
+	} );
+
+
+
+
+// Support: Android <=4.0 only
+// Make sure we trim BOM and NBSP
+var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
+
+// Bind a function to a context, optionally partially applying any
+// arguments.
+// jQuery.proxy is deprecated to promote standards (specifically Function#bind)
+// However, it is not slated for removal any time soon
+jQuery.proxy = function( fn, context ) {
+	var tmp, args, proxy;
+
+	if ( typeof context === "string" ) {
+		tmp = fn[ context ];
+		context = fn;
+		fn = tmp;
+	}
+
+	// Quick check to determine if target is callable, in the spec
+	// this throws a TypeError, but we will just return undefined.
+	if ( !isFunction( fn ) ) {
+		return undefined;
+	}
+
+	// Simulated bind
+	args = slice.call( arguments, 2 );
+	proxy = function() {
+		return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
+	};
+
+	// Set the guid of unique handler to the same of original handler, so it can be removed
+	proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+	return proxy;
+};
+
+jQuery.holdReady = function( hold ) {
+	if ( hold ) {
+		jQuery.readyWait++;
+	} else {
+		jQuery.ready( true );
+	}
+};
+jQuery.isArray = Array.isArray;
+jQuery.parseJSON = JSON.parse;
+jQuery.nodeName = nodeName;
+jQuery.isFunction = isFunction;
+jQuery.isWindow = isWindow;
+jQuery.camelCase = camelCase;
+jQuery.type = toType;
+
+jQuery.now = Date.now;
+
+jQuery.isNumeric = function( obj ) {
+
+	// As of jQuery 3.0, isNumeric is limited to
+	// strings and numbers (primitives or objects)
+	// that can be coerced to finite numbers (gh-2662)
+	var type = jQuery.type( obj );
+	return ( type === "number" || type === "string" ) &&
+
+		// parseFloat NaNs numeric-cast false positives ("")
+		// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
+		// subtraction forces infinities to NaN
+		!isNaN( obj - parseFloat( obj ) );
+};
+
+jQuery.trim = function( text ) {
+	return text == null ?
+		"" :
+		( text + "" ).replace( rtrim, "" );
+};
+
+
+
+// Register as a named AMD module, since jQuery can be concatenated with other
+// files that may use define, but not via a proper concatenation script that
+// understands anonymous AMD modules. A named AMD is safest and most robust
+// way to register. Lowercase jquery is used because AMD module names are
+// derived from file names, and jQuery is normally delivered in a lowercase
+// file name. Do this after creating the global so that if an AMD module wants
+// to call noConflict to hide this version of jQuery, it will work.
+
+// Note that for maximum portability, libraries that are not jQuery should
+// declare themselves as anonymous modules, and avoid setting a global if an
+// AMD loader is present. jQuery is a special case. For more information, see
+// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
+
+if ( typeof define === "function" && define.amd ) {
+	define( "jquery", [], function() {
+		return jQuery;
+	} );
+}
+
+
+
+
+var
+
+	// Map over jQuery in case of overwrite
+	_jQuery = window.jQuery,
+
+	// Map over the $ in case of overwrite
+	_$ = window.$;
+
+jQuery.noConflict = function( deep ) {
+	if ( window.$ === jQuery ) {
+		window.$ = _$;
+	}
+
+	if ( deep && window.jQuery === jQuery ) {
+		window.jQuery = _jQuery;
+	}
+
+	return jQuery;
+};
+
+// Expose jQuery and $ identifiers, even in AMD
+// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
+// and CommonJS for browser emulators (#13566)
+if ( typeof noGlobal === "undefined" ) {
+	window.jQuery = window.$ = jQuery;
+}
+
+
+
+
+return jQuery;
+} );
--- /dev/null
+++ b/domino/main.js
@@ -1,0 +1,23 @@
+
+	    global = {};
+	    //global.__domino_frozen__ = true; // Must precede any require('domino')
+	    var domino = require('domino-lib/index');
+	    var Element = domino.impl.Element; // etc
+
+	    // JSDOM also knows the style tag
+	    // https://github.com/jsdom/jsdom/issues/2485
+		Object.assign(this, domino.createWindow(s.html, 'http://example.com'));
+		window = this;
+		window.parent = window;
+		window.top = window;
+		window.self = window;
+		addEventListener = function() {};
+		window.location.href = 'http://example.com';
+		navigator = {};
+		HTMLElement = domino.impl.HTMLElement;
+	    // Fire DOMContentLoaded
+	    // to trigger $(document)readfy!!!!!!!
+	    document.close();
+	
+	console.log('Hello!!')
+	
\ No newline at end of file
--- /dev/null
+++ b/go.mod
@@ -1,0 +1,26 @@
+module opossum
+
+go 1.15
+
+replace 9fans.net/go v0.0.0-00010101000000-000000000000 => github.com/knusbaum/go v0.0.0-20200413212707-848f58a0ec6e
+
+exclude github.com/aymerick/douceur v0.1.0
+
+exclude github.com/aymerick/douceur v0.2.0
+
+require (
+	9fans.net/go v0.0.0-00010101000000-000000000000
+	github.com/PuerkitoBio/goquery v1.6.0 // indirect
+	github.com/chris-ramon/douceur v0.2.1-0.20160603235419-f3463056cd52
+	github.com/dop251/goja v0.0.0-20201107160812-7545ac6de48a
+	github.com/dop251/goja_nodejs v0.0.0-20200811150831-9bc458b4bbeb
+	github.com/gorilla/css v1.0.0 // indirect
+	github.com/jvatic/goja-babel v0.0.0-20200102152603-63c66b7c796a
+	github.com/mjl-/duit v0.0.0-20200330125617-580cb0b2843f
+	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
+	github.com/psilva261/css v0.1.0
+	golang.org/x/image v0.0.0-20200927104501-e162460cd6b5
+	golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
+	golang.org/x/text v0.3.4
+	gopkg.in/yaml.v2 v2.3.0 // indirect
+)
--- /dev/null
+++ b/go.sum
@@ -1,0 +1,60 @@
+github.com/PuerkitoBio/goquery v1.6.0 h1:j7taAbelrdcsOlGeMenZxc2AWXD5fieT1/znArdnx94=
+github.com/PuerkitoBio/goquery v1.6.0/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
+github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
+github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
+github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
+github.com/chris-ramon/douceur v0.2.1-0.20160603235419-f3463056cd52 h1:xJWyi77j4VQwdeo6bO3wQSQ7o7yVwEM0ZvwXpyKHZZ8=
+github.com/chris-ramon/douceur v0.2.1-0.20160603235419-f3463056cd52/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
+github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
+github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/dop251/goja v0.0.0-20191203121440-007eef3bc40f/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
+github.com/dop251/goja v0.0.0-20201107160812-7545ac6de48a h1:RYcWAh8DBgQQ7Fi3YhoyMhtGiF8JHKBDSeym7wd9o10=
+github.com/dop251/goja v0.0.0-20201107160812-7545ac6de48a/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
+github.com/dop251/goja_nodejs v0.0.0-20200811150831-9bc458b4bbeb h1:UGtCiVzBK40WGYBmNui17MHCkAqdo1j3BbhtU3mB1fI=
+github.com/dop251/goja_nodejs v0.0.0-20200811150831-9bc458b4bbeb/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
+github.com/go-sourcemap/sourcemap v2.1.2+incompatible h1:0b/xya7BKGhXuqFESKM4oIiRo9WOt2ebz7KxfreD6ug=
+github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
+github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
+github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
+github.com/jvatic/goja-babel v0.0.0-20200102152603-63c66b7c796a h1:WuwcEKWfDriJzlronFYhSDz9me9Xl7UbanAxTHpCLXA=
+github.com/jvatic/goja-babel v0.0.0-20200102152603-63c66b7c796a/go.mod h1:2Rjou2jq2BUH7Adnd9cIcU2fOYAIz2GxyuyqsNJm0N4=
+github.com/knusbaum/go v0.0.0-20200413212707-848f58a0ec6e h1:sMC7OcZa45aGaUJlCN2gK6l5IQD9WXTLXtFWIzZaeJQ=
+github.com/knusbaum/go v0.0.0-20200413212707-848f58a0ec6e/go.mod h1:VCPNE8vAcDWdtdY1piEGVOtcdrgFfQ3xV6q4XUwdAm8=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/mjl-/duit v0.0.0-20200330125617-580cb0b2843f h1:eGFou1VfXmiti7EMQED6BIzfALMYi6/fBMIRL4usKfw=
+github.com/mjl-/duit v0.0.0-20200330125617-580cb0b2843f/go.mod h1:OlRagobzQ97GoM+WaQ5kyzdyts952BFYsuY5bMyv9tw=
+github.com/mjl-/go v0.0.0-20180429123528-fafada5f286e h1:M6KcUwCT34dkXLeYORteGPNguMsAaYkE/IBGKRGXFUI=
+github.com/mjl-/go v0.0.0-20180429123528-fafada5f286e/go.mod h1:G+1evjukGD/lUyIfAq/sr0YX5EcukfBUeA+tFPF5JUA=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
+github.com/psilva261/css v0.1.0 h1:lzQtXIUKSdH7s6Vi4SeOt1YnTWFY2O/H//rujTGcu10=
+github.com/psilva261/css v0.1.0/go.mod h1:3jVsGoeXcAQqOOyDYqJe4p3bFgR1IXdAilbe2V4WMbE=
+github.com/stvp/assert v0.0.0-20170616060220-4bc16443988b h1:GlTM/aMVIwU3luIuSN2SIVRuTqGPt1P97YxAi514ulw=
+github.com/stvp/assert v0.0.0-20170616060220-4bc16443988b/go.mod h1:CC7OXV9IjEZRA+znA6/Kz5vbSwh69QioernOHeDCatU=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
+golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20191108045252-6f6bbb1828be/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
--- /dev/null
+++ b/logger/logger.go
@@ -1,0 +1,104 @@
+package logger
+
+import (
+	"fmt"
+	"io"
+	goLog "log"
+	"os"
+)
+
+// Sink for Go's log pkg
+var Sink io.Writer
+var Quiet *bool
+var Log *Logger
+var gl *goLog.Logger
+
+func Init() {
+	gl = goLog.New(os.Stderr, "", goLog.LstdFlags)
+	Log = &Logger{}
+	if *Quiet {
+		Sink = &NullWriter{}
+		goLog.SetOutput(Sink)
+	}
+}
+
+type NullWriter struct{}
+
+func (w *NullWriter) Write(p []byte) (n int, err error) {
+	n = len(p)
+	return
+}
+
+type Logger struct {
+	Debug    bool
+	last     string
+	lastSev  int
+	repeated int
+}
+
+func (l *Logger) Printf(format string, v ...interface{}) {
+	l.emit(debug, format, v...)
+}
+
+func (l *Logger) Infof(format string, v ...interface{}) {
+	l.emit(info, format, v...)
+}
+
+func (l *Logger) Errorf(format string, v ...interface{}) {
+	l.emit(er, format, v...)
+}
+
+func (l *Logger) Fatal(v ...interface{}) {
+	gl.Fatal(v...)
+}
+
+func (l *Logger) Fatalf(format string, v ...interface{}) {
+	gl.Fatalf(format, v...)
+}
+
+const (
+	debug = iota
+	print
+	info
+	er
+	fatal
+
+	flush
+)
+
+func (l *Logger) Flush() {
+	l.emit(flush, "")
+}
+
+func (l *Logger) emit(severity int, format string, v ...interface{}) {
+	if severity == debug && !l.Debug {
+		return
+	}
+	if (severity != fatal && severity != flush) && *Quiet {
+		return
+	}
+
+	msg := fmt.Sprintf(format, v...)
+	switch {
+	case l.last == msg && l.lastSev == severity:
+		l.repeated++
+	case l.repeated > 0:
+		goLog.Printf("...and %v more", l.repeated)
+		l.repeated = 0
+		fallthrough
+	default:
+		switch severity {
+		case debug:
+			gl.Printf(format, v...)
+		case info:
+			gl.Printf(format, v...)
+		case er:
+			gl.Printf(format, v...)
+		case fatal:
+			gl.Fatalf(format, v...)
+		case flush:
+		}
+	}
+	l.last = msg
+	l.lastSev = severity
+}
--- /dev/null
+++ b/nodes/experimental.go
@@ -1,0 +1,57 @@
+package nodes
+
+import (
+	//"golang.org/x/net/html"
+	//"opossum/style"
+	//"strings"
+)
+func (n *Node) NumVClusters() (m int) {
+	if n.IsFlex() {
+		if n.IsFlexDirectionRow() {
+			return 1
+		} else {
+			return len(n.Children)
+		}
+	} else {
+		for i, c := range n.Children {
+			if i == 0 || !c.IsInline() {
+				m++
+			}
+		}
+	}
+	return
+}
+
+func (n *Node) VSlice(i, j int) (s []*Node) {
+	s = make([]*Node, 0, j-i)
+	m := 0
+	for l, c := range n.Children {
+		if l == 0 || !c.IsInline() {
+			m++
+		}
+		if i <= l && l <= j {
+			s = append(s, c)
+		}
+	}
+	return
+}
+
+type Fan struct {
+	from []int
+	to   []int
+}
+
+func (f Fan) Slice(root *Node) *Node {
+	return nil
+}
+
+/*func FanSlice(root *Node, depth, k, i, j int) *Node {
+	newRoot := *root
+	if depth == 0 {
+
+	} else {
+		for i, c := range root.Children {
+			newRoot.Children[i] =
+		}
+	}
+}*/
--- /dev/null
+++ b/nodes/nodes.go
@@ -1,0 +1,117 @@
+package nodes
+
+import (
+	"golang.org/x/net/html"
+	"opossum/logger"
+	"opossum/style"
+	"strings"
+)
+
+var log *logger.Logger
+func SetLogger(l *logger.Logger) {
+	log = l
+}
+
+// Node represents a node at the render stage. It
+// represents a subTree or just a single html node.
+type Node struct {
+	DomSubtree *html.Node
+	Text string
+	Wrappable bool
+	Attr []html.Attribute
+	style.Map
+	Children []*Node
+	Parent *Node
+}
+
+// NewNodeTree propagates the cascading styles to the leaves
+//
+// First applies the global style and at the end the local style attribute's style is attached.
+func NewNodeTree(doc *html.Node, cs style.Map, nodeMap map[*html.Node]style.Map, parent *Node) (n *Node) {
+	ncs := cs
+	if m, ok := nodeMap[doc]; ok {
+		ncs = ncs.ApplyChildStyle(m)
+	}
+	ncs = ncs.ApplyChildStyle(style.NewMap(doc))
+	data := doc.Data
+	if doc.Type == html.ElementNode {
+		data = strings.ToLower(data)
+	}
+	n = &Node{
+		//Data:           data,
+		//Type:           doc.Type,
+		DomSubtree:   doc,
+		Attr:           doc.Attr,
+		Map: ncs,
+		Children:       make([]*Node, 0, 2),
+		Parent: parent,
+	}
+	n.Wrappable = doc.Type == html.TextNode || doc.Data == "span" // TODO: probably this list needs to be extended
+	if doc.Type == html.TextNode {
+
+		n.Text = filterText(doc.Data)
+	}
+	i := 0
+	for c := doc.FirstChild; c != nil; c = c.NextSibling {
+		if c.Type != html.CommentNode {
+			n.Children = append(n.Children, NewNodeTree(c, ncs, nodeMap, n))
+			i++
+		}
+	}
+
+	return
+}
+
+// filterText removes line break runes (TODO: add this later but handle properly)
+func filterText(t string) (text string) {
+	return strings.ReplaceAll(t, "­", "")
+}
+
+func (n Node) Type() html.NodeType {
+	return n.DomSubtree.Type
+}
+
+func (n Node) Data() string {
+	return n.DomSubtree.Data
+}
+
+func (n *Node) ParentForm() *Node {
+	log.Printf("<%v>.ParentForm()", n.DomSubtree.Data)
+	if n.DomSubtree.Data == "form" {
+		log.Printf("  I'm a form :-)")
+		return n
+	}
+	if n.Parent != nil {
+		log.Printf("  go to my parent")
+		return n.Parent.ParentForm()
+	}
+	return nil
+}
+
+func IsPureTextContent(n Node) bool {
+	if n.Text != "" {
+		return true
+	}
+	for _, c := range n.Children {
+		if c.Text == "" {
+			return false
+		}
+	}
+	return true
+}
+
+func ContentFrom(n Node) string {
+	var content string
+
+	if n.Text != "" && n.Type() == html.TextNode && !n.Map.IsDisplayNone() {
+		content += n.Text
+	}
+
+	for _, c := range n.Children {
+		if !c.Map.IsDisplayNone() {
+			content += ContentFrom(*c)
+		}
+	}
+
+	return strings.TrimSpace(content)
+}
\ No newline at end of file
--- /dev/null
+++ b/nodes/nodes_test.go
@@ -1,0 +1,13 @@
+package nodes
+
+import (
+	"testing"
+)
+
+func TestFilterText(t *testing.T) {
+	in := "eben­falls"
+	exp := "ebenfalls"
+	if out := filterText(in); out != exp {
+		t.Fatalf("%+v", out)
+	}
+}
\ No newline at end of file
--- /dev/null
+++ b/normalize.css
@@ -1,0 +1,349 @@
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+
+/* Document
+   ========================================================================== */
+
+/**
+ * 1. Correct the line height in all browsers.
+ * 2. Prevent adjustments of font size after orientation changes in iOS.
+ */
+
+html {
+  line-height: 1.15; /* 1 */
+  -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/* Sections
+   ========================================================================== */
+
+/**
+ * Remove the margin in all browsers.
+ */
+
+body {
+  margin: 0;
+}
+
+/**
+ * Render the `main` element consistently in IE.
+ */
+
+main {
+  display: block;
+}
+
+/**
+ * Correct the font size and margin on `h1` elements within `section` and
+ * `article` contexts in Chrome, Firefox, and Safari.
+ */
+
+h1 {
+  font-size: 2em;
+  margin: 0.67em 0;
+}
+
+/* Grouping content
+   ========================================================================== */
+
+/**
+ * 1. Add the correct box sizing in Firefox.
+ * 2. Show the overflow in Edge and IE.
+ */
+
+hr {
+  box-sizing: content-box; /* 1 */
+  height: 0; /* 1 */
+  overflow: visible; /* 2 */
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+pre {
+  font-family: monospace, monospace; /* 1 */
+  font-size: 1em; /* 2 */
+}
+
+/* Text-level semantics
+   ========================================================================== */
+
+/**
+ * Remove the gray background on active links in IE 10.
+ */
+
+a {
+  background-color: transparent;
+}
+
+/**
+ * 1. Remove the bottom border in Chrome 57-
+ * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
+ */
+
+abbr[title] {
+  border-bottom: none; /* 1 */
+  text-decoration: underline; /* 2 */
+  text-decoration: underline dotted; /* 2 */
+}
+
+/**
+ * Add the correct font weight in Chrome, Edge, and Safari.
+ */
+
+b,
+strong {
+  font-weight: bolder;
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+code,
+kbd,
+samp {
+  font-family: monospace, monospace; /* 1 */
+  font-size: 1em; /* 2 */
+}
+
+/**
+ * Add the correct font size in all browsers.
+ */
+
+small {
+  font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` elements from affecting the line height in
+ * all browsers.
+ */
+
+sub,
+sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+sup {
+  top: -0.5em;
+}
+
+/* Embedded content
+   ========================================================================== */
+
+/**
+ * Remove the border on images inside links in IE 10.
+ */
+
+img {
+  border-style: none;
+}
+
+/* Forms
+   ========================================================================== */
+
+/**
+ * 1. Change the font styles in all browsers.
+ * 2. Remove the margin in Firefox and Safari.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+  font-family: inherit; /* 1 */
+  font-size: 100%; /* 1 */
+  line-height: 1.15; /* 1 */
+  margin: 0; /* 2 */
+}
+
+/**
+ * Show the overflow in IE.
+ * 1. Show the overflow in Edge.
+ */
+
+button,
+input { /* 1 */
+  overflow: visible;
+}
+
+/**
+ * Remove the inheritance of text transform in Edge, Firefox, and IE.
+ * 1. Remove the inheritance of text transform in Firefox.
+ */
+
+button,
+select { /* 1 */
+  text-transform: none;
+}
+
+/**
+ * Correct the inability to style clickable types in iOS and Safari.
+ */
+
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+  -webkit-appearance: button;
+}
+
+/**
+ * Remove the inner border and padding in Firefox.
+ */
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+  border-style: none;
+  padding: 0;
+}
+
+/**
+ * Restore the focus styles unset by the previous rule.
+ */
+
+button:-moz-focusring,
+[type="button"]:-moz-focusring,
+[type="reset"]:-moz-focusring,
+[type="submit"]:-moz-focusring {
+  outline: 1px dotted ButtonText;
+}
+
+/**
+ * Correct the padding in Firefox.
+ */
+
+fieldset {
+  padding: 0.35em 0.75em 0.625em;
+}
+
+/**
+ * 1. Correct the text wrapping in Edge and IE.
+ * 2. Correct the color inheritance from `fieldset` elements in IE.
+ * 3. Remove the padding so developers are not caught out when they zero out
+ *    `fieldset` elements in all browsers.
+ */
+
+legend {
+  box-sizing: border-box; /* 1 */
+  color: inherit; /* 2 */
+  display: table; /* 1 */
+  max-width: 100%; /* 1 */
+  padding: 0; /* 3 */
+  white-space: normal; /* 1 */
+}
+
+/**
+ * Add the correct vertical alignment in Chrome, Firefox, and Opera.
+ */
+
+progress {
+  vertical-align: baseline;
+}
+
+/**
+ * Remove the default vertical scrollbar in IE 10+.
+ */
+
+textarea {
+  overflow: auto;
+}
+
+/**
+ * 1. Add the correct box sizing in IE 10.
+ * 2. Remove the padding in IE 10.
+ */
+
+[type="checkbox"],
+[type="radio"] {
+  box-sizing: border-box; /* 1 */
+  padding: 0; /* 2 */
+}
+
+/**
+ * Correct the cursor style of increment and decrement buttons in Chrome.
+ */
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+  height: auto;
+}
+
+/**
+ * 1. Correct the odd appearance in Chrome and Safari.
+ * 2. Correct the outline style in Safari.
+ */
+
+[type="search"] {
+  -webkit-appearance: textfield; /* 1 */
+  outline-offset: -2px; /* 2 */
+}
+
+/**
+ * Remove the inner padding in Chrome and Safari on macOS.
+ */
+
+[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+/**
+ * 1. Correct the inability to style clickable types in iOS and Safari.
+ * 2. Change font properties to `inherit` in Safari.
+ */
+
+::-webkit-file-upload-button {
+  -webkit-appearance: button; /* 1 */
+  font: inherit; /* 2 */
+}
+
+/* Interactive
+   ========================================================================== */
+
+/*
+ * Add the correct display in Edge, IE 10+, and Firefox.
+ */
+
+details {
+  display: block;
+}
+
+/*
+ * Add the correct display in all browsers.
+ */
+
+summary {
+  display: list-item;
+}
+
+/* Misc
+   ========================================================================== */
+
+/**
+ * Add the correct display in IE 10+.
+ */
+
+template {
+  display: none;
+}
+
+/**
+ * Add the correct display in IE 10.
+ */
+
+[hidden] {
+  display: none;
+}
--- /dev/null
+++ b/opossum.go
@@ -1,0 +1,79 @@
+package opossum
+
+import (
+	"bytes"
+	"golang.org/x/text/encoding/charmap"
+	"io/ioutil"
+	"mime"
+	"opossum/logger"
+	"net/url"
+	"strings"
+)
+
+var log *logger.Logger
+
+func SetLogger(l *logger.Logger) {
+	log = l
+}
+
+type Fetcher interface {
+	// LinkedUrl relative to current page
+	LinkedUrl(string) (*url.URL, error)
+
+	Get(url.URL) ([]byte, ContentType, error)
+}
+
+type ContentType struct {
+	MediaType string
+	Params map[string]string
+}
+
+func NewContentType(s string) (c ContentType, err error) {
+	c.MediaType, c.Params, err = mime.ParseMediaType(s)
+	return
+}
+
+func (c ContentType) IsHTML() bool {
+	return c.MediaType == "text/html"
+}
+
+func (c ContentType) IsCSS() bool {
+	return c.MediaType != "text/html"	
+}
+
+func (c ContentType) IsJS() bool {
+	for _, t := range []string{"application/javascript", "application/ecmascript", "text/javascript", "text/ecmascript"} {
+		if t == c.MediaType {
+			return true
+		}
+	}
+	return false
+}
+
+func (c ContentType) IsPlain() bool {
+	return c.MediaType == "text/plain"	
+}
+
+func (c ContentType) IsDownload() bool {
+	return c.MediaType == "application/octet-stream" ||
+		c.MediaType == "application/zip"
+}
+
+func (c ContentType) Utf8(buf []byte) []byte {
+	charset, ok := c.Params["charset"]
+	if !ok || charset == "utf8" || charset == "utf-8" {
+		return buf
+	}
+	if strings.ToLower(charset) == "iso-8859-1" {
+		r := bytes.NewReader(buf)
+	    cr := charmap.ISO8859_1.NewDecoder().Reader(r)
+
+		updated, err := ioutil.ReadAll(cr)
+		if err == nil {
+			buf = updated
+		} else {
+			log.Errorf("utf8: unable to decode to %v: %v", charset, err)
+		}
+	}
+	return buf
+}
\ No newline at end of file
--- /dev/null
+++ b/opossum_test.go
@@ -1,0 +1,16 @@
+package opossum
+
+import (
+    "testing"
+
+    "github.com/chris-ramon/douceur/parser"
+)
+
+// aymerick douceur issues #6
+func TestInfiniteLoop(t *testing.T) {
+    parser.Parse(`
+@media ( __desktop ) {
+  background-color: red;
+}
+`)
+}
--- /dev/null
+++ b/package.rc
@@ -1,0 +1,21 @@
+echo Compiling...
+GOOS=plan9 GOARCH=amd64 go build -o opossum-plan9-amd64.bin
+rm -rf ./opossum-plan9-amd64
+mkdir ./opossum-plan9-amd64
+mv opossum-plan9-amd64.bin ./opossum-plan9-amd64/
+cp normalize.css ./opossum-plan9-amd64/
+cp -r domino-lib ./opossum-plan9-amd64/domino-lib
+tar czf opossum-plan9-amd64.tgz opossum-plan9-amd64
+echo Created opossum-plan9-amd64.tgz
+
+rm -rf ./opossum-src
+mkdir ./opossum-src
+cp go.mod opossum-src
+cp go.sum opossum-src
+cp main*.go opossum-src
+cp normalize.css ./opossum-src/
+cp -r domino-lib ./opossum-src/domino-lib
+cp -r stylesheets ./opossum-src/stylesheets 
+cp -r tokenization ./opossum-src/tokenization 
+tar czf opossum-src.tgz opossum-src
+echo Created opossum-src.tgz
binary files /dev/null b/possum-655x493.jpg differ
--- /dev/null
+++ b/style/experimental.go
@@ -1,0 +1,113 @@
+package style
+
+import (
+	"9fans.net/go/draw"
+	"bytes"
+	"github.com/chris-ramon/douceur/css"
+	"fmt"
+	"github.com/mjl-/duit"
+	"image"
+	"opossum"
+	"strings"
+)
+
+var colorCache = make(map[draw.Color]*draw.Image)
+var fetcher opossum.Fetcher
+
+func SetFetcher(f opossum.Fetcher) {
+	fetcher = f
+}
+
+var TextNode = Map{
+	Declarations: map[string]css.Declaration{
+		"display": css.Declaration{
+			Property: "display",
+			Value:    "inline",
+		},
+	},
+}
+
+func (cs Map) BoxBackground() (i *draw.Image, err error) {
+	if ExperimentalUseBoxBackgrounds {
+		var bgImg *draw.Image
+
+		bgImg = cs.backgroundImage()
+
+		if bgImg == nil {
+			bgColor := cs.backgroundColor()
+			log.Printf("bgColor=%+v", bgColor)
+			var ok bool
+			i, ok = colorCache[bgColor]
+			if !ok {
+				var err error
+				i, err = dui.Display.AllocImage(image.Rect(0, 0, 10, 10), draw.ARGB32, true, bgColor)
+				if err != nil {
+					return nil, fmt.Errorf("alloc img: %w", err)
+				}
+				colorCache[bgColor] = i
+			}
+		} else {
+			i = bgImg
+		}
+	}
+	return
+}
+
+func (cs Map) backgroundColor() draw.Color {
+	_, ok := cs.Declarations["background-color"]
+	if ok {
+		return draw.Color(cs.colorHex("background-color"))
+	}
+	_, ok = cs.Declarations["background"]
+	if ok {
+		return draw.Color(cs.colorHex("background"))
+	}
+	return draw.Color(uint32(draw.White))
+}
+
+func (cs Map) backgroundImage() (img *draw.Image) {
+	decl, ok := cs.Declarations["background"]
+	log.Printf("decl=%+v\n", decl)
+	if ok {
+		log.Printf("bg img ok")
+		if strings.Contains(decl.Value, "url(") && strings.Contains(decl.Value, ")") {
+			from := strings.Index(decl.Value, "url(")
+			if from < 0 {
+				log.Printf("bg img: no url: %v", decl.Value)
+				return
+			}
+			from += len("url('")
+			imgUrl := decl.Value[from:]
+			to := strings.Index(imgUrl, ")")
+			if to < 0 {
+				log.Printf("bg img: no ): %v", decl.Value)
+				return
+			}
+			to -= len("'")
+			imgUrl = imgUrl[:to]
+			uri, err := fetcher.LinkedUrl(imgUrl)
+			if err != nil {
+				log.Printf("bg img interpet url: %v", err)
+				return nil
+			}
+			buf, contentType, err := fetcher.Get(*uri)
+			if err != nil {
+				log.Printf("bg img get %v (%v): %v", uri, contentType, err)
+				return nil
+			}
+			r := bytes.NewReader(buf)
+			log.Printf("Read %v...", imgUrl)
+			img, err = duit.ReadImage(dui.Display, r)
+			if err != nil {
+				log.Printf("bg read image: %v", err)
+				return
+			}
+			return img
+		} else {
+			log.Printf("bg img: missing fixes '%+v'", decl.Value)
+		}
+	} else {
+		log.Printf("bg img not ok")
+	}
+	return
+}
\ No newline at end of file
--- /dev/null
+++ b/style/fonts_plan9.go
@@ -1,0 +1,22 @@
+// +build plan9
+
+package style
+
+import (
+	"fmt"
+	"math"
+)
+
+func matchClosestFontSize(desired float64, available []int) (closest int) {
+	for _, a := range available {
+		if closest == 0 || math.Abs(float64(a)-desired) < math.Abs(float64(closest)-desired) {
+			closest = a
+		}
+	}
+	return
+}
+
+func (cs Map) FontFilename() string {
+	fontSize := matchClosestFontSize(cs.FontSize(), []int{5,6,7,8,9,10,12,14,16,18,20,24,28,32})
+	return fmt.Sprintf("/lib/font/bit/lucida/unicode.%v.font", fontSize)
+}
--- /dev/null
+++ b/style/fonts_unix.go
@@ -1,0 +1,15 @@
+// +build darwin freebsd netbsd openbsd linux
+
+package style
+
+import (
+	"fmt"
+	"math"
+)
+
+func (cs Map) FontFilename() string {
+	pref := cs.preferedFontName([]string{"HelveticaNeue", "Helvetica"})
+	fontSize := 2 * /*dui.Scale(*/int(math.RoundToEven(cs.FontSize()))/*)*/
+
+	return fmt.Sprintf("/mnt/font/"+pref+"%va/font", fontSize)
+}
\ No newline at end of file
--- /dev/null
+++ b/style/stylesheets.go
@@ -1,0 +1,505 @@
+package style
+
+import (
+	"9fans.net/go/draw"
+	"fmt"
+	"github.com/chris-ramon/douceur/css"
+	"github.com/chris-ramon/douceur/inliner"
+	"github.com/chris-ramon/douceur/parser"
+	cssSel "github.com/psilva261/css"
+	"github.com/mjl-/duit"
+	"golang.org/x/image/colornames"
+	"golang.org/x/net/html"
+	"io/ioutil"
+	"opossum/logger"
+	"os/exec"
+	"regexp"
+	"strconv"
+	"strings"
+)
+
+var CssFonts = true
+var fontCache = make(map[string]*draw.Font)
+
+// experimentalUseBoxBackgrounds should probably be combined with
+// setting stashElements to false
+var ExperimentalUseBoxBackgrounds = false
+var dui *duit.DUI
+var availableFontNames []string
+var log *logger.Logger
+
+var rMinWidth = regexp.MustCompile(`min-width: (\d+)px`)
+var rMaxWidth = regexp.MustCompile(`max-width: (\d+)px`)
+
+const AddOnCSS = `
+a, span, i, tt, b {
+  display: inline;
+}
+
+h1, h2, h3, div, center {
+	display: block;
+}
+
+a {
+  color: blue;
+}
+`
+
+func Init(d *duit.DUI, l *logger.Logger) {
+	dui = d
+	log = l
+
+	initFontserver()
+}
+
+func initFontserver() {
+	buf, err := exec.Command("fontsrv", "-p", ".").Output()
+	if err == nil {
+		availableFontNames = strings.Split(string(buf), "\n")
+	} else {
+		log.Printf("exec fontsrv: %v", err)
+	}
+}
+
+func Hrefs(doc *html.Node) (hrefs []string) {
+	hrefs = make([]string, 0, 3)
+
+	var f func(n *html.Node)
+	f = func(n *html.Node) {
+		if n.Type == html.ElementNode && n.Data == "link" {
+			isStylesheet := false
+			href := ""
+
+			for _, a := range n.Attr {
+				switch strings.ToLower(a.Key) {
+				case "rel":
+					if a.Val == "stylesheet" {
+						isStylesheet = true
+					}
+				case "href":
+					href = a.Val
+				}
+			}
+
+			if isStylesheet {
+				hrefs = append(hrefs, href)
+			}
+		}
+		for c := n.FirstChild; c != nil; c = c.NextSibling {
+			f(c)
+		}
+	}
+
+	f(doc)
+
+	return
+}
+
+func MergeNodeMaps(m, addOn map[*html.Node]Map) {
+	for n, mp := range addOn {
+		// "zero" valued Map if it doesn't exist yet
+		initial := m[n]
+
+		m[n] = initial.ApplyChildStyle(mp)
+	}
+}
+
+func FetchNodeMap(doc *html.Node, cssText string, windowWidth int) (m map[*html.Node]Map, err error) {
+	mr, err := FetchNodeRules(doc, cssText, windowWidth)
+	if err != nil {
+		return nil, fmt.Errorf("fetch rules: %w", err)
+	}
+	m = make(map[*html.Node]Map)
+	for n, rs := range mr {
+		ds := make(map[string]css.Declaration)
+		for _, r := range rs {
+			for _, d := range r.Declarations {
+				ds[d.Property] = *d
+			}
+		}
+		m[n] = Map{ds}
+	}
+	return
+}
+
+func FetchNodeRules(doc *html.Node, cssText string, windowWidth int) (m map[*html.Node][]*css.Rule, err error) {
+	m = make(map[*html.Node][]*css.Rule)
+	s, err := parser.Parse(cssText)
+	if err != nil {
+		return nil, fmt.Errorf("douceur parse: %w", err)
+	}
+	processRule := func(m map[*html.Node][]*css.Rule, r *css.Rule) (err error) {
+		log.Printf("r: %+v", r)
+		log.Printf("r.Rules: %+v", r.Rules)
+		log.Printf("r.Prelude: %+v", r.Prelude)
+		for _, sel := range r.Selectors {
+			log.Printf("sel=%+v", sel)
+			cs, err := cssSel.Compile(sel.Value)
+			if err != nil {
+				log.Printf("cssSel compile %v: %v", sel.Value, err)
+				continue
+			}
+			for _, el := range cs.Select(doc) {
+				existing, ok := m[el]
+				if !ok {
+					existing = make([]*css.Rule, 0, 3)
+				}
+				existing = append(existing, r)
+				m[el] = existing
+			}
+		}
+		return
+	}
+	for _, r := range s.Rules {
+		if err := processRule(m, r); err != nil {
+			return nil, fmt.Errorf("process rule: %w", err)
+		}
+
+		// for media queries
+		if rMaxWidth.MatchString(r.Prelude) {
+			maxWidth, err := strconv.Atoi(rMaxWidth.FindStringSubmatch(r.Prelude)[1])
+			if err != nil {
+				return nil, fmt.Errorf("atoi: %w", err)
+			}
+			if windowWidth > maxWidth {
+				continue
+			}
+		}
+		if rMinWidth.MatchString(r.Prelude) {
+			minWidth, err := strconv.Atoi(rMinWidth.FindStringSubmatch(r.Prelude)[1])
+			if err != nil {
+				return nil, fmt.Errorf("atoi: %w", err)
+			}
+			if windowWidth < minWidth {
+				continue
+			}
+		}
+		for _, rr := range r.Rules {
+			if err := processRule(m, rr); err != nil {
+				return nil, fmt.Errorf("process embedded rule: %w", err)
+			}
+		}
+	}
+	return
+}
+
+func Inline(html string, csss ...string) (string, error) {
+	revertCSS, err := ioutil.ReadFile("normalize.css")
+	if err != nil {
+		return "", fmt.Errorf("revert ffox css: %w", err)
+	}
+	csss = append([]string{string(revertCSS), AddOnCSS}, csss...)
+	style := "<style>" + strings.Join(csss, "\n\n") + "</style>"
+
+	var replaced string
+	if strings.Contains(html, "</head>") {
+		replaced = strings.Replace(html, "</head>", style+"\n</head>", 1)
+	} else if strings.Contains(html, "<body") {
+		replaced = strings.Replace(html, "<body", style+"\n<body", 1)
+	} else {
+		replaced = style + html
+	}
+
+	if replaced == html {
+		panic("woot")
+	}
+
+	inlined, err := inliner.Inline(replaced)
+	if err == nil {
+		html = inlined
+	} else {
+		err = fmt.Errorf("inling failed: %w", err)
+	}
+	return html, err
+}
+
+type Map struct {
+	Declarations map[string]css.Declaration
+}
+
+func NewMap(n *html.Node) Map {
+	s := Map{
+		Declarations: make(map[string]css.Declaration),
+	}
+
+	for _, a := range n.Attr {
+		if a.Key == "style" {
+			decls, err := parser.ParseDeclarations(a.Val)
+			if err != nil {
+				log.Printf("could not parse '%v'", a.Val)
+				break
+			}
+			for _, d := range decls {
+				s.Declarations[d.Property] = *d
+			}
+		} else if a.Key == "bgcolor" {
+			s.Declarations["background-color"] = css.Declaration{
+				Property: "background-color",
+				Value: a.Val,
+			}
+		}
+	}
+
+	return s
+}
+
+func (cs Map) ApplyChildStyle(ccs Map) (res Map) {
+	res.Declarations = make(map[string]css.Declaration)
+
+	for k, v := range cs.Declarations {
+		res.Declarations[k] = v
+	}
+	// overwrite with higher prio child props
+	for k, v := range ccs.Declarations {
+		switch k {
+		case "height", "width":
+			parentL, ok := res.Declarations[k]
+			if ok && strings.HasSuffix(v.Value, "%") && strings.HasSuffix(parentL.Value, "px") {
+				parentLNum, err := strconv.Atoi(strings.TrimSuffix(parentL.Value, "px"))
+				if err != nil {
+					log.Errorf("atoi: %v", err)
+					continue
+				}
+				percentNum, err := strconv.ParseFloat(strings.TrimSuffix(v.Value, "%"), 64)
+				if err != nil {
+					log.Errorf("atoi: %v", err)
+					continue
+				}
+				prod := int(percentNum * float64(parentLNum) / 100.0)
+				res.Declarations[k] = css.Declaration{
+					Property: k,
+					Value: fmt.Sprintf("%vpx", prod),
+				}
+				continue
+			}
+			fallthrough
+		default:
+			res.Declarations[k] = v
+		}
+	}
+
+	return
+}
+
+func (cs Map) Font() *draw.Font {
+	if !CssFonts {
+		return nil
+	}
+	fn := cs.FontFilename()
+	if dui == nil {
+		return nil
+	}
+	font, ok := fontCache[fn]
+	if ok {
+		return font
+	}
+	font, err := dui.Display.OpenFont(fn)
+	if err != nil {
+		log.Printf("%v is not avail", fn)
+		return nil
+	}
+	fontCache[fn] = font
+
+	return font
+}
+
+func (cs Map) preferedFontName(preferences []string) string {
+	avails := availableFontNames
+	if len(avails) == 0 {
+		return preferences[0]
+	}
+
+	for len(preferences) > 0 {
+		var pref string
+		pref, preferences = preferences[0], preferences[1:]
+
+		for _, avail := range avails {
+			if pref == avail {
+				return avail
+			}
+		}
+	}
+
+	return avails[0]
+}
+
+func (cs Map) FontSize() float64 {
+	fs, ok := cs.Declarations["font-size"]
+	if !ok || fs.Value == "" {
+		return 14
+	}
+
+	if len(fs.Value) <= 2 {
+		log.Printf("error parsing font size %v", fs.Value)
+		return 14.0
+	}
+	numStr := fs.Value[0 : len(fs.Value)-2]
+	f, err := strconv.ParseFloat(numStr, 64)
+	if err != nil {
+		log.Printf("error parsing font size %v", fs.Value)
+		return 14.0
+	}
+	if strings.HasSuffix(fs.Value, "em") {
+		f *= 14.0
+	}
+	return f
+}
+
+func (cs Map) Color() draw.Color {
+	h := cs.colorHex("color")
+	c := draw.Color(h)
+	return c
+}
+
+func (cs Map) colorHex(cssPropName string) uint32 {
+	propVal, ok := cs.Declarations[cssPropName]
+	if ok {
+		var r, g, b, a uint32
+		if strings.HasPrefix(propVal.Value, "rgb") {
+			val := propVal.Value[3:]
+			val = strings.TrimPrefix(val, "(")
+			val = strings.TrimSuffix(val, ")")
+			vals := strings.Split(val, ",")
+			rr, err := strconv.ParseInt(vals[0], 10, 32)
+			if err != nil {
+				goto default_value
+			}
+			gg, err := strconv.ParseInt(vals[1], 10, 32)
+			if err != nil {
+				goto default_value
+			}
+			bb, err := strconv.ParseInt(vals[2], 10, 32)
+			if err != nil {
+				goto default_value
+			}
+			r = uint32(rr) * 256
+			g = uint32(gg) * 256
+			b = uint32(bb) * 256
+		} else if strings.HasPrefix(propVal.Value, "#") {
+			hexColor := propVal.Value[1:]
+			a = 256 * 256
+			if len(hexColor) == 3 {
+				rr, err := strconv.ParseInt(hexColor[0:1], 16, 32)
+				if err != nil {
+					goto default_value
+				}
+				gg, err := strconv.ParseInt(hexColor[1:2], 16, 32)
+				if err != nil {
+					goto default_value
+				}
+				bb, err := strconv.ParseInt(hexColor[2:3], 16, 32)
+				if err != nil {
+					goto default_value
+				}
+				r = uint32(rr) * 256 * 16
+				g = uint32(gg) * 256 * 16
+				b = uint32(bb) * 256 * 16
+			} else if len(hexColor) == 6 {
+				rr, err := strconv.ParseInt(hexColor[0:2], 16, 32)
+				if err != nil {
+					goto default_value
+				}
+				gg, err := strconv.ParseInt(hexColor[2:4], 16, 32)
+				if err != nil {
+					goto default_value
+				}
+				bb, err := strconv.ParseInt(hexColor[4:6], 16, 32)
+				if err != nil {
+					goto default_value
+				}
+				r = uint32(rr) * 256
+				g = uint32(gg) * 256
+				b = uint32(bb) * 256
+			} else {
+				goto default_value
+			}
+		} else if propVal.Value == "inherit" {
+			// TODO: handle properly
+			goto default_value
+		} else {
+			colorRGBA, ok := colornames.Map[propVal.Value]
+			if !ok {
+				goto default_value
+			}
+			r, g, b, a = colorRGBA.RGBA()
+		}
+		m := uint32(16)
+		downSample := func(a uint32) uint32 {
+			return a
+			return a - (a % m)
+		}
+		x := (downSample(r / 256)) << 24
+		x = x | (downSample((g / 256)) << 16)
+		x = x | (downSample((b / 256)) << 8)
+		//x = x | (a / 256)
+		_ = a
+		x = x | 0x000000ff
+		if x == 0xffffffff {
+			// TODO: white on white background...
+			return uint32(draw.Black)
+		}
+		return uint32(x)
+	} else {
+		return uint32(draw.Black)
+	}
+default_value:
+	log.Printf("could not interpret %v", propVal)
+	return uint32(draw.Black)
+}
+
+func (cs Map) IsInline() bool {
+	propVal, ok := cs.Declarations["float"]
+	if ok && propVal.Value == "left" {
+		return false
+	}
+	propVal, ok = cs.Declarations["display"]
+	if ok {
+		return propVal.Value == "inline" ||
+			propVal.Value == "inline-block"
+	}
+	return false
+}
+
+func (cs Map) IsDisplayNone() bool {
+	propVal, ok := cs.Declarations["display"]
+	if ok && propVal.Value == "none" {
+		return true
+	}
+	propVal, ok = cs.Declarations["position"]
+	if ok && propVal.Value == "fixed" {
+		return true
+	}
+	propVal, ok = cs.Declarations["clip"]
+	if ok && strings.ReplaceAll(propVal.Value, " ", "") == "rect(1px,1px,1px,1px)" {
+		return true
+	}
+	propVal, ok = cs.Declarations["width"]
+	if ok && propVal.Value == "1px" {
+		propVal, ok = cs.Declarations["height"]
+		if ok && propVal.Value == "1px" {
+			return true
+		}
+	}
+	return false
+}
+
+func (cs Map) IsFlex() bool {
+	propVal, ok := cs.Declarations["display"]
+	if ok {
+		return propVal.Value == "flex"
+	}
+	return false
+}
+
+func (cs Map) IsFlexDirectionRow() bool {
+	propVal, ok := cs.Declarations["flex-direction"]
+	if ok {
+		switch propVal.Value {
+		case "row":
+			return true
+		case "column":
+			return false
+		}
+	}
+	return true // TODO: be more specific
+}
--- /dev/null
+++ b/style/stylesheets_test.go
@@ -1,0 +1,166 @@
+package style
+
+import (
+	"github.com/chris-ramon/douceur/css"
+	"golang.org/x/net/html"
+	"opossum/logger"
+	"strings"
+	"testing"
+)
+
+func init() {
+	quiet := true
+	logger.Quiet = &quiet
+	logger.Init()
+	log = &logger.Logger{Debug: true}
+}
+
+func d(c string) Map {
+	m := Map{
+		Declarations: make(map[string]css.Declaration),
+	}
+	m.Declarations["color"] = css.Declaration{
+		Property: "color",
+		Value:    c,
+	}
+	return m
+}
+
+func TestColorHex(t *testing.T) {
+	tr := d("red")
+	hr := d("#ff0000")
+
+	tri := tr.colorHex("color")
+	hri := hr.colorHex("color")
+	if tri != hri {
+		t.Fatalf("tri=%x hri=%x", tri, hri)
+	}
+}
+
+func TestFetchNodeRules(t *testing.T) {
+	data := `<body>
+      		<h2 id="foo">a header</h2>
+     		<h2 id="bar">another header</h2>
+   		<p>Some text <b>in bold</b></p>
+    	</body>`
+	doc, err := html.Parse(strings.NewReader(data))
+	if err != nil {
+		t.Fail()
+	}
+	css := AddOnCSS + `
+b {
+	width: 100px!important;
+}
+
+@media only screen and (max-width: 600px) {
+  body {
+    background-color: lightblue;
+  }
+}
+	`
+	for _, w := range []int{400, 800} {
+		t.Logf("w=%v", w)
+		m, err := FetchNodeRules(doc, css, w)
+		if err != nil {
+			t.Fail()
+		}
+		t.Logf("m=%+v", m)
+
+		var b *html.Node
+		var body *html.Node
+
+		var f func(n *html.Node)
+		f = func(n *html.Node) {
+			switch n.Data {
+			case "b":
+				b = n
+			case "body":
+				body = n
+			}
+			for c := n.FirstChild; c != nil; c = c.NextSibling {
+				f(c)
+			}
+		}
+
+		f(doc)
+
+		importantFound := false
+		for _, r := range m[b] {
+			if r.Declarations[0].Important {
+				importantFound = true
+			}
+		}
+		if !importantFound {
+			t.Fail()
+		}
+
+		if w == 400 {
+			_ =m[body][0] 
+			if m[body][0].Declarations[0].Value != "lightblue" {
+				t.Fail()
+			}
+			t.Logf("%v", m[body][0].Name)
+		} else {
+			if _, ok := m[body]; ok {
+				t.Fail()
+			}
+		}
+	}
+}
+
+func TestFetchNodeMap(t *testing.T) {
+	data := `<p>
+      		<h2 id="foo">a header</h2>
+     		<h2 id="bar">another header</h2>
+   		<p>Some text <b>in bold</b></p>
+    	</p>`
+	doc, err := html.Parse(strings.NewReader(data))
+	if err != nil {
+		t.Fail()
+	}
+	m, err := FetchNodeMap(doc, AddOnCSS, 1024)
+	if err != nil {
+		t.Fail()
+	}
+	t.Logf("m=%+v", m)
+}
+
+func TestApplyChildStyleInherit(t *testing.T) {
+	parent := Map{
+		Declarations: make(map[string]css.Declaration),
+	}
+	parent.Declarations["height"] = css.Declaration{
+		Property: "height",
+		Value:    "80px",
+	}
+	child := Map{
+		Declarations: make(map[string]css.Declaration),
+	}
+
+	res := parent.ApplyChildStyle(child)
+	if v := res.Declarations["height"].Value; v != "80px" {
+		t.Fatalf(v)
+	}
+}
+
+func TestApplyChildStyleMultiply(t *testing.T) {
+	parent := Map{
+		Declarations: make(map[string]css.Declaration),
+	}
+	parent.Declarations["height"] = css.Declaration{
+		Property: "height",
+		Value:    "80px",
+	}
+	child := Map{
+		Declarations: make(map[string]css.Declaration),
+	}
+	child.Declarations["height"] = css.Declaration{
+		Property: "height",
+		Value:    "50%",
+	}
+
+	res := parent.ApplyChildStyle(child)
+	if v := res.Declarations["height"].Value; v != "40px" {
+		t.Fatalf(v)
+	}
+}