Skip to content

Instantly share code, notes, and snippets.

@MaxMorais
Created October 14, 2022 18:42
Show Gist options
  • Save MaxMorais/4711355b95073e908365652295bdddae to your computer and use it in GitHub Desktop.
Save MaxMorais/4711355b95073e908365652295bdddae to your computer and use it in GitHub Desktop.
Remote DOM in Python
import json
import threading
from functools import wraps
__all__ = ['Commands', 'Node', 'MessageQueue', 'Pipe', 'Constants',
'StyleAttributes', 'SupportedEvents', 'EventDOMNodeAttributes']
def delay(delay=0.):
"""
Decorator delaying the execution of a function of a while
"""
def wrap(f):
@wraps(f)
def delayed(*args, **kwargs):
timer = threading.Timer(delay, f, args=args, kwargs=kwargs)
timer.start()
return timer
return delayed
return wrap
class setterproperty(object):
def __init__(self, func, doc=None):
self.func = func
self.__doc__ = doc if doc is not None else func.__doc__
def __set__(self, obj, value):
return self.func(obj, value)
Node = type('Node', (object,), {
'ELEMENT_NODE': 1,
'ATTRIBUTE_NODE': 2,
'TEXT_NODE': 3,
'CDATA_SECTION_NODE': 4,
'ENTITY_REFERENCE_NODE': 5,
'ENTITY_NODE': 6,
'PROCESSING_INSTRUCTION_NODE': 7,
'COMMENT_NODE': 8,
'DOCUMENT_NODE': 9,
'DOCUMENT_TYPE_NODE': 10,
'DOCUMENT_FRAGMENT_NODE': 11,
'NOTATION_NODE': 12,
'DOCUMENT_POSITION_DISCONNECTED': 1,
'DOCUMENT_POSITION_PRECEDING': 2,
'DOCUMENT_POSITION_FOLLOWING': 4,
'DOCUMENT_POSITION_CONTAINS': 8,
'DOCUMENT_POSITION_CONTAINED_BY': 16,
'DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC': 32
})
Commands = type('Commands', (object,), {
x: x for x in (
'createContainer',
'createElement',
'createTextNode',
'createComment',
'createDocumentFragment',
'appendChild',
'insertBefore',
'removeChild',
'replaceChild',
'setAttribute',
'removeAttribute',
'setStyles',
'setStyle',
'innerHTML',
'innerText',
'textContent',
'setValue',
'addEventListener',
'removeEventListener',
'dispatchEvent',
'invokeNative',
'updateProperties',
'initiated',
'pause',
'play',
'src',
'focus',
'setSelectionRange'
)
})
Constants = type('Constants', (object,), {
x: x for x in (
'REMOTE_DOM',
'DOCUMENT',
'WINDOW',
'DEFAULT_NAME',
'QUEUE_INDEX',
'NODE_INDEX',
'INIT',
'EVENT'
)
})
index = 0
class Pipe(object):
def __init__(self, channel, handler):
self.channel = channel
def message_handler(ev):
message = None
try:
message = json.loads(ev.data)
except TypeError:
return
if Constants.REMOTE_DOM in message:
handler(message[Constants.REMOTE_DOM])
self.channel.addEventListener('message', message_handler)
def postMessage(self, messages):
self.channel.postMessage(json.dumps({Constants.REMOTE_DOM: messages}))
class MessagesQueue(object):
def __init__(self):
self.queue = []
self.index = id(self)
self.pipe = None
self.timer = None
def push(self, message):
self.queue.append(message)
self.schedule()
def setPipe(self, channel, handler, timerFunction = None):
self.pipe = Pipe(channel, handler)
if timerFunction:
self.timerFunction = timerFunction
else:
def callback(cb):
delay()(cb)
self.timerFunction = callback
def schedule(self):
if not self.timer and not self.pipe:
return
self.timer = self.timerFunction(self.flushQueue)
def flushQueue(self):
self.timer = None
if not self.pipe or not self.queue:
return
self.pipe.postMessage(self.queue)
self.queue = []
EventDOMNodeAttribute = [
'currentTarget',
'originalTarget',
'srcElement',
'target',
'toElement',
'path',
'view'
]
StyleAttributes = [
'alignContent',
'alignItems',
'alignSelf',
'alignmentBaseline',
'all',
'animation',
'animationDelay',
'animationDirection',
'animationDuration',
'animationFillMode',
'animationIterationCount',
'animationName',
'animationPlayState',
'animationTimingFunction',
'backfaceVisibility',
'background',
'backgroundAttachment',
'backgroundBlendMode',
'backgroundClip',
'backgroundColor',
'backgroundImage',
'backgroundOrigin',
'backgroundPosition',
'backgroundPositionX',
'backgroundPositionY',
'backgroundRepeat',
'backgroundRepeatX',
'backgroundRepeatY',
'backgroundSize',
'baselineShift',
'border',
'borderBottom',
'borderBottomColor',
'borderBottomLeftRadius',
'borderBottomRightRadius',
'borderBottomStyle',
'borderBottomWidth',
'borderCollapse',
'borderColor',
'borderImage',
'borderImageOutset',
'borderImageRepeat',
'borderImageSlice',
'borderImageSource',
'borderImageWidth',
'borderLeft',
'borderLeftColor',
'borderLeftStyle',
'borderLeftWidth',
'borderRadius',
'borderRight',
'borderRightColor',
'borderRightStyle',
'borderRightWidth',
'borderSpacing',
'borderStyle',
'borderTop',
'borderTopColor',
'borderTopLeftRadius',
'borderTopRightRadius',
'borderTopStyle',
'borderTopWidth',
'borderWidth',
'bottom',
'boxShadow',
'boxSizing',
'breakAfter',
'breakBefore',
'breakInside',
'bufferedRendering',
'captionSide',
'clear',
'clip',
'clipPath',
'clipRule',
'color',
'colorInterpolation',
'colorInterpolationFilters',
'colorRendering',
'columnCount',
'columnFill',
'columnGap',
'columnRule',
'columnRuleColor',
'columnRuleStyle',
'columnRuleWidth',
'columnSpan',
'columnWidth',
'columns',
'contain',
'content',
'counterIncrement',
'counterReset',
'cursor',
'cx',
'cy',
'd',
'direction',
'display',
'dominantBaseline',
'emptyCells',
'fill',
'fillOpacity',
'fillRule',
'filter',
'flex',
'flexBasis',
'flexDirection',
'flexFlow',
'flexGrow',
'flexShrink',
'flexWrap',
'float',
'floodColor',
'floodOpacity',
'font',
'fontFamily',
'fontFeatureSettings',
'fontKerning',
'fontSize',
'fontStretch',
'fontStyle',
'fontVariant',
'fontVariantCaps',
'fontVariantLigatures',
'fontVariantNumeric',
'fontWeight',
'height',
'imageRendering',
'isolation',
'justifyContent',
'left',
'letterSpacing',
'lightingColor',
'lineHeight',
'listStyle',
'listStyleImage',
'listStylePosition',
'listStyleType',
'margin',
'marginBottom',
'marginLeft',
'marginRight',
'marginTop',
'marker',
'markerEnd',
'markerMid',
'markerStart',
'mask',
'maskType',
'maxHeight',
'maxWidth',
'maxZoom',
'minHeight',
'minWidth',
'minZoom',
'mixBlendMode',
'motion',
'motionOffset',
'motionPath',
'motionRotation',
'objectFit',
'objectPosition',
'opacity',
'order',
'orientation',
'orphans',
'outline',
'outlineColor',
'outlineOffset',
'outlineStyle',
'outlineWidth',
'overflow',
'overflowWrap',
'overflowX',
'overflowY',
'padding',
'paddingBottom',
'paddingLeft',
'paddingRight',
'paddingTop',
'page',
'pageBreakAfter',
'pageBreakBefore',
'pageBreakInside',
'paintOrder',
'perspective',
'perspectiveOrigin',
'pointerEvents',
'position',
'quotes',
'r',
'resize',
'right',
'rx',
'ry',
'shapeImageThreshold',
'shapeMargin',
'shapeOutside',
'shapeRendering',
'size',
'speak',
'src',
'stopColor',
'stopOpacity',
'stroke',
'strokeDasharray',
'strokeDashoffset',
'strokeLinecap',
'strokeLinejoin',
'strokeMiterlimit',
'strokeOpacity',
'strokeWidth',
'tabSize',
'tableLayout',
'textAlign',
'textAlignLast',
'textAnchor',
'textCombineUpright',
'textDecoration',
'textIndent',
'textOrientation',
'textOverflow',
'textRendering',
'textShadow',
'textSizeAdjust',
'textTransform',
'top',
'touchAction',
'transform',
'transformOrigin',
'transformStyle',
'transition',
'transitionDelay',
'transitionDuration',
'transitionProperty',
'transitionTimingFunction',
'unicodeBidi',
'unicodeRange',
'userSelect',
'userZoom',
'vectorEffect',
'verticalAlign',
'visibility',
'webkitAppRegion',
'webkitAppearance',
'webkitBackgroundClip',
'webkitBackgroundOrigin',
'webkitBorderAfter',
'webkitBorderAfterColor',
'webkitBorderAfterStyle',
'webkitBorderAfterWidth',
'webkitBorderBefore',
'webkitBorderBeforeColor',
'webkitBorderBeforeStyle',
'webkitBorderBeforeWidth',
'webkitBorderEnd',
'webkitBorderEndColor',
'webkitBorderEndStyle',
'webkitBorderEndWidth',
'webkitBorderHorizontalSpacing',
'webkitBorderImage',
'webkitBorderStart',
'webkitBorderStartColor',
'webkitBorderStartStyle',
'webkitBorderStartWidth',
'webkitBorderVerticalSpacing',
'webkitBoxAlign',
'webkitBoxDecorationBreak',
'webkitBoxDirection',
'webkitBoxFlex',
'webkitBoxFlexGroup',
'webkitBoxLines',
'webkitBoxOrdinalGroup',
'webkitBoxOrient',
'webkitBoxPack',
'webkitBoxReflect',
'webkitClipPath',
'webkitColumnBreakAfter',
'webkitColumnBreakBefore',
'webkitColumnBreakInside',
'webkitFontSizeDelta',
'webkitFontSmoothing',
'webkitHighlight',
'webkitHyphenateCharacter',
'webkitLineBreak',
'webkitLineClamp',
'webkitLocale',
'webkitLogicalHeight',
'webkitLogicalWidth',
'webkitMarginAfter',
'webkitMarginAfterCollapse',
'webkitMarginBefore',
'webkitMarginBeforeCollapse',
'webkitMarginBottomCollapse',
'webkitMarginCollapse',
'webkitMarginEnd',
'webkitMarginStart',
'webkitMarginTopCollapse',
'webkitMask',
'webkitMaskBoxImage',
'webkitMaskBoxImageOutset',
'webkitMaskBoxImageRepeat',
'webkitMaskBoxImageSlice',
'webkitMaskBoxImageSource',
'webkitMaskBoxImageWidth',
'webkitMaskClip',
'webkitMaskComposite',
'webkitMaskImage',
'webkitMaskOrigin',
'webkitMaskPosition',
'webkitMaskPositionX',
'webkitMaskPositionY',
'webkitMaskRepeat',
'webkitMaskRepeatX',
'webkitMaskRepeatY',
'webkitMaskSize',
'webkitMaxLogicalHeight',
'webkitMaxLogicalWidth',
'webkitMinLogicalHeight',
'webkitMinLogicalWidth',
'webkitPaddingAfter',
'webkitPaddingBefore',
'webkitPaddingEnd',
'webkitPaddingStart',
'webkitPerspectiveOriginX',
'webkitPerspectiveOriginY',
'webkitPrintColorAdjust',
'webkitRtlOrdering',
'webkitRubyPosition',
'webkitTapHighlightColor',
'webkitTextCombine',
'webkitTextDecorationsInEffect',
'webkitTextEmphasis',
'webkitTextEmphasisColor',
'webkitTextEmphasisPosition',
'webkitTextEmphasisStyle',
'webkitTextFillColor',
'webkitTextOrientation',
'webkitTextSecurity',
'webkitTextStroke',
'webkitTextStrokeColor',
'webkitTextStrokeWidth',
'webkitTransformOriginX',
'webkitTransformOriginY',
'webkitTransformOriginZ',
'webkitUserDrag',
'webkitUserModify',
'webkitWritingMode',
'whiteSpace',
'widows',
'width',
'willChange',
'wordBreak',
'wordSpacing',
'wordWrap',
'writingMode',
'x',
'y',
'zIndex',
'zoom'
]
SupportedEvents = ['onreadystatechange',
'onpointerlockchange',
'onpointerlockerror',
'onbeforecopy',
'onbeforecut',
'onbeforepaste',
'oncopy',
'oncut',
'onpaste',
'onsearch',
'onselectionchange',
'onselectstart',
'onwheel',
'onwebkitfullscreenchange',
'onwebkitfullscreenerror',
'onabort',
'onblur',
'oncancel',
'oncanplay',
'oncanplaythrough',
'onchange',
'onclick',
'onclose',
'oncontextmenu',
'oncuechange',
'ondblclick',
'ondrag',
'ondragend',
'ondragenter',
'ondragleave',
'ondragover',
'ondragstart',
'ondrop',
'ondurationchange',
'onemptied',
'onended',
'onerror',
'onfocus',
'oninput',
'oninvalid',
'onkeydown',
'onkeypress',
'onkeyup',
'onload',
'onloadeddata',
'onloadedmetadata',
'onloadstart',
'onmousedown',
'onmouseenter',
'onmouseleave',
'onmousemove',
'onmouseout',
'onmouseover',
'onmouseup',
'onmousewheel',
'onpause',
'onplay',
'onplaying',
'onprogress',
'onratechange',
'onreset',
'onresize',
'onscroll',
'onseeked',
'onseeking',
'onselect',
'onshow',
'onstalled',
'onsubmit',
'onsuspend',
'ontimeupdate',
'ontoggle',
'onvolumechange',
'onwaiting'
]
from functools import partial
from collections import defaultdict
from common import (
Node, Commands, Constants, MessagesQueue, StyleAttributes,
EventDOMNodeAttribute, SupportedEvents, setterproperty, index
)
__all__ = ['window', 'document', 'populateGlobalScope', 'createContainer',
'setChannel']
def tree(): return defaultdict(tree)
queue = MessagesQueue()
eventsByTypeAndTarget = tree()
connectedElementsByIndex = {}
index = 0
INLINE_EVENT = 'INLINE_EVENT'
def addToQueue(command, host, *args):
queue.push([command] + list(args) + [host])
def staticpartial(func, *args, **keywords):
return staticmethod(partial(func, *args, **keywords))
class MetaRemoteStyle(type):
def __new__(cls, name, bases, attrs):
for k in StyleAttributes:
def setter(self, val):
addToQueue(Commands.setStyle, self.host, [self._index, k, val])
self[k] = val
setter.__name__ = k
def getter(self):
return self.get(k)
getter.__name__ = k
attrs[k] = property(setter, getter)
return type.__new__(cls, name, bases, attrs)
class RemoteStyle(dict):
__metaclass__ = MetaRemoteStyle
def __init__(self, index, values=None):
self._index = index
class RemoteNodeInternal(object):
def __init__(self, nodeType, val):
self.nodeType = nodeType
self._index = id(self)
self._host = None
self.parentNode = None
self.childNodes = []
self._value = val
def __repr__(self):
return f'<{self.__class__.__name__}(nodeType={self.nodeType!r}, index:{self._index!r})>'
toString = __repr__
@property
def children(self):
return self.childNodes
@property
def firstChild(self):
return self.childNodes[0] if self.childNodes else None
@property
def nextSibling(self):
if not self.parentNode:
return None
elif not self.parentNode.childNodes:
return None
elif self not in self.parentNode.childNodes:
return None
idx = self.parentNode.childNodes.index(self)
if idx == len(self.parentNode.childNodes) - 1:
return None
return self.parentNode.childNodes[idx + 1]
@property
def prevSibling(self):
if not self.parentNode:
return None
elif not self.parentNode.childNodes:
return None
elif self not in self.parentNode.childNodes:
return None
idx = self.parentNode.childNodes.index(self)
if idx == len(self.parentNode.childNodes) - 1:
return None
return self.parentNode.childNodes[idx + 1]
@property
def owerDocument(self):
return NotImplemented
@setterproperty
def host(self, val):
if bool(val) == bool(self._host):
return
self._host = val
if val:
connectedElementsByIndex[self._index] = self
else:
del connectedElementsByIndex[self._index]
for child in self.childNodes:
child.host = val
def appendChild(self, child):
addToQueue(Commands.appendChild, self._host, [self._index, child._index])
childrenToAppend = child.childNodes \
if child.nodeType == Node.DOCUMENT_FRAGMENT_NODE else [child]
self.childNodes.extend(childrenToAppend)
for childNode in childrenToAppend:
childNode.parentNode = self
childNode.host = self._host
return child
def insertBefore(self, child, refChild=None):
if not child:
raise ValueError('No child')
try:
idx = self.childNodes.index(refChild)
except ValueError:
raise ValueError("Failed to execute 'insertBefore' on 'Node': "
"The node before which the new node is to be inserted is not "
"a child of this node.")
addToQueue(Commands.insertBefore, self._host, [self._index,
child._index, refChild._index if refChild else None ])
childreToInsert = child.childNodes \
if child.nodeType == Node.DOCUMENT_FRAGMENT_NODE else [child]
for childNode in childreToInsert:
self.childNodes.insert(idx, child)
childNode.parentNode = self
childNode.host = self._host
idx += 1
return child
def removeChild(self, child):
addToQueue(Commands.removeChild, self._host, [self._index, child._index])
if child in self.childNodes:
self.childNodes.remove(child)
child.host = None
def replaceChild(self, newChild, oldChild):
if oldChild not in self.childNodes:
raise ValueError("Faile to execute 'replaceChild' on 'Node': "
"The node to be replaced is not child of this node.")
addToQueue(Commands.replaceChild, self._host, [self._index,
newChild._index, oldChild._index])
childrenToInsert = newChild.childNodes \
if newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE else [newChild]
idx = self.childNodes.index(oldChild)
self.childNodes.remove(oldChild)
for childNode in childrenToInsert:
self.childNodes.insert(idx, childNode)
childNode.parentNode = self
childNode.host = self._host
oldChild.parentNode = None
oldChild.host = None
def addEventListener(self, evtType, callback=None, capture=False):
if callback is None:
return partial(self.addEventListener, evtType, capture=capture)
addEventListener(self._index, self._host, evtType, callback, capture)
def removeEventListener(self, eventType, callback):
if callback is None:
return partial(self.addEventListener, eventType)
removeEventListener(self._index, self._host, eventType, callback)
def dispatchEvent(self, event):
dispatchEvent(self._index, self._host, event)
@property
def value(self):
return self._value
@value.setter
def value(self, val):
self._value = val
addToQueue(Commands.setValue, self._host, [self._index, val])
@setterproperty
def textContent(self, val):
if len(self.childNodes) != 1 \
or self.childNodes[0].nodeType != Node.TEXT_NODE:
childTextNode = RemoteTextualNode(val)
childTextNode.parentNode = self
childTextNode._host = self._host
self.childNodes = [childTextNode]
addToQueue(Commands.textContent, self._host, [self._index, val,
self.childNodes[0]._index])
def invokeNative(self, name, args):
addToQueue(Commands.invokeNative, self._host, [self._index, name, args])
class MetaRemoteNode(type):
def __new__(cls, name, bases, attrs):
for evtType in SupportedEvents:
def getter(self):
return self._eventHandlers.get(evtType)
def setter(self, evtHandler):
self._eventHandlers[evtType] = evtHandler
setEventListener(self._index, self._host, evtType[2:],
evtHandler)
attrs[evtType] = property(getter, setter)
return type.__new__(cls, name, bases, attrs)
class RemoteNode(RemoteNodeInternal):
__metaclass__ = MetaRemoteNode
def __init__(self, nodeType, val=None):
super().__init__(nodeType, val)
self._eventHandlers = {}
class BaseRemoteTextualNode(RemoteNode):
def __init__(self, nodeType, text):
super().__init__(nodeType, text)
self._value = text
@property
def nodeValue(self):
return self._value
@nodeValue.setter
def nodeValue(self, val):
self._value = val
addToQueue(Commands.textContent, self._host, [self._index, val])
class RemoteTextualNode(BaseRemoteTextualNode):
def __init__(self, text):
super().__init__(Node.TEXT_NODE, text)
class RemoteComment(BaseRemoteTextualNode):
def __init__(self, text):
super().__init__(Node.TEXT_COMMENT, text)
class RemoteText(BaseRemoteTextualNode):
def __init__(self, text):
super().__init__(Node.TEXT_NODE, text)
class RemoteElement(RemoteNode):
def __init__(self, tagName):
super().__init__(Node.ELEMENT_NODE)
self.tagName = tagName.upper()
self._style = RemoteStyle(self._index)
self._attr = {}
@property
def nodeName(self):
return self.tagName
def setAttribute(self, k, v):
addToQueue(Commands.setAttribute, self._host, [self._index, k, v])
self._attr[k] = v
def removeAttribute(self, k):
addToQueue(Commands.removeAttribute, self._host, [self._index, k])
self._attr.pop(k, None)
def hasAttribute(self, k):
return k in self._attr
def focus(self):
addToQueue(Commands.focus, self._host, [self._index])
def setSelectionRange(self, selectionStart, selectionEnd, selectionDirection):
addToQueue(Commands.setSelectionRange, self._host, [self._index,
selectionStart, selectionEnd, selectionDirection])
@property
def style(self):
return self._style
@style.setter
def style(self, val):
addToQueue(Commands.setStyle, self._host, [self._index, val])
def innerHTML(self, val):
addToQueue(Commands.innerHTML, self._host, [self._index, val])
self._innerHTML = val
def innerText(self, val):
addToQueue(Commands.innerText, self._host, [self._index, val])
class RemoteContainer(RemoteElement):
def __init__(self):
super(RemoteContainer, self).__init__('div')
self._host = self._index
class RemoteFragment(RemoteNode):
def __init__(self):
super(RemoteFragment, self).__init__(Node.DOCUMENT_FRAGMENT_NODE)
class RemoteVideo(RemoteElement):
def __init__(self):
super(RemoteVideo, self).__init__('video')
self._src = None
def pause(self):
addToQueue(Commands.pause, self._host, [self._index])
def play(self):
addToQueue(Commands.play, self._host, [self._index])
@property
def src(self):
return self._src
@src.setter
def src(self, value):
self._src = value
addToQueue(Commands.src, self._host, [self._index, value])
class RemoteImage(RemoteElement):
def __init__(self):
super(RemoteImage, self).__init__('img')
self._src = None
@property
def src(self):
return self._src
@src.setter
def src(self, value):
self._src = value
addToQueue(Commands.src, self._host, [self._index, value])
class RemoteInput(RemoteElement):
def __init__(self):
super(RemoteInput, self).__init__('input')
@property
def value(self):
return self._value
@value.setter
def value(self, val):
self._value = val
addToQueue(Commands.setValue, self._host, [self._index, val])
class RemoteSelect(RemoteElement):
def __init__(self):
super(RemoteSelect, self).__init__('select')
@property
def options(self):
return filter(lambda c: c.tagName.lower() == 'option', self.childNodes)
def createElement(nodeName):
if nodeName == 'video':
res = RemoteVideo()
elif nodeName == 'img':
res = RemoteImage()
elif nodeName == 'input':
res = RemoteInput()
elif nodeName == 'select':
res = RemoteSelect()
else:
res = RemoteElement(nodeName)
addToQueue(Commands.createElement, res._host, [res._index, res.tagName])
return res
def createTextNode(text):
res = RemoteText(text)
addToQueue(Commands.createTextNode, res._host, [res._index, text])
return res
def createComment(text):
res = RemoteFragment()
addToQueue(Commands.createDocumentFragment, res._host, [res._index])
return res
def createDocumentFragment():
res = RemoteFragment()
addToQueue(Commands.createDocumentFragment, res._host, [res._index])
return res
def createContainer(name):
name = name or Constants.DEFAULT_NAME
res = RemoteContainer()
connectedElementsByIndex[res._index] = res
addToQueue(Commands.createContainer, res._host, [res._index, name])
return res
def addEventListener(target, host, evtName, callback, capture):
index += 1
eventsByTypeAndTarget[evtName][target][index] = callback
addToQueue(Commands.addEventListener, host, [target, evtName, index, capture])
def removeEventListener(target, host, evtName, callback):
evts = eventsByTypeAndTarget[evtName][target]
for k, v in evts.items():
if v == callback:
idx = k
break
del evts[idx]
addToQueue(Commands.removeEventListener, host, [target, evtName, index])
def setEventListener(target, host, evtName, evtHandler):
if evtHandler and INLINE_EVENT not in eventsByTypeAndTarget[evtName][target]:
addToQueue(Commands.addEventListener, host, [target, evtName,
INLINE_EVENT, False])
elif not evtHandler and INLINE_EVENT in eventsByTypeAndTarget[evtName][target]:
addToQueue(Commands.removeEventListener, host, [target, evtName, INLINE_EVENT])
eventsByTypeAndTarget[evtName][target][INLINE_EVENT] = evtHandler
def dispatchEvent(target, host, event):
addToQueue(Commands.dispatchEvent, host, [target, event._type,
event._eventInit, getattr(event, '_isCustom', False)])
def updateConnectedElement(index, eventData):
if index in connectedElementsByIndex and index in eventData:
connectedElementsByIndex[index].update(eventData[index])
def handleMessagesFromPipe(messages):
for msg in messages:
evtIntent = msg[0]
if evtIntent == Constants.INIT:
#InitMsgPromiseResolver and initMsgPromiseResolver()
eventData = msg[1]
for index in eventData:
updateConnectedElement(index, eventData)
else:
evtTarget = msg[1]
evtName = msg[2]
evtJSON = msg[3]
if 'extraData' in evtJSON:
for index in evtJSON['extraData']:
updateConnectedElement(index, evtJSON['extraData'])
for field in EventDOMNodeAttribute:
if field in evtJSON:
if isinstance(evtJSON[field], list):
evtJSON[field] = map(
lambda val: connectedElementsByIndex[val],
evtJSON[field])
else:
evtJSON[field] = connectedElementsByIndex[evtJSON[field]]
if evtName in eventsByTypeAndTarget \
and evtTarget in eventsByTypeAndTarget[evtName]:
for callback in eventsByTypeAndTarget[evtName][evtTarget].values():
callback(evtJSON)
def setChannel(channel, timerFunction=None):
#initMsgPromise = Promise(lambda resolve: globals().update({'initMsgPromiseResolver': resolve}))
queue.setPipe(channel, handleMessagesFromPipe, timerFunction)
queue.push([Commands.initiated])
#return initMsgPromise
def Image(width, height):
imgNode = document.createElement('img')
imgNode.setAttribute('width', width)
imgNode.setAttribute('height', height)
return imgNode
def populateGlobalScope(scope):
scope.window = window
scope.document = document
scope.requestAnimationFrame = window.requestAnimationFrame
scope.Image = Image
document = type('document', (object,), dict(list(dict({
'createElement': staticmethod(createElement),
'createTextNode': staticmethod(createTextNode),
'createComment': staticmethod(createComment),
'createDocumentFragment': staticmethod(createDocumentFragment),
'addEventListener': staticpartial(
addEventListener, target=Constants.DOCUMENT, host=None),
'removeEventListener': staticpartial(
removeEventListener, target=Constants.DOCUMENT, host=None),
'documentElement': RemoteElement('html'),
'dispatchEvent': staticpartial(
dispatchEvent, target=Constants.DOCUMENT, host=None)
}).items()) + [
(k, staticpartial(
addEventListener, target=Constants.DOCUMENT,
host=None, evtName=k[2:]))
for k in SupportedEvents])
)
class Event(object):
def __init__(self, typeArg, eventInit):
self._type = typeArg
self._eventInit = eventInit
class CustomEvent(Event):
def __init__(self, typeArg, eventInit):
super(CustomEvent, self).__init__(typeArg, eventInit)
self._isCustom = True
window = type('window', (object,), {
'Event': Event,
'CustomEvent': CustomEvent,
'dispatchEvent': staticpartial(
dispatchEvent, target=Constants.WINDOW, host=None),
'addEventListener': staticpartial(
dispatchEvent, target=Constants.WINDOW, host=None),
'removeEventListener': staticpartial(
removeEventListener, target=Constants.WINDOW, host=None),
'document': document,
'location': type('location', (object,), {
'href': 'https://localhost',
'protocol': 'https:'
}),
'navigator': type('navigator', (object,), {
'userAgent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36',
}),
'history': {},
#'requestAnimationFrame': setTimeout,
#'cancelAnimationFrame': clearTimeout,
'Image': Image,
'top': None
})
window.top = window
connectedElementsByIndex[Constants.WINDOW] = window
connectedElementsByIndex[Constants.DOCUMENT] = document
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment