js,jsdom: make JS structs plain objects; add more DOM API support for jsdom (#12501)

pull/12507/head
playX 2021-11-18 11:09:53 +03:00 committed by GitHub
parent 409321327b
commit 1edb3e559e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 291 additions and 13 deletions

View File

@ -131,8 +131,270 @@ mut:
y JS.Number
}
[use_new]
pub fn JS.DOMRect.prototype.constructor(x JS.Number, y JS.Number, width JS.Number, height JS.Number) JS.DOMRect
pub interface JS.DOMStringList {
length JS.Number
contains(JS.String) JS.Boolean
item(index JS.Number) ?JS.String
}
pub interface JS.DOMRectList {
length JS.Number
contains(JS.String) JS.Boolean
item(index JS.Number) ?JS.Rect
}
pub type DOMTokenListForEachCb = fn (JS.String, JS.Number, JS.DOMTokenList)
pub interface JS.DOMTokenList {
length JS.Number
toString() JS.String
add(tokens ...JS.Any) ?JS.Any
contains(token JS.String) JS.Boolean
item(index JS.Number) ?JS.String
remove(tokens ...JS.Any) ?JS.Any
replace(token JS.String, newToken JS.String) JS.Boolean
supports(token JS.String) JS.Boolean
toggle(token JS.String, force JS.Boolean) JS.Boolean
forEach(cb DOMTokenListForEachCb, thisArg JS.Any)
mut:
value JS.String
}
pub struct JS.EventListenerOptions {
capture bool
}
pub interface JS.EventTarget {
addEventListener(cb EventCallback, options JS.EventListenerOptions)
dispatchEvent(event JS.Event) JS.Boolean
removeEventListener(cb EventCallback, options JS.EventListenerOptions)
}
// Event is an event which takes place in the DOM.
pub interface JS.Event {
JS.EventTarget
bubbles JS.Boolean
cancelable JS.Boolean
composed JS.Boolean
currentTarget JS.EventTarget
defaultPrevented JS.Boolean
eventPhase JS.Number
isTrusted JS.Boolean
srcElement JS.EventTarget
timeStamp JS.DOMHighResTimeStamp // composedPath returns the invocation target objects of event's path.
composedPath() JS.Array
initEvent(typ JS.String, bubbles JS.Boolean, cancelable JS.Boolean)
preventDefault()
stopImmediatePropagation()
stopPropagation()
mut:
returnValue JS.Boolean
}
pub fn event_type(ev JS.Event) string {
res := ''
#res.str = ev.type;
return res
}
pub fn create_event(typ string, bubbles bool, cancelable bool, composed bool) JS.Event {
mut ev := JS.Event(voidptr(0))
#ev = new Event(typ.str,bubbles.val,cancelable.val,composed.val);
return ev
}
pub interface JS.UIEvent {
JS.Event
detail JS.Number
view JS.Any
}
[use_new]
pub fn JS.UIEvent.prototype.constructor(typ JS.String, dict JS.UIEventDict) JS.UIEvent
pub struct JS.EventInit {
bubbles JS.Boolean
cancelable JS.Boolean
composed JS.Boolean
}
pub struct JS.UIEventInitDict {
bubbles JS.Boolean
cancelable JS.Boolean
composed JS.Boolean
detail JS.Number
view JS.Any
which JS.Number
}
pub interface JS.MouseEvent {
JS.UIEvent
altKey JS.Boolean
button JS.Number
buttons JS.Number
clientX JS.Number
clientY JS.Number
ctrlKey JS.Number
metaKey JS.Number
movementX JS.Number
movementY JS.Number
offsetX JS.Number
offsetY JS.Number
pageX JS.Number
pageY JS.Number
relatedTarget JS.Any
screenX JS.Number
screenY JS.Number
shiftKey JS.Boolean
x JS.Number
y JS.Number
getModifierState(keyArg JS.String) JS.Boolean
}
pub interface JS.Node {
JS.EventTarget
baseURI JS.String
childNodes JS.Any
firstChild JS.ChildNode
isConnected JS.Boolean
lastChild JS.ChildNode
nextSibling JS.ChildNode
nodeName JS.String
nodeType JS.Number
ownerDocument JS.Document
parentElement JS.HTMLElement
parentNode JS.ParentNode
previousSibling JS.ChildNode
appendChild(node JS.Node) JS.Node
cloneNode(deep JS.Boolean) JS.Node
compareDocumentPosition(other JS.Node) JS.Number
contains(other JS.Node) JS.Boolean
getRootNode(composed JS.Boolean) JS.Node
hasChildNodes() JS.Boolean
insertBefore(node JS.Node, child JS.Node) JS.Node
isEqualNode(otherNode JS.Node) JS.Boolean
isSameNode(otherNode JS.Node) JS.Boolean
lookupPrefix(namespace JS.String) JS.String
normalize()
removeChild(child JS.Node) JS.Node
replaceChild(node JS.Node, child JS.Node) JS.Npde
mut:
nodeValue JS.String
textContent JS.String
}
pub interface JS.ChildNode {
JS.Node
after(nodes ...JS.Any)
before(nodes ...JS.Any)
remove()
replaceWith(nodes ...JS.Any)
}
pub interface JS.ParentNode {
JS.Node
childElementCount JS.Number
children JS.HTMLCollection
}
pub interface JS.Document {
JS.Node
all JS.HTMLAllCollection
anchros JS.HTMLCollection
applets JS.HTMLCollection
characterSet JS.String
charset JS.String
compatMode JS.String
contentType JS.String
documentURI JS.String
documentElement JS.HTMLElement
hidden JS.Boolean
head JS.HTMLHeadElement
fullscreenEnabled JS.Boolean
fullscreen JS.Boolean
lastModified JS.String
inputEncoding JS.String
implementation JS.DOMImplementation
mut:
bgColor JS.String
body JS.HTMLElement
cookie JS.String
domain JS.String
}
pub interface JS.PointerEvent {
JS.MouseEvent
height JS.Number
isPrimary JS.Boolean
pointerId JS.Number
pointerType JS.String
pressure JS.Number
tangentialPressure JS.Number
tiltX JS.Number
tiltY JS.Number
twist JS.Number
width JS.Number
getCoalescedEvents() JS.Array
getPredictedEvents() JS.Array
}
pub interface JS.Element {
JS.Node
classList JS.DOMTokenList
clientHeight JS.Number
clientLeft JS.Number
clientTop JS.Number
clientWidth JS.Number
localName JS.String
namespaceURI JS.String
ownerDocument JS.Document
part JS.DOMTokenList
prefix JS.String
scrollHeight JS.Number
scrollWidth JS.Number
tagName JS.String
closest(selector JS.String) ?JS.Element
getAttribute(qualifiedName JS.String) ?JS.String
getAttributeNS(namespace JS.String, localName JS.String) ?JS.String
getAttributeNames() JS.Array
getClientRects() JS.DOMRectList
getBoundingClientRect() JS.DOMRect
scrollTo(x JS.Number, y JS.Number)
scroll(x JS.Number, y JS.Number)
scrollBy(x JS.Number, y JS.Number)
toggleAttribute(qualifiedName JS.String, force JS.Boolean) JS.Boolean
mut:
className JS.String
id JS.String
onfullscreenchange fn (this JS.Element, ev JS.Event) JS.Any
onfullscreenerror fn (this JS.Element, ev JS.Event) JS.Any
outerHTML JS.String
scrollLeft JS.Number
scrollTop JS.Number
slot JS.String
}
pub const (
document = JS.Document{}
)
fn init() {
#jsdom__document = document;
}
pub type EventCallback = fn (JS.Event)
// event_listener returns proper listener callback. This function is useful when you need access to `this` value
// that is EventTarget. When you need access only to Event itself you can just use `fn (JS.Event)` as listener.
pub fn event_listener(callback fn (JS.EventTarget, JS.Event)) EventCallback {
return fn [callback] (event JS.Event) {
mut target := JS.EventTarget(voidptr(0))
#target = this;
callback(target, event)
}
}

