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 // }