github.com/vugu/vugu@v0.3.5/domrender/renderer-js-instructions.go (about)

     1  package domrender
     2  
     3  import (
     4  	"encoding/binary"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"strings"
     9  )
    10  
    11  // NOTE: I looked at using Protobuf for this, and in some ways it makes sense.  The main issue though is that it brings in
    12  // a bunch of code I don't necessarily need, particularly on the JS side.  There are only a few data types that need to be
    13  // encoded and it's not too big a deal.  Whereas with protobuf I immediately bring in 250k of protobuf JS code, the vast
    14  // majority of which is not needed.  So I'm proceeding with the idea that the encoding/decoding is simple enough to just do
    15  // by hand.  I hope I'm right.  -bgp
    16  
    17  // NOTE: I needed a single concise word which means, essentially "make it so".  The idea being that the element described
    18  // should exist, and if it does not, update/replace whatever is there so it is.  Unable to find a suitable term in the
    19  // English language, I've chosen the word "Picard" for this purpose.  UPDATE: Alas, this didn't pan out, but it was worth
    20  // a try ;)
    21  
    22  const (
    23  	opcodeEnd uint8 = 0 // no more instructions in this buffer
    24  	// opcodeClearRefmap      uint8 = 1 // clear the reference map, all following instructions must not reference prior IDs
    25  	// opcodeClearElStack uint8 = 1 // clear the stack of elements
    26  	opcodeClearEl uint8 = 1 // unset current element
    27  	// opcodeSetHTMLRef       uint8 = 2 // assign ref for html tag
    28  	// opcodeSetHeadRef       uint8 = 3 // assign ref for head tag
    29  	// opcodeSetBodyRef       uint8 = 4 // assign ref for body tag
    30  	// opcodeSelectRef        uint8 = 5 // select element by ref
    31  	opcodeRemoveOtherAttrs uint8 = 5 // remove any elements for the current element that we didn't just set
    32  	opcodeSetAttrStr       uint8 = 6 // assign attribute string to the current selected element
    33  	opcodeSelectMountPoint uint8 = 7 // selects the mount point element and pushes to the stack - the first time by selector but every subsequent time it will reuse the element from before (because the selector may not match after it's been synced over, it's id etc), also make sure it's of this element name and recreate if so
    34  	// opcodePicardFirstChildElement uint8 = 8  // ensure an element first child and select element
    35  	// opcodePicardFirstChildText    uint8 = 9  // ensure a text first child and select element
    36  	// opcodePicardFirstChildComment uint8 = 10 // ensure a comment first child and select element
    37  	// opcodeSelectParent     uint8 = 11 // select parent element
    38  	// opcodePicardFirstChild uint8 = 12 // ensure an element first child and select element
    39  
    40  	opcodeMoveToFirstChild uint8 = 20 // move node selection to first child (doesn't have to exist)
    41  	opcodeSetElement       uint8 = 21 // assign current selected node as an element of the specified type
    42  	// opcodeSetElementAttr      uint8 = 22 // set attribute on current element
    43  	opcodeSetText                   uint8 = 23 // assign current selected node as text with specified content
    44  	opcodeSetComment                uint8 = 24 // assign current selected node as comment with specified content
    45  	opcodeMoveToParent              uint8 = 25 // move node selection to parent
    46  	opcodeMoveToNextSibling         uint8 = 26 // move node selection to next sibling (doesn't have to exist)
    47  	opcodeRemoveOtherEventListeners uint8 = 27 // remove all event listeners from currently selected element that were not just set
    48  	opcodeSetEventListener          uint8 = 28 // assign event listener to currently selected element
    49  	opcodeSetInnerHTML              uint8 = 29 // set the innerHTML for an element
    50  
    51  	opcodeSetCSSTag          uint8 = 30 // write a CSS (style or link) tag
    52  	opcodeRemoveOtherCSSTags uint8 = 31 // remove any CSS tags that have not been written since the last call
    53  	opcodeSetJSTag           uint8 = 32 // write a JS (script) tag
    54  	opcodeRemoveOtherJSTags  uint8 = 33 // remove any JS tags that have not been written since the last call
    55  
    56  	opcodeSetProperty     uint8 = 35 // assign a JS property to the current element
    57  	opcodeSelectQuery     uint8 = 36 // select an element
    58  	opcodeBufferInnerHTML uint8 = 37 // pass chunked text to set as inner html, complete with opcodeSetInnerHTML
    59  
    60  	opcodeSetAttrNSStr uint8 = 38 // assign attribute string to the current selected namespaced element
    61  	opcodeSetElementNS uint8 = 39 // assign current selected node as an element of the specified type in the specified namespace
    62  
    63  	opcodeCallback            uint8 = 40 // issue callback, sends just callbackID
    64  	opcodeCallbackLastElement uint8 = 41 // issue callback with callbackID and most recent element reference
    65  
    66  )
    67  
    68  // newInstructionList will create a new instance backed by the specified slice and with a clearBufFunc
    69  // that is called when the buffer is about to overflow.
    70  func newInstructionList(buf []byte, flushBufFunc func(il *instructionList) error) *instructionList {
    71  	if buf == nil {
    72  		panic("buf is nil")
    73  	}
    74  	if flushBufFunc == nil {
    75  		panic("flushBufFunc is nil")
    76  	}
    77  	return &instructionList{
    78  		buf:          buf,
    79  		flushBufFunc: flushBufFunc,
    80  	}
    81  }
    82  
    83  type instructionList struct {
    84  	buf          []byte
    85  	pos          int
    86  	flushBufFunc func(il *instructionList) error
    87  	logWriter    io.Writer // set to non-nil to enable debug log output
    88  }
    89  
    90  var errDoesNotFit = errors.New("requested instruction does not fit in the buffer")
    91  
    92  func (il *instructionList) logf(f string, args ...interface{}) error {
    93  	if il.logWriter == nil {
    94  		return nil
    95  	}
    96  	if !strings.HasSuffix(f, "\n") {
    97  		f += "\n"
    98  	}
    99  	_, err := fmt.Fprintf(il.logWriter, "domrender ildebug: "+f, args...)
   100  	return err
   101  }
   102  
   103  func (il *instructionList) flush() error {
   104  	il.logf("flush() calling flushBufFunc")
   105  	err := il.flushBufFunc(il)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	il.pos = 0
   110  	il.logf("flush() completed")
   111  	return nil
   112  }
   113  
   114  // checkLenAndFlush calls checkLen(), if it fails attempts to flush the buffer and checkLen again, at which point any error is returned.
   115  func (il *instructionList) checkLenAndFlush(l int) error {
   116  
   117  	err := il.checkLen(l)
   118  	if err != nil {
   119  
   120  		if err == errDoesNotFit {
   121  			err = il.flush()
   122  			if err != nil {
   123  				return err
   124  			}
   125  			err = il.checkLen(l)
   126  		}
   127  	}
   128  
   129  	return err
   130  }
   131  
   132  func (il *instructionList) checkLen(l int) error {
   133  	if il.pos+l > len(il.buf)-1 {
   134  		return errDoesNotFit
   135  	}
   136  	return nil
   137  }
   138  
   139  func (il *instructionList) writeEnd() {
   140  	il.logf("writeEnd[%d]()", opcodeEnd)
   141  	il.buf[il.pos] = opcodeEnd
   142  	il.pos++
   143  }
   144  
   145  func (il *instructionList) writeClearEl() error {
   146  	il.logf("writeClearEl[%d]()", opcodeClearEl)
   147  
   148  	err := il.checkLenAndFlush(1)
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	il.writeValUint8(opcodeClearEl)
   154  
   155  	return nil
   156  }
   157  
   158  func (il *instructionList) writeRemoveOtherAttrs() error {
   159  
   160  	il.logf("writeRemoveOtherAttrs[%d]()", opcodeRemoveOtherAttrs)
   161  
   162  	err := il.checkLenAndFlush(1)
   163  	if err != nil {
   164  		return err
   165  	}
   166  
   167  	il.writeValUint8(opcodeRemoveOtherAttrs)
   168  
   169  	return nil
   170  }
   171  
   172  func (il *instructionList) writeSetAttrStr(name, value string) error {
   173  
   174  	il.logf("writeSetAttrStr[%d](name=%q, value=%q)", opcodeSetAttrStr, name, value)
   175  
   176  	size := len(name) + len(value) + 9
   177  
   178  	err := il.checkLenAndFlush(size)
   179  	if err != nil {
   180  		return err
   181  	}
   182  
   183  	il.writeValUint8(opcodeSetAttrStr)
   184  	il.writeValString(name)
   185  	il.writeValString(value)
   186  
   187  	return nil
   188  }
   189  
   190  func (il *instructionList) writeSetAttrNSStr(namespace, name, value string) error {
   191  
   192  	il.logf("writeSetAttrNSStr[%d](ns=%q, name=%q, value=%q)", opcodeSetAttrNSStr, namespace, name, value)
   193  
   194  	size := len(namespace) + len(name) + len(value) + 13
   195  
   196  	err := il.checkLenAndFlush(size)
   197  	if err != nil {
   198  		return err
   199  	}
   200  
   201  	il.writeValUint8(opcodeSetAttrNSStr)
   202  	il.writeValString(namespace)
   203  	il.writeValString(name)
   204  	il.writeValString(value)
   205  
   206  	return nil
   207  }
   208  
   209  func (il *instructionList) writeSelectQuery(selector string) error {
   210  
   211  	il.logf("writeSelectQuery[%d](selector=%q)", opcodeSelectQuery, selector)
   212  
   213  	err := il.checkLenAndFlush(5 + len(selector))
   214  	if err != nil {
   215  		return err
   216  	}
   217  	il.writeValUint8(opcodeSelectQuery)
   218  	il.writeValString(selector)
   219  	return nil
   220  }
   221  
   222  func (il *instructionList) writeSelectMountPoint(selector, nodeName string) error {
   223  
   224  	il.logf("writeSelectMountPoint[%d](selector=%q, nodeName=%q)", opcodeSelectMountPoint, selector, nodeName)
   225  
   226  	err := il.checkLenAndFlush(len(selector) + len(nodeName) + 9)
   227  	if err != nil {
   228  		return err
   229  	}
   230  
   231  	il.writeValUint8(opcodeSelectMountPoint)
   232  	il.writeValString(selector)
   233  	il.writeValString(nodeName)
   234  
   235  	return nil
   236  
   237  }
   238  
   239  func (il *instructionList) writeMoveToFirstChild() error {
   240  
   241  	il.logf("writeMoveToFirstChild[%d]()", opcodeMoveToFirstChild)
   242  
   243  	err := il.checkLenAndFlush(1)
   244  	if err != nil {
   245  		return err
   246  	}
   247  
   248  	il.writeValUint8(opcodeMoveToFirstChild)
   249  
   250  	return nil
   251  }
   252  
   253  func (il *instructionList) writeSetElement(nodeName string) error {
   254  
   255  	il.logf("writeSetElement[%d](nodeName=%q)", opcodeSetElement, nodeName)
   256  
   257  	err := il.checkLenAndFlush(len(nodeName) + 5)
   258  	if err != nil {
   259  		return err
   260  	}
   261  
   262  	il.writeValUint8(opcodeSetElement)
   263  	il.writeValString(nodeName)
   264  
   265  	return nil
   266  
   267  }
   268  
   269  func (il *instructionList) writeSetElementNS(nodeName, namespace string) error {
   270  
   271  	il.logf("writeSetElementNS[%d](nodeName=%q, ns=%q)", opcodeSetElementNS, nodeName, namespace)
   272  
   273  	size := len(nodeName) + len(namespace) + 9
   274  	err := il.checkLenAndFlush(size)
   275  
   276  	if err != nil {
   277  		return err
   278  	}
   279  
   280  	il.writeValUint8(opcodeSetElementNS)
   281  	il.writeValString(nodeName)
   282  	il.writeValString(namespace)
   283  
   284  	return nil
   285  
   286  }
   287  
   288  func (il *instructionList) writeSetText(text string) error {
   289  
   290  	il.logf("writeSetText[%d](text=%q)", opcodeSetText, text)
   291  
   292  	err := il.checkLenAndFlush(len(text) + 5)
   293  	if err != nil {
   294  		return err
   295  	}
   296  
   297  	il.writeValUint8(opcodeSetText)
   298  	il.writeValString(text)
   299  
   300  	return nil
   301  
   302  }
   303  
   304  func (il *instructionList) writeSetComment(comment string) error {
   305  
   306  	il.logf("writeSetComment[%d](comment=%q)", opcodeSetComment, comment)
   307  
   308  	err := il.checkLenAndFlush(len(comment) + 5)
   309  	if err != nil {
   310  		return err
   311  	}
   312  
   313  	il.writeValUint8(opcodeSetComment)
   314  	il.writeValString(comment)
   315  
   316  	return nil
   317  
   318  }
   319  
   320  func (il *instructionList) writeMoveToParent() error {
   321  
   322  	il.logf("writeMoveToParent[%d]()", opcodeMoveToParent)
   323  
   324  	err := il.checkLenAndFlush(1)
   325  	if err != nil {
   326  		return err
   327  	}
   328  
   329  	il.writeValUint8(opcodeMoveToParent)
   330  
   331  	return nil
   332  }
   333  
   334  func (il *instructionList) writeMoveToNextSibling() error {
   335  
   336  	il.logf("writeMoveToNextSibling[%d]()", opcodeMoveToNextSibling)
   337  
   338  	err := il.checkLenAndFlush(1)
   339  	if err != nil {
   340  		return err
   341  	}
   342  
   343  	il.writeValUint8(opcodeMoveToNextSibling)
   344  
   345  	return nil
   346  }
   347  
   348  func (il *instructionList) writeSetInnerHTML(html string) error {
   349  
   350  	il.logf("writeSetInnerHTML[%d](html=%q)", opcodeSetInnerHTML, html)
   351  
   352  	// Make sure there is room to write at least one byte
   353  	// (1 byte for opcode, 4 bytes for string length, 1 byte of data)
   354  	// [This further ensures that maxLen - il.pos > 0]
   355  	err := il.checkLenAndFlush(6)
   356  	if err != nil {
   357  		return err
   358  	}
   359  
   360  	remaining := html
   361  	maxLen := len(il.buf) - 6
   362  	for len(remaining) > maxLen-il.pos {
   363  		chunk := remaining[:maxLen-il.pos]
   364  		remaining = remaining[maxLen-il.pos:]
   365  		err := il.checkLenAndFlush(len(chunk) + 5)
   366  		if err != nil {
   367  			return err
   368  		}
   369  
   370  		il.writeValUint8(opcodeBufferInnerHTML)
   371  		il.writeValString(chunk)
   372  		il.flush()
   373  	}
   374  
   375  	err = il.checkLenAndFlush(len(remaining) + 5)
   376  	if err != nil {
   377  		return err
   378  	}
   379  
   380  	il.writeValUint8(opcodeSetInnerHTML)
   381  	il.writeValString(remaining)
   382  
   383  	return nil
   384  }
   385  
   386  func (il *instructionList) writeSetEventListener(positionID []byte, eventType string, capture, passive bool) error {
   387  
   388  	il.logf("writeSetInnerHTML[%d](positionID=%q, eventType=%q, capture=%v, passive=%v)", opcodeSetEventListener, positionID, eventType, capture, passive)
   389  
   390  	err := il.checkLenAndFlush(len(positionID) + len(eventType) + 11)
   391  	if err != nil {
   392  		return err
   393  	}
   394  
   395  	il.writeValUint8(opcodeSetEventListener)
   396  	il.writeValBytes(positionID)
   397  	il.writeValString(eventType)
   398  
   399  	captureB := uint8(0)
   400  	if capture {
   401  		captureB = 1
   402  	}
   403  	il.writeValUint8(captureB)
   404  
   405  	passiveB := uint8(0)
   406  	if passive {
   407  		passiveB = 1
   408  	}
   409  	il.writeValUint8(passiveB)
   410  
   411  	return nil
   412  
   413  }
   414  
   415  func (il *instructionList) writeRemoveOtherEventListeners(positionID []byte) error {
   416  
   417  	il.logf("writeRemoveOtherEventListeners[%d](positionID=%q)", opcodeRemoveOtherEventListeners, positionID)
   418  
   419  	err := il.checkLenAndFlush(5 + len(positionID))
   420  	if err != nil {
   421  		return err
   422  	}
   423  
   424  	il.writeValUint8(opcodeRemoveOtherEventListeners)
   425  	il.writeValBytes(positionID)
   426  
   427  	return nil
   428  
   429  }
   430  
   431  func (il *instructionList) writeSetCSSTag(elementName string, textContent []byte, attrPairs []string) error {
   432  
   433  	il.logf("writeSetCSSTag[%d](elementName=%q, textContext=%q, attrPairs=%#v)", opcodeSetCSSTag, elementName, textContent, attrPairs)
   434  
   435  	if len(attrPairs) > 254 {
   436  		return fmt.Errorf("attrPairs is %d, too large, max is 254", len(attrPairs))
   437  	}
   438  
   439  	var al = 0
   440  	for _, s := range attrPairs {
   441  		al += len(s) + 4
   442  	}
   443  
   444  	var l = 1 + // opcode
   445  		al + // attrs
   446  		// 8 + // hashCode
   447  		1 + // 1 byte for number of strings to read
   448  		len(elementName) + 4 +
   449  		len(textContent) + 4
   450  
   451  	err := il.checkLenAndFlush(l)
   452  	if err != nil {
   453  		return err
   454  	}
   455  
   456  	il.writeValUint8(opcodeSetCSSTag)
   457  	// il.writeValUint64(hashCode)
   458  	il.writeValString(elementName)
   459  	il.writeValBytes(textContent)
   460  	il.writeValUint8(uint8(len(attrPairs)))
   461  	for _, s := range attrPairs {
   462  		il.writeValString(s)
   463  	}
   464  
   465  	return nil
   466  
   467  }
   468  
   469  func (il *instructionList) writeRemoveOtherCSSTags() error {
   470  
   471  	il.logf("writeRemoveOtherCSSTags[%d]()", opcodeRemoveOtherCSSTags)
   472  
   473  	err := il.checkLenAndFlush(1)
   474  	if err != nil {
   475  		return err
   476  	}
   477  
   478  	il.writeValUint8(opcodeRemoveOtherCSSTags)
   479  
   480  	return nil
   481  }
   482  
   483  func (il *instructionList) writeSetProperty(key string, jsonValue []byte) error {
   484  
   485  	il.logf("writeSetProperty[%d](key=%q, jsonValue=%q)", opcodeSetProperty, key, jsonValue)
   486  
   487  	size := len(key) + len(jsonValue) + 9
   488  
   489  	err := il.checkLenAndFlush(size)
   490  	if err != nil {
   491  		return err
   492  	}
   493  
   494  	il.writeValUint8(opcodeSetProperty)
   495  	il.writeValString(key)
   496  	il.writeValBytes(jsonValue)
   497  
   498  	return nil
   499  }
   500  
   501  func (il *instructionList) writeCallback(callbackID uint32) error {
   502  
   503  	il.logf("writeCallback[%d](callbackID=%v)", opcodeCallback, callbackID)
   504  
   505  	size := 5
   506  
   507  	err := il.checkLenAndFlush(size)
   508  	if err != nil {
   509  		return err
   510  	}
   511  
   512  	il.writeValUint8(opcodeCallback)
   513  	il.writeValUint32(callbackID)
   514  
   515  	return nil
   516  }
   517  
   518  func (il *instructionList) writeCallbackLastElement(callbackID uint32) error {
   519  
   520  	il.logf("writeCallbackLastElement[%d](callbackID=%v)", opcodeCallbackLastElement, callbackID)
   521  
   522  	size := 5
   523  
   524  	err := il.checkLenAndFlush(size)
   525  	if err != nil {
   526  		return err
   527  	}
   528  
   529  	il.writeValUint8(opcodeCallbackLastElement)
   530  	il.writeValUint32(callbackID)
   531  
   532  	return nil
   533  }
   534  
   535  func (il *instructionList) writeValUint8(b uint8) {
   536  	il.buf[il.pos] = b
   537  	il.pos++
   538  }
   539  
   540  func (il *instructionList) writeValUint32(v uint32) {
   541  	binary.BigEndian.PutUint32(il.buf[il.pos:il.pos+4], v)
   542  	il.pos += 4
   543  }
   544  
   545  func (il *instructionList) writeValUint64(ref uint64) {
   546  	binary.BigEndian.PutUint64(il.buf[il.pos:il.pos+8], ref)
   547  	il.pos += 8
   548  }
   549  
   550  func (il *instructionList) writeValString(s string) {
   551  
   552  	lenstr := len(s)
   553  	pos := il.pos
   554  
   555  	// write length as uint32
   556  	binary.BigEndian.PutUint32(il.buf[pos:pos+4], uint32(lenstr))
   557  
   558  	// copy bytes directly from string into buf
   559  	copy(il.buf[pos+4:pos+4+lenstr], s)
   560  
   561  	il.pos = pos + 4 + lenstr
   562  }
   563  
   564  func (il *instructionList) writeValBytes(s []byte) {
   565  
   566  	lenstr := len(s)
   567  	pos := il.pos
   568  
   569  	// write length as uint32
   570  	binary.BigEndian.PutUint32(il.buf[pos:pos+4], uint32(lenstr))
   571  
   572  	// copy bytes directly from string into buf
   573  	copy(il.buf[pos+4:pos+4+lenstr], s)
   574  
   575  	il.pos = pos + 4 + lenstr
   576  }
   577  
   578  // // "element and text" pattern (used for script, style, link) goes like:
   579  // // string - element name
   580  // // string - text content (zero length means no text content)
   581  // // uint32 - number of attributes
   582  // // string... - string pairs of key and then value for attributes (number of pairs is number of attributes above, so 1 attr would be 1 in the uint32 above and 2 string - 2 would mean 4 strings, etc.)
   583  // func (il *instructionList) writeValElementAndText(elName, textContent string, attrKV []string) error {
   584  
   585  // 	return nil
   586  // }