From 1edb3e559e7dbb0407b1667c95f93505cfb715f8 Mon Sep 17 00:00:00 2001 From: playX Date: Thu, 18 Nov 2021 11:09:53 +0300 Subject: [PATCH] js,jsdom: make JS structs plain objects; add more DOM API support for jsdom (#12501) --- vlib/jsdom/jsdom.js.v | 262 +++++++++++++++++++++++++++++++++++++++ vlib/v/checker/checker.v | 11 +- vlib/v/gen/js/fn.v | 4 +- vlib/v/gen/js/js.v | 27 +++- 4 files changed, 291 insertions(+), 13 deletions(-) diff --git a/vlib/jsdom/jsdom.js.v b/vlib/jsdom/jsdom.js.v index 4d9f1825ae..06c2d5483f 100644 --- a/vlib/jsdom/jsdom.js.v +++ b/vlib/jsdom/jsdom.js.v @@ -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) + } +} diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 27521b4dbd..947ca95640 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -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) } diff --git a/vlib/v/gen/js/fn.v b/vlib/v/gen/js/fn.v index 264450305a..5a8996ab60 100644 --- a/vlib/v/gen/js/fn.v +++ b/vlib/v/gen/js/fn.v @@ -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() diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 4574da2059..ecb19f200d 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -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('})') + } } }