View File

@ -567,8 +567,9 @@ pub fn (mut c Checker) interface_decl(mut node ast.InterfaceDecl) {
c.ensure_type_exists(param.typ, param.pos) or { return }
if is_js {
ptyp := c.table.get_type_symbol(param.typ)
if ptyp.kind != .function && ptyp.language != .js
&& !ptyp.name.starts_with('JS.') {
if (ptyp.kind != .function && ptyp.language != .js
&& !ptyp.name.starts_with('JS.')) && !(j == method.params.len - 1
&& method.is_variadic) {
c.error('method `$method.name` accepts non JS type as parameter',
method.pos)
}
@ -594,7 +595,7 @@ pub fn (mut c Checker) interface_decl(mut node ast.InterfaceDecl) {
c.ensure_type_exists(field.typ, field.pos) or { return }
if is_js {
tsym := c.table.get_type_symbol(field.typ)
if tsym.language != .js && !tsym.name.starts_with('JS.') {
if tsym.language != .js && !tsym.name.starts_with('JS.') && tsym.kind != .function {
c.error('field `$field.name` uses non JS type', field.pos)
}
}
@ -3153,8 +3154,8 @@ fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos to
// `none` "implements" the Error interface
return true
}
if typ_sym.kind == .interface_ && inter_sym.kind == .interface_ && styp != 'JS.Any'
&& inter_sym.name != 'JS.Any' {
if typ_sym.kind == .interface_ && inter_sym.kind == .interface_ && !styp.starts_with('JS.')
&& !inter_sym.name.starts_with('JS.') {
c.error('cannot implement interface `$inter_sym.name` with a different interface `$styp`',
pos)
}

View File

@ -85,7 +85,7 @@ fn (mut g JsGen) js_call(node ast.CallExpr) {
g.write(')')
if call_return_is_optional {
g.write(';\n')
g.writeln('if (tmp === null || tmp === undefined) throw "none";')
g.writeln('if (tmp === null) throw "none";')
g.writeln('return tmp;')
g.writeln('} catch(err) {')
g.inc_indent()
@ -140,7 +140,7 @@ fn (mut g JsGen) js_method_call(node ast.CallExpr) {
g.write(')')
if call_return_is_optional {
g.write(';\n')
g.writeln('if (tmp === null || tmp === undefined) throw "none";')
g.writeln('if (tmp === null) throw "none";')
g.writeln('return tmp;')
g.writeln('} catch(err) {')
g.inc_indent()

View File

@ -3091,9 +3091,12 @@ fn (mut g JsGen) gen_selector_expr(it ast.SelectorExpr) {
}
g.expr(it.expr)
mut ltyp := it.expr_type
for ltyp.is_ptr() {
g.write('.val')
ltyp = ltyp.deref()
lsym := g.table.get_type_symbol(ltyp)
if lsym.kind != .interface_ && lsym.language != .js {
for ltyp.is_ptr() {
g.write('.val')
ltyp = ltyp.deref()
}
}
g.write('.$it.field_name')
}
@ -3159,7 +3162,11 @@ fn (mut g JsGen) gen_struct_init(it ast.StructInit) {
type_sym := g.table.get_type_symbol(it.typ)
name := type_sym.name
if it.fields.len == 0 && type_sym.kind != .interface_ {
g.write('new ${g.js_name(name)}({})')
if type_sym.kind == .struct_ && type_sym.language == .js {
g.write('{}')
} else {
g.write('new ${g.js_name(name)}({})')
}
} else if it.fields.len == 0 && type_sym.kind == .interface_ {
g.write('new ${g.js_name(name)}()') // JS interfaces can be instantiated with default ctor
} else if type_sym.kind == .interface_ && it.fields.len != 0 {
@ -3176,7 +3183,11 @@ fn (mut g JsGen) gen_struct_init(it ast.StructInit) {
g.dec_indent()
g.writeln('})()')
} else {
g.writeln('new ${g.js_name(name)}({')
if type_sym.kind == .struct_ && type_sym.language == .js {
g.writeln('{')
} else {
g.writeln('new ${g.js_name(name)}({')
}
g.inc_indent()
for i, field in it.fields {
g.write('$field.name: ')
@ -3187,7 +3198,11 @@ fn (mut g JsGen) gen_struct_init(it ast.StructInit) {
g.writeln('')
}
g.dec_indent()
g.write('})')
if type_sym.kind == .struct_ && type_sym.language == .js {
g.writeln('}')
} else {
g.writeln('})')
}
}
}