ref: 39318cd9ec341341102854546dcd54326d9ba4fe
author: Philip Silva <philip.silva@protonmail.com>
date: Sat Dec 5 09:12:53 EST 2020
First public commit
--- /dev/null
+++ b/.gitignore
@@ -1,0 +1,14 @@
+/possum
+/opossum
+/possum-src
+/possum-plan9-amd64
+*.bin
+*.out
+/-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 '&';
+ case '<': return '<';
+ case '>': return '>';
+ case '\u00A0': return ' ';
+ }
+ });
+}
+
+function escapeAttr(s) {
+ var toEscape = /[&"\u00A0]/g;
+ if (!toEscape.test(s)) {
+ // nothing to do, fast path
+ return s;
+ } else {
+ return s.replace(toEscape, function(c) {
+ switch(c) {
+ case '&': return '&';
+ case '"': return '"';
+ case '\u00A0': return ' ';
+ }
+ });
+ }
+}
+
+function attrname(a) {
+ var ns = a.namespaceURI;
+ if (!ns)
+ return a.localName;
+ if (ns === NAMESPACE.XML)
+ return 'xml:' + a.localName;
+ if (ns === NAMESPACE.XLINK)
+ return 'xlink:' + a.localName;
+
+ if (ns === NAMESPACE.XMLNS) {
+ if (a.localName === 'xmlns') return 'xmlns';
+ else return 'xmlns:' + a.localName;
+ }
+ return a.name;
+}
+
+function serializeOne(kid, parent) {
+ var s = '';
+ switch(kid.nodeType) {
+ case 1: //ELEMENT_NODE
+ var ns = kid.namespaceURI;
+ var html = ns === NAMESPACE.HTML;
+ var tagname = (html || ns === NAMESPACE.SVG || ns === NAMESPACE.MATHML) ? kid.localName : kid.tagName;
+
+ s += '<' + tagname;
+
+ for(var j = 0, k = kid._numattrs; j < k; j++) {
+ var a = kid._attr(j);
+ s += ' ' + attrname(a);
+ if (a.value !== undefined) s += '="' + escapeAttr(a.value) + '"';
+ }
+ s += '>';
+
+ if (!(html && emptyElements[tagname])) {
+ var ss = kid.serialize();
+ if (html && extraNewLine[tagname] && ss.charAt(0)==='\n') s += '\n';
+ // Serialize children and add end tag for all others
+ s += ss;
+ s += '</' + tagname + '>';
+ }
+ break;
+ case 3: //TEXT_NODE
+ case 4: //CDATA_SECTION_NODE
+ var parenttag;
+ if (parent.nodeType === 1 /*ELEMENT_NODE*/ &&
+ parent.namespaceURI === NAMESPACE.HTML)
+ parenttag = parent.tagName;
+ else
+ parenttag = '';
+
+ if (hasRawContent[parenttag] ||
+ (parenttag==='NOSCRIPT' && parent.ownerDocument._scripting_enabled)) {
+ s += kid.data;
+ } else {
+ s += escape(kid.data);
+ }
+ break;
+ case 8: //COMMENT_NODE
+ s += '<!--' + kid.data + '-->';
+ break;
+ case 7: //PROCESSING_INSTRUCTION_NODE
+ s += '<?' + kid.target + ' ' + kid.data + '?>';
+ break;
+ case 10: //DOCUMENT_TYPE_NODE
+ s += '<!DOCTYPE ' + kid.name;
+
+ if (false) {
+ // Latest HTML serialization spec omits the public/system ID
+ if (kid.publicID) {
+ s += ' PUBLIC "' + kid.publicId + '"';
+ }
+
+ if (kid.systemId) {
+ s += ' "' + kid.systemId + '"';
+ }
+ }
+
+ s += '>';
+ break;
+ default:
+ utils.InvalidStateError();
+ }
+ return s;
+}
--- /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
+ 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 := "ebenfalls"
+ 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)
+ }
+